mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-22 20:10:33 +03:00
support custom options
This commit is contained in:
+16
-14
@@ -1,18 +1,20 @@
|
|||||||
import getMetaInfo from '../shared/getMetaInfo'
|
import getMetaInfo from '../shared/getMetaInfo'
|
||||||
import updateClientMetaInfo from './updateClientMetaInfo'
|
import updateClientMetaInfo from './updateClientMetaInfo'
|
||||||
|
|
||||||
/**
|
export default function _refresh (options) {
|
||||||
* When called, will update the current meta info with new meta info.
|
/**
|
||||||
* Useful when updating meta info as the result of an asynchronous
|
* When called, will update the current meta info with new meta info.
|
||||||
* action that resolves after the initial render takes place.
|
* Useful when updating meta info as the result of an asynchronous
|
||||||
*
|
* action that resolves after the initial render takes place.
|
||||||
* Credit to [Sébastien Chopin](https://github.com/Atinux) for the suggestion
|
*
|
||||||
* to implement this method.
|
* Credit to [Sébastien Chopin](https://github.com/Atinux) for the suggestion
|
||||||
*
|
* to implement this method.
|
||||||
* @return {Object} - new meta info
|
*
|
||||||
*/
|
* @return {Object} - new meta info
|
||||||
export default function refresh () {
|
*/
|
||||||
const info = getMetaInfo(this.$root)
|
return function refresh () {
|
||||||
updateClientMetaInfo(info)
|
const info = getMetaInfo(options)(this.$root)
|
||||||
return info
|
updateClientMetaInfo(options)(info)
|
||||||
|
return info
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +1,56 @@
|
|||||||
import updateTitle from './updaters/updateTitle'
|
import updateTitle from './updaters/updateTitle'
|
||||||
import updateTagAttributes from './updaters/updateTagAttributes'
|
import updateTagAttributes from './updaters/updateTagAttributes'
|
||||||
import updateTags from './updaters/updateTags'
|
import updateTags from './updaters/updateTags'
|
||||||
import { SERVER_RENDERED_ATTRIBUTE } from '../shared/constants'
|
|
||||||
|
|
||||||
/**
|
export default function _updateClientMetaInfo (options) {
|
||||||
* Performs client-side updates when new meta info is received
|
const { ssrAttribute } = options
|
||||||
*
|
|
||||||
* @param {Object} newInfo - the meta info to update to
|
|
||||||
*/
|
|
||||||
export default function updateClientMetaInfo (newInfo) {
|
|
||||||
const htmlTag = document.getElementsByTagName('html')[0]
|
|
||||||
// if this is not a server render, then update
|
|
||||||
if (htmlTag.getAttribute(SERVER_RENDERED_ATTRIBUTE) === null) {
|
|
||||||
// initialize tracked changes
|
|
||||||
const addedTags = {}
|
|
||||||
const removedTags = {}
|
|
||||||
|
|
||||||
Object.keys(newInfo).forEach((key) => {
|
/**
|
||||||
switch (key) {
|
* Performs client-side updates when new meta info is received
|
||||||
// update the title
|
*
|
||||||
case 'title':
|
* @param {Object} newInfo - the meta info to update to
|
||||||
updateTitle(newInfo.title)
|
*/
|
||||||
break
|
return function updateClientMetaInfo (newInfo) {
|
||||||
// update attributes
|
const htmlTag = document.getElementsByTagName('html')[0]
|
||||||
case 'htmlAttrs':
|
// if this is not a server render, then update
|
||||||
case 'bodyAttrs':
|
if (htmlTag.getAttribute(ssrAttribute) === null) {
|
||||||
updateTagAttributes(newInfo[key], key === 'htmlAttrs' ? htmlTag : document.getElementsByTagName('body')[0])
|
// initialize tracked changes
|
||||||
break
|
const addedTags = {}
|
||||||
// ignore these
|
const removedTags = {}
|
||||||
case 'titleChunk':
|
|
||||||
case 'titleTemplate':
|
Object.keys(newInfo).forEach((key) => {
|
||||||
case 'changed':
|
switch (key) {
|
||||||
break
|
// update the title
|
||||||
// catch-all update tags
|
case 'title':
|
||||||
default:
|
updateTitle(options)(newInfo.title)
|
||||||
const { oldTags, newTags } = updateTags(key, newInfo[key], document.getElementsByTagName('head')[0])
|
break
|
||||||
if (newTags.length) {
|
// update attributes
|
||||||
addedTags[key] = newTags
|
case 'htmlAttrs':
|
||||||
removedTags[key] = oldTags
|
case 'bodyAttrs':
|
||||||
}
|
updateTagAttributes(options)(newInfo[key], key === 'htmlAttrs' ? htmlTag : document.getElementsByTagName('body')[0])
|
||||||
|
break
|
||||||
|
// ignore these
|
||||||
|
case 'titleChunk':
|
||||||
|
case 'titleTemplate':
|
||||||
|
case 'changed':
|
||||||
|
break
|
||||||
|
// catch-all update tags
|
||||||
|
default:
|
||||||
|
const { oldTags, newTags } = updateTags(options)(key, newInfo[key], document.getElementsByTagName('head')[0])
|
||||||
|
if (newTags.length) {
|
||||||
|
addedTags[key] = newTags
|
||||||
|
removedTags[key] = oldTags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// emit "event" with new info
|
||||||
|
if (typeof newInfo.changed === 'function') {
|
||||||
|
newInfo.changed(newInfo, addedTags, removedTags)
|
||||||
}
|
}
|
||||||
})
|
} else {
|
||||||
|
// remove the server render attribute so we can update on changes
|
||||||
// emit "event" with new info
|
htmlTag.removeAttribute(ssrAttribute)
|
||||||
if (typeof newInfo.changed === 'function') {
|
|
||||||
newInfo.changed(newInfo, addedTags, removedTags)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// remove the server render attribute so we can update on changes
|
|
||||||
htmlTag.removeAttribute(SERVER_RENDERED_ATTRIBUTE)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
import { VUE_META_ATTRIBUTE } from '../../shared/constants'
|
export default function _updateTagAttributes ({ attribute }) {
|
||||||
|
/**
|
||||||
/**
|
* updates the document's html tag attributes
|
||||||
* updates the document's html tag attributes
|
*
|
||||||
*
|
* @param {Object} attrs - the new document html attributes
|
||||||
* @param {Object} attrs - the new document html attributes
|
* @param {HTMLElement} tag - the HTMLElment tag to update with new attrs
|
||||||
* @param {HTMLElement} tag - the HTMLElment tag to update with new attrs
|
*/
|
||||||
*/
|
return function updateTagAttributes (attrs, tag) {
|
||||||
export default function updateTagAttributes (attrs, tag) {
|
const vueMetaAttrString = tag.getAttribute(attribute)
|
||||||
const vueMetaAttrString = tag.getAttribute(VUE_META_ATTRIBUTE)
|
const vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : []
|
||||||
const vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : []
|
const toRemove = [].concat(vueMetaAttrs)
|
||||||
const toRemove = [].concat(vueMetaAttrs)
|
for (let attr in attrs) {
|
||||||
for (let attr in attrs) {
|
if (attrs.hasOwnProperty(attr)) {
|
||||||
if (attrs.hasOwnProperty(attr)) {
|
const val = attrs[attr] || ''
|
||||||
const val = attrs[attr] || ''
|
tag.setAttribute(attr, val)
|
||||||
tag.setAttribute(attr, val)
|
if (vueMetaAttrs.indexOf(attr) === -1) {
|
||||||
if (vueMetaAttrs.indexOf(attr) === -1) {
|
vueMetaAttrs.push(attr)
|
||||||
vueMetaAttrs.push(attr)
|
}
|
||||||
}
|
const saveIndex = toRemove.indexOf(attr)
|
||||||
const saveIndex = toRemove.indexOf(attr)
|
if (saveIndex !== -1) {
|
||||||
if (saveIndex !== -1) {
|
toRemove.splice(saveIndex, 1)
|
||||||
toRemove.splice(saveIndex, 1)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
let i = toRemove.length - 1
|
||||||
let i = toRemove.length - 1
|
for (; i >= 0; i--) {
|
||||||
for (; i >= 0; i--) {
|
tag.removeAttribute(toRemove[i])
|
||||||
tag.removeAttribute(toRemove[i])
|
}
|
||||||
}
|
if (vueMetaAttrs.length === toRemove.length) {
|
||||||
if (vueMetaAttrs.length === toRemove.length) {
|
tag.removeAttribute(attribute)
|
||||||
tag.removeAttribute(VUE_META_ATTRIBUTE)
|
} else {
|
||||||
} else {
|
tag.setAttribute(attribute, vueMetaAttrs.join(','))
|
||||||
tag.setAttribute(VUE_META_ATTRIBUTE, vueMetaAttrs.join(','))
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +1,59 @@
|
|||||||
import { VUE_META_ATTRIBUTE } from '../../shared/constants'
|
|
||||||
|
|
||||||
// borrow the slice method
|
// borrow the slice method
|
||||||
const toArray = Function.prototype.call.bind(Array.prototype.slice)
|
const toArray = Function.prototype.call.bind(Array.prototype.slice)
|
||||||
|
|
||||||
/**
|
export default function _updateTags ({ attribute }) {
|
||||||
* Updates meta tags inside <head> on the client. Borrowed from `react-helmet`:
|
/**
|
||||||
* https://github.com/nfl/react-helmet/blob/004d448f8de5f823d10f838b02317521180f34da/src/Helmet.js#L195-L245
|
* Updates meta tags inside <head> on the client. Borrowed from `react-helmet`:
|
||||||
*
|
* https://github.com/nfl/react-helmet/blob/004d448f8de5f823d10f838b02317521180f34da/src/Helmet.js#L195-L245
|
||||||
* @param {('meta'|'base'|'link'|'style'|'script'|'noscript')} type - the name of the tag
|
*
|
||||||
* @param {(Array<Object>|Object)} tags - an array of tag objects or a single object in case of base
|
* @param {('meta'|'base'|'link'|'style'|'script'|'noscript')} type - the name of the tag
|
||||||
* @return {Object} - a representation of what tags changed
|
* @param {(Array<Object>|Object)} tags - an array of tag objects or a single object in case of base
|
||||||
*/
|
* @return {Object} - a representation of what tags changed
|
||||||
export default function updateTags (type, tags, headTag) {
|
*/
|
||||||
const nodes = headTag.querySelectorAll(`${type}[${VUE_META_ATTRIBUTE}]`)
|
return function updateTags (type, tags, headTag) {
|
||||||
const oldTags = toArray(nodes)
|
const nodes = headTag.querySelectorAll(`${type}[${attribute}]`)
|
||||||
const newTags = []
|
const oldTags = toArray(nodes)
|
||||||
let indexToDelete
|
const newTags = []
|
||||||
|
let indexToDelete
|
||||||
|
|
||||||
if (tags && tags.length) {
|
if (tags && tags.length) {
|
||||||
tags.forEach((tag) => {
|
tags.forEach((tag) => {
|
||||||
const newElement = document.createElement(type)
|
const newElement = document.createElement(type)
|
||||||
|
|
||||||
for (const attribute in tag) {
|
for (const attr in tag) {
|
||||||
if (tag.hasOwnProperty(attribute)) {
|
if (tag.hasOwnProperty(attr)) {
|
||||||
if (attribute === 'innerHTML') {
|
if (attribute === 'innerHTML') {
|
||||||
newElement.innerHTML = tag.innerHTML
|
newElement.innerHTML = tag.innerHTML
|
||||||
} else if (attribute === 'cssText') {
|
} else if (attribute === 'cssText') {
|
||||||
if (newElement.styleSheet) {
|
if (newElement.styleSheet) {
|
||||||
newElement.styleSheet.cssText = tag.cssText
|
newElement.styleSheet.cssText = tag.cssText
|
||||||
|
} else {
|
||||||
|
newElement.appendChild(document.createTextNode(tag.cssText))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
newElement.appendChild(document.createTextNode(tag.cssText))
|
const value = (typeof tag[attr] === 'undefined') ? '' : tag[attr]
|
||||||
|
newElement.setAttribute(attr, value)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const value = (typeof tag[attribute] === 'undefined') ? '' : tag[attribute]
|
|
||||||
newElement.setAttribute(attribute, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
newElement.setAttribute(VUE_META_ATTRIBUTE, 'true')
|
newElement.setAttribute(attribute, 'true')
|
||||||
|
|
||||||
// Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
|
// Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
|
||||||
if (oldTags.some((existingTag, index) => {
|
if (oldTags.some((existingTag, index) => {
|
||||||
indexToDelete = index
|
indexToDelete = index
|
||||||
return newElement.isEqualNode(existingTag)
|
return newElement.isEqualNode(existingTag)
|
||||||
})) {
|
})) {
|
||||||
oldTags.splice(indexToDelete, 1)
|
oldTags.splice(indexToDelete, 1)
|
||||||
} else {
|
} else {
|
||||||
newTags.push(newElement)
|
newTags.push(newElement)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
oldTags.forEach((tag) => tag.parentNode.removeChild(tag))
|
||||||
|
newTags.forEach((tag) => headTag.appendChild(tag))
|
||||||
|
|
||||||
|
return { oldTags, newTags }
|
||||||
}
|
}
|
||||||
|
|
||||||
oldTags.forEach((tag) => tag.parentNode.removeChild(tag))
|
|
||||||
newTags.forEach((tag) => headTag.appendChild(tag))
|
|
||||||
|
|
||||||
return { oldTags, newTags }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
/**
|
export default function _updateTitle () {
|
||||||
* updates the document title
|
/**
|
||||||
*
|
* updates the document title
|
||||||
* @param {String} title - the new title of the document
|
*
|
||||||
*/
|
* @param {String} title - the new title of the document
|
||||||
export default function updateTitle (title = document.title) {
|
*/
|
||||||
document.title = title
|
return function updateTitle (title = document.title) {
|
||||||
|
document.title = title
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,21 +2,23 @@ import titleGenerator from './generators/titleGenerator'
|
|||||||
import attrsGenerator from './generators/attrsGenerator'
|
import attrsGenerator from './generators/attrsGenerator'
|
||||||
import tagGenerator from './generators/tagGenerator'
|
import tagGenerator from './generators/tagGenerator'
|
||||||
|
|
||||||
/**
|
export default function _generateServerInjector (options) {
|
||||||
* Converts a meta info property to one that can be stringified on the server
|
/**
|
||||||
*
|
* Converts a meta info property to one that can be stringified on the server
|
||||||
* @param {String} type - the type of data to convert
|
*
|
||||||
* @param {(String|Object|Array<Object>)} data - the data value
|
* @param {String} type - the type of data to convert
|
||||||
* @return {Object} - the new injector
|
* @param {(String|Object|Array<Object>)} data - the data value
|
||||||
*/
|
* @return {Object} - the new injector
|
||||||
export default function generateServerInjector (type, data) {
|
*/
|
||||||
switch (type) {
|
return function generateServerInjector (type, data) {
|
||||||
case 'title':
|
switch (type) {
|
||||||
return titleGenerator(type, data)
|
case 'title':
|
||||||
case 'htmlAttrs':
|
return titleGenerator(options)(type, data)
|
||||||
case 'bodyAttrs':
|
case 'htmlAttrs':
|
||||||
return attrsGenerator(type, data)
|
case 'bodyAttrs':
|
||||||
default:
|
return attrsGenerator(options)(type, data)
|
||||||
return tagGenerator(type, data)
|
default:
|
||||||
|
return tagGenerator(options)(type, data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
import { VUE_META_ATTRIBUTE } from '../../shared/constants'
|
export default function _attrsGenerator ({ attribute }) {
|
||||||
|
/**
|
||||||
/**
|
* Generates tag attributes for use on the server.
|
||||||
* Generates tag attributes for use on the server.
|
*
|
||||||
*
|
* @param {('bodyAttrs'|'htmlAttrs')} type - the type of attributes to generate
|
||||||
* @param {('bodyAttrs'|'htmlAttrs')} type - the type of attributes to generate
|
* @param {Object} data - the attributes to generate
|
||||||
* @param {Object} data - the attributes to generate
|
* @return {Object} - the attribute generator
|
||||||
* @return {Object} - the attribute generator
|
*/
|
||||||
*/
|
return function attrsGenerator (type, data) {
|
||||||
export default function attrsGenerator (type, data) {
|
return {
|
||||||
return {
|
text () {
|
||||||
text () {
|
let attributeStr = ''
|
||||||
let attributeStr = ''
|
let watchedAttrs = []
|
||||||
let watchedAttrs = []
|
for (let attr in data) {
|
||||||
for (let attr in data) {
|
if (data.hasOwnProperty(attr)) {
|
||||||
if (data.hasOwnProperty(attr)) {
|
watchedAttrs.push(attr)
|
||||||
watchedAttrs.push(attr)
|
attributeStr += `${
|
||||||
attributeStr += `${
|
typeof data[attr] !== 'undefined'
|
||||||
typeof data[attr] !== 'undefined'
|
? `${attr}="${data[attr]}"`
|
||||||
? `${attr}="${data[attr]}"`
|
: attr
|
||||||
: attr
|
} `
|
||||||
} `
|
}
|
||||||
}
|
}
|
||||||
|
attributeStr += `${attribute}="${watchedAttrs.join(',')}"`
|
||||||
|
return attributeStr.trim()
|
||||||
}
|
}
|
||||||
attributeStr += `${VUE_META_ATTRIBUTE}="${watchedAttrs.join(',')}"`
|
|
||||||
return attributeStr.trim()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
import { VUE_META_ATTRIBUTE } from '../../shared/constants'
|
export default function _tagGenerator ({ attribute }) {
|
||||||
|
/**
|
||||||
|
* Generates meta, base, link, style, script, noscript tags for use on the server
|
||||||
|
*
|
||||||
|
* @param {('meta'|'base'|'link'|'style'|'script'|'noscript')} the name of the tag
|
||||||
|
* @param {(Array<Object>|Object)} tags - an array of tag objects or a single object in case of base
|
||||||
|
* @return {Object} - the tag generator
|
||||||
|
*/
|
||||||
|
return function tagGenerator (type, tags) {
|
||||||
|
return {
|
||||||
|
text () {
|
||||||
|
// build a string containing all tags of this type
|
||||||
|
return tags.reduce((tagsStr, tag) => {
|
||||||
|
// build a string containing all attributes of this tag
|
||||||
|
const attrs = Object.keys(tag).reduce((attrsStr, attr) => {
|
||||||
|
switch (attr) {
|
||||||
|
// these attributes are treated as children on the tag
|
||||||
|
case 'innerHTML':
|
||||||
|
case 'cssText':
|
||||||
|
return attrsStr
|
||||||
|
|
||||||
/**
|
// these form the attribute list for this tag
|
||||||
* Generates meta, base, link, style, script, noscript tags for use on the server
|
default:
|
||||||
*
|
return typeof tag[attr] === 'undefined'
|
||||||
* @param {('meta'|'base'|'link'|'style'|'script'|'noscript')} the name of the tag
|
? `${attrsStr} ${attr}`
|
||||||
* @param {(Array<Object>|Object)} tags - an array of tag objects or a single object in case of base
|
: `${attrsStr} ${attr}="${tag[attr]}"`
|
||||||
* @return {Object} - the tag generator
|
}
|
||||||
*/
|
}, '').trim()
|
||||||
export default function tagGenerator (type, tags) {
|
|
||||||
return {
|
|
||||||
text () {
|
|
||||||
// build a string containing all tags of this type
|
|
||||||
return tags.reduce((tagsStr, tag) => {
|
|
||||||
// build a string containing all attributes of this tag
|
|
||||||
const attrs = Object.keys(tag).reduce((attrsStr, attr) => {
|
|
||||||
switch (attr) {
|
|
||||||
// these attributes are treated as children on the tag
|
|
||||||
case 'innerHTML':
|
|
||||||
case 'cssText':
|
|
||||||
return attrsStr
|
|
||||||
|
|
||||||
// these form the attribute list for this tag
|
// grab child content from one of these attributes, if possible
|
||||||
default:
|
const content = tag.innerHTML || tag.cssText || ''
|
||||||
return typeof tag[attr] === 'undefined'
|
|
||||||
? `${attrsStr} ${attr}`
|
|
||||||
: `${attrsStr} ${attr}="${tag[attr]}"`
|
|
||||||
}
|
|
||||||
}, '').trim()
|
|
||||||
|
|
||||||
// grab child content from one of these attributes, if possible
|
// these tag types will have content inserted
|
||||||
const content = tag.innerHTML || tag.cssText || ''
|
const closed = ['noscript', 'script', 'style'].indexOf(type) === -1
|
||||||
|
|
||||||
// these tag types will have content inserted
|
// the final string for this specific tag
|
||||||
const closed = ['noscript', 'script', 'style'].indexOf(type) === -1
|
return closed
|
||||||
|
? `${tagsStr}<${type} ${attribute}="true" ${attrs}/>`
|
||||||
// the final string for this specific tag
|
: `${tagsStr}<${type} ${attribute}="true" ${attrs}>${content}</${type}>`
|
||||||
return closed
|
}, '')
|
||||||
? `${tagsStr}<${type} ${VUE_META_ATTRIBUTE}="true" ${attrs}/>`
|
}
|
||||||
: `${tagsStr}<${type} ${VUE_META_ATTRIBUTE}="true" ${attrs}>${content}</${type}>`
|
|
||||||
}, '')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { VUE_META_ATTRIBUTE } from '../../shared/constants'
|
export default function _titleGenerator ({ attribute }) {
|
||||||
|
/**
|
||||||
/**
|
* Generates title output for the server
|
||||||
* Generates title output for the server
|
*
|
||||||
*
|
* @param {'title'} type - the string "title"
|
||||||
* @param {'title'} type - the string "title"
|
* @param {String} data - the title text
|
||||||
* @param {String} data - the title text
|
* @return {Object} - the title generator
|
||||||
* @return {Object} - the title generator
|
*/
|
||||||
*/
|
return function titleGenerator (type, data) {
|
||||||
export default function titleGenerator (type, data) {
|
return {
|
||||||
return {
|
text () {
|
||||||
text () {
|
return `<${type} ${attribute}="true">${data}</${type}>`
|
||||||
return `<${type} ${VUE_META_ATTRIBUTE}="true">${data}</${type}>`
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-16
@@ -1,23 +1,25 @@
|
|||||||
import getMetaInfo from '../shared/getMetaInfo'
|
import getMetaInfo from '../shared/getMetaInfo'
|
||||||
import generateServerInjector from './generateServerInjector'
|
import generateServerInjector from './generateServerInjector'
|
||||||
|
|
||||||
/**
|
export default function _inject (options) {
|
||||||
* Converts the state of the meta info object such that each item
|
/**
|
||||||
* can be compiled to a tag string on the server
|
* Converts the state of the meta info object such that each item
|
||||||
*
|
* can be compiled to a tag string on the server
|
||||||
* @this {Object} - Vue instance - ideally the root component
|
*
|
||||||
* @return {Object} - server meta info with `toString` methods
|
* @this {Object} - Vue instance - ideally the root component
|
||||||
*/
|
* @return {Object} - server meta info with `toString` methods
|
||||||
export default function inject () {
|
*/
|
||||||
// get meta info with sensible defaults
|
return function inject () {
|
||||||
const info = getMetaInfo(this.$root)
|
// get meta info with sensible defaults
|
||||||
|
const info = getMetaInfo(options)(this.$root)
|
||||||
|
|
||||||
// generate server injectors
|
// generate server injectors
|
||||||
for (let key in info) {
|
for (let key in info) {
|
||||||
if (info.hasOwnProperty(key) && key !== 'titleTemplate') {
|
if (info.hasOwnProperty(key) && key !== 'titleTemplate') {
|
||||||
info[key] = generateServerInjector(key, info[key])
|
info[key] = generateServerInjector(options)(key, info[key])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-10
@@ -1,15 +1,16 @@
|
|||||||
import inject from '../server/inject'
|
import inject from '../server/inject'
|
||||||
import refresh from '../client/refresh'
|
import refresh from '../client/refresh'
|
||||||
|
|
||||||
/**
|
export default function _$meta (options) {
|
||||||
* Returns an injector for server-side rendering.
|
/**
|
||||||
* @this {Object} - the Vue instance (a root component)
|
* Returns an injector for server-side rendering.
|
||||||
* @return {Object} - injector
|
* @this {Object} - the Vue instance (a root component)
|
||||||
*/
|
* @return {Object} - injector
|
||||||
export default function $meta () {
|
*/
|
||||||
// bind inject method to this component
|
return function $meta () {
|
||||||
return {
|
return {
|
||||||
inject: inject.bind(this),
|
inject: inject(options).bind(this),
|
||||||
refresh: refresh.bind(this)
|
refresh: refresh(options).bind(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-1
@@ -1,2 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* These are constant variables used throughout the application.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This is the name of the component option that contains all the information that
|
||||||
|
// gets converted to the various meta tags & attributes for the page.
|
||||||
|
export const VUE_META_KEY_NAME = 'metaInfo'
|
||||||
|
|
||||||
|
// This is the attribute vue-meta augments on elements to know which it should
|
||||||
|
// manage and which it should ignore.
|
||||||
export const VUE_META_ATTRIBUTE = 'data-vue-meta'
|
export const VUE_META_ATTRIBUTE = 'data-vue-meta'
|
||||||
export const SERVER_RENDERED_ATTRIBUTE = 'data-vue-meta-server-rendered'
|
|
||||||
|
// This is the attribute that goes on the `html` tag to inform `vue-meta`
|
||||||
|
// that the server has already generated the meta tags for the initial render.
|
||||||
|
export const VUE_META_SERVER_RENDERED_ATTRIBUTE = 'data-vue-meta-server-rendered'
|
||||||
|
|
||||||
|
// This is the property that tells vue-meta to overwrite (instead of append)
|
||||||
|
// an item in a tag list. For example, if you have two `meta` tag list items
|
||||||
|
// 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'
|
||||||
|
|||||||
+65
-63
@@ -1,75 +1,77 @@
|
|||||||
import deepmerge from 'deepmerge'
|
import deepmerge from 'deepmerge'
|
||||||
import getComponentOption from './getComponentOption'
|
import getComponentOption from './getComponentOption'
|
||||||
|
|
||||||
/**
|
export default function _getMetaInfo ({ keyName, tagIDKeyName }) {
|
||||||
* Returns the correct meta info for the given component
|
/**
|
||||||
* (child components will overwrite parent meta info)
|
* Returns the correct meta info for the given component
|
||||||
*
|
* (child components will overwrite parent meta info)
|
||||||
* @param {Object} component - the Vue instance to get meta info from
|
*
|
||||||
* @return {Object} - returned meta info
|
* @param {Object} component - the Vue instance to get meta info from
|
||||||
*/
|
* @return {Object} - returned meta info
|
||||||
export default function getMetaInfo (component) {
|
*/
|
||||||
// set some sane defaults
|
return function getMetaInfo (component) {
|
||||||
const defaultInfo = {
|
// set some sane defaults
|
||||||
title: '',
|
const defaultInfo = {
|
||||||
titleChunk: '',
|
title: '',
|
||||||
titleTemplate: '%s',
|
titleChunk: '',
|
||||||
htmlAttrs: {},
|
titleTemplate: '%s',
|
||||||
bodyAttrs: {},
|
htmlAttrs: {},
|
||||||
meta: [],
|
bodyAttrs: {},
|
||||||
base: [],
|
meta: [],
|
||||||
link: [],
|
base: [],
|
||||||
style: [],
|
link: [],
|
||||||
script: [],
|
style: [],
|
||||||
noscript: []
|
script: [],
|
||||||
}
|
noscript: []
|
||||||
|
}
|
||||||
|
|
||||||
// collect & aggregate all metaInfo $options
|
// collect & aggregate all metaInfo $options
|
||||||
const info = getComponentOption({
|
const info = getComponentOption({
|
||||||
component,
|
component,
|
||||||
option: 'metaInfo',
|
option: keyName,
|
||||||
deep: true,
|
deep: true,
|
||||||
arrayMerge (target, source) {
|
arrayMerge (target, source) {
|
||||||
// we concat the arrays without merging objects contained therein,
|
// we concat the arrays without merging objects contained therein,
|
||||||
// but we check for a `vmid` property on each object in the array
|
// but we check for a `vmid` property on each object in the array
|
||||||
// using an O(1) lookup associative array exploit
|
// using an O(1) lookup associative array exploit
|
||||||
// note the use of "for in" - we are looping through arrays here, not
|
// note the use of "for in" - we are looping through arrays here, not
|
||||||
// plain objects
|
// plain objects
|
||||||
const destination = []
|
const destination = []
|
||||||
for (let targetIndex in target) {
|
for (let targetIndex in target) {
|
||||||
const targetItem = target[targetIndex]
|
const targetItem = target[targetIndex]
|
||||||
let shared = false
|
let shared = false
|
||||||
for (let sourceIndex in source) {
|
for (let sourceIndex in source) {
|
||||||
const sourceItem = source[sourceIndex]
|
const sourceItem = source[sourceIndex]
|
||||||
if (targetItem.vmid === sourceItem.vmid) {
|
if (targetItem[tagIDKeyName] === sourceItem[tagIDKeyName]) {
|
||||||
shared = true
|
shared = true
|
||||||
break
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!shared) {
|
||||||
|
destination.push(targetItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!shared) {
|
|
||||||
destination.push(targetItem)
|
return destination.concat(source)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return destination.concat(source)
|
// backup the title chunk in case user wants access to it
|
||||||
|
if (info.title) {
|
||||||
|
info.titleChunk = info.title
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
// backup the title chunk in case user wants access to it
|
// replace title with populated template
|
||||||
if (info.title) {
|
if (info.titleTemplate) {
|
||||||
info.titleChunk = info.title
|
info.title = info.titleTemplate.replace(/%s/g, info.titleChunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert base tag to an array so it can be handled the same way
|
||||||
|
// as the other tags
|
||||||
|
if (info.base) {
|
||||||
|
info.base = Object.keys(info.base).length ? [info.base] : []
|
||||||
|
}
|
||||||
|
|
||||||
|
return deepmerge(defaultInfo, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
// replace title with populated template
|
|
||||||
if (info.titleTemplate) {
|
|
||||||
info.title = info.titleTemplate.replace(/%s/g, info.titleChunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert base tag to an array so it can be handled the same way
|
|
||||||
// as the other tags
|
|
||||||
if (info.base) {
|
|
||||||
info.base = Object.keys(info.base).length ? [info.base] : []
|
|
||||||
}
|
|
||||||
|
|
||||||
return deepmerge(defaultInfo, info)
|
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-2
@@ -1,17 +1,36 @@
|
|||||||
|
import assign from 'object-assign'
|
||||||
import $meta from './$meta'
|
import $meta from './$meta'
|
||||||
|
|
||||||
|
import {
|
||||||
|
VUE_META_KEY_NAME,
|
||||||
|
VUE_META_ATTRIBUTE,
|
||||||
|
VUE_META_SERVER_RENDERED_ATTRIBUTE,
|
||||||
|
VUE_META_TAG_LIST_ID_KEY_NAME
|
||||||
|
} from './constants'
|
||||||
|
|
||||||
// automatic install
|
// automatic install
|
||||||
if (typeof Vue !== 'undefined') {
|
if (typeof Vue !== 'undefined') {
|
||||||
Vue.use(VueMeta)
|
Vue.use(VueMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set some default options
|
||||||
|
const defaultOptions = {
|
||||||
|
keyName: VUE_META_KEY_NAME,
|
||||||
|
attribute: VUE_META_ATTRIBUTE,
|
||||||
|
ssrAttribute: VUE_META_SERVER_RENDERED_ATTRIBUTE,
|
||||||
|
tagIDKeyName: VUE_META_TAG_LIST_ID_KEY_NAME
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin install function.
|
* Plugin install function.
|
||||||
* @param {Function} Vue - the Vue constructor.
|
* @param {Function} Vue - the Vue constructor.
|
||||||
*/
|
*/
|
||||||
export default function VueMeta (Vue) {
|
export default function VueMeta (Vue, options = {}) {
|
||||||
|
// combine options
|
||||||
|
options = assign(defaultOptions, options)
|
||||||
|
|
||||||
// bind the $meta method to this component instance
|
// bind the $meta method to this component instance
|
||||||
Vue.prototype.$meta = $meta
|
Vue.prototype.$meta = $meta(options)
|
||||||
|
|
||||||
// store an id to keep track of DOM updates
|
// store an id to keep track of DOM updates
|
||||||
let requestId = null
|
let requestId = null
|
||||||
|
|||||||
Reference in New Issue
Block a user