diff --git a/package-lock.json b/package-lock.json index eec780a..a0c8b70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,6 @@ }, "devDependencies": { "@tailwindcss/typography": "^0.5.9", - "@unhead/schema-org": "^0.5.0", "@unhead/ssr": "^1.1.26", "@vitejs/plugin-vue": "^4.2.3", "autoprefixer": "^10.4.14", @@ -609,18 +608,6 @@ "url": "https://github.com/sponsors/harlan-zw" } }, - "node_modules/@unhead/schema-org": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@unhead/schema-org/-/schema-org-0.5.0.tgz", - "integrity": "sha512-UrO/wUFzGnRjUAYmkEZmgtknTjn9wPHwtXnEIgCxYcTDwOr9fiU/kFYTKBf6OF1VhRWrFAVNTz/fIzLZ1f/Hdw==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/harlan-zw" - }, - "peerDependencies": { - "unhead": ">=1.1.9" - } - }, "node_modules/@unhead/shared": { "version": "1.1.27", "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.1.27.tgz", diff --git a/package.json b/package.json index f15e001..14e7f00 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ }, "devDependencies": { "@tailwindcss/typography": "^0.5.9", - "@unhead/schema-org": "^0.5.0", "@unhead/ssr": "^1.1.26", "@vitejs/plugin-vue": "^4.2.3", "autoprefixer": "^10.4.14", diff --git a/server.js b/server.js index be1cf73..6b8cc10 100644 --- a/server.js +++ b/server.js @@ -63,6 +63,7 @@ export async function createServer( app.use('*', async (req, res) => { try { const url = req.originalUrl + const origin = req.protocol + '://' + req.get('host') let template, render if (!isProd) { @@ -77,7 +78,8 @@ export async function createServer( const [appHtml, appState, preloadLinks, tagMeta] = await render( url, - manifest + manifest, + origin ) const html = template diff --git a/src/App.vue b/src/App.vue index 4b090ad..bb29998 100644 --- a/src/App.vue +++ b/src/App.vue @@ -6,13 +6,10 @@ diff --git a/src/entry-client.js b/src/entry-client.js index 138948d..a4c46e7 100644 --- a/src/entry-client.js +++ b/src/entry-client.js @@ -2,8 +2,9 @@ import { createApp } from './main' import { registerOnlyClientComponents } from '@/components/clientComponents' import { registerGlobalComponents } from './components/globalComponents' -const { app, router, store } = createApp() +const originUrl = window.location.origin const storeInitialState = window.initialState +const { app, router, store } = createApp({ originUrl }) if (storeInitialState) { store.state.value = storeInitialState diff --git a/src/entry-server.js b/src/entry-server.js index f695cee..f2e99b9 100644 --- a/src/entry-server.js +++ b/src/entry-server.js @@ -7,8 +7,8 @@ import { renderSSRHead } from '@unhead/ssr' import { registerFakeClientComponents } from '@/ssr/utils/registerFakeClientComponents' import devalue from '@nuxt/devalue' -export async function render(url, manifest) { - const { app, router, store } = createApp() +export async function render(url, manifest, originUrl) { + const { app, router, store } = createApp({ originUrl }) // Register global components, create a fake server components for components that should only render on client side registerGlobalComponents(app) diff --git a/src/main.js b/src/main.js index 7ee3302..d2abe9b 100644 --- a/src/main.js +++ b/src/main.js @@ -2,29 +2,33 @@ import.meta.glob('@/assets/css/main.css', { eager: true }) import.meta.glob('../config/style/*.{scss,css}', { eager: true }) import App from './App.vue' -import SetupApp from './modules/setup/views/Index.vue' -import { SchemaOrgUnheadPlugin } from '@unhead/schema-org' +import SetupApp from '@/modules/setup/views/Index.vue' +import { schemaOrgPlugin } from '@/plugins/schemaOrg' import { createHead } from 'unhead' import { createPinia } from 'pinia' import { createSSRApp } from 'vue' import { createRouter } from './router' -export function createApp() { - const { url, project_token, schema_host } = __APP_ENV__ +export function createApp({ originUrl }) { + const { url, project_token } = __APP_ENV__ const isAPIConfigurationSet = url && project_token const app = createSSRApp(isAPIConfigurationSet ? App : SetupApp) const router = createRouter() const store = createPinia() const head = createHead({ plugins: [ - SchemaOrgUnheadPlugin({ host: schema_host }, () => { - const { meta, path } = router.currentRoute.value - - return { - ...meta, - path + schemaOrgPlugin( + { + host: originUrl + }, + () => { + const route = router.currentRoute.value + return { + path: route.path, + ...route.meta + } } - }) + ) ] }) diff --git a/src/modules/otus/views/Index.vue b/src/modules/otus/views/Index.vue index 4aa025e..84db824 100644 --- a/src/modules/otus/views/Index.vue +++ b/src/modules/otus/views/Index.vue @@ -74,8 +74,7 @@ import { ref, watch, onServerPrefetch, computed, onMounted } from 'vue' import { useRoute, useRouter } from 'vue-router' import { useOtuStore } from '../store/store' import { useHead } from 'unhead' -import { useSchemaOrg } from '@unhead/schema-org' -import { defineTaxon } from '../helpers/schema.js' +import { useSchemaOrg, defineTaxon } from '@/plugins/schemaOrg/composables' import Breadcrumb from '../components/Breadcrumb/Breadcrumb.vue' import TaxaInfo from '../components/TaxaInfo.vue' import DWCDownload from '../components/DWCDownload.vue' @@ -123,6 +122,10 @@ async function loadInitialData() { } function updateMetadata() { + useHead({ + title: `${__APP_ENV__.project_name} - ${taxon.value.full_name}` + }) + useSchemaOrg([ defineTaxon({ id: route.fullPath, @@ -140,10 +143,6 @@ function updateMetadata() { alternateName: store.taxonomy.synonyms }) ]) - - useHead({ - title: `${__APP_ENV__.project_name} - ${taxon.value.full_name}` - }) } function loadOtu({ id, otu_valid_id }) { diff --git a/src/plugins/schemaOrg/composables/index.js b/src/plugins/schemaOrg/composables/index.js new file mode 100644 index 0000000..ac70896 --- /dev/null +++ b/src/plugins/schemaOrg/composables/index.js @@ -0,0 +1,25 @@ +import { useHead } from 'unhead' + +function provideResolver(input, resolver) { + if (!input) input = {} + + input._resolver = resolver + + return input +} + +export function defineTaxon(input) { + return provideResolver(input, 'taxon') +} + +export function useSchemaOrg(nodes) { + return useHead({ + script: [ + { + type: 'application/ld+json', + key: 'schema-org-graph', + nodes + } + ] + }) +} diff --git a/src/plugins/schemaOrg/index.js b/src/plugins/schemaOrg/index.js new file mode 100644 index 0000000..49984b6 --- /dev/null +++ b/src/plugins/schemaOrg/index.js @@ -0,0 +1,31 @@ +import { loadResolver } from './loadResolver' + +function transformToSchemaNode(node, { host }) { + const nodeResolver = loadResolver(node._resolver) + + return nodeResolver(node, { host }) +} + +export function schemaOrgPlugin({ host }) { + return { + hooks: { + 'tags:resolve': async function (ctx) { + for (const tag of ctx.tags) { + if (tag.tag === 'script' && tag.key === 'schema-org-graph') { + tag.innerHTML = JSON.stringify( + { + '@context': 'https://schema.org', + '@graph': tag.props.nodes.map((node) => { + return transformToSchemaNode(node, { host }) + }) + }, + null, + 2 + ) + delete tag.props.nodes + } + } + } + } + } +} diff --git a/src/plugins/schemaOrg/loadResolver.js b/src/plugins/schemaOrg/loadResolver.js new file mode 100644 index 0000000..4f60222 --- /dev/null +++ b/src/plugins/schemaOrg/loadResolver.js @@ -0,0 +1,10 @@ +import { taxonResolver } from './nodes' + +export function loadResolver(resolver) { + switch (resolver) { + case 'taxon': + return taxonResolver + default: + return () => ({}) + } +} diff --git a/src/modules/otus/helpers/schema.js b/src/plugins/schemaOrg/nodes/Taxon.js similarity index 68% rename from src/modules/otus/helpers/schema.js rename to src/plugins/schemaOrg/nodes/Taxon.js index c7b4273..0789ced 100644 --- a/src/modules/otus/helpers/schema.js +++ b/src/plugins/schemaOrg/nodes/Taxon.js @@ -1,17 +1,3 @@ -const { schema_host } = __APP_ENV__ - -const TAXON_RANK = { - species: [ - 'http://rs.tdwg.org/ontology/voc/TaxonRank#Species', - 'http://www.wikidata.org/entity/Q7432' - ], - genus: [ - 'genus', - 'http://rs.tdwg.org/ontology/voc/TaxonRank#Genus', - 'http://www.wikidata.org/entity/Q34740' - ] -} - function removeEmptyProperties(obj) { const copyObj = { ...obj } @@ -26,24 +12,27 @@ function removeEmptyProperties(obj) { return copyObj } -function makeUrlPath(path) { - return schema_host ? `${schema_host}${path}` : '' +function makeUrlPath(host, path) { + return host && path ? `${host}${path}` : '' } -export function defineTaxon({ - id, - childTaxon, - parentTaxon, - taxonRank, - name, - scientificName, - identifier, - commonNames, - alternateName -}) { +export function taxonResolver( + { + id, + childTaxon, + parentTaxon, + taxonRank, + name, + scientificName, + identifier, + commonNames, + alternateName + }, + { host } +) { return removeEmptyProperties({ '@type': 'Taxon', - '@id': makeUrlPath(id), + '@id': makeUrlPath(host, id), 'http://purl.org/dc/terms/conformsTo': { '@id': 'https://bioschemas.org/profiles/Taxon/1.0-RELEASE' }, diff --git a/src/plugins/schemaOrg/nodes/index.js b/src/plugins/schemaOrg/nodes/index.js new file mode 100644 index 0000000..9bde594 --- /dev/null +++ b/src/plugins/schemaOrg/nodes/index.js @@ -0,0 +1 @@ +export * from './Taxon' diff --git a/src/utils/getMeta.js b/src/utils/getMeta.js deleted file mode 100644 index b927d21..0000000 --- a/src/utils/getMeta.js +++ /dev/null @@ -1,14 +0,0 @@ -export function getMetaFromConfig(configObj) { - const metaObj = Object.entries(configObj).filter(([key]) => - key.startsWith('meta_') - ) - - return metaObj.map(([key, value]) => { - const metaKey = key.slice(5) - - return { - name: metaKey, - content: value - } - }) -}