diff --git a/README.md b/README.md index 0d98ad1..e470be6 100644 --- a/README.md +++ b/README.md @@ -458,6 +458,30 @@ Each item in the array maps to a newly-created `` element, where object pr ``` +Since v1.5.0, you can now set up meta templates that work similar to the titleTemplate: + +```js +{ + metaInfo: { + meta: [ + { charset: 'utf-8' }, + { + 'vmid': 'og:title', + 'property': 'og:title', + 'content': 'Test title', + 'template': chunk => `${chunk} - My page` //or as string template: '%s - My page' + } + ] + } +} +``` + +```html + + +``` + + #### `link` ([Object]) Each item in the array maps to a newly-created `` element, where object properties map to attributes. diff --git a/src/shared/constants.js b/src/shared/constants.js index 7bd5c55..0165fe4 100644 --- a/src/shared/constants.js +++ b/src/shared/constants.js @@ -19,3 +19,6 @@ export const VUE_META_SERVER_RENDERED_ATTRIBUTE = 'data-vue-meta-server-rendered // that both have `vmid` of "description", then vue-meta will overwrite the // shallowest one with the deepest one. export const VUE_META_TAG_LIST_ID_KEY_NAME = 'vmid' + +// This is the key name for possible meta templates +export const VUE_META_TEMPLATE_KEY_NAME = 'template' diff --git a/src/shared/getComponentOption.js b/src/shared/getComponentOption.js index 37d3193..ae76e2b 100644 --- a/src/shared/getComponentOption.js +++ b/src/shared/getComponentOption.js @@ -15,7 +15,7 @@ import deepmerge from 'deepmerge' * @return {Object} result - final aggregated result */ export default function getComponentOption (opts, result = {}) { - const { component, option, deep, arrayMerge } = opts + const { component, option, deep, arrayMerge, metaTemplateKeyName } = opts const { $options } = component if (component._inactive) return result @@ -48,6 +48,22 @@ export default function getComponentOption (opts, result = {}) { }, result) }) } + if (metaTemplateKeyName && result.hasOwnProperty('meta')) { + result.meta = Object.keys(result.meta).map(metaKey => { + const metaObject = result.meta[metaKey] + if (!metaObject.hasOwnProperty(metaTemplateKeyName) || !metaObject.hasOwnProperty('content') || typeof metaObject[metaTemplateKeyName] === 'undefined') { + return result.meta[metaKey] + } + const template = metaObject[metaTemplateKeyName] + delete metaObject[metaTemplateKeyName] + + if (template) { + metaObject.content = typeof template === 'function' ? template(metaObject.content) : template.replace(/%s/g, metaObject.content) + } + + return metaObject + }) + } return result } diff --git a/src/shared/getMetaInfo.js b/src/shared/getMetaInfo.js index 8774487..c8203fe 100644 --- a/src/shared/getMetaInfo.js +++ b/src/shared/getMetaInfo.js @@ -20,7 +20,7 @@ const escapeHTML = (str) => typeof window === 'undefined' .replace(/'/g, '\u0027') export default function _getMetaInfo (options = {}) { - const { keyName, tagIDKeyName } = options + const { keyName, tagIDKeyName, metaTemplateKeyName } = options /** * Returns the correct meta info for the given component * (child components will overwrite parent meta info) @@ -52,6 +52,7 @@ export default function _getMetaInfo (options = {}) { component, option: keyName, deep: true, + metaTemplateKeyName, arrayMerge (target, source) { // we concat the arrays without merging objects contained in, // but we check for a `vmid` property on each object in the array diff --git a/src/shared/plugin.js b/src/shared/plugin.js index 1649348..86f5caa 100644 --- a/src/shared/plugin.js +++ b/src/shared/plugin.js @@ -6,7 +6,8 @@ import { VUE_META_KEY_NAME, VUE_META_ATTRIBUTE, VUE_META_SERVER_RENDERED_ATTRIBUTE, - VUE_META_TAG_LIST_ID_KEY_NAME + VUE_META_TAG_LIST_ID_KEY_NAME, + VUE_META_TEMPLATE_KEY_NAME } from './constants' // automatic install @@ -22,6 +23,7 @@ export default function VueMeta (Vue, options = {}) { // set some default options const defaultOptions = { keyName: VUE_META_KEY_NAME, + metaTemplateKeyName: VUE_META_TEMPLATE_KEY_NAME, attribute: VUE_META_ATTRIBUTE, ssrAttribute: VUE_META_SERVER_RENDERED_ATTRIBUTE, tagIDKeyName: VUE_META_TAG_LIST_ID_KEY_NAME diff --git a/test/getMetaInfo.spec.js b/test/getMetaInfo.spec.js index acf1c09..3f45aab 100644 --- a/test/getMetaInfo.spec.js +++ b/test/getMetaInfo.spec.js @@ -1,10 +1,11 @@ import Vue from 'vue' import _getMetaInfo from '../src/shared/getMetaInfo' import { - VUE_META_KEY_NAME, VUE_META_ATTRIBUTE, + VUE_META_KEY_NAME, VUE_META_SERVER_RENDERED_ATTRIBUTE, - VUE_META_TAG_LIST_ID_KEY_NAME + VUE_META_TAG_LIST_ID_KEY_NAME, + VUE_META_TEMPLATE_KEY_NAME } from '../src/shared/constants' // set some default options @@ -12,6 +13,7 @@ const defaultOptions = { keyName: VUE_META_KEY_NAME, attribute: VUE_META_ATTRIBUTE, ssrAttribute: VUE_META_SERVER_RENDERED_ATTRIBUTE, + metaTemplateKeyName: VUE_META_TEMPLATE_KEY_NAME, tagIDKeyName: VUE_META_TAG_LIST_ID_KEY_NAME } @@ -108,7 +110,7 @@ describe('getMetaInfo', () => { component = new Vue({ metaInfo: { title: 'Hello', - titleTemplate: titleTemplate, + titleTemplate, meta: [ { charset: 'utf-8' } ] @@ -117,7 +119,7 @@ describe('getMetaInfo', () => { expect(getMetaInfo(component)).to.eql({ title: 'Hello Function World', titleChunk: 'Hello', - titleTemplate: titleTemplate, + titleTemplate, htmlAttrs: {}, headAttrs: {}, bodyAttrs: {}, @@ -142,7 +144,7 @@ describe('getMetaInfo', () => { component = new Vue({ metaInfo: { title: 'Hello', - titleTemplate: titleTemplate, + titleTemplate, meta: [ { charset: 'utf-8' } ] @@ -156,7 +158,7 @@ describe('getMetaInfo', () => { expect(getMetaInfo(component)).to.eql({ title: 'Hello Function World', titleChunk: 'Hello', - titleTemplate: titleTemplate, + titleTemplate, htmlAttrs: {}, headAttrs: {}, bodyAttrs: {}, @@ -172,4 +174,193 @@ describe('getMetaInfo', () => { __dangerouslyDisableSanitizersByTagID: {} }) }) + + it('properly uses string meta templates', () => { + component = new Vue({ + metaInfo: { + title: 'Hello', + meta: [ + { + vmid: 'og:title', + property: 'og:title', + content: 'Test title', + template: '%s - My page' + } + ] + } + }) + expect(getMetaInfo(component)).to.eql({ + title: 'Hello', + titleChunk: 'Hello', + titleTemplate: '%s', + htmlAttrs: {}, + headAttrs: {}, + bodyAttrs: {}, + meta: [ + { + vmid: 'og:title', + property: 'og:title', + content: 'Test title - My page' + } + ], + base: [], + link: [], + style: [], + script: [], + noscript: [], + __dangerouslyDisableSanitizers: [], + __dangerouslyDisableSanitizersByTagID: {} + }) + }) + + it('properly uses function meta templates', () => { + component = new Vue({ + metaInfo: { + title: 'Hello', + meta: [ + { + vmid: 'og:title', + property: 'og:title', + content: 'Test title', + template: chunk => `${chunk} - My page` + } + ] + } + }) + expect(getMetaInfo(component)).to.eql({ + title: 'Hello', + titleChunk: 'Hello', + titleTemplate: '%s', + htmlAttrs: {}, + headAttrs: {}, + bodyAttrs: {}, + meta: [ + { + vmid: 'og:title', + property: 'og:title', + content: 'Test title - My page' + } + ], + base: [], + link: [], + style: [], + script: [], + noscript: [], + __dangerouslyDisableSanitizers: [], + __dangerouslyDisableSanitizersByTagID: {} + }) + }) + + it('properly uses content only if template is not defined', () => { + component = new Vue({ + metaInfo: { + title: 'Hello', + meta: [ + { + vmid: 'og:title', + property: 'og:title', + content: 'Test title' + } + ] + } + }) + expect(getMetaInfo(component)).to.eql({ + title: 'Hello', + titleChunk: 'Hello', + titleTemplate: '%s', + htmlAttrs: {}, + headAttrs: {}, + bodyAttrs: {}, + meta: [ + { + vmid: 'og:title', + property: 'og:title', + content: 'Test title' + } + ], + base: [], + link: [], + style: [], + script: [], + noscript: [], + __dangerouslyDisableSanitizers: [], + __dangerouslyDisableSanitizersByTagID: {} + }) + }) + + it('properly uses content only if template is null', () => { + component = new Vue({ + metaInfo: { + title: 'Hello', + meta: [ + { + vmid: 'og:title', + property: 'og:title', + content: 'Test title', + template: null + } + ] + } + }) + expect(getMetaInfo(component)).to.eql({ + title: 'Hello', + titleChunk: 'Hello', + titleTemplate: '%s', + htmlAttrs: {}, + headAttrs: {}, + bodyAttrs: {}, + meta: [ + { + vmid: 'og:title', + property: 'og:title', + content: 'Test title' + } + ], + base: [], + link: [], + style: [], + script: [], + noscript: [], + __dangerouslyDisableSanitizers: [], + __dangerouslyDisableSanitizersByTagID: {} + }) + }) + + it('properly uses content only if template is false', () => { + component = new Vue({ + metaInfo: { + title: 'Hello', + meta: [ + { + vmid: 'og:title', + property: 'og:title', + content: 'Test title', + template: false + } + ] + } + }) + expect(getMetaInfo(component)).to.eql({ + title: 'Hello', + titleChunk: 'Hello', + titleTemplate: '%s', + htmlAttrs: {}, + headAttrs: {}, + bodyAttrs: {}, + meta: [ + { + vmid: 'og:title', + property: 'og:title', + content: 'Test title' + } + ], + base: [], + link: [], + style: [], + script: [], + noscript: [], + __dangerouslyDisableSanitizers: [], + __dangerouslyDisableSanitizersByTagID: {} + }) + }) })