2
0
mirror of https://github.com/tenrok/vue-meta.git synced 2026-06-06 03:22:24 +03:00
Files
vue-meta/src/shared/getMetaInfo.js
T
2017-05-15 14:48:32 +02:00

130 lines
3.7 KiB
JavaScript

import deepmerge from 'deepmerge'
import isPlainObject from 'lodash.isplainobject'
import isArray from './isArray'
import getComponentOption from './getComponentOption'
const escapeHTML = (str) => typeof window === 'undefined'
// server-side escape sequence
? String(str)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;')
// client-side escape sequence
: String(str)
.replace(/&/g, '\u0026')
.replace(/</g, '\u003c')
.replace(/>/g, '\u003e')
.replace(/"/g, '\u0022')
.replace(/'/g, '\u0027')
export default function _getMetaInfo (options = {}) {
const { keyName, tagIDKeyName } = options
/**
* 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
*/
return function getMetaInfo (component) {
// set some sane defaults
const defaultInfo = {
title: '',
titleChunk: '',
titleTemplate: '%s',
htmlAttrs: {},
bodyAttrs: {},
meta: [],
base: [],
link: [],
style: [],
script: [],
noscript: [],
__dangerouslyDisableSanitizers: []
}
// collect & aggregate all metaInfo $options
let info = getComponentOption({
component,
option: keyName,
deep: true,
arrayMerge (target, source) {
// we concat the arrays without merging objects contained therein,
// but we check for a `vmid` property on each object in the array
// using an O(1) lookup associative array exploit
// note the use of "for in" - we are looping through arrays here, not
// plain objects
const destination = []
for (let targetIndex in target) {
const targetItem = target[targetIndex]
let shared = false
for (let sourceIndex in source) {
const sourceItem = source[sourceIndex]
if (targetItem[tagIDKeyName] && targetItem[tagIDKeyName] === sourceItem[tagIDKeyName]) {
shared = true
break
}
}
if (!shared) {
destination.push(targetItem)
}
}
return destination.concat(source)
}
})
// backup the title chunk in case user wants access to it
if (info.title) {
info.titleChunk = info.title
}
// 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] : []
}
// sanitizes potentially dangerous characters
const escape = (info) => Object.keys(info).reduce((escaped, key) => {
const ref = info.__dangerouslyDisableSanitizers
const isDisabled = ref && ref.indexOf(key) > -1
const val = info[key]
escaped[key] = val;
if (key === '__dangerouslyDisableSanitizers') {
return escaped
}
if (!isDisabled) {
if (typeof val === 'string') {
escaped[key] = escapeHTML(val)
} else if (isPlainObject(val)) {
escaped[key] = escape(val)
} else if (isArray(val)) {
escaped[key] = val.map(escape)
} else {
escaped[key] = val
}
} else {
escaped[key] = val
}
return escaped
}, {})
// merge with defaults
info = deepmerge(defaultInfo, info)
// begin sanitization
info = escape(info)
return info
}
}