diff --git a/CHANGELOG.md b/CHANGELOG.md
index 43a365e..9e288c9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,36 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+## [2.1.0](https://github.com/nuxt/vue-meta/compare/v2.0.3...v2.1.0) (2019-07-24)
+
+
+### Bug Fixes
+
+* add warning for v1 boolean attribute syntax ([bfeab17](https://github.com/nuxt/vue-meta/commit/bfeab17))
+* also use ssrAppId for client update ([50c0509](https://github.com/nuxt/vue-meta/commit/50c0509))
+* don't generate
tag if metaInfo.title is null or false ([#409](https://github.com/nuxt/vue-meta/issues/409)) ([39ef287](https://github.com/nuxt/vue-meta/commit/39ef287))
+* dont change title when value is undefined (fix [#396](https://github.com/nuxt/vue-meta/issues/396)) ([90f9710](https://github.com/nuxt/vue-meta/commit/90f9710))
+* dont update title on client with falsy value except empty string ([6efcdf1](https://github.com/nuxt/vue-meta/commit/6efcdf1))
+* ensure hasAttribute exists on $root.$el ([f1511ac](https://github.com/nuxt/vue-meta/commit/f1511ac))
+* only show boolean attrs with truthy value ([1d9072a](https://github.com/nuxt/vue-meta/commit/1d9072a))
+
+
+### Features
+
+* add option for prepending (no)script to body ([#410](https://github.com/nuxt/vue-meta/issues/410)) ([05163a7](https://github.com/nuxt/vue-meta/commit/05163a7))
+* auto add ssrAttribute to htmlAttrs ([9cf6d32](https://github.com/nuxt/vue-meta/commit/9cf6d32))
+* enable onload callbacks ([#414](https://github.com/nuxt/vue-meta/issues/414)) ([fc71e1f](https://github.com/nuxt/vue-meta/commit/fc71e1f))
+* make ssr app id configurable ([b0c85e5](https://github.com/nuxt/vue-meta/commit/b0c85e5))
+* support json content (without disabling sanitizers) ([#415](https://github.com/nuxt/vue-meta/issues/415)) ([51fe6ea](https://github.com/nuxt/vue-meta/commit/51fe6ea))
+
+
+### Tests
+
+* enable all getMetaInfo tests again ([24d7fee](https://github.com/nuxt/vue-meta/commit/24d7fee))
+* update browser config ([8c35863](https://github.com/nuxt/vue-meta/commit/8c35863))
+
+
+
### [2.0.5](https://github.com/nuxt/vue-meta/compare/v2.0.3...v2.0.5) (2019-07-11)
diff --git a/dist/vue-meta.common.js b/dist/vue-meta.common.js
index e6c2e5b..f92adca 100644
--- a/dist/vue-meta.common.js
+++ b/dist/vue-meta.common.js
@@ -1,5 +1,5 @@
/**
- * vue-meta v2.0.5
+ * vue-meta v2.1.0
* (c) 2019
* - Declan de Wet
* - Sébastien Chopin (@Atinux)
@@ -13,12 +13,11 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'defau
var deepmerge = _interopDefault(require('deepmerge'));
-var version = "2.0.5";
+var version = "2.1.0";
// store an id to keep track of DOM updates
-var batchId = null;
-
-function triggerUpdate (vm, hookName) {
+let batchId = null;
+function triggerUpdate(vm, hookName) {
// if an update was triggered during initialization or when an update was triggered by the
// metaInfo watcher, set initialized to null
// then we keep falsy value but know we need to run a triggerUpdate after initialization
@@ -28,10 +27,9 @@ function triggerUpdate (vm, hookName) {
if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) {
// batch potential DOM updates to prevent extraneous re-rendering
- batchUpdate(function () { return vm.$meta().refresh(); });
+ batchUpdate(() => vm.$meta().refresh());
}
}
-
/**
* Performs a batched update.
*
@@ -39,16 +37,13 @@ function triggerUpdate (vm, hookName) {
* @param {Function} callback - the update to perform
* @return {Number} id - a new ID
*/
-function batchUpdate (callback, timeout) {
- if ( timeout === void 0 ) timeout = 10;
+function batchUpdate(callback, timeout = 10) {
clearTimeout(batchId);
-
- batchId = setTimeout(function () {
+ batchId = setTimeout(() => {
callback();
}, timeout);
-
- return batchId
+ return batchId;
}
/**
@@ -56,247 +51,254 @@ function batchUpdate (callback, timeout) {
* @param {any} arg - the object to check
* @return {Boolean} - true if `arg` is an array
*/
-function isArray (arg) {
- return Array.isArray(arg)
+function isArray(arg) {
+ return Array.isArray(arg);
+}
+function isUndefined(arg) {
+ return typeof arg === 'undefined';
+}
+function isObject(arg) {
+ return typeof arg === 'object';
+}
+function isPureObject(arg) {
+ return typeof arg === 'object' && arg !== null;
+}
+function isFunction(arg) {
+ return typeof arg === 'function';
+}
+function isString(arg) {
+ return typeof arg === 'string';
}
-function isUndefined (arg) {
- return typeof arg === 'undefined'
-}
-
-function isObject (arg) {
- return typeof arg === 'object'
-}
-
-function isFunction (arg) {
- return typeof arg === 'function'
-}
-
-function isString (arg) {
- return typeof arg === 'string'
-}
-
-function ensureIsArray (arg, key) {
+function ensureIsArray(arg, key) {
if (!key || !isObject(arg)) {
- return isArray(arg) ? arg : []
+ return isArray(arg) ? arg : [];
}
if (!isArray(arg[key])) {
arg[key] = [];
}
- return arg
+
+ return arg;
}
-
-function ensuredPush (object, key, el) {
+function ensuredPush(object, key, el) {
ensureIsArray(object, key);
-
object[key].push(el);
}
-// Vue $root instance has a _vueMeta object property, otherwise its a boolean true
-function hasMetaInfo (vm) {
- if ( vm === void 0 ) vm = this;
+function hasMetaInfo(vm = this) {
+ return vm && (vm._vueMeta === true || isObject(vm._vueMeta));
+} // a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has
- return vm && (vm._vueMeta === true || isObject(vm._vueMeta))
+function inMetaInfoBranch(vm = this) {
+ return vm && !isUndefined(vm._vueMeta);
}
-// a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has
-function inMetaInfoBranch (vm) {
- if ( vm === void 0 ) vm = this;
-
- return vm && !isUndefined(vm._vueMeta)
-}
-
-function addNavGuards (vm) {
+function addNavGuards(vm) {
// return when nav guards already added or no router exists
if (vm.$root._vueMeta.navGuards || !vm.$root.$router) {
/* istanbul ignore next */
- return
+ return;
}
vm.$root._vueMeta.navGuards = true;
-
- var $router = vm.$root.$router;
- var $meta = vm.$root.$meta();
-
- $router.beforeEach(function (to, from, next) {
+ const $router = vm.$root.$router;
+ const $meta = vm.$root.$meta();
+ $router.beforeEach((to, from, next) => {
$meta.pause();
next();
});
+ $router.afterEach(() => {
+ const {
+ metaInfo
+ } = $meta.resume();
- $router.afterEach(function () {
- var ref = $meta.resume();
- var metaInfo = ref.metaInfo;
if (metaInfo && metaInfo.afterNavigation && isFunction(metaInfo.afterNavigation)) {
metaInfo.afterNavigation(metaInfo);
}
});
}
-var appId = 1;
+function hasGlobalWindowFn() {
+ try {
+ return !isUndefined(window);
+ } catch (e) {
+ return false;
+ }
+}
+const hasGlobalWindow = hasGlobalWindowFn();
-function createMixin (Vue, options) {
+const _global = hasGlobalWindow ? window : global;
+
+const console = _global.console = _global.console || {};
+function warn(...args) {
+ /* istanbul ignore next */
+ if (!console || !console.warn) {
+ return;
+ }
+
+ console.warn(...args);
+}
+
+let appId = 1;
+function createMixin(Vue, options) {
// for which Vue lifecycle hooks should the metaInfo be refreshed
- var updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount'];
+ const updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount']; // watch for client side component updates
- // watch for client side component updates
return {
- beforeCreate: function beforeCreate () {
- var this$1 = this;
-
+ beforeCreate() {
Object.defineProperty(this, '_hasMetaInfo', {
configurable: true,
- get: function get () {
+
+ get() {
// Show deprecation warning once when devtools enabled
if (Vue.config.devtools && !this.$root._vueMeta.hasMetaInfoDeprecationWarningShown) {
- console.warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead'); // eslint-disable-line no-console
+ warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead');
this.$root._vueMeta.hasMetaInfoDeprecationWarningShown = true;
}
- return hasMetaInfo(this)
- }
- });
- // Add a marker to know if it uses metaInfo
+ return hasMetaInfo(this);
+ }
+
+ }); // Add a marker to know if it uses metaInfo
// _vnode is used to know that it's attached to a real component
// useful if we use some mixin to add some meta tags (like nuxt-i18n)
+
if (!isUndefined(this.$options[options.keyName]) && this.$options[options.keyName] !== null) {
if (!this.$root._vueMeta) {
- this.$root._vueMeta = { appId: appId };
+ this.$root._vueMeta = {
+ appId
+ };
appId++;
- }
-
- // to speed up updates we keep track of branches which have a component with vue-meta info defined
+ } // to speed up updates we keep track of branches which have a component with vue-meta info defined
// if _vueMeta = true it has info, if _vueMeta = false a child has info
+
+
if (!this._vueMeta) {
this._vueMeta = true;
+ let p = this.$parent;
- var p = this.$parent;
while (p && p !== this.$root) {
if (isUndefined(p._vueMeta)) {
p._vueMeta = false;
}
+
p = p.$parent;
}
- }
-
- // coerce function-style metaInfo to a computed prop so we can observe
+ } // coerce function-style metaInfo to a computed prop so we can observe
// it on creation
+
+
if (isFunction(this.$options[options.keyName])) {
if (!this.$options.computed) {
this.$options.computed = {};
}
+
this.$options.computed.$metaInfo = this.$options[options.keyName];
if (!this.$isServer) {
// if computed $metaInfo exists, watch it for updates & trigger a refresh
// when it changes (i.e. automatically handle async actions that affect metaInfo)
// credit for this suggestion goes to [Sébastien Chopin](https://github.com/Atinux)
- ensuredPush(this.$options, 'created', function () {
- this$1.$watch('$metaInfo', function () {
+ ensuredPush(this.$options, 'created', () => {
+ this.$watch('$metaInfo', function () {
triggerUpdate(this, 'watcher');
});
});
}
- }
-
- // force an initial refresh on page load and prevent other lifecycleHooks
+ } // force an initial refresh on page load and prevent other lifecycleHooks
// to triggerUpdate until this initial refresh is finished
// this is to make sure that when a page is opened in an inactive tab which
// has throttled rAF/timers we still immediately set the page title
+
+
if (isUndefined(this.$root._vueMeta.initialized)) {
this.$root._vueMeta.initialized = this.$isServer;
if (!this.$root._vueMeta.initialized) {
- ensuredPush(this.$options, 'beforeMount', function () {
+ ensuredPush(this.$options, 'beforeMount', () => {
// if this Vue-app was server rendered, set the appId to 'ssr'
// only one SSR app per page is supported
- if (this$1.$root.$el && this$1.$root.$el.hasAttribute && this$1.$root.$el.hasAttribute('data-server-rendered')) {
- this$1.$root._vueMeta.appId = 'ssr';
+ if (this.$root.$el && this.$root.$el.hasAttribute && this.$root.$el.hasAttribute('data-server-rendered')) {
+ this.$root._vueMeta.appId = options.ssrAppId;
}
- });
+ }); // we use the mounted hook here as on page load
- // we use the mounted hook here as on page load
- ensuredPush(this.$options, 'mounted', function () {
- if (!this$1.$root._vueMeta.initialized) {
+ ensuredPush(this.$options, 'mounted', () => {
+ if (!this.$root._vueMeta.initialized) {
// used in triggerUpdate to check if a change was triggered
// during initialization
- this$1.$root._vueMeta.initializing = true;
+ this.$root._vueMeta.initializing = true; // refresh meta in nextTick so all child components have loaded
- // refresh meta in nextTick so all child components have loaded
- this$1.$nextTick(function () {
- var this$1 = this;
-
- var ref = this.$root.$meta().refresh();
- var tags = ref.tags;
- var metaInfo = ref.metaInfo;
-
- // After ssr hydration (identifier by tags === false) check
+ this.$nextTick(function () {
+ const {
+ tags,
+ metaInfo
+ } = this.$root.$meta().refresh(); // After ssr hydration (identifier by tags === false) check
// if initialized was set to null in triggerUpdate. That'd mean
// that during initilazation changes where triggered which need
// to be applied OR a metaInfo watcher was triggered before the
// current hook was called
// (during initialization all changes are blocked)
+
if (tags === false && this.$root._vueMeta.initialized === null) {
- this.$nextTick(function () { return triggerUpdate(this$1, 'initializing'); });
+ this.$nextTick(() => triggerUpdate(this, 'initializing'));
}
this.$root._vueMeta.initialized = true;
- delete this.$root._vueMeta.initializing;
-
- // add the navigation guards if they havent been added yet
+ delete this.$root._vueMeta.initializing; // add the navigation guards if they havent been added yet
// they are needed for the afterNavigation callback
+
if (!options.refreshOnceOnNavigation && metaInfo.afterNavigation) {
addNavGuards(this);
}
});
}
- });
+ }); // add the navigation guards if requested
- // add the navigation guards if requested
if (options.refreshOnceOnNavigation) {
addNavGuards(this);
}
}
- }
+ } // do not trigger refresh on the server side
+
- // do not trigger refresh on the server side
if (!this.$isServer) {
// no need to add this hooks on server side
- updateOnLifecycleHook.forEach(function (lifecycleHook) {
- ensuredPush(this$1.$options, lifecycleHook, function () { return triggerUpdate(this$1, lifecycleHook); });
- });
+ updateOnLifecycleHook.forEach(lifecycleHook => {
+ ensuredPush(this.$options, lifecycleHook, () => triggerUpdate(this, lifecycleHook));
+ }); // re-render meta data when returning from a child component to parent
- // re-render meta data when returning from a child component to parent
- ensuredPush(this.$options, 'destroyed', function () {
+ ensuredPush(this.$options, 'destroyed', () => {
// Wait that element is hidden before refreshing meta tags (to support animations)
- var interval = setInterval(function () {
- if (this$1.$el && this$1.$el.offsetParent !== null) {
+ const interval = setInterval(() => {
+ if (this.$el && this.$el.offsetParent !== null) {
/* istanbul ignore next line */
- return
+ return;
}
clearInterval(interval);
- if (!this$1.$parent) {
+ if (!this.$parent) {
/* istanbul ignore next line */
- return
+ return;
}
- triggerUpdate(this$1, 'destroyed');
+ triggerUpdate(this, 'destroyed');
}, 50);
});
}
}
}
- }
+
+ };
}
/**
* These are constant variables used throughout the application.
*/
-
// set some sane defaults
-var defaultInfo = {
+const defaultInfo = {
title: undefined,
titleChunk: '',
titleTemplate: '%s',
@@ -310,183 +312,112 @@ var defaultInfo = {
script: [],
noscript: [],
__dangerouslyDisableSanitizers: [],
- __dangerouslyDisableSanitizersByTagID: {}
+ __dangerouslyDisableSanitizersByTagID: {} // 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.
+
};
-
-// 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.
-var keyName = 'metaInfo';
-
-// This is the attribute vue-meta arguments on elements to know which it should
+const keyName = 'metaInfo'; // This is the attribute vue-meta arguments on elements to know which it should
// manage and which it should ignore.
-var attribute = 'data-vue-meta';
-// This is the attribute that goes on the `html` tag to inform `vue-meta`
+const attribute = 'data-vue-meta'; // 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.
-var ssrAttribute = 'data-vue-meta-server-rendered';
-// This is the property that tells vue-meta to overwrite (instead of append)
+const ssrAttribute = '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.
-var tagIDKeyName = 'vmid';
-// This is the key name for possible meta templates
-var metaTemplateKeyName = 'template';
+const tagIDKeyName = 'vmid'; // This is the key name for possible meta templates
-// This is the key name for the content-holding property
-var contentKeyName = 'content';
+const metaTemplateKeyName = 'template'; // This is the key name for the content-holding property
+
+const contentKeyName = 'content'; // The id used for the ssr app
+
+const ssrAppId = 'ssr';
+const defaultOptions = {
+ keyName,
+ attribute,
+ ssrAttribute,
+ tagIDKeyName,
+ contentKeyName,
+ metaTemplateKeyName,
+ ssrAppId // List of metaInfo property keys which are configuration options (and dont generate html)
-var defaultOptions = {
- keyName: keyName,
- attribute: attribute,
- ssrAttribute: ssrAttribute,
- tagIDKeyName: tagIDKeyName,
- contentKeyName: contentKeyName,
- metaTemplateKeyName: metaTemplateKeyName
};
+const metaInfoOptionKeys = ['titleChunk', 'titleTemplate', 'changed', '__dangerouslyDisableSanitizers', '__dangerouslyDisableSanitizersByTagID']; // The metaInfo property keys which are used to disable escaping
-// List of metaInfo property keys which are configuration options (and dont generate html)
-var metaInfoOptionKeys = [
- 'titleChunk',
- 'titleTemplate',
- 'changed',
- '__dangerouslyDisableSanitizers',
- '__dangerouslyDisableSanitizersByTagID'
-];
+const disableOptionKeys = ['__dangerouslyDisableSanitizers', '__dangerouslyDisableSanitizersByTagID']; // List of metaInfo property keys which only generates attributes and no tags
-// The metaInfo property keys which are used to disable escaping
-var disableOptionKeys = [
- '__dangerouslyDisableSanitizers',
- '__dangerouslyDisableSanitizersByTagID'
-];
+const metaInfoAttributeKeys = ['htmlAttrs', 'headAttrs', 'bodyAttrs']; // HTML elements which support the onload event
-// List of metaInfo property keys which only generates attributes and no tags
-var metaInfoAttributeKeys = [
- 'htmlAttrs',
- 'headAttrs',
- 'bodyAttrs'
-];
-
-// HTML elements which dont have a head tag (shortened to our needs)
+const tagsSupportingOnload = ['link', 'style', 'script']; // HTML elements which dont have a head tag (shortened to our needs)
// see: https://www.w3.org/TR/html52/document-metadata.html
-var tagsWithoutEndTag = ['base', 'meta', 'link'];
-// HTML elements which can have inner content (shortened to our needs)
-var tagsWithInnerContent = ['noscript', 'script', 'style'];
+const tagsWithoutEndTag = ['base', 'meta', 'link']; // HTML elements which can have inner content (shortened to our needs)
-// Attributes which are inserted as childNodes instead of HTMLAttribute
-var tagAttributeAsInnerContent = ['innerHTML', 'cssText'];
+const tagsWithInnerContent = ['noscript', 'script', 'style']; // Attributes which are inserted as childNodes instead of HTMLAttribute
-// from: https://github.com/kangax/html-minifier/blob/gh-pages/src/htmlminifier.js#L202
-var booleanHtmlAttributes = [
- 'allowfullscreen',
- 'amp',
- 'async',
- 'autofocus',
- 'autoplay',
- 'checked',
- 'compact',
- 'controls',
- 'declare',
- 'default',
- 'defaultchecked',
- 'defaultmuted',
- 'defaultselected',
- 'defer',
- 'disabled',
- 'enabled',
- 'formnovalidate',
- 'hidden',
- 'indeterminate',
- 'inert',
- 'ismap',
- 'itemscope',
- 'loop',
- 'multiple',
- 'muted',
- 'nohref',
- 'noresize',
- 'noshade',
- 'novalidate',
- 'nowrap',
- 'open',
- 'pauseonexit',
- 'readonly',
- 'required',
- 'reversed',
- 'scoped',
- 'seamless',
- 'selected',
- 'sortable',
- 'truespeed',
- 'typemustmatch',
- 'visible'
-];
+const tagAttributeAsInnerContent = ['innerHTML', 'cssText', 'json']; // Attributes which should be added with data- prefix
-function setOptions (options) {
+const commonDataAttributes = ['body', 'pbody']; // from: https://github.com/kangax/html-minifier/blob/gh-pages/src/htmlminifier.js#L202
+
+const booleanHtmlAttributes = ['allowfullscreen', 'amp', 'async', 'autofocus', 'autoplay', 'checked', 'compact', 'controls', 'declare', 'default', 'defaultchecked', 'defaultmuted', 'defaultselected', 'defer', 'disabled', 'enabled', 'formnovalidate', 'hidden', 'indeterminate', 'inert', 'ismap', 'itemscope', 'loop', 'multiple', 'muted', 'nohref', 'noresize', 'noshade', 'novalidate', 'nowrap', 'open', 'pauseonexit', 'readonly', 'required', 'reversed', 'scoped', 'seamless', 'selected', 'sortable', 'truespeed', 'typemustmatch', 'visible'];
+
+function setOptions(options) {
// combine options
options = isObject(options) ? options : {};
- for (var key in defaultOptions) {
+ for (const key in defaultOptions) {
if (!options[key]) {
options[key] = defaultOptions[key];
}
}
- return options
+ return options;
}
+function getOptions(options) {
+ const optionsCopy = {};
-function getOptions (options) {
- var optionsCopy = {};
- for (var key in options) {
+ for (const key in options) {
optionsCopy[key] = options[key];
}
- return optionsCopy
+
+ return optionsCopy;
}
-function pause (refresh) {
- if ( refresh === void 0 ) refresh = true;
-
+function pause(refresh = true) {
this.$root._vueMeta.paused = true;
-
- return function () { return resume(refresh); }
+ return () => resume(refresh);
}
-
-function resume (refresh) {
- if ( refresh === void 0 ) refresh = true;
-
+function resume(refresh = true) {
this.$root._vueMeta.paused = false;
if (refresh) {
- return this.$root.$meta().refresh()
+ return this.$root.$meta().refresh();
}
}
-function applyTemplate (ref, headObject, template, chunk) {
- var component = ref.component;
- var metaTemplateKeyName = ref.metaTemplateKeyName;
- var contentKeyName = ref.contentKeyName;
-
+function applyTemplate({
+ component,
+ metaTemplateKeyName,
+ contentKeyName
+}, headObject, template, chunk) {
if (isUndefined(template)) {
template = headObject[metaTemplateKeyName];
delete headObject[metaTemplateKeyName];
- }
+ } // return early if no template defined
+
- // return early if no template defined
if (!template) {
- return false
+ return false;
}
if (isUndefined(chunk)) {
chunk = headObject[contentKeyName];
}
- headObject[contentKeyName] = isFunction(template)
- ? template.call(component, chunk)
- : template.replace(/%s/g, chunk);
-
- return true
+ headObject[contentKeyName] = isFunction(template) ? template.call(component, chunk) : template.replace(/%s/g, chunk);
+ return true;
}
/*
@@ -497,179 +428,185 @@ function applyTemplate (ref, headObject, template, chunk) {
* Also, only files in client/ & shared/ should use these functions
* files in server/ still use normal js function
*/
-
-function findIndex (array, predicate) {
- var arguments$1 = arguments;
-
+function findIndex(array, predicate) {
if ( !Array.prototype.findIndex) {
// idx needs to be a Number, for..in returns string
- for (var idx = 0; idx < array.length; idx++) {
- if (predicate.call(arguments$1[2], array[idx], idx, array)) {
- return idx
+ for (let idx = 0; idx < array.length; idx++) {
+ if (predicate.call(arguments[2], array[idx], idx, array)) {
+ return idx;
}
}
- return -1
- }
- return array.findIndex(predicate, arguments[2])
-}
-function toArray (arg) {
+ return -1;
+ }
+
+ return array.findIndex(predicate, arguments[2]);
+}
+function toArray(arg) {
if ( !Array.from) {
- return Array.prototype.slice.call(arg)
+ return Array.prototype.slice.call(arg);
}
- return Array.from(arg)
-}
-function includes (array, value) {
+ return Array.from(arg);
+}
+function includes(array, value) {
if ( !Array.prototype.includes) {
- for (var idx in array) {
+ for (const idx in array) {
if (array[idx] === value) {
- return true
+ return true;
}
}
- return false
+ return false;
}
- return array.includes(value)
+
+ return array.includes(value);
}
-var serverSequences = [
- [/&/g, '&'],
- [//g, '>'],
- [/"/g, '"'],
- [/'/g, ''']
-];
+const serverSequences = [[/&/g, '&'], [//g, '>'], [/"/g, '"'], [/'/g, ''']];
+const clientSequences = [[/&/g, '\u0026'], [//g, '\u003E'], [/"/g, '\u0022'], [/'/g, '\u0027']]; // sanitizes potentially dangerous characters
-var clientSequences = [
- [/&/g, '\u0026'],
- [//g, '\u003E'],
- [/"/g, '\u0022'],
- [/'/g, '\u0027']
-];
+function escape(info, options, escapeOptions) {
+ const {
+ tagIDKeyName
+ } = options;
+ const {
+ doEscape = v => v,
+ escapeKeys
+ } = escapeOptions;
+ const escaped = {};
-// sanitizes potentially dangerous characters
-function escape (info, options, escapeOptions) {
- var tagIDKeyName = options.tagIDKeyName;
- var doEscape = escapeOptions.doEscape; if ( doEscape === void 0 ) doEscape = function (v) { return v; };
- var escaped = {};
+ for (const key in info) {
+ const value = info[key]; // no need to escape configuration options
- for (var key in info) {
- var value = info[key];
-
- // no need to escape configuration options
if (includes(metaInfoOptionKeys, key)) {
escaped[key] = value;
- continue
+ continue;
}
- var disableKey = disableOptionKeys[0];
+ let [disableKey] = disableOptionKeys;
+
if (escapeOptions[disableKey] && includes(escapeOptions[disableKey], key)) {
// this info[key] doesnt need to escaped if the option is listed in __dangerouslyDisableSanitizers
escaped[key] = value;
- continue
+ continue;
}
- var tagId = info[tagIDKeyName];
+ const tagId = info[tagIDKeyName];
+
if (tagId) {
- disableKey = disableOptionKeys[1];
+ disableKey = disableOptionKeys[1]; // keys which are listed in __dangerouslyDisableSanitizersByTagID for the current vmid do not need to be escaped
- // keys which are listed in __dangerouslyDisableSanitizersByTagID for the current vmid do not need to be escaped
if (escapeOptions[disableKey] && escapeOptions[disableKey][tagId] && includes(escapeOptions[disableKey][tagId], key)) {
escaped[key] = value;
- continue
+ continue;
}
}
if (isString(value)) {
escaped[key] = doEscape(value);
} else if (isArray(value)) {
- escaped[key] = value.map(function (v) {
- return isObject(v)
- ? escape(v, options, escapeOptions)
- : doEscape(v)
+ escaped[key] = value.map(v => {
+ if (isPureObject(v)) {
+ return escape(v, options, { ...escapeOptions,
+ escapeKeys: true
+ });
+ }
+
+ return doEscape(v);
+ });
+ } else if (isPureObject(value)) {
+ escaped[key] = escape(value, options, { ...escapeOptions,
+ escapeKeys: true
});
- } else if (isObject(value)) {
- escaped[key] = escape(value, options, escapeOptions);
} else {
escaped[key] = value;
}
+
+ if (escapeKeys) {
+ const escapedKey = doEscape(key);
+
+ if (key !== escapedKey) {
+ escaped[escapedKey] = escaped[key];
+ delete escaped[key];
+ }
+ }
}
- return escaped
+ return escaped;
}
-function arrayMerge (ref, target, source) {
- var component = ref.component;
- var tagIDKeyName = ref.tagIDKeyName;
- var metaTemplateKeyName = ref.metaTemplateKeyName;
- var contentKeyName = ref.contentKeyName;
-
+function arrayMerge({
+ component,
+ tagIDKeyName,
+ metaTemplateKeyName,
+ contentKeyName
+}, target, source) {
// we concat the arrays without merging objects contained in,
// but we check for a `vmid` property on each object in the array
// using an O(1) lookup associative array exploit
- var destination = [];
-
- target.forEach(function (targetItem, targetIndex) {
+ const destination = [];
+ target.forEach((targetItem, targetIndex) => {
// no tagID so no need to check for duplicity
if (!targetItem[tagIDKeyName]) {
destination.push(targetItem);
- return
+ return;
}
- var sourceIndex = findIndex(source, function (item) { return item[tagIDKeyName] === targetItem[tagIDKeyName]; });
- var sourceItem = source[sourceIndex];
+ const sourceIndex = findIndex(source, item => item[tagIDKeyName] === targetItem[tagIDKeyName]);
+ const sourceItem = source[sourceIndex]; // source doesnt contain any duplicate vmid's, we can keep targetItem
- // source doesnt contain any duplicate vmid's, we can keep targetItem
if (sourceIndex === -1) {
destination.push(targetItem);
- return
- }
-
- // when sourceItem explictly defines contentKeyName or innerHTML as undefined, its
+ return;
+ } // when sourceItem explictly defines contentKeyName or innerHTML as undefined, its
// an indication that we need to skip the default behaviour or child has preference over parent
// which means we keep the targetItem and ignore/remove the sourceItem
- if ((sourceItem.hasOwnProperty(contentKeyName) && sourceItem[contentKeyName] === undefined) ||
- (sourceItem.hasOwnProperty('innerHTML') && sourceItem.innerHTML === undefined)) {
- destination.push(targetItem);
- // remove current index from source array so its not concatenated to destination below
+
+
+ if (sourceItem.hasOwnProperty(contentKeyName) && sourceItem[contentKeyName] === undefined || sourceItem.hasOwnProperty('innerHTML') && sourceItem.innerHTML === undefined) {
+ destination.push(targetItem); // remove current index from source array so its not concatenated to destination below
+
source.splice(sourceIndex, 1);
- return
- }
-
- // we now know that targetItem is a duplicate and we should ignore it in favor of sourceItem
-
+ return;
+ } // we now know that targetItem is a duplicate and we should ignore it in favor of sourceItem
// if source specifies null as content then ignore both the target as the source
+
+
if (sourceItem[contentKeyName] === null || sourceItem.innerHTML === null) {
// remove current index from source array so its not concatenated to destination below
source.splice(sourceIndex, 1);
- return
- }
+ return;
+ } // now we only need to check if the target has a template to combine it with the source
+
+
+ const targetTemplate = targetItem[metaTemplateKeyName];
- // now we only need to check if the target has a template to combine it with the source
- var targetTemplate = targetItem[metaTemplateKeyName];
if (!targetTemplate) {
- return
+ return;
}
- var sourceTemplate = sourceItem[metaTemplateKeyName];
+ const sourceTemplate = sourceItem[metaTemplateKeyName];
if (!sourceTemplate) {
// use parent template and child content
- applyTemplate({ component: component, metaTemplateKeyName: metaTemplateKeyName, contentKeyName: contentKeyName }, sourceItem, targetTemplate);
+ applyTemplate({
+ component,
+ metaTemplateKeyName,
+ contentKeyName
+ }, sourceItem, targetTemplate);
} else if (!sourceItem[contentKeyName]) {
// use child template and parent content
- applyTemplate({ component: component, metaTemplateKeyName: metaTemplateKeyName, contentKeyName: contentKeyName }, sourceItem, undefined, targetItem[contentKeyName]);
+ applyTemplate({
+ component,
+ metaTemplateKeyName,
+ contentKeyName
+ }, sourceItem, undefined, targetItem[contentKeyName]);
}
});
-
- return destination.concat(source)
+ return destination.concat(source);
}
-
-function merge (target, source, options) {
- if ( options === void 0 ) options = {};
-
+function merge(target, source, options = {}) {
// remove properties explicitly set to false so child components can
// optionally _not_ overwrite the parents content
// (for array properties this is checked in arrayMerge)
@@ -677,25 +614,24 @@ function merge (target, source, options) {
delete source.title;
}
- metaInfoAttributeKeys.forEach(function (attrKey) {
+ metaInfoAttributeKeys.forEach(attrKey => {
if (!source[attrKey]) {
- return
+ return;
}
- for (var key in source[attrKey]) {
+ for (const key in source[attrKey]) {
if (source[attrKey].hasOwnProperty(key) && source[attrKey][key] === undefined) {
- if (booleanHtmlAttributes.includes(key)) {
- // eslint-disable-next-line no-console
- console.warn('VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details');
+ if (includes(booleanHtmlAttributes, key)) {
+ warn('VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details');
}
+
delete source[attrKey][key];
}
}
});
-
return deepmerge(target, source, {
- arrayMerge: function (t, s) { return arrayMerge(options, t, s); }
- })
+ arrayMerge: (t, s) => arrayMerge(options, t, s)
+ });
}
/**
@@ -712,45 +648,46 @@ function merge (target, source, options) {
* @param {Object} [result={}] - result so far
* @return {Object} result - final aggregated result
*/
-function getComponentOption (options, component, result) {
- if ( options === void 0 ) options = {};
- if ( result === void 0 ) result = {};
- var keyName = options.keyName;
- var metaTemplateKeyName = options.metaTemplateKeyName;
- var tagIDKeyName = options.tagIDKeyName;
- var $options = component.$options;
- var $children = component.$children;
+function getComponentOption(options = {}, component, result = {}) {
+ const {
+ keyName,
+ metaTemplateKeyName,
+ tagIDKeyName
+ } = options;
+ const {
+ $options,
+ $children
+ } = component;
if (component._inactive) {
- return result
- }
+ return result;
+ } // only collect option data if it exists
+
- // only collect option data if it exists
if ($options[keyName]) {
- var data = $options[keyName];
+ let data = $options[keyName]; // if option is a function, replace it with it's result
- // if option is a function, replace it with it's result
if (isFunction(data)) {
data = data.call(component);
- }
+ } // ignore data if its not an object, then we keep our previous result
+
- // ignore data if its not an object, then we keep our previous result
if (!isObject(data)) {
- return result
- }
+ return result;
+ } // merge with existing options
+
- // merge with existing options
result = merge(result, data, options);
- }
+ } // collect & aggregate child options if deep = true
+
- // collect & aggregate child options if deep = true
if ($children.length) {
- $children.forEach(function (childComponent) {
+ $children.forEach(childComponent => {
// check if the childComponent is in a branch
// return otherwise so we dont walk all component branches unnecessarily
if (!inMetaInfoBranch(childComponent)) {
- return
+ return;
}
result = getComponentOption(options, childComponent, result);
@@ -759,20 +696,17 @@ function getComponentOption (options, component, result) {
if (metaTemplateKeyName && result.meta) {
// apply templates if needed
- result.meta.forEach(function (metaObject) { return applyTemplate(options, metaObject); });
+ result.meta.forEach(metaObject => applyTemplate(options, metaObject)); // remove meta items with duplicate vmid's
- // remove meta items with duplicate vmid's
- result.meta = result.meta.filter(function (metaItem, index, arr) {
- return (
- // keep meta item if it doesnt has a vmid
- !metaItem.hasOwnProperty(tagIDKeyName) ||
- // or if it's the first item in the array with this vmid
- index === findIndex(arr, function (item) { return item[tagIDKeyName] === metaItem[tagIDKeyName]; })
- )
+ result.meta = result.meta.filter((metaItem, index, arr) => {
+ return (// keep meta item if it doesnt has a vmid
+ !metaItem.hasOwnProperty(tagIDKeyName) || // or if it's the first item in the array with this vmid
+ index === findIndex(arr, item => item[tagIDKeyName] === metaItem[tagIDKeyName])
+ );
});
}
- return result
+ return result;
}
/**
@@ -782,56 +716,180 @@ function getComponentOption (options, component, result) {
* @param {Object} component - the Vue instance to get meta info from
* @return {Object} - returned meta info
*/
-function getMetaInfo (options, component, escapeSequences) {
- if ( options === void 0 ) options = {};
- if ( escapeSequences === void 0 ) escapeSequences = [];
+function getMetaInfo(options = {}, component, escapeSequences = []) {
// collect & aggregate all metaInfo $options
- var info = getComponentOption(options, component, defaultInfo);
-
- // Remove all "template" tags from meta
-
+ let info = getComponentOption(options, component, defaultInfo); // Remove all "template" tags from meta
// backup the title chunk in case user wants access to it
+
if (info.title) {
info.titleChunk = info.title;
- }
+ } // replace title with populated template
+
- // replace title with populated template
if (info.titleTemplate && info.titleTemplate !== '%s') {
- applyTemplate({ component: component, contentKeyName: 'title' }, info, info.titleTemplate, info.titleChunk || '');
- }
-
- // convert base tag to an array so it can be handled the same way
+ applyTemplate({
+ component,
+ contentKeyName: 'title'
+ }, info, info.titleTemplate, 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] : [];
}
- var escapeOptions = {
- doEscape: function (value) { return escapeSequences.reduce(function (val, ref) {
- var v = ref[0];
- var r = ref[1];
-
- return val.replace(v, r);
- }, value); }
+ const escapeOptions = {
+ doEscape: value => escapeSequences.reduce((val, [v, r]) => val.replace(v, r), value)
};
-
- disableOptionKeys.forEach(function (disableKey, index) {
+ disableOptionKeys.forEach((disableKey, index) => {
if (index === 0) {
ensureIsArray(info, disableKey);
} else if (index === 1) {
- for (var key in info[disableKey]) {
+ for (const key in info[disableKey]) {
ensureIsArray(info[disableKey], key);
}
}
escapeOptions[disableKey] = info[disableKey];
- });
+ }); // begin sanitization
- // begin sanitization
info = escape(info, options, escapeOptions);
+ return info;
+}
- return info
+function getTag(tags, tag) {
+ if (!tags[tag]) {
+ tags[tag] = document.getElementsByTagName(tag)[0];
+ }
+
+ return tags[tag];
+}
+function getElementsKey({
+ body,
+ pbody
+}) {
+ return body ? 'body' : pbody ? 'pbody' : 'head';
+}
+function queryElements(parentNode, {
+ appId,
+ attribute,
+ type,
+ tagIDKeyName
+}, attributes = {}) {
+ const queries = [`${type}[${attribute}="${appId}"]`, `${type}[data-${tagIDKeyName}]`].map(query => {
+ for (const key in attributes) {
+ const val = attributes[key];
+ const attributeValue = val && val !== true ? `="${val}"` : '';
+ query += `[data-${key}${attributeValue}]`;
+ }
+
+ return query;
+ });
+ return toArray(parentNode.querySelectorAll(queries.join(', ')));
+}
+
+const callbacks = [];
+function isDOMComplete(d = document) {
+ return d.readyState === 'complete';
+}
+function addCallback(query, callback) {
+ if (arguments.length === 1) {
+ callback = query;
+ query = '';
+ }
+
+ callbacks.push([query, callback]);
+}
+function addCallbacks({
+ tagIDKeyName
+}, type, tags, autoAddListeners) {
+ let hasAsyncCallback = false;
+
+ for (const tag of tags) {
+ if (!tag[tagIDKeyName] || !tag.callback) {
+ continue;
+ }
+
+ hasAsyncCallback = true;
+ addCallback(`${type}[data-${tagIDKeyName}="${tag[tagIDKeyName]}"]`, tag.callback);
+ }
+
+ if (!autoAddListeners || !hasAsyncCallback) {
+ return hasAsyncCallback;
+ }
+
+ return addListeners();
+}
+function addListeners() {
+ if (isDOMComplete()) {
+ applyCallbacks();
+ return;
+ } // Instead of using a MutationObserver, we just apply
+
+ /* istanbul ignore next */
+
+
+ document.onreadystatechange = () => {
+ applyCallbacks();
+ };
+}
+function applyCallbacks(matchElement) {
+ for (const [query, callback] of callbacks) {
+ const selector = `${query}[onload="this.__vm_l=1"]`;
+ let elements = [];
+
+ if (!matchElement) {
+ elements = toArray(document.querySelectorAll(selector));
+ }
+
+ if (matchElement && matchElement.matches(selector)) {
+ elements = [matchElement];
+ }
+
+ for (const element of elements) {
+ /* __vm_cb: whether the load callback has been called
+ * __vm_l: set by onload attribute, whether the element was loaded
+ * __vm_ev: whether the event listener was added or not
+ */
+ if (element.__vm_cb) {
+ continue;
+ }
+
+ const onload = () => {
+ /* Mark that the callback for this element has already been called,
+ * this prevents the callback to run twice in some (rare) conditions
+ */
+ element.__vm_cb = true;
+ /* onload needs to be removed because we only need the
+ * attribute after ssr and if we dont remove it the node
+ * will fail isEqualNode on the client
+ */
+
+ element.removeAttribute('onload');
+ callback(element);
+ };
+ /* IE9 doesnt seem to load scripts synchronously,
+ * causing a script sometimes/often already to be loaded
+ * when we add the event listener below (thus adding an onload event
+ * listener has no use because it will never be triggered).
+ * Therefore we add the onload attribute during ssr, and
+ * check here if it was already loaded or not
+ */
+
+
+ if (element.__vm_l) {
+ onload();
+ continue;
+ }
+
+ if (!element.__vm_ev) {
+ element.__vm_ev = true;
+ element.addEventListener('load', onload);
+ }
+ }
+ }
}
/**
@@ -840,43 +898,38 @@ function getMetaInfo (options, component, escapeSequences) {
* @param {Object} attrs - the new document html attributes
* @param {HTMLElement} tag - the HTMLElement tag to update with new attrs
*/
-function updateAttribute (ref, attrs, tag) {
- if ( ref === void 0 ) ref = {};
- var attribute = ref.attribute;
- var vueMetaAttrString = tag.getAttribute(attribute);
- var vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : [];
- var toRemove = toArray(vueMetaAttrs);
+function updateAttribute({
+ attribute
+} = {}, attrs, tag) {
+ const vueMetaAttrString = tag.getAttribute(attribute);
+ const vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : [];
+ const toRemove = toArray(vueMetaAttrs);
+ const keepIndexes = [];
- var keepIndexes = [];
- for (var attr in attrs) {
+ for (const attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
- var value = includes(booleanHtmlAttributes, attr)
- ? ''
- : isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr];
-
+ const value = includes(booleanHtmlAttributes, attr) ? '' : isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr];
tag.setAttribute(attr, value || '');
if (!includes(vueMetaAttrs, attr)) {
vueMetaAttrs.push(attr);
- }
+ } // filter below wont ever check -1
+
- // filter below wont ever check -1
keepIndexes.push(toRemove.indexOf(attr));
}
}
- var removedAttributesCount = toRemove
- .filter(function (el, index) { return !includes(keepIndexes, index); })
- .reduce(function (acc, attr) {
- tag.removeAttribute(attr);
- return acc + 1
- }, 0);
+ const removedAttributesCount = toRemove.filter((el, index) => !includes(keepIndexes, index)).reduce((acc, attr) => {
+ tag.removeAttribute(attr);
+ return acc + 1;
+ }, 0);
if (vueMetaAttrs.length === removedAttributesCount) {
tag.removeAttribute(attribute);
} else {
- tag.setAttribute(attribute, (vueMetaAttrs.sort()).join(','));
+ tag.setAttribute(attribute, vueMetaAttrs.sort().join(','));
}
}
@@ -885,9 +938,9 @@ function updateAttribute (ref, attrs, tag) {
*
* @param {String} title - the new title of the document
*/
-function updateTitle (title) {
- if (title === undefined) {
- return
+function updateTitle(title) {
+ if (!title && title !== '') {
+ return;
}
document.title = title;
@@ -901,98 +954,143 @@ function updateTitle (title) {
* @param {(Array|Object)} tags - an array of tag objects or a single object in case of base
* @return {Object} - a representation of what tags changed
*/
-function updateTag (appId, ref, type, tags, headTag, bodyTag) {
- if ( ref === void 0 ) ref = {};
- var attribute = ref.attribute;
- var tagIDKeyName = ref.tagIDKeyName;
- var oldHeadTags = toArray(headTag.querySelectorAll((type + "[" + attribute + "=\"" + appId + "\"], " + type + "[data-" + tagIDKeyName + "]")));
- var oldBodyTags = toArray(bodyTag.querySelectorAll((type + "[" + attribute + "=\"" + appId + "\"][data-body=\"true\"], " + type + "[data-" + tagIDKeyName + "][data-body=\"true\"]")));
- var dataAttributes = [tagIDKeyName, 'body'];
- var newTags = [];
+function updateTag(appId, options = {}, type, tags, head, body) {
+ const {
+ attribute,
+ tagIDKeyName
+ } = options;
+ const dataAttributes = [tagIDKeyName, ...commonDataAttributes];
+ const newElements = [];
+ const queryOptions = {
+ appId,
+ attribute,
+ type,
+ tagIDKeyName
+ };
+ const currentElements = {
+ head: queryElements(head, queryOptions),
+ pbody: queryElements(body, queryOptions, {
+ pbody: true
+ }),
+ body: queryElements(body, queryOptions, {
+ body: true
+ })
+ };
if (tags.length > 1) {
// remove duplicates that could have been found by merging tags
// which include a mixin with metaInfo and that mixin is used
// by multiple components on the same page
- var found = [];
- tags = tags.filter(function (x) {
- var k = JSON.stringify(x);
- var res = !includes(found, k);
+ const found = [];
+ tags = tags.filter(x => {
+ const k = JSON.stringify(x);
+ const res = !includes(found, k);
found.push(k);
- return res
+ return res;
});
}
if (tags.length) {
- tags.forEach(function (tag) {
- var newElement = document.createElement(type);
-
- newElement.setAttribute(attribute, appId);
-
- var oldTags = tag.body !== true ? oldHeadTags : oldBodyTags;
-
- for (var attr in tag) {
- if (tag.hasOwnProperty(attr)) {
- if (attr === 'innerHTML') {
- newElement.innerHTML = tag.innerHTML;
- } else if (attr === 'cssText') {
- if (newElement.styleSheet) {
- /* istanbul ignore next */
- newElement.styleSheet.cssText = tag.cssText;
- } else {
- newElement.appendChild(document.createTextNode(tag.cssText));
- }
- } else {
- var _attr = includes(dataAttributes, attr)
- ? ("data-" + attr)
- : attr;
-
- var isBooleanAttribute = includes(booleanHtmlAttributes, attr);
- if (isBooleanAttribute && !tag[attr]) {
- continue
- }
-
- var value = isBooleanAttribute ? '' : tag[attr];
- newElement.setAttribute(_attr, value);
- }
- }
+ for (const tag of tags) {
+ if (tag.skip) {
+ continue;
}
- // Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
- var indexToDelete;
- var hasEqualElement = oldTags.some(function (existingTag, index) {
+ const newElement = document.createElement(type);
+ newElement.setAttribute(attribute, appId);
+
+ for (const attr in tag) {
+ /* istanbul ignore next */
+ if (!tag.hasOwnProperty(attr)) {
+ continue;
+ }
+
+ if (attr === 'innerHTML') {
+ newElement.innerHTML = tag.innerHTML;
+ continue;
+ }
+
+ if (attr === 'json') {
+ newElement.innerHTML = JSON.stringify(tag.json);
+ continue;
+ }
+
+ if (attr === 'cssText') {
+ if (newElement.styleSheet) {
+ /* istanbul ignore next */
+ newElement.styleSheet.cssText = tag.cssText;
+ } else {
+ newElement.appendChild(document.createTextNode(tag.cssText));
+ }
+
+ continue;
+ }
+
+ if (attr === 'callback') {
+ newElement.onload = () => tag[attr](newElement);
+
+ continue;
+ }
+
+ const _attr = includes(dataAttributes, attr) ? `data-${attr}` : attr;
+
+ const isBooleanAttribute = includes(booleanHtmlAttributes, attr);
+
+ if (isBooleanAttribute && !tag[attr]) {
+ continue;
+ }
+
+ const value = isBooleanAttribute ? '' : tag[attr];
+ newElement.setAttribute(_attr, value);
+ }
+
+ const oldElements = currentElements[getElementsKey(tag)]; // Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
+
+ let indexToDelete;
+ const hasEqualElement = oldElements.some((existingTag, index) => {
indexToDelete = index;
- return newElement.isEqualNode(existingTag)
+ return newElement.isEqualNode(existingTag);
});
if (hasEqualElement && (indexToDelete || indexToDelete === 0)) {
- oldTags.splice(indexToDelete, 1);
+ oldElements.splice(indexToDelete, 1);
} else {
- newTags.push(newElement);
+ newElements.push(newElement);
}
- });
- }
-
- var oldTags = oldHeadTags.concat(oldBodyTags);
- oldTags.forEach(function (tag) { return tag.parentNode.removeChild(tag); });
- newTags.forEach(function (tag) {
- if (tag.getAttribute('data-body') === 'true') {
- bodyTag.appendChild(tag);
- } else {
- headTag.appendChild(tag);
}
- });
-
- return { oldTags: oldTags, newTags: newTags }
-}
-
-function getTag (tags, tag) {
- if (!tags[tag]) {
- tags[tag] = document.getElementsByTagName(tag)[0];
}
- return tags[tag]
+ let oldElements = [];
+
+ for (const current of Object.values(currentElements)) {
+ oldElements = [...oldElements, ...current];
+ } // remove old elements
+
+
+ for (const element of oldElements) {
+ element.parentNode.removeChild(element);
+ } // insert new elements
+
+
+ for (const element of newElements) {
+ if (element.hasAttribute('data-body')) {
+ body.appendChild(element);
+ continue;
+ }
+
+ if (element.hasAttribute('data-pbody')) {
+ body.insertBefore(element, body.firstChild);
+ continue;
+ }
+
+ head.appendChild(element);
+ }
+
+ return {
+ oldTags: oldElements,
+ newTags: newElements
+ };
}
/**
@@ -1000,60 +1098,66 @@ function getTag (tags, tag) {
*
* @param {Object} newInfo - the meta info to update to
*/
-function updateClientMetaInfo (appId, options, newInfo) {
- if ( options === void 0 ) options = {};
- var ssrAttribute = options.ssrAttribute;
+function updateClientMetaInfo(appId, options = {}, newInfo) {
+ const {
+ ssrAttribute,
+ ssrAppId
+ } = options; // only cache tags for current update
- // only cache tags for current update
- var tags = {};
+ const tags = {};
+ const htmlTag = getTag(tags, 'html'); // if this is a server render, then dont update
- var htmlTag = getTag(tags, 'html');
-
- // if this is a server render, then dont update
- if (appId === 'ssr' && htmlTag.hasAttribute(ssrAttribute)) {
+ if (appId === ssrAppId && htmlTag.hasAttribute(ssrAttribute)) {
// remove the server render attribute so we can update on (next) changes
- htmlTag.removeAttribute(ssrAttribute);
- return false
- }
+ htmlTag.removeAttribute(ssrAttribute); // add load callbacks if the
- // initialize tracked changes
- var addedTags = {};
- var removedTags = {};
+ let addLoadListeners = false;
- for (var type in newInfo) {
+ for (const type of tagsSupportingOnload) {
+ if (newInfo[type] && addCallbacks(options, type, newInfo[type])) {
+ addLoadListeners = true;
+ }
+ }
+
+ if (addLoadListeners) {
+ addListeners();
+ }
+
+ return false;
+ } // initialize tracked changes
+
+
+ const addedTags = {};
+ const removedTags = {};
+
+ for (const type in newInfo) {
// ignore these
if (includes(metaInfoOptionKeys, type)) {
- continue
+ continue;
}
if (type === 'title') {
// update the title
updateTitle(newInfo.title);
- continue
+ continue;
}
if (includes(metaInfoAttributeKeys, type)) {
- var tagName = type.substr(0, 4);
+ const tagName = type.substr(0, 4);
updateAttribute(options, newInfo[type], getTag(tags, tagName));
- continue
- }
+ continue;
+ } // tags should always be an array, ignore if it isnt
+
- // tags should always be an array, ignore if it isnt
if (!isArray(newInfo[type])) {
- continue
+ continue;
}
- var ref = updateTag(
- appId,
- options,
- type,
- newInfo[type],
- getTag(tags, 'head'),
- getTag(tags, 'body')
- );
- var oldTags = ref.oldTags;
- var newTags = ref.newTags;
+ const {
+ oldTags,
+ newTags
+ } = updateTag(appId, options, type, newInfo[type], getTag(tags, 'head'), getTag(tags, 'body'));
if (newTags.length) {
addedTags[type] = newTags;
@@ -1061,12 +1165,13 @@ function updateClientMetaInfo (appId, options, newInfo) {
}
}
- return { addedTags: addedTags, removedTags: removedTags }
+ return {
+ addedTags,
+ removedTags
+ };
}
-function _refresh (options) {
- if ( options === void 0 ) options = {};
-
+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
@@ -1077,18 +1182,21 @@ function _refresh (options) {
*
* @return {Object} - new meta info
*/
- return function refresh () {
- var metaInfo = getMetaInfo(options, this.$root, clientSequences);
+ return function refresh() {
+ const metaInfo = getMetaInfo(options, this.$root, clientSequences);
+ const appId = this.$root._vueMeta.appId;
+ const tags = updateClientMetaInfo(appId, options, metaInfo); // emit "event" with new info
- var appId = this.$root._vueMeta.appId;
- var tags = updateClientMetaInfo(appId, options, metaInfo);
- // emit "event" with new info
if (tags && isFunction(metaInfo.changed)) {
metaInfo.changed(metaInfo, tags.addedTags, tags.removedTags);
}
- return { vm: this, metaInfo: metaInfo, tags: tags }
- }
+ return {
+ vm: this,
+ metaInfo,
+ tags
+ };
+ };
}
/**
@@ -1098,31 +1206,36 @@ function _refresh (options) {
* @param {Object} data - the attributes to generate
* @return {Object} - the attribute generator
*/
-function attributeGenerator (ref, type, data) {
- if ( ref === void 0 ) ref = {};
- var attribute = ref.attribute;
+function attributeGenerator({
+ attribute,
+ ssrAttribute
+} = {}, type, data) {
return {
- text: function text () {
- var attributeStr = '';
- var watchedAttrs = [];
+ text(addSrrAttribute) {
+ let attributeStr = '';
+ const watchedAttrs = [];
- for (var attr in data) {
+ for (const attr in data) {
if (data.hasOwnProperty(attr)) {
watchedAttrs.push(attr);
-
- attributeStr += isUndefined(data[attr]) || booleanHtmlAttributes.includes(attr)
- ? attr
- : (attr + "=\"" + (isArray(data[attr]) ? data[attr].join(' ') : data[attr]) + "\"");
-
+ attributeStr += isUndefined(data[attr]) || booleanHtmlAttributes.includes(attr) ? attr : `${attr}="${isArray(data[attr]) ? data[attr].join(' ') : data[attr]}"`;
attributeStr += ' ';
}
}
- attributeStr += attribute + "=\"" + ((watchedAttrs.sort()).join(',')) + "\"";
- return attributeStr
+ if (attributeStr) {
+ attributeStr += `${attribute}="${watchedAttrs.sort().join(',')}"`;
+ }
+
+ if (type === 'htmlAttrs' && addSrrAttribute) {
+ return `${ssrAttribute}${attributeStr ? ' ' : ''}${attributeStr}`;
+ }
+
+ return attributeStr;
}
- }
+
+ };
}
/**
@@ -1132,15 +1245,19 @@ function attributeGenerator (ref, type, data) {
* @param {String} data - the title text
* @return {Object} - the title generator
*/
-function titleGenerator (appId, ref, type, data) {
- if ( ref === void 0 ) ref = {};
- var attribute = ref.attribute;
-
+function titleGenerator({
+ attribute
+} = {}, type, data) {
return {
- text: function text () {
- return ("<" + type + ">" + data + "" + type + ">")
+ text() {
+ if (!data) {
+ return '';
+ }
+
+ return `<${type}>${data}${type}>`;
}
- }
+
+ };
}
/**
@@ -1150,72 +1267,82 @@ function titleGenerator (appId, ref, type, data) {
* @param {(Array|Object)} tags - an array of tag objects or a single object in case of base
* @return {Object} - the tag generator
*/
-function tagGenerator (appId, ref, type, tags) {
- if ( ref === void 0 ) ref = {};
- var attribute = ref.attribute;
- var tagIDKeyName = ref.tagIDKeyName;
+function tagGenerator({
+ ssrAppId,
+ attribute,
+ tagIDKeyName
+} = {}, type, tags) {
+ const dataAttributes = [tagIDKeyName, 'callback', ...commonDataAttributes];
return {
- text: function text (ref) {
- if ( ref === void 0 ) ref = {};
- var body = ref.body; if ( body === void 0 ) body = false;
-
+ text({
+ body = false,
+ pbody = false
+ } = {}) {
// build a string containing all tags of this type
- return tags.reduce(function (tagsStr, tag) {
- var tagKeys = Object.keys(tag);
+ return tags.reduce((tagsStr, tag) => {
+ if (tag.skip) {
+ return tagsStr;
+ }
+
+ const tagKeys = Object.keys(tag);
if (tagKeys.length === 0) {
- return tagsStr // Bail on empty tag object
+ return tagsStr; // Bail on empty tag object
}
- if (Boolean(tag.body) !== body) {
- return tagsStr
+ if (Boolean(tag.body) !== body || Boolean(tag.pbody) !== pbody) {
+ return tagsStr;
}
- // build a string containing all attributes of this tag
- var attrs = tagKeys.reduce(function (attrsStr, attr) {
+ let attrs = tag.once ? '' : ` ${attribute}="${ssrAppId}"`; // build a string containing all attributes of this tag
+
+ for (const attr in tag) {
// these attributes are treated as children on the tag
if (tagAttributeAsInnerContent.includes(attr) || attr === 'once') {
- return attrsStr
- }
+ continue;
+ } // these form the attribute list for this tag
- // these form the attribute list for this tag
- var prefix = '';
- if ([tagIDKeyName, 'body'].includes(attr)) {
+
+ let prefix = '';
+
+ if (dataAttributes.includes(attr)) {
prefix = 'data-';
}
- var isBooleanAttr = booleanHtmlAttributes.includes(attr);
- if (isBooleanAttr && !tag[attr]) {
- return attrsStr
+ if (attr === 'callback') {
+ attrs += ` onload="this.__vm_l=1"`;
+ continue;
}
- return isBooleanAttr
- ? (attrsStr + " " + prefix + attr)
- : (attrsStr + " " + prefix + attr + "=\"" + (tag[attr]) + "\"")
- }, '');
+ const isBooleanAttr = !prefix && booleanHtmlAttributes.includes(attr);
- // grab child content from one of these attributes, if possible
- var content = tag.innerHTML || tag.cssText || '';
+ if (isBooleanAttr && !tag[attr]) {
+ continue;
+ }
- // generate tag exactly without any other redundant attribute
- var observeTag = tag.once
- ? ''
- : (attribute + "=\"" + appId + "\"");
+ attrs += ` ${prefix}${attr}` + (isBooleanAttr ? '' : `="${tag[attr]}"`);
+ }
+ let json = '';
+
+ if (tag.json) {
+ json = JSON.stringify(tag.json);
+ } // grab child content from one of these attributes, if possible
+
+
+ const content = tag.innerHTML || tag.cssText || json; // generate tag exactly without any other redundant attribute
// these tags have no end tag
- var hasEndTag = !tagsWithoutEndTag.includes(type);
- // these tag types will have content inserted
- var hasContent = hasEndTag && tagsWithInnerContent.includes(type);
+ const hasEndTag = !tagsWithoutEndTag.includes(type); // these tag types will have content inserted
- // the final string for this specific tag
- return !hasContent
- ? (tagsStr + "<" + type + " " + observeTag + attrs + (hasEndTag ? '/' : '') + ">")
- : (tagsStr + "<" + type + " " + observeTag + attrs + ">" + content + "" + type + ">")
- }, '')
+ const hasContent = hasEndTag && tagsWithInnerContent.includes(type); // the final string for this specific tag
+
+ return `${tagsStr}<${type}${attrs}${!hasContent && hasEndTag ? '/' : ''}>` + (hasContent ? `${content}${type}>` : '');
+ }, '');
}
- }
+
+ };
}
/**
@@ -1226,21 +1353,19 @@ function tagGenerator (appId, ref, type, tags) {
* @return {Object} - the new injector
*/
-function generateServerInjector (appId, options, type, data) {
+function generateServerInjector(options, type, data) {
if (type === 'title') {
- return titleGenerator(appId, options, type, data)
+ return titleGenerator(options, type, data);
}
if (metaInfoAttributeKeys.includes(type)) {
- return attributeGenerator(options, type, data)
+ return attributeGenerator(options, type, data);
}
- return tagGenerator(appId, options, type, data)
+ return tagGenerator(options, type, data);
}
-function _inject (options) {
- if ( options === void 0 ) options = {};
-
+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
@@ -1248,66 +1373,62 @@ function _inject (options) {
* @this {Object} - Vue instance - ideally the root component
* @return {Object} - server meta info with `toString` methods
*/
- return function inject () {
+ return function inject() {
// get meta info with sensible defaults
- var metaInfo = getMetaInfo(options, this.$root, serverSequences);
+ const metaInfo = getMetaInfo(options, this.$root, serverSequences); // generate server injectors
- // generate server injectors
- for (var key in metaInfo) {
+ for (const key in metaInfo) {
if (!metaInfoOptionKeys.includes(key) && metaInfo.hasOwnProperty(key)) {
- metaInfo[key] = generateServerInjector('ssr', options, key, metaInfo[key]);
+ metaInfo[key] = generateServerInjector(options, key, metaInfo[key]);
}
}
- return metaInfo
- }
+ return metaInfo;
+ };
}
-function _$meta (options) {
- if ( options === void 0 ) options = {};
-
- var _refresh$1 = _refresh(options);
- var _inject$1 = _inject(options);
+function _$meta(options = {}) {
+ const _refresh$1 = _refresh(options);
+ const _inject$1 = _inject(options);
/**
* Returns an injector for server-side rendering.
* @this {Object} - the Vue instance (a root component)
* @return {Object} - injector
*/
- return function $meta () {
+
+
+ return function $meta() {
return {
- getOptions: function () { return getOptions(options); },
+ getOptions: () => getOptions(options),
refresh: _refresh$1.bind(this),
inject: _inject$1.bind(this),
pause: pause.bind(this),
resume: resume.bind(this)
- }
- }
+ };
+ };
}
/**
* Plugin install function.
* @param {Function} Vue - the Vue constructor.
*/
-function install (Vue, options) {
- if ( options === void 0 ) options = {};
+function install(Vue, options = {}) {
if (Vue.__vuemeta_installed) {
- return
+ return;
}
+
Vue.__vuemeta_installed = true;
-
options = setOptions(options);
-
Vue.prototype.$meta = _$meta(options);
-
Vue.mixin(createMixin(Vue, options));
}
var index = {
- version: version,
- install: install,
- hasMetaInfo: hasMetaInfo
+ version,
+ install,
+ hasMetaInfo
};
module.exports = index;
diff --git a/dist/vue-meta.esm.browser.js b/dist/vue-meta.esm.browser.js
index c75dcd5..09d4d9a 100644
--- a/dist/vue-meta.esm.browser.js
+++ b/dist/vue-meta.esm.browser.js
@@ -1,5 +1,5 @@
/**
- * vue-meta v2.0.5
+ * vue-meta v2.1.0
* (c) 2019
* - Declan de Wet
* - Sébastien Chopin (@Atinux)
@@ -9,7 +9,7 @@
import deepmerge from 'deepmerge';
-var version = "2.0.5";
+var version = "2.1.0";
// store an id to keep track of DOM updates
let batchId = null;
@@ -62,6 +62,10 @@ function isObject (arg) {
return typeof arg === 'object'
}
+function isPureObject (arg) {
+ return typeof arg === 'object' && arg !== null
+}
+
function isFunction (arg) {
return typeof arg === 'function'
}
@@ -122,6 +126,31 @@ function addNavGuards (vm) {
});
}
+function hasGlobalWindowFn () {
+ try {
+ return !isUndefined(window)
+ } catch (e) {
+ return false
+ }
+}
+
+const hasGlobalWindow = hasGlobalWindowFn();
+
+const _global = hasGlobalWindow ? window : global;
+
+const console = (_global.console = _global.console || {});
+
+function warn (...args) {
+ /* istanbul ignore next */
+ if (!console || !console.warn) {
+ return
+ }
+
+ console.warn(...args);
+}
+
+const showWarningNotSupported = () => warn('This vue app/component has no vue-meta configuration');
+
let appId = 1;
function createMixin (Vue, options) {
@@ -136,7 +165,7 @@ function createMixin (Vue, options) {
get () {
// Show deprecation warning once when devtools enabled
if (Vue.config.devtools && !this.$root._vueMeta.hasMetaInfoDeprecationWarningShown) {
- console.warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead'); // eslint-disable-line no-console
+ warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead');
this.$root._vueMeta.hasMetaInfoDeprecationWarningShown = true;
}
return hasMetaInfo(this)
@@ -198,7 +227,7 @@ function createMixin (Vue, options) {
// if this Vue-app was server rendered, set the appId to 'ssr'
// only one SSR app per page is supported
if (this.$root.$el && this.$root.$el.hasAttribute && this.$root.$el.hasAttribute('data-server-rendered')) {
- this.$root._vueMeta.appId = 'ssr';
+ this.$root._vueMeta.appId = options.ssrAppId;
}
});
@@ -320,13 +349,17 @@ const metaTemplateKeyName = 'template';
// This is the key name for the content-holding property
const contentKeyName = 'content';
+// The id used for the ssr app
+const ssrAppId = 'ssr';
+
const defaultOptions = {
keyName,
attribute,
ssrAttribute,
tagIDKeyName,
contentKeyName,
- metaTemplateKeyName
+ metaTemplateKeyName,
+ ssrAppId
};
// List of metaInfo property keys which are configuration options (and dont generate html)
@@ -351,6 +384,12 @@ const metaInfoAttributeKeys = [
'bodyAttrs'
];
+// HTML elements which support the onload event
+const tagsSupportingOnload = ['link', 'style', 'script'];
+
+// Attributes which should be added with data- prefix
+const commonDataAttributes = ['body', 'pbody'];
+
// from: https://github.com/kangax/html-minifier/blob/gh-pages/src/htmlminifier.js#L202
const booleanHtmlAttributes = [
'allowfullscreen',
@@ -397,9 +436,6 @@ const booleanHtmlAttributes = [
'visible'
];
-// eslint-disable-next-line no-console
-const showWarningNotSupported = () => console.warn('This vue app/component has no vue-meta configuration');
-
function setOptions (options) {
// combine options
options = isObject(options) ? options : {};
@@ -489,7 +525,7 @@ const clientSequences = [
// sanitizes potentially dangerous characters
function escape (info, options, escapeOptions) {
const { tagIDKeyName } = options;
- const { doEscape = v => v } = escapeOptions;
+ const { doEscape = v => v, escapeKeys } = escapeOptions;
const escaped = {};
for (const key in info) {
@@ -523,15 +559,25 @@ function escape (info, options, escapeOptions) {
escaped[key] = doEscape(value);
} else if (isArray(value)) {
escaped[key] = value.map((v) => {
- return isObject(v)
- ? escape(v, options, escapeOptions)
- : doEscape(v)
+ if (isPureObject(v)) {
+ return escape(v, options, { ...escapeOptions, escapeKeys: true })
+ }
+
+ return doEscape(v)
});
- } else if (isObject(value)) {
- escaped[key] = escape(value, options, escapeOptions);
+ } else if (isPureObject(value)) {
+ escaped[key] = escape(value, options, { ...escapeOptions, escapeKeys: true });
} else {
escaped[key] = value;
}
+
+ if (escapeKeys) {
+ const escapedKey = doEscape(key);
+ if (key !== escapedKey) {
+ escaped[escapedKey] = escaped[key];
+ delete escaped[key];
+ }
+ }
}
return escaped
@@ -614,9 +660,8 @@ function merge (target, source, options = {}) {
for (const key in source[attrKey]) {
if (source[attrKey].hasOwnProperty(key) && source[attrKey][key] === undefined) {
- if (booleanHtmlAttributes.includes(key)) {
- // eslint-disable-next-line no-console
- console.warn('VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details');
+ if (includes(booleanHtmlAttributes, key)) {
+ warn('VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details');
}
delete source[attrKey][key];
}
@@ -750,6 +795,141 @@ function getMetaInfo (options = {}, component, escapeSequences = []) {
return info
}
+function getTag (tags, tag) {
+ if (!tags[tag]) {
+ tags[tag] = document.getElementsByTagName(tag)[0];
+ }
+
+ return tags[tag]
+}
+
+function getElementsKey ({ body, pbody }) {
+ return body
+ ? 'body'
+ : (pbody ? 'pbody' : 'head')
+}
+
+function queryElements (parentNode, { appId, attribute, type, tagIDKeyName }, attributes = {}) {
+ const queries = [
+ `${type}[${attribute}="${appId}"]`,
+ `${type}[data-${tagIDKeyName}]`
+ ].map((query) => {
+ for (const key in attributes) {
+ const val = attributes[key];
+ const attributeValue = val && val !== true ? `="${val}"` : '';
+ query += `[data-${key}${attributeValue}]`;
+ }
+ return query
+ });
+
+ return toArray(parentNode.querySelectorAll(queries.join(', ')))
+}
+
+const callbacks = [];
+
+function isDOMComplete (d = document) {
+ return d.readyState === 'complete'
+}
+
+function addCallback (query, callback) {
+ if (arguments.length === 1) {
+ callback = query;
+ query = '';
+ }
+
+ callbacks.push([ query, callback ]);
+}
+
+function addCallbacks ({ tagIDKeyName }, type, tags, autoAddListeners) {
+ let hasAsyncCallback = false;
+
+ for (const tag of tags) {
+ if (!tag[tagIDKeyName] || !tag.callback) {
+ continue
+ }
+
+ hasAsyncCallback = true;
+ addCallback(`${type}[data-${tagIDKeyName}="${tag[tagIDKeyName]}"]`, tag.callback);
+ }
+
+ if (!autoAddListeners || !hasAsyncCallback) {
+ return hasAsyncCallback
+ }
+
+ return addListeners()
+}
+
+function addListeners () {
+ if (isDOMComplete()) {
+ applyCallbacks();
+ return
+ }
+
+ // Instead of using a MutationObserver, we just apply
+ /* istanbul ignore next */
+ document.onreadystatechange = () => {
+ applyCallbacks();
+ };
+}
+
+function applyCallbacks (matchElement) {
+ for (const [query, callback] of callbacks) {
+ const selector = `${query}[onload="this.__vm_l=1"]`;
+
+ let elements = [];
+ if (!matchElement) {
+ elements = toArray(document.querySelectorAll(selector));
+ }
+
+ if (matchElement && matchElement.matches(selector)) {
+ elements = [matchElement];
+ }
+
+ for (const element of elements) {
+ /* __vm_cb: whether the load callback has been called
+ * __vm_l: set by onload attribute, whether the element was loaded
+ * __vm_ev: whether the event listener was added or not
+ */
+ if (element.__vm_cb) {
+ continue
+ }
+
+ const onload = () => {
+ /* Mark that the callback for this element has already been called,
+ * this prevents the callback to run twice in some (rare) conditions
+ */
+ element.__vm_cb = true;
+
+ /* onload needs to be removed because we only need the
+ * attribute after ssr and if we dont remove it the node
+ * will fail isEqualNode on the client
+ */
+ element.removeAttribute('onload');
+
+ callback(element);
+ };
+
+ /* IE9 doesnt seem to load scripts synchronously,
+ * causing a script sometimes/often already to be loaded
+ * when we add the event listener below (thus adding an onload event
+ * listener has no use because it will never be triggered).
+ * Therefore we add the onload attribute during ssr, and
+ * check here if it was already loaded or not
+ */
+ if (element.__vm_l) {
+ onload();
+ continue
+ }
+
+ if (!element.__vm_ev) {
+ element.__vm_ev = true;
+
+ element.addEventListener('load', onload);
+ }
+ }
+ }
+}
+
/**
* Updates the document's html tag attributes
*
@@ -799,7 +979,7 @@ function updateAttribute ({ attribute } = {}, attrs, tag) {
* @param {String} title - the new title of the document
*/
function updateTitle (title) {
- if (title === undefined) {
+ if (!title && title !== '') {
return
}
@@ -814,11 +994,18 @@ function updateTitle (title) {
* @param {(Array|Object)} tags - an array of tag objects or a single object in case of base
* @return {Object} - a representation of what tags changed
*/
-function updateTag (appId, { attribute, tagIDKeyName } = {}, type, tags, headTag, bodyTag) {
- const oldHeadTags = toArray(headTag.querySelectorAll(`${type}[${attribute}="${appId}"], ${type}[data-${tagIDKeyName}]`));
- const oldBodyTags = toArray(bodyTag.querySelectorAll(`${type}[${attribute}="${appId}"][data-body="true"], ${type}[data-${tagIDKeyName}][data-body="true"]`));
- const dataAttributes = [tagIDKeyName, 'body'];
- const newTags = [];
+function updateTag (appId, options = {}, type, tags, head, body) {
+ const { attribute, tagIDKeyName } = options;
+
+ const dataAttributes = [tagIDKeyName, ...commonDataAttributes];
+ const newElements = [];
+
+ const queryOptions = { appId, attribute, type, tagIDKeyName };
+ const currentElements = {
+ head: queryElements(head, queryOptions),
+ pbody: queryElements(body, queryOptions, { pbody: true }),
+ body: queryElements(body, queryOptions, { body: true })
+ };
if (tags.length > 1) {
// remove duplicates that could have been found by merging tags
@@ -834,74 +1021,107 @@ function updateTag (appId, { attribute, tagIDKeyName } = {}, type, tags, headTag
}
if (tags.length) {
- tags.forEach((tag) => {
- const newElement = document.createElement(type);
+ for (const tag of tags) {
+ if (tag.skip) {
+ continue
+ }
+ const newElement = document.createElement(type);
newElement.setAttribute(attribute, appId);
- const oldTags = tag.body !== true ? oldHeadTags : oldBodyTags;
-
for (const attr in tag) {
- if (tag.hasOwnProperty(attr)) {
- if (attr === 'innerHTML') {
- newElement.innerHTML = tag.innerHTML;
- } else if (attr === 'cssText') {
- if (newElement.styleSheet) {
- /* istanbul ignore next */
- newElement.styleSheet.cssText = tag.cssText;
- } else {
- newElement.appendChild(document.createTextNode(tag.cssText));
- }
- } else {
- const _attr = includes(dataAttributes, attr)
- ? `data-${attr}`
- : attr;
-
- const isBooleanAttribute = includes(booleanHtmlAttributes, attr);
- if (isBooleanAttribute && !tag[attr]) {
- continue
- }
-
- const value = isBooleanAttribute ? '' : tag[attr];
- newElement.setAttribute(_attr, value);
- }
+ /* istanbul ignore next */
+ if (!tag.hasOwnProperty(attr)) {
+ continue
}
+
+ if (attr === 'innerHTML') {
+ newElement.innerHTML = tag.innerHTML;
+ continue
+ }
+
+ if (attr === 'json') {
+ newElement.innerHTML = JSON.stringify(tag.json);
+ continue
+ }
+
+ if (attr === 'cssText') {
+ if (newElement.styleSheet) {
+ /* istanbul ignore next */
+ newElement.styleSheet.cssText = tag.cssText;
+ } else {
+ newElement.appendChild(document.createTextNode(tag.cssText));
+ }
+ continue
+ }
+
+ if (attr === 'callback') {
+ newElement.onload = () => tag[attr](newElement);
+ continue
+ }
+
+ const _attr = includes(dataAttributes, attr)
+ ? `data-${attr}`
+ : attr;
+
+ const isBooleanAttribute = includes(booleanHtmlAttributes, attr);
+ if (isBooleanAttribute && !tag[attr]) {
+ continue
+ }
+
+ const value = isBooleanAttribute ? '' : tag[attr];
+ newElement.setAttribute(_attr, value);
}
+ const oldElements = currentElements[getElementsKey(tag)];
+
// Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
let indexToDelete;
- const hasEqualElement = oldTags.some((existingTag, index) => {
+ const hasEqualElement = oldElements.some((existingTag, index) => {
indexToDelete = index;
return newElement.isEqualNode(existingTag)
});
if (hasEqualElement && (indexToDelete || indexToDelete === 0)) {
- oldTags.splice(indexToDelete, 1);
+ oldElements.splice(indexToDelete, 1);
} else {
- newTags.push(newElement);
+ newElements.push(newElement);
}
- });
- }
-
- const oldTags = oldHeadTags.concat(oldBodyTags);
- oldTags.forEach(tag => tag.parentNode.removeChild(tag));
- newTags.forEach((tag) => {
- if (tag.getAttribute('data-body') === 'true') {
- bodyTag.appendChild(tag);
- } else {
- headTag.appendChild(tag);
}
- });
-
- return { oldTags, newTags }
-}
-
-function getTag (tags, tag) {
- if (!tags[tag]) {
- tags[tag] = document.getElementsByTagName(tag)[0];
}
- return tags[tag]
+ let oldElements = [];
+ for (const current of Object.values(currentElements)) {
+ oldElements = [
+ ...oldElements,
+ ...current
+ ];
+ }
+
+ // remove old elements
+ for (const element of oldElements) {
+ element.parentNode.removeChild(element);
+ }
+
+ // insert new elements
+ for (const element of newElements) {
+ if (element.hasAttribute('data-body')) {
+ body.appendChild(element);
+ continue
+ }
+
+ if (element.hasAttribute('data-pbody')) {
+ body.insertBefore(element, body.firstChild);
+ continue
+ }
+
+ head.appendChild(element);
+ }
+
+ return {
+ oldTags: oldElements,
+ newTags: newElements
+ }
}
/**
@@ -910,7 +1130,7 @@ function getTag (tags, tag) {
* @param {Object} newInfo - the meta info to update to
*/
function updateClientMetaInfo (appId, options = {}, newInfo) {
- const { ssrAttribute } = options;
+ const { ssrAttribute, ssrAppId } = options;
// only cache tags for current update
const tags = {};
@@ -918,9 +1138,22 @@ function updateClientMetaInfo (appId, options = {}, newInfo) {
const htmlTag = getTag(tags, 'html');
// if this is a server render, then dont update
- if (appId === 'ssr' && htmlTag.hasAttribute(ssrAttribute)) {
+ if (appId === ssrAppId && htmlTag.hasAttribute(ssrAttribute)) {
// remove the server render attribute so we can update on (next) changes
htmlTag.removeAttribute(ssrAttribute);
+
+ // add load callbacks if the
+ let addLoadListeners = false;
+ for (const type of tagsSupportingOnload) {
+ if (newInfo[type] && addCallbacks(options, type, newInfo[type])) {
+ addLoadListeners = true;
+ }
+ }
+
+ if (addLoadListeners) {
+ addListeners();
+ }
+
return false
}
diff --git a/dist/vue-meta.esm.browser.min.js b/dist/vue-meta.esm.browser.min.js
index 01b66f7..0b0749e 100644
--- a/dist/vue-meta.esm.browser.min.js
+++ b/dist/vue-meta.esm.browser.min.js
@@ -1 +1 @@
-import e from"deepmerge";let t=null;function n(e,n){e.$root._vueMeta.initialized||!e.$root._vueMeta.initializing&&"watcher"!==n||(e.$root._vueMeta.initialized=null),e.$root._vueMeta.initialized&&!e.$root._vueMeta.paused&&function(e,n=10){clearTimeout(t),t=setTimeout(()=>{e()},n)}(()=>e.$meta().refresh())}function i(e){return Array.isArray(e)}function o(e){return void 0===e}function a(e){return"object"==typeof e}function r(e){return"function"==typeof e}function s(e,t){return t&&a(e)?(i(e[t])||(e[t]=[]),e):i(e)?e:[]}function u(e,t,n){s(e,t),e[t].push(n)}function c(e=this){return e&&(!0===e._vueMeta||a(e._vueMeta))}function l(e){if(e.$root._vueMeta.navGuards||!e.$root.$router)return;e.$root._vueMeta.navGuards=!0;const t=e.$root.$router,n=e.$root.$meta();t.beforeEach((e,t,i)=>{n.pause(),i()}),t.afterEach(()=>{const{metaInfo:e}=n.resume();e&&e.afterNavigation&&r(e.afterNavigation)&&e.afterNavigation(e)})}let d=1;const h={title:void 0,titleChunk:"",titleTemplate:"%s",htmlAttrs:{},bodyAttrs:{},headAttrs:{},base:[],link:[],meta:[],style:[],script:[],noscript:[],__dangerouslyDisableSanitizers:[],__dangerouslyDisableSanitizersByTagID:{}},f={keyName:"metaInfo",attribute:"data-vue-meta",ssrAttribute:"data-vue-meta-server-rendered",tagIDKeyName:"vmid",contentKeyName:"content",metaTemplateKeyName:"template"},m=["titleChunk","titleTemplate","changed","__dangerouslyDisableSanitizers","__dangerouslyDisableSanitizersByTagID"],p=["__dangerouslyDisableSanitizers","__dangerouslyDisableSanitizersByTagID"],v=["htmlAttrs","headAttrs","bodyAttrs"],$=["allowfullscreen","amp","async","autofocus","autoplay","checked","compact","controls","declare","default","defaultchecked","defaultmuted","defaultselected","defer","disabled","enabled","formnovalidate","hidden","indeterminate","inert","ismap","itemscope","loop","multiple","muted","nohref","noresize","noshade","novalidate","nowrap","open","pauseonexit","readonly","required","reversed","scoped","seamless","selected","sortable","truespeed","typemustmatch","visible"],y=()=>console.warn("This vue app/component has no vue-meta configuration");function g(e=!0){return this.$root._vueMeta.paused=!0,()=>b(e)}function b(e=!0){if(this.$root._vueMeta.paused=!1,e)return this.$root.$meta().refresh()}function _({component:e,metaTemplateKeyName:t,contentKeyName:n},i,a,s){return o(a)&&(a=i[t],delete i[t]),!!a&&(o(s)&&(s=i[n]),i[n]=r(a)?a.call(e,s):a.replace(/%s/g,s),!0)}function M(e,t){return e.findIndex(t,arguments[2])}function T(e){return Array.from(e)}function N(e,t){return e.includes(t)}const w=[[/&/g,"&"],[//g,">"],[/"/g,'"'],[/'/g,"'"]];function I(t,n,i={}){return n.hasOwnProperty("title")&&void 0===n.title&&delete n.title,v.forEach(e=>{if(n[e])for(const t in n[e])n[e].hasOwnProperty(t)&&void 0===n[e][t]&&($.includes(t)&&console.warn("VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details"),delete n[e][t])}),e(t,n,{arrayMerge:(e,t)=>(function({component:e,tagIDKeyName:t,metaTemplateKeyName:n,contentKeyName:i},o,a){const r=[];return o.forEach((o,s)=>{if(!o[t])return void r.push(o);const u=M(a,e=>e[t]===o[t]),c=a[u];if(-1===u)return void r.push(o);if(c.hasOwnProperty(i)&&void 0===c[i]||c.hasOwnProperty("innerHTML")&&void 0===c.innerHTML)return r.push(o),void a.splice(u,1);if(null===c[i]||null===c.innerHTML)return void a.splice(u,1);const l=o[n];l&&(c[n]?c[i]||_({component:e,metaTemplateKeyName:n,contentKeyName:i},c,void 0,o[i]):_({component:e,metaTemplateKeyName:n,contentKeyName:i},c,l))}),r.concat(a)})(i,e,t)})}function A(e={},t,n={}){const{keyName:i,metaTemplateKeyName:s,tagIDKeyName:u}=e,{$options:c,$children:l}=t;if(t._inactive)return n;if(c[i]){let o=c[i];if(r(o)&&(o=o.call(t)),!a(o))return n;n=I(n,o,e)}return l.length&&l.forEach(t=>{(function(e=this){return e&&!o(e._vueMeta)})(t)&&(n=A(e,t,n))}),s&&n.meta&&(n.meta.forEach(t=>_(e,t)),n.meta=n.meta.filter((e,t,n)=>!e.hasOwnProperty(u)||t===M(n,t=>t[u]===e[u]))),n}function z(e={},t,n=[]){let o=A(e,t,h);o.title&&(o.titleChunk=o.title),o.titleTemplate&&"%s"!==o.titleTemplate&&_({component:t,contentKeyName:"title"},o,o.titleTemplate,o.titleChunk||""),o.base&&(o.base=Object.keys(o.base).length?[o.base]:[]);const r={doEscape:e=>n.reduce((e,[t,n])=>e.replace(t,n),e)};return p.forEach((e,t)=>{if(0===t)s(o,e);else if(1===t)for(const t in o[e])s(o[e],t);r[e]=o[e]}),o=function e(t,n,o){const{tagIDKeyName:r}=n,{doEscape:s=(e=>e)}=o,u={};for(const c in t){const l=t[c];if(N(m,c)){u[c]=l;continue}let[d]=p;if(o[d]&&N(o[d],c)){u[c]=l;continue}const h=t[r];h&&(d=p[1],o[d]&&o[d][h]&&N(o[d][h],c))?u[c]=l:"string"==typeof l?u[c]=s(l):i(l)?u[c]=l.map(t=>a(t)?e(t,n,o):s(t)):a(l)?u[c]=e(l,n,o):u[c]=l}return u}(o,e,r)}function D({attribute:e}={},t,n){const o=n.getAttribute(e),a=o?o.split(","):[],r=T(a),s=[];for(const e in t)if(t.hasOwnProperty(e)){const o=N($,e)?"":i(t[e])?t[e].join(" "):t[e];n.setAttribute(e,o||""),N(a,e)||a.push(e),s.push(r.indexOf(e))}const u=r.filter((e,t)=>!N(s,t)).reduce((e,t)=>(n.removeAttribute(t),e+1),0);a.length===u?n.removeAttribute(e):n.setAttribute(e,a.sort().join(","))}function K(e,{attribute:t,tagIDKeyName:n}={},i,o,a,r){const s=T(a.querySelectorAll(`${i}[${t}="${e}"], ${i}[data-${n}]`)),u=T(r.querySelectorAll(`${i}[${t}="${e}"][data-body="true"], ${i}[data-${n}][data-body="true"]`)),c=[n,"body"],l=[];if(o.length>1){const e=[];o=o.filter(t=>{const n=JSON.stringify(t),i=!N(e,n);return e.push(n),i})}o.length&&o.forEach(n=>{const o=document.createElement(i);o.setAttribute(t,e);const a=!0!==n.body?s:u;for(const e in n)if(n.hasOwnProperty(e))if("innerHTML"===e)o.innerHTML=n.innerHTML;else if("cssText"===e)o.styleSheet?o.styleSheet.cssText=n.cssText:o.appendChild(document.createTextNode(n.cssText));else{const t=N(c,e)?`data-${e}`:e,i=N($,e);if(i&&!n[e])continue;const a=i?"":n[e];o.setAttribute(t,a)}let r;a.some((e,t)=>(r=t,o.isEqualNode(e)))&&(r||0===r)?a.splice(r,1):l.push(o)});const d=s.concat(u);return d.forEach(e=>e.parentNode.removeChild(e)),l.forEach(e=>{"true"===e.getAttribute("data-body")?r.appendChild(e):a.appendChild(e)}),{oldTags:d,newTags:l}}function O(e,t){return e[t]||(e[t]=document.getElementsByTagName(t)[0]),e[t]}function k(e={}){return function(){const t=z(e,this.$root,w),n=function(e,t={},n){const{ssrAttribute:o}=t,a={},r=O(a,"html");if("ssr"===e&&r.hasAttribute(o))return r.removeAttribute(o),!1;const s={},u={};for(const o in n){if(N(m,o))continue;if("title"===o){void 0!==(c=n.title)&&(document.title=c);continue}if(N(v,o)){const e=o.substr(0,4);D(t,n[o],O(a,e));continue}if(!i(n[o]))continue;const{oldTags:r,newTags:l}=K(e,t,o,n[o],O(a,"head"),O(a,"body"));l.length&&(s[o]=l,u[o]=r)}var c;return{addedTags:s,removedTags:u}}(this.$root._vueMeta.appId,e,t);return n&&r(t.changed)&&t.changed(t,n.addedTags,n.removedTags),{vm:this,metaInfo:t,tags:n}}}function E(e,t={}){e.__vuemeta_installed||(e.__vuemeta_installed=!0,t=function(e){e=a(e)?e:{};for(const t in f)e[t]||(e[t]=f[t]);return e}(t),e.prototype.$meta=function(e={}){const t=k(e),n=()=>{};return function(){return this.$root._vueMeta?{getOptions:()=>(function(e){const t={};for(const n in e)t[n]=e[n];return t})(e),refresh:t.bind(this),inject:n,pause:g.bind(this),resume:b.bind(this)}:{getOptions:y,refresh:y,inject:y,pause:y,resume:y}}}(t),e.mixin(function(e,t){const i=["activated","deactivated","beforeMount"];return{beforeCreate(){if(Object.defineProperty(this,"_hasMetaInfo",{configurable:!0,get(){return e.config.devtools&&!this.$root._vueMeta.hasMetaInfoDeprecationWarningShown&&(console.warn("VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead"),this.$root._vueMeta.hasMetaInfoDeprecationWarningShown=!0),c(this)}}),!o(this.$options[t.keyName])&&null!==this.$options[t.keyName]){if(this.$root._vueMeta||(this.$root._vueMeta={appId:d},d++),!this._vueMeta){this._vueMeta=!0;let e=this.$parent;for(;e&&e!==this.$root;)o(e._vueMeta)&&(e._vueMeta=!1),e=e.$parent}r(this.$options[t.keyName])&&(this.$options.computed||(this.$options.computed={}),this.$options.computed.$metaInfo=this.$options[t.keyName],this.$isServer||u(this.$options,"created",()=>{this.$watch("$metaInfo",function(){n(this,"watcher")})})),o(this.$root._vueMeta.initialized)&&(this.$root._vueMeta.initialized=this.$isServer,this.$root._vueMeta.initialized||(u(this.$options,"beforeMount",()=>{this.$root.$el&&this.$root.$el.hasAttribute&&this.$root.$el.hasAttribute("data-server-rendered")&&(this.$root._vueMeta.appId="ssr")}),u(this.$options,"mounted",()=>{this.$root._vueMeta.initialized||(this.$root._vueMeta.initializing=!0,this.$nextTick(function(){const{tags:e,metaInfo:i}=this.$root.$meta().refresh();!1===e&&null===this.$root._vueMeta.initialized&&this.$nextTick(()=>n(this,"initializing")),this.$root._vueMeta.initialized=!0,delete this.$root._vueMeta.initializing,!t.refreshOnceOnNavigation&&i.afterNavigation&&l(this)}))}),t.refreshOnceOnNavigation&&l(this))),this.$isServer||(i.forEach(e=>{u(this.$options,e,()=>n(this,e))}),u(this.$options,"destroyed",()=>{const e=setInterval(()=>{this.$el&&null!==this.$el.offsetParent||(clearInterval(e),this.$parent&&n(this,"destroyed"))},50)}))}}}}(e,t)))}o(window)||o(window.Vue)||E(window.Vue);var S={version:"2.0.5",install:E,hasMetaInfo:c};export default S;
+import t from"deepmerge";let e=null;function n(t,n){t.$root._vueMeta.initialized||!t.$root._vueMeta.initializing&&"watcher"!==n||(t.$root._vueMeta.initialized=null),t.$root._vueMeta.initialized&&!t.$root._vueMeta.paused&&function(t,n=10){clearTimeout(e),e=setTimeout(()=>{t()},n)}(()=>t.$meta().refresh())}function o(t){return Array.isArray(t)}function i(t){return void 0===t}function a(t){return"object"==typeof t}function r(t){return"object"==typeof t&&null!==t}function s(t){return"function"==typeof t}function u(t,e){return e&&a(t)?(o(t[e])||(t[e]=[]),t):o(t)?t:[]}function c(t,e,n){u(t,e),t[e].push(n)}function l(t=this){return t&&(!0===t._vueMeta||a(t._vueMeta))}function d(t){if(t.$root._vueMeta.navGuards||!t.$root.$router)return;t.$root._vueMeta.navGuards=!0;const e=t.$root.$router,n=t.$root.$meta();e.beforeEach((t,e,o)=>{n.pause(),o()}),e.afterEach(()=>{const{metaInfo:t}=n.resume();t&&t.afterNavigation&&s(t.afterNavigation)&&t.afterNavigation(t)})}const f=function(){try{return!i(window)}catch(t){return!1}}()?window:global,h=f.console=f.console||{};function p(...t){h&&h.warn&&h.warn(...t)}const m=()=>p("This vue app/component has no vue-meta configuration");let v=1;const y={title:void 0,titleChunk:"",titleTemplate:"%s",htmlAttrs:{},bodyAttrs:{},headAttrs:{},base:[],link:[],meta:[],style:[],script:[],noscript:[],__dangerouslyDisableSanitizers:[],__dangerouslyDisableSanitizersByTagID:{}},$={keyName:"metaInfo",attribute:"data-vue-meta",ssrAttribute:"data-vue-meta-server-rendered",tagIDKeyName:"vmid",contentKeyName:"content",metaTemplateKeyName:"template",ssrAppId:"ssr"},b=["titleChunk","titleTemplate","changed","__dangerouslyDisableSanitizers","__dangerouslyDisableSanitizersByTagID"],g=["__dangerouslyDisableSanitizers","__dangerouslyDisableSanitizersByTagID"],_=["htmlAttrs","headAttrs","bodyAttrs"],M=["link","style","script"],T=["body","pbody"],N=["allowfullscreen","amp","async","autofocus","autoplay","checked","compact","controls","declare","default","defaultchecked","defaultmuted","defaultselected","defer","disabled","enabled","formnovalidate","hidden","indeterminate","inert","ismap","itemscope","loop","multiple","muted","nohref","noresize","noshade","novalidate","nowrap","open","pauseonexit","readonly","required","reversed","scoped","seamless","selected","sortable","truespeed","typemustmatch","visible"];function I(t=!0){return this.$root._vueMeta.paused=!0,()=>A(t)}function A(t=!0){if(this.$root._vueMeta.paused=!1,t)return this.$root.$meta().refresh()}function w({component:t,metaTemplateKeyName:e,contentKeyName:n},o,a,r){return i(a)&&(a=o[e],delete o[e]),!!a&&(i(r)&&(r=o[n]),o[n]=s(a)?a.call(t,r):a.replace(/%s/g,r),!0)}function K(t,e){return t.findIndex(e,arguments[2])}function k(t){return Array.from(t)}function z(t,e){return t.includes(e)}const D=[[/&/g,"&"],[//g,">"],[/"/g,'"'],[/'/g,"'"]];function O(e,n,o={}){return n.hasOwnProperty("title")&&void 0===n.title&&delete n.title,_.forEach(t=>{if(n[t])for(const e in n[t])n[t].hasOwnProperty(e)&&void 0===n[t][e]&&(z(N,e)&&p("VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details"),delete n[t][e])}),t(e,n,{arrayMerge:(t,e)=>(function({component:t,tagIDKeyName:e,metaTemplateKeyName:n,contentKeyName:o},i,a){const r=[];return i.forEach((i,s)=>{if(!i[e])return void r.push(i);const u=K(a,t=>t[e]===i[e]),c=a[u];if(-1===u)return void r.push(i);if(c.hasOwnProperty(o)&&void 0===c[o]||c.hasOwnProperty("innerHTML")&&void 0===c.innerHTML)return r.push(i),void a.splice(u,1);if(null===c[o]||null===c.innerHTML)return void a.splice(u,1);const l=i[n];l&&(c[n]?c[o]||w({component:t,metaTemplateKeyName:n,contentKeyName:o},c,void 0,i[o]):w({component:t,metaTemplateKeyName:n,contentKeyName:o},c,l))}),r.concat(a)})(o,t,e)})}function S(t={},e,n={}){const{keyName:o,metaTemplateKeyName:r,tagIDKeyName:u}=t,{$options:c,$children:l}=e;if(e._inactive)return n;if(c[o]){let i=c[o];if(s(i)&&(i=i.call(e)),!a(i))return n;n=O(n,i,t)}return l.length&&l.forEach(e=>{(function(t=this){return t&&!i(t._vueMeta)})(e)&&(n=S(t,e,n))}),r&&n.meta&&(n.meta.forEach(e=>w(t,e)),n.meta=n.meta.filter((t,e,n)=>!t.hasOwnProperty(u)||e===K(n,e=>e[u]===t[u]))),n}function E(t={},e,n=[]){let i=S(t,e,y);i.title&&(i.titleChunk=i.title),i.titleTemplate&&"%s"!==i.titleTemplate&&w({component:e,contentKeyName:"title"},i,i.titleTemplate,i.titleChunk||""),i.base&&(i.base=Object.keys(i.base).length?[i.base]:[]);const a={doEscape:t=>n.reduce((t,[e,n])=>t.replace(e,n),t)};return g.forEach((t,e)=>{if(0===e)u(i,t);else if(1===e)for(const e in i[t])u(i[t],e);a[t]=i[t]}),i=function t(e,n,i){const{tagIDKeyName:a}=n,{doEscape:s=(t=>t),escapeKeys:u}=i,c={};for(const l in e){const d=e[l];if(z(b,l)){c[l]=d;continue}let[f]=g;if(i[f]&&z(i[f],l)){c[l]=d;continue}const h=e[a];if(h&&(f=g[1],i[f]&&i[f][h]&&z(i[f][h],l)))c[l]=d;else if("string"==typeof d?c[l]=s(d):o(d)?c[l]=d.map(e=>r(e)?t(e,n,{...i,escapeKeys:!0}):s(e)):r(d)?c[l]=t(d,n,{...i,escapeKeys:!0}):c[l]=d,u){const t=s(l);l!==t&&(c[t]=c[l],delete c[l])}}return c}(i,t,a)}function j(t,e){return t[e]||(t[e]=document.getElementsByTagName(e)[0]),t[e]}function x({body:t,pbody:e}){return t?"body":e?"pbody":"head"}function P(t,{appId:e,attribute:n,type:o,tagIDKeyName:i},a={}){const r=[`${o}[${n}="${e}"]`,`${o}[data-${i}]`].map(t=>{for(const e in a){const n=a[e];t+=`[data-${e}${n&&!0!==n?`="${n}"`:""}]`}return t});return k(t.querySelectorAll(r.join(", ")))}const C=[];function L(t,e){1===arguments.length&&(e=t,t=""),C.push([t,e])}function H({tagIDKeyName:t},e,n,o){let i=!1;for(const o of n)o[t]&&o.callback&&(i=!0,L(`${e}[data-${t}="${o[t]}"]`,o.callback));return o&&i?B():i}function B(){!function(t=document){return"complete"===t.readyState}()?document.onreadystatechange=()=>{q()}:q()}function q(t){for(const[e,n]of C){const o=`${e}[onload="this.__vm_l=1"]`;let i=[];t||(i=k(document.querySelectorAll(o))),t&&t.matches(o)&&(i=[t]);for(const t of i){if(t.__vm_cb)continue;const e=()=>{t.__vm_cb=!0,t.removeAttribute("onload"),n(t)};t.__vm_l?e():t.__vm_ev||(t.__vm_ev=!0,t.addEventListener("load",e))}}}function V({attribute:t}={},e,n){const i=n.getAttribute(t),a=i?i.split(","):[],r=k(a),s=[];for(const t in e)if(e.hasOwnProperty(t)){const i=z(N,t)?"":o(e[t])?e[t].join(" "):e[t];n.setAttribute(t,i||""),z(a,t)||a.push(t),s.push(r.indexOf(t))}const u=r.filter((t,e)=>!z(s,e)).reduce((t,e)=>(n.removeAttribute(e),t+1),0);a.length===u?n.removeAttribute(t):n.setAttribute(t,a.sort().join(","))}function W(t,e={},n,o,i,a){const{attribute:r,tagIDKeyName:s}=e,u=[s,...T],c=[],l={appId:t,attribute:r,type:n,tagIDKeyName:s},d={head:P(i,l),pbody:P(a,l,{pbody:!0}),body:P(a,l,{body:!0})};if(o.length>1){const t=[];o=o.filter(e=>{const n=JSON.stringify(e),o=!z(t,n);return t.push(n),o})}if(o.length)for(const e of o){if(e.skip)continue;const o=document.createElement(n);o.setAttribute(r,t);for(const t in e){if(!e.hasOwnProperty(t))continue;if("innerHTML"===t){o.innerHTML=e.innerHTML;continue}if("json"===t){o.innerHTML=JSON.stringify(e.json);continue}if("cssText"===t){o.styleSheet?o.styleSheet.cssText=e.cssText:o.appendChild(document.createTextNode(e.cssText));continue}if("callback"===t){o.onload=()=>e[t](o);continue}const n=z(u,t)?`data-${t}`:t,i=z(N,t);if(i&&!e[t])continue;const a=i?"":e[t];o.setAttribute(n,a)}const i=d[x(e)];let a;i.some((t,e)=>(a=e,o.isEqualNode(t)))&&(a||0===a)?i.splice(a,1):c.push(o)}let f=[];for(const t of Object.values(d))f=[...f,...t];for(const t of f)t.parentNode.removeChild(t);for(const t of c)t.hasAttribute("data-body")?a.appendChild(t):t.hasAttribute("data-pbody")?a.insertBefore(t,a.firstChild):i.appendChild(t);return{oldTags:f,newTags:c}}function G(t={}){return function(){const e=E(t,this.$root,D),n=function(t,e={},n){const{ssrAttribute:i,ssrAppId:a}=e,r={},s=j(r,"html");if(t===a&&s.hasAttribute(i)){s.removeAttribute(i);let t=!1;for(const o of M)n[o]&&H(e,o,n[o])&&(t=!0);return t&&B(),!1}const u={},c={};for(const i in n){if(z(b,i))continue;if("title"===i){((l=n.title)||""===l)&&(document.title=l);continue}if(z(_,i)){const t=i.substr(0,4);V(e,n[i],j(r,t));continue}if(!o(n[i]))continue;const{oldTags:a,newTags:s}=W(t,e,i,n[i],j(r,"head"),j(r,"body"));s.length&&(u[i]=s,c[i]=a)}var l;return{addedTags:u,removedTags:c}}(this.$root._vueMeta.appId,t,e);return n&&s(e.changed)&&e.changed(e,n.addedTags,n.removedTags),{vm:this,metaInfo:e,tags:n}}}function J(t,e={}){t.__vuemeta_installed||(t.__vuemeta_installed=!0,e=function(t){t=a(t)?t:{};for(const e in $)t[e]||(t[e]=$[e]);return t}(e),t.prototype.$meta=function(t={}){const e=G(t),n=()=>{};return function(){return this.$root._vueMeta?{getOptions:()=>(function(t){const e={};for(const n in t)e[n]=t[n];return e})(t),refresh:e.bind(this),inject:n,pause:I.bind(this),resume:A.bind(this)}:{getOptions:m,refresh:m,inject:m,pause:m,resume:m}}}(e),t.mixin(function(t,e){const o=["activated","deactivated","beforeMount"];return{beforeCreate(){if(Object.defineProperty(this,"_hasMetaInfo",{configurable:!0,get(){return t.config.devtools&&!this.$root._vueMeta.hasMetaInfoDeprecationWarningShown&&(p("VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead"),this.$root._vueMeta.hasMetaInfoDeprecationWarningShown=!0),l(this)}}),!i(this.$options[e.keyName])&&null!==this.$options[e.keyName]){if(this.$root._vueMeta||(this.$root._vueMeta={appId:v},v++),!this._vueMeta){this._vueMeta=!0;let t=this.$parent;for(;t&&t!==this.$root;)i(t._vueMeta)&&(t._vueMeta=!1),t=t.$parent}s(this.$options[e.keyName])&&(this.$options.computed||(this.$options.computed={}),this.$options.computed.$metaInfo=this.$options[e.keyName],this.$isServer||c(this.$options,"created",()=>{this.$watch("$metaInfo",function(){n(this,"watcher")})})),i(this.$root._vueMeta.initialized)&&(this.$root._vueMeta.initialized=this.$isServer,this.$root._vueMeta.initialized||(c(this.$options,"beforeMount",()=>{this.$root.$el&&this.$root.$el.hasAttribute&&this.$root.$el.hasAttribute("data-server-rendered")&&(this.$root._vueMeta.appId=e.ssrAppId)}),c(this.$options,"mounted",()=>{this.$root._vueMeta.initialized||(this.$root._vueMeta.initializing=!0,this.$nextTick(function(){const{tags:t,metaInfo:o}=this.$root.$meta().refresh();!1===t&&null===this.$root._vueMeta.initialized&&this.$nextTick(()=>n(this,"initializing")),this.$root._vueMeta.initialized=!0,delete this.$root._vueMeta.initializing,!e.refreshOnceOnNavigation&&o.afterNavigation&&d(this)}))}),e.refreshOnceOnNavigation&&d(this))),this.$isServer||(o.forEach(t=>{c(this.$options,t,()=>n(this,t))}),c(this.$options,"destroyed",()=>{const t=setInterval(()=>{this.$el&&null!==this.$el.offsetParent||(clearInterval(t),this.$parent&&n(this,"destroyed"))},50)}))}}}}(t,e)))}i(window)||i(window.Vue)||J(window.Vue);var F={version:"2.1.0",install:J,hasMetaInfo:l};export default F;
diff --git a/dist/vue-meta.esm.js b/dist/vue-meta.esm.js
index e83fd1a..c19ec99 100644
--- a/dist/vue-meta.esm.js
+++ b/dist/vue-meta.esm.js
@@ -1,5 +1,5 @@
/**
- * vue-meta v2.0.5
+ * vue-meta v2.1.0
* (c) 2019
* - Declan de Wet
* - Sébastien Chopin (@Atinux)
@@ -9,12 +9,11 @@
import deepmerge from 'deepmerge';
-var version = "2.0.5";
+var version = "2.1.0";
// store an id to keep track of DOM updates
-var batchId = null;
-
-function triggerUpdate (vm, hookName) {
+let batchId = null;
+function triggerUpdate(vm, hookName) {
// if an update was triggered during initialization or when an update was triggered by the
// metaInfo watcher, set initialized to null
// then we keep falsy value but know we need to run a triggerUpdate after initialization
@@ -24,10 +23,9 @@ function triggerUpdate (vm, hookName) {
if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) {
// batch potential DOM updates to prevent extraneous re-rendering
- batchUpdate(function () { return vm.$meta().refresh(); });
+ batchUpdate(() => vm.$meta().refresh());
}
}
-
/**
* Performs a batched update.
*
@@ -35,16 +33,13 @@ function triggerUpdate (vm, hookName) {
* @param {Function} callback - the update to perform
* @return {Number} id - a new ID
*/
-function batchUpdate (callback, timeout) {
- if ( timeout === void 0 ) timeout = 10;
+function batchUpdate(callback, timeout = 10) {
clearTimeout(batchId);
-
- batchId = setTimeout(function () {
+ batchId = setTimeout(() => {
callback();
}, timeout);
-
- return batchId
+ return batchId;
}
/**
@@ -52,247 +47,254 @@ function batchUpdate (callback, timeout) {
* @param {any} arg - the object to check
* @return {Boolean} - true if `arg` is an array
*/
-function isArray (arg) {
- return Array.isArray(arg)
+function isArray(arg) {
+ return Array.isArray(arg);
+}
+function isUndefined(arg) {
+ return typeof arg === 'undefined';
+}
+function isObject(arg) {
+ return typeof arg === 'object';
+}
+function isPureObject(arg) {
+ return typeof arg === 'object' && arg !== null;
+}
+function isFunction(arg) {
+ return typeof arg === 'function';
+}
+function isString(arg) {
+ return typeof arg === 'string';
}
-function isUndefined (arg) {
- return typeof arg === 'undefined'
-}
-
-function isObject (arg) {
- return typeof arg === 'object'
-}
-
-function isFunction (arg) {
- return typeof arg === 'function'
-}
-
-function isString (arg) {
- return typeof arg === 'string'
-}
-
-function ensureIsArray (arg, key) {
+function ensureIsArray(arg, key) {
if (!key || !isObject(arg)) {
- return isArray(arg) ? arg : []
+ return isArray(arg) ? arg : [];
}
if (!isArray(arg[key])) {
arg[key] = [];
}
- return arg
+
+ return arg;
}
-
-function ensuredPush (object, key, el) {
+function ensuredPush(object, key, el) {
ensureIsArray(object, key);
-
object[key].push(el);
}
-// Vue $root instance has a _vueMeta object property, otherwise its a boolean true
-function hasMetaInfo (vm) {
- if ( vm === void 0 ) vm = this;
+function hasMetaInfo(vm = this) {
+ return vm && (vm._vueMeta === true || isObject(vm._vueMeta));
+} // a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has
- return vm && (vm._vueMeta === true || isObject(vm._vueMeta))
+function inMetaInfoBranch(vm = this) {
+ return vm && !isUndefined(vm._vueMeta);
}
-// a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has
-function inMetaInfoBranch (vm) {
- if ( vm === void 0 ) vm = this;
-
- return vm && !isUndefined(vm._vueMeta)
-}
-
-function addNavGuards (vm) {
+function addNavGuards(vm) {
// return when nav guards already added or no router exists
if (vm.$root._vueMeta.navGuards || !vm.$root.$router) {
/* istanbul ignore next */
- return
+ return;
}
vm.$root._vueMeta.navGuards = true;
-
- var $router = vm.$root.$router;
- var $meta = vm.$root.$meta();
-
- $router.beforeEach(function (to, from, next) {
+ const $router = vm.$root.$router;
+ const $meta = vm.$root.$meta();
+ $router.beforeEach((to, from, next) => {
$meta.pause();
next();
});
+ $router.afterEach(() => {
+ const {
+ metaInfo
+ } = $meta.resume();
- $router.afterEach(function () {
- var ref = $meta.resume();
- var metaInfo = ref.metaInfo;
if (metaInfo && metaInfo.afterNavigation && isFunction(metaInfo.afterNavigation)) {
metaInfo.afterNavigation(metaInfo);
}
});
}
-var appId = 1;
+function hasGlobalWindowFn() {
+ try {
+ return !isUndefined(window);
+ } catch (e) {
+ return false;
+ }
+}
+const hasGlobalWindow = hasGlobalWindowFn();
-function createMixin (Vue, options) {
+const _global = hasGlobalWindow ? window : global;
+
+const console = _global.console = _global.console || {};
+function warn(...args) {
+ /* istanbul ignore next */
+ if (!console || !console.warn) {
+ return;
+ }
+
+ console.warn(...args);
+}
+
+let appId = 1;
+function createMixin(Vue, options) {
// for which Vue lifecycle hooks should the metaInfo be refreshed
- var updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount'];
+ const updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount']; // watch for client side component updates
- // watch for client side component updates
return {
- beforeCreate: function beforeCreate () {
- var this$1 = this;
-
+ beforeCreate() {
Object.defineProperty(this, '_hasMetaInfo', {
configurable: true,
- get: function get () {
+
+ get() {
// Show deprecation warning once when devtools enabled
if (Vue.config.devtools && !this.$root._vueMeta.hasMetaInfoDeprecationWarningShown) {
- console.warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead'); // eslint-disable-line no-console
+ warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead');
this.$root._vueMeta.hasMetaInfoDeprecationWarningShown = true;
}
- return hasMetaInfo(this)
- }
- });
- // Add a marker to know if it uses metaInfo
+ return hasMetaInfo(this);
+ }
+
+ }); // Add a marker to know if it uses metaInfo
// _vnode is used to know that it's attached to a real component
// useful if we use some mixin to add some meta tags (like nuxt-i18n)
+
if (!isUndefined(this.$options[options.keyName]) && this.$options[options.keyName] !== null) {
if (!this.$root._vueMeta) {
- this.$root._vueMeta = { appId: appId };
+ this.$root._vueMeta = {
+ appId
+ };
appId++;
- }
-
- // to speed up updates we keep track of branches which have a component with vue-meta info defined
+ } // to speed up updates we keep track of branches which have a component with vue-meta info defined
// if _vueMeta = true it has info, if _vueMeta = false a child has info
+
+
if (!this._vueMeta) {
this._vueMeta = true;
+ let p = this.$parent;
- var p = this.$parent;
while (p && p !== this.$root) {
if (isUndefined(p._vueMeta)) {
p._vueMeta = false;
}
+
p = p.$parent;
}
- }
-
- // coerce function-style metaInfo to a computed prop so we can observe
+ } // coerce function-style metaInfo to a computed prop so we can observe
// it on creation
+
+
if (isFunction(this.$options[options.keyName])) {
if (!this.$options.computed) {
this.$options.computed = {};
}
+
this.$options.computed.$metaInfo = this.$options[options.keyName];
if (!this.$isServer) {
// if computed $metaInfo exists, watch it for updates & trigger a refresh
// when it changes (i.e. automatically handle async actions that affect metaInfo)
// credit for this suggestion goes to [Sébastien Chopin](https://github.com/Atinux)
- ensuredPush(this.$options, 'created', function () {
- this$1.$watch('$metaInfo', function () {
+ ensuredPush(this.$options, 'created', () => {
+ this.$watch('$metaInfo', function () {
triggerUpdate(this, 'watcher');
});
});
}
- }
-
- // force an initial refresh on page load and prevent other lifecycleHooks
+ } // force an initial refresh on page load and prevent other lifecycleHooks
// to triggerUpdate until this initial refresh is finished
// this is to make sure that when a page is opened in an inactive tab which
// has throttled rAF/timers we still immediately set the page title
+
+
if (isUndefined(this.$root._vueMeta.initialized)) {
this.$root._vueMeta.initialized = this.$isServer;
if (!this.$root._vueMeta.initialized) {
- ensuredPush(this.$options, 'beforeMount', function () {
+ ensuredPush(this.$options, 'beforeMount', () => {
// if this Vue-app was server rendered, set the appId to 'ssr'
// only one SSR app per page is supported
- if (this$1.$root.$el && this$1.$root.$el.hasAttribute && this$1.$root.$el.hasAttribute('data-server-rendered')) {
- this$1.$root._vueMeta.appId = 'ssr';
+ if (this.$root.$el && this.$root.$el.hasAttribute && this.$root.$el.hasAttribute('data-server-rendered')) {
+ this.$root._vueMeta.appId = options.ssrAppId;
}
- });
+ }); // we use the mounted hook here as on page load
- // we use the mounted hook here as on page load
- ensuredPush(this.$options, 'mounted', function () {
- if (!this$1.$root._vueMeta.initialized) {
+ ensuredPush(this.$options, 'mounted', () => {
+ if (!this.$root._vueMeta.initialized) {
// used in triggerUpdate to check if a change was triggered
// during initialization
- this$1.$root._vueMeta.initializing = true;
+ this.$root._vueMeta.initializing = true; // refresh meta in nextTick so all child components have loaded
- // refresh meta in nextTick so all child components have loaded
- this$1.$nextTick(function () {
- var this$1 = this;
-
- var ref = this.$root.$meta().refresh();
- var tags = ref.tags;
- var metaInfo = ref.metaInfo;
-
- // After ssr hydration (identifier by tags === false) check
+ this.$nextTick(function () {
+ const {
+ tags,
+ metaInfo
+ } = this.$root.$meta().refresh(); // After ssr hydration (identifier by tags === false) check
// if initialized was set to null in triggerUpdate. That'd mean
// that during initilazation changes where triggered which need
// to be applied OR a metaInfo watcher was triggered before the
// current hook was called
// (during initialization all changes are blocked)
+
if (tags === false && this.$root._vueMeta.initialized === null) {
- this.$nextTick(function () { return triggerUpdate(this$1, 'initializing'); });
+ this.$nextTick(() => triggerUpdate(this, 'initializing'));
}
this.$root._vueMeta.initialized = true;
- delete this.$root._vueMeta.initializing;
-
- // add the navigation guards if they havent been added yet
+ delete this.$root._vueMeta.initializing; // add the navigation guards if they havent been added yet
// they are needed for the afterNavigation callback
+
if (!options.refreshOnceOnNavigation && metaInfo.afterNavigation) {
addNavGuards(this);
}
});
}
- });
+ }); // add the navigation guards if requested
- // add the navigation guards if requested
if (options.refreshOnceOnNavigation) {
addNavGuards(this);
}
}
- }
+ } // do not trigger refresh on the server side
+
- // do not trigger refresh on the server side
if (!this.$isServer) {
// no need to add this hooks on server side
- updateOnLifecycleHook.forEach(function (lifecycleHook) {
- ensuredPush(this$1.$options, lifecycleHook, function () { return triggerUpdate(this$1, lifecycleHook); });
- });
+ updateOnLifecycleHook.forEach(lifecycleHook => {
+ ensuredPush(this.$options, lifecycleHook, () => triggerUpdate(this, lifecycleHook));
+ }); // re-render meta data when returning from a child component to parent
- // re-render meta data when returning from a child component to parent
- ensuredPush(this.$options, 'destroyed', function () {
+ ensuredPush(this.$options, 'destroyed', () => {
// Wait that element is hidden before refreshing meta tags (to support animations)
- var interval = setInterval(function () {
- if (this$1.$el && this$1.$el.offsetParent !== null) {
+ const interval = setInterval(() => {
+ if (this.$el && this.$el.offsetParent !== null) {
/* istanbul ignore next line */
- return
+ return;
}
clearInterval(interval);
- if (!this$1.$parent) {
+ if (!this.$parent) {
/* istanbul ignore next line */
- return
+ return;
}
- triggerUpdate(this$1, 'destroyed');
+ triggerUpdate(this, 'destroyed');
}, 50);
});
}
}
}
- }
+
+ };
}
/**
* These are constant variables used throughout the application.
*/
-
// set some sane defaults
-var defaultInfo = {
+const defaultInfo = {
title: undefined,
titleChunk: '',
titleTemplate: '%s',
@@ -306,183 +308,112 @@ var defaultInfo = {
script: [],
noscript: [],
__dangerouslyDisableSanitizers: [],
- __dangerouslyDisableSanitizersByTagID: {}
+ __dangerouslyDisableSanitizersByTagID: {} // 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.
+
};
-
-// 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.
-var keyName = 'metaInfo';
-
-// This is the attribute vue-meta arguments on elements to know which it should
+const keyName = 'metaInfo'; // This is the attribute vue-meta arguments on elements to know which it should
// manage and which it should ignore.
-var attribute = 'data-vue-meta';
-// This is the attribute that goes on the `html` tag to inform `vue-meta`
+const attribute = 'data-vue-meta'; // 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.
-var ssrAttribute = 'data-vue-meta-server-rendered';
-// This is the property that tells vue-meta to overwrite (instead of append)
+const ssrAttribute = '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.
-var tagIDKeyName = 'vmid';
-// This is the key name for possible meta templates
-var metaTemplateKeyName = 'template';
+const tagIDKeyName = 'vmid'; // This is the key name for possible meta templates
-// This is the key name for the content-holding property
-var contentKeyName = 'content';
+const metaTemplateKeyName = 'template'; // This is the key name for the content-holding property
+
+const contentKeyName = 'content'; // The id used for the ssr app
+
+const ssrAppId = 'ssr';
+const defaultOptions = {
+ keyName,
+ attribute,
+ ssrAttribute,
+ tagIDKeyName,
+ contentKeyName,
+ metaTemplateKeyName,
+ ssrAppId // List of metaInfo property keys which are configuration options (and dont generate html)
-var defaultOptions = {
- keyName: keyName,
- attribute: attribute,
- ssrAttribute: ssrAttribute,
- tagIDKeyName: tagIDKeyName,
- contentKeyName: contentKeyName,
- metaTemplateKeyName: metaTemplateKeyName
};
+const metaInfoOptionKeys = ['titleChunk', 'titleTemplate', 'changed', '__dangerouslyDisableSanitizers', '__dangerouslyDisableSanitizersByTagID']; // The metaInfo property keys which are used to disable escaping
-// List of metaInfo property keys which are configuration options (and dont generate html)
-var metaInfoOptionKeys = [
- 'titleChunk',
- 'titleTemplate',
- 'changed',
- '__dangerouslyDisableSanitizers',
- '__dangerouslyDisableSanitizersByTagID'
-];
+const disableOptionKeys = ['__dangerouslyDisableSanitizers', '__dangerouslyDisableSanitizersByTagID']; // List of metaInfo property keys which only generates attributes and no tags
-// The metaInfo property keys which are used to disable escaping
-var disableOptionKeys = [
- '__dangerouslyDisableSanitizers',
- '__dangerouslyDisableSanitizersByTagID'
-];
+const metaInfoAttributeKeys = ['htmlAttrs', 'headAttrs', 'bodyAttrs']; // HTML elements which support the onload event
-// List of metaInfo property keys which only generates attributes and no tags
-var metaInfoAttributeKeys = [
- 'htmlAttrs',
- 'headAttrs',
- 'bodyAttrs'
-];
-
-// HTML elements which dont have a head tag (shortened to our needs)
+const tagsSupportingOnload = ['link', 'style', 'script']; // HTML elements which dont have a head tag (shortened to our needs)
// see: https://www.w3.org/TR/html52/document-metadata.html
-var tagsWithoutEndTag = ['base', 'meta', 'link'];
-// HTML elements which can have inner content (shortened to our needs)
-var tagsWithInnerContent = ['noscript', 'script', 'style'];
+const tagsWithoutEndTag = ['base', 'meta', 'link']; // HTML elements which can have inner content (shortened to our needs)
-// Attributes which are inserted as childNodes instead of HTMLAttribute
-var tagAttributeAsInnerContent = ['innerHTML', 'cssText'];
+const tagsWithInnerContent = ['noscript', 'script', 'style']; // Attributes which are inserted as childNodes instead of HTMLAttribute
-// from: https://github.com/kangax/html-minifier/blob/gh-pages/src/htmlminifier.js#L202
-var booleanHtmlAttributes = [
- 'allowfullscreen',
- 'amp',
- 'async',
- 'autofocus',
- 'autoplay',
- 'checked',
- 'compact',
- 'controls',
- 'declare',
- 'default',
- 'defaultchecked',
- 'defaultmuted',
- 'defaultselected',
- 'defer',
- 'disabled',
- 'enabled',
- 'formnovalidate',
- 'hidden',
- 'indeterminate',
- 'inert',
- 'ismap',
- 'itemscope',
- 'loop',
- 'multiple',
- 'muted',
- 'nohref',
- 'noresize',
- 'noshade',
- 'novalidate',
- 'nowrap',
- 'open',
- 'pauseonexit',
- 'readonly',
- 'required',
- 'reversed',
- 'scoped',
- 'seamless',
- 'selected',
- 'sortable',
- 'truespeed',
- 'typemustmatch',
- 'visible'
-];
+const tagAttributeAsInnerContent = ['innerHTML', 'cssText', 'json']; // Attributes which should be added with data- prefix
-function setOptions (options) {
+const commonDataAttributes = ['body', 'pbody']; // from: https://github.com/kangax/html-minifier/blob/gh-pages/src/htmlminifier.js#L202
+
+const booleanHtmlAttributes = ['allowfullscreen', 'amp', 'async', 'autofocus', 'autoplay', 'checked', 'compact', 'controls', 'declare', 'default', 'defaultchecked', 'defaultmuted', 'defaultselected', 'defer', 'disabled', 'enabled', 'formnovalidate', 'hidden', 'indeterminate', 'inert', 'ismap', 'itemscope', 'loop', 'multiple', 'muted', 'nohref', 'noresize', 'noshade', 'novalidate', 'nowrap', 'open', 'pauseonexit', 'readonly', 'required', 'reversed', 'scoped', 'seamless', 'selected', 'sortable', 'truespeed', 'typemustmatch', 'visible'];
+
+function setOptions(options) {
// combine options
options = isObject(options) ? options : {};
- for (var key in defaultOptions) {
+ for (const key in defaultOptions) {
if (!options[key]) {
options[key] = defaultOptions[key];
}
}
- return options
+ return options;
}
+function getOptions(options) {
+ const optionsCopy = {};
-function getOptions (options) {
- var optionsCopy = {};
- for (var key in options) {
+ for (const key in options) {
optionsCopy[key] = options[key];
}
- return optionsCopy
+
+ return optionsCopy;
}
-function pause (refresh) {
- if ( refresh === void 0 ) refresh = true;
-
+function pause(refresh = true) {
this.$root._vueMeta.paused = true;
-
- return function () { return resume(refresh); }
+ return () => resume(refresh);
}
-
-function resume (refresh) {
- if ( refresh === void 0 ) refresh = true;
-
+function resume(refresh = true) {
this.$root._vueMeta.paused = false;
if (refresh) {
- return this.$root.$meta().refresh()
+ return this.$root.$meta().refresh();
}
}
-function applyTemplate (ref, headObject, template, chunk) {
- var component = ref.component;
- var metaTemplateKeyName = ref.metaTemplateKeyName;
- var contentKeyName = ref.contentKeyName;
-
+function applyTemplate({
+ component,
+ metaTemplateKeyName,
+ contentKeyName
+}, headObject, template, chunk) {
if (isUndefined(template)) {
template = headObject[metaTemplateKeyName];
delete headObject[metaTemplateKeyName];
- }
+ } // return early if no template defined
+
- // return early if no template defined
if (!template) {
- return false
+ return false;
}
if (isUndefined(chunk)) {
chunk = headObject[contentKeyName];
}
- headObject[contentKeyName] = isFunction(template)
- ? template.call(component, chunk)
- : template.replace(/%s/g, chunk);
-
- return true
+ headObject[contentKeyName] = isFunction(template) ? template.call(component, chunk) : template.replace(/%s/g, chunk);
+ return true;
}
/*
@@ -493,179 +424,185 @@ function applyTemplate (ref, headObject, template, chunk) {
* Also, only files in client/ & shared/ should use these functions
* files in server/ still use normal js function
*/
-
-function findIndex (array, predicate) {
- var arguments$1 = arguments;
-
+function findIndex(array, predicate) {
if ( !Array.prototype.findIndex) {
// idx needs to be a Number, for..in returns string
- for (var idx = 0; idx < array.length; idx++) {
- if (predicate.call(arguments$1[2], array[idx], idx, array)) {
- return idx
+ for (let idx = 0; idx < array.length; idx++) {
+ if (predicate.call(arguments[2], array[idx], idx, array)) {
+ return idx;
}
}
- return -1
- }
- return array.findIndex(predicate, arguments[2])
-}
-function toArray (arg) {
+ return -1;
+ }
+
+ return array.findIndex(predicate, arguments[2]);
+}
+function toArray(arg) {
if ( !Array.from) {
- return Array.prototype.slice.call(arg)
+ return Array.prototype.slice.call(arg);
}
- return Array.from(arg)
-}
-function includes (array, value) {
+ return Array.from(arg);
+}
+function includes(array, value) {
if ( !Array.prototype.includes) {
- for (var idx in array) {
+ for (const idx in array) {
if (array[idx] === value) {
- return true
+ return true;
}
}
- return false
+ return false;
}
- return array.includes(value)
+
+ return array.includes(value);
}
-var serverSequences = [
- [/&/g, '&'],
- [//g, '>'],
- [/"/g, '"'],
- [/'/g, ''']
-];
+const serverSequences = [[/&/g, '&'], [//g, '>'], [/"/g, '"'], [/'/g, ''']];
+const clientSequences = [[/&/g, '\u0026'], [//g, '\u003E'], [/"/g, '\u0022'], [/'/g, '\u0027']]; // sanitizes potentially dangerous characters
-var clientSequences = [
- [/&/g, '\u0026'],
- [//g, '\u003E'],
- [/"/g, '\u0022'],
- [/'/g, '\u0027']
-];
+function escape(info, options, escapeOptions) {
+ const {
+ tagIDKeyName
+ } = options;
+ const {
+ doEscape = v => v,
+ escapeKeys
+ } = escapeOptions;
+ const escaped = {};
-// sanitizes potentially dangerous characters
-function escape (info, options, escapeOptions) {
- var tagIDKeyName = options.tagIDKeyName;
- var doEscape = escapeOptions.doEscape; if ( doEscape === void 0 ) doEscape = function (v) { return v; };
- var escaped = {};
+ for (const key in info) {
+ const value = info[key]; // no need to escape configuration options
- for (var key in info) {
- var value = info[key];
-
- // no need to escape configuration options
if (includes(metaInfoOptionKeys, key)) {
escaped[key] = value;
- continue
+ continue;
}
- var disableKey = disableOptionKeys[0];
+ let [disableKey] = disableOptionKeys;
+
if (escapeOptions[disableKey] && includes(escapeOptions[disableKey], key)) {
// this info[key] doesnt need to escaped if the option is listed in __dangerouslyDisableSanitizers
escaped[key] = value;
- continue
+ continue;
}
- var tagId = info[tagIDKeyName];
+ const tagId = info[tagIDKeyName];
+
if (tagId) {
- disableKey = disableOptionKeys[1];
+ disableKey = disableOptionKeys[1]; // keys which are listed in __dangerouslyDisableSanitizersByTagID for the current vmid do not need to be escaped
- // keys which are listed in __dangerouslyDisableSanitizersByTagID for the current vmid do not need to be escaped
if (escapeOptions[disableKey] && escapeOptions[disableKey][tagId] && includes(escapeOptions[disableKey][tagId], key)) {
escaped[key] = value;
- continue
+ continue;
}
}
if (isString(value)) {
escaped[key] = doEscape(value);
} else if (isArray(value)) {
- escaped[key] = value.map(function (v) {
- return isObject(v)
- ? escape(v, options, escapeOptions)
- : doEscape(v)
+ escaped[key] = value.map(v => {
+ if (isPureObject(v)) {
+ return escape(v, options, { ...escapeOptions,
+ escapeKeys: true
+ });
+ }
+
+ return doEscape(v);
+ });
+ } else if (isPureObject(value)) {
+ escaped[key] = escape(value, options, { ...escapeOptions,
+ escapeKeys: true
});
- } else if (isObject(value)) {
- escaped[key] = escape(value, options, escapeOptions);
} else {
escaped[key] = value;
}
+
+ if (escapeKeys) {
+ const escapedKey = doEscape(key);
+
+ if (key !== escapedKey) {
+ escaped[escapedKey] = escaped[key];
+ delete escaped[key];
+ }
+ }
}
- return escaped
+ return escaped;
}
-function arrayMerge (ref, target, source) {
- var component = ref.component;
- var tagIDKeyName = ref.tagIDKeyName;
- var metaTemplateKeyName = ref.metaTemplateKeyName;
- var contentKeyName = ref.contentKeyName;
-
+function arrayMerge({
+ component,
+ tagIDKeyName,
+ metaTemplateKeyName,
+ contentKeyName
+}, target, source) {
// we concat the arrays without merging objects contained in,
// but we check for a `vmid` property on each object in the array
// using an O(1) lookup associative array exploit
- var destination = [];
-
- target.forEach(function (targetItem, targetIndex) {
+ const destination = [];
+ target.forEach((targetItem, targetIndex) => {
// no tagID so no need to check for duplicity
if (!targetItem[tagIDKeyName]) {
destination.push(targetItem);
- return
+ return;
}
- var sourceIndex = findIndex(source, function (item) { return item[tagIDKeyName] === targetItem[tagIDKeyName]; });
- var sourceItem = source[sourceIndex];
+ const sourceIndex = findIndex(source, item => item[tagIDKeyName] === targetItem[tagIDKeyName]);
+ const sourceItem = source[sourceIndex]; // source doesnt contain any duplicate vmid's, we can keep targetItem
- // source doesnt contain any duplicate vmid's, we can keep targetItem
if (sourceIndex === -1) {
destination.push(targetItem);
- return
- }
-
- // when sourceItem explictly defines contentKeyName or innerHTML as undefined, its
+ return;
+ } // when sourceItem explictly defines contentKeyName or innerHTML as undefined, its
// an indication that we need to skip the default behaviour or child has preference over parent
// which means we keep the targetItem and ignore/remove the sourceItem
- if ((sourceItem.hasOwnProperty(contentKeyName) && sourceItem[contentKeyName] === undefined) ||
- (sourceItem.hasOwnProperty('innerHTML') && sourceItem.innerHTML === undefined)) {
- destination.push(targetItem);
- // remove current index from source array so its not concatenated to destination below
+
+
+ if (sourceItem.hasOwnProperty(contentKeyName) && sourceItem[contentKeyName] === undefined || sourceItem.hasOwnProperty('innerHTML') && sourceItem.innerHTML === undefined) {
+ destination.push(targetItem); // remove current index from source array so its not concatenated to destination below
+
source.splice(sourceIndex, 1);
- return
- }
-
- // we now know that targetItem is a duplicate and we should ignore it in favor of sourceItem
-
+ return;
+ } // we now know that targetItem is a duplicate and we should ignore it in favor of sourceItem
// if source specifies null as content then ignore both the target as the source
+
+
if (sourceItem[contentKeyName] === null || sourceItem.innerHTML === null) {
// remove current index from source array so its not concatenated to destination below
source.splice(sourceIndex, 1);
- return
- }
+ return;
+ } // now we only need to check if the target has a template to combine it with the source
+
+
+ const targetTemplate = targetItem[metaTemplateKeyName];
- // now we only need to check if the target has a template to combine it with the source
- var targetTemplate = targetItem[metaTemplateKeyName];
if (!targetTemplate) {
- return
+ return;
}
- var sourceTemplate = sourceItem[metaTemplateKeyName];
+ const sourceTemplate = sourceItem[metaTemplateKeyName];
if (!sourceTemplate) {
// use parent template and child content
- applyTemplate({ component: component, metaTemplateKeyName: metaTemplateKeyName, contentKeyName: contentKeyName }, sourceItem, targetTemplate);
+ applyTemplate({
+ component,
+ metaTemplateKeyName,
+ contentKeyName
+ }, sourceItem, targetTemplate);
} else if (!sourceItem[contentKeyName]) {
// use child template and parent content
- applyTemplate({ component: component, metaTemplateKeyName: metaTemplateKeyName, contentKeyName: contentKeyName }, sourceItem, undefined, targetItem[contentKeyName]);
+ applyTemplate({
+ component,
+ metaTemplateKeyName,
+ contentKeyName
+ }, sourceItem, undefined, targetItem[contentKeyName]);
}
});
-
- return destination.concat(source)
+ return destination.concat(source);
}
-
-function merge (target, source, options) {
- if ( options === void 0 ) options = {};
-
+function merge(target, source, options = {}) {
// remove properties explicitly set to false so child components can
// optionally _not_ overwrite the parents content
// (for array properties this is checked in arrayMerge)
@@ -673,25 +610,24 @@ function merge (target, source, options) {
delete source.title;
}
- metaInfoAttributeKeys.forEach(function (attrKey) {
+ metaInfoAttributeKeys.forEach(attrKey => {
if (!source[attrKey]) {
- return
+ return;
}
- for (var key in source[attrKey]) {
+ for (const key in source[attrKey]) {
if (source[attrKey].hasOwnProperty(key) && source[attrKey][key] === undefined) {
- if (booleanHtmlAttributes.includes(key)) {
- // eslint-disable-next-line no-console
- console.warn('VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details');
+ if (includes(booleanHtmlAttributes, key)) {
+ warn('VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details');
}
+
delete source[attrKey][key];
}
}
});
-
return deepmerge(target, source, {
- arrayMerge: function (t, s) { return arrayMerge(options, t, s); }
- })
+ arrayMerge: (t, s) => arrayMerge(options, t, s)
+ });
}
/**
@@ -708,45 +644,46 @@ function merge (target, source, options) {
* @param {Object} [result={}] - result so far
* @return {Object} result - final aggregated result
*/
-function getComponentOption (options, component, result) {
- if ( options === void 0 ) options = {};
- if ( result === void 0 ) result = {};
- var keyName = options.keyName;
- var metaTemplateKeyName = options.metaTemplateKeyName;
- var tagIDKeyName = options.tagIDKeyName;
- var $options = component.$options;
- var $children = component.$children;
+function getComponentOption(options = {}, component, result = {}) {
+ const {
+ keyName,
+ metaTemplateKeyName,
+ tagIDKeyName
+ } = options;
+ const {
+ $options,
+ $children
+ } = component;
if (component._inactive) {
- return result
- }
+ return result;
+ } // only collect option data if it exists
+
- // only collect option data if it exists
if ($options[keyName]) {
- var data = $options[keyName];
+ let data = $options[keyName]; // if option is a function, replace it with it's result
- // if option is a function, replace it with it's result
if (isFunction(data)) {
data = data.call(component);
- }
+ } // ignore data if its not an object, then we keep our previous result
+
- // ignore data if its not an object, then we keep our previous result
if (!isObject(data)) {
- return result
- }
+ return result;
+ } // merge with existing options
+
- // merge with existing options
result = merge(result, data, options);
- }
+ } // collect & aggregate child options if deep = true
+
- // collect & aggregate child options if deep = true
if ($children.length) {
- $children.forEach(function (childComponent) {
+ $children.forEach(childComponent => {
// check if the childComponent is in a branch
// return otherwise so we dont walk all component branches unnecessarily
if (!inMetaInfoBranch(childComponent)) {
- return
+ return;
}
result = getComponentOption(options, childComponent, result);
@@ -755,20 +692,17 @@ function getComponentOption (options, component, result) {
if (metaTemplateKeyName && result.meta) {
// apply templates if needed
- result.meta.forEach(function (metaObject) { return applyTemplate(options, metaObject); });
+ result.meta.forEach(metaObject => applyTemplate(options, metaObject)); // remove meta items with duplicate vmid's
- // remove meta items with duplicate vmid's
- result.meta = result.meta.filter(function (metaItem, index, arr) {
- return (
- // keep meta item if it doesnt has a vmid
- !metaItem.hasOwnProperty(tagIDKeyName) ||
- // or if it's the first item in the array with this vmid
- index === findIndex(arr, function (item) { return item[tagIDKeyName] === metaItem[tagIDKeyName]; })
- )
+ result.meta = result.meta.filter((metaItem, index, arr) => {
+ return (// keep meta item if it doesnt has a vmid
+ !metaItem.hasOwnProperty(tagIDKeyName) || // or if it's the first item in the array with this vmid
+ index === findIndex(arr, item => item[tagIDKeyName] === metaItem[tagIDKeyName])
+ );
});
}
- return result
+ return result;
}
/**
@@ -778,56 +712,180 @@ function getComponentOption (options, component, result) {
* @param {Object} component - the Vue instance to get meta info from
* @return {Object} - returned meta info
*/
-function getMetaInfo (options, component, escapeSequences) {
- if ( options === void 0 ) options = {};
- if ( escapeSequences === void 0 ) escapeSequences = [];
+function getMetaInfo(options = {}, component, escapeSequences = []) {
// collect & aggregate all metaInfo $options
- var info = getComponentOption(options, component, defaultInfo);
-
- // Remove all "template" tags from meta
-
+ let info = getComponentOption(options, component, defaultInfo); // Remove all "template" tags from meta
// backup the title chunk in case user wants access to it
+
if (info.title) {
info.titleChunk = info.title;
- }
+ } // replace title with populated template
+
- // replace title with populated template
if (info.titleTemplate && info.titleTemplate !== '%s') {
- applyTemplate({ component: component, contentKeyName: 'title' }, info, info.titleTemplate, info.titleChunk || '');
- }
-
- // convert base tag to an array so it can be handled the same way
+ applyTemplate({
+ component,
+ contentKeyName: 'title'
+ }, info, info.titleTemplate, 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] : [];
}
- var escapeOptions = {
- doEscape: function (value) { return escapeSequences.reduce(function (val, ref) {
- var v = ref[0];
- var r = ref[1];
-
- return val.replace(v, r);
- }, value); }
+ const escapeOptions = {
+ doEscape: value => escapeSequences.reduce((val, [v, r]) => val.replace(v, r), value)
};
-
- disableOptionKeys.forEach(function (disableKey, index) {
+ disableOptionKeys.forEach((disableKey, index) => {
if (index === 0) {
ensureIsArray(info, disableKey);
} else if (index === 1) {
- for (var key in info[disableKey]) {
+ for (const key in info[disableKey]) {
ensureIsArray(info[disableKey], key);
}
}
escapeOptions[disableKey] = info[disableKey];
- });
+ }); // begin sanitization
- // begin sanitization
info = escape(info, options, escapeOptions);
+ return info;
+}
- return info
+function getTag(tags, tag) {
+ if (!tags[tag]) {
+ tags[tag] = document.getElementsByTagName(tag)[0];
+ }
+
+ return tags[tag];
+}
+function getElementsKey({
+ body,
+ pbody
+}) {
+ return body ? 'body' : pbody ? 'pbody' : 'head';
+}
+function queryElements(parentNode, {
+ appId,
+ attribute,
+ type,
+ tagIDKeyName
+}, attributes = {}) {
+ const queries = [`${type}[${attribute}="${appId}"]`, `${type}[data-${tagIDKeyName}]`].map(query => {
+ for (const key in attributes) {
+ const val = attributes[key];
+ const attributeValue = val && val !== true ? `="${val}"` : '';
+ query += `[data-${key}${attributeValue}]`;
+ }
+
+ return query;
+ });
+ return toArray(parentNode.querySelectorAll(queries.join(', ')));
+}
+
+const callbacks = [];
+function isDOMComplete(d = document) {
+ return d.readyState === 'complete';
+}
+function addCallback(query, callback) {
+ if (arguments.length === 1) {
+ callback = query;
+ query = '';
+ }
+
+ callbacks.push([query, callback]);
+}
+function addCallbacks({
+ tagIDKeyName
+}, type, tags, autoAddListeners) {
+ let hasAsyncCallback = false;
+
+ for (const tag of tags) {
+ if (!tag[tagIDKeyName] || !tag.callback) {
+ continue;
+ }
+
+ hasAsyncCallback = true;
+ addCallback(`${type}[data-${tagIDKeyName}="${tag[tagIDKeyName]}"]`, tag.callback);
+ }
+
+ if (!autoAddListeners || !hasAsyncCallback) {
+ return hasAsyncCallback;
+ }
+
+ return addListeners();
+}
+function addListeners() {
+ if (isDOMComplete()) {
+ applyCallbacks();
+ return;
+ } // Instead of using a MutationObserver, we just apply
+
+ /* istanbul ignore next */
+
+
+ document.onreadystatechange = () => {
+ applyCallbacks();
+ };
+}
+function applyCallbacks(matchElement) {
+ for (const [query, callback] of callbacks) {
+ const selector = `${query}[onload="this.__vm_l=1"]`;
+ let elements = [];
+
+ if (!matchElement) {
+ elements = toArray(document.querySelectorAll(selector));
+ }
+
+ if (matchElement && matchElement.matches(selector)) {
+ elements = [matchElement];
+ }
+
+ for (const element of elements) {
+ /* __vm_cb: whether the load callback has been called
+ * __vm_l: set by onload attribute, whether the element was loaded
+ * __vm_ev: whether the event listener was added or not
+ */
+ if (element.__vm_cb) {
+ continue;
+ }
+
+ const onload = () => {
+ /* Mark that the callback for this element has already been called,
+ * this prevents the callback to run twice in some (rare) conditions
+ */
+ element.__vm_cb = true;
+ /* onload needs to be removed because we only need the
+ * attribute after ssr and if we dont remove it the node
+ * will fail isEqualNode on the client
+ */
+
+ element.removeAttribute('onload');
+ callback(element);
+ };
+ /* IE9 doesnt seem to load scripts synchronously,
+ * causing a script sometimes/often already to be loaded
+ * when we add the event listener below (thus adding an onload event
+ * listener has no use because it will never be triggered).
+ * Therefore we add the onload attribute during ssr, and
+ * check here if it was already loaded or not
+ */
+
+
+ if (element.__vm_l) {
+ onload();
+ continue;
+ }
+
+ if (!element.__vm_ev) {
+ element.__vm_ev = true;
+ element.addEventListener('load', onload);
+ }
+ }
+ }
}
/**
@@ -836,43 +894,38 @@ function getMetaInfo (options, component, escapeSequences) {
* @param {Object} attrs - the new document html attributes
* @param {HTMLElement} tag - the HTMLElement tag to update with new attrs
*/
-function updateAttribute (ref, attrs, tag) {
- if ( ref === void 0 ) ref = {};
- var attribute = ref.attribute;
- var vueMetaAttrString = tag.getAttribute(attribute);
- var vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : [];
- var toRemove = toArray(vueMetaAttrs);
+function updateAttribute({
+ attribute
+} = {}, attrs, tag) {
+ const vueMetaAttrString = tag.getAttribute(attribute);
+ const vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : [];
+ const toRemove = toArray(vueMetaAttrs);
+ const keepIndexes = [];
- var keepIndexes = [];
- for (var attr in attrs) {
+ for (const attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
- var value = includes(booleanHtmlAttributes, attr)
- ? ''
- : isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr];
-
+ const value = includes(booleanHtmlAttributes, attr) ? '' : isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr];
tag.setAttribute(attr, value || '');
if (!includes(vueMetaAttrs, attr)) {
vueMetaAttrs.push(attr);
- }
+ } // filter below wont ever check -1
+
- // filter below wont ever check -1
keepIndexes.push(toRemove.indexOf(attr));
}
}
- var removedAttributesCount = toRemove
- .filter(function (el, index) { return !includes(keepIndexes, index); })
- .reduce(function (acc, attr) {
- tag.removeAttribute(attr);
- return acc + 1
- }, 0);
+ const removedAttributesCount = toRemove.filter((el, index) => !includes(keepIndexes, index)).reduce((acc, attr) => {
+ tag.removeAttribute(attr);
+ return acc + 1;
+ }, 0);
if (vueMetaAttrs.length === removedAttributesCount) {
tag.removeAttribute(attribute);
} else {
- tag.setAttribute(attribute, (vueMetaAttrs.sort()).join(','));
+ tag.setAttribute(attribute, vueMetaAttrs.sort().join(','));
}
}
@@ -881,9 +934,9 @@ function updateAttribute (ref, attrs, tag) {
*
* @param {String} title - the new title of the document
*/
-function updateTitle (title) {
- if (title === undefined) {
- return
+function updateTitle(title) {
+ if (!title && title !== '') {
+ return;
}
document.title = title;
@@ -897,98 +950,143 @@ function updateTitle (title) {
* @param {(Array|Object)} tags - an array of tag objects or a single object in case of base
* @return {Object} - a representation of what tags changed
*/
-function updateTag (appId, ref, type, tags, headTag, bodyTag) {
- if ( ref === void 0 ) ref = {};
- var attribute = ref.attribute;
- var tagIDKeyName = ref.tagIDKeyName;
- var oldHeadTags = toArray(headTag.querySelectorAll((type + "[" + attribute + "=\"" + appId + "\"], " + type + "[data-" + tagIDKeyName + "]")));
- var oldBodyTags = toArray(bodyTag.querySelectorAll((type + "[" + attribute + "=\"" + appId + "\"][data-body=\"true\"], " + type + "[data-" + tagIDKeyName + "][data-body=\"true\"]")));
- var dataAttributes = [tagIDKeyName, 'body'];
- var newTags = [];
+function updateTag(appId, options = {}, type, tags, head, body) {
+ const {
+ attribute,
+ tagIDKeyName
+ } = options;
+ const dataAttributes = [tagIDKeyName, ...commonDataAttributes];
+ const newElements = [];
+ const queryOptions = {
+ appId,
+ attribute,
+ type,
+ tagIDKeyName
+ };
+ const currentElements = {
+ head: queryElements(head, queryOptions),
+ pbody: queryElements(body, queryOptions, {
+ pbody: true
+ }),
+ body: queryElements(body, queryOptions, {
+ body: true
+ })
+ };
if (tags.length > 1) {
// remove duplicates that could have been found by merging tags
// which include a mixin with metaInfo and that mixin is used
// by multiple components on the same page
- var found = [];
- tags = tags.filter(function (x) {
- var k = JSON.stringify(x);
- var res = !includes(found, k);
+ const found = [];
+ tags = tags.filter(x => {
+ const k = JSON.stringify(x);
+ const res = !includes(found, k);
found.push(k);
- return res
+ return res;
});
}
if (tags.length) {
- tags.forEach(function (tag) {
- var newElement = document.createElement(type);
-
- newElement.setAttribute(attribute, appId);
-
- var oldTags = tag.body !== true ? oldHeadTags : oldBodyTags;
-
- for (var attr in tag) {
- if (tag.hasOwnProperty(attr)) {
- if (attr === 'innerHTML') {
- newElement.innerHTML = tag.innerHTML;
- } else if (attr === 'cssText') {
- if (newElement.styleSheet) {
- /* istanbul ignore next */
- newElement.styleSheet.cssText = tag.cssText;
- } else {
- newElement.appendChild(document.createTextNode(tag.cssText));
- }
- } else {
- var _attr = includes(dataAttributes, attr)
- ? ("data-" + attr)
- : attr;
-
- var isBooleanAttribute = includes(booleanHtmlAttributes, attr);
- if (isBooleanAttribute && !tag[attr]) {
- continue
- }
-
- var value = isBooleanAttribute ? '' : tag[attr];
- newElement.setAttribute(_attr, value);
- }
- }
+ for (const tag of tags) {
+ if (tag.skip) {
+ continue;
}
- // Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
- var indexToDelete;
- var hasEqualElement = oldTags.some(function (existingTag, index) {
+ const newElement = document.createElement(type);
+ newElement.setAttribute(attribute, appId);
+
+ for (const attr in tag) {
+ /* istanbul ignore next */
+ if (!tag.hasOwnProperty(attr)) {
+ continue;
+ }
+
+ if (attr === 'innerHTML') {
+ newElement.innerHTML = tag.innerHTML;
+ continue;
+ }
+
+ if (attr === 'json') {
+ newElement.innerHTML = JSON.stringify(tag.json);
+ continue;
+ }
+
+ if (attr === 'cssText') {
+ if (newElement.styleSheet) {
+ /* istanbul ignore next */
+ newElement.styleSheet.cssText = tag.cssText;
+ } else {
+ newElement.appendChild(document.createTextNode(tag.cssText));
+ }
+
+ continue;
+ }
+
+ if (attr === 'callback') {
+ newElement.onload = () => tag[attr](newElement);
+
+ continue;
+ }
+
+ const _attr = includes(dataAttributes, attr) ? `data-${attr}` : attr;
+
+ const isBooleanAttribute = includes(booleanHtmlAttributes, attr);
+
+ if (isBooleanAttribute && !tag[attr]) {
+ continue;
+ }
+
+ const value = isBooleanAttribute ? '' : tag[attr];
+ newElement.setAttribute(_attr, value);
+ }
+
+ const oldElements = currentElements[getElementsKey(tag)]; // Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
+
+ let indexToDelete;
+ const hasEqualElement = oldElements.some((existingTag, index) => {
indexToDelete = index;
- return newElement.isEqualNode(existingTag)
+ return newElement.isEqualNode(existingTag);
});
if (hasEqualElement && (indexToDelete || indexToDelete === 0)) {
- oldTags.splice(indexToDelete, 1);
+ oldElements.splice(indexToDelete, 1);
} else {
- newTags.push(newElement);
+ newElements.push(newElement);
}
- });
- }
-
- var oldTags = oldHeadTags.concat(oldBodyTags);
- oldTags.forEach(function (tag) { return tag.parentNode.removeChild(tag); });
- newTags.forEach(function (tag) {
- if (tag.getAttribute('data-body') === 'true') {
- bodyTag.appendChild(tag);
- } else {
- headTag.appendChild(tag);
}
- });
-
- return { oldTags: oldTags, newTags: newTags }
-}
-
-function getTag (tags, tag) {
- if (!tags[tag]) {
- tags[tag] = document.getElementsByTagName(tag)[0];
}
- return tags[tag]
+ let oldElements = [];
+
+ for (const current of Object.values(currentElements)) {
+ oldElements = [...oldElements, ...current];
+ } // remove old elements
+
+
+ for (const element of oldElements) {
+ element.parentNode.removeChild(element);
+ } // insert new elements
+
+
+ for (const element of newElements) {
+ if (element.hasAttribute('data-body')) {
+ body.appendChild(element);
+ continue;
+ }
+
+ if (element.hasAttribute('data-pbody')) {
+ body.insertBefore(element, body.firstChild);
+ continue;
+ }
+
+ head.appendChild(element);
+ }
+
+ return {
+ oldTags: oldElements,
+ newTags: newElements
+ };
}
/**
@@ -996,60 +1094,66 @@ function getTag (tags, tag) {
*
* @param {Object} newInfo - the meta info to update to
*/
-function updateClientMetaInfo (appId, options, newInfo) {
- if ( options === void 0 ) options = {};
- var ssrAttribute = options.ssrAttribute;
+function updateClientMetaInfo(appId, options = {}, newInfo) {
+ const {
+ ssrAttribute,
+ ssrAppId
+ } = options; // only cache tags for current update
- // only cache tags for current update
- var tags = {};
+ const tags = {};
+ const htmlTag = getTag(tags, 'html'); // if this is a server render, then dont update
- var htmlTag = getTag(tags, 'html');
-
- // if this is a server render, then dont update
- if (appId === 'ssr' && htmlTag.hasAttribute(ssrAttribute)) {
+ if (appId === ssrAppId && htmlTag.hasAttribute(ssrAttribute)) {
// remove the server render attribute so we can update on (next) changes
- htmlTag.removeAttribute(ssrAttribute);
- return false
- }
+ htmlTag.removeAttribute(ssrAttribute); // add load callbacks if the
- // initialize tracked changes
- var addedTags = {};
- var removedTags = {};
+ let addLoadListeners = false;
- for (var type in newInfo) {
+ for (const type of tagsSupportingOnload) {
+ if (newInfo[type] && addCallbacks(options, type, newInfo[type])) {
+ addLoadListeners = true;
+ }
+ }
+
+ if (addLoadListeners) {
+ addListeners();
+ }
+
+ return false;
+ } // initialize tracked changes
+
+
+ const addedTags = {};
+ const removedTags = {};
+
+ for (const type in newInfo) {
// ignore these
if (includes(metaInfoOptionKeys, type)) {
- continue
+ continue;
}
if (type === 'title') {
// update the title
updateTitle(newInfo.title);
- continue
+ continue;
}
if (includes(metaInfoAttributeKeys, type)) {
- var tagName = type.substr(0, 4);
+ const tagName = type.substr(0, 4);
updateAttribute(options, newInfo[type], getTag(tags, tagName));
- continue
- }
+ continue;
+ } // tags should always be an array, ignore if it isnt
+
- // tags should always be an array, ignore if it isnt
if (!isArray(newInfo[type])) {
- continue
+ continue;
}
- var ref = updateTag(
- appId,
- options,
- type,
- newInfo[type],
- getTag(tags, 'head'),
- getTag(tags, 'body')
- );
- var oldTags = ref.oldTags;
- var newTags = ref.newTags;
+ const {
+ oldTags,
+ newTags
+ } = updateTag(appId, options, type, newInfo[type], getTag(tags, 'head'), getTag(tags, 'body'));
if (newTags.length) {
addedTags[type] = newTags;
@@ -1057,12 +1161,13 @@ function updateClientMetaInfo (appId, options, newInfo) {
}
}
- return { addedTags: addedTags, removedTags: removedTags }
+ return {
+ addedTags,
+ removedTags
+ };
}
-function _refresh (options) {
- if ( options === void 0 ) options = {};
-
+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
@@ -1073,18 +1178,21 @@ function _refresh (options) {
*
* @return {Object} - new meta info
*/
- return function refresh () {
- var metaInfo = getMetaInfo(options, this.$root, clientSequences);
+ return function refresh() {
+ const metaInfo = getMetaInfo(options, this.$root, clientSequences);
+ const appId = this.$root._vueMeta.appId;
+ const tags = updateClientMetaInfo(appId, options, metaInfo); // emit "event" with new info
- var appId = this.$root._vueMeta.appId;
- var tags = updateClientMetaInfo(appId, options, metaInfo);
- // emit "event" with new info
if (tags && isFunction(metaInfo.changed)) {
metaInfo.changed(metaInfo, tags.addedTags, tags.removedTags);
}
- return { vm: this, metaInfo: metaInfo, tags: tags }
- }
+ return {
+ vm: this,
+ metaInfo,
+ tags
+ };
+ };
}
/**
@@ -1094,31 +1202,36 @@ function _refresh (options) {
* @param {Object} data - the attributes to generate
* @return {Object} - the attribute generator
*/
-function attributeGenerator (ref, type, data) {
- if ( ref === void 0 ) ref = {};
- var attribute = ref.attribute;
+function attributeGenerator({
+ attribute,
+ ssrAttribute
+} = {}, type, data) {
return {
- text: function text () {
- var attributeStr = '';
- var watchedAttrs = [];
+ text(addSrrAttribute) {
+ let attributeStr = '';
+ const watchedAttrs = [];
- for (var attr in data) {
+ for (const attr in data) {
if (data.hasOwnProperty(attr)) {
watchedAttrs.push(attr);
-
- attributeStr += isUndefined(data[attr]) || booleanHtmlAttributes.includes(attr)
- ? attr
- : (attr + "=\"" + (isArray(data[attr]) ? data[attr].join(' ') : data[attr]) + "\"");
-
+ attributeStr += isUndefined(data[attr]) || booleanHtmlAttributes.includes(attr) ? attr : `${attr}="${isArray(data[attr]) ? data[attr].join(' ') : data[attr]}"`;
attributeStr += ' ';
}
}
- attributeStr += attribute + "=\"" + ((watchedAttrs.sort()).join(',')) + "\"";
- return attributeStr
+ if (attributeStr) {
+ attributeStr += `${attribute}="${watchedAttrs.sort().join(',')}"`;
+ }
+
+ if (type === 'htmlAttrs' && addSrrAttribute) {
+ return `${ssrAttribute}${attributeStr ? ' ' : ''}${attributeStr}`;
+ }
+
+ return attributeStr;
}
- }
+
+ };
}
/**
@@ -1128,15 +1241,19 @@ function attributeGenerator (ref, type, data) {
* @param {String} data - the title text
* @return {Object} - the title generator
*/
-function titleGenerator (appId, ref, type, data) {
- if ( ref === void 0 ) ref = {};
- var attribute = ref.attribute;
-
+function titleGenerator({
+ attribute
+} = {}, type, data) {
return {
- text: function text () {
- return ("<" + type + ">" + data + "" + type + ">")
+ text() {
+ if (!data) {
+ return '';
+ }
+
+ return `<${type}>${data}${type}>`;
}
- }
+
+ };
}
/**
@@ -1146,72 +1263,82 @@ function titleGenerator (appId, ref, type, data) {
* @param {(Array|Object)} tags - an array of tag objects or a single object in case of base
* @return {Object} - the tag generator
*/
-function tagGenerator (appId, ref, type, tags) {
- if ( ref === void 0 ) ref = {};
- var attribute = ref.attribute;
- var tagIDKeyName = ref.tagIDKeyName;
+function tagGenerator({
+ ssrAppId,
+ attribute,
+ tagIDKeyName
+} = {}, type, tags) {
+ const dataAttributes = [tagIDKeyName, 'callback', ...commonDataAttributes];
return {
- text: function text (ref) {
- if ( ref === void 0 ) ref = {};
- var body = ref.body; if ( body === void 0 ) body = false;
-
+ text({
+ body = false,
+ pbody = false
+ } = {}) {
// build a string containing all tags of this type
- return tags.reduce(function (tagsStr, tag) {
- var tagKeys = Object.keys(tag);
+ return tags.reduce((tagsStr, tag) => {
+ if (tag.skip) {
+ return tagsStr;
+ }
+
+ const tagKeys = Object.keys(tag);
if (tagKeys.length === 0) {
- return tagsStr // Bail on empty tag object
+ return tagsStr; // Bail on empty tag object
}
- if (Boolean(tag.body) !== body) {
- return tagsStr
+ if (Boolean(tag.body) !== body || Boolean(tag.pbody) !== pbody) {
+ return tagsStr;
}
- // build a string containing all attributes of this tag
- var attrs = tagKeys.reduce(function (attrsStr, attr) {
+ let attrs = tag.once ? '' : ` ${attribute}="${ssrAppId}"`; // build a string containing all attributes of this tag
+
+ for (const attr in tag) {
// these attributes are treated as children on the tag
if (tagAttributeAsInnerContent.includes(attr) || attr === 'once') {
- return attrsStr
- }
+ continue;
+ } // these form the attribute list for this tag
- // these form the attribute list for this tag
- var prefix = '';
- if ([tagIDKeyName, 'body'].includes(attr)) {
+
+ let prefix = '';
+
+ if (dataAttributes.includes(attr)) {
prefix = 'data-';
}
- var isBooleanAttr = booleanHtmlAttributes.includes(attr);
- if (isBooleanAttr && !tag[attr]) {
- return attrsStr
+ if (attr === 'callback') {
+ attrs += ` onload="this.__vm_l=1"`;
+ continue;
}
- return isBooleanAttr
- ? (attrsStr + " " + prefix + attr)
- : (attrsStr + " " + prefix + attr + "=\"" + (tag[attr]) + "\"")
- }, '');
+ const isBooleanAttr = !prefix && booleanHtmlAttributes.includes(attr);
- // grab child content from one of these attributes, if possible
- var content = tag.innerHTML || tag.cssText || '';
+ if (isBooleanAttr && !tag[attr]) {
+ continue;
+ }
- // generate tag exactly without any other redundant attribute
- var observeTag = tag.once
- ? ''
- : (attribute + "=\"" + appId + "\"");
+ attrs += ` ${prefix}${attr}` + (isBooleanAttr ? '' : `="${tag[attr]}"`);
+ }
+ let json = '';
+
+ if (tag.json) {
+ json = JSON.stringify(tag.json);
+ } // grab child content from one of these attributes, if possible
+
+
+ const content = tag.innerHTML || tag.cssText || json; // generate tag exactly without any other redundant attribute
// these tags have no end tag
- var hasEndTag = !tagsWithoutEndTag.includes(type);
- // these tag types will have content inserted
- var hasContent = hasEndTag && tagsWithInnerContent.includes(type);
+ const hasEndTag = !tagsWithoutEndTag.includes(type); // these tag types will have content inserted
- // the final string for this specific tag
- return !hasContent
- ? (tagsStr + "<" + type + " " + observeTag + attrs + (hasEndTag ? '/' : '') + ">")
- : (tagsStr + "<" + type + " " + observeTag + attrs + ">" + content + "" + type + ">")
- }, '')
+ const hasContent = hasEndTag && tagsWithInnerContent.includes(type); // the final string for this specific tag
+
+ return `${tagsStr}<${type}${attrs}${!hasContent && hasEndTag ? '/' : ''}>` + (hasContent ? `${content}${type}>` : '');
+ }, '');
}
- }
+
+ };
}
/**
@@ -1222,21 +1349,19 @@ function tagGenerator (appId, ref, type, tags) {
* @return {Object} - the new injector
*/
-function generateServerInjector (appId, options, type, data) {
+function generateServerInjector(options, type, data) {
if (type === 'title') {
- return titleGenerator(appId, options, type, data)
+ return titleGenerator(options, type, data);
}
if (metaInfoAttributeKeys.includes(type)) {
- return attributeGenerator(options, type, data)
+ return attributeGenerator(options, type, data);
}
- return tagGenerator(appId, options, type, data)
+ return tagGenerator(options, type, data);
}
-function _inject (options) {
- if ( options === void 0 ) options = {};
-
+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
@@ -1244,66 +1369,62 @@ function _inject (options) {
* @this {Object} - Vue instance - ideally the root component
* @return {Object} - server meta info with `toString` methods
*/
- return function inject () {
+ return function inject() {
// get meta info with sensible defaults
- var metaInfo = getMetaInfo(options, this.$root, serverSequences);
+ const metaInfo = getMetaInfo(options, this.$root, serverSequences); // generate server injectors
- // generate server injectors
- for (var key in metaInfo) {
+ for (const key in metaInfo) {
if (!metaInfoOptionKeys.includes(key) && metaInfo.hasOwnProperty(key)) {
- metaInfo[key] = generateServerInjector('ssr', options, key, metaInfo[key]);
+ metaInfo[key] = generateServerInjector(options, key, metaInfo[key]);
}
}
- return metaInfo
- }
+ return metaInfo;
+ };
}
-function _$meta (options) {
- if ( options === void 0 ) options = {};
-
- var _refresh$1 = _refresh(options);
- var _inject$1 = _inject(options);
+function _$meta(options = {}) {
+ const _refresh$1 = _refresh(options);
+ const _inject$1 = _inject(options);
/**
* Returns an injector for server-side rendering.
* @this {Object} - the Vue instance (a root component)
* @return {Object} - injector
*/
- return function $meta () {
+
+
+ return function $meta() {
return {
- getOptions: function () { return getOptions(options); },
+ getOptions: () => getOptions(options),
refresh: _refresh$1.bind(this),
inject: _inject$1.bind(this),
pause: pause.bind(this),
resume: resume.bind(this)
- }
- }
+ };
+ };
}
/**
* Plugin install function.
* @param {Function} Vue - the Vue constructor.
*/
-function install (Vue, options) {
- if ( options === void 0 ) options = {};
+function install(Vue, options = {}) {
if (Vue.__vuemeta_installed) {
- return
+ return;
}
+
Vue.__vuemeta_installed = true;
-
options = setOptions(options);
-
Vue.prototype.$meta = _$meta(options);
-
Vue.mixin(createMixin(Vue, options));
}
var index = {
- version: version,
- install: install,
- hasMetaInfo: hasMetaInfo
+ version,
+ install,
+ hasMetaInfo
};
export default index;
diff --git a/dist/vue-meta.js b/dist/vue-meta.js
index a255c38..3341bb1 100644
--- a/dist/vue-meta.js
+++ b/dist/vue-meta.js
@@ -1,5 +1,5 @@
/**
- * vue-meta v2.0.5
+ * vue-meta v2.1.0
* (c) 2019
* - Declan de Wet
* - Sébastien Chopin (@Atinux)
@@ -13,12 +13,11 @@
(global = global || self, global.VueMeta = factory());
}(this, function () { 'use strict';
- var version = "2.0.5";
+ var version = "2.1.0";
// store an id to keep track of DOM updates
- var batchId = null;
-
- function triggerUpdate (vm, hookName) {
+ let batchId = null;
+ function triggerUpdate(vm, hookName) {
// if an update was triggered during initialization or when an update was triggered by the
// metaInfo watcher, set initialized to null
// then we keep falsy value but know we need to run a triggerUpdate after initialization
@@ -28,10 +27,9 @@
if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) {
// batch potential DOM updates to prevent extraneous re-rendering
- batchUpdate(function () { return vm.$meta().refresh(); });
+ batchUpdate(() => vm.$meta().refresh());
}
}
-
/**
* Performs a batched update.
*
@@ -39,16 +37,13 @@
* @param {Function} callback - the update to perform
* @return {Number} id - a new ID
*/
- function batchUpdate (callback, timeout) {
- if ( timeout === void 0 ) timeout = 10;
+ function batchUpdate(callback, timeout = 10) {
clearTimeout(batchId);
-
- batchId = setTimeout(function () {
+ batchId = setTimeout(() => {
callback();
}, timeout);
-
- return batchId
+ return batchId;
}
/**
@@ -56,247 +51,255 @@
* @param {any} arg - the object to check
* @return {Boolean} - true if `arg` is an array
*/
- function isArray (arg) {
- return Array.isArray(arg)
+ function isArray(arg) {
+ return Array.isArray(arg);
+ }
+ function isUndefined(arg) {
+ return typeof arg === 'undefined';
+ }
+ function isObject(arg) {
+ return typeof arg === 'object';
+ }
+ function isPureObject(arg) {
+ return typeof arg === 'object' && arg !== null;
+ }
+ function isFunction(arg) {
+ return typeof arg === 'function';
+ }
+ function isString(arg) {
+ return typeof arg === 'string';
}
- function isUndefined (arg) {
- return typeof arg === 'undefined'
- }
-
- function isObject (arg) {
- return typeof arg === 'object'
- }
-
- function isFunction (arg) {
- return typeof arg === 'function'
- }
-
- function isString (arg) {
- return typeof arg === 'string'
- }
-
- function ensureIsArray (arg, key) {
+ function ensureIsArray(arg, key) {
if (!key || !isObject(arg)) {
- return isArray(arg) ? arg : []
+ return isArray(arg) ? arg : [];
}
if (!isArray(arg[key])) {
arg[key] = [];
}
- return arg
+
+ return arg;
}
-
- function ensuredPush (object, key, el) {
+ function ensuredPush(object, key, el) {
ensureIsArray(object, key);
-
object[key].push(el);
}
- // Vue $root instance has a _vueMeta object property, otherwise its a boolean true
- function hasMetaInfo (vm) {
- if ( vm === void 0 ) vm = this;
+ function hasMetaInfo(vm = this) {
+ return vm && (vm._vueMeta === true || isObject(vm._vueMeta));
+ } // a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has
- return vm && (vm._vueMeta === true || isObject(vm._vueMeta))
+ function inMetaInfoBranch(vm = this) {
+ return vm && !isUndefined(vm._vueMeta);
}
- // a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has
- function inMetaInfoBranch (vm) {
- if ( vm === void 0 ) vm = this;
-
- return vm && !isUndefined(vm._vueMeta)
- }
-
- function addNavGuards (vm) {
+ function addNavGuards(vm) {
// return when nav guards already added or no router exists
if (vm.$root._vueMeta.navGuards || !vm.$root.$router) {
/* istanbul ignore next */
- return
+ return;
}
vm.$root._vueMeta.navGuards = true;
-
- var $router = vm.$root.$router;
- var $meta = vm.$root.$meta();
-
- $router.beforeEach(function (to, from, next) {
+ const $router = vm.$root.$router;
+ const $meta = vm.$root.$meta();
+ $router.beforeEach((to, from, next) => {
$meta.pause();
next();
});
+ $router.afterEach(() => {
+ const {
+ metaInfo
+ } = $meta.resume();
- $router.afterEach(function () {
- var ref = $meta.resume();
- var metaInfo = ref.metaInfo;
if (metaInfo && metaInfo.afterNavigation && isFunction(metaInfo.afterNavigation)) {
metaInfo.afterNavigation(metaInfo);
}
});
}
- var appId = 1;
+ function hasGlobalWindowFn() {
+ try {
+ return !isUndefined(window);
+ } catch (e) {
+ return false;
+ }
+ }
+ const hasGlobalWindow = hasGlobalWindowFn();
- function createMixin (Vue, options) {
+ const _global = hasGlobalWindow ? window : global;
+
+ const console = _global.console = _global.console || {};
+ function warn(...args) {
+ /* istanbul ignore next */
+ if (!console || !console.warn) {
+ return;
+ }
+
+ console.warn(...args);
+ }
+ const showWarningNotSupported = () => warn('This vue app/component has no vue-meta configuration');
+
+ let appId = 1;
+ function createMixin(Vue, options) {
// for which Vue lifecycle hooks should the metaInfo be refreshed
- var updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount'];
+ const updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount']; // watch for client side component updates
- // watch for client side component updates
return {
- beforeCreate: function beforeCreate () {
- var this$1 = this;
-
+ beforeCreate() {
Object.defineProperty(this, '_hasMetaInfo', {
configurable: true,
- get: function get () {
+
+ get() {
// Show deprecation warning once when devtools enabled
if (Vue.config.devtools && !this.$root._vueMeta.hasMetaInfoDeprecationWarningShown) {
- console.warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead'); // eslint-disable-line no-console
+ warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead');
this.$root._vueMeta.hasMetaInfoDeprecationWarningShown = true;
}
- return hasMetaInfo(this)
- }
- });
- // Add a marker to know if it uses metaInfo
+ return hasMetaInfo(this);
+ }
+
+ }); // Add a marker to know if it uses metaInfo
// _vnode is used to know that it's attached to a real component
// useful if we use some mixin to add some meta tags (like nuxt-i18n)
+
if (!isUndefined(this.$options[options.keyName]) && this.$options[options.keyName] !== null) {
if (!this.$root._vueMeta) {
- this.$root._vueMeta = { appId: appId };
+ this.$root._vueMeta = {
+ appId
+ };
appId++;
- }
-
- // to speed up updates we keep track of branches which have a component with vue-meta info defined
+ } // to speed up updates we keep track of branches which have a component with vue-meta info defined
// if _vueMeta = true it has info, if _vueMeta = false a child has info
+
+
if (!this._vueMeta) {
this._vueMeta = true;
+ let p = this.$parent;
- var p = this.$parent;
while (p && p !== this.$root) {
if (isUndefined(p._vueMeta)) {
p._vueMeta = false;
}
+
p = p.$parent;
}
- }
-
- // coerce function-style metaInfo to a computed prop so we can observe
+ } // coerce function-style metaInfo to a computed prop so we can observe
// it on creation
+
+
if (isFunction(this.$options[options.keyName])) {
if (!this.$options.computed) {
this.$options.computed = {};
}
+
this.$options.computed.$metaInfo = this.$options[options.keyName];
if (!this.$isServer) {
// if computed $metaInfo exists, watch it for updates & trigger a refresh
// when it changes (i.e. automatically handle async actions that affect metaInfo)
// credit for this suggestion goes to [Sébastien Chopin](https://github.com/Atinux)
- ensuredPush(this.$options, 'created', function () {
- this$1.$watch('$metaInfo', function () {
+ ensuredPush(this.$options, 'created', () => {
+ this.$watch('$metaInfo', function () {
triggerUpdate(this, 'watcher');
});
});
}
- }
-
- // force an initial refresh on page load and prevent other lifecycleHooks
+ } // force an initial refresh on page load and prevent other lifecycleHooks
// to triggerUpdate until this initial refresh is finished
// this is to make sure that when a page is opened in an inactive tab which
// has throttled rAF/timers we still immediately set the page title
+
+
if (isUndefined(this.$root._vueMeta.initialized)) {
this.$root._vueMeta.initialized = this.$isServer;
if (!this.$root._vueMeta.initialized) {
- ensuredPush(this.$options, 'beforeMount', function () {
+ ensuredPush(this.$options, 'beforeMount', () => {
// if this Vue-app was server rendered, set the appId to 'ssr'
// only one SSR app per page is supported
- if (this$1.$root.$el && this$1.$root.$el.hasAttribute && this$1.$root.$el.hasAttribute('data-server-rendered')) {
- this$1.$root._vueMeta.appId = 'ssr';
+ if (this.$root.$el && this.$root.$el.hasAttribute && this.$root.$el.hasAttribute('data-server-rendered')) {
+ this.$root._vueMeta.appId = options.ssrAppId;
}
- });
+ }); // we use the mounted hook here as on page load
- // we use the mounted hook here as on page load
- ensuredPush(this.$options, 'mounted', function () {
- if (!this$1.$root._vueMeta.initialized) {
+ ensuredPush(this.$options, 'mounted', () => {
+ if (!this.$root._vueMeta.initialized) {
// used in triggerUpdate to check if a change was triggered
// during initialization
- this$1.$root._vueMeta.initializing = true;
+ this.$root._vueMeta.initializing = true; // refresh meta in nextTick so all child components have loaded
- // refresh meta in nextTick so all child components have loaded
- this$1.$nextTick(function () {
- var this$1 = this;
-
- var ref = this.$root.$meta().refresh();
- var tags = ref.tags;
- var metaInfo = ref.metaInfo;
-
- // After ssr hydration (identifier by tags === false) check
+ this.$nextTick(function () {
+ const {
+ tags,
+ metaInfo
+ } = this.$root.$meta().refresh(); // After ssr hydration (identifier by tags === false) check
// if initialized was set to null in triggerUpdate. That'd mean
// that during initilazation changes where triggered which need
// to be applied OR a metaInfo watcher was triggered before the
// current hook was called
// (during initialization all changes are blocked)
+
if (tags === false && this.$root._vueMeta.initialized === null) {
- this.$nextTick(function () { return triggerUpdate(this$1, 'initializing'); });
+ this.$nextTick(() => triggerUpdate(this, 'initializing'));
}
this.$root._vueMeta.initialized = true;
- delete this.$root._vueMeta.initializing;
-
- // add the navigation guards if they havent been added yet
+ delete this.$root._vueMeta.initializing; // add the navigation guards if they havent been added yet
// they are needed for the afterNavigation callback
+
if (!options.refreshOnceOnNavigation && metaInfo.afterNavigation) {
addNavGuards(this);
}
});
}
- });
+ }); // add the navigation guards if requested
- // add the navigation guards if requested
if (options.refreshOnceOnNavigation) {
addNavGuards(this);
}
}
- }
+ } // do not trigger refresh on the server side
+
- // do not trigger refresh on the server side
if (!this.$isServer) {
// no need to add this hooks on server side
- updateOnLifecycleHook.forEach(function (lifecycleHook) {
- ensuredPush(this$1.$options, lifecycleHook, function () { return triggerUpdate(this$1, lifecycleHook); });
- });
+ updateOnLifecycleHook.forEach(lifecycleHook => {
+ ensuredPush(this.$options, lifecycleHook, () => triggerUpdate(this, lifecycleHook));
+ }); // re-render meta data when returning from a child component to parent
- // re-render meta data when returning from a child component to parent
- ensuredPush(this.$options, 'destroyed', function () {
+ ensuredPush(this.$options, 'destroyed', () => {
// Wait that element is hidden before refreshing meta tags (to support animations)
- var interval = setInterval(function () {
- if (this$1.$el && this$1.$el.offsetParent !== null) {
+ const interval = setInterval(() => {
+ if (this.$el && this.$el.offsetParent !== null) {
/* istanbul ignore next line */
- return
+ return;
}
clearInterval(interval);
- if (!this$1.$parent) {
+ if (!this.$parent) {
/* istanbul ignore next line */
- return
+ return;
}
- triggerUpdate(this$1, 'destroyed');
+ triggerUpdate(this, 'destroyed');
}, 50);
});
}
}
}
- }
+
+ };
}
/**
* These are constant variables used throughout the application.
*/
-
// set some sane defaults
- var defaultInfo = {
+ const defaultInfo = {
title: undefined,
titleChunk: '',
titleTemplate: '%s',
@@ -310,176 +313,105 @@
script: [],
noscript: [],
__dangerouslyDisableSanitizers: [],
- __dangerouslyDisableSanitizersByTagID: {}
+ __dangerouslyDisableSanitizersByTagID: {} // 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.
+
};
-
- // 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.
- var keyName = 'metaInfo';
-
- // This is the attribute vue-meta arguments on elements to know which it should
+ const keyName = 'metaInfo'; // This is the attribute vue-meta arguments on elements to know which it should
// manage and which it should ignore.
- var attribute = 'data-vue-meta';
- // This is the attribute that goes on the `html` tag to inform `vue-meta`
+ const attribute = 'data-vue-meta'; // 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.
- var ssrAttribute = 'data-vue-meta-server-rendered';
- // This is the property that tells vue-meta to overwrite (instead of append)
+ const ssrAttribute = '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.
- var tagIDKeyName = 'vmid';
- // This is the key name for possible meta templates
- var metaTemplateKeyName = 'template';
+ const tagIDKeyName = 'vmid'; // This is the key name for possible meta templates
- // This is the key name for the content-holding property
- var contentKeyName = 'content';
+ const metaTemplateKeyName = 'template'; // This is the key name for the content-holding property
+
+ const contentKeyName = 'content'; // The id used for the ssr app
+
+ const ssrAppId = 'ssr';
+ const defaultOptions = {
+ keyName,
+ attribute,
+ ssrAttribute,
+ tagIDKeyName,
+ contentKeyName,
+ metaTemplateKeyName,
+ ssrAppId // List of metaInfo property keys which are configuration options (and dont generate html)
- var defaultOptions = {
- keyName: keyName,
- attribute: attribute,
- ssrAttribute: ssrAttribute,
- tagIDKeyName: tagIDKeyName,
- contentKeyName: contentKeyName,
- metaTemplateKeyName: metaTemplateKeyName
};
+ const metaInfoOptionKeys = ['titleChunk', 'titleTemplate', 'changed', '__dangerouslyDisableSanitizers', '__dangerouslyDisableSanitizersByTagID']; // The metaInfo property keys which are used to disable escaping
- // List of metaInfo property keys which are configuration options (and dont generate html)
- var metaInfoOptionKeys = [
- 'titleChunk',
- 'titleTemplate',
- 'changed',
- '__dangerouslyDisableSanitizers',
- '__dangerouslyDisableSanitizersByTagID'
- ];
+ const disableOptionKeys = ['__dangerouslyDisableSanitizers', '__dangerouslyDisableSanitizersByTagID']; // List of metaInfo property keys which only generates attributes and no tags
- // The metaInfo property keys which are used to disable escaping
- var disableOptionKeys = [
- '__dangerouslyDisableSanitizers',
- '__dangerouslyDisableSanitizersByTagID'
- ];
+ const metaInfoAttributeKeys = ['htmlAttrs', 'headAttrs', 'bodyAttrs']; // HTML elements which support the onload event
- // List of metaInfo property keys which only generates attributes and no tags
- var metaInfoAttributeKeys = [
- 'htmlAttrs',
- 'headAttrs',
- 'bodyAttrs'
- ];
+ const tagsSupportingOnload = ['link', 'style', 'script']; // HTML elements which dont have a head tag (shortened to our needs)
- // from: https://github.com/kangax/html-minifier/blob/gh-pages/src/htmlminifier.js#L202
- var booleanHtmlAttributes = [
- 'allowfullscreen',
- 'amp',
- 'async',
- 'autofocus',
- 'autoplay',
- 'checked',
- 'compact',
- 'controls',
- 'declare',
- 'default',
- 'defaultchecked',
- 'defaultmuted',
- 'defaultselected',
- 'defer',
- 'disabled',
- 'enabled',
- 'formnovalidate',
- 'hidden',
- 'indeterminate',
- 'inert',
- 'ismap',
- 'itemscope',
- 'loop',
- 'multiple',
- 'muted',
- 'nohref',
- 'noresize',
- 'noshade',
- 'novalidate',
- 'nowrap',
- 'open',
- 'pauseonexit',
- 'readonly',
- 'required',
- 'reversed',
- 'scoped',
- 'seamless',
- 'selected',
- 'sortable',
- 'truespeed',
- 'typemustmatch',
- 'visible'
- ];
+ const commonDataAttributes = ['body', 'pbody']; // from: https://github.com/kangax/html-minifier/blob/gh-pages/src/htmlminifier.js#L202
- // eslint-disable-next-line no-console
- var showWarningNotSupported = function () { return console.warn('This vue app/component has no vue-meta configuration'); };
+ const booleanHtmlAttributes = ['allowfullscreen', 'amp', 'async', 'autofocus', 'autoplay', 'checked', 'compact', 'controls', 'declare', 'default', 'defaultchecked', 'defaultmuted', 'defaultselected', 'defer', 'disabled', 'enabled', 'formnovalidate', 'hidden', 'indeterminate', 'inert', 'ismap', 'itemscope', 'loop', 'multiple', 'muted', 'nohref', 'noresize', 'noshade', 'novalidate', 'nowrap', 'open', 'pauseonexit', 'readonly', 'required', 'reversed', 'scoped', 'seamless', 'selected', 'sortable', 'truespeed', 'typemustmatch', 'visible'];
- function setOptions (options) {
+ function setOptions(options) {
// combine options
options = isObject(options) ? options : {};
- for (var key in defaultOptions) {
+ for (const key in defaultOptions) {
if (!options[key]) {
options[key] = defaultOptions[key];
}
}
- return options
+ return options;
}
+ function getOptions(options) {
+ const optionsCopy = {};
- function getOptions (options) {
- var optionsCopy = {};
- for (var key in options) {
+ for (const key in options) {
optionsCopy[key] = options[key];
}
- return optionsCopy
+
+ return optionsCopy;
}
- function pause (refresh) {
- if ( refresh === void 0 ) refresh = true;
-
+ function pause(refresh = true) {
this.$root._vueMeta.paused = true;
-
- return function () { return resume(refresh); }
+ return () => resume(refresh);
}
-
- function resume (refresh) {
- if ( refresh === void 0 ) refresh = true;
-
+ function resume(refresh = true) {
this.$root._vueMeta.paused = false;
if (refresh) {
- return this.$root.$meta().refresh()
+ return this.$root.$meta().refresh();
}
}
- function applyTemplate (ref, headObject, template, chunk) {
- var component = ref.component;
- var metaTemplateKeyName = ref.metaTemplateKeyName;
- var contentKeyName = ref.contentKeyName;
-
+ function applyTemplate({
+ component,
+ metaTemplateKeyName,
+ contentKeyName
+ }, headObject, template, chunk) {
if (isUndefined(template)) {
template = headObject[metaTemplateKeyName];
delete headObject[metaTemplateKeyName];
- }
+ } // return early if no template defined
+
- // return early if no template defined
if (!template) {
- return false
+ return false;
}
if (isUndefined(chunk)) {
chunk = headObject[contentKeyName];
}
- headObject[contentKeyName] = isFunction(template)
- ? template.call(component, chunk)
- : template.replace(/%s/g, chunk);
-
- return true
+ headObject[contentKeyName] = isFunction(template) ? template.call(component, chunk) : template.replace(/%s/g, chunk);
+ return true;
}
/*
@@ -490,281 +422,287 @@
* Also, only files in client/ & shared/ should use these functions
* files in server/ still use normal js function
*/
-
- function findIndex (array, predicate) {
- var arguments$1 = arguments;
-
+ function findIndex(array, predicate) {
if ( !Array.prototype.findIndex) {
// idx needs to be a Number, for..in returns string
- for (var idx = 0; idx < array.length; idx++) {
- if (predicate.call(arguments$1[2], array[idx], idx, array)) {
- return idx
+ for (let idx = 0; idx < array.length; idx++) {
+ if (predicate.call(arguments[2], array[idx], idx, array)) {
+ return idx;
}
}
- return -1
- }
- return array.findIndex(predicate, arguments[2])
- }
- function toArray (arg) {
+ return -1;
+ }
+
+ return array.findIndex(predicate, arguments[2]);
+ }
+ function toArray(arg) {
if ( !Array.from) {
- return Array.prototype.slice.call(arg)
+ return Array.prototype.slice.call(arg);
}
- return Array.from(arg)
- }
- function includes (array, value) {
+ return Array.from(arg);
+ }
+ function includes(array, value) {
if ( !Array.prototype.includes) {
- for (var idx in array) {
+ for (const idx in array) {
if (array[idx] === value) {
- return true
+ return true;
}
}
- return false
+ return false;
}
- return array.includes(value)
+
+ return array.includes(value);
}
- var clientSequences = [
- [/&/g, '\u0026'],
- [//g, '\u003E'],
- [/"/g, '\u0022'],
- [/'/g, '\u0027']
- ];
+ const clientSequences = [[/&/g, '\u0026'], [//g, '\u003E'], [/"/g, '\u0022'], [/'/g, '\u0027']]; // sanitizes potentially dangerous characters
- // sanitizes potentially dangerous characters
- function escape (info, options, escapeOptions) {
- var tagIDKeyName = options.tagIDKeyName;
- var doEscape = escapeOptions.doEscape; if ( doEscape === void 0 ) doEscape = function (v) { return v; };
- var escaped = {};
+ function escape(info, options, escapeOptions) {
+ const {
+ tagIDKeyName
+ } = options;
+ const {
+ doEscape = v => v,
+ escapeKeys
+ } = escapeOptions;
+ const escaped = {};
- for (var key in info) {
- var value = info[key];
+ for (const key in info) {
+ const value = info[key]; // no need to escape configuration options
- // no need to escape configuration options
if (includes(metaInfoOptionKeys, key)) {
escaped[key] = value;
- continue
+ continue;
}
- var disableKey = disableOptionKeys[0];
+ let [disableKey] = disableOptionKeys;
+
if (escapeOptions[disableKey] && includes(escapeOptions[disableKey], key)) {
// this info[key] doesnt need to escaped if the option is listed in __dangerouslyDisableSanitizers
escaped[key] = value;
- continue
+ continue;
}
- var tagId = info[tagIDKeyName];
+ const tagId = info[tagIDKeyName];
+
if (tagId) {
- disableKey = disableOptionKeys[1];
+ disableKey = disableOptionKeys[1]; // keys which are listed in __dangerouslyDisableSanitizersByTagID for the current vmid do not need to be escaped
- // keys which are listed in __dangerouslyDisableSanitizersByTagID for the current vmid do not need to be escaped
if (escapeOptions[disableKey] && escapeOptions[disableKey][tagId] && includes(escapeOptions[disableKey][tagId], key)) {
escaped[key] = value;
- continue
+ continue;
}
}
if (isString(value)) {
escaped[key] = doEscape(value);
} else if (isArray(value)) {
- escaped[key] = value.map(function (v) {
- return isObject(v)
- ? escape(v, options, escapeOptions)
- : doEscape(v)
+ escaped[key] = value.map(v => {
+ if (isPureObject(v)) {
+ return escape(v, options, { ...escapeOptions,
+ escapeKeys: true
+ });
+ }
+
+ return doEscape(v);
+ });
+ } else if (isPureObject(value)) {
+ escaped[key] = escape(value, options, { ...escapeOptions,
+ escapeKeys: true
});
- } else if (isObject(value)) {
- escaped[key] = escape(value, options, escapeOptions);
} else {
escaped[key] = value;
}
+
+ if (escapeKeys) {
+ const escapedKey = doEscape(key);
+
+ if (key !== escapedKey) {
+ escaped[escapedKey] = escaped[key];
+ delete escaped[key];
+ }
+ }
}
- return escaped
+ return escaped;
}
var isMergeableObject = function isMergeableObject(value) {
- return isNonNullObject(value)
- && !isSpecial(value)
+ return isNonNullObject(value) && !isSpecial(value);
};
function isNonNullObject(value) {
- return !!value && typeof value === 'object'
+ return !!value && typeof value === 'object';
}
function isSpecial(value) {
- var stringValue = Object.prototype.toString.call(value);
+ var stringValue = Object.prototype.toString.call(value);
+ return stringValue === '[object RegExp]' || stringValue === '[object Date]' || isReactElement(value);
+ } // see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25
- return stringValue === '[object RegExp]'
- || stringValue === '[object Date]'
- || isReactElement(value)
- }
- // see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25
var canUseSymbol = typeof Symbol === 'function' && Symbol.for;
var REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7;
function isReactElement(value) {
- return value.$$typeof === REACT_ELEMENT_TYPE
+ return value.$$typeof === REACT_ELEMENT_TYPE;
}
function emptyTarget(val) {
- return Array.isArray(val) ? [] : {}
+ return Array.isArray(val) ? [] : {};
}
function cloneUnlessOtherwiseSpecified(value, options) {
- return (options.clone !== false && options.isMergeableObject(value))
- ? deepmerge(emptyTarget(value), value, options)
- : value
+ return options.clone !== false && options.isMergeableObject(value) ? deepmerge(emptyTarget(value), value, options) : value;
}
function defaultArrayMerge(target, source, options) {
- return target.concat(source).map(function(element) {
- return cloneUnlessOtherwiseSpecified(element, options)
- })
+ return target.concat(source).map(function (element) {
+ return cloneUnlessOtherwiseSpecified(element, options);
+ });
}
function getMergeFunction(key, options) {
- if (!options.customMerge) {
- return deepmerge
- }
- var customMerge = options.customMerge(key);
- return typeof customMerge === 'function' ? customMerge : deepmerge
+ if (!options.customMerge) {
+ return deepmerge;
+ }
+
+ var customMerge = options.customMerge(key);
+ return typeof customMerge === 'function' ? customMerge : deepmerge;
}
function getEnumerableOwnPropertySymbols(target) {
- return Object.getOwnPropertySymbols
- ? Object.getOwnPropertySymbols(target).filter(function(symbol) {
- return target.propertyIsEnumerable(symbol)
- })
- : []
+ return Object.getOwnPropertySymbols ? Object.getOwnPropertySymbols(target).filter(function (symbol) {
+ return target.propertyIsEnumerable(symbol);
+ }) : [];
}
function getKeys(target) {
- return Object.keys(target).concat(getEnumerableOwnPropertySymbols(target))
+ return Object.keys(target).concat(getEnumerableOwnPropertySymbols(target));
}
function mergeObject(target, source, options) {
- var destination = {};
- if (options.isMergeableObject(target)) {
- getKeys(target).forEach(function(key) {
- destination[key] = cloneUnlessOtherwiseSpecified(target[key], options);
- });
- }
- getKeys(source).forEach(function(key) {
- if (!options.isMergeableObject(source[key]) || !target[key]) {
- destination[key] = cloneUnlessOtherwiseSpecified(source[key], options);
- } else {
- destination[key] = getMergeFunction(key, options)(target[key], source[key], options);
- }
- });
- return destination
+ var destination = {};
+
+ if (options.isMergeableObject(target)) {
+ getKeys(target).forEach(function (key) {
+ destination[key] = cloneUnlessOtherwiseSpecified(target[key], options);
+ });
+ }
+
+ getKeys(source).forEach(function (key) {
+ if (!options.isMergeableObject(source[key]) || !target[key]) {
+ destination[key] = cloneUnlessOtherwiseSpecified(source[key], options);
+ } else {
+ destination[key] = getMergeFunction(key, options)(target[key], source[key], options);
+ }
+ });
+ return destination;
}
function deepmerge(target, source, options) {
- options = options || {};
- options.arrayMerge = options.arrayMerge || defaultArrayMerge;
- options.isMergeableObject = options.isMergeableObject || isMergeableObject;
+ options = options || {};
+ options.arrayMerge = options.arrayMerge || defaultArrayMerge;
+ options.isMergeableObject = options.isMergeableObject || isMergeableObject;
+ var sourceIsArray = Array.isArray(source);
+ var targetIsArray = Array.isArray(target);
+ var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray;
- var sourceIsArray = Array.isArray(source);
- var targetIsArray = Array.isArray(target);
- var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray;
-
- if (!sourceAndTargetTypesMatch) {
- return cloneUnlessOtherwiseSpecified(source, options)
- } else if (sourceIsArray) {
- return options.arrayMerge(target, source, options)
- } else {
- return mergeObject(target, source, options)
- }
+ if (!sourceAndTargetTypesMatch) {
+ return cloneUnlessOtherwiseSpecified(source, options);
+ } else if (sourceIsArray) {
+ return options.arrayMerge(target, source, options);
+ } else {
+ return mergeObject(target, source, options);
+ }
}
deepmerge.all = function deepmergeAll(array, options) {
- if (!Array.isArray(array)) {
- throw new Error('first argument should be an array')
- }
+ if (!Array.isArray(array)) {
+ throw new Error('first argument should be an array');
+ }
- return array.reduce(function(prev, next) {
- return deepmerge(prev, next, options)
- }, {})
+ return array.reduce(function (prev, next) {
+ return deepmerge(prev, next, options);
+ }, {});
};
var deepmerge_1 = deepmerge;
-
var cjs = deepmerge_1;
- function arrayMerge (ref, target, source) {
- var component = ref.component;
- var tagIDKeyName = ref.tagIDKeyName;
- var metaTemplateKeyName = ref.metaTemplateKeyName;
- var contentKeyName = ref.contentKeyName;
-
+ function arrayMerge({
+ component,
+ tagIDKeyName,
+ metaTemplateKeyName,
+ contentKeyName
+ }, target, source) {
// we concat the arrays without merging objects contained in,
// but we check for a `vmid` property on each object in the array
// using an O(1) lookup associative array exploit
- var destination = [];
-
- target.forEach(function (targetItem, targetIndex) {
+ const destination = [];
+ target.forEach((targetItem, targetIndex) => {
// no tagID so no need to check for duplicity
if (!targetItem[tagIDKeyName]) {
destination.push(targetItem);
- return
+ return;
}
- var sourceIndex = findIndex(source, function (item) { return item[tagIDKeyName] === targetItem[tagIDKeyName]; });
- var sourceItem = source[sourceIndex];
+ const sourceIndex = findIndex(source, item => item[tagIDKeyName] === targetItem[tagIDKeyName]);
+ const sourceItem = source[sourceIndex]; // source doesnt contain any duplicate vmid's, we can keep targetItem
- // source doesnt contain any duplicate vmid's, we can keep targetItem
if (sourceIndex === -1) {
destination.push(targetItem);
- return
- }
-
- // when sourceItem explictly defines contentKeyName or innerHTML as undefined, its
+ return;
+ } // when sourceItem explictly defines contentKeyName or innerHTML as undefined, its
// an indication that we need to skip the default behaviour or child has preference over parent
// which means we keep the targetItem and ignore/remove the sourceItem
- if ((sourceItem.hasOwnProperty(contentKeyName) && sourceItem[contentKeyName] === undefined) ||
- (sourceItem.hasOwnProperty('innerHTML') && sourceItem.innerHTML === undefined)) {
- destination.push(targetItem);
- // remove current index from source array so its not concatenated to destination below
+
+
+ if (sourceItem.hasOwnProperty(contentKeyName) && sourceItem[contentKeyName] === undefined || sourceItem.hasOwnProperty('innerHTML') && sourceItem.innerHTML === undefined) {
+ destination.push(targetItem); // remove current index from source array so its not concatenated to destination below
+
source.splice(sourceIndex, 1);
- return
- }
-
- // we now know that targetItem is a duplicate and we should ignore it in favor of sourceItem
-
+ return;
+ } // we now know that targetItem is a duplicate and we should ignore it in favor of sourceItem
// if source specifies null as content then ignore both the target as the source
+
+
if (sourceItem[contentKeyName] === null || sourceItem.innerHTML === null) {
// remove current index from source array so its not concatenated to destination below
source.splice(sourceIndex, 1);
- return
- }
+ return;
+ } // now we only need to check if the target has a template to combine it with the source
+
+
+ const targetTemplate = targetItem[metaTemplateKeyName];
- // now we only need to check if the target has a template to combine it with the source
- var targetTemplate = targetItem[metaTemplateKeyName];
if (!targetTemplate) {
- return
+ return;
}
- var sourceTemplate = sourceItem[metaTemplateKeyName];
+ const sourceTemplate = sourceItem[metaTemplateKeyName];
if (!sourceTemplate) {
// use parent template and child content
- applyTemplate({ component: component, metaTemplateKeyName: metaTemplateKeyName, contentKeyName: contentKeyName }, sourceItem, targetTemplate);
+ applyTemplate({
+ component,
+ metaTemplateKeyName,
+ contentKeyName
+ }, sourceItem, targetTemplate);
} else if (!sourceItem[contentKeyName]) {
// use child template and parent content
- applyTemplate({ component: component, metaTemplateKeyName: metaTemplateKeyName, contentKeyName: contentKeyName }, sourceItem, undefined, targetItem[contentKeyName]);
+ applyTemplate({
+ component,
+ metaTemplateKeyName,
+ contentKeyName
+ }, sourceItem, undefined, targetItem[contentKeyName]);
}
});
-
- return destination.concat(source)
+ return destination.concat(source);
}
-
- function merge (target, source, options) {
- if ( options === void 0 ) options = {};
-
+ function merge(target, source, options = {}) {
// remove properties explicitly set to false so child components can
// optionally _not_ overwrite the parents content
// (for array properties this is checked in arrayMerge)
@@ -772,25 +710,24 @@
delete source.title;
}
- metaInfoAttributeKeys.forEach(function (attrKey) {
+ metaInfoAttributeKeys.forEach(attrKey => {
if (!source[attrKey]) {
- return
+ return;
}
- for (var key in source[attrKey]) {
+ for (const key in source[attrKey]) {
if (source[attrKey].hasOwnProperty(key) && source[attrKey][key] === undefined) {
- if (booleanHtmlAttributes.includes(key)) {
- // eslint-disable-next-line no-console
- console.warn('VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details');
+ if (includes(booleanHtmlAttributes, key)) {
+ warn('VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details');
}
+
delete source[attrKey][key];
}
}
});
-
return cjs(target, source, {
- arrayMerge: function (t, s) { return arrayMerge(options, t, s); }
- })
+ arrayMerge: (t, s) => arrayMerge(options, t, s)
+ });
}
/**
@@ -807,45 +744,46 @@
* @param {Object} [result={}] - result so far
* @return {Object} result - final aggregated result
*/
- function getComponentOption (options, component, result) {
- if ( options === void 0 ) options = {};
- if ( result === void 0 ) result = {};
- var keyName = options.keyName;
- var metaTemplateKeyName = options.metaTemplateKeyName;
- var tagIDKeyName = options.tagIDKeyName;
- var $options = component.$options;
- var $children = component.$children;
+ function getComponentOption(options = {}, component, result = {}) {
+ const {
+ keyName,
+ metaTemplateKeyName,
+ tagIDKeyName
+ } = options;
+ const {
+ $options,
+ $children
+ } = component;
if (component._inactive) {
- return result
- }
+ return result;
+ } // only collect option data if it exists
+
- // only collect option data if it exists
if ($options[keyName]) {
- var data = $options[keyName];
+ let data = $options[keyName]; // if option is a function, replace it with it's result
- // if option is a function, replace it with it's result
if (isFunction(data)) {
data = data.call(component);
- }
+ } // ignore data if its not an object, then we keep our previous result
+
- // ignore data if its not an object, then we keep our previous result
if (!isObject(data)) {
- return result
- }
+ return result;
+ } // merge with existing options
+
- // merge with existing options
result = merge(result, data, options);
- }
+ } // collect & aggregate child options if deep = true
+
- // collect & aggregate child options if deep = true
if ($children.length) {
- $children.forEach(function (childComponent) {
+ $children.forEach(childComponent => {
// check if the childComponent is in a branch
// return otherwise so we dont walk all component branches unnecessarily
if (!inMetaInfoBranch(childComponent)) {
- return
+ return;
}
result = getComponentOption(options, childComponent, result);
@@ -854,20 +792,17 @@
if (metaTemplateKeyName && result.meta) {
// apply templates if needed
- result.meta.forEach(function (metaObject) { return applyTemplate(options, metaObject); });
+ result.meta.forEach(metaObject => applyTemplate(options, metaObject)); // remove meta items with duplicate vmid's
- // remove meta items with duplicate vmid's
- result.meta = result.meta.filter(function (metaItem, index, arr) {
- return (
- // keep meta item if it doesnt has a vmid
- !metaItem.hasOwnProperty(tagIDKeyName) ||
- // or if it's the first item in the array with this vmid
- index === findIndex(arr, function (item) { return item[tagIDKeyName] === metaItem[tagIDKeyName]; })
- )
+ result.meta = result.meta.filter((metaItem, index, arr) => {
+ return (// keep meta item if it doesnt has a vmid
+ !metaItem.hasOwnProperty(tagIDKeyName) || // or if it's the first item in the array with this vmid
+ index === findIndex(arr, item => item[tagIDKeyName] === metaItem[tagIDKeyName])
+ );
});
}
- return result
+ return result;
}
/**
@@ -877,56 +812,180 @@
* @param {Object} component - the Vue instance to get meta info from
* @return {Object} - returned meta info
*/
- function getMetaInfo (options, component, escapeSequences) {
- if ( options === void 0 ) options = {};
- if ( escapeSequences === void 0 ) escapeSequences = [];
+ function getMetaInfo(options = {}, component, escapeSequences = []) {
// collect & aggregate all metaInfo $options
- var info = getComponentOption(options, component, defaultInfo);
-
- // Remove all "template" tags from meta
-
+ let info = getComponentOption(options, component, defaultInfo); // Remove all "template" tags from meta
// backup the title chunk in case user wants access to it
+
if (info.title) {
info.titleChunk = info.title;
- }
+ } // replace title with populated template
+
- // replace title with populated template
if (info.titleTemplate && info.titleTemplate !== '%s') {
- applyTemplate({ component: component, contentKeyName: 'title' }, info, info.titleTemplate, info.titleChunk || '');
- }
-
- // convert base tag to an array so it can be handled the same way
+ applyTemplate({
+ component,
+ contentKeyName: 'title'
+ }, info, info.titleTemplate, 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] : [];
}
- var escapeOptions = {
- doEscape: function (value) { return escapeSequences.reduce(function (val, ref) {
- var v = ref[0];
- var r = ref[1];
-
- return val.replace(v, r);
- }, value); }
+ const escapeOptions = {
+ doEscape: value => escapeSequences.reduce((val, [v, r]) => val.replace(v, r), value)
};
-
- disableOptionKeys.forEach(function (disableKey, index) {
+ disableOptionKeys.forEach((disableKey, index) => {
if (index === 0) {
ensureIsArray(info, disableKey);
} else if (index === 1) {
- for (var key in info[disableKey]) {
+ for (const key in info[disableKey]) {
ensureIsArray(info[disableKey], key);
}
}
escapeOptions[disableKey] = info[disableKey];
- });
+ }); // begin sanitization
- // begin sanitization
info = escape(info, options, escapeOptions);
+ return info;
+ }
- return info
+ function getTag(tags, tag) {
+ if (!tags[tag]) {
+ tags[tag] = document.getElementsByTagName(tag)[0];
+ }
+
+ return tags[tag];
+ }
+ function getElementsKey({
+ body,
+ pbody
+ }) {
+ return body ? 'body' : pbody ? 'pbody' : 'head';
+ }
+ function queryElements(parentNode, {
+ appId,
+ attribute,
+ type,
+ tagIDKeyName
+ }, attributes = {}) {
+ const queries = [`${type}[${attribute}="${appId}"]`, `${type}[data-${tagIDKeyName}]`].map(query => {
+ for (const key in attributes) {
+ const val = attributes[key];
+ const attributeValue = val && val !== true ? `="${val}"` : '';
+ query += `[data-${key}${attributeValue}]`;
+ }
+
+ return query;
+ });
+ return toArray(parentNode.querySelectorAll(queries.join(', ')));
+ }
+
+ const callbacks = [];
+ function isDOMComplete(d = document) {
+ return d.readyState === 'complete';
+ }
+ function addCallback(query, callback) {
+ if (arguments.length === 1) {
+ callback = query;
+ query = '';
+ }
+
+ callbacks.push([query, callback]);
+ }
+ function addCallbacks({
+ tagIDKeyName
+ }, type, tags, autoAddListeners) {
+ let hasAsyncCallback = false;
+
+ for (const tag of tags) {
+ if (!tag[tagIDKeyName] || !tag.callback) {
+ continue;
+ }
+
+ hasAsyncCallback = true;
+ addCallback(`${type}[data-${tagIDKeyName}="${tag[tagIDKeyName]}"]`, tag.callback);
+ }
+
+ if (!autoAddListeners || !hasAsyncCallback) {
+ return hasAsyncCallback;
+ }
+
+ return addListeners();
+ }
+ function addListeners() {
+ if (isDOMComplete()) {
+ applyCallbacks();
+ return;
+ } // Instead of using a MutationObserver, we just apply
+
+ /* istanbul ignore next */
+
+
+ document.onreadystatechange = () => {
+ applyCallbacks();
+ };
+ }
+ function applyCallbacks(matchElement) {
+ for (const [query, callback] of callbacks) {
+ const selector = `${query}[onload="this.__vm_l=1"]`;
+ let elements = [];
+
+ if (!matchElement) {
+ elements = toArray(document.querySelectorAll(selector));
+ }
+
+ if (matchElement && matchElement.matches(selector)) {
+ elements = [matchElement];
+ }
+
+ for (const element of elements) {
+ /* __vm_cb: whether the load callback has been called
+ * __vm_l: set by onload attribute, whether the element was loaded
+ * __vm_ev: whether the event listener was added or not
+ */
+ if (element.__vm_cb) {
+ continue;
+ }
+
+ const onload = () => {
+ /* Mark that the callback for this element has already been called,
+ * this prevents the callback to run twice in some (rare) conditions
+ */
+ element.__vm_cb = true;
+ /* onload needs to be removed because we only need the
+ * attribute after ssr and if we dont remove it the node
+ * will fail isEqualNode on the client
+ */
+
+ element.removeAttribute('onload');
+ callback(element);
+ };
+ /* IE9 doesnt seem to load scripts synchronously,
+ * causing a script sometimes/often already to be loaded
+ * when we add the event listener below (thus adding an onload event
+ * listener has no use because it will never be triggered).
+ * Therefore we add the onload attribute during ssr, and
+ * check here if it was already loaded or not
+ */
+
+
+ if (element.__vm_l) {
+ onload();
+ continue;
+ }
+
+ if (!element.__vm_ev) {
+ element.__vm_ev = true;
+ element.addEventListener('load', onload);
+ }
+ }
+ }
}
/**
@@ -935,43 +994,38 @@
* @param {Object} attrs - the new document html attributes
* @param {HTMLElement} tag - the HTMLElement tag to update with new attrs
*/
- function updateAttribute (ref, attrs, tag) {
- if ( ref === void 0 ) ref = {};
- var attribute = ref.attribute;
- var vueMetaAttrString = tag.getAttribute(attribute);
- var vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : [];
- var toRemove = toArray(vueMetaAttrs);
+ function updateAttribute({
+ attribute
+ } = {}, attrs, tag) {
+ const vueMetaAttrString = tag.getAttribute(attribute);
+ const vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : [];
+ const toRemove = toArray(vueMetaAttrs);
+ const keepIndexes = [];
- var keepIndexes = [];
- for (var attr in attrs) {
+ for (const attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
- var value = includes(booleanHtmlAttributes, attr)
- ? ''
- : isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr];
-
+ const value = includes(booleanHtmlAttributes, attr) ? '' : isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr];
tag.setAttribute(attr, value || '');
if (!includes(vueMetaAttrs, attr)) {
vueMetaAttrs.push(attr);
- }
+ } // filter below wont ever check -1
+
- // filter below wont ever check -1
keepIndexes.push(toRemove.indexOf(attr));
}
}
- var removedAttributesCount = toRemove
- .filter(function (el, index) { return !includes(keepIndexes, index); })
- .reduce(function (acc, attr) {
- tag.removeAttribute(attr);
- return acc + 1
- }, 0);
+ const removedAttributesCount = toRemove.filter((el, index) => !includes(keepIndexes, index)).reduce((acc, attr) => {
+ tag.removeAttribute(attr);
+ return acc + 1;
+ }, 0);
if (vueMetaAttrs.length === removedAttributesCount) {
tag.removeAttribute(attribute);
} else {
- tag.setAttribute(attribute, (vueMetaAttrs.sort()).join(','));
+ tag.setAttribute(attribute, vueMetaAttrs.sort().join(','));
}
}
@@ -980,9 +1034,9 @@
*
* @param {String} title - the new title of the document
*/
- function updateTitle (title) {
- if (title === undefined) {
- return
+ function updateTitle(title) {
+ if (!title && title !== '') {
+ return;
}
document.title = title;
@@ -996,98 +1050,143 @@
* @param {(Array|Object)} tags - an array of tag objects or a single object in case of base
* @return {Object} - a representation of what tags changed
*/
- function updateTag (appId, ref, type, tags, headTag, bodyTag) {
- if ( ref === void 0 ) ref = {};
- var attribute = ref.attribute;
- var tagIDKeyName = ref.tagIDKeyName;
- var oldHeadTags = toArray(headTag.querySelectorAll((type + "[" + attribute + "=\"" + appId + "\"], " + type + "[data-" + tagIDKeyName + "]")));
- var oldBodyTags = toArray(bodyTag.querySelectorAll((type + "[" + attribute + "=\"" + appId + "\"][data-body=\"true\"], " + type + "[data-" + tagIDKeyName + "][data-body=\"true\"]")));
- var dataAttributes = [tagIDKeyName, 'body'];
- var newTags = [];
+ function updateTag(appId, options = {}, type, tags, head, body) {
+ const {
+ attribute,
+ tagIDKeyName
+ } = options;
+ const dataAttributes = [tagIDKeyName, ...commonDataAttributes];
+ const newElements = [];
+ const queryOptions = {
+ appId,
+ attribute,
+ type,
+ tagIDKeyName
+ };
+ const currentElements = {
+ head: queryElements(head, queryOptions),
+ pbody: queryElements(body, queryOptions, {
+ pbody: true
+ }),
+ body: queryElements(body, queryOptions, {
+ body: true
+ })
+ };
if (tags.length > 1) {
// remove duplicates that could have been found by merging tags
// which include a mixin with metaInfo and that mixin is used
// by multiple components on the same page
- var found = [];
- tags = tags.filter(function (x) {
- var k = JSON.stringify(x);
- var res = !includes(found, k);
+ const found = [];
+ tags = tags.filter(x => {
+ const k = JSON.stringify(x);
+ const res = !includes(found, k);
found.push(k);
- return res
+ return res;
});
}
if (tags.length) {
- tags.forEach(function (tag) {
- var newElement = document.createElement(type);
-
- newElement.setAttribute(attribute, appId);
-
- var oldTags = tag.body !== true ? oldHeadTags : oldBodyTags;
-
- for (var attr in tag) {
- if (tag.hasOwnProperty(attr)) {
- if (attr === 'innerHTML') {
- newElement.innerHTML = tag.innerHTML;
- } else if (attr === 'cssText') {
- if (newElement.styleSheet) {
- /* istanbul ignore next */
- newElement.styleSheet.cssText = tag.cssText;
- } else {
- newElement.appendChild(document.createTextNode(tag.cssText));
- }
- } else {
- var _attr = includes(dataAttributes, attr)
- ? ("data-" + attr)
- : attr;
-
- var isBooleanAttribute = includes(booleanHtmlAttributes, attr);
- if (isBooleanAttribute && !tag[attr]) {
- continue
- }
-
- var value = isBooleanAttribute ? '' : tag[attr];
- newElement.setAttribute(_attr, value);
- }
- }
+ for (const tag of tags) {
+ if (tag.skip) {
+ continue;
}
- // Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
- var indexToDelete;
- var hasEqualElement = oldTags.some(function (existingTag, index) {
+ const newElement = document.createElement(type);
+ newElement.setAttribute(attribute, appId);
+
+ for (const attr in tag) {
+ /* istanbul ignore next */
+ if (!tag.hasOwnProperty(attr)) {
+ continue;
+ }
+
+ if (attr === 'innerHTML') {
+ newElement.innerHTML = tag.innerHTML;
+ continue;
+ }
+
+ if (attr === 'json') {
+ newElement.innerHTML = JSON.stringify(tag.json);
+ continue;
+ }
+
+ if (attr === 'cssText') {
+ if (newElement.styleSheet) {
+ /* istanbul ignore next */
+ newElement.styleSheet.cssText = tag.cssText;
+ } else {
+ newElement.appendChild(document.createTextNode(tag.cssText));
+ }
+
+ continue;
+ }
+
+ if (attr === 'callback') {
+ newElement.onload = () => tag[attr](newElement);
+
+ continue;
+ }
+
+ const _attr = includes(dataAttributes, attr) ? `data-${attr}` : attr;
+
+ const isBooleanAttribute = includes(booleanHtmlAttributes, attr);
+
+ if (isBooleanAttribute && !tag[attr]) {
+ continue;
+ }
+
+ const value = isBooleanAttribute ? '' : tag[attr];
+ newElement.setAttribute(_attr, value);
+ }
+
+ const oldElements = currentElements[getElementsKey(tag)]; // Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
+
+ let indexToDelete;
+ const hasEqualElement = oldElements.some((existingTag, index) => {
indexToDelete = index;
- return newElement.isEqualNode(existingTag)
+ return newElement.isEqualNode(existingTag);
});
if (hasEqualElement && (indexToDelete || indexToDelete === 0)) {
- oldTags.splice(indexToDelete, 1);
+ oldElements.splice(indexToDelete, 1);
} else {
- newTags.push(newElement);
+ newElements.push(newElement);
}
- });
- }
-
- var oldTags = oldHeadTags.concat(oldBodyTags);
- oldTags.forEach(function (tag) { return tag.parentNode.removeChild(tag); });
- newTags.forEach(function (tag) {
- if (tag.getAttribute('data-body') === 'true') {
- bodyTag.appendChild(tag);
- } else {
- headTag.appendChild(tag);
}
- });
-
- return { oldTags: oldTags, newTags: newTags }
- }
-
- function getTag (tags, tag) {
- if (!tags[tag]) {
- tags[tag] = document.getElementsByTagName(tag)[0];
}
- return tags[tag]
+ let oldElements = [];
+
+ for (const current of Object.values(currentElements)) {
+ oldElements = [...oldElements, ...current];
+ } // remove old elements
+
+
+ for (const element of oldElements) {
+ element.parentNode.removeChild(element);
+ } // insert new elements
+
+
+ for (const element of newElements) {
+ if (element.hasAttribute('data-body')) {
+ body.appendChild(element);
+ continue;
+ }
+
+ if (element.hasAttribute('data-pbody')) {
+ body.insertBefore(element, body.firstChild);
+ continue;
+ }
+
+ head.appendChild(element);
+ }
+
+ return {
+ oldTags: oldElements,
+ newTags: newElements
+ };
}
/**
@@ -1095,60 +1194,66 @@
*
* @param {Object} newInfo - the meta info to update to
*/
- function updateClientMetaInfo (appId, options, newInfo) {
- if ( options === void 0 ) options = {};
- var ssrAttribute = options.ssrAttribute;
+ function updateClientMetaInfo(appId, options = {}, newInfo) {
+ const {
+ ssrAttribute,
+ ssrAppId
+ } = options; // only cache tags for current update
- // only cache tags for current update
- var tags = {};
+ const tags = {};
+ const htmlTag = getTag(tags, 'html'); // if this is a server render, then dont update
- var htmlTag = getTag(tags, 'html');
-
- // if this is a server render, then dont update
- if (appId === 'ssr' && htmlTag.hasAttribute(ssrAttribute)) {
+ if (appId === ssrAppId && htmlTag.hasAttribute(ssrAttribute)) {
// remove the server render attribute so we can update on (next) changes
- htmlTag.removeAttribute(ssrAttribute);
- return false
- }
+ htmlTag.removeAttribute(ssrAttribute); // add load callbacks if the
- // initialize tracked changes
- var addedTags = {};
- var removedTags = {};
+ let addLoadListeners = false;
- for (var type in newInfo) {
+ for (const type of tagsSupportingOnload) {
+ if (newInfo[type] && addCallbacks(options, type, newInfo[type])) {
+ addLoadListeners = true;
+ }
+ }
+
+ if (addLoadListeners) {
+ addListeners();
+ }
+
+ return false;
+ } // initialize tracked changes
+
+
+ const addedTags = {};
+ const removedTags = {};
+
+ for (const type in newInfo) {
// ignore these
if (includes(metaInfoOptionKeys, type)) {
- continue
+ continue;
}
if (type === 'title') {
// update the title
updateTitle(newInfo.title);
- continue
+ continue;
}
if (includes(metaInfoAttributeKeys, type)) {
- var tagName = type.substr(0, 4);
+ const tagName = type.substr(0, 4);
updateAttribute(options, newInfo[type], getTag(tags, tagName));
- continue
- }
+ continue;
+ } // tags should always be an array, ignore if it isnt
+
- // tags should always be an array, ignore if it isnt
if (!isArray(newInfo[type])) {
- continue
+ continue;
}
- var ref = updateTag(
- appId,
- options,
- type,
- newInfo[type],
- getTag(tags, 'head'),
- getTag(tags, 'body')
- );
- var oldTags = ref.oldTags;
- var newTags = ref.newTags;
+ const {
+ oldTags,
+ newTags
+ } = updateTag(appId, options, type, newInfo[type], getTag(tags, 'head'), getTag(tags, 'body'));
if (newTags.length) {
addedTags[type] = newTags;
@@ -1156,12 +1261,13 @@
}
}
- return { addedTags: addedTags, removedTags: removedTags }
+ return {
+ addedTags,
+ removedTags
+ };
}
- function _refresh (options) {
- if ( options === void 0 ) options = {};
-
+ 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
@@ -1172,32 +1278,35 @@
*
* @return {Object} - new meta info
*/
- return function refresh () {
- var metaInfo = getMetaInfo(options, this.$root, clientSequences);
+ return function refresh() {
+ const metaInfo = getMetaInfo(options, this.$root, clientSequences);
+ const appId = this.$root._vueMeta.appId;
+ const tags = updateClientMetaInfo(appId, options, metaInfo); // emit "event" with new info
- var appId = this.$root._vueMeta.appId;
- var tags = updateClientMetaInfo(appId, options, metaInfo);
- // emit "event" with new info
if (tags && isFunction(metaInfo.changed)) {
metaInfo.changed(metaInfo, tags.addedTags, tags.removedTags);
}
- return { vm: this, metaInfo: metaInfo, tags: tags }
- }
+ return {
+ vm: this,
+ metaInfo,
+ tags
+ };
+ };
}
- function _$meta (options) {
- if ( options === void 0 ) options = {};
-
- var _refresh$1 = _refresh(options);
- var inject = function () {};
+ function _$meta(options = {}) {
+ const _refresh$1 = _refresh(options);
+ const inject = () => {};
/**
* Returns an injector for server-side rendering.
* @this {Object} - the Vue instance (a root component)
* @return {Object} - injector
*/
- return function $meta () {
+
+
+ return function $meta() {
if (!this.$root._vueMeta) {
return {
getOptions: showWarningNotSupported,
@@ -1205,48 +1314,45 @@
inject: showWarningNotSupported,
pause: showWarningNotSupported,
resume: showWarningNotSupported
- }
+ };
}
return {
- getOptions: function () { return getOptions(options); },
+ getOptions: () => getOptions(options),
refresh: _refresh$1.bind(this),
- inject: inject,
+ inject,
pause: pause.bind(this),
resume: resume.bind(this)
- }
- }
+ };
+ };
}
/**
* Plugin install function.
* @param {Function} Vue - the Vue constructor.
*/
- function install (Vue, options) {
- if ( options === void 0 ) options = {};
+ function install(Vue, options = {}) {
if (Vue.__vuemeta_installed) {
- return
+ return;
}
+
Vue.__vuemeta_installed = true;
-
options = setOptions(options);
-
Vue.prototype.$meta = _$meta(options);
-
Vue.mixin(createMixin(Vue, options));
- }
+ } // automatic install
+
- // automatic install
if (!isUndefined(window) && !isUndefined(window.Vue)) {
/* istanbul ignore next */
install(window.Vue);
}
var browser = {
- version: version,
- install: install,
- hasMetaInfo: hasMetaInfo
+ version,
+ install,
+ hasMetaInfo
};
return browser;
diff --git a/dist/vue-meta.min.js b/dist/vue-meta.min.js
index 9d2cd25..6b51206 100644
--- a/dist/vue-meta.min.js
+++ b/dist/vue-meta.min.js
@@ -1 +1 @@
-!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).VueMeta=t()}(this,function(){"use strict";var e=null;function t(t,n){t.$root._vueMeta.initialized||!t.$root._vueMeta.initializing&&"watcher"!==n||(t.$root._vueMeta.initialized=null),t.$root._vueMeta.initialized&&!t.$root._vueMeta.paused&&function(t,n){void 0===n&&(n=10);clearTimeout(e),e=setTimeout(function(){t()},n)}(function(){return t.$meta().refresh()})}function n(e){return Array.isArray(e)}function r(e){return void 0===e}function i(e){return"object"==typeof e}function o(e){return"function"==typeof e}function a(e,t){return t&&i(e)?(n(e[t])||(e[t]=[]),e):n(e)?e:[]}function u(e,t,n){a(e,t),e[t].push(n)}function s(e){return void 0===e&&(e=this),e&&(!0===e._vueMeta||i(e._vueMeta))}function c(e){if(!e.$root._vueMeta.navGuards&&e.$root.$router){e.$root._vueMeta.navGuards=!0;var t=e.$root.$router,n=e.$root.$meta();t.beforeEach(function(e,t,r){n.pause(),r()}),t.afterEach(function(){var e=n.resume().metaInfo;e&&e.afterNavigation&&o(e.afterNavigation)&&e.afterNavigation(e)})}}var f=1;var l={title:void 0,titleChunk:"",titleTemplate:"%s",htmlAttrs:{},bodyAttrs:{},headAttrs:{},base:[],link:[],meta:[],style:[],script:[],noscript:[],__dangerouslyDisableSanitizers:[],__dangerouslyDisableSanitizersByTagID:{}},d={keyName:"metaInfo",attribute:"data-vue-meta",ssrAttribute:"data-vue-meta-server-rendered",tagIDKeyName:"vmid",contentKeyName:"content",metaTemplateKeyName:"template"},v=["titleChunk","titleTemplate","changed","__dangerouslyDisableSanitizers","__dangerouslyDisableSanitizersByTagID"],h=["__dangerouslyDisableSanitizers","__dangerouslyDisableSanitizersByTagID"],p=["htmlAttrs","headAttrs","bodyAttrs"],m=["allowfullscreen","amp","async","autofocus","autoplay","checked","compact","controls","declare","default","defaultchecked","defaultmuted","defaultselected","defer","disabled","enabled","formnovalidate","hidden","indeterminate","inert","ismap","itemscope","loop","multiple","muted","nohref","noresize","noshade","novalidate","nowrap","open","pauseonexit","readonly","required","reversed","scoped","seamless","selected","sortable","truespeed","typemustmatch","visible"],y=function(){return console.warn("This vue app/component has no vue-meta configuration")};function g(e){return void 0===e&&(e=!0),this.$root._vueMeta.paused=!0,function(){return b(e)}}function b(e){if(void 0===e&&(e=!0),this.$root._vueMeta.paused=!1,e)return this.$root.$meta().refresh()}function $(e,t,n,i){var a=e.component,u=e.metaTemplateKeyName,s=e.contentKeyName;return r(n)&&(n=t[u],delete t[u]),!!n&&(r(i)&&(i=t[s]),t[s]=o(n)?n.call(a,i):n.replace(/%s/g,i),!0)}function M(e,t){var n=arguments;if(!Array.prototype.findIndex){for(var r=0;r/g,">"],[/"/g,'"'],[/'/g,"'"]];var N=function(e){return function(e){return!!e&&"object"==typeof e}(e)&&!function(e){var t=Object.prototype.toString.call(e);return"[object RegExp]"===t||"[object Date]"===t||function(e){return e.$$typeof===w}(e)}(e)};var w="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function I(e,t){return!1!==t.clone&&t.isMergeableObject(e)?j((n=e,Array.isArray(n)?[]:{}),e,t):e;var n}function O(e,t,n){return e.concat(t).map(function(e){return I(e,n)})}function S(e){return Object.keys(e).concat(function(e){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e).filter(function(t){return e.propertyIsEnumerable(t)}):[]}(e))}function E(e,t,n){var r={};return n.isMergeableObject(e)&&S(e).forEach(function(t){r[t]=I(e[t],n)}),S(t).forEach(function(i){n.isMergeableObject(t[i])&&e[i]?r[i]=function(e,t){if(!t.customMerge)return j;var n=t.customMerge(e);return"function"==typeof n?n:j}(i,n)(e[i],t[i],n):r[i]=I(t[i],n)}),r}function j(e,t,n){(n=n||{}).arrayMerge=n.arrayMerge||O,n.isMergeableObject=n.isMergeableObject||N;var r=Array.isArray(t);return r===Array.isArray(e)?r?n.arrayMerge(e,t,n):E(e,t,n):I(t,n)}j.all=function(e,t){if(!Array.isArray(e))throw new Error("first argument should be an array");return e.reduce(function(e,n){return j(e,n,t)},{})};var z=j;function D(e,t,n){return void 0===n&&(n={}),t.hasOwnProperty("title")&&void 0===t.title&&delete t.title,p.forEach(function(e){if(t[e])for(var n in t[e])t[e].hasOwnProperty(n)&&void 0===t[e][n]&&(m.includes(n)&&console.warn("VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details"),delete t[e][n])}),z(e,t,{arrayMerge:function(e,t){return function(e,t,n){var r=e.component,i=e.tagIDKeyName,o=e.metaTemplateKeyName,a=e.contentKeyName,u=[];return t.forEach(function(e,t){if(e[i]){var s=M(n,function(t){return t[i]===e[i]}),c=n[s];if(-1!==s){if(c.hasOwnProperty(a)&&void 0===c[a]||c.hasOwnProperty("innerHTML")&&void 0===c.innerHTML)return u.push(e),void n.splice(s,1);if(null!==c[a]&&null!==c.innerHTML){var f=e[o];f&&(c[o]?c[a]||$({component:r,metaTemplateKeyName:o,contentKeyName:a},c,void 0,e[a]):$({component:r,metaTemplateKeyName:o,contentKeyName:a},c,f))}else n.splice(s,1)}else u.push(e)}else u.push(e)}),u.concat(n)}(n,e,t)}})}function k(e,t,n){void 0===e&&(e={}),void 0===n&&(n={});var a=e.keyName,u=e.metaTemplateKeyName,s=e.tagIDKeyName,c=t.$options,f=t.$children;if(t._inactive)return n;if(c[a]){var l=c[a];if(o(l)&&(l=l.call(t)),!i(l))return n;n=D(n,l,e)}return f.length&&f.forEach(function(t){(function(e){return void 0===e&&(e=this),e&&!r(e._vueMeta)})(t)&&(n=k(e,t,n))}),u&&n.meta&&(n.meta.forEach(function(t){return $(e,t)}),n.meta=n.meta.filter(function(e,t,n){return!e.hasOwnProperty(s)||t===M(n,function(t){return t[s]===e[s]})})),n}function K(e,t,r){void 0===e&&(e={}),void 0===r&&(r=[]);var o=k(e,t,l);o.title&&(o.titleChunk=o.title),o.titleTemplate&&"%s"!==o.titleTemplate&&$({component:t,contentKeyName:"title"},o,o.titleTemplate,o.titleChunk||""),o.base&&(o.base=Object.keys(o.base).length?[o.base]:[]);var u={doEscape:function(e){return r.reduce(function(e,t){var n=t[0],r=t[1];return e.replace(n,r)},e)}};return h.forEach(function(e,t){if(0===t)a(o,e);else if(1===t)for(var n in o[e])a(o[e],n);u[e]=o[e]}),o=function e(t,r,o){var a=r.tagIDKeyName,u=o.doEscape;void 0===u&&(u=function(e){return e});var s={};for(var c in t){var f=t[c];if(T(v,c))s[c]=f;else{var l=h[0];if(o[l]&&T(o[l],c))s[c]=f;else{var d=t[a];d&&(l=h[1],o[l]&&o[l][d]&&T(o[l][d],c))?s[c]=f:"string"==typeof f?s[c]=u(f):n(f)?s[c]=f.map(function(t){return i(t)?e(t,r,o):u(t)}):i(f)?s[c]=e(f,r,o):s[c]=f}}}return s}(o,e,u)}function x(e,t,r){void 0===e&&(e={});var i=e.attribute,o=r.getAttribute(i),a=o?o.split(","):[],u=_(a),s=[];for(var c in t)if(t.hasOwnProperty(c)){var f=T(m,c)?"":n(t[c])?t[c].join(" "):t[c];r.setAttribute(c,f||""),T(a,c)||a.push(c),s.push(u.indexOf(c))}var l=u.filter(function(e,t){return!T(s,t)}).reduce(function(e,t){return r.removeAttribute(t),e+1},0);a.length===l?r.removeAttribute(i):r.setAttribute(i,a.sort().join(","))}function P(e,t,n,r,i,o){void 0===t&&(t={});var a=t.attribute,u=t.tagIDKeyName,s=_(i.querySelectorAll(n+"["+a+'="'+e+'"], '+n+"[data-"+u+"]")),c=_(o.querySelectorAll(n+"["+a+'="'+e+'"][data-body="true"], '+n+"[data-"+u+'][data-body="true"]')),f=[u,"body"],l=[];if(r.length>1){var d=[];r=r.filter(function(e){var t=JSON.stringify(e),n=!T(d,t);return d.push(t),n})}r.length&&r.forEach(function(t){var r=document.createElement(n);r.setAttribute(a,e);var i,o=!0!==t.body?s:c;for(var u in t)if(t.hasOwnProperty(u))if("innerHTML"===u)r.innerHTML=t.innerHTML;else if("cssText"===u)r.styleSheet?r.styleSheet.cssText=t.cssText:r.appendChild(document.createTextNode(t.cssText));else{var d=T(f,u)?"data-"+u:u,v=T(m,u);if(v&&!t[u])continue;var h=v?"":t[u];r.setAttribute(d,h)}o.some(function(e,t){return i=t,r.isEqualNode(e)})&&(i||0===i)?o.splice(i,1):l.push(r)});var v=s.concat(c);return v.forEach(function(e){return e.parentNode.removeChild(e)}),l.forEach(function(e){"true"===e.getAttribute("data-body")?o.appendChild(e):i.appendChild(e)}),{oldTags:v,newTags:l}}function C(e,t){return e[t]||(e[t]=document.getElementsByTagName(t)[0]),e[t]}function H(e){return void 0===e&&(e={}),function(){var t=K(e,this.$root,A),r=function(e,t,r){void 0===t&&(t={});var i=t.ssrAttribute,o={},a=C(o,"html");if("ssr"===e&&a.hasAttribute(i))return a.removeAttribute(i),!1;var u,s={},c={};for(var f in r)if(!T(v,f))if("title"!==f){if(T(p,f)){var l=f.substr(0,4);x(t,r[f],C(o,l))}else if(n(r[f])){var d=P(e,t,f,r[f],C(o,"head"),C(o,"body")),h=d.oldTags,m=d.newTags;m.length&&(s[f]=m,c[f]=h)}}else void 0!==(u=r.title)&&(document.title=u);return{addedTags:s,removedTags:c}}(this.$root._vueMeta.appId,e,t);return r&&o(t.changed)&&t.changed(t,r.addedTags,r.removedTags),{vm:this,metaInfo:t,tags:r}}}function L(e,n){void 0===n&&(n={}),e.__vuemeta_installed||(e.__vuemeta_installed=!0,n=function(e){for(var t in e=i(e)?e:{},d)e[t]||(e[t]=d[t]);return e}(n),e.prototype.$meta=function(e){void 0===e&&(e={});var t=H(e),n=function(){};return function(){return this.$root._vueMeta?{getOptions:function(){return function(e){var t={};for(var n in e)t[n]=e[n];return t}(e)},refresh:t.bind(this),inject:n,pause:g.bind(this),resume:b.bind(this)}:{getOptions:y,refresh:y,inject:y,pause:y,resume:y}}}(n),e.mixin(function(e,n){var i=["activated","deactivated","beforeMount"];return{beforeCreate:function(){var a=this;if(Object.defineProperty(this,"_hasMetaInfo",{configurable:!0,get:function(){return e.config.devtools&&!this.$root._vueMeta.hasMetaInfoDeprecationWarningShown&&(console.warn("VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead"),this.$root._vueMeta.hasMetaInfoDeprecationWarningShown=!0),s(this)}}),!r(this.$options[n.keyName])&&null!==this.$options[n.keyName]){if(this.$root._vueMeta||(this.$root._vueMeta={appId:f},f++),!this._vueMeta){this._vueMeta=!0;for(var l=this.$parent;l&&l!==this.$root;)r(l._vueMeta)&&(l._vueMeta=!1),l=l.$parent}o(this.$options[n.keyName])&&(this.$options.computed||(this.$options.computed={}),this.$options.computed.$metaInfo=this.$options[n.keyName],this.$isServer||u(this.$options,"created",function(){a.$watch("$metaInfo",function(){t(this,"watcher")})})),r(this.$root._vueMeta.initialized)&&(this.$root._vueMeta.initialized=this.$isServer,this.$root._vueMeta.initialized||(u(this.$options,"beforeMount",function(){a.$root.$el&&a.$root.$el.hasAttribute&&a.$root.$el.hasAttribute("data-server-rendered")&&(a.$root._vueMeta.appId="ssr")}),u(this.$options,"mounted",function(){a.$root._vueMeta.initialized||(a.$root._vueMeta.initializing=!0,a.$nextTick(function(){var e=this,r=this.$root.$meta().refresh(),i=r.tags,o=r.metaInfo;!1===i&&null===this.$root._vueMeta.initialized&&this.$nextTick(function(){return t(e,"initializing")}),this.$root._vueMeta.initialized=!0,delete this.$root._vueMeta.initializing,!n.refreshOnceOnNavigation&&o.afterNavigation&&c(this)}))}),n.refreshOnceOnNavigation&&c(this))),this.$isServer||(i.forEach(function(e){u(a.$options,e,function(){return t(a,e)})}),u(this.$options,"destroyed",function(){var e=setInterval(function(){a.$el&&null!==a.$el.offsetParent||(clearInterval(e),a.$parent&&t(a,"destroyed"))},50)}))}}}}(e,n)))}return r(window)||r(window.Vue)||L(window.Vue),{version:"2.0.5",install:L,hasMetaInfo:s}});
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).VueMeta=t()}(this,function(){"use strict";let e=null;function t(t,n){t.$root._vueMeta.initialized||!t.$root._vueMeta.initializing&&"watcher"!==n||(t.$root._vueMeta.initialized=null),t.$root._vueMeta.initialized&&!t.$root._vueMeta.paused&&function(t,n=10){clearTimeout(e),e=setTimeout(()=>{t()},n)}(()=>t.$meta().refresh())}function n(e){return Array.isArray(e)}function o(e){return void 0===e}function r(e){return"object"==typeof e}function i(e){return"object"==typeof e&&null!==e}function a(e){return"function"==typeof e}function s(e,t){return t&&r(e)?(n(e[t])||(e[t]=[]),e):n(e)?e:[]}function u(e,t,n){s(e,t),e[t].push(n)}function c(e=this){return e&&(!0===e._vueMeta||r(e._vueMeta))}function l(e){if(e.$root._vueMeta.navGuards||!e.$root.$router)return;e.$root._vueMeta.navGuards=!0;const t=e.$root.$router,n=e.$root.$meta();t.beforeEach((e,t,o)=>{n.pause(),o()}),t.afterEach(()=>{const{metaInfo:e}=n.resume();e&&e.afterNavigation&&a(e.afterNavigation)&&e.afterNavigation(e)})}const f=function(){try{return!o(window)}catch(e){return!1}}()?window:global,d=f.console=f.console||{};function h(...e){d&&d.warn&&d.warn(...e)}const p=()=>h("This vue app/component has no vue-meta configuration");let m=1;const y={title:void 0,titleChunk:"",titleTemplate:"%s",htmlAttrs:{},bodyAttrs:{},headAttrs:{},base:[],link:[],meta:[],style:[],script:[],noscript:[],__dangerouslyDisableSanitizers:[],__dangerouslyDisableSanitizersByTagID:{}},v={keyName:"metaInfo",attribute:"data-vue-meta",ssrAttribute:"data-vue-meta-server-rendered",tagIDKeyName:"vmid",contentKeyName:"content",metaTemplateKeyName:"template",ssrAppId:"ssr"},b=["titleChunk","titleTemplate","changed","__dangerouslyDisableSanitizers","__dangerouslyDisableSanitizersByTagID"],g=["__dangerouslyDisableSanitizers","__dangerouslyDisableSanitizersByTagID"],$=["htmlAttrs","headAttrs","bodyAttrs"],_=["link","style","script"],M=["body","pbody"],A=["allowfullscreen","amp","async","autofocus","autoplay","checked","compact","controls","declare","default","defaultchecked","defaultmuted","defaultselected","defer","disabled","enabled","formnovalidate","hidden","indeterminate","inert","ismap","itemscope","loop","multiple","muted","nohref","noresize","noshade","novalidate","nowrap","open","pauseonexit","readonly","required","reversed","scoped","seamless","selected","sortable","truespeed","typemustmatch","visible"];function T(e=!0){return this.$root._vueMeta.paused=!0,()=>N(e)}function N(e=!0){if(this.$root._vueMeta.paused=!1,e)return this.$root.$meta().refresh()}function I({component:e,metaTemplateKeyName:t,contentKeyName:n},r,i,s){return o(i)&&(i=r[t],delete r[t]),!!i&&(o(s)&&(s=r[n]),r[n]=a(i)?i.call(e,s):i.replace(/%s/g,s),!0)}function w(e,t){if(!Array.prototype.findIndex){for(let n=0;n/g,">"],[/"/g,'"'],[/'/g,"'"]];var K=function(e){return function(e){return!!e&&"object"==typeof e}(e)&&!function(e){var t=Object.prototype.toString.call(e);return"[object RegExp]"===t||"[object Date]"===t||function(e){return e.$$typeof===k}(e)}(e)};var k="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function D(e,t){return!1!==t.clone&&t.isMergeableObject(e)?P((n=e,Array.isArray(n)?[]:{}),e,t):e;var n}function z(e,t,n){return e.concat(t).map(function(e){return D(e,n)})}function E(e){return Object.keys(e).concat(function(e){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e).filter(function(t){return e.propertyIsEnumerable(t)}):[]}(e))}function x(e,t,n){var o={};return n.isMergeableObject(e)&&E(e).forEach(function(t){o[t]=D(e[t],n)}),E(t).forEach(function(r){n.isMergeableObject(t[r])&&e[r]?o[r]=function(e,t){if(!t.customMerge)return P;var n=t.customMerge(e);return"function"==typeof n?n:P}(r,n)(e[r],t[r],n):o[r]=D(t[r],n)}),o}function P(e,t,n){(n=n||{}).arrayMerge=n.arrayMerge||z,n.isMergeableObject=n.isMergeableObject||K;var o=Array.isArray(t);return o===Array.isArray(e)?o?n.arrayMerge(e,t,n):x(e,t,n):D(t,n)}P.all=function(e,t){if(!Array.isArray(e))throw new Error("first argument should be an array");return e.reduce(function(e,n){return P(e,n,t)},{})};var C=P;function L(e,t,n={}){return t.hasOwnProperty("title")&&void 0===t.title&&delete t.title,$.forEach(e=>{if(t[e])for(const n in t[e])t[e].hasOwnProperty(n)&&void 0===t[e][n]&&(j(A,n)&&h("VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details"),delete t[e][n])}),C(e,t,{arrayMerge:(e,t)=>(function({component:e,tagIDKeyName:t,metaTemplateKeyName:n,contentKeyName:o},r,i){const a=[];return r.forEach((r,s)=>{if(!r[t])return void a.push(r);const u=w(i,e=>e[t]===r[t]),c=i[u];if(-1===u)return void a.push(r);if(c.hasOwnProperty(o)&&void 0===c[o]||c.hasOwnProperty("innerHTML")&&void 0===c.innerHTML)return a.push(r),void i.splice(u,1);if(null===c[o]||null===c.innerHTML)return void i.splice(u,1);const l=r[n];l&&(c[n]?c[o]||I({component:e,metaTemplateKeyName:n,contentKeyName:o},c,void 0,r[o]):I({component:e,metaTemplateKeyName:n,contentKeyName:o},c,l))}),a.concat(i)})(n,e,t)})}function H(e={},t,n={}){const{keyName:i,metaTemplateKeyName:s,tagIDKeyName:u}=e,{$options:c,$children:l}=t;if(t._inactive)return n;if(c[i]){let o=c[i];if(a(o)&&(o=o.call(t)),!r(o))return n;n=L(n,o,e)}return l.length&&l.forEach(t=>{(function(e=this){return e&&!o(e._vueMeta)})(t)&&(n=H(e,t,n))}),s&&n.meta&&(n.meta.forEach(t=>I(e,t)),n.meta=n.meta.filter((e,t,n)=>!e.hasOwnProperty(u)||t===w(n,t=>t[u]===e[u]))),n}function B(e={},t,o=[]){let r=H(e,t,y);r.title&&(r.titleChunk=r.title),r.titleTemplate&&"%s"!==r.titleTemplate&&I({component:t,contentKeyName:"title"},r,r.titleTemplate,r.titleChunk||""),r.base&&(r.base=Object.keys(r.base).length?[r.base]:[]);const a={doEscape:e=>o.reduce((e,[t,n])=>e.replace(t,n),e)};return g.forEach((e,t)=>{if(0===t)s(r,e);else if(1===t)for(const t in r[e])s(r[e],t);a[e]=r[e]}),r=function e(t,o,r){const{tagIDKeyName:a}=o,{doEscape:s=(e=>e),escapeKeys:u}=r,c={};for(const l in t){const f=t[l];if(j(b,l)){c[l]=f;continue}let[d]=g;if(r[d]&&j(r[d],l)){c[l]=f;continue}const h=t[a];if(h&&(d=g[1],r[d]&&r[d][h]&&j(r[d][h],l)))c[l]=f;else if("string"==typeof f?c[l]=s(f):n(f)?c[l]=f.map(t=>i(t)?e(t,o,{...r,escapeKeys:!0}):s(t)):i(f)?c[l]=e(f,o,{...r,escapeKeys:!0}):c[l]=f,u){const e=s(l);l!==e&&(c[e]=c[l],delete c[l])}}return c}(r,e,a)}function V(e,t){return e[t]||(e[t]=document.getElementsByTagName(t)[0]),e[t]}function q({body:e,pbody:t}){return e?"body":t?"pbody":"head"}function W(e,{appId:t,attribute:n,type:o,tagIDKeyName:r},i={}){const a=[`${o}[${n}="${t}"]`,`${o}[data-${r}]`].map(e=>{for(const t in i){const n=i[t];e+=`[data-${t}${n&&!0!==n?`="${n}"`:""}]`}return e});return O(e.querySelectorAll(a.join(", ")))}const G=[];function J(e,t){1===arguments.length&&(t=e,e=""),G.push([e,t])}function R({tagIDKeyName:e},t,n,o){let r=!1;for(const o of n)o[e]&&o.callback&&(r=!0,J(`${t}[data-${e}="${o[e]}"]`,o.callback));return o&&r?F():r}function F(){!function(e=document){return"complete"===e.readyState}()?document.onreadystatechange=()=>{Q()}:Q()}function Q(e){for(const[t,n]of G){const o=`${t}[onload="this.__vm_l=1"]`;let r=[];e||(r=O(document.querySelectorAll(o))),e&&e.matches(o)&&(r=[e]);for(const e of r){if(e.__vm_cb)continue;const t=()=>{e.__vm_cb=!0,e.removeAttribute("onload"),n(e)};e.__vm_l?t():e.__vm_ev||(e.__vm_ev=!0,e.addEventListener("load",t))}}}function U({attribute:e}={},t,o){const r=o.getAttribute(e),i=r?r.split(","):[],a=O(i),s=[];for(const e in t)if(t.hasOwnProperty(e)){const r=j(A,e)?"":n(t[e])?t[e].join(" "):t[e];o.setAttribute(e,r||""),j(i,e)||i.push(e),s.push(a.indexOf(e))}const u=a.filter((e,t)=>!j(s,t)).reduce((e,t)=>(o.removeAttribute(t),e+1),0);i.length===u?o.removeAttribute(e):o.setAttribute(e,i.sort().join(","))}function X(e,t={},n,o,r,i){const{attribute:a,tagIDKeyName:s}=t,u=[s,...M],c=[],l={appId:e,attribute:a,type:n,tagIDKeyName:s},f={head:W(r,l),pbody:W(i,l,{pbody:!0}),body:W(i,l,{body:!0})};if(o.length>1){const e=[];o=o.filter(t=>{const n=JSON.stringify(t),o=!j(e,n);return e.push(n),o})}if(o.length)for(const t of o){if(t.skip)continue;const o=document.createElement(n);o.setAttribute(a,e);for(const e in t){if(!t.hasOwnProperty(e))continue;if("innerHTML"===e){o.innerHTML=t.innerHTML;continue}if("json"===e){o.innerHTML=JSON.stringify(t.json);continue}if("cssText"===e){o.styleSheet?o.styleSheet.cssText=t.cssText:o.appendChild(document.createTextNode(t.cssText));continue}if("callback"===e){o.onload=()=>t[e](o);continue}const n=j(u,e)?`data-${e}`:e,r=j(A,e);if(r&&!t[e])continue;const i=r?"":t[e];o.setAttribute(n,i)}const r=f[q(t)];let i;r.some((e,t)=>(i=t,o.isEqualNode(e)))&&(i||0===i)?r.splice(i,1):c.push(o)}let d=[];for(const e of Object.values(f))d=[...d,...e];for(const e of d)e.parentNode.removeChild(e);for(const e of c)e.hasAttribute("data-body")?i.appendChild(e):e.hasAttribute("data-pbody")?i.insertBefore(e,i.firstChild):r.appendChild(e);return{oldTags:d,newTags:c}}function Y(e={}){return function(){const t=B(e,this.$root,S),o=function(e,t={},o){const{ssrAttribute:r,ssrAppId:i}=t,a={},s=V(a,"html");if(e===i&&s.hasAttribute(r)){s.removeAttribute(r);let e=!1;for(const n of _)o[n]&&R(t,n,o[n])&&(e=!0);return e&&F(),!1}const u={},c={};for(const r in o){if(j(b,r))continue;if("title"===r){((l=o.title)||""===l)&&(document.title=l);continue}if(j($,r)){const e=r.substr(0,4);U(t,o[r],V(a,e));continue}if(!n(o[r]))continue;const{oldTags:i,newTags:s}=X(e,t,r,o[r],V(a,"head"),V(a,"body"));s.length&&(u[r]=s,c[r]=i)}var l;return{addedTags:u,removedTags:c}}(this.$root._vueMeta.appId,e,t);return o&&a(t.changed)&&t.changed(t,o.addedTags,o.removedTags),{vm:this,metaInfo:t,tags:o}}}function Z(e,n={}){e.__vuemeta_installed||(e.__vuemeta_installed=!0,n=function(e){e=r(e)?e:{};for(const t in v)e[t]||(e[t]=v[t]);return e}(n),e.prototype.$meta=function(e={}){const t=Y(e),n=()=>{};return function(){return this.$root._vueMeta?{getOptions:()=>(function(e){const t={};for(const n in e)t[n]=e[n];return t})(e),refresh:t.bind(this),inject:n,pause:T.bind(this),resume:N.bind(this)}:{getOptions:p,refresh:p,inject:p,pause:p,resume:p}}}(n),e.mixin(function(e,n){const r=["activated","deactivated","beforeMount"];return{beforeCreate(){if(Object.defineProperty(this,"_hasMetaInfo",{configurable:!0,get(){return e.config.devtools&&!this.$root._vueMeta.hasMetaInfoDeprecationWarningShown&&(h("VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead"),this.$root._vueMeta.hasMetaInfoDeprecationWarningShown=!0),c(this)}}),!o(this.$options[n.keyName])&&null!==this.$options[n.keyName]){if(this.$root._vueMeta||(this.$root._vueMeta={appId:m},m++),!this._vueMeta){this._vueMeta=!0;let e=this.$parent;for(;e&&e!==this.$root;)o(e._vueMeta)&&(e._vueMeta=!1),e=e.$parent}a(this.$options[n.keyName])&&(this.$options.computed||(this.$options.computed={}),this.$options.computed.$metaInfo=this.$options[n.keyName],this.$isServer||u(this.$options,"created",()=>{this.$watch("$metaInfo",function(){t(this,"watcher")})})),o(this.$root._vueMeta.initialized)&&(this.$root._vueMeta.initialized=this.$isServer,this.$root._vueMeta.initialized||(u(this.$options,"beforeMount",()=>{this.$root.$el&&this.$root.$el.hasAttribute&&this.$root.$el.hasAttribute("data-server-rendered")&&(this.$root._vueMeta.appId=n.ssrAppId)}),u(this.$options,"mounted",()=>{this.$root._vueMeta.initialized||(this.$root._vueMeta.initializing=!0,this.$nextTick(function(){const{tags:e,metaInfo:o}=this.$root.$meta().refresh();!1===e&&null===this.$root._vueMeta.initialized&&this.$nextTick(()=>t(this,"initializing")),this.$root._vueMeta.initialized=!0,delete this.$root._vueMeta.initializing,!n.refreshOnceOnNavigation&&o.afterNavigation&&l(this)}))}),n.refreshOnceOnNavigation&&l(this))),this.$isServer||(r.forEach(e=>{u(this.$options,e,()=>t(this,e))}),u(this.$options,"destroyed",()=>{const e=setInterval(()=>{this.$el&&null!==this.$el.offsetParent||(clearInterval(e),this.$parent&&t(this,"destroyed"))},50)}))}}}}(e,n)))}return o(window)||o(window.Vue)||Z(window.Vue),{version:"2.1.0",install:Z,hasMetaInfo:c}});
diff --git a/package.json b/package.json
index 92ac04f..403c43d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "vue-meta",
- "version": "2.0.5",
+ "version": "2.1.0",
"description": "Manage HTML metadata in Vue.js components with ssr support",
"keywords": [
"attribute",