diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f732ad..f1cb8bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ 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.3.0-beta.0](https://github.com/nuxt/vue-meta/compare/v2.2.2...v2.3.0-beta.0) (2019-09-17) + + +### Bug Fixes + +* use computed prop (which uses caching) instead of calling the fn directly ([c344d60](https://github.com/nuxt/vue-meta/commit/c344d60)) + + +### Features + +* add option waitOnDestroyed ([f745059](https://github.com/nuxt/vue-meta/commit/f745059)) +* add options debounceWait ([d43b77c](https://github.com/nuxt/vue-meta/commit/d43b77c)) +* add possibility to add additional meta info ([0ab76ee](https://github.com/nuxt/vue-meta/commit/0ab76ee)) +* add support for setting attributes from multiple apps ([d9b0ab2](https://github.com/nuxt/vue-meta/commit/d9b0ab2)) +* enable setting refreshOnceOnNavigation during runtime ([9d14387](https://github.com/nuxt/vue-meta/commit/9d14387)) + ### [2.2.2](https://github.com/nuxt/vue-meta/compare/v2.2.1...v2.2.2) (2019-08-30) diff --git a/dist/vue-meta.common.js b/dist/vue-meta.common.js index c886a72..c3abc26 100644 --- a/dist/vue-meta.common.js +++ b/dist/vue-meta.common.js @@ -1,9 +1,10 @@ /** - * vue-meta v2.2.2 + * vue-meta v2.3.0-beta.0 * (c) 2019 * - Declan de Wet * - Sébastien Chopin (@Atinux) - * - All the amazing contributors + * - Pim (@pimlie) + * - All the amazing contributors * @license MIT */ @@ -13,41 +14,7 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'defau var deepmerge = _interopDefault(require('deepmerge')); -var version = "2.2.2"; - -// store an id to keep track of DOM updates -var 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 - if (!vm.$root._vueMeta.initialized && (vm.$root._vueMeta.initializing || hookName === 'watcher')) { - vm.$root._vueMeta.initialized = null; - } - - if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) { - // batch potential DOM updates to prevent extraneous re-rendering - batchUpdate(function () { - return vm.$meta().refresh(); - }); - } -} -/** - * Performs a batched update. - * - * @param {(null|Number)} id - the ID of this update - * @param {Function} callback - the update to perform - * @return {Number} id - a new ID - */ - -function batchUpdate(callback) { - var timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 10; - clearTimeout(batchId); - batchId = setTimeout(function () { - callback(); - }, timeout); - return batchId; -} +var version = "2.3.0-beta.0"; function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { @@ -63,8 +30,19 @@ function _typeof(obj) { return _typeof(obj); } -function _slicedToArray(arr, i) { - return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); +function _defineProperty(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; } function _toConsumableArray(arr) { @@ -79,48 +57,14 @@ function _arrayWithoutHoles(arr) { } } -function _arrayWithHoles(arr) { - if (Array.isArray(arr)) return arr; -} - function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } -function _iterableToArrayLimit(arr, i) { - var _arr = []; - var _n = true; - var _d = false; - var _e = undefined; - - try { - for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { - _arr.push(_s.value); - - if (i && _arr.length === i) break; - } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i["return"] != null) _i["return"](); - } finally { - if (_d) throw _e; - } - } - - return _arr; -} - function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } -function _nonIterableRest() { - throw new TypeError("Invalid attempt to destructure non-iterable instance"); -} - /** * checks if passed argument is an array * @param {any} arg - the object to check @@ -145,56 +89,6 @@ function isString(arg) { return typeof arg === 'string'; } -function ensureIsArray(arg, key) { - if (!key || !isObject(arg)) { - return isArray(arg) ? arg : []; - } - - if (!isArray(arg[key])) { - arg[key] = []; - } - - return arg; -} -function ensuredPush(object, key, el) { - ensureIsArray(object, key); - object[key].push(el); -} - -function hasMetaInfo() { - var vm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 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 - -function inMetaInfoBranch() { - var vm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this; - return vm && !isUndefined(vm._vueMeta); -} - -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; - } - - vm.$root._vueMeta.navGuards = true; - var $router = vm.$root.$router; - var $meta = vm.$root.$meta(); - $router.beforeEach(function (to, from, next) { - $meta.pause(); - next(); - }); - $router.afterEach(function () { - var _$meta$resume = $meta.resume(), - metaInfo = _$meta$resume.metaInfo; - - if (metaInfo && metaInfo.afterNavigation && isFunction(metaInfo.afterNavigation)) { - metaInfo.afterNavigation(metaInfo); - } - }); -} - function hasGlobalWindowFn() { try { return !isUndefined(window); @@ -215,173 +109,9 @@ function warn(str) { console.warn(str); } - -var appId = 1; -function createMixin(Vue, options) { - // for which Vue lifecycle hooks should the metaInfo be refreshed - var updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount']; // watch for client side component updates - - return { - beforeCreate: function beforeCreate() { - var _this = this; - - Object.defineProperty(this, '_hasMetaInfo', { - configurable: true, - get: function get() { - // Show deprecation warning once when devtools enabled - if (Vue.config.devtools && !this.$root._vueMeta.hasMetaInfoDeprecationWarningShown) { - 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 - // _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) { - return; - } - - if (!this.$root._vueMeta) { - this.$root._vueMeta = { - appId: appId - }; - appId++; - } // 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; - 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 - // 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.$watch('$metaInfo', function () { - _this.__metaInfo = undefined; - triggerUpdate(_this, 'watcher'); - }); - }); - } - } // 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 () { - // 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 = options.ssrAppId; - } - }); // we use the mounted hook here as on page load - - ensuredPush(this.$options, 'mounted', function () { - if (!_this.$root._vueMeta.initialized) { - // used in triggerUpdate to check if a change was triggered - // during initialization - _this.$root._vueMeta.initializing = true; // refresh meta in nextTick so all child components have loaded - - _this.$nextTick(function () { - var _this2 = this; - - var _this$$root$$meta$ref = this.$root.$meta().refresh(), - tags = _this$$root$$meta$ref.tags, - metaInfo = _this$$root$$meta$ref.metaInfo; // 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(_this2, 'initializing'); - }); - } - - this.$root._vueMeta.initialized = true; - 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 - - if (options.refreshOnceOnNavigation) { - addNavGuards(this); - } - } - } // do not trigger refresh on the server side - - - if (this.$isServer) { - return; - } // no need to add this hooks on server side - - - updateOnLifecycleHook.forEach(function (lifecycleHook) { - ensuredPush(_this.$options, lifecycleHook, function () { - return triggerUpdate(_this, lifecycleHook); - }); - }); - }, - // TODO: move back into beforeCreate when Vue issue is resolved - destroyed: function destroyed() { - var _this3 = this; - - // do not trigger refresh: - // - on the server side - // - when the component doesnt have a parent - // - doesnt have metaInfo defined - if (this.$isServer || !this.$parent || !hasMetaInfo(this)) { - return; - } // Wait that element is hidden before refreshing meta tags (to support animations) - - - var interval = setInterval(function () { - if (_this3.$el && _this3.$el.offsetParent !== null) { - return; - } - - clearInterval(interval); - triggerUpdate(_this3, 'destroyed'); - }, 50); - } - }; -} +var showWarningNotSupported = function showWarningNotSupported() { + return warn('This vue app/component has no vue-meta configuration'); +}; /** * These are constant variables used throughout the application. @@ -401,10 +131,11 @@ var defaultInfo = { script: [], noscript: [], __dangerouslyDisableSanitizers: [], - __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. - + __dangerouslyDisableSanitizersByTagID: {} }; +var rootConfigKey = '_vueMeta'; // 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 // manage and which it should ignore. @@ -422,7 +153,11 @@ var metaTemplateKeyName = 'template'; // This is the key name for the content-ho var contentKeyName = 'content'; // The id used for the ssr app -var ssrAppId = 'ssr'; +var ssrAppId = 'ssr'; // How long meta update + +var debounceWait = 10; // How long meta update + +var waitOnDestroyed = true; var defaultOptions = { keyName: keyName, attribute: attribute, @@ -430,14 +165,18 @@ var defaultOptions = { tagIDKeyName: tagIDKeyName, contentKeyName: contentKeyName, metaTemplateKeyName: metaTemplateKeyName, - ssrAppId: ssrAppId // List of metaInfo property keys which are configuration options (and dont generate html) + waitOnDestroyed: waitOnDestroyed, + debounceWait: debounceWait, + ssrAppId: ssrAppId +}; // might be a bit ugly, but minimizes the browser bundles a bit -}; -var metaInfoOptionKeys = ['titleChunk', 'titleTemplate', 'changed', '__dangerouslyDisableSanitizers', '__dangerouslyDisableSanitizersByTagID']; // The metaInfo property keys which are used to disable escaping +var defaultInfoKeys = Object.keys(defaultInfo); // The metaInfo property keys which are used to disable escaping -var disableOptionKeys = ['__dangerouslyDisableSanitizers', '__dangerouslyDisableSanitizersByTagID']; // List of metaInfo property keys which only generates attributes and no tags +var disableOptionKeys = [defaultInfoKeys[12], defaultInfoKeys[13]]; // List of metaInfo property keys which are configuration options (and dont generate html) -var metaInfoAttributeKeys = ['htmlAttrs', 'headAttrs', 'bodyAttrs']; // HTML elements which support the onload event +var metaInfoOptionKeys = [defaultInfoKeys[1], defaultInfoKeys[2], 'changed'].concat(disableOptionKeys); // List of metaInfo property keys which only generates attributes and no tags + +var metaInfoAttributeKeys = [defaultInfoKeys[3], defaultInfoKeys[4], defaultInfoKeys[5]]; // HTML elements which support the onload event var 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 @@ -453,17 +192,305 @@ var commonDataAttributes = ['body', 'pbody']; // from: https://github.com/kangax 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']; -function setOptions(options) { - // combine options - options = isObject(options) ? options : {}; +var batchId = null; +function triggerUpdate(_ref, rootVm, hookName) { + var debounceWait = _ref.debounceWait; - for (var key in defaultOptions) { - if (!options[key]) { - options[key] = defaultOptions[key]; - } + // 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 + if (!rootVm[rootConfigKey].initialized && (rootVm[rootConfigKey].initializing || hookName === 'watcher')) { + rootVm[rootConfigKey].initialized = null; } - return options; + if (rootVm[rootConfigKey].initialized && !rootVm[rootConfigKey].pausing) { + // batch potential DOM updates to prevent extraneous re-rendering + batchUpdate(function () { + return void rootVm.$meta().refresh(); + }, debounceWait); + } +} +/** + * Performs a batched update. + * + * @param {(null|Number)} id - the ID of this update + * @param {Function} callback - the update to perform + * @return {Number} id - a new ID + */ + +function batchUpdate(callback, timeout) { + timeout = timeout === undefined ? 10 : timeout; + + if (!timeout) { + callback(); + return; + } + + clearTimeout(batchId); + batchId = setTimeout(function () { + callback(); + }, timeout); + return batchId; +} + +function ensureIsArray(arg, key) { + if (!key || !isObject(arg)) { + return isArray(arg) ? arg : []; + } + + if (!isArray(arg[key])) { + arg[key] = []; + } + + return arg; +} +function ensuredPush(object, key, el) { + ensureIsArray(object, key); + object[key].push(el); +} + +function hasMetaInfo(vm) { + vm = vm || this; + return vm && (vm[rootConfigKey] === true || isObject(vm[rootConfigKey])); +} // a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has + +function inMetaInfoBranch(vm) { + vm = vm || this; + return vm && !isUndefined(vm[rootConfigKey]); +} + +function pause(rootVm, refresh) { + rootVm[rootConfigKey].pausing = true; + return function () { + return resume(rootVm, refresh); + }; +} +function resume(rootVm, refresh) { + rootVm[rootConfigKey].pausing = false; + + if (refresh || refresh === undefined) { + return rootVm.$meta().refresh(); + } +} + +function addNavGuards(rootVm) { + var router = rootVm.$router; // return when nav guards already added or no router exists + + if (rootVm[rootConfigKey].navGuards || !router) { + /* istanbul ignore next */ + return; + } + + rootVm[rootConfigKey].navGuards = true; + router.beforeEach(function (to, from, next) { + pause(rootVm); + next(); + }); + router.afterEach(function () { + var _resume = resume(rootVm), + metaInfo = _resume.metaInfo; + + if (metaInfo && isFunction(metaInfo.afterNavigation)) { + metaInfo.afterNavigation(metaInfo); + } + }); +} + +var appId = 1; +function createMixin(Vue, options) { + // for which Vue lifecycle hooks should the metaInfo be refreshed + var updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount']; // watch for client side component updates + + return { + beforeCreate: function beforeCreate() { + var rootKey = '$root'; + var $root = this[rootKey]; + var $options = this.$options; + Object.defineProperty(this, '_hasMetaInfo', { + configurable: true, + get: function get() { + // Show deprecation warning once when devtools enabled + if (Vue.config.devtools && !$root[rootConfigKey].deprecationWarningShown) { + warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead'); + $root[rootConfigKey].deprecationWarningShown = true; + } + + 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($options[options.keyName]) || $options[options.keyName] === null) { + return; + } + + if (!$root[rootConfigKey]) { + $root[rootConfigKey] = { + appId: appId + }; + appId++; + } // 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[rootConfigKey]) { + this[rootConfigKey] = true; + var parent = this.$parent; + + while (parent && parent !== $root) { + if (isUndefined(parent[rootConfigKey])) { + parent[rootConfigKey] = false; + } + + parent = parent.$parent; + } + } // coerce function-style metaInfo to a computed prop so we can observe + // it on creation + + + if (isFunction($options[options.keyName])) { + $options.computed = $options.computed || {}; + $options.computed.$metaInfo = $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($options, 'created', function () { + this.$watch('$metaInfo', function () { + triggerUpdate(options, this[rootKey], 'watcher'); + }); + }); + } + } // 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($root[rootConfigKey].initialized)) { + $root[rootConfigKey].initialized = this.$isServer; + + if (!$root[rootConfigKey].initialized) { + ensuredPush($options, 'beforeMount', function () { + var $root = this[rootKey]; // if this Vue-app was server rendered, set the appId to 'ssr' + // only one SSR app per page is supported + + if ($root.$el && $root.$el.nodeType === 1 && $root.$el.hasAttribute('data-server-rendered')) { + $root[rootConfigKey].appId = options.ssrAppId; + } + }); // we use the mounted hook here as on page load + + ensuredPush($options, 'mounted', function () { + var $root = this[rootKey]; + + if (!$root[rootConfigKey].initialized) { + // used in triggerUpdate to check if a change was triggered + // during initialization + $root[rootConfigKey].initializing = true; // refresh meta in nextTick so all child components have loaded + + this.$nextTick(function () { + var _$root$$meta$refresh = $root.$meta().refresh(), + tags = _$root$$meta$refresh.tags, + metaInfo = _$root$$meta$refresh.metaInfo; // 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 && $root[rootConfigKey].initialized === null) { + this.$nextTick(function () { + return triggerUpdate(options, $root, 'init'); + }); + } + + $root[rootConfigKey].initialized = true; + delete $root[rootConfigKey].initializing; // add the navigation guards if they havent been added yet + // they are needed for the afterNavigation callback + + if (!options.refreshOnceOnNavigation && metaInfo.afterNavigation) { + addNavGuards($root); + } + }); + } + }); // add the navigation guards if requested + + if (options.refreshOnceOnNavigation) { + addNavGuards($root); + } + } + } // do not trigger refresh on the server side + + + if (this.$isServer) { + return; + } // no need to add this hooks on server side + + + updateOnLifecycleHook.forEach(function (lifecycleHook) { + ensuredPush($options, lifecycleHook, function () { + triggerUpdate(options, this[rootKey], lifecycleHook); + }); + }); + }, + // TODO: move back into beforeCreate when Vue issue is resolved + destroyed: function destroyed() { + var _this = this; + + // do not trigger refresh: + // - when user configured to not wait for transitions on destroyed + // - when the component doesnt have a parent + // - doesnt have metaInfo defined + if (!this.$parent || !hasMetaInfo(this)) { + return; + } + + this.$nextTick(function () { + if (!options.waitOnDestroyed || !_this.$el || !_this.$el.offsetParent) { + triggerUpdate(options, _this.$root, 'destroyed'); + return; + } // Wait that element is hidden before refreshing meta tags (to support animations) + + + var interval = setInterval(function () { + if (_this.$el && _this.$el.offsetParent !== null) { + /* istanbul ignore next line */ + return; + } + + clearInterval(interval); + triggerUpdate(options, _this.$root, 'destroyed'); + }, 50); + }); + } + }; +} + +function setOptions(options) { + // combine options + options = isObject(options) ? options : {}; // The options are set like this so they can + // be minified by terser while keeping the + // user api intact + // terser --mangle-properties keep_quoted=strict + + /* eslint-disable dot-notation */ + + return { + keyName: options['keyName'] || defaultOptions.keyName, + attribute: options['attribute'] || defaultOptions.attribute, + ssrAttribute: options['ssrAttribute'] || defaultOptions.ssrAttribute, + tagIDKeyName: options['tagIDKeyName'] || defaultOptions.tagIDKeyName, + contentKeyName: options['contentKeyName'] || defaultOptions.contentKeyName, + metaTemplateKeyName: options['metaTemplateKeyName'] || defaultOptions.metaTemplateKeyName, + debounceWait: isUndefined(options['debounceWait']) ? defaultOptions.debounceWait : options['debounceWait'], + waitOnDestroyed: isUndefined(options['waitOnDestroyed']) ? defaultOptions.waitOnDestroyed : options['waitOnDestroyed'], + ssrAppId: options['ssrAppId'] || defaultOptions.ssrAppId, + refreshOnceOnNavigation: !!options['refreshOnceOnNavigation'] + }; + /* eslint-enable dot-notation */ } function getOptions(options) { var optionsCopy = {}; @@ -475,22 +502,6 @@ function getOptions(options) { return optionsCopy; } -function pause() { - var refresh = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - this.$root._vueMeta.paused = true; - return function () { - return resume(refresh); - }; -} -function resume() { - var refresh = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - this.$root._vueMeta.paused = false; - - if (refresh) { - return this.$root.$meta().refresh(); - } -} - /* * To reduce build size, this file provides simple polyfills without * overly excessive type checking and without modifying @@ -499,11 +510,11 @@ function resume() { * Also, only files in client/ & shared/ should use these functions * files in server/ still use normal js function */ -function findIndex(array, predicate) { +function findIndex(array, predicate, thisArg) { 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[2], array[idx], idx, array)) { + if (predicate.call(thisArg, array[idx], idx, array)) { return idx; } } @@ -511,7 +522,7 @@ function findIndex(array, predicate) { return -1; } - return array.findIndex(predicate, arguments[2]); + return array.findIndex(predicate, thisArg); } function toArray(arg) { if ( !Array.from) { @@ -551,10 +562,11 @@ function escape(info, options, escapeOptions, escapeKeys) { if (includes(metaInfoOptionKeys, key)) { escaped[key] = value; continue; - } + } // do not use destructuring for disableOptionKeys, it increases transpiled size + // due to var checks while we are guaranteed the structure of the cb - var _disableOptionKeys = _slicedToArray(disableOptionKeys, 1), - disableKey = _disableOptionKeys[0]; + + var disableKey = disableOptionKeys[0]; if (escapeOptions[disableKey] && includes(escapeOptions[disableKey], key)) { // this info[key] doesnt need to escaped if the option is listed in __dangerouslyDisableSanitizers @@ -601,16 +613,14 @@ function escape(info, options, escapeOptions, escapeKeys) { return escaped; } -function escapeMetaInfo(options, info) { - var escapeSequences = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; +function escapeMetaInfo(options, info, escapeSequences) { + escapeSequences = escapeSequences || []; // do not use destructuring for seq, it increases transpiled size + // due to var checks while we are guaranteed the structure of the cb + var escapeOptions = { doEscape: function doEscape(value) { - return escapeSequences.reduce(function (val, _ref) { - var _ref2 = _slicedToArray(_ref, 2), - v = _ref2[0], - r = _ref2[1]; - - return val.replace(v, r); + return escapeSequences.reduce(function (val, seq) { + return val.replace(seq[0], seq[1]); }, value); } }; @@ -647,10 +657,7 @@ function applyTemplate(_ref, headObject, template, chunk) { if (!template) { // cleanup faulty template properties - if (headObject.hasOwnProperty(metaTemplateKeyName)) { - delete headObject[metaTemplateKeyName]; - } - + delete headObject[metaTemplateKeyName]; return false; } @@ -696,7 +703,7 @@ function _arrayMerge(_ref, target, source) { // which means we keep the targetItem and ignore/remove the sourceItem - if (sourceItem.hasOwnProperty(contentKeyName) && sourceItem[contentKeyName] === undefined || sourceItem.hasOwnProperty('innerHTML') && sourceItem.innerHTML === undefined) { + if (contentKeyName in sourceItem && sourceItem[contentKeyName] === undefined || 'innerHTML' in sourceItem && sourceItem.innerHTML === undefined) { destination.push(targetItem); // remove current index from source array so its not concatenated to destination below source.splice(sourceIndex, 1); @@ -743,13 +750,13 @@ function _arrayMerge(_ref, target, source) { }); return destination.concat(source); } -function merge(target, source) { - var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - - // remove properties explicitly set to false so child components can +var warningShown = false; +function merge(target, source, options) { + options = 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) - if (source.hasOwnProperty('title') && source.title === undefined) { + + if (source.title === undefined) { delete source.title; } @@ -759,9 +766,10 @@ function merge(target, source) { } for (var key in source[attrKey]) { - if (source[attrKey].hasOwnProperty(key) && source[attrKey][key] === undefined) { - if (includes(booleanHtmlAttributes, key)) { + if (key in source[attrKey] && source[attrKey][key] === undefined) { + if (includes(booleanHtmlAttributes, key) && !warningShown) { warn('VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details'); + warningShown = true; } delete source[attrKey][key]; @@ -775,10 +783,8 @@ function merge(target, source) { }); } -function getComponentMetaInfo() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var component = arguments.length > 1 ? arguments[1] : undefined; - return getComponentOption(options, component, defaultInfo); +function getComponentMetaInfo(options, component) { + return getComponentOption(options || {}, component, defaultInfo); } /** * Returns the `opts.option` $option value of the given `opts.component`. @@ -795,26 +801,26 @@ function getComponentMetaInfo() { * @return {Object} result - final aggregated result */ -function getComponentOption() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var component = arguments.length > 1 ? arguments[1] : undefined; - var result = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - var keyName = options.keyName; - var $options = component.$options, - $children = component.$children; +function getComponentOption(options, component, result) { + result = result || {}; if (component._inactive) { return result; - } // only collect option data if it exists + } + options = options || {}; + var _options = options, + keyName = _options.keyName; + var $metaInfo = component.$metaInfo, + $options = component.$options, + $children = component.$children; // only collect option data if it exists if ($options[keyName]) { - var data = $options[keyName]; // 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 - + // if $metaInfo exists then [keyName] was defined as a function + // and set to the computed prop $metaInfo in the mixin + // using the computed prop should be a small performance increase + // because Vue caches those internally + var data = $metaInfo || $options[keyName]; // ignore data if its not an object, then we keep our previous result if (!isObject(data)) { return result; @@ -840,63 +846,9 @@ function getComponentOption() { return result; } -/** - * Returns the correct meta info for the given component - * (child components will overwrite parent meta info) - * - * @param {Object} component - the Vue instance to get meta info from - * @return {Object} - returned meta info - */ - -function getMetaInfo() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var info = arguments.length > 1 ? arguments[1] : undefined; - var escapeSequences = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; - var component = arguments.length > 3 ? arguments[3] : undefined; - var tagIDKeyName = options.tagIDKeyName; // 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 - - - 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 - // as the other tags - - - if (info.base) { - info.base = Object.keys(info.base).length ? [info.base] : []; - } - - if (info.meta) { - // remove meta items with duplicate vmid's - info.meta = info.meta.filter(function (metaItem, index, arr) { - var hasVmid = metaItem.hasOwnProperty(tagIDKeyName); - - if (!hasVmid) { - return true; - } - - var isFirstItemForVmid = index === findIndex(arr, function (item) { - return item[tagIDKeyName] === metaItem[tagIDKeyName]; - }); - return isFirstItemForVmid; - }); // apply templates if needed - - info.meta.forEach(function (metaObject) { - return applyTemplate(options, metaObject); - }); - } - - return escapeMetaInfo(options, info, escapeSequences); -} - +var querySelector = function querySelector(arg, el) { + return (el || document).querySelectorAll(arg); +}; function getTag(tags, tag) { if (!tags[tag]) { tags[tag] = document.getElementsByTagName(tag)[0]; @@ -909,12 +861,12 @@ function getElementsKey(_ref) { pbody = _ref.pbody; return body ? 'body' : pbody ? 'pbody' : 'head'; } -function queryElements(parentNode, _ref2) { +function queryElements(parentNode, _ref2, attributes) { var appId = _ref2.appId, attribute = _ref2.attribute, type = _ref2.type, tagIDKeyName = _ref2.tagIDKeyName; - var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + attributes = attributes || {}; var queries = ["".concat(type, "[").concat(attribute, "=\"").concat(appId, "\"]"), "".concat(type, "[data-").concat(tagIDKeyName, "]")].map(function (query) { for (var key in attributes) { var val = attributes[key]; @@ -924,13 +876,21 @@ function queryElements(parentNode, _ref2) { return query; }); - return toArray(parentNode.querySelectorAll(queries.join(', '))); + return toArray(querySelector(queries.join(', '), parentNode)); +} +function removeElementsByAppId(_ref3, appId) { + var attribute = _ref3.attribute; + toArray(querySelector("[".concat(attribute, "=\"").concat(appId, "\"]"))).map(function (el) { + return el.remove(); + }); +} +function removeAttribute(el, attributeName) { + el.removeAttribute(attributeName); } var callbacks = []; -function isDOMComplete() { - var d = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document; - return d.readyState === 'complete'; +function isDOMComplete(d) { + return (d || document).readyState === 'complete'; } function addCallback(query, callback) { if (arguments.length === 1) { @@ -972,16 +932,16 @@ function addListeners() { }; } function applyCallbacks(matchElement) { - callbacks.forEach(function (_ref2) { - var _ref3 = _slicedToArray(_ref2, 2), - query = _ref3[0], - callback = _ref3[1]; - + callbacks.forEach(function (args) { + // do not use destructuring for args, it increases transpiled size + // due to var checks while we are guaranteed the structure of the cb + var query = args[0]; + var callback = args[1]; var selector = "".concat(query, "[onload=\"this.__vm_l=1\"]"); var elements = []; if (!matchElement) { - elements = toArray(document.querySelectorAll(selector)); + elements = toArray(querySelector(selector)); } if (matchElement && matchElement.matches(selector)) { @@ -1007,7 +967,7 @@ function applyCallbacks(matchElement) { * will fail isEqualNode on the client */ - element.removeAttribute('onload'); + removeAttribute(element, 'onload'); callback(element); }; /* IE9 doesnt seem to load scripts synchronously, @@ -1032,6 +992,9 @@ function applyCallbacks(matchElement) { }); } +// instead of adding it to the html + +var attributeMap = {}; /** * Updates the document's html tag attributes * @@ -1039,43 +1002,62 @@ function applyCallbacks(matchElement) { * @param {HTMLElement} tag - the HTMLElement tag to update with new attrs */ -function updateAttribute() { - var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, +function updateAttribute(appId, options, type, attrs, tag) { + var _ref = options || {}, attribute = _ref.attribute; - var attrs = arguments.length > 1 ? arguments[1] : undefined; - var tag = arguments.length > 2 ? arguments[2] : undefined; var vueMetaAttrString = tag.getAttribute(attribute); - var vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : []; - var toRemove = toArray(vueMetaAttrs); - var keepIndexes = []; - for (var attr in attrs) { - if (attrs.hasOwnProperty(attr)) { - var value = includes(booleanHtmlAttributes, attr) ? '' : isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr]; - tag.setAttribute(attr, value || ''); + if (vueMetaAttrString) { + attributeMap[type] = JSON.parse(decodeURI(vueMetaAttrString)); + removeAttribute(tag, attribute); + } - if (!includes(vueMetaAttrs, attr)) { - vueMetaAttrs.push(attr); - } // filter below wont ever check -1 + var data = attributeMap[type] || {}; + var toUpdate = []; // remove attributes from the map + // which have been removed for this appId + for (var attr in data) { + if (data[attr] && appId in data[attr]) { + toUpdate.push(attr); - keepIndexes.push(toRemove.indexOf(attr)); + if (!attrs[attr]) { + delete data[attr][appId]; + } } } - var removedAttributesCount = toRemove.filter(function (el, index) { - return !includes(keepIndexes, index); - }).reduce(function (acc, attr) { - tag.removeAttribute(attr); - return acc + 1; - }, 0); + for (var _attr in attrs) { + var attrData = data[_attr]; - if (vueMetaAttrs.length === removedAttributesCount) { - tag.removeAttribute(attribute); - } else { - tag.setAttribute(attribute, vueMetaAttrs.sort().join(',')); + if (!attrData || attrData[appId] !== attrs[_attr]) { + toUpdate.push(_attr); + + if (attrs[_attr]) { + data[_attr] = data[_attr] || {}; + data[_attr][appId] = attrs[_attr]; + } + } } + + for (var _i = 0, _toUpdate = toUpdate; _i < _toUpdate.length; _i++) { + var _attr2 = _toUpdate[_i]; + var _attrData = data[_attr2]; + var attrValues = []; + + for (var _appId in _attrData) { + Array.prototype.push.apply(attrValues, [].concat(_attrData[_appId])); + } + + if (attrValues.length) { + var attrValue = includes(booleanHtmlAttributes, _attr2) && attrValues.some(Boolean) ? '' : attrValues.filter(Boolean).join(' '); + tag.setAttribute(_attr2, attrValue); + } else { + removeAttribute(tag, _attr2); + } + } + + attributeMap[type] = data; } /** @@ -1100,14 +1082,11 @@ function updateTitle(title) { * @return {Object} - a representation of what tags changed */ -function updateTag(appId) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var type = arguments.length > 2 ? arguments[2] : undefined; - var tags = arguments.length > 3 ? arguments[3] : undefined; - var head = arguments.length > 4 ? arguments[4] : undefined; - var body = arguments.length > 5 ? arguments[5] : undefined; - var attribute = options.attribute, - tagIDKeyName = options.tagIDKeyName; +function updateTag(appId, options, type, tags, head, body) { + var _ref = options || {}, + attribute = _ref.attribute, + tagIDKeyName = _ref.tagIDKeyName; + var dataAttributes = commonDataAttributes.slice(); dataAttributes.push(tagIDKeyName); var newElements = []; @@ -1147,21 +1126,20 @@ function updateTag(appId) { var newElement = document.createElement(type); newElement.setAttribute(attribute, appId); - - var _loop = function _loop(attr) { + Object.keys(tag).forEach(function (attr) { /* istanbul ignore next */ - if (!tag.hasOwnProperty(attr) || includes(tagProperties, attr)) { - return "continue"; + if (includes(tagProperties, attr)) { + return; } if (attr === 'innerHTML') { newElement.innerHTML = tag.innerHTML; - return "continue"; + return; } if (attr === 'json') { newElement.innerHTML = JSON.stringify(tag.json); - return "continue"; + return; } if (attr === 'cssText') { @@ -1172,7 +1150,7 @@ function updateTag(appId) { newElement.appendChild(document.createTextNode(tag.cssText)); } - return "continue"; + return; } if (attr === 'callback') { @@ -1180,7 +1158,7 @@ function updateTag(appId) { return tag[attr](newElement); }; - return "continue"; + return; } var _attr = includes(dataAttributes, attr) ? "data-".concat(attr) : attr; @@ -1188,19 +1166,12 @@ function updateTag(appId) { var isBooleanAttribute = includes(booleanHtmlAttributes, attr); if (isBooleanAttribute && !tag[attr]) { - return "continue"; + return; } var value = isBooleanAttribute ? '' : tag[attr]; newElement.setAttribute(_attr, value); - }; - - for (var attr in tag) { - var _ret = _loop(attr); - - if (_ret === "continue") continue; - } - + }); var oldElements = currentElements[getElementsKey(tag)]; // Remove a duplicate tag from domTagstoRemove, so it isn't cleared. var indexToDelete; @@ -1251,18 +1222,18 @@ function updateTag(appId) { * @param {Object} newInfo - the meta info to update to */ -function updateClientMetaInfo(appId) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var newInfo = arguments.length > 2 ? arguments[2] : undefined; - var ssrAttribute = options.ssrAttribute, - ssrAppId = options.ssrAppId; // only cache tags for current update +function updateClientMetaInfo(appId, options, newInfo) { + options = options || {}; + var _options = options, + ssrAttribute = _options.ssrAttribute, + ssrAppId = _options.ssrAppId; // only cache tags for current update var tags = {}; var htmlTag = getTag(tags, 'html'); // if this is a server render, then dont update 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 + removeAttribute(htmlTag, ssrAttribute); // add load callbacks if the var addLoadListeners = false; tagsSupportingOnload.forEach(function (type) { @@ -1279,8 +1250,8 @@ function updateClientMetaInfo(appId) { } // initialize tracked changes - var addedTags = {}; - var removedTags = {}; + var tagsAdded = {}; + var tagsRemoved = {}; for (var type in newInfo) { // ignore these @@ -1296,7 +1267,7 @@ function updateClientMetaInfo(appId) { if (includes(metaInfoAttributeKeys, type)) { var tagName = type.substr(0, 4); - updateAttribute(options, newInfo[type], getTag(tags, tagName)); + updateAttribute(appId, options, type, newInfo[type], getTag(tags, tagName)); continue; } // tags should always be an array, ignore if it isnt @@ -1310,46 +1281,189 @@ function updateClientMetaInfo(appId) { newTags = _updateTag.newTags; if (newTags.length) { - addedTags[type] = newTags; - removedTags[type] = oldTags; + tagsAdded[type] = newTags; + tagsRemoved[type] = oldTags; } } return { - addedTags: addedTags, - removedTags: removedTags + tagsAdded: tagsAdded, + tagsRemoved: tagsRemoved }; } -function _refresh() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; +var appsMetaInfo; +function addApp(rootVm, appId, options) { + return { + set: function set(metaInfo) { + return setMetaInfo(rootVm, appId, options, metaInfo); + }, + remove: function remove() { + return removeMetaInfo(rootVm, appId, options); + } + }; +} +function setMetaInfo(rootVm, appId, options, metaInfo) { + // if a vm exists _and_ its mounted then immediately update + if (rootVm && rootVm.$el) { + return updateClientMetaInfo(appId, options, metaInfo); + } // store for later, the info + // will be set on the first refresh - /** - * When called, will update the current meta info with new meta info. - * Useful when updating meta info as the result of an asynchronous - * action that resolves after the initial render takes place. - * - * Credit to [Sébastien Chopin](https://github.com/Atinux) for the suggestion - * to implement this method. - * - * @return {Object} - new meta info - */ - return function refresh() { - // collect & aggregate all metaInfo $options - var rawInfo = getComponentMetaInfo(options, this.$root); - var metaInfo = getMetaInfo(options, rawInfo, clientSequences, this.$root); - 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); + appsMetaInfo = appsMetaInfo || {}; + appsMetaInfo[appId] = metaInfo; +} +function removeMetaInfo(rootVm, appId, options) { + if (rootVm && rootVm.$el) { + var tags = {}; + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = metaInfoAttributeKeys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var type = _step.value; + var tagName = type.substr(0, 4); + updateAttribute(appId, options, type, {}, getTag(tags, tagName)); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return != null) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } } - return { - vm: this, - metaInfo: metaInfo, - tags: tags + return removeElementsByAppId(options, appId); + } + + if (appsMetaInfo[appId]) { + delete appsMetaInfo[appId]; + clearAppsMetaInfo(); + } +} +function getAppsMetaInfo() { + return appsMetaInfo; +} +function clearAppsMetaInfo(force) { + if (force || !Object.keys(appsMetaInfo).length) { + appsMetaInfo = undefined; + } +} + +/** + * Returns the correct meta info for the given component + * (child components will overwrite parent meta info) + * + * @param {Object} component - the Vue instance to get meta info from + * @return {Object} - returned meta info + */ + +function getMetaInfo(options, info, escapeSequences, component) { + options = options || {}; + escapeSequences = escapeSequences || []; + var _options = options, + tagIDKeyName = _options.tagIDKeyName; // 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 + + + 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 + // as the other tags + + + if (info.base) { + info.base = Object.keys(info.base).length ? [info.base] : []; + } + + if (info.meta) { + // remove meta items with duplicate vmid's + info.meta = info.meta.filter(function (metaItem, index, arr) { + var hasVmid = !!metaItem[tagIDKeyName]; + + if (!hasVmid) { + return true; + } + + var isFirstItemForVmid = index === findIndex(arr, function (item) { + return item[tagIDKeyName] === metaItem[tagIDKeyName]; + }); + return isFirstItemForVmid; + }); // apply templates if needed + + info.meta.forEach(function (metaObject) { + return applyTemplate(options, metaObject); + }); + } + + return escapeMetaInfo(options, info, escapeSequences); +} + +/** + * When called, will update the current meta info with new meta info. + * Useful when updating meta info as the result of an asynchronous + * action that resolves after the initial render takes place. + * + * Credit to [Sébastien Chopin](https://github.com/Atinux) for the suggestion + * to implement this method. + * + * @return {Object} - new meta info + */ + +function refresh(rootVm, options) { + options = options || {}; // make sure vue-meta was initiated + + if (!rootVm[rootConfigKey]) { + showWarningNotSupported(); + return {}; + } // collect & aggregate all metaInfo $options + + + var rawInfo = getComponentMetaInfo(options, rootVm); + var metaInfo = getMetaInfo(options, rawInfo, clientSequences, rootVm); + var appId = rootVm[rootConfigKey].appId; + var tags = updateClientMetaInfo(appId, options, metaInfo); // emit "event" with new info + + if (tags && isFunction(metaInfo.changed)) { + metaInfo.changed(metaInfo, tags.tagsAdded, tags.tagsRemoved); + tags = { + addedTags: tags.tagsAdded, + removedTags: tags.tagsRemoved }; + } + + var appsMetaInfo = getAppsMetaInfo(); + + if (appsMetaInfo) { + for (var additionalAppId in appsMetaInfo) { + updateClientMetaInfo(additionalAppId, options, appsMetaInfo[additionalAppId]); + delete appsMetaInfo[additionalAppId]; + } + + clearAppsMetaInfo(true); + } + + return { + vm: rootVm, + metaInfo: metaInfo, + // eslint-disable-line object-shorthand + tags: tags }; } @@ -1361,37 +1475,36 @@ function _refresh() { * @return {Object} - the attribute generator */ -function attributeGenerator() { - var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, +function attributeGenerator(options, type, data, addSrrAttribute) { + var _ref = options || {}, attribute = _ref.attribute, ssrAttribute = _ref.ssrAttribute; - var type = arguments.length > 1 ? arguments[1] : undefined; - var data = arguments.length > 2 ? arguments[2] : undefined; - return { - text: function text(addSrrAttribute) { - var attributeStr = ''; - var watchedAttrs = []; + var attributeStr = ''; - for (var attr in data) { - if (data.hasOwnProperty(attr)) { - watchedAttrs.push(attr); - attributeStr += isUndefined(data[attr]) || booleanHtmlAttributes.includes(attr) ? attr : "".concat(attr, "=\"").concat(isArray(data[attr]) ? data[attr].join(' ') : data[attr], "\""); - attributeStr += ' '; - } - } + for (var attr in data) { + var attrData = data[attr]; + var attrValues = []; - if (attributeStr) { - attributeStr += "".concat(attribute, "=\"").concat(watchedAttrs.sort().join(','), "\""); - } - - if (type === 'htmlAttrs' && addSrrAttribute) { - return "".concat(ssrAttribute).concat(attributeStr ? ' ' : '').concat(attributeStr); - } - - return attributeStr; + for (var appId in attrData) { + attrValues.push.apply(attrValues, _toConsumableArray([].concat(attrData[appId]))); } - }; + + if (attrValues.length) { + attributeStr += booleanHtmlAttributes.includes(attr) && attrValues.some(Boolean) ? "".concat(attr) : "".concat(attr, "=\"").concat(attrValues.join(' '), "\""); + attributeStr += ' '; + } + } + + if (attributeStr) { + attributeStr += "".concat(attribute, "=\"").concat(encodeURI(JSON.stringify(data)), "\""); + } + + if (type === 'htmlAttrs' && addSrrAttribute) { + return "".concat(ssrAttribute).concat(attributeStr ? ' ' : '').concat(attributeStr); + } + + return attributeStr; } /** @@ -1401,21 +1514,15 @@ function attributeGenerator() { * @param {String} data - the title text * @return {Object} - the title generator */ -function titleGenerator() { - var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, - attribute = _ref.attribute; +function titleGenerator(options, type, data, generatorOptions) { + var _ref = generatorOptions || {}, + ln = _ref.ln; - var type = arguments.length > 1 ? arguments[1] : undefined; - var data = arguments.length > 2 ? arguments[2] : undefined; - return { - text: function text() { - if (!data) { - return ''; - } + if (!data) { + return ''; + } - return "<".concat(type, ">").concat(data, ""); - } - }; + return "<".concat(type, ">").concat(data, "").concat(ln ? '\n' : ''); } /** @@ -1426,90 +1533,88 @@ function titleGenerator() { * @return {Object} - the tag generator */ -function tagGenerator() { - var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, +function tagGenerator(options, type, tags, generatorOptions) { + var _ref = options || {}, ssrAppId = _ref.ssrAppId, attribute = _ref.attribute, tagIDKeyName = _ref.tagIDKeyName; - var type = arguments.length > 1 ? arguments[1] : undefined; - var tags = arguments.length > 2 ? arguments[2] : undefined; + var _ref2 = generatorOptions || {}, + appId = _ref2.appId, + _ref2$body = _ref2.body, + body = _ref2$body === void 0 ? false : _ref2$body, + _ref2$pbody = _ref2.pbody, + pbody = _ref2$pbody === void 0 ? false : _ref2$pbody, + _ref2$ln = _ref2.ln, + ln = _ref2$ln === void 0 ? false : _ref2$ln; + var dataAttributes = [tagIDKeyName].concat(_toConsumableArray(commonDataAttributes)); - return { - text: function text() { - var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, - _ref2$body = _ref2.body, - body = _ref2$body === void 0 ? false : _ref2$body, - _ref2$pbody = _ref2.pbody, - pbody = _ref2$pbody === void 0 ? false : _ref2$pbody; - if (!tags || !tags.length) { - return ''; - } // build a string containing all tags of this type + if (!tags || !tags.length) { + return ''; + } // build a string containing all tags of this type - return tags.reduce(function (tagsStr, tag) { - if (tag.skip) { - return tagsStr; - } - - var tagKeys = Object.keys(tag); - - if (tagKeys.length === 0) { - return tagsStr; // Bail on empty tag object - } - - if (Boolean(tag.body) !== body || Boolean(tag.pbody) !== pbody) { - return tagsStr; - } - - var attrs = tag.once ? '' : " ".concat(attribute, "=\"").concat(ssrAppId, "\""); // build a string containing all attributes of this tag - - for (var attr in tag) { - // these attributes are treated as children on the tag - if (tagAttributeAsInnerContent.includes(attr) || tagProperties.includes(attr)) { - continue; - } - - if (attr === 'callback') { - attrs += " onload=\"this.__vm_l=1\""; - continue; - } // these form the attribute list for this tag - - - var prefix = ''; - - if (dataAttributes.includes(attr)) { - prefix = 'data-'; - } - - var isBooleanAttr = !prefix && booleanHtmlAttributes.includes(attr); - - if (isBooleanAttr && !tag[attr]) { - continue; - } - - attrs += " ".concat(prefix).concat(attr) + (isBooleanAttr ? '' : "=\"".concat(tag[attr], "\"")); - } - - var json = ''; - - if (tag.json) { - json = JSON.stringify(tag.json); - } // grab child content from one of these attributes, if possible - - - var 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); // the final string for this specific tag - - return "".concat(tagsStr, "<").concat(type).concat(attrs).concat(!hasContent && hasEndTag ? '/' : '', ">") + (hasContent ? "".concat(content, "") : ''); - }, ''); + return tags.reduce(function (tagsStr, tag) { + if (tag.skip) { + return tagsStr; } - }; + + var tagKeys = Object.keys(tag); + + if (tagKeys.length === 0) { + return tagsStr; // Bail on empty tag object + } + + if (Boolean(tag.body) !== body || Boolean(tag.pbody) !== pbody) { + return tagsStr; + } + + var attrs = tag.once ? '' : " ".concat(attribute, "=\"").concat(appId || ssrAppId, "\""); // build a string containing all attributes of this tag + + for (var attr in tag) { + // these attributes are treated as children on the tag + if (tagAttributeAsInnerContent.includes(attr) || tagProperties.includes(attr)) { + continue; + } + + if (attr === 'callback') { + attrs += " onload=\"this.__vm_l=1\""; + continue; + } // these form the attribute list for this tag + + + var prefix = ''; + + if (dataAttributes.includes(attr)) { + prefix = 'data-'; + } + + var isBooleanAttr = !prefix && booleanHtmlAttributes.includes(attr); + + if (isBooleanAttr && !tag[attr]) { + continue; + } + + attrs += " ".concat(prefix).concat(attr) + (isBooleanAttr ? '' : "=\"".concat(tag[attr], "\"")); + } + + var json = ''; + + if (tag.json) { + json = JSON.stringify(tag.json); + } // grab child content from one of these attributes, if possible + + + var 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); // the final string for this specific tag + + return "".concat(tagsStr, "<").concat(type).concat(attrs).concat(!hasContent && hasEndTag ? '/' : '', ">") + (hasContent ? "".concat(content, "") : '') + (ln ? '\n' : ''); + }, ''); } /** @@ -1520,78 +1625,175 @@ function tagGenerator() { * @return {Object} - the new injector */ -function generateServerInjector(options, newInfo) { - for (var type in defaultInfo) { +function generateServerInjector(options, metaInfo) { + var serverInjector = { + data: metaInfo, + extraData: undefined, + addInfo: function addInfo(appId, metaInfo) { + this.extraData = this.extraData || {}; + this.extraData[appId] = metaInfo; + }, + callInjectors: function callInjectors(opts) { + var m = this.injectors; // only call title for the head + + return (opts.body || opts.pbody ? '' : m.title.text(opts)) + m.meta.text(opts) + m.link.text(opts) + m.style.text(opts) + m.script.text(opts) + m.noscript.text(opts); + }, + injectors: { + head: function head(ln) { + return serverInjector.callInjectors({ + ln: ln + }); + }, + bodyPrepend: function bodyPrepend(ln) { + return serverInjector.callInjectors({ + ln: ln, + pbody: true + }); + }, + bodyAppend: function bodyAppend(ln) { + return serverInjector.callInjectors({ + ln: ln, + body: true + }); + } + } + }; + + var _loop = function _loop(type) { if (metaInfoOptionKeys.includes(type)) { - continue; + return "continue"; } - if (type === 'title') { - newInfo[type] = titleGenerator(options, type, newInfo[type]); - continue; - } + serverInjector.injectors[type] = { + text: function text(arg) { + if (type === 'title') { + return titleGenerator(options, type, serverInjector.data[type], arg); + } - if (metaInfoAttributeKeys.includes(type)) { - newInfo[type] = attributeGenerator(options, type, newInfo[type]); - continue; - } + if (metaInfoAttributeKeys.includes(type)) { + var attributeData = {}; + var data = serverInjector.data[type]; - newInfo[type] = tagGenerator(options, type, newInfo[type]); + if (data) { + for (var attr in data) { + attributeData[attr] = _defineProperty({}, options.ssrAppId, data[attr]); + } + } + + return attributeGenerator(options, type, attributeData, arg); + } + + var str = tagGenerator(options, type, serverInjector.data[type], arg); + + return str; + } + }; + }; + + for (var type in defaultInfo) { + var _ret = _loop(type); + + if (_ret === "continue") continue; } - return newInfo; + return serverInjector; } -function _inject() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; +/** + * Converts the state of the meta info object such that each item + * can be compiled to a tag string on the server + * + * @vm {Object} - Vue instance - ideally the root component + * @return {Object} - server meta info with `toString` methods + */ - /** - * Converts the state of the meta info object such that each item - * can be compiled to a tag string on the server - * - * @this {Object} - Vue instance - ideally the root component - * @return {Object} - server meta info with `toString` methods - */ - return function inject() { - // collect & aggregate all metaInfo $options - var rawInfo = getComponentMetaInfo(options, this.$root); - var metaInfo = getMetaInfo(options, rawInfo, serverSequences, this.$root); // generate server injectors +function inject(rootVm, options) { + // make sure vue-meta was initiated + if (!rootVm[rootConfigKey]) { + showWarningNotSupported(); + return {}; + } // collect & aggregate all metaInfo $options - generateServerInjector(options, metaInfo); - return metaInfo; - }; + + var rawInfo = getComponentMetaInfo(options, rootVm); + var metaInfo = getMetaInfo(options, rawInfo, serverSequences, rootVm); // generate server injector + + var serverInjector = generateServerInjector(options, metaInfo); // add meta info from additional apps + + var appsMetaInfo = getAppsMetaInfo(); + + if (appsMetaInfo) { + for (var additionalAppId in appsMetaInfo) { + serverInjector.addInfo(additionalAppId, appsMetaInfo[additionalAppId]); + delete appsMetaInfo[additionalAppId]; + } + + clearAppsMetaInfo(true); + } + + return serverInjector.injectors; } -function _$meta() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - - var _refresh$1 = _refresh(options); - - var _inject$1 = _inject(options); +function $meta(options) { + options = options || {}; /** * Returns an injector for server-side rendering. * @this {Object} - the Vue instance (a root component) * @return {Object} - injector */ + var $root = this.$root; + return { + 'getOptions': function getOptions$1() { + return getOptions(options); + }, + 'setOptions': function setOptions(newOptions) { + var refreshNavKey = 'refreshOnceOnNavigation'; - return function $meta() { - return { - getOptions: function getOptions$1() { - return getOptions(options); - }, - refresh: _refresh$1.bind(this), - inject: _inject$1.bind(this), - pause: pause.bind(this), - resume: resume.bind(this) - }; + if (newOptions && newOptions[refreshNavKey]) { + options.refreshOnceOnNavigation = !!newOptions[refreshNavKey]; + addNavGuards($root); + } + + var debounceWaitKey = 'debounceWait'; + + if (newOptions && debounceWaitKey in newOptions) { + var debounceWait = parseInt(newOptions[debounceWaitKey]); + + if (!isNaN(debounceWait)) { + options.debounceWait = debounceWait; + } + } + + var waitOnDestroyedKey = 'waitOnDestroyed'; + + if (newOptions && waitOnDestroyedKey in newOptions) { + options.waitOnDestroyed = !!newOptions[waitOnDestroyedKey]; + } + }, + 'refresh': function refresh$1() { + return refresh($root, options); + }, + 'inject': function inject$1() { + return inject($root, options) ; + }, + 'pause': function pause$1() { + return pause($root); + }, + 'resume': function resume$1() { + return resume($root); + }, + 'addApp': function addApp$1(appId) { + return addApp($root, appId, options); + } }; } -function generate(rawInfo) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var metaInfo = getMetaInfo(setOptions(options), rawInfo, serverSequences); - return generateServerInjector(options, metaInfo); +function generate(rawInfo, options) { + options = setOptions(options); + var metaInfo = getMetaInfo(options, rawInfo, serverSequences); + var serverInjector = generateServerInjector(options, metaInfo); + return serverInjector.injectors; } /** @@ -1599,24 +1801,28 @@ function generate(rawInfo) { * @param {Function} Vue - the Vue constructor. */ -function install(Vue) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - +function install(Vue, options) { if (Vue.__vuemeta_installed) { return; } Vue.__vuemeta_installed = true; options = setOptions(options); - Vue.prototype.$meta = _$meta(options); + + Vue.prototype.$meta = function () { + return $meta.call(this, options); + }; + Vue.mixin(createMixin(Vue, options)); } var index = { version: version, install: install, - hasMetaInfo: hasMetaInfo, - generate: generate + generate: function generate$1(metaInfo) { + return generate(metaInfo) ; + }, + hasMetaInfo: hasMetaInfo }; module.exports = index; diff --git a/dist/vue-meta.esm.browser.js b/dist/vue-meta.esm.browser.js index 0603c7b..3a6624a 100644 --- a/dist/vue-meta.esm.browser.js +++ b/dist/vue-meta.esm.browser.js @@ -1,49 +1,16 @@ /** - * vue-meta v2.2.2 + * vue-meta v2.3.0-beta.0 * (c) 2019 * - Declan de Wet * - Sébastien Chopin (@Atinux) - * - All the amazing contributors + * - Pim (@pimlie) + * - All the amazing contributors * @license MIT */ import deepmerge from 'deepmerge'; -var version = "2.2.2"; - -// store an id to keep track of DOM updates -var 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 - if (!vm.$root._vueMeta.initialized && (vm.$root._vueMeta.initializing || hookName === 'watcher')) { - vm.$root._vueMeta.initialized = null; - } - - if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) { - // batch potential DOM updates to prevent extraneous re-rendering - batchUpdate(function () { - return vm.$meta().refresh(); - }); - } -} -/** - * Performs a batched update. - * - * @param {(null|Number)} id - the ID of this update - * @param {Function} callback - the update to perform - * @return {Number} id - a new ID - */ - -function batchUpdate(callback) { - var timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 10; - clearTimeout(batchId); - batchId = setTimeout(function () { - callback(); - }, timeout); - return batchId; -} +var version = "2.3.0-beta.0"; function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { @@ -59,44 +26,6 @@ function _typeof(obj) { return _typeof(obj); } -function _slicedToArray(arr, i) { - return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); -} - -function _arrayWithHoles(arr) { - if (Array.isArray(arr)) return arr; -} - -function _iterableToArrayLimit(arr, i) { - var _arr = []; - var _n = true; - var _d = false; - var _e = undefined; - - try { - for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { - _arr.push(_s.value); - - if (i && _arr.length === i) break; - } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i["return"] != null) _i["return"](); - } finally { - if (_d) throw _e; - } - } - - return _arr; -} - -function _nonIterableRest() { - throw new TypeError("Invalid attempt to destructure non-iterable instance"); -} - /** * checks if passed argument is an array * @param {any} arg - the object to check @@ -121,56 +50,6 @@ function isString(arg) { return typeof arg === 'string'; } -function ensureIsArray(arg, key) { - if (!key || !isObject(arg)) { - return isArray(arg) ? arg : []; - } - - if (!isArray(arg[key])) { - arg[key] = []; - } - - return arg; -} -function ensuredPush(object, key, el) { - ensureIsArray(object, key); - object[key].push(el); -} - -function hasMetaInfo() { - var vm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 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 - -function inMetaInfoBranch() { - var vm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this; - return vm && !isUndefined(vm._vueMeta); -} - -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; - } - - vm.$root._vueMeta.navGuards = true; - var $router = vm.$root.$router; - var $meta = vm.$root.$meta(); - $router.beforeEach(function (to, from, next) { - $meta.pause(); - next(); - }); - $router.afterEach(function () { - var _$meta$resume = $meta.resume(), - metaInfo = _$meta$resume.metaInfo; - - if (metaInfo && metaInfo.afterNavigation && isFunction(metaInfo.afterNavigation)) { - metaInfo.afterNavigation(metaInfo); - } - }); -} - function hasGlobalWindowFn() { try { return !isUndefined(window); @@ -191,177 +70,13 @@ function warn(str) { console.warn(str); } +var showWarningNotSupportedInBrowserBundle = function showWarningNotSupportedInBrowserBundle(method) { + return warn("".concat(method, " is not supported in browser builds")); +}; var showWarningNotSupported = function showWarningNotSupported() { return warn('This vue app/component has no vue-meta configuration'); }; -var appId = 1; -function createMixin(Vue, options) { - // for which Vue lifecycle hooks should the metaInfo be refreshed - var updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount']; // watch for client side component updates - - return { - beforeCreate: function beforeCreate() { - var _this = this; - - Object.defineProperty(this, '_hasMetaInfo', { - configurable: true, - get: function get() { - // Show deprecation warning once when devtools enabled - if (Vue.config.devtools && !this.$root._vueMeta.hasMetaInfoDeprecationWarningShown) { - 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 - // _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) { - return; - } - - if (!this.$root._vueMeta) { - this.$root._vueMeta = { - appId: appId - }; - appId++; - } // 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; - 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 - // 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.$watch('$metaInfo', function () { - _this.__metaInfo = undefined; - triggerUpdate(_this, 'watcher'); - }); - }); - } - } // 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 () { - // 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 = options.ssrAppId; - } - }); // we use the mounted hook here as on page load - - ensuredPush(this.$options, 'mounted', function () { - if (!_this.$root._vueMeta.initialized) { - // used in triggerUpdate to check if a change was triggered - // during initialization - _this.$root._vueMeta.initializing = true; // refresh meta in nextTick so all child components have loaded - - _this.$nextTick(function () { - var _this2 = this; - - var _this$$root$$meta$ref = this.$root.$meta().refresh(), - tags = _this$$root$$meta$ref.tags, - metaInfo = _this$$root$$meta$ref.metaInfo; // 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(_this2, 'initializing'); - }); - } - - this.$root._vueMeta.initialized = true; - 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 - - if (options.refreshOnceOnNavigation) { - addNavGuards(this); - } - } - } // do not trigger refresh on the server side - - - if (this.$isServer) { - return; - } // no need to add this hooks on server side - - - updateOnLifecycleHook.forEach(function (lifecycleHook) { - ensuredPush(_this.$options, lifecycleHook, function () { - return triggerUpdate(_this, lifecycleHook); - }); - }); - }, - // TODO: move back into beforeCreate when Vue issue is resolved - destroyed: function destroyed() { - var _this3 = this; - - // do not trigger refresh: - // - on the server side - // - when the component doesnt have a parent - // - doesnt have metaInfo defined - if (this.$isServer || !this.$parent || !hasMetaInfo(this)) { - return; - } // Wait that element is hidden before refreshing meta tags (to support animations) - - - var interval = setInterval(function () { - if (_this3.$el && _this3.$el.offsetParent !== null) { - return; - } - - clearInterval(interval); - triggerUpdate(_this3, 'destroyed'); - }, 50); - } - }; -} - /** * These are constant variables used throughout the application. */ @@ -380,10 +95,11 @@ var defaultInfo = { script: [], noscript: [], __dangerouslyDisableSanitizers: [], - __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. - + __dangerouslyDisableSanitizersByTagID: {} }; +var rootConfigKey = '_vueMeta'; // 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 // manage and which it should ignore. @@ -401,7 +117,11 @@ var metaTemplateKeyName = 'template'; // This is the key name for the content-ho var contentKeyName = 'content'; // The id used for the ssr app -var ssrAppId = 'ssr'; +var ssrAppId = 'ssr'; // How long meta update + +var debounceWait = 10; // How long meta update + +var waitOnDestroyed = true; var defaultOptions = { keyName: keyName, attribute: attribute, @@ -409,14 +129,18 @@ var defaultOptions = { tagIDKeyName: tagIDKeyName, contentKeyName: contentKeyName, metaTemplateKeyName: metaTemplateKeyName, - ssrAppId: ssrAppId // List of metaInfo property keys which are configuration options (and dont generate html) + waitOnDestroyed: waitOnDestroyed, + debounceWait: debounceWait, + ssrAppId: ssrAppId +}; // might be a bit ugly, but minimizes the browser bundles a bit -}; -var metaInfoOptionKeys = ['titleChunk', 'titleTemplate', 'changed', '__dangerouslyDisableSanitizers', '__dangerouslyDisableSanitizersByTagID']; // The metaInfo property keys which are used to disable escaping +var defaultInfoKeys = Object.keys(defaultInfo); // The metaInfo property keys which are used to disable escaping -var disableOptionKeys = ['__dangerouslyDisableSanitizers', '__dangerouslyDisableSanitizersByTagID']; // List of metaInfo property keys which only generates attributes and no tags +var disableOptionKeys = [defaultInfoKeys[12], defaultInfoKeys[13]]; // List of metaInfo property keys which are configuration options (and dont generate html) -var metaInfoAttributeKeys = ['htmlAttrs', 'headAttrs', 'bodyAttrs']; // HTML elements which support the onload event +var metaInfoOptionKeys = [defaultInfoKeys[1], defaultInfoKeys[2], 'changed'].concat(disableOptionKeys); // List of metaInfo property keys which only generates attributes and no tags + +var metaInfoAttributeKeys = [defaultInfoKeys[3], defaultInfoKeys[4], defaultInfoKeys[5]]; // HTML elements which support the onload event var tagsSupportingOnload = ['link', 'style', 'script']; // HTML elements which dont have a head tag (shortened to our needs) var tagProperties = ['once', 'template']; // Attributes which should be added with data- prefix @@ -425,17 +149,305 @@ var commonDataAttributes = ['body', 'pbody']; // from: https://github.com/kangax 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']; -function setOptions(options) { - // combine options - options = isObject(options) ? options : {}; +var batchId = null; +function triggerUpdate(_ref, rootVm, hookName) { + var debounceWait = _ref.debounceWait; - for (var key in defaultOptions) { - if (!options[key]) { - options[key] = defaultOptions[key]; - } + // 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 + if (!rootVm[rootConfigKey].initialized && (rootVm[rootConfigKey].initializing || hookName === 'watcher')) { + rootVm[rootConfigKey].initialized = null; } - return options; + if (rootVm[rootConfigKey].initialized && !rootVm[rootConfigKey].pausing) { + // batch potential DOM updates to prevent extraneous re-rendering + batchUpdate(function () { + return void rootVm.$meta().refresh(); + }, debounceWait); + } +} +/** + * Performs a batched update. + * + * @param {(null|Number)} id - the ID of this update + * @param {Function} callback - the update to perform + * @return {Number} id - a new ID + */ + +function batchUpdate(callback, timeout) { + timeout = timeout === undefined ? 10 : timeout; + + if (!timeout) { + callback(); + return; + } + + clearTimeout(batchId); + batchId = setTimeout(function () { + callback(); + }, timeout); + return batchId; +} + +function ensureIsArray(arg, key) { + if (!key || !isObject(arg)) { + return isArray(arg) ? arg : []; + } + + if (!isArray(arg[key])) { + arg[key] = []; + } + + return arg; +} +function ensuredPush(object, key, el) { + ensureIsArray(object, key); + object[key].push(el); +} + +function hasMetaInfo(vm) { + vm = vm || this; + return vm && (vm[rootConfigKey] === true || isObject(vm[rootConfigKey])); +} // a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has + +function inMetaInfoBranch(vm) { + vm = vm || this; + return vm && !isUndefined(vm[rootConfigKey]); +} + +function pause(rootVm, refresh) { + rootVm[rootConfigKey].pausing = true; + return function () { + return resume(rootVm, refresh); + }; +} +function resume(rootVm, refresh) { + rootVm[rootConfigKey].pausing = false; + + if (refresh || refresh === undefined) { + return rootVm.$meta().refresh(); + } +} + +function addNavGuards(rootVm) { + var router = rootVm.$router; // return when nav guards already added or no router exists + + if (rootVm[rootConfigKey].navGuards || !router) { + /* istanbul ignore next */ + return; + } + + rootVm[rootConfigKey].navGuards = true; + router.beforeEach(function (to, from, next) { + pause(rootVm); + next(); + }); + router.afterEach(function () { + var _resume = resume(rootVm), + metaInfo = _resume.metaInfo; + + if (metaInfo && isFunction(metaInfo.afterNavigation)) { + metaInfo.afterNavigation(metaInfo); + } + }); +} + +var appId = 1; +function createMixin(Vue, options) { + // for which Vue lifecycle hooks should the metaInfo be refreshed + var updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount']; // watch for client side component updates + + return { + beforeCreate: function beforeCreate() { + var rootKey = '$root'; + var $root = this[rootKey]; + var $options = this.$options; + Object.defineProperty(this, '_hasMetaInfo', { + configurable: true, + get: function get() { + // Show deprecation warning once when devtools enabled + if (Vue.config.devtools && !$root[rootConfigKey].deprecationWarningShown) { + warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead'); + $root[rootConfigKey].deprecationWarningShown = true; + } + + 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($options[options.keyName]) || $options[options.keyName] === null) { + return; + } + + if (!$root[rootConfigKey]) { + $root[rootConfigKey] = { + appId: appId + }; + appId++; + } // 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[rootConfigKey]) { + this[rootConfigKey] = true; + var parent = this.$parent; + + while (parent && parent !== $root) { + if (isUndefined(parent[rootConfigKey])) { + parent[rootConfigKey] = false; + } + + parent = parent.$parent; + } + } // coerce function-style metaInfo to a computed prop so we can observe + // it on creation + + + if (isFunction($options[options.keyName])) { + $options.computed = $options.computed || {}; + $options.computed.$metaInfo = $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($options, 'created', function () { + this.$watch('$metaInfo', function () { + triggerUpdate(options, this[rootKey], 'watcher'); + }); + }); + } + } // 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($root[rootConfigKey].initialized)) { + $root[rootConfigKey].initialized = this.$isServer; + + if (!$root[rootConfigKey].initialized) { + ensuredPush($options, 'beforeMount', function () { + var $root = this[rootKey]; // if this Vue-app was server rendered, set the appId to 'ssr' + // only one SSR app per page is supported + + if ($root.$el && $root.$el.nodeType === 1 && $root.$el.hasAttribute('data-server-rendered')) { + $root[rootConfigKey].appId = options.ssrAppId; + } + }); // we use the mounted hook here as on page load + + ensuredPush($options, 'mounted', function () { + var $root = this[rootKey]; + + if (!$root[rootConfigKey].initialized) { + // used in triggerUpdate to check if a change was triggered + // during initialization + $root[rootConfigKey].initializing = true; // refresh meta in nextTick so all child components have loaded + + this.$nextTick(function () { + var _$root$$meta$refresh = $root.$meta().refresh(), + tags = _$root$$meta$refresh.tags, + metaInfo = _$root$$meta$refresh.metaInfo; // 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 && $root[rootConfigKey].initialized === null) { + this.$nextTick(function () { + return triggerUpdate(options, $root, 'init'); + }); + } + + $root[rootConfigKey].initialized = true; + delete $root[rootConfigKey].initializing; // add the navigation guards if they havent been added yet + // they are needed for the afterNavigation callback + + if (!options.refreshOnceOnNavigation && metaInfo.afterNavigation) { + addNavGuards($root); + } + }); + } + }); // add the navigation guards if requested + + if (options.refreshOnceOnNavigation) { + addNavGuards($root); + } + } + } // do not trigger refresh on the server side + + + if (this.$isServer) { + return; + } // no need to add this hooks on server side + + + updateOnLifecycleHook.forEach(function (lifecycleHook) { + ensuredPush($options, lifecycleHook, function () { + triggerUpdate(options, this[rootKey], lifecycleHook); + }); + }); + }, + // TODO: move back into beforeCreate when Vue issue is resolved + destroyed: function destroyed() { + var _this = this; + + // do not trigger refresh: + // - when user configured to not wait for transitions on destroyed + // - when the component doesnt have a parent + // - doesnt have metaInfo defined + if (!this.$parent || !hasMetaInfo(this)) { + return; + } + + this.$nextTick(function () { + if (!options.waitOnDestroyed || !_this.$el || !_this.$el.offsetParent) { + triggerUpdate(options, _this.$root, 'destroyed'); + return; + } // Wait that element is hidden before refreshing meta tags (to support animations) + + + var interval = setInterval(function () { + if (_this.$el && _this.$el.offsetParent !== null) { + /* istanbul ignore next line */ + return; + } + + clearInterval(interval); + triggerUpdate(options, _this.$root, 'destroyed'); + }, 50); + }); + } + }; +} + +function setOptions(options) { + // combine options + options = isObject(options) ? options : {}; // The options are set like this so they can + // be minified by terser while keeping the + // user api intact + // terser --mangle-properties keep_quoted=strict + + /* eslint-disable dot-notation */ + + return { + keyName: options['keyName'] || defaultOptions.keyName, + attribute: options['attribute'] || defaultOptions.attribute, + ssrAttribute: options['ssrAttribute'] || defaultOptions.ssrAttribute, + tagIDKeyName: options['tagIDKeyName'] || defaultOptions.tagIDKeyName, + contentKeyName: options['contentKeyName'] || defaultOptions.contentKeyName, + metaTemplateKeyName: options['metaTemplateKeyName'] || defaultOptions.metaTemplateKeyName, + debounceWait: isUndefined(options['debounceWait']) ? defaultOptions.debounceWait : options['debounceWait'], + waitOnDestroyed: isUndefined(options['waitOnDestroyed']) ? defaultOptions.waitOnDestroyed : options['waitOnDestroyed'], + ssrAppId: options['ssrAppId'] || defaultOptions.ssrAppId, + refreshOnceOnNavigation: !!options['refreshOnceOnNavigation'] + }; + /* eslint-enable dot-notation */ } function getOptions(options) { var optionsCopy = {}; @@ -447,22 +459,6 @@ function getOptions(options) { return optionsCopy; } -function pause() { - var refresh = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - this.$root._vueMeta.paused = true; - return function () { - return resume(refresh); - }; -} -function resume() { - var refresh = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - this.$root._vueMeta.paused = false; - - if (refresh) { - return this.$root.$meta().refresh(); - } -} - /* * To reduce build size, this file provides simple polyfills without * overly excessive type checking and without modifying @@ -471,11 +467,11 @@ function resume() { * Also, only files in client/ & shared/ should use these functions * files in server/ still use normal js function */ -function findIndex(array, predicate) { +function findIndex(array, predicate, thisArg) { 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[2], array[idx], idx, array)) { + if (predicate.call(thisArg, array[idx], idx, array)) { return idx; } } @@ -483,7 +479,7 @@ function findIndex(array, predicate) { return -1; } - return array.findIndex(predicate, arguments[2]); + return array.findIndex(predicate, thisArg); } function toArray(arg) { if ( !Array.from) { @@ -522,10 +518,11 @@ function escape(info, options, escapeOptions, escapeKeys) { if (includes(metaInfoOptionKeys, key)) { escaped[key] = value; continue; - } + } // do not use destructuring for disableOptionKeys, it increases transpiled size + // due to var checks while we are guaranteed the structure of the cb - var _disableOptionKeys = _slicedToArray(disableOptionKeys, 1), - disableKey = _disableOptionKeys[0]; + + var disableKey = disableOptionKeys[0]; if (escapeOptions[disableKey] && includes(escapeOptions[disableKey], key)) { // this info[key] doesnt need to escaped if the option is listed in __dangerouslyDisableSanitizers @@ -572,16 +569,14 @@ function escape(info, options, escapeOptions, escapeKeys) { return escaped; } -function escapeMetaInfo(options, info) { - var escapeSequences = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; +function escapeMetaInfo(options, info, escapeSequences) { + escapeSequences = escapeSequences || []; // do not use destructuring for seq, it increases transpiled size + // due to var checks while we are guaranteed the structure of the cb + var escapeOptions = { doEscape: function doEscape(value) { - return escapeSequences.reduce(function (val, _ref) { - var _ref2 = _slicedToArray(_ref, 2), - v = _ref2[0], - r = _ref2[1]; - - return val.replace(v, r); + return escapeSequences.reduce(function (val, seq) { + return val.replace(seq[0], seq[1]); }, value); } }; @@ -618,10 +613,7 @@ function applyTemplate(_ref, headObject, template, chunk) { if (!template) { // cleanup faulty template properties - if (headObject.hasOwnProperty(metaTemplateKeyName)) { - delete headObject[metaTemplateKeyName]; - } - + delete headObject[metaTemplateKeyName]; return false; } @@ -667,7 +659,7 @@ function _arrayMerge(_ref, target, source) { // which means we keep the targetItem and ignore/remove the sourceItem - if (sourceItem.hasOwnProperty(contentKeyName) && sourceItem[contentKeyName] === undefined || sourceItem.hasOwnProperty('innerHTML') && sourceItem.innerHTML === undefined) { + if (contentKeyName in sourceItem && sourceItem[contentKeyName] === undefined || 'innerHTML' in sourceItem && sourceItem.innerHTML === undefined) { destination.push(targetItem); // remove current index from source array so its not concatenated to destination below source.splice(sourceIndex, 1); @@ -714,13 +706,13 @@ function _arrayMerge(_ref, target, source) { }); return destination.concat(source); } -function merge(target, source) { - var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - - // remove properties explicitly set to false so child components can +var warningShown = false; +function merge(target, source, options) { + options = 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) - if (source.hasOwnProperty('title') && source.title === undefined) { + + if (source.title === undefined) { delete source.title; } @@ -730,9 +722,10 @@ function merge(target, source) { } for (var key in source[attrKey]) { - if (source[attrKey].hasOwnProperty(key) && source[attrKey][key] === undefined) { - if (includes(booleanHtmlAttributes, key)) { + if (key in source[attrKey] && source[attrKey][key] === undefined) { + if (includes(booleanHtmlAttributes, key) && !warningShown) { warn('VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details'); + warningShown = true; } delete source[attrKey][key]; @@ -746,10 +739,8 @@ function merge(target, source) { }); } -function getComponentMetaInfo() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var component = arguments.length > 1 ? arguments[1] : undefined; - return getComponentOption(options, component, defaultInfo); +function getComponentMetaInfo(options, component) { + return getComponentOption(options || {}, component, defaultInfo); } /** * Returns the `opts.option` $option value of the given `opts.component`. @@ -766,26 +757,26 @@ function getComponentMetaInfo() { * @return {Object} result - final aggregated result */ -function getComponentOption() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var component = arguments.length > 1 ? arguments[1] : undefined; - var result = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - var keyName = options.keyName; - var $options = component.$options, - $children = component.$children; +function getComponentOption(options, component, result) { + result = result || {}; if (component._inactive) { return result; - } // only collect option data if it exists + } + options = options || {}; + var _options = options, + keyName = _options.keyName; + var $metaInfo = component.$metaInfo, + $options = component.$options, + $children = component.$children; // only collect option data if it exists if ($options[keyName]) { - var data = $options[keyName]; // 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 - + // if $metaInfo exists then [keyName] was defined as a function + // and set to the computed prop $metaInfo in the mixin + // using the computed prop should be a small performance increase + // because Vue caches those internally + var data = $metaInfo || $options[keyName]; // ignore data if its not an object, then we keep our previous result if (!isObject(data)) { return result; @@ -811,63 +802,9 @@ function getComponentOption() { return result; } -/** - * Returns the correct meta info for the given component - * (child components will overwrite parent meta info) - * - * @param {Object} component - the Vue instance to get meta info from - * @return {Object} - returned meta info - */ - -function getMetaInfo() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var info = arguments.length > 1 ? arguments[1] : undefined; - var escapeSequences = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; - var component = arguments.length > 3 ? arguments[3] : undefined; - var tagIDKeyName = options.tagIDKeyName; // 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 - - - 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 - // as the other tags - - - if (info.base) { - info.base = Object.keys(info.base).length ? [info.base] : []; - } - - if (info.meta) { - // remove meta items with duplicate vmid's - info.meta = info.meta.filter(function (metaItem, index, arr) { - var hasVmid = metaItem.hasOwnProperty(tagIDKeyName); - - if (!hasVmid) { - return true; - } - - var isFirstItemForVmid = index === findIndex(arr, function (item) { - return item[tagIDKeyName] === metaItem[tagIDKeyName]; - }); - return isFirstItemForVmid; - }); // apply templates if needed - - info.meta.forEach(function (metaObject) { - return applyTemplate(options, metaObject); - }); - } - - return escapeMetaInfo(options, info, escapeSequences); -} - +var querySelector = function querySelector(arg, el) { + return (el || document).querySelectorAll(arg); +}; function getTag(tags, tag) { if (!tags[tag]) { tags[tag] = document.getElementsByTagName(tag)[0]; @@ -880,12 +817,12 @@ function getElementsKey(_ref) { pbody = _ref.pbody; return body ? 'body' : pbody ? 'pbody' : 'head'; } -function queryElements(parentNode, _ref2) { +function queryElements(parentNode, _ref2, attributes) { var appId = _ref2.appId, attribute = _ref2.attribute, type = _ref2.type, tagIDKeyName = _ref2.tagIDKeyName; - var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + attributes = attributes || {}; var queries = ["".concat(type, "[").concat(attribute, "=\"").concat(appId, "\"]"), "".concat(type, "[data-").concat(tagIDKeyName, "]")].map(function (query) { for (var key in attributes) { var val = attributes[key]; @@ -895,13 +832,21 @@ function queryElements(parentNode, _ref2) { return query; }); - return toArray(parentNode.querySelectorAll(queries.join(', '))); + return toArray(querySelector(queries.join(', '), parentNode)); +} +function removeElementsByAppId(_ref3, appId) { + var attribute = _ref3.attribute; + toArray(querySelector("[".concat(attribute, "=\"").concat(appId, "\"]"))).map(function (el) { + return el.remove(); + }); +} +function removeAttribute(el, attributeName) { + el.removeAttribute(attributeName); } var callbacks = []; -function isDOMComplete() { - var d = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document; - return d.readyState === 'complete'; +function isDOMComplete(d) { + return (d || document).readyState === 'complete'; } function addCallback(query, callback) { if (arguments.length === 1) { @@ -943,16 +888,16 @@ function addListeners() { }; } function applyCallbacks(matchElement) { - callbacks.forEach(function (_ref2) { - var _ref3 = _slicedToArray(_ref2, 2), - query = _ref3[0], - callback = _ref3[1]; - + callbacks.forEach(function (args) { + // do not use destructuring for args, it increases transpiled size + // due to var checks while we are guaranteed the structure of the cb + var query = args[0]; + var callback = args[1]; var selector = "".concat(query, "[onload=\"this.__vm_l=1\"]"); var elements = []; if (!matchElement) { - elements = toArray(document.querySelectorAll(selector)); + elements = toArray(querySelector(selector)); } if (matchElement && matchElement.matches(selector)) { @@ -978,7 +923,7 @@ function applyCallbacks(matchElement) { * will fail isEqualNode on the client */ - element.removeAttribute('onload'); + removeAttribute(element, 'onload'); callback(element); }; /* IE9 doesnt seem to load scripts synchronously, @@ -1003,6 +948,9 @@ function applyCallbacks(matchElement) { }); } +// instead of adding it to the html + +var attributeMap = {}; /** * Updates the document's html tag attributes * @@ -1010,43 +958,62 @@ function applyCallbacks(matchElement) { * @param {HTMLElement} tag - the HTMLElement tag to update with new attrs */ -function updateAttribute() { - var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, +function updateAttribute(appId, options, type, attrs, tag) { + var _ref = options || {}, attribute = _ref.attribute; - var attrs = arguments.length > 1 ? arguments[1] : undefined; - var tag = arguments.length > 2 ? arguments[2] : undefined; var vueMetaAttrString = tag.getAttribute(attribute); - var vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : []; - var toRemove = toArray(vueMetaAttrs); - var keepIndexes = []; - for (var attr in attrs) { - if (attrs.hasOwnProperty(attr)) { - var value = includes(booleanHtmlAttributes, attr) ? '' : isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr]; - tag.setAttribute(attr, value || ''); + if (vueMetaAttrString) { + attributeMap[type] = JSON.parse(decodeURI(vueMetaAttrString)); + removeAttribute(tag, attribute); + } - if (!includes(vueMetaAttrs, attr)) { - vueMetaAttrs.push(attr); - } // filter below wont ever check -1 + var data = attributeMap[type] || {}; + var toUpdate = []; // remove attributes from the map + // which have been removed for this appId + for (var attr in data) { + if (data[attr] && appId in data[attr]) { + toUpdate.push(attr); - keepIndexes.push(toRemove.indexOf(attr)); + if (!attrs[attr]) { + delete data[attr][appId]; + } } } - var removedAttributesCount = toRemove.filter(function (el, index) { - return !includes(keepIndexes, index); - }).reduce(function (acc, attr) { - tag.removeAttribute(attr); - return acc + 1; - }, 0); + for (var _attr in attrs) { + var attrData = data[_attr]; - if (vueMetaAttrs.length === removedAttributesCount) { - tag.removeAttribute(attribute); - } else { - tag.setAttribute(attribute, vueMetaAttrs.sort().join(',')); + if (!attrData || attrData[appId] !== attrs[_attr]) { + toUpdate.push(_attr); + + if (attrs[_attr]) { + data[_attr] = data[_attr] || {}; + data[_attr][appId] = attrs[_attr]; + } + } } + + for (var _i = 0, _toUpdate = toUpdate; _i < _toUpdate.length; _i++) { + var _attr2 = _toUpdate[_i]; + var _attrData = data[_attr2]; + var attrValues = []; + + for (var _appId in _attrData) { + Array.prototype.push.apply(attrValues, [].concat(_attrData[_appId])); + } + + if (attrValues.length) { + var attrValue = includes(booleanHtmlAttributes, _attr2) && attrValues.some(Boolean) ? '' : attrValues.filter(Boolean).join(' '); + tag.setAttribute(_attr2, attrValue); + } else { + removeAttribute(tag, _attr2); + } + } + + attributeMap[type] = data; } /** @@ -1071,14 +1038,11 @@ function updateTitle(title) { * @return {Object} - a representation of what tags changed */ -function updateTag(appId) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var type = arguments.length > 2 ? arguments[2] : undefined; - var tags = arguments.length > 3 ? arguments[3] : undefined; - var head = arguments.length > 4 ? arguments[4] : undefined; - var body = arguments.length > 5 ? arguments[5] : undefined; - var attribute = options.attribute, - tagIDKeyName = options.tagIDKeyName; +function updateTag(appId, options, type, tags, head, body) { + var _ref = options || {}, + attribute = _ref.attribute, + tagIDKeyName = _ref.tagIDKeyName; + var dataAttributes = commonDataAttributes.slice(); dataAttributes.push(tagIDKeyName); var newElements = []; @@ -1118,21 +1082,20 @@ function updateTag(appId) { var newElement = document.createElement(type); newElement.setAttribute(attribute, appId); - - var _loop = function _loop(attr) { + Object.keys(tag).forEach(function (attr) { /* istanbul ignore next */ - if (!tag.hasOwnProperty(attr) || includes(tagProperties, attr)) { - return "continue"; + if (includes(tagProperties, attr)) { + return; } if (attr === 'innerHTML') { newElement.innerHTML = tag.innerHTML; - return "continue"; + return; } if (attr === 'json') { newElement.innerHTML = JSON.stringify(tag.json); - return "continue"; + return; } if (attr === 'cssText') { @@ -1143,7 +1106,7 @@ function updateTag(appId) { newElement.appendChild(document.createTextNode(tag.cssText)); } - return "continue"; + return; } if (attr === 'callback') { @@ -1151,7 +1114,7 @@ function updateTag(appId) { return tag[attr](newElement); }; - return "continue"; + return; } var _attr = includes(dataAttributes, attr) ? "data-".concat(attr) : attr; @@ -1159,19 +1122,12 @@ function updateTag(appId) { var isBooleanAttribute = includes(booleanHtmlAttributes, attr); if (isBooleanAttribute && !tag[attr]) { - return "continue"; + return; } var value = isBooleanAttribute ? '' : tag[attr]; newElement.setAttribute(_attr, value); - }; - - for (var attr in tag) { - var _ret = _loop(attr); - - if (_ret === "continue") continue; - } - + }); var oldElements = currentElements[getElementsKey(tag)]; // Remove a duplicate tag from domTagstoRemove, so it isn't cleared. var indexToDelete; @@ -1222,18 +1178,18 @@ function updateTag(appId) { * @param {Object} newInfo - the meta info to update to */ -function updateClientMetaInfo(appId) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var newInfo = arguments.length > 2 ? arguments[2] : undefined; - var ssrAttribute = options.ssrAttribute, - ssrAppId = options.ssrAppId; // only cache tags for current update +function updateClientMetaInfo(appId, options, newInfo) { + options = options || {}; + var _options = options, + ssrAttribute = _options.ssrAttribute, + ssrAppId = _options.ssrAppId; // only cache tags for current update var tags = {}; var htmlTag = getTag(tags, 'html'); // if this is a server render, then dont update 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 + removeAttribute(htmlTag, ssrAttribute); // add load callbacks if the var addLoadListeners = false; tagsSupportingOnload.forEach(function (type) { @@ -1250,8 +1206,8 @@ function updateClientMetaInfo(appId) { } // initialize tracked changes - var addedTags = {}; - var removedTags = {}; + var tagsAdded = {}; + var tagsRemoved = {}; for (var type in newInfo) { // ignore these @@ -1267,7 +1223,7 @@ function updateClientMetaInfo(appId) { if (includes(metaInfoAttributeKeys, type)) { var tagName = type.substr(0, 4); - updateAttribute(options, newInfo[type], getTag(tags, tagName)); + updateAttribute(appId, options, type, newInfo[type], getTag(tags, tagName)); continue; } // tags should always be an array, ignore if it isnt @@ -1281,82 +1237,244 @@ function updateClientMetaInfo(appId) { newTags = _updateTag.newTags; if (newTags.length) { - addedTags[type] = newTags; - removedTags[type] = oldTags; + tagsAdded[type] = newTags; + tagsRemoved[type] = oldTags; } } return { - addedTags: addedTags, - removedTags: removedTags + tagsAdded: tagsAdded, + tagsRemoved: tagsRemoved }; } -function _refresh() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; +var appsMetaInfo; +function addApp(rootVm, appId, options) { + return { + set: function set(metaInfo) { + return setMetaInfo(rootVm, appId, options, metaInfo); + }, + remove: function remove() { + return removeMetaInfo(rootVm, appId, options); + } + }; +} +function setMetaInfo(rootVm, appId, options, metaInfo) { + // if a vm exists _and_ its mounted then immediately update + if (rootVm && rootVm.$el) { + return updateClientMetaInfo(appId, options, metaInfo); + } // store for later, the info + // will be set on the first refresh - /** - * When called, will update the current meta info with new meta info. - * Useful when updating meta info as the result of an asynchronous - * action that resolves after the initial render takes place. - * - * Credit to [Sébastien Chopin](https://github.com/Atinux) for the suggestion - * to implement this method. - * - * @return {Object} - new meta info - */ - return function refresh() { - // collect & aggregate all metaInfo $options - var rawInfo = getComponentMetaInfo(options, this.$root); - var metaInfo = getMetaInfo(options, rawInfo, clientSequences, this.$root); - 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); + appsMetaInfo = appsMetaInfo || {}; + appsMetaInfo[appId] = metaInfo; +} +function removeMetaInfo(rootVm, appId, options) { + if (rootVm && rootVm.$el) { + var tags = {}; + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = metaInfoAttributeKeys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var type = _step.value; + var tagName = type.substr(0, 4); + updateAttribute(appId, options, type, {}, getTag(tags, tagName)); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return != null) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } } - return { - vm: this, - metaInfo: metaInfo, - tags: tags + return removeElementsByAppId(options, appId); + } + + if (appsMetaInfo[appId]) { + delete appsMetaInfo[appId]; + clearAppsMetaInfo(); + } +} +function getAppsMetaInfo() { + return appsMetaInfo; +} +function clearAppsMetaInfo(force) { + if (force || !Object.keys(appsMetaInfo).length) { + appsMetaInfo = undefined; + } +} + +/** + * Returns the correct meta info for the given component + * (child components will overwrite parent meta info) + * + * @param {Object} component - the Vue instance to get meta info from + * @return {Object} - returned meta info + */ + +function getMetaInfo(options, info, escapeSequences, component) { + options = options || {}; + escapeSequences = escapeSequences || []; + var _options = options, + tagIDKeyName = _options.tagIDKeyName; // 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 + + + 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 + // as the other tags + + + if (info.base) { + info.base = Object.keys(info.base).length ? [info.base] : []; + } + + if (info.meta) { + // remove meta items with duplicate vmid's + info.meta = info.meta.filter(function (metaItem, index, arr) { + var hasVmid = !!metaItem[tagIDKeyName]; + + if (!hasVmid) { + return true; + } + + var isFirstItemForVmid = index === findIndex(arr, function (item) { + return item[tagIDKeyName] === metaItem[tagIDKeyName]; + }); + return isFirstItemForVmid; + }); // apply templates if needed + + info.meta.forEach(function (metaObject) { + return applyTemplate(options, metaObject); + }); + } + + return escapeMetaInfo(options, info, escapeSequences); +} + +/** + * When called, will update the current meta info with new meta info. + * Useful when updating meta info as the result of an asynchronous + * action that resolves after the initial render takes place. + * + * Credit to [Sébastien Chopin](https://github.com/Atinux) for the suggestion + * to implement this method. + * + * @return {Object} - new meta info + */ + +function refresh(rootVm, options) { + options = options || {}; // make sure vue-meta was initiated + + if (!rootVm[rootConfigKey]) { + showWarningNotSupported(); + return {}; + } // collect & aggregate all metaInfo $options + + + var rawInfo = getComponentMetaInfo(options, rootVm); + var metaInfo = getMetaInfo(options, rawInfo, clientSequences, rootVm); + var appId = rootVm[rootConfigKey].appId; + var tags = updateClientMetaInfo(appId, options, metaInfo); // emit "event" with new info + + if (tags && isFunction(metaInfo.changed)) { + metaInfo.changed(metaInfo, tags.tagsAdded, tags.tagsRemoved); + tags = { + addedTags: tags.tagsAdded, + removedTags: tags.tagsRemoved }; + } + + var appsMetaInfo = getAppsMetaInfo(); + + if (appsMetaInfo) { + for (var additionalAppId in appsMetaInfo) { + updateClientMetaInfo(additionalAppId, options, appsMetaInfo[additionalAppId]); + delete appsMetaInfo[additionalAppId]; + } + + clearAppsMetaInfo(true); + } + + return { + vm: rootVm, + metaInfo: metaInfo, + // eslint-disable-line object-shorthand + tags: tags }; } -function _$meta() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - - var _refresh$1 = _refresh(options); - - var inject = function inject() {}; +function $meta(options) { + options = options || {}; /** * Returns an injector for server-side rendering. * @this {Object} - the Vue instance (a root component) * @return {Object} - injector */ + var $root = this.$root; + return { + 'getOptions': function getOptions$1() { + return getOptions(options); + }, + 'setOptions': function setOptions(newOptions) { + var refreshNavKey = 'refreshOnceOnNavigation'; - return function $meta() { - if (!this.$root._vueMeta) { - return { - getOptions: showWarningNotSupported, - refresh: showWarningNotSupported, - inject: showWarningNotSupported, - pause: showWarningNotSupported, - resume: showWarningNotSupported - }; + if (newOptions && newOptions[refreshNavKey]) { + options.refreshOnceOnNavigation = !!newOptions[refreshNavKey]; + addNavGuards($root); + } + + var debounceWaitKey = 'debounceWait'; + + if (newOptions && debounceWaitKey in newOptions) { + var debounceWait = parseInt(newOptions[debounceWaitKey]); + + if (!isNaN(debounceWait)) { + options.debounceWait = debounceWait; + } + } + + var waitOnDestroyedKey = 'waitOnDestroyed'; + + if (newOptions && waitOnDestroyedKey in newOptions) { + options.waitOnDestroyed = !!newOptions[waitOnDestroyedKey]; + } + }, + 'refresh': function refresh$1() { + return refresh($root, options); + }, + 'inject': function inject() { + return showWarningNotSupportedInBrowserBundle('inject'); + }, + 'pause': function pause$1() { + return pause($root); + }, + 'resume': function resume$1() { + return resume($root); + }, + 'addApp': function addApp$1(appId) { + return addApp($root, appId, options); } - - return { - getOptions: function getOptions$1() { - return getOptions(options); - }, - refresh: _refresh$1.bind(this), - inject: inject, - pause: pause.bind(this), - resume: resume.bind(this) - }; }; } @@ -1365,29 +1483,28 @@ function _$meta() { * @param {Function} Vue - the Vue constructor. */ -function install(Vue) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - +function install(Vue, options) { if (Vue.__vuemeta_installed) { return; } Vue.__vuemeta_installed = true; options = setOptions(options); - Vue.prototype.$meta = _$meta(options); + + Vue.prototype.$meta = function () { + return $meta.call(this, options); + }; + Vue.mixin(createMixin(Vue, options)); -} // automatic install - - -if (!isUndefined(window) && !isUndefined(window.Vue)) { - /* istanbul ignore next */ - install(window.Vue); } -var browser = { +var index = { version: version, install: install, + generate: function generate(metaInfo) { + return showWarningNotSupportedInBrowserBundle('generate'); + }, hasMetaInfo: hasMetaInfo }; -export default browser; +export default index; diff --git a/dist/vue-meta.esm.browser.min.js b/dist/vue-meta.esm.browser.min.js index 3f06bab..4f0c2f4 100644 --- a/dist/vue-meta.esm.browser.min.js +++ b/dist/vue-meta.esm.browser.min.js @@ -1 +1 @@ -import t from"deepmerge";var 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){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:10;clearTimeout(e),e=setTimeout(function(){t()},n)}(function(){return t.$meta().refresh()})}function r(t){return(r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function i(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var a,u=t[Symbol.iterator]();!(r=(a=u.next()).done)&&(n.push(a.value),!e||n.length!==e);r=!0);}catch(t){i=!0,o=t}finally{try{r||null==u.return||u.return()}finally{if(i)throw o}}return n}(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}function o(t){return Array.isArray(t)}function a(t){return void 0===t}function u(t){return"object"===r(t)}function s(t){return"object"===r(t)&&null!==t}function c(t){return"function"==typeof t}function l(t,e){return e&&u(t)?(o(t[e])||(t[e]=[]),t):o(t)?t:[]}function f(t,e,n){l(t,e),t[e].push(n)}function d(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this;return t&&(!0===t._vueMeta||u(t._vueMeta))}function v(t){if(!t.$root._vueMeta.navGuards&&t.$root.$router){t.$root._vueMeta.navGuards=!0;var e=t.$root.$router,n=t.$root.$meta();e.beforeEach(function(t,e,r){n.pause(),r()}),e.afterEach(function(){var t=n.resume().metaInfo;t&&t.afterNavigation&&c(t.afterNavigation)&&t.afterNavigation(t)})}}var h=(function(){try{return!a(window)}catch(t){return!1}}()?window:global).console||{};function p(t){h&&h.warn&&h.warn(t)}var m=function(){return p("This vue app/component has no vue-meta configuration")},y=1;var g={title:void 0,titleChunk:"",titleTemplate:"%s",htmlAttrs:{},bodyAttrs:{},headAttrs:{},base:[],link:[],meta:[],style:[],script:[],noscript:[],__dangerouslyDisableSanitizers:[],__dangerouslyDisableSanitizersByTagID:{}},b={keyName:"metaInfo",attribute:"data-vue-meta",ssrAttribute:"data-vue-meta-server-rendered",tagIDKeyName:"vmid",contentKeyName:"content",metaTemplateKeyName:"template",ssrAppId:"ssr"},$=["titleChunk","titleTemplate","changed","__dangerouslyDisableSanitizers","__dangerouslyDisableSanitizersByTagID"],_=["__dangerouslyDisableSanitizers","__dangerouslyDisableSanitizersByTagID"],M=["htmlAttrs","headAttrs","bodyAttrs"],T=["link","style","script"],A=["once","template"],I=["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 w(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];return this.$root._vueMeta.paused=!0,function(){return S(t)}}function S(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];if(this.$root._vueMeta.paused=!1,t)return this.$root.$meta().refresh()}function E(t,e){if(!Array.prototype.findIndex){for(var n=0;n/g,">"],[/"/g,'"'],[/'/g,"'"]];function K(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r={doEscape:function(t){return n.reduce(function(t,e){var n=i(e,2),r=n[0],o=n[1];return t.replace(r,o)},t)}};return _.forEach(function(t,n){if(0===n)l(e,t);else if(1===n)for(var i in e[t])l(e[t],i);r[t]=e[t]}),function t(e,n,r,a){var u=n.tagIDKeyName,c=r.doEscape,l=void 0===c?function(t){return t}:c,f={};for(var d in e){var v=e[d];if(z($,d))f[d]=v;else{var h=i(_,1)[0];if(r[h]&&z(r[h],d))f[d]=v;else{var p=e[u];if(p&&(h=_[1],r[h]&&r[h][p]&&z(r[h][p],d)))f[d]=v;else if("string"==typeof v?f[d]=l(v):o(v)?f[d]=v.map(function(e){return s(e)?t(e,n,r,!0):l(e)}):s(v)?f[d]=t(v,n,r,!0):f[d]=v,a){var m=l(d);d!==m&&(f[m]=f[d],delete f[d])}}}}return f}(e,t,r)}function O(t,e,n,r){var i=t.component,o=t.metaTemplateKeyName,u=t.contentKeyName;return!0!==n&&!0!==e[o]&&(a(n)&&e[o]&&(n=e[o],e[o]=!0),n?(a(r)&&(r=e[u]),e[u]=c(n)?n.call(i,r):n.replace(/%s/g,r),!0):(e.hasOwnProperty(o)&&delete e[o],!1))}function x(e,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return n.hasOwnProperty("title")&&void 0===n.title&&delete n.title,M.forEach(function(t){if(n[t])for(var 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:function(t,e){return function(t,e,n){var r=t.component,i=t.tagIDKeyName,o=t.metaTemplateKeyName,a=t.contentKeyName,u=[];return e.length||n.length?(e.forEach(function(t,e){if(t[i]){var s=E(n,function(e){return e[i]===t[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(t),void n.splice(s,1);if(null!==c[a]&&null!==c.innerHTML){var l=t[o];if(l){if(!c[o])return O({component:r,metaTemplateKeyName:o,contentKeyName:a},c,l),void(c.template=!0);c[a]||O({component:r,metaTemplateKeyName:o,contentKeyName:a},c,void 0,t[a])}}else n.splice(s,1)}else u.push(t)}else u.push(t)}),u.concat(n)):u}(r,t,e)}})}function P(){return function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};var n=arguments.length>1?arguments[1]:void 0;var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};var i=e.keyName;var o=n.$options,s=n.$children;if(n._inactive)return r;if(o[i]){var l=o[i];if(c(l)&&(l=l.call(n)),!u(l))return r;r=x(r,l,e)}s.length&&s.forEach(function(n){(function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this;return t&&!a(t._vueMeta)})(n)&&(r=t(e,n,r))});return r}(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},arguments.length>1?arguments[1]:void 0,g)}function j(t,e){return t[e]||(t[e]=document.getElementsByTagName(e)[0]),t[e]}function C(t,e){var n=e.appId,r=e.attribute,i=e.type,o=e.tagIDKeyName,a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},u=["".concat(i,"[").concat(r,'="').concat(n,'"]'),"".concat(i,"[data-").concat(o,"]")].map(function(t){for(var e in a){var n=a[e],r=n&&!0!==n?'="'.concat(n,'"'):"";t+="[data-".concat(e).concat(r,"]")}return t});return k(t.querySelectorAll(u.join(", ")))}var L=[];function H(t,e,n,r){var i=t.tagIDKeyName,o=!1;return n.forEach(function(t){t[i]&&t.callback&&(o=!0,function(t,e){1===arguments.length&&(e=t,t=""),L.push([t,e])}("".concat(e,"[data-").concat(i,'="').concat(t[i],'"]'),t.callback))}),r&&o?B():o}function B(){!function(){return"complete"===(arguments.length>0&&void 0!==arguments[0]?arguments[0]:document).readyState}()?document.onreadystatechange=function(){q()}:q()}function q(t){L.forEach(function(e){var n=i(e,2),r=n[0],o=n[1],a="".concat(r,'[onload="this.__vm_l=1"]'),u=[];t||(u=k(document.querySelectorAll(a))),t&&t.matches(a)&&(u=[t]),u.forEach(function(t){if(!t.__vm_cb){var e=function(){t.__vm_cb=!0,t.removeAttribute("onload"),o(t)};t.__vm_l?e():t.__vm_ev||(t.__vm_ev=!0,t.addEventListener("load",e))}})})}function V(){var t=(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}).attribute,e=arguments.length>1?arguments[1]:void 0,n=arguments.length>2?arguments[2]:void 0,r=n.getAttribute(t),i=r?r.split(","):[],a=k(i),u=[];for(var s in e)if(e.hasOwnProperty(s)){var c=z(N,s)?"":o(e[s])?e[s].join(" "):e[s];n.setAttribute(s,c||""),z(i,s)||i.push(s),u.push(a.indexOf(s))}var l=a.filter(function(t,e){return!z(u,e)}).reduce(function(t,e){return n.removeAttribute(e),t+1},0);i.length===l?n.removeAttribute(t):n.setAttribute(t,i.sort().join(","))}function W(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,r=arguments.length>3?arguments[3]:void 0,i=arguments.length>4?arguments[4]:void 0,o=arguments.length>5?arguments[5]:void 0,a=e.attribute,u=e.tagIDKeyName,s=I.slice();s.push(u);var c=[],l={appId:t,attribute:a,type:n,tagIDKeyName:u},f={head:C(i,l),pbody:C(o,l,{pbody:!0}),body:C(o,l,{body:!0})};if(r.length>1){var d=[];r=r.filter(function(t){var e=JSON.stringify(t),n=!z(d,e);return d.push(e),n})}r.forEach(function(e){if(!e.skip){var r=document.createElement(n);r.setAttribute(a,t);var i=function(t){if(!e.hasOwnProperty(t)||z(A,t))return"continue";if("innerHTML"===t)return r.innerHTML=e.innerHTML,"continue";if("json"===t)return r.innerHTML=JSON.stringify(e.json),"continue";if("cssText"===t)return r.styleSheet?r.styleSheet.cssText=e.cssText:r.appendChild(document.createTextNode(e.cssText)),"continue";if("callback"===t)return r.onload=function(){return e[t](r)},"continue";var n=z(s,t)?"data-".concat(t):t,i=z(N,t);if(i&&!e[t])return"continue";var o=i?"":e[t];r.setAttribute(n,o)};for(var o in e)i(o);var u,l=f[function(t){var e=t.body,n=t.pbody;return e?"body":n?"pbody":"head"}(e)];l.some(function(t,e){return u=e,r.isEqualNode(t)})&&(u||0===u)?l.splice(u,1):c.push(r)}});var v=[];for(var h in f)Array.prototype.push.apply(v,f[h]);return v.forEach(function(t){t.parentNode.removeChild(t)}),c.forEach(function(t){t.hasAttribute("data-body")?o.appendChild(t):t.hasAttribute("data-pbody")?o.insertBefore(t,o.firstChild):i.appendChild(t)}),{oldTags:v,newTags:c}}function G(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return function(){var e=P(t,this.$root),n=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=arguments.length>1?arguments[1]:void 0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=arguments.length>3?arguments[3]:void 0,i=t.tagIDKeyName;return e.title&&(e.titleChunk=e.title),e.titleTemplate&&"%s"!==e.titleTemplate&&O({component:r,contentKeyName:"title"},e,e.titleTemplate,e.titleChunk||""),e.base&&(e.base=Object.keys(e.base).length?[e.base]:[]),e.meta&&(e.meta=e.meta.filter(function(t,e,n){return!t.hasOwnProperty(i)||e===E(n,function(e){return e[i]===t[i]})}),e.meta.forEach(function(e){return O(t,e)})),K(t,e,n)}(t,e,D,this.$root),r=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,r=e.ssrAttribute,i=e.ssrAppId,a={},u=j(a,"html");if(t===i&&u.hasAttribute(r)){u.removeAttribute(r);var s=!1;return T.forEach(function(t){n[t]&&H(e,t,n[t])&&(s=!0)}),s&&B(),!1}var c,l={},f={};for(var d in n)if(!z($,d))if("title"!==d){if(z(M,d)){var v=d.substr(0,4);V(e,n[d],j(a,v))}else if(o(n[d])){var h=W(t,e,d,n[d],j(a,"head"),j(a,"body")),p=h.oldTags,m=h.newTags;m.length&&(l[d]=m,f[d]=p)}}else((c=n.title)||""===c)&&(document.title=c);return{addedTags:l,removedTags:f}}(this.$root._vueMeta.appId,t,n);return r&&c(n.changed)&&n.changed(n,r.addedTags,r.removedTags),{vm:this,metaInfo:n,tags:r}}}function J(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t.__vuemeta_installed||(t.__vuemeta_installed=!0,e=function(t){for(var e in t=u(t)?t:{},b)t[e]||(t[e]=b[e]);return t}(e),t.prototype.$meta=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=G(t),n=function(){};return function(){return this.$root._vueMeta?{getOptions:function(){return function(t){var e={};for(var n in t)e[n]=t[n];return e}(t)},refresh:e.bind(this),inject:n,pause:w.bind(this),resume:S.bind(this)}:{getOptions:m,refresh:m,inject:m,pause:m,resume:m}}}(e),t.mixin(function(t,e){var r=["activated","deactivated","beforeMount"];return{beforeCreate:function(){var i=this;if(Object.defineProperty(this,"_hasMetaInfo",{configurable:!0,get:function(){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),d(this)}}),!a(this.$options[e.keyName])&&null!==this.$options[e.keyName]){if(this.$root._vueMeta||(this.$root._vueMeta={appId:y},y++),!this._vueMeta){this._vueMeta=!0;for(var o=this.$parent;o&&o!==this.$root;)a(o._vueMeta)&&(o._vueMeta=!1),o=o.$parent}c(this.$options[e.keyName])&&(this.$options.computed||(this.$options.computed={}),this.$options.computed.$metaInfo=this.$options[e.keyName],this.$isServer||f(this.$options,"created",function(){i.$watch("$metaInfo",function(){i.__metaInfo=void 0,n(i,"watcher")})})),a(this.$root._vueMeta.initialized)&&(this.$root._vueMeta.initialized=this.$isServer,this.$root._vueMeta.initialized||(f(this.$options,"beforeMount",function(){i.$root.$el&&i.$root.$el.hasAttribute&&i.$root.$el.hasAttribute("data-server-rendered")&&(i.$root._vueMeta.appId=e.ssrAppId)}),f(this.$options,"mounted",function(){i.$root._vueMeta.initialized||(i.$root._vueMeta.initializing=!0,i.$nextTick(function(){var t=this,r=this.$root.$meta().refresh(),i=r.tags,o=r.metaInfo;!1===i&&null===this.$root._vueMeta.initialized&&this.$nextTick(function(){return n(t,"initializing")}),this.$root._vueMeta.initialized=!0,delete this.$root._vueMeta.initializing,!e.refreshOnceOnNavigation&&o.afterNavigation&&v(this)}))}),e.refreshOnceOnNavigation&&v(this))),this.$isServer||r.forEach(function(t){f(i.$options,t,function(){return n(i,t)})})}},destroyed:function(){var t=this;if(!this.$isServer&&this.$parent&&d(this))var e=setInterval(function(){t.$el&&null!==t.$el.offsetParent||(clearInterval(e),n(t,"destroyed"))},50)}}}(t,e)))}a(window)||a(window.Vue)||J(window.Vue);var F={version:"2.2.2",install:J,hasMetaInfo:d};export default F; +import n from"deepmerge";function e(n){return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(n){return typeof n}:function(n){return n&&"function"==typeof Symbol&&n.constructor===Symbol&&n!==Symbol.prototype?"symbol":typeof n})(n)}function t(n){return Array.isArray(n)}function r(n){return void 0===n}function i(n){return"object"===e(n)}function o(n){return"object"===e(n)&&null!==n}function a(n){return"function"==typeof n}var u=(function(){try{return!r(window)}catch(n){return!1}}()?window:global).console||{};function f(n){u&&u.warn&&u.warn(n)}var c=function(n){return f("".concat(n," is not supported in browser builds"))},s=function(){return f("This vue app/component has no vue-meta configuration")},d={title:void 0,titleChunk:"",titleTemplate:"%s",htmlAttrs:{},bodyAttrs:{},headAttrs:{},base:[],link:[],meta:[],style:[],script:[],noscript:[],__dangerouslyDisableSanitizers:[],__dangerouslyDisableSanitizersByTagID:{}},l="_vueMeta",v={t:"metaInfo",i:"data-vue-meta",o:"data-vue-meta-server-rendered",u:"vmid",s:"content",l:"template",v:!0,m:10,p:"ssr"},m=Object.keys(d),y=[m[12],m[13]],p=[m[1],m[2],"changed"].concat(y),h=[m[3],m[4],m[5]],b=["link","style","script"],g=["once","template"],N=["body","pbody"],I=["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"],A=null;function T(n,e,t){var r=n.m;e[l].h||!e[l].g&&"watcher"!==t||(e[l].h=null),e[l].h&&!e[l].N&&function(n,e){if(!(e=void 0===e?10:e))return void n();clearTimeout(A),A=setTimeout((function(){n()}),e)}((function(){e.$meta().refresh()}),r)}function w(n,e){return e&&i(n)?(t(n[e])||(n[e]=[]),n):t(n)?n:[]}function O(n,e,t){w(n,e),n[e].push(t)}function K(n){return(n=n||this)&&(!0===n[l]||i(n[l]))}function M(n,e){return n[l].N=!0,function(){return S(n,e)}}function S(n,e){if(n[l].N=!1,e||void 0===e)return n.$meta().refresh()}function j(n){var e=n.$router;!n[l].I&&e&&(n[l].I=!0,e.beforeEach((function(e,t,r){M(n),r()})),e.afterEach((function(){var e=S(n).metaInfo;e&&a(e.afterNavigation)&&e.afterNavigation(e)})))}var D=1;function _(n,e,t){if(!Array.prototype.findIndex){for(var r=0;r/g,">"],[/"/g,'"'],[/'/g,"'"]];function z(n,e,r){r=r||[];var i={A:function(n){return r.reduce((function(n,e){return n.replace(e[0],e[1])}),n)}};return y.forEach((function(n,t){if(0===t)w(e,n);else if(1===t)for(var r in e[n])w(e[n],r);i[n]=e[n]})),function n(e,r,i,a){var u=r.u,f=i.A,c=void 0===f?function(n){return n}:f,s={};for(var d in e){var l=e[d];if(W(p,d))s[d]=l;else{var v=y[0];if(i[v]&&W(i[v],d))s[d]=l;else{var m=e[u];if(m&&(v=y[1],i[v]&&i[v][m]&&W(i[v][m],d)))s[d]=l;else if("string"==typeof l?s[d]=c(l):t(l)?s[d]=l.map((function(e){return o(e)?n(e,r,i,!0):c(e)})):o(l)?s[d]=n(l,r,i,!0):s[d]=l,a){var h=c(d);d!==h&&(s[h]=s[d],delete s[d])}}}}return s}(e,n,i)}function B(n,e,t,i){var o=n.component,u=n.l,f=n.s;return!0!==t&&!0!==e[u]&&(r(t)&&e[u]&&(t=e[u],e[u]=!0),t?(r(i)&&(i=e[f]),e[f]=a(t)?t.call(o,i):t.replace(/%s/g,i),!0):(delete e[u],!1))}var J=!1;function C(e,t,r){return r=r||{},void 0===t.title&&delete t.title,h.forEach((function(n){if(t[n])for(var e in t[n])e in t[n]&&void 0===t[n][e]&&(W(I,e)&&!J&&(f("VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details"),J=!0),delete t[n][e])})),n(e,t,{T:function(n,e){return function(n,e,t){var r=n.component,i=n.u,o=n.l,a=n.s,u=[];return e.length||t.length?(e.forEach((function(n,e){if(n[i]){var f=_(t,(function(e){return e[i]===n[i]})),c=t[f];if(-1!==f){if(a in c&&void 0===c[a]||"innerHTML"in c&&void 0===c.innerHTML)return u.push(n),void t.splice(f,1);if(null!==c[a]&&null!==c.innerHTML){var s=n[o];if(s){if(!c[o])return B({component:r,l:o,s:a},c,s),void(c.template=!0);c[a]||B({component:r,l:o,s:a},c,void 0,n[a])}}else t.splice(f,1)}else u.push(n)}else u.push(n)})),u.concat(t)):u}(r,n,e)}})}function H(n,e){return function n(e,t,o){o=o||{};if(t._inactive)return o;e=e||{};var a=e,u=a.t;var f=t.$metaInfo,c=t.$options,s=t.$children;if(c[u]){var d=f||c[u];if(!i(d))return o;o=C(o,d,e)}s.length&&s.forEach((function(t){(function(n){return(n=n||this)&&!r(n[l])})(t)&&(o=n(e,t,o))}));return o}(n||{},e,d)}var L=function(n,e){return(e||document).querySelectorAll(n)};function P(n,e){return n[e]||(n[e]=document.getElementsByTagName(e)[0]),n[e]}function R(n,e,t){var r=e.O,i=e.i,o=e.type,a=e.u;t=t||{};var u=["".concat(o,"[").concat(i,'="').concat(r,'"]'),"".concat(o,"[data-").concat(a,"]")].map((function(n){for(var e in t){var r=t[e],i=r&&!0!==r?'="'.concat(r,'"'):"";n+="[data-".concat(e).concat(i,"]")}return n}));return k(L(u.join(", "),n))}function V(n,e){n.removeAttribute(e)}var q=[];function E(n,e,t,r){var i=n.u,o=!1;return t.forEach((function(n){n[i]&&n.callback&&(o=!0,function(n,e){1===arguments.length&&(e=n,n=""),q.push([n,e])}("".concat(e,"[data-").concat(i,'="').concat(n[i],'"]'),n.callback))})),r&&o?U():o}function U(){var n;"complete"!==(n||document).readyState?document.onreadystatechange=function(){$()}:$()}function $(n){q.forEach((function(e){var t=e[0],r=e[1],i="".concat(t,'[onload="this.__vm_l=1"]'),o=[];n||(o=k(L(i))),n&&n.matches(i)&&(o=[n]),o.forEach((function(n){if(!n.__vm_cb){var e=function(){n.__vm_cb=!0,V(n,"onload"),r(n)};n.__vm_l?e():n.__vm_ev||(n.__vm_ev=!0,n.addEventListener("load",e))}}))}))}var F,G={};function Q(n,e,t,r,i){var o=(e||{}).i,a=i.getAttribute(o);a&&(G[t]=JSON.parse(decodeURI(a)),V(i,o));var u=G[t]||{},f=[];for(var c in u)u[c]&&n in u[c]&&(f.push(c),r[c]||delete u[c][n]);for(var s in r){var d=u[s];d&&d[n]===r[s]||(f.push(s),r[s]&&(u[s]=u[s]||{},u[s][n]=r[s]))}for(var l=0,v=f;l1){var v=[];r=r.filter((function(n){var e=JSON.stringify(n),t=!W(v,e);return v.push(e),t}))}r.forEach((function(e){if(!e.skip){var r=document.createElement(t);r.setAttribute(u,n),Object.keys(e).forEach((function(n){if(!W(g,n))if("innerHTML"!==n)if("json"!==n)if("cssText"!==n)if("callback"!==n){var t=W(c,n)?"data-".concat(n):n,i=W(I,n);if(!i||e[n]){var o=i?"":e[n];r.setAttribute(t,o)}}else r.onload=function(){return e[n](r)};else r.styleSheet?r.styleSheet.cssText=e.cssText:r.appendChild(document.createTextNode(e.cssText));else r.innerHTML=JSON.stringify(e.json);else r.innerHTML=e.innerHTML}));var i,o=l[function(n){var e=n.body,t=n.pbody;return e?"body":t?"pbody":"head"}(e)];o.some((function(n,e){return i=e,r.isEqualNode(n)}))&&(i||0===i)?o.splice(i,1):s.push(r)}}));var m=[];for(var y in l)Array.prototype.push.apply(m,l[y]);return m.forEach((function(n){n.parentNode.removeChild(n)})),s.forEach((function(n){n.hasAttribute("data-body")?o.appendChild(n):n.hasAttribute("data-pbody")?o.insertBefore(n,o.firstChild):i.appendChild(n)})),{oldTags:m,newTags:s}}function Y(n,e,r){var i=e=e||{},o=i.o,a=i.p,u={},f=P(u,"html");if(n===a&&f.hasAttribute(o)){V(f,o);var c=!1;return b.forEach((function(n){r[n]&&E(e,n,r[n])&&(c=!0)})),c&&U(),!1}var s,d={},l={};for(var v in r)if(!W(p,v))if("title"!==v){if(W(h,v)){var m=v.substr(0,4);Q(n,e,v,r[v],P(u,m))}else if(t(r[v])){var y=X(n,e,v,r[v],P(u,"head"),P(u,"body")),g=y.oldTags,N=y.newTags;N.length&&(d[v]=N,l[v]=g)}}else((s=r.title)||""===s)&&(document.title=s);return{K:d,M:l}}function Z(n,e,t){return{set:function(r){return function(n,e,t,r){if(n&&n.$el)return Y(e,t,r);(F=F||{})[e]=r}(n,e,t,r)},remove:function(){return function(n,e,t){if(n&&n.$el){var r={},i=!0,o=!1,a=void 0;try{for(var u,f=h[Symbol.iterator]();!(i=(u=f.next()).done);i=!0){var c=u.value,s=c.substr(0,4);Q(e,t,c,{},P(r,s))}}catch(n){o=!0,a=n}finally{try{i||null==f.return||f.return()}finally{if(o)throw a}}return function(n,e){var t=n.i;k(L("[".concat(t,'="').concat(e,'"]'))).map((function(n){return n.remove()}))}(t,e)}F[e]&&(delete F[e],en())}(n,e,t)}}}function nn(){return F}function en(n){!n&&Object.keys(F).length||(F=void 0)}function tn(n,e){if(e=e||{},!n[l])return s(),{};var t=function(n,e,t,r){t=t||[];var i=(n=n||{}).u;return e.title&&(e.titleChunk=e.title),e.titleTemplate&&"%s"!==e.titleTemplate&&B({component:r,s:"title"},e,e.titleTemplate,e.titleChunk||""),e.base&&(e.base=Object.keys(e.base).length?[e.base]:[]),e.meta&&(e.meta=e.meta.filter((function(n,e,t){return!n[i]||e===_(t,(function(e){return e[i]===n[i]}))})),e.meta.forEach((function(e){return B(n,e)}))),z(n,e,t)}(e,H(e,n),x,n),r=Y(n[l].O,e,t);r&&a(t.changed)&&(t.changed(t,r.K,r.M),r={addedTags:r.K,removedTags:r.M});var i=nn();if(i){for(var o in i)Y(o,e,i[o]),delete i[o];en(!0)}return{vm:n,metaInfo:t,tags:r}}function rn(n){n=n||{};var e=this.$root;return{getOptions:function(){return function(n){var e={};for(var t in n)e[t]=n[t];return e}(n)},setOptions:function(t){t&&t.S&&(n.S=!!t.S,j(e));if(t&&"debounceWait"in t){var r=parseInt(t.m);isNaN(r)||(n.m=r)}t&&"waitOnDestroyed"in t&&(n.v=!!t.v)},refresh:function(){return tn(e,n)},inject:function(){return c("inject")},pause:function(){return M(e)},resume:function(){return S(e)},addApp:function(t){return Z(e,t,n)}}}var on={version:"2.3.0-beta.0",install:function(n,e){n.__vuemeta_installed||(n.__vuemeta_installed=!0,e=function(n){return{t:(n=i(n)?n:{}).keyName||v.t,i:n.attribute||v.i,o:n.ssrAttribute||v.o,u:n.tagIDKeyName||v.u,s:n.contentKeyName||v.s,l:n.metaTemplateKeyName||v.l,m:r(n.debounceWait)?v.m:n.debounceWait,v:r(n.waitOnDestroyed)?v.v:n.waitOnDestroyed,p:n.ssrAppId||v.p,S:!!n.refreshOnceOnNavigation}}(e),n.prototype.$meta=function(){return rn.call(this,e)},n.mixin(function(n,e){var t=["activated","deactivated","beforeMount"];return{beforeCreate:function(){var i=this.$root,o=this.$options;if(Object.defineProperty(this,"_hasMetaInfo",{configurable:!0,get:function(){return n.config.devtools&&!i[l].j&&(f("VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead"),i[l].j=!0),K(this)}}),!r(o[e.t])&&null!==o[e.t]){if(i[l]||(i[l]={O:D},D++),!this[l]){this[l]=!0;for(var u=this.$parent;u&&u!==i;)r(u[l])&&(u[l]=!1),u=u.$parent}a(o[e.t])&&(o.computed=o.computed||{},o.computed.$metaInfo=o[e.t],this.$isServer||O(o,"created",(function(){this.$watch("$metaInfo",(function(){T(e,this.$root,"watcher")}))}))),r(i[l].h)&&(i[l].h=this.$isServer,i[l].h||(O(o,"beforeMount",(function(){var n=this.$root;n.$el&&1===n.$el.nodeType&&n.$el.hasAttribute("data-server-rendered")&&(n[l].O=e.p)})),O(o,"mounted",(function(){var n=this.$root;n[l].h||(n[l].g=!0,this.$nextTick((function(){var t=n.$meta().refresh(),r=t.tags,i=t.metaInfo;!1===r&&null===n[l].h&&this.$nextTick((function(){return T(e,n,"init")})),n[l].h=!0,delete n[l].g,!e.S&&i.afterNavigation&&j(n)})))})),e.S&&j(i))),this.$isServer||t.forEach((function(n){O(o,n,(function(){T(e,this.$root,n)}))}))}},destroyed:function(){var n=this;this.$parent&&K(this)&&this.$nextTick((function(){if(e.v&&n.$el&&n.$el.offsetParent)var t=setInterval((function(){n.$el&&null!==n.$el.offsetParent||(clearInterval(t),T(e,n.$root,"destroyed"))}),50);else T(e,n.$root,"destroyed")}))}}}(n,e)))},generate:function(n){return c("generate")},hasMetaInfo:K};export default on; diff --git a/dist/vue-meta.esm.js b/dist/vue-meta.esm.js index f059dfc..53607ce 100644 --- a/dist/vue-meta.esm.js +++ b/dist/vue-meta.esm.js @@ -1,49 +1,16 @@ /** - * vue-meta v2.2.2 + * vue-meta v2.3.0-beta.0 * (c) 2019 * - Declan de Wet * - Sébastien Chopin (@Atinux) - * - All the amazing contributors + * - Pim (@pimlie) + * - All the amazing contributors * @license MIT */ import deepmerge from 'deepmerge'; -var version = "2.2.2"; - -// store an id to keep track of DOM updates -var 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 - if (!vm.$root._vueMeta.initialized && (vm.$root._vueMeta.initializing || hookName === 'watcher')) { - vm.$root._vueMeta.initialized = null; - } - - if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) { - // batch potential DOM updates to prevent extraneous re-rendering - batchUpdate(function () { - return vm.$meta().refresh(); - }); - } -} -/** - * Performs a batched update. - * - * @param {(null|Number)} id - the ID of this update - * @param {Function} callback - the update to perform - * @return {Number} id - a new ID - */ - -function batchUpdate(callback) { - var timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 10; - clearTimeout(batchId); - batchId = setTimeout(function () { - callback(); - }, timeout); - return batchId; -} +var version = "2.3.0-beta.0"; function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { @@ -59,8 +26,19 @@ function _typeof(obj) { return _typeof(obj); } -function _slicedToArray(arr, i) { - return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); +function _defineProperty(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; } function _toConsumableArray(arr) { @@ -75,48 +53,14 @@ function _arrayWithoutHoles(arr) { } } -function _arrayWithHoles(arr) { - if (Array.isArray(arr)) return arr; -} - function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } -function _iterableToArrayLimit(arr, i) { - var _arr = []; - var _n = true; - var _d = false; - var _e = undefined; - - try { - for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { - _arr.push(_s.value); - - if (i && _arr.length === i) break; - } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i["return"] != null) _i["return"](); - } finally { - if (_d) throw _e; - } - } - - return _arr; -} - function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } -function _nonIterableRest() { - throw new TypeError("Invalid attempt to destructure non-iterable instance"); -} - /** * checks if passed argument is an array * @param {any} arg - the object to check @@ -141,56 +85,6 @@ function isString(arg) { return typeof arg === 'string'; } -function ensureIsArray(arg, key) { - if (!key || !isObject(arg)) { - return isArray(arg) ? arg : []; - } - - if (!isArray(arg[key])) { - arg[key] = []; - } - - return arg; -} -function ensuredPush(object, key, el) { - ensureIsArray(object, key); - object[key].push(el); -} - -function hasMetaInfo() { - var vm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 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 - -function inMetaInfoBranch() { - var vm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this; - return vm && !isUndefined(vm._vueMeta); -} - -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; - } - - vm.$root._vueMeta.navGuards = true; - var $router = vm.$root.$router; - var $meta = vm.$root.$meta(); - $router.beforeEach(function (to, from, next) { - $meta.pause(); - next(); - }); - $router.afterEach(function () { - var _$meta$resume = $meta.resume(), - metaInfo = _$meta$resume.metaInfo; - - if (metaInfo && metaInfo.afterNavigation && isFunction(metaInfo.afterNavigation)) { - metaInfo.afterNavigation(metaInfo); - } - }); -} - function hasGlobalWindowFn() { try { return !isUndefined(window); @@ -211,173 +105,9 @@ function warn(str) { console.warn(str); } - -var appId = 1; -function createMixin(Vue, options) { - // for which Vue lifecycle hooks should the metaInfo be refreshed - var updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount']; // watch for client side component updates - - return { - beforeCreate: function beforeCreate() { - var _this = this; - - Object.defineProperty(this, '_hasMetaInfo', { - configurable: true, - get: function get() { - // Show deprecation warning once when devtools enabled - if (Vue.config.devtools && !this.$root._vueMeta.hasMetaInfoDeprecationWarningShown) { - 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 - // _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) { - return; - } - - if (!this.$root._vueMeta) { - this.$root._vueMeta = { - appId: appId - }; - appId++; - } // 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; - 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 - // 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.$watch('$metaInfo', function () { - _this.__metaInfo = undefined; - triggerUpdate(_this, 'watcher'); - }); - }); - } - } // 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 () { - // 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 = options.ssrAppId; - } - }); // we use the mounted hook here as on page load - - ensuredPush(this.$options, 'mounted', function () { - if (!_this.$root._vueMeta.initialized) { - // used in triggerUpdate to check if a change was triggered - // during initialization - _this.$root._vueMeta.initializing = true; // refresh meta in nextTick so all child components have loaded - - _this.$nextTick(function () { - var _this2 = this; - - var _this$$root$$meta$ref = this.$root.$meta().refresh(), - tags = _this$$root$$meta$ref.tags, - metaInfo = _this$$root$$meta$ref.metaInfo; // 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(_this2, 'initializing'); - }); - } - - this.$root._vueMeta.initialized = true; - 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 - - if (options.refreshOnceOnNavigation) { - addNavGuards(this); - } - } - } // do not trigger refresh on the server side - - - if (this.$isServer) { - return; - } // no need to add this hooks on server side - - - updateOnLifecycleHook.forEach(function (lifecycleHook) { - ensuredPush(_this.$options, lifecycleHook, function () { - return triggerUpdate(_this, lifecycleHook); - }); - }); - }, - // TODO: move back into beforeCreate when Vue issue is resolved - destroyed: function destroyed() { - var _this3 = this; - - // do not trigger refresh: - // - on the server side - // - when the component doesnt have a parent - // - doesnt have metaInfo defined - if (this.$isServer || !this.$parent || !hasMetaInfo(this)) { - return; - } // Wait that element is hidden before refreshing meta tags (to support animations) - - - var interval = setInterval(function () { - if (_this3.$el && _this3.$el.offsetParent !== null) { - return; - } - - clearInterval(interval); - triggerUpdate(_this3, 'destroyed'); - }, 50); - } - }; -} +var showWarningNotSupported = function showWarningNotSupported() { + return warn('This vue app/component has no vue-meta configuration'); +}; /** * These are constant variables used throughout the application. @@ -397,10 +127,11 @@ var defaultInfo = { script: [], noscript: [], __dangerouslyDisableSanitizers: [], - __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. - + __dangerouslyDisableSanitizersByTagID: {} }; +var rootConfigKey = '_vueMeta'; // 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 // manage and which it should ignore. @@ -418,7 +149,11 @@ var metaTemplateKeyName = 'template'; // This is the key name for the content-ho var contentKeyName = 'content'; // The id used for the ssr app -var ssrAppId = 'ssr'; +var ssrAppId = 'ssr'; // How long meta update + +var debounceWait = 10; // How long meta update + +var waitOnDestroyed = true; var defaultOptions = { keyName: keyName, attribute: attribute, @@ -426,14 +161,18 @@ var defaultOptions = { tagIDKeyName: tagIDKeyName, contentKeyName: contentKeyName, metaTemplateKeyName: metaTemplateKeyName, - ssrAppId: ssrAppId // List of metaInfo property keys which are configuration options (and dont generate html) + waitOnDestroyed: waitOnDestroyed, + debounceWait: debounceWait, + ssrAppId: ssrAppId +}; // might be a bit ugly, but minimizes the browser bundles a bit -}; -var metaInfoOptionKeys = ['titleChunk', 'titleTemplate', 'changed', '__dangerouslyDisableSanitizers', '__dangerouslyDisableSanitizersByTagID']; // The metaInfo property keys which are used to disable escaping +var defaultInfoKeys = Object.keys(defaultInfo); // The metaInfo property keys which are used to disable escaping -var disableOptionKeys = ['__dangerouslyDisableSanitizers', '__dangerouslyDisableSanitizersByTagID']; // List of metaInfo property keys which only generates attributes and no tags +var disableOptionKeys = [defaultInfoKeys[12], defaultInfoKeys[13]]; // List of metaInfo property keys which are configuration options (and dont generate html) -var metaInfoAttributeKeys = ['htmlAttrs', 'headAttrs', 'bodyAttrs']; // HTML elements which support the onload event +var metaInfoOptionKeys = [defaultInfoKeys[1], defaultInfoKeys[2], 'changed'].concat(disableOptionKeys); // List of metaInfo property keys which only generates attributes and no tags + +var metaInfoAttributeKeys = [defaultInfoKeys[3], defaultInfoKeys[4], defaultInfoKeys[5]]; // HTML elements which support the onload event var 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 @@ -449,17 +188,305 @@ var commonDataAttributes = ['body', 'pbody']; // from: https://github.com/kangax 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']; -function setOptions(options) { - // combine options - options = isObject(options) ? options : {}; +var batchId = null; +function triggerUpdate(_ref, rootVm, hookName) { + var debounceWait = _ref.debounceWait; - for (var key in defaultOptions) { - if (!options[key]) { - options[key] = defaultOptions[key]; - } + // 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 + if (!rootVm[rootConfigKey].initialized && (rootVm[rootConfigKey].initializing || hookName === 'watcher')) { + rootVm[rootConfigKey].initialized = null; } - return options; + if (rootVm[rootConfigKey].initialized && !rootVm[rootConfigKey].pausing) { + // batch potential DOM updates to prevent extraneous re-rendering + batchUpdate(function () { + return void rootVm.$meta().refresh(); + }, debounceWait); + } +} +/** + * Performs a batched update. + * + * @param {(null|Number)} id - the ID of this update + * @param {Function} callback - the update to perform + * @return {Number} id - a new ID + */ + +function batchUpdate(callback, timeout) { + timeout = timeout === undefined ? 10 : timeout; + + if (!timeout) { + callback(); + return; + } + + clearTimeout(batchId); + batchId = setTimeout(function () { + callback(); + }, timeout); + return batchId; +} + +function ensureIsArray(arg, key) { + if (!key || !isObject(arg)) { + return isArray(arg) ? arg : []; + } + + if (!isArray(arg[key])) { + arg[key] = []; + } + + return arg; +} +function ensuredPush(object, key, el) { + ensureIsArray(object, key); + object[key].push(el); +} + +function hasMetaInfo(vm) { + vm = vm || this; + return vm && (vm[rootConfigKey] === true || isObject(vm[rootConfigKey])); +} // a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has + +function inMetaInfoBranch(vm) { + vm = vm || this; + return vm && !isUndefined(vm[rootConfigKey]); +} + +function pause(rootVm, refresh) { + rootVm[rootConfigKey].pausing = true; + return function () { + return resume(rootVm, refresh); + }; +} +function resume(rootVm, refresh) { + rootVm[rootConfigKey].pausing = false; + + if (refresh || refresh === undefined) { + return rootVm.$meta().refresh(); + } +} + +function addNavGuards(rootVm) { + var router = rootVm.$router; // return when nav guards already added or no router exists + + if (rootVm[rootConfigKey].navGuards || !router) { + /* istanbul ignore next */ + return; + } + + rootVm[rootConfigKey].navGuards = true; + router.beforeEach(function (to, from, next) { + pause(rootVm); + next(); + }); + router.afterEach(function () { + var _resume = resume(rootVm), + metaInfo = _resume.metaInfo; + + if (metaInfo && isFunction(metaInfo.afterNavigation)) { + metaInfo.afterNavigation(metaInfo); + } + }); +} + +var appId = 1; +function createMixin(Vue, options) { + // for which Vue lifecycle hooks should the metaInfo be refreshed + var updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount']; // watch for client side component updates + + return { + beforeCreate: function beforeCreate() { + var rootKey = '$root'; + var $root = this[rootKey]; + var $options = this.$options; + Object.defineProperty(this, '_hasMetaInfo', { + configurable: true, + get: function get() { + // Show deprecation warning once when devtools enabled + if (Vue.config.devtools && !$root[rootConfigKey].deprecationWarningShown) { + warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead'); + $root[rootConfigKey].deprecationWarningShown = true; + } + + 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($options[options.keyName]) || $options[options.keyName] === null) { + return; + } + + if (!$root[rootConfigKey]) { + $root[rootConfigKey] = { + appId: appId + }; + appId++; + } // 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[rootConfigKey]) { + this[rootConfigKey] = true; + var parent = this.$parent; + + while (parent && parent !== $root) { + if (isUndefined(parent[rootConfigKey])) { + parent[rootConfigKey] = false; + } + + parent = parent.$parent; + } + } // coerce function-style metaInfo to a computed prop so we can observe + // it on creation + + + if (isFunction($options[options.keyName])) { + $options.computed = $options.computed || {}; + $options.computed.$metaInfo = $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($options, 'created', function () { + this.$watch('$metaInfo', function () { + triggerUpdate(options, this[rootKey], 'watcher'); + }); + }); + } + } // 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($root[rootConfigKey].initialized)) { + $root[rootConfigKey].initialized = this.$isServer; + + if (!$root[rootConfigKey].initialized) { + ensuredPush($options, 'beforeMount', function () { + var $root = this[rootKey]; // if this Vue-app was server rendered, set the appId to 'ssr' + // only one SSR app per page is supported + + if ($root.$el && $root.$el.nodeType === 1 && $root.$el.hasAttribute('data-server-rendered')) { + $root[rootConfigKey].appId = options.ssrAppId; + } + }); // we use the mounted hook here as on page load + + ensuredPush($options, 'mounted', function () { + var $root = this[rootKey]; + + if (!$root[rootConfigKey].initialized) { + // used in triggerUpdate to check if a change was triggered + // during initialization + $root[rootConfigKey].initializing = true; // refresh meta in nextTick so all child components have loaded + + this.$nextTick(function () { + var _$root$$meta$refresh = $root.$meta().refresh(), + tags = _$root$$meta$refresh.tags, + metaInfo = _$root$$meta$refresh.metaInfo; // 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 && $root[rootConfigKey].initialized === null) { + this.$nextTick(function () { + return triggerUpdate(options, $root, 'init'); + }); + } + + $root[rootConfigKey].initialized = true; + delete $root[rootConfigKey].initializing; // add the navigation guards if they havent been added yet + // they are needed for the afterNavigation callback + + if (!options.refreshOnceOnNavigation && metaInfo.afterNavigation) { + addNavGuards($root); + } + }); + } + }); // add the navigation guards if requested + + if (options.refreshOnceOnNavigation) { + addNavGuards($root); + } + } + } // do not trigger refresh on the server side + + + if (this.$isServer) { + return; + } // no need to add this hooks on server side + + + updateOnLifecycleHook.forEach(function (lifecycleHook) { + ensuredPush($options, lifecycleHook, function () { + triggerUpdate(options, this[rootKey], lifecycleHook); + }); + }); + }, + // TODO: move back into beforeCreate when Vue issue is resolved + destroyed: function destroyed() { + var _this = this; + + // do not trigger refresh: + // - when user configured to not wait for transitions on destroyed + // - when the component doesnt have a parent + // - doesnt have metaInfo defined + if (!this.$parent || !hasMetaInfo(this)) { + return; + } + + this.$nextTick(function () { + if (!options.waitOnDestroyed || !_this.$el || !_this.$el.offsetParent) { + triggerUpdate(options, _this.$root, 'destroyed'); + return; + } // Wait that element is hidden before refreshing meta tags (to support animations) + + + var interval = setInterval(function () { + if (_this.$el && _this.$el.offsetParent !== null) { + /* istanbul ignore next line */ + return; + } + + clearInterval(interval); + triggerUpdate(options, _this.$root, 'destroyed'); + }, 50); + }); + } + }; +} + +function setOptions(options) { + // combine options + options = isObject(options) ? options : {}; // The options are set like this so they can + // be minified by terser while keeping the + // user api intact + // terser --mangle-properties keep_quoted=strict + + /* eslint-disable dot-notation */ + + return { + keyName: options['keyName'] || defaultOptions.keyName, + attribute: options['attribute'] || defaultOptions.attribute, + ssrAttribute: options['ssrAttribute'] || defaultOptions.ssrAttribute, + tagIDKeyName: options['tagIDKeyName'] || defaultOptions.tagIDKeyName, + contentKeyName: options['contentKeyName'] || defaultOptions.contentKeyName, + metaTemplateKeyName: options['metaTemplateKeyName'] || defaultOptions.metaTemplateKeyName, + debounceWait: isUndefined(options['debounceWait']) ? defaultOptions.debounceWait : options['debounceWait'], + waitOnDestroyed: isUndefined(options['waitOnDestroyed']) ? defaultOptions.waitOnDestroyed : options['waitOnDestroyed'], + ssrAppId: options['ssrAppId'] || defaultOptions.ssrAppId, + refreshOnceOnNavigation: !!options['refreshOnceOnNavigation'] + }; + /* eslint-enable dot-notation */ } function getOptions(options) { var optionsCopy = {}; @@ -471,22 +498,6 @@ function getOptions(options) { return optionsCopy; } -function pause() { - var refresh = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - this.$root._vueMeta.paused = true; - return function () { - return resume(refresh); - }; -} -function resume() { - var refresh = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - this.$root._vueMeta.paused = false; - - if (refresh) { - return this.$root.$meta().refresh(); - } -} - /* * To reduce build size, this file provides simple polyfills without * overly excessive type checking and without modifying @@ -495,11 +506,11 @@ function resume() { * Also, only files in client/ & shared/ should use these functions * files in server/ still use normal js function */ -function findIndex(array, predicate) { +function findIndex(array, predicate, thisArg) { 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[2], array[idx], idx, array)) { + if (predicate.call(thisArg, array[idx], idx, array)) { return idx; } } @@ -507,7 +518,7 @@ function findIndex(array, predicate) { return -1; } - return array.findIndex(predicate, arguments[2]); + return array.findIndex(predicate, thisArg); } function toArray(arg) { if ( !Array.from) { @@ -547,10 +558,11 @@ function escape(info, options, escapeOptions, escapeKeys) { if (includes(metaInfoOptionKeys, key)) { escaped[key] = value; continue; - } + } // do not use destructuring for disableOptionKeys, it increases transpiled size + // due to var checks while we are guaranteed the structure of the cb - var _disableOptionKeys = _slicedToArray(disableOptionKeys, 1), - disableKey = _disableOptionKeys[0]; + + var disableKey = disableOptionKeys[0]; if (escapeOptions[disableKey] && includes(escapeOptions[disableKey], key)) { // this info[key] doesnt need to escaped if the option is listed in __dangerouslyDisableSanitizers @@ -597,16 +609,14 @@ function escape(info, options, escapeOptions, escapeKeys) { return escaped; } -function escapeMetaInfo(options, info) { - var escapeSequences = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; +function escapeMetaInfo(options, info, escapeSequences) { + escapeSequences = escapeSequences || []; // do not use destructuring for seq, it increases transpiled size + // due to var checks while we are guaranteed the structure of the cb + var escapeOptions = { doEscape: function doEscape(value) { - return escapeSequences.reduce(function (val, _ref) { - var _ref2 = _slicedToArray(_ref, 2), - v = _ref2[0], - r = _ref2[1]; - - return val.replace(v, r); + return escapeSequences.reduce(function (val, seq) { + return val.replace(seq[0], seq[1]); }, value); } }; @@ -643,10 +653,7 @@ function applyTemplate(_ref, headObject, template, chunk) { if (!template) { // cleanup faulty template properties - if (headObject.hasOwnProperty(metaTemplateKeyName)) { - delete headObject[metaTemplateKeyName]; - } - + delete headObject[metaTemplateKeyName]; return false; } @@ -692,7 +699,7 @@ function _arrayMerge(_ref, target, source) { // which means we keep the targetItem and ignore/remove the sourceItem - if (sourceItem.hasOwnProperty(contentKeyName) && sourceItem[contentKeyName] === undefined || sourceItem.hasOwnProperty('innerHTML') && sourceItem.innerHTML === undefined) { + if (contentKeyName in sourceItem && sourceItem[contentKeyName] === undefined || 'innerHTML' in sourceItem && sourceItem.innerHTML === undefined) { destination.push(targetItem); // remove current index from source array so its not concatenated to destination below source.splice(sourceIndex, 1); @@ -739,13 +746,13 @@ function _arrayMerge(_ref, target, source) { }); return destination.concat(source); } -function merge(target, source) { - var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - - // remove properties explicitly set to false so child components can +var warningShown = false; +function merge(target, source, options) { + options = 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) - if (source.hasOwnProperty('title') && source.title === undefined) { + + if (source.title === undefined) { delete source.title; } @@ -755,9 +762,10 @@ function merge(target, source) { } for (var key in source[attrKey]) { - if (source[attrKey].hasOwnProperty(key) && source[attrKey][key] === undefined) { - if (includes(booleanHtmlAttributes, key)) { + if (key in source[attrKey] && source[attrKey][key] === undefined) { + if (includes(booleanHtmlAttributes, key) && !warningShown) { warn('VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details'); + warningShown = true; } delete source[attrKey][key]; @@ -771,10 +779,8 @@ function merge(target, source) { }); } -function getComponentMetaInfo() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var component = arguments.length > 1 ? arguments[1] : undefined; - return getComponentOption(options, component, defaultInfo); +function getComponentMetaInfo(options, component) { + return getComponentOption(options || {}, component, defaultInfo); } /** * Returns the `opts.option` $option value of the given `opts.component`. @@ -791,26 +797,26 @@ function getComponentMetaInfo() { * @return {Object} result - final aggregated result */ -function getComponentOption() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var component = arguments.length > 1 ? arguments[1] : undefined; - var result = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - var keyName = options.keyName; - var $options = component.$options, - $children = component.$children; +function getComponentOption(options, component, result) { + result = result || {}; if (component._inactive) { return result; - } // only collect option data if it exists + } + options = options || {}; + var _options = options, + keyName = _options.keyName; + var $metaInfo = component.$metaInfo, + $options = component.$options, + $children = component.$children; // only collect option data if it exists if ($options[keyName]) { - var data = $options[keyName]; // 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 - + // if $metaInfo exists then [keyName] was defined as a function + // and set to the computed prop $metaInfo in the mixin + // using the computed prop should be a small performance increase + // because Vue caches those internally + var data = $metaInfo || $options[keyName]; // ignore data if its not an object, then we keep our previous result if (!isObject(data)) { return result; @@ -836,63 +842,9 @@ function getComponentOption() { return result; } -/** - * Returns the correct meta info for the given component - * (child components will overwrite parent meta info) - * - * @param {Object} component - the Vue instance to get meta info from - * @return {Object} - returned meta info - */ - -function getMetaInfo() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var info = arguments.length > 1 ? arguments[1] : undefined; - var escapeSequences = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; - var component = arguments.length > 3 ? arguments[3] : undefined; - var tagIDKeyName = options.tagIDKeyName; // 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 - - - 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 - // as the other tags - - - if (info.base) { - info.base = Object.keys(info.base).length ? [info.base] : []; - } - - if (info.meta) { - // remove meta items with duplicate vmid's - info.meta = info.meta.filter(function (metaItem, index, arr) { - var hasVmid = metaItem.hasOwnProperty(tagIDKeyName); - - if (!hasVmid) { - return true; - } - - var isFirstItemForVmid = index === findIndex(arr, function (item) { - return item[tagIDKeyName] === metaItem[tagIDKeyName]; - }); - return isFirstItemForVmid; - }); // apply templates if needed - - info.meta.forEach(function (metaObject) { - return applyTemplate(options, metaObject); - }); - } - - return escapeMetaInfo(options, info, escapeSequences); -} - +var querySelector = function querySelector(arg, el) { + return (el || document).querySelectorAll(arg); +}; function getTag(tags, tag) { if (!tags[tag]) { tags[tag] = document.getElementsByTagName(tag)[0]; @@ -905,12 +857,12 @@ function getElementsKey(_ref) { pbody = _ref.pbody; return body ? 'body' : pbody ? 'pbody' : 'head'; } -function queryElements(parentNode, _ref2) { +function queryElements(parentNode, _ref2, attributes) { var appId = _ref2.appId, attribute = _ref2.attribute, type = _ref2.type, tagIDKeyName = _ref2.tagIDKeyName; - var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + attributes = attributes || {}; var queries = ["".concat(type, "[").concat(attribute, "=\"").concat(appId, "\"]"), "".concat(type, "[data-").concat(tagIDKeyName, "]")].map(function (query) { for (var key in attributes) { var val = attributes[key]; @@ -920,13 +872,21 @@ function queryElements(parentNode, _ref2) { return query; }); - return toArray(parentNode.querySelectorAll(queries.join(', '))); + return toArray(querySelector(queries.join(', '), parentNode)); +} +function removeElementsByAppId(_ref3, appId) { + var attribute = _ref3.attribute; + toArray(querySelector("[".concat(attribute, "=\"").concat(appId, "\"]"))).map(function (el) { + return el.remove(); + }); +} +function removeAttribute(el, attributeName) { + el.removeAttribute(attributeName); } var callbacks = []; -function isDOMComplete() { - var d = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document; - return d.readyState === 'complete'; +function isDOMComplete(d) { + return (d || document).readyState === 'complete'; } function addCallback(query, callback) { if (arguments.length === 1) { @@ -968,16 +928,16 @@ function addListeners() { }; } function applyCallbacks(matchElement) { - callbacks.forEach(function (_ref2) { - var _ref3 = _slicedToArray(_ref2, 2), - query = _ref3[0], - callback = _ref3[1]; - + callbacks.forEach(function (args) { + // do not use destructuring for args, it increases transpiled size + // due to var checks while we are guaranteed the structure of the cb + var query = args[0]; + var callback = args[1]; var selector = "".concat(query, "[onload=\"this.__vm_l=1\"]"); var elements = []; if (!matchElement) { - elements = toArray(document.querySelectorAll(selector)); + elements = toArray(querySelector(selector)); } if (matchElement && matchElement.matches(selector)) { @@ -1003,7 +963,7 @@ function applyCallbacks(matchElement) { * will fail isEqualNode on the client */ - element.removeAttribute('onload'); + removeAttribute(element, 'onload'); callback(element); }; /* IE9 doesnt seem to load scripts synchronously, @@ -1028,6 +988,9 @@ function applyCallbacks(matchElement) { }); } +// instead of adding it to the html + +var attributeMap = {}; /** * Updates the document's html tag attributes * @@ -1035,43 +998,62 @@ function applyCallbacks(matchElement) { * @param {HTMLElement} tag - the HTMLElement tag to update with new attrs */ -function updateAttribute() { - var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, +function updateAttribute(appId, options, type, attrs, tag) { + var _ref = options || {}, attribute = _ref.attribute; - var attrs = arguments.length > 1 ? arguments[1] : undefined; - var tag = arguments.length > 2 ? arguments[2] : undefined; var vueMetaAttrString = tag.getAttribute(attribute); - var vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : []; - var toRemove = toArray(vueMetaAttrs); - var keepIndexes = []; - for (var attr in attrs) { - if (attrs.hasOwnProperty(attr)) { - var value = includes(booleanHtmlAttributes, attr) ? '' : isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr]; - tag.setAttribute(attr, value || ''); + if (vueMetaAttrString) { + attributeMap[type] = JSON.parse(decodeURI(vueMetaAttrString)); + removeAttribute(tag, attribute); + } - if (!includes(vueMetaAttrs, attr)) { - vueMetaAttrs.push(attr); - } // filter below wont ever check -1 + var data = attributeMap[type] || {}; + var toUpdate = []; // remove attributes from the map + // which have been removed for this appId + for (var attr in data) { + if (data[attr] && appId in data[attr]) { + toUpdate.push(attr); - keepIndexes.push(toRemove.indexOf(attr)); + if (!attrs[attr]) { + delete data[attr][appId]; + } } } - var removedAttributesCount = toRemove.filter(function (el, index) { - return !includes(keepIndexes, index); - }).reduce(function (acc, attr) { - tag.removeAttribute(attr); - return acc + 1; - }, 0); + for (var _attr in attrs) { + var attrData = data[_attr]; - if (vueMetaAttrs.length === removedAttributesCount) { - tag.removeAttribute(attribute); - } else { - tag.setAttribute(attribute, vueMetaAttrs.sort().join(',')); + if (!attrData || attrData[appId] !== attrs[_attr]) { + toUpdate.push(_attr); + + if (attrs[_attr]) { + data[_attr] = data[_attr] || {}; + data[_attr][appId] = attrs[_attr]; + } + } } + + for (var _i = 0, _toUpdate = toUpdate; _i < _toUpdate.length; _i++) { + var _attr2 = _toUpdate[_i]; + var _attrData = data[_attr2]; + var attrValues = []; + + for (var _appId in _attrData) { + Array.prototype.push.apply(attrValues, [].concat(_attrData[_appId])); + } + + if (attrValues.length) { + var attrValue = includes(booleanHtmlAttributes, _attr2) && attrValues.some(Boolean) ? '' : attrValues.filter(Boolean).join(' '); + tag.setAttribute(_attr2, attrValue); + } else { + removeAttribute(tag, _attr2); + } + } + + attributeMap[type] = data; } /** @@ -1096,14 +1078,11 @@ function updateTitle(title) { * @return {Object} - a representation of what tags changed */ -function updateTag(appId) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var type = arguments.length > 2 ? arguments[2] : undefined; - var tags = arguments.length > 3 ? arguments[3] : undefined; - var head = arguments.length > 4 ? arguments[4] : undefined; - var body = arguments.length > 5 ? arguments[5] : undefined; - var attribute = options.attribute, - tagIDKeyName = options.tagIDKeyName; +function updateTag(appId, options, type, tags, head, body) { + var _ref = options || {}, + attribute = _ref.attribute, + tagIDKeyName = _ref.tagIDKeyName; + var dataAttributes = commonDataAttributes.slice(); dataAttributes.push(tagIDKeyName); var newElements = []; @@ -1143,21 +1122,20 @@ function updateTag(appId) { var newElement = document.createElement(type); newElement.setAttribute(attribute, appId); - - var _loop = function _loop(attr) { + Object.keys(tag).forEach(function (attr) { /* istanbul ignore next */ - if (!tag.hasOwnProperty(attr) || includes(tagProperties, attr)) { - return "continue"; + if (includes(tagProperties, attr)) { + return; } if (attr === 'innerHTML') { newElement.innerHTML = tag.innerHTML; - return "continue"; + return; } if (attr === 'json') { newElement.innerHTML = JSON.stringify(tag.json); - return "continue"; + return; } if (attr === 'cssText') { @@ -1168,7 +1146,7 @@ function updateTag(appId) { newElement.appendChild(document.createTextNode(tag.cssText)); } - return "continue"; + return; } if (attr === 'callback') { @@ -1176,7 +1154,7 @@ function updateTag(appId) { return tag[attr](newElement); }; - return "continue"; + return; } var _attr = includes(dataAttributes, attr) ? "data-".concat(attr) : attr; @@ -1184,19 +1162,12 @@ function updateTag(appId) { var isBooleanAttribute = includes(booleanHtmlAttributes, attr); if (isBooleanAttribute && !tag[attr]) { - return "continue"; + return; } var value = isBooleanAttribute ? '' : tag[attr]; newElement.setAttribute(_attr, value); - }; - - for (var attr in tag) { - var _ret = _loop(attr); - - if (_ret === "continue") continue; - } - + }); var oldElements = currentElements[getElementsKey(tag)]; // Remove a duplicate tag from domTagstoRemove, so it isn't cleared. var indexToDelete; @@ -1247,18 +1218,18 @@ function updateTag(appId) { * @param {Object} newInfo - the meta info to update to */ -function updateClientMetaInfo(appId) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var newInfo = arguments.length > 2 ? arguments[2] : undefined; - var ssrAttribute = options.ssrAttribute, - ssrAppId = options.ssrAppId; // only cache tags for current update +function updateClientMetaInfo(appId, options, newInfo) { + options = options || {}; + var _options = options, + ssrAttribute = _options.ssrAttribute, + ssrAppId = _options.ssrAppId; // only cache tags for current update var tags = {}; var htmlTag = getTag(tags, 'html'); // if this is a server render, then dont update 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 + removeAttribute(htmlTag, ssrAttribute); // add load callbacks if the var addLoadListeners = false; tagsSupportingOnload.forEach(function (type) { @@ -1275,8 +1246,8 @@ function updateClientMetaInfo(appId) { } // initialize tracked changes - var addedTags = {}; - var removedTags = {}; + var tagsAdded = {}; + var tagsRemoved = {}; for (var type in newInfo) { // ignore these @@ -1292,7 +1263,7 @@ function updateClientMetaInfo(appId) { if (includes(metaInfoAttributeKeys, type)) { var tagName = type.substr(0, 4); - updateAttribute(options, newInfo[type], getTag(tags, tagName)); + updateAttribute(appId, options, type, newInfo[type], getTag(tags, tagName)); continue; } // tags should always be an array, ignore if it isnt @@ -1306,46 +1277,189 @@ function updateClientMetaInfo(appId) { newTags = _updateTag.newTags; if (newTags.length) { - addedTags[type] = newTags; - removedTags[type] = oldTags; + tagsAdded[type] = newTags; + tagsRemoved[type] = oldTags; } } return { - addedTags: addedTags, - removedTags: removedTags + tagsAdded: tagsAdded, + tagsRemoved: tagsRemoved }; } -function _refresh() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; +var appsMetaInfo; +function addApp(rootVm, appId, options) { + return { + set: function set(metaInfo) { + return setMetaInfo(rootVm, appId, options, metaInfo); + }, + remove: function remove() { + return removeMetaInfo(rootVm, appId, options); + } + }; +} +function setMetaInfo(rootVm, appId, options, metaInfo) { + // if a vm exists _and_ its mounted then immediately update + if (rootVm && rootVm.$el) { + return updateClientMetaInfo(appId, options, metaInfo); + } // store for later, the info + // will be set on the first refresh - /** - * When called, will update the current meta info with new meta info. - * Useful when updating meta info as the result of an asynchronous - * action that resolves after the initial render takes place. - * - * Credit to [Sébastien Chopin](https://github.com/Atinux) for the suggestion - * to implement this method. - * - * @return {Object} - new meta info - */ - return function refresh() { - // collect & aggregate all metaInfo $options - var rawInfo = getComponentMetaInfo(options, this.$root); - var metaInfo = getMetaInfo(options, rawInfo, clientSequences, this.$root); - 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); + appsMetaInfo = appsMetaInfo || {}; + appsMetaInfo[appId] = metaInfo; +} +function removeMetaInfo(rootVm, appId, options) { + if (rootVm && rootVm.$el) { + var tags = {}; + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = metaInfoAttributeKeys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var type = _step.value; + var tagName = type.substr(0, 4); + updateAttribute(appId, options, type, {}, getTag(tags, tagName)); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return != null) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } } - return { - vm: this, - metaInfo: metaInfo, - tags: tags + return removeElementsByAppId(options, appId); + } + + if (appsMetaInfo[appId]) { + delete appsMetaInfo[appId]; + clearAppsMetaInfo(); + } +} +function getAppsMetaInfo() { + return appsMetaInfo; +} +function clearAppsMetaInfo(force) { + if (force || !Object.keys(appsMetaInfo).length) { + appsMetaInfo = undefined; + } +} + +/** + * Returns the correct meta info for the given component + * (child components will overwrite parent meta info) + * + * @param {Object} component - the Vue instance to get meta info from + * @return {Object} - returned meta info + */ + +function getMetaInfo(options, info, escapeSequences, component) { + options = options || {}; + escapeSequences = escapeSequences || []; + var _options = options, + tagIDKeyName = _options.tagIDKeyName; // 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 + + + 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 + // as the other tags + + + if (info.base) { + info.base = Object.keys(info.base).length ? [info.base] : []; + } + + if (info.meta) { + // remove meta items with duplicate vmid's + info.meta = info.meta.filter(function (metaItem, index, arr) { + var hasVmid = !!metaItem[tagIDKeyName]; + + if (!hasVmid) { + return true; + } + + var isFirstItemForVmid = index === findIndex(arr, function (item) { + return item[tagIDKeyName] === metaItem[tagIDKeyName]; + }); + return isFirstItemForVmid; + }); // apply templates if needed + + info.meta.forEach(function (metaObject) { + return applyTemplate(options, metaObject); + }); + } + + return escapeMetaInfo(options, info, escapeSequences); +} + +/** + * When called, will update the current meta info with new meta info. + * Useful when updating meta info as the result of an asynchronous + * action that resolves after the initial render takes place. + * + * Credit to [Sébastien Chopin](https://github.com/Atinux) for the suggestion + * to implement this method. + * + * @return {Object} - new meta info + */ + +function refresh(rootVm, options) { + options = options || {}; // make sure vue-meta was initiated + + if (!rootVm[rootConfigKey]) { + showWarningNotSupported(); + return {}; + } // collect & aggregate all metaInfo $options + + + var rawInfo = getComponentMetaInfo(options, rootVm); + var metaInfo = getMetaInfo(options, rawInfo, clientSequences, rootVm); + var appId = rootVm[rootConfigKey].appId; + var tags = updateClientMetaInfo(appId, options, metaInfo); // emit "event" with new info + + if (tags && isFunction(metaInfo.changed)) { + metaInfo.changed(metaInfo, tags.tagsAdded, tags.tagsRemoved); + tags = { + addedTags: tags.tagsAdded, + removedTags: tags.tagsRemoved }; + } + + var appsMetaInfo = getAppsMetaInfo(); + + if (appsMetaInfo) { + for (var additionalAppId in appsMetaInfo) { + updateClientMetaInfo(additionalAppId, options, appsMetaInfo[additionalAppId]); + delete appsMetaInfo[additionalAppId]; + } + + clearAppsMetaInfo(true); + } + + return { + vm: rootVm, + metaInfo: metaInfo, + // eslint-disable-line object-shorthand + tags: tags }; } @@ -1357,37 +1471,36 @@ function _refresh() { * @return {Object} - the attribute generator */ -function attributeGenerator() { - var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, +function attributeGenerator(options, type, data, addSrrAttribute) { + var _ref = options || {}, attribute = _ref.attribute, ssrAttribute = _ref.ssrAttribute; - var type = arguments.length > 1 ? arguments[1] : undefined; - var data = arguments.length > 2 ? arguments[2] : undefined; - return { - text: function text(addSrrAttribute) { - var attributeStr = ''; - var watchedAttrs = []; + var attributeStr = ''; - for (var attr in data) { - if (data.hasOwnProperty(attr)) { - watchedAttrs.push(attr); - attributeStr += isUndefined(data[attr]) || booleanHtmlAttributes.includes(attr) ? attr : "".concat(attr, "=\"").concat(isArray(data[attr]) ? data[attr].join(' ') : data[attr], "\""); - attributeStr += ' '; - } - } + for (var attr in data) { + var attrData = data[attr]; + var attrValues = []; - if (attributeStr) { - attributeStr += "".concat(attribute, "=\"").concat(watchedAttrs.sort().join(','), "\""); - } - - if (type === 'htmlAttrs' && addSrrAttribute) { - return "".concat(ssrAttribute).concat(attributeStr ? ' ' : '').concat(attributeStr); - } - - return attributeStr; + for (var appId in attrData) { + attrValues.push.apply(attrValues, _toConsumableArray([].concat(attrData[appId]))); } - }; + + if (attrValues.length) { + attributeStr += booleanHtmlAttributes.includes(attr) && attrValues.some(Boolean) ? "".concat(attr) : "".concat(attr, "=\"").concat(attrValues.join(' '), "\""); + attributeStr += ' '; + } + } + + if (attributeStr) { + attributeStr += "".concat(attribute, "=\"").concat(encodeURI(JSON.stringify(data)), "\""); + } + + if (type === 'htmlAttrs' && addSrrAttribute) { + return "".concat(ssrAttribute).concat(attributeStr ? ' ' : '').concat(attributeStr); + } + + return attributeStr; } /** @@ -1397,21 +1510,15 @@ function attributeGenerator() { * @param {String} data - the title text * @return {Object} - the title generator */ -function titleGenerator() { - var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, - attribute = _ref.attribute; +function titleGenerator(options, type, data, generatorOptions) { + var _ref = generatorOptions || {}, + ln = _ref.ln; - var type = arguments.length > 1 ? arguments[1] : undefined; - var data = arguments.length > 2 ? arguments[2] : undefined; - return { - text: function text() { - if (!data) { - return ''; - } + if (!data) { + return ''; + } - return "<".concat(type, ">").concat(data, ""); - } - }; + return "<".concat(type, ">").concat(data, "").concat(ln ? '\n' : ''); } /** @@ -1422,90 +1529,88 @@ function titleGenerator() { * @return {Object} - the tag generator */ -function tagGenerator() { - var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, +function tagGenerator(options, type, tags, generatorOptions) { + var _ref = options || {}, ssrAppId = _ref.ssrAppId, attribute = _ref.attribute, tagIDKeyName = _ref.tagIDKeyName; - var type = arguments.length > 1 ? arguments[1] : undefined; - var tags = arguments.length > 2 ? arguments[2] : undefined; + var _ref2 = generatorOptions || {}, + appId = _ref2.appId, + _ref2$body = _ref2.body, + body = _ref2$body === void 0 ? false : _ref2$body, + _ref2$pbody = _ref2.pbody, + pbody = _ref2$pbody === void 0 ? false : _ref2$pbody, + _ref2$ln = _ref2.ln, + ln = _ref2$ln === void 0 ? false : _ref2$ln; + var dataAttributes = [tagIDKeyName].concat(_toConsumableArray(commonDataAttributes)); - return { - text: function text() { - var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, - _ref2$body = _ref2.body, - body = _ref2$body === void 0 ? false : _ref2$body, - _ref2$pbody = _ref2.pbody, - pbody = _ref2$pbody === void 0 ? false : _ref2$pbody; - if (!tags || !tags.length) { - return ''; - } // build a string containing all tags of this type + if (!tags || !tags.length) { + return ''; + } // build a string containing all tags of this type - return tags.reduce(function (tagsStr, tag) { - if (tag.skip) { - return tagsStr; - } - - var tagKeys = Object.keys(tag); - - if (tagKeys.length === 0) { - return tagsStr; // Bail on empty tag object - } - - if (Boolean(tag.body) !== body || Boolean(tag.pbody) !== pbody) { - return tagsStr; - } - - var attrs = tag.once ? '' : " ".concat(attribute, "=\"").concat(ssrAppId, "\""); // build a string containing all attributes of this tag - - for (var attr in tag) { - // these attributes are treated as children on the tag - if (tagAttributeAsInnerContent.includes(attr) || tagProperties.includes(attr)) { - continue; - } - - if (attr === 'callback') { - attrs += " onload=\"this.__vm_l=1\""; - continue; - } // these form the attribute list for this tag - - - var prefix = ''; - - if (dataAttributes.includes(attr)) { - prefix = 'data-'; - } - - var isBooleanAttr = !prefix && booleanHtmlAttributes.includes(attr); - - if (isBooleanAttr && !tag[attr]) { - continue; - } - - attrs += " ".concat(prefix).concat(attr) + (isBooleanAttr ? '' : "=\"".concat(tag[attr], "\"")); - } - - var json = ''; - - if (tag.json) { - json = JSON.stringify(tag.json); - } // grab child content from one of these attributes, if possible - - - var 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); // the final string for this specific tag - - return "".concat(tagsStr, "<").concat(type).concat(attrs).concat(!hasContent && hasEndTag ? '/' : '', ">") + (hasContent ? "".concat(content, "") : ''); - }, ''); + return tags.reduce(function (tagsStr, tag) { + if (tag.skip) { + return tagsStr; } - }; + + var tagKeys = Object.keys(tag); + + if (tagKeys.length === 0) { + return tagsStr; // Bail on empty tag object + } + + if (Boolean(tag.body) !== body || Boolean(tag.pbody) !== pbody) { + return tagsStr; + } + + var attrs = tag.once ? '' : " ".concat(attribute, "=\"").concat(appId || ssrAppId, "\""); // build a string containing all attributes of this tag + + for (var attr in tag) { + // these attributes are treated as children on the tag + if (tagAttributeAsInnerContent.includes(attr) || tagProperties.includes(attr)) { + continue; + } + + if (attr === 'callback') { + attrs += " onload=\"this.__vm_l=1\""; + continue; + } // these form the attribute list for this tag + + + var prefix = ''; + + if (dataAttributes.includes(attr)) { + prefix = 'data-'; + } + + var isBooleanAttr = !prefix && booleanHtmlAttributes.includes(attr); + + if (isBooleanAttr && !tag[attr]) { + continue; + } + + attrs += " ".concat(prefix).concat(attr) + (isBooleanAttr ? '' : "=\"".concat(tag[attr], "\"")); + } + + var json = ''; + + if (tag.json) { + json = JSON.stringify(tag.json); + } // grab child content from one of these attributes, if possible + + + var 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); // the final string for this specific tag + + return "".concat(tagsStr, "<").concat(type).concat(attrs).concat(!hasContent && hasEndTag ? '/' : '', ">") + (hasContent ? "".concat(content, "") : '') + (ln ? '\n' : ''); + }, ''); } /** @@ -1516,78 +1621,175 @@ function tagGenerator() { * @return {Object} - the new injector */ -function generateServerInjector(options, newInfo) { - for (var type in defaultInfo) { +function generateServerInjector(options, metaInfo) { + var serverInjector = { + data: metaInfo, + extraData: undefined, + addInfo: function addInfo(appId, metaInfo) { + this.extraData = this.extraData || {}; + this.extraData[appId] = metaInfo; + }, + callInjectors: function callInjectors(opts) { + var m = this.injectors; // only call title for the head + + return (opts.body || opts.pbody ? '' : m.title.text(opts)) + m.meta.text(opts) + m.link.text(opts) + m.style.text(opts) + m.script.text(opts) + m.noscript.text(opts); + }, + injectors: { + head: function head(ln) { + return serverInjector.callInjectors({ + ln: ln + }); + }, + bodyPrepend: function bodyPrepend(ln) { + return serverInjector.callInjectors({ + ln: ln, + pbody: true + }); + }, + bodyAppend: function bodyAppend(ln) { + return serverInjector.callInjectors({ + ln: ln, + body: true + }); + } + } + }; + + var _loop = function _loop(type) { if (metaInfoOptionKeys.includes(type)) { - continue; + return "continue"; } - if (type === 'title') { - newInfo[type] = titleGenerator(options, type, newInfo[type]); - continue; - } + serverInjector.injectors[type] = { + text: function text(arg) { + if (type === 'title') { + return titleGenerator(options, type, serverInjector.data[type], arg); + } - if (metaInfoAttributeKeys.includes(type)) { - newInfo[type] = attributeGenerator(options, type, newInfo[type]); - continue; - } + if (metaInfoAttributeKeys.includes(type)) { + var attributeData = {}; + var data = serverInjector.data[type]; - newInfo[type] = tagGenerator(options, type, newInfo[type]); + if (data) { + for (var attr in data) { + attributeData[attr] = _defineProperty({}, options.ssrAppId, data[attr]); + } + } + + return attributeGenerator(options, type, attributeData, arg); + } + + var str = tagGenerator(options, type, serverInjector.data[type], arg); + + return str; + } + }; + }; + + for (var type in defaultInfo) { + var _ret = _loop(type); + + if (_ret === "continue") continue; } - return newInfo; + return serverInjector; } -function _inject() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; +/** + * Converts the state of the meta info object such that each item + * can be compiled to a tag string on the server + * + * @vm {Object} - Vue instance - ideally the root component + * @return {Object} - server meta info with `toString` methods + */ - /** - * Converts the state of the meta info object such that each item - * can be compiled to a tag string on the server - * - * @this {Object} - Vue instance - ideally the root component - * @return {Object} - server meta info with `toString` methods - */ - return function inject() { - // collect & aggregate all metaInfo $options - var rawInfo = getComponentMetaInfo(options, this.$root); - var metaInfo = getMetaInfo(options, rawInfo, serverSequences, this.$root); // generate server injectors +function inject(rootVm, options) { + // make sure vue-meta was initiated + if (!rootVm[rootConfigKey]) { + showWarningNotSupported(); + return {}; + } // collect & aggregate all metaInfo $options - generateServerInjector(options, metaInfo); - return metaInfo; - }; + + var rawInfo = getComponentMetaInfo(options, rootVm); + var metaInfo = getMetaInfo(options, rawInfo, serverSequences, rootVm); // generate server injector + + var serverInjector = generateServerInjector(options, metaInfo); // add meta info from additional apps + + var appsMetaInfo = getAppsMetaInfo(); + + if (appsMetaInfo) { + for (var additionalAppId in appsMetaInfo) { + serverInjector.addInfo(additionalAppId, appsMetaInfo[additionalAppId]); + delete appsMetaInfo[additionalAppId]; + } + + clearAppsMetaInfo(true); + } + + return serverInjector.injectors; } -function _$meta() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - - var _refresh$1 = _refresh(options); - - var _inject$1 = _inject(options); +function $meta(options) { + options = options || {}; /** * Returns an injector for server-side rendering. * @this {Object} - the Vue instance (a root component) * @return {Object} - injector */ + var $root = this.$root; + return { + 'getOptions': function getOptions$1() { + return getOptions(options); + }, + 'setOptions': function setOptions(newOptions) { + var refreshNavKey = 'refreshOnceOnNavigation'; - return function $meta() { - return { - getOptions: function getOptions$1() { - return getOptions(options); - }, - refresh: _refresh$1.bind(this), - inject: _inject$1.bind(this), - pause: pause.bind(this), - resume: resume.bind(this) - }; + if (newOptions && newOptions[refreshNavKey]) { + options.refreshOnceOnNavigation = !!newOptions[refreshNavKey]; + addNavGuards($root); + } + + var debounceWaitKey = 'debounceWait'; + + if (newOptions && debounceWaitKey in newOptions) { + var debounceWait = parseInt(newOptions[debounceWaitKey]); + + if (!isNaN(debounceWait)) { + options.debounceWait = debounceWait; + } + } + + var waitOnDestroyedKey = 'waitOnDestroyed'; + + if (newOptions && waitOnDestroyedKey in newOptions) { + options.waitOnDestroyed = !!newOptions[waitOnDestroyedKey]; + } + }, + 'refresh': function refresh$1() { + return refresh($root, options); + }, + 'inject': function inject$1() { + return inject($root, options) ; + }, + 'pause': function pause$1() { + return pause($root); + }, + 'resume': function resume$1() { + return resume($root); + }, + 'addApp': function addApp$1(appId) { + return addApp($root, appId, options); + } }; } -function generate(rawInfo) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var metaInfo = getMetaInfo(setOptions(options), rawInfo, serverSequences); - return generateServerInjector(options, metaInfo); +function generate(rawInfo, options) { + options = setOptions(options); + var metaInfo = getMetaInfo(options, rawInfo, serverSequences); + var serverInjector = generateServerInjector(options, metaInfo); + return serverInjector.injectors; } /** @@ -1595,24 +1797,28 @@ function generate(rawInfo) { * @param {Function} Vue - the Vue constructor. */ -function install(Vue) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - +function install(Vue, options) { if (Vue.__vuemeta_installed) { return; } Vue.__vuemeta_installed = true; options = setOptions(options); - Vue.prototype.$meta = _$meta(options); + + Vue.prototype.$meta = function () { + return $meta.call(this, options); + }; + Vue.mixin(createMixin(Vue, options)); } var index = { version: version, install: install, - hasMetaInfo: hasMetaInfo, - generate: generate + generate: function generate$1(metaInfo) { + return generate(metaInfo) ; + }, + hasMetaInfo: hasMetaInfo }; export default index; diff --git a/dist/vue-meta.js b/dist/vue-meta.js index 321f8c7..b0857f4 100644 --- a/dist/vue-meta.js +++ b/dist/vue-meta.js @@ -1,9 +1,10 @@ /** - * vue-meta v2.2.2 + * vue-meta v2.3.0-beta.0 * (c) 2019 * - Declan de Wet * - Sébastien Chopin (@Atinux) - * - All the amazing contributors + * - Pim (@pimlie) + * - All the amazing contributors * @license MIT */ @@ -13,41 +14,7 @@ (global = global || self, global.VueMeta = factory()); }(this, function () { 'use strict'; - var version = "2.2.2"; - - // store an id to keep track of DOM updates - var 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 - if (!vm.$root._vueMeta.initialized && (vm.$root._vueMeta.initializing || hookName === 'watcher')) { - vm.$root._vueMeta.initialized = null; - } - - if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) { - // batch potential DOM updates to prevent extraneous re-rendering - batchUpdate(function () { - return vm.$meta().refresh(); - }); - } - } - /** - * Performs a batched update. - * - * @param {(null|Number)} id - the ID of this update - * @param {Function} callback - the update to perform - * @return {Number} id - a new ID - */ - - function batchUpdate(callback) { - var timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 10; - clearTimeout(batchId); - batchId = setTimeout(function () { - callback(); - }, timeout); - return batchId; - } + var version = "2.3.0-beta.0"; function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { @@ -63,44 +30,6 @@ return _typeof(obj); } - function _slicedToArray(arr, i) { - return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); - } - - function _arrayWithHoles(arr) { - if (Array.isArray(arr)) return arr; - } - - function _iterableToArrayLimit(arr, i) { - var _arr = []; - var _n = true; - var _d = false; - var _e = undefined; - - try { - for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { - _arr.push(_s.value); - - if (i && _arr.length === i) break; - } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i["return"] != null) _i["return"](); - } finally { - if (_d) throw _e; - } - } - - return _arr; - } - - function _nonIterableRest() { - throw new TypeError("Invalid attempt to destructure non-iterable instance"); - } - /** * checks if passed argument is an array * @param {any} arg - the object to check @@ -125,56 +54,6 @@ return typeof arg === 'string'; } - function ensureIsArray(arg, key) { - if (!key || !isObject(arg)) { - return isArray(arg) ? arg : []; - } - - if (!isArray(arg[key])) { - arg[key] = []; - } - - return arg; - } - function ensuredPush(object, key, el) { - ensureIsArray(object, key); - object[key].push(el); - } - - function hasMetaInfo() { - var vm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 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 - - function inMetaInfoBranch() { - var vm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this; - return vm && !isUndefined(vm._vueMeta); - } - - 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; - } - - vm.$root._vueMeta.navGuards = true; - var $router = vm.$root.$router; - var $meta = vm.$root.$meta(); - $router.beforeEach(function (to, from, next) { - $meta.pause(); - next(); - }); - $router.afterEach(function () { - var _$meta$resume = $meta.resume(), - metaInfo = _$meta$resume.metaInfo; - - if (metaInfo && metaInfo.afterNavigation && isFunction(metaInfo.afterNavigation)) { - metaInfo.afterNavigation(metaInfo); - } - }); - } - function hasGlobalWindowFn() { try { return !isUndefined(window); @@ -195,177 +74,13 @@ console.warn(str); } + var showWarningNotSupportedInBrowserBundle = function showWarningNotSupportedInBrowserBundle(method) { + return warn("".concat(method, " is not supported in browser builds")); + }; var showWarningNotSupported = function showWarningNotSupported() { return warn('This vue app/component has no vue-meta configuration'); }; - var appId = 1; - function createMixin(Vue, options) { - // for which Vue lifecycle hooks should the metaInfo be refreshed - var updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount']; // watch for client side component updates - - return { - beforeCreate: function beforeCreate() { - var _this = this; - - Object.defineProperty(this, '_hasMetaInfo', { - configurable: true, - get: function get() { - // Show deprecation warning once when devtools enabled - if (Vue.config.devtools && !this.$root._vueMeta.hasMetaInfoDeprecationWarningShown) { - 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 - // _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) { - return; - } - - if (!this.$root._vueMeta) { - this.$root._vueMeta = { - appId: appId - }; - appId++; - } // 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; - 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 - // 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.$watch('$metaInfo', function () { - _this.__metaInfo = undefined; - triggerUpdate(_this, 'watcher'); - }); - }); - } - } // 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 () { - // 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 = options.ssrAppId; - } - }); // we use the mounted hook here as on page load - - ensuredPush(this.$options, 'mounted', function () { - if (!_this.$root._vueMeta.initialized) { - // used in triggerUpdate to check if a change was triggered - // during initialization - _this.$root._vueMeta.initializing = true; // refresh meta in nextTick so all child components have loaded - - _this.$nextTick(function () { - var _this2 = this; - - var _this$$root$$meta$ref = this.$root.$meta().refresh(), - tags = _this$$root$$meta$ref.tags, - metaInfo = _this$$root$$meta$ref.metaInfo; // 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(_this2, 'initializing'); - }); - } - - this.$root._vueMeta.initialized = true; - 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 - - if (options.refreshOnceOnNavigation) { - addNavGuards(this); - } - } - } // do not trigger refresh on the server side - - - if (this.$isServer) { - return; - } // no need to add this hooks on server side - - - updateOnLifecycleHook.forEach(function (lifecycleHook) { - ensuredPush(_this.$options, lifecycleHook, function () { - return triggerUpdate(_this, lifecycleHook); - }); - }); - }, - // TODO: move back into beforeCreate when Vue issue is resolved - destroyed: function destroyed() { - var _this3 = this; - - // do not trigger refresh: - // - on the server side - // - when the component doesnt have a parent - // - doesnt have metaInfo defined - if (this.$isServer || !this.$parent || !hasMetaInfo(this)) { - return; - } // Wait that element is hidden before refreshing meta tags (to support animations) - - - var interval = setInterval(function () { - if (_this3.$el && _this3.$el.offsetParent !== null) { - return; - } - - clearInterval(interval); - triggerUpdate(_this3, 'destroyed'); - }, 50); - } - }; - } - /** * These are constant variables used throughout the application. */ @@ -384,10 +99,11 @@ script: [], noscript: [], __dangerouslyDisableSanitizers: [], - __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. - + __dangerouslyDisableSanitizersByTagID: {} }; + var rootConfigKey = '_vueMeta'; // 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 // manage and which it should ignore. @@ -405,7 +121,11 @@ var contentKeyName = 'content'; // The id used for the ssr app - var ssrAppId = 'ssr'; + var ssrAppId = 'ssr'; // How long meta update + + var debounceWait = 10; // How long meta update + + var waitOnDestroyed = true; var defaultOptions = { keyName: keyName, attribute: attribute, @@ -413,14 +133,18 @@ tagIDKeyName: tagIDKeyName, contentKeyName: contentKeyName, metaTemplateKeyName: metaTemplateKeyName, - ssrAppId: ssrAppId // List of metaInfo property keys which are configuration options (and dont generate html) + waitOnDestroyed: waitOnDestroyed, + debounceWait: debounceWait, + ssrAppId: ssrAppId + }; // might be a bit ugly, but minimizes the browser bundles a bit - }; - var metaInfoOptionKeys = ['titleChunk', 'titleTemplate', 'changed', '__dangerouslyDisableSanitizers', '__dangerouslyDisableSanitizersByTagID']; // The metaInfo property keys which are used to disable escaping + var defaultInfoKeys = Object.keys(defaultInfo); // The metaInfo property keys which are used to disable escaping - var disableOptionKeys = ['__dangerouslyDisableSanitizers', '__dangerouslyDisableSanitizersByTagID']; // List of metaInfo property keys which only generates attributes and no tags + var disableOptionKeys = [defaultInfoKeys[12], defaultInfoKeys[13]]; // List of metaInfo property keys which are configuration options (and dont generate html) - var metaInfoAttributeKeys = ['htmlAttrs', 'headAttrs', 'bodyAttrs']; // HTML elements which support the onload event + var metaInfoOptionKeys = [defaultInfoKeys[1], defaultInfoKeys[2], 'changed'].concat(disableOptionKeys); // List of metaInfo property keys which only generates attributes and no tags + + var metaInfoAttributeKeys = [defaultInfoKeys[3], defaultInfoKeys[4], defaultInfoKeys[5]]; // HTML elements which support the onload event var tagsSupportingOnload = ['link', 'style', 'script']; // HTML elements which dont have a head tag (shortened to our needs) var tagProperties = ['once', 'template']; // Attributes which should be added with data- prefix @@ -429,17 +153,305 @@ 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']; - function setOptions(options) { - // combine options - options = isObject(options) ? options : {}; + var batchId = null; + function triggerUpdate(_ref, rootVm, hookName) { + var debounceWait = _ref.debounceWait; - for (var key in defaultOptions) { - if (!options[key]) { - options[key] = defaultOptions[key]; - } + // 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 + if (!rootVm[rootConfigKey].initialized && (rootVm[rootConfigKey].initializing || hookName === 'watcher')) { + rootVm[rootConfigKey].initialized = null; } - return options; + if (rootVm[rootConfigKey].initialized && !rootVm[rootConfigKey].pausing) { + // batch potential DOM updates to prevent extraneous re-rendering + batchUpdate(function () { + return void rootVm.$meta().refresh(); + }, debounceWait); + } + } + /** + * Performs a batched update. + * + * @param {(null|Number)} id - the ID of this update + * @param {Function} callback - the update to perform + * @return {Number} id - a new ID + */ + + function batchUpdate(callback, timeout) { + timeout = timeout === undefined ? 10 : timeout; + + if (!timeout) { + callback(); + return; + } + + clearTimeout(batchId); + batchId = setTimeout(function () { + callback(); + }, timeout); + return batchId; + } + + function ensureIsArray(arg, key) { + if (!key || !isObject(arg)) { + return isArray(arg) ? arg : []; + } + + if (!isArray(arg[key])) { + arg[key] = []; + } + + return arg; + } + function ensuredPush(object, key, el) { + ensureIsArray(object, key); + object[key].push(el); + } + + function hasMetaInfo(vm) { + vm = vm || this; + return vm && (vm[rootConfigKey] === true || isObject(vm[rootConfigKey])); + } // a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has + + function inMetaInfoBranch(vm) { + vm = vm || this; + return vm && !isUndefined(vm[rootConfigKey]); + } + + function pause(rootVm, refresh) { + rootVm[rootConfigKey].pausing = true; + return function () { + return resume(rootVm, refresh); + }; + } + function resume(rootVm, refresh) { + rootVm[rootConfigKey].pausing = false; + + if (refresh || refresh === undefined) { + return rootVm.$meta().refresh(); + } + } + + function addNavGuards(rootVm) { + var router = rootVm.$router; // return when nav guards already added or no router exists + + if (rootVm[rootConfigKey].navGuards || !router) { + /* istanbul ignore next */ + return; + } + + rootVm[rootConfigKey].navGuards = true; + router.beforeEach(function (to, from, next) { + pause(rootVm); + next(); + }); + router.afterEach(function () { + var _resume = resume(rootVm), + metaInfo = _resume.metaInfo; + + if (metaInfo && isFunction(metaInfo.afterNavigation)) { + metaInfo.afterNavigation(metaInfo); + } + }); + } + + var appId = 1; + function createMixin(Vue, options) { + // for which Vue lifecycle hooks should the metaInfo be refreshed + var updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount']; // watch for client side component updates + + return { + beforeCreate: function beforeCreate() { + var rootKey = '$root'; + var $root = this[rootKey]; + var $options = this.$options; + Object.defineProperty(this, '_hasMetaInfo', { + configurable: true, + get: function get() { + // Show deprecation warning once when devtools enabled + if (Vue.config.devtools && !$root[rootConfigKey].deprecationWarningShown) { + warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead'); + $root[rootConfigKey].deprecationWarningShown = true; + } + + 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($options[options.keyName]) || $options[options.keyName] === null) { + return; + } + + if (!$root[rootConfigKey]) { + $root[rootConfigKey] = { + appId: appId + }; + appId++; + } // 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[rootConfigKey]) { + this[rootConfigKey] = true; + var parent = this.$parent; + + while (parent && parent !== $root) { + if (isUndefined(parent[rootConfigKey])) { + parent[rootConfigKey] = false; + } + + parent = parent.$parent; + } + } // coerce function-style metaInfo to a computed prop so we can observe + // it on creation + + + if (isFunction($options[options.keyName])) { + $options.computed = $options.computed || {}; + $options.computed.$metaInfo = $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($options, 'created', function () { + this.$watch('$metaInfo', function () { + triggerUpdate(options, this[rootKey], 'watcher'); + }); + }); + } + } // 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($root[rootConfigKey].initialized)) { + $root[rootConfigKey].initialized = this.$isServer; + + if (!$root[rootConfigKey].initialized) { + ensuredPush($options, 'beforeMount', function () { + var $root = this[rootKey]; // if this Vue-app was server rendered, set the appId to 'ssr' + // only one SSR app per page is supported + + if ($root.$el && $root.$el.nodeType === 1 && $root.$el.hasAttribute('data-server-rendered')) { + $root[rootConfigKey].appId = options.ssrAppId; + } + }); // we use the mounted hook here as on page load + + ensuredPush($options, 'mounted', function () { + var $root = this[rootKey]; + + if (!$root[rootConfigKey].initialized) { + // used in triggerUpdate to check if a change was triggered + // during initialization + $root[rootConfigKey].initializing = true; // refresh meta in nextTick so all child components have loaded + + this.$nextTick(function () { + var _$root$$meta$refresh = $root.$meta().refresh(), + tags = _$root$$meta$refresh.tags, + metaInfo = _$root$$meta$refresh.metaInfo; // 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 && $root[rootConfigKey].initialized === null) { + this.$nextTick(function () { + return triggerUpdate(options, $root, 'init'); + }); + } + + $root[rootConfigKey].initialized = true; + delete $root[rootConfigKey].initializing; // add the navigation guards if they havent been added yet + // they are needed for the afterNavigation callback + + if (!options.refreshOnceOnNavigation && metaInfo.afterNavigation) { + addNavGuards($root); + } + }); + } + }); // add the navigation guards if requested + + if (options.refreshOnceOnNavigation) { + addNavGuards($root); + } + } + } // do not trigger refresh on the server side + + + if (this.$isServer) { + return; + } // no need to add this hooks on server side + + + updateOnLifecycleHook.forEach(function (lifecycleHook) { + ensuredPush($options, lifecycleHook, function () { + triggerUpdate(options, this[rootKey], lifecycleHook); + }); + }); + }, + // TODO: move back into beforeCreate when Vue issue is resolved + destroyed: function destroyed() { + var _this = this; + + // do not trigger refresh: + // - when user configured to not wait for transitions on destroyed + // - when the component doesnt have a parent + // - doesnt have metaInfo defined + if (!this.$parent || !hasMetaInfo(this)) { + return; + } + + this.$nextTick(function () { + if (!options.waitOnDestroyed || !_this.$el || !_this.$el.offsetParent) { + triggerUpdate(options, _this.$root, 'destroyed'); + return; + } // Wait that element is hidden before refreshing meta tags (to support animations) + + + var interval = setInterval(function () { + if (_this.$el && _this.$el.offsetParent !== null) { + /* istanbul ignore next line */ + return; + } + + clearInterval(interval); + triggerUpdate(options, _this.$root, 'destroyed'); + }, 50); + }); + } + }; + } + + function setOptions(options) { + // combine options + options = isObject(options) ? options : {}; // The options are set like this so they can + // be minified by terser while keeping the + // user api intact + // terser --mangle-properties keep_quoted=strict + + /* eslint-disable dot-notation */ + + return { + keyName: options['keyName'] || defaultOptions.keyName, + attribute: options['attribute'] || defaultOptions.attribute, + ssrAttribute: options['ssrAttribute'] || defaultOptions.ssrAttribute, + tagIDKeyName: options['tagIDKeyName'] || defaultOptions.tagIDKeyName, + contentKeyName: options['contentKeyName'] || defaultOptions.contentKeyName, + metaTemplateKeyName: options['metaTemplateKeyName'] || defaultOptions.metaTemplateKeyName, + debounceWait: isUndefined(options['debounceWait']) ? defaultOptions.debounceWait : options['debounceWait'], + waitOnDestroyed: isUndefined(options['waitOnDestroyed']) ? defaultOptions.waitOnDestroyed : options['waitOnDestroyed'], + ssrAppId: options['ssrAppId'] || defaultOptions.ssrAppId, + refreshOnceOnNavigation: !!options['refreshOnceOnNavigation'] + }; + /* eslint-enable dot-notation */ } function getOptions(options) { var optionsCopy = {}; @@ -451,22 +463,6 @@ return optionsCopy; } - function pause() { - var refresh = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - this.$root._vueMeta.paused = true; - return function () { - return resume(refresh); - }; - } - function resume() { - var refresh = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - this.$root._vueMeta.paused = false; - - if (refresh) { - return this.$root.$meta().refresh(); - } - } - /* * To reduce build size, this file provides simple polyfills without * overly excessive type checking and without modifying @@ -475,11 +471,11 @@ * Also, only files in client/ & shared/ should use these functions * files in server/ still use normal js function */ - function findIndex(array, predicate) { + function findIndex(array, predicate, thisArg) { 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[2], array[idx], idx, array)) { + if (predicate.call(thisArg, array[idx], idx, array)) { return idx; } } @@ -487,7 +483,7 @@ return -1; } - return array.findIndex(predicate, arguments[2]); + return array.findIndex(predicate, thisArg); } function toArray(arg) { if ( !Array.from) { @@ -526,10 +522,11 @@ if (includes(metaInfoOptionKeys, key)) { escaped[key] = value; continue; - } + } // do not use destructuring for disableOptionKeys, it increases transpiled size + // due to var checks while we are guaranteed the structure of the cb - var _disableOptionKeys = _slicedToArray(disableOptionKeys, 1), - disableKey = _disableOptionKeys[0]; + + var disableKey = disableOptionKeys[0]; if (escapeOptions[disableKey] && includes(escapeOptions[disableKey], key)) { // this info[key] doesnt need to escaped if the option is listed in __dangerouslyDisableSanitizers @@ -576,16 +573,14 @@ return escaped; } - function escapeMetaInfo(options, info) { - var escapeSequences = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; + function escapeMetaInfo(options, info, escapeSequences) { + escapeSequences = escapeSequences || []; // do not use destructuring for seq, it increases transpiled size + // due to var checks while we are guaranteed the structure of the cb + var escapeOptions = { doEscape: function doEscape(value) { - return escapeSequences.reduce(function (val, _ref) { - var _ref2 = _slicedToArray(_ref, 2), - v = _ref2[0], - r = _ref2[1]; - - return val.replace(v, r); + return escapeSequences.reduce(function (val, seq) { + return val.replace(seq[0], seq[1]); }, value); } }; @@ -614,48 +609,24 @@ function isSpecial(value) { var stringValue = Object.prototype.toString.call(value); - return stringValue === '[object RegExp]' || stringValue === '[object Date]' || isReactElement(value); + return stringValue === '[object RegExp]' || stringValue === '[object Date]' || false; } // 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; - } - - function emptyTarget(val) { - return Array.isArray(val) ? [] : {}; - } - function cloneUnlessOtherwiseSpecified(value, options) { - 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 value; } function getMergeFunction(key, options) { - if (!options.customMerge) { + { return deepmerge; } - var customMerge = options.customMerge(key); + var customMerge = false(key); return typeof customMerge === 'function' ? customMerge : deepmerge; } - function getEnumerableOwnPropertySymbols(target) { - 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); } function mergeObject(target, source, options) { @@ -663,15 +634,15 @@ if (options.isMergeableObject(target)) { getKeys(target).forEach(function (key) { - destination[key] = cloneUnlessOtherwiseSpecified(target[key], options); + destination[key] = cloneUnlessOtherwiseSpecified(target[key]); }); } getKeys(source).forEach(function (key) { if (!options.isMergeableObject(source[key]) || !target[key]) { - destination[key] = cloneUnlessOtherwiseSpecified(source[key], options); + destination[key] = cloneUnlessOtherwiseSpecified(source[key]); } else { - destination[key] = getMergeFunction(key, options)(target[key], source[key], options); + destination[key] = getMergeFunction(key)(target[key], source[key], options); } }); return destination; @@ -679,31 +650,20 @@ function deepmerge(target, source, options) { options = options || {}; - options.arrayMerge = options.arrayMerge || defaultArrayMerge; + options.arrayMerge = options.arrayMerge; options.isMergeableObject = options.isMergeableObject || isMergeableObject; var sourceIsArray = Array.isArray(source); var targetIsArray = Array.isArray(target); var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray; if (!sourceAndTargetTypesMatch) { - return cloneUnlessOtherwiseSpecified(source, options); + return cloneUnlessOtherwiseSpecified(source); } 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'); - } - - return array.reduce(function (prev, next) { - return deepmerge(prev, next, options); - }, {}); - }; - var deepmerge_1 = deepmerge; var cjs = deepmerge_1; @@ -725,10 +685,7 @@ if (!template) { // cleanup faulty template properties - if (headObject.hasOwnProperty(metaTemplateKeyName)) { - delete headObject[metaTemplateKeyName]; - } - + delete headObject[metaTemplateKeyName]; return false; } @@ -774,7 +731,7 @@ // which means we keep the targetItem and ignore/remove the sourceItem - if (sourceItem.hasOwnProperty(contentKeyName) && sourceItem[contentKeyName] === undefined || sourceItem.hasOwnProperty('innerHTML') && sourceItem.innerHTML === undefined) { + if (contentKeyName in sourceItem && sourceItem[contentKeyName] === undefined || 'innerHTML' in sourceItem && sourceItem.innerHTML === undefined) { destination.push(targetItem); // remove current index from source array so its not concatenated to destination below source.splice(sourceIndex, 1); @@ -821,13 +778,13 @@ }); return destination.concat(source); } - function merge(target, source) { - var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - - // remove properties explicitly set to false so child components can + var warningShown = false; + function merge(target, source, options) { + options = 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) - if (source.hasOwnProperty('title') && source.title === undefined) { + + if (source.title === undefined) { delete source.title; } @@ -837,9 +794,10 @@ } for (var key in source[attrKey]) { - if (source[attrKey].hasOwnProperty(key) && source[attrKey][key] === undefined) { - if (includes(booleanHtmlAttributes, key)) { + if (key in source[attrKey] && source[attrKey][key] === undefined) { + if (includes(booleanHtmlAttributes, key) && !warningShown) { warn('VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details'); + warningShown = true; } delete source[attrKey][key]; @@ -853,10 +811,8 @@ }); } - function getComponentMetaInfo() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var component = arguments.length > 1 ? arguments[1] : undefined; - return getComponentOption(options, component, defaultInfo); + function getComponentMetaInfo(options, component) { + return getComponentOption(options || {}, component, defaultInfo); } /** * Returns the `opts.option` $option value of the given `opts.component`. @@ -873,26 +829,26 @@ * @return {Object} result - final aggregated result */ - function getComponentOption() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var component = arguments.length > 1 ? arguments[1] : undefined; - var result = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - var keyName = options.keyName; - var $options = component.$options, - $children = component.$children; + function getComponentOption(options, component, result) { + result = result || {}; if (component._inactive) { return result; - } // only collect option data if it exists + } + options = options || {}; + var _options = options, + keyName = _options.keyName; + var $metaInfo = component.$metaInfo, + $options = component.$options, + $children = component.$children; // only collect option data if it exists if ($options[keyName]) { - var data = $options[keyName]; // 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 - + // if $metaInfo exists then [keyName] was defined as a function + // and set to the computed prop $metaInfo in the mixin + // using the computed prop should be a small performance increase + // because Vue caches those internally + var data = $metaInfo || $options[keyName]; // ignore data if its not an object, then we keep our previous result if (!isObject(data)) { return result; @@ -918,63 +874,9 @@ return result; } - /** - * Returns the correct meta info for the given component - * (child components will overwrite parent meta info) - * - * @param {Object} component - the Vue instance to get meta info from - * @return {Object} - returned meta info - */ - - function getMetaInfo() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var info = arguments.length > 1 ? arguments[1] : undefined; - var escapeSequences = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; - var component = arguments.length > 3 ? arguments[3] : undefined; - var tagIDKeyName = options.tagIDKeyName; // 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 - - - 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 - // as the other tags - - - if (info.base) { - info.base = Object.keys(info.base).length ? [info.base] : []; - } - - if (info.meta) { - // remove meta items with duplicate vmid's - info.meta = info.meta.filter(function (metaItem, index, arr) { - var hasVmid = metaItem.hasOwnProperty(tagIDKeyName); - - if (!hasVmid) { - return true; - } - - var isFirstItemForVmid = index === findIndex(arr, function (item) { - return item[tagIDKeyName] === metaItem[tagIDKeyName]; - }); - return isFirstItemForVmid; - }); // apply templates if needed - - info.meta.forEach(function (metaObject) { - return applyTemplate(options, metaObject); - }); - } - - return escapeMetaInfo(options, info, escapeSequences); - } - + var querySelector = function querySelector(arg, el) { + return (el || document).querySelectorAll(arg); + }; function getTag(tags, tag) { if (!tags[tag]) { tags[tag] = document.getElementsByTagName(tag)[0]; @@ -987,12 +889,12 @@ pbody = _ref.pbody; return body ? 'body' : pbody ? 'pbody' : 'head'; } - function queryElements(parentNode, _ref2) { + function queryElements(parentNode, _ref2, attributes) { var appId = _ref2.appId, attribute = _ref2.attribute, type = _ref2.type, tagIDKeyName = _ref2.tagIDKeyName; - var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + attributes = attributes || {}; var queries = ["".concat(type, "[").concat(attribute, "=\"").concat(appId, "\"]"), "".concat(type, "[data-").concat(tagIDKeyName, "]")].map(function (query) { for (var key in attributes) { var val = attributes[key]; @@ -1002,13 +904,21 @@ return query; }); - return toArray(parentNode.querySelectorAll(queries.join(', '))); + return toArray(querySelector(queries.join(', '), parentNode)); + } + function removeElementsByAppId(_ref3, appId) { + var attribute = _ref3.attribute; + toArray(querySelector("[".concat(attribute, "=\"").concat(appId, "\"]"))).map(function (el) { + return el.remove(); + }); + } + function removeAttribute(el, attributeName) { + el.removeAttribute(attributeName); } var callbacks = []; - function isDOMComplete() { - var d = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document; - return d.readyState === 'complete'; + function isDOMComplete(d) { + return (d || document).readyState === 'complete'; } function addCallback(query, callback) { if (arguments.length === 1) { @@ -1050,16 +960,16 @@ }; } function applyCallbacks(matchElement) { - callbacks.forEach(function (_ref2) { - var _ref3 = _slicedToArray(_ref2, 2), - query = _ref3[0], - callback = _ref3[1]; - + callbacks.forEach(function (args) { + // do not use destructuring for args, it increases transpiled size + // due to var checks while we are guaranteed the structure of the cb + var query = args[0]; + var callback = args[1]; var selector = "".concat(query, "[onload=\"this.__vm_l=1\"]"); var elements = []; if (!matchElement) { - elements = toArray(document.querySelectorAll(selector)); + elements = toArray(querySelector(selector)); } if (matchElement && matchElement.matches(selector)) { @@ -1085,7 +995,7 @@ * will fail isEqualNode on the client */ - element.removeAttribute('onload'); + removeAttribute(element, 'onload'); callback(element); }; /* IE9 doesnt seem to load scripts synchronously, @@ -1110,6 +1020,9 @@ }); } + // instead of adding it to the html + + var attributeMap = {}; /** * Updates the document's html tag attributes * @@ -1117,43 +1030,62 @@ * @param {HTMLElement} tag - the HTMLElement tag to update with new attrs */ - function updateAttribute() { - var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, + function updateAttribute(appId, options, type, attrs, tag) { + var _ref = options || {}, attribute = _ref.attribute; - var attrs = arguments.length > 1 ? arguments[1] : undefined; - var tag = arguments.length > 2 ? arguments[2] : undefined; var vueMetaAttrString = tag.getAttribute(attribute); - var vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : []; - var toRemove = toArray(vueMetaAttrs); - var keepIndexes = []; - for (var attr in attrs) { - if (attrs.hasOwnProperty(attr)) { - var value = includes(booleanHtmlAttributes, attr) ? '' : isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr]; - tag.setAttribute(attr, value || ''); + if (vueMetaAttrString) { + attributeMap[type] = JSON.parse(decodeURI(vueMetaAttrString)); + removeAttribute(tag, attribute); + } - if (!includes(vueMetaAttrs, attr)) { - vueMetaAttrs.push(attr); - } // filter below wont ever check -1 + var data = attributeMap[type] || {}; + var toUpdate = []; // remove attributes from the map + // which have been removed for this appId + for (var attr in data) { + if (data[attr] && appId in data[attr]) { + toUpdate.push(attr); - keepIndexes.push(toRemove.indexOf(attr)); + if (!attrs[attr]) { + delete data[attr][appId]; + } } } - var removedAttributesCount = toRemove.filter(function (el, index) { - return !includes(keepIndexes, index); - }).reduce(function (acc, attr) { - tag.removeAttribute(attr); - return acc + 1; - }, 0); + for (var _attr in attrs) { + var attrData = data[_attr]; - if (vueMetaAttrs.length === removedAttributesCount) { - tag.removeAttribute(attribute); - } else { - tag.setAttribute(attribute, vueMetaAttrs.sort().join(',')); + if (!attrData || attrData[appId] !== attrs[_attr]) { + toUpdate.push(_attr); + + if (attrs[_attr]) { + data[_attr] = data[_attr] || {}; + data[_attr][appId] = attrs[_attr]; + } + } } + + for (var _i = 0, _toUpdate = toUpdate; _i < _toUpdate.length; _i++) { + var _attr2 = _toUpdate[_i]; + var _attrData = data[_attr2]; + var attrValues = []; + + for (var _appId in _attrData) { + Array.prototype.push.apply(attrValues, [].concat(_attrData[_appId])); + } + + if (attrValues.length) { + var attrValue = includes(booleanHtmlAttributes, _attr2) && attrValues.some(Boolean) ? '' : attrValues.filter(Boolean).join(' '); + tag.setAttribute(_attr2, attrValue); + } else { + removeAttribute(tag, _attr2); + } + } + + attributeMap[type] = data; } /** @@ -1178,14 +1110,11 @@ * @return {Object} - a representation of what tags changed */ - function updateTag(appId) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var type = arguments.length > 2 ? arguments[2] : undefined; - var tags = arguments.length > 3 ? arguments[3] : undefined; - var head = arguments.length > 4 ? arguments[4] : undefined; - var body = arguments.length > 5 ? arguments[5] : undefined; - var attribute = options.attribute, - tagIDKeyName = options.tagIDKeyName; + function updateTag(appId, options, type, tags, head, body) { + var _ref = options || {}, + attribute = _ref.attribute, + tagIDKeyName = _ref.tagIDKeyName; + var dataAttributes = commonDataAttributes.slice(); dataAttributes.push(tagIDKeyName); var newElements = []; @@ -1225,21 +1154,20 @@ var newElement = document.createElement(type); newElement.setAttribute(attribute, appId); - - var _loop = function _loop(attr) { + Object.keys(tag).forEach(function (attr) { /* istanbul ignore next */ - if (!tag.hasOwnProperty(attr) || includes(tagProperties, attr)) { - return "continue"; + if (includes(tagProperties, attr)) { + return; } if (attr === 'innerHTML') { newElement.innerHTML = tag.innerHTML; - return "continue"; + return; } if (attr === 'json') { newElement.innerHTML = JSON.stringify(tag.json); - return "continue"; + return; } if (attr === 'cssText') { @@ -1250,7 +1178,7 @@ newElement.appendChild(document.createTextNode(tag.cssText)); } - return "continue"; + return; } if (attr === 'callback') { @@ -1258,7 +1186,7 @@ return tag[attr](newElement); }; - return "continue"; + return; } var _attr = includes(dataAttributes, attr) ? "data-".concat(attr) : attr; @@ -1266,19 +1194,12 @@ var isBooleanAttribute = includes(booleanHtmlAttributes, attr); if (isBooleanAttribute && !tag[attr]) { - return "continue"; + return; } var value = isBooleanAttribute ? '' : tag[attr]; newElement.setAttribute(_attr, value); - }; - - for (var attr in tag) { - var _ret = _loop(attr); - - if (_ret === "continue") continue; - } - + }); var oldElements = currentElements[getElementsKey(tag)]; // Remove a duplicate tag from domTagstoRemove, so it isn't cleared. var indexToDelete; @@ -1329,18 +1250,18 @@ * @param {Object} newInfo - the meta info to update to */ - function updateClientMetaInfo(appId) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var newInfo = arguments.length > 2 ? arguments[2] : undefined; - var ssrAttribute = options.ssrAttribute, - ssrAppId = options.ssrAppId; // only cache tags for current update + function updateClientMetaInfo(appId, options, newInfo) { + options = options || {}; + var _options = options, + ssrAttribute = _options.ssrAttribute, + ssrAppId = _options.ssrAppId; // only cache tags for current update var tags = {}; var htmlTag = getTag(tags, 'html'); // if this is a server render, then dont update 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 + removeAttribute(htmlTag, ssrAttribute); // add load callbacks if the var addLoadListeners = false; tagsSupportingOnload.forEach(function (type) { @@ -1357,8 +1278,8 @@ } // initialize tracked changes - var addedTags = {}; - var removedTags = {}; + var tagsAdded = {}; + var tagsRemoved = {}; for (var type in newInfo) { // ignore these @@ -1374,7 +1295,7 @@ if (includes(metaInfoAttributeKeys, type)) { var tagName = type.substr(0, 4); - updateAttribute(options, newInfo[type], getTag(tags, tagName)); + updateAttribute(appId, options, type, newInfo[type], getTag(tags, tagName)); continue; } // tags should always be an array, ignore if it isnt @@ -1388,82 +1309,244 @@ newTags = _updateTag.newTags; if (newTags.length) { - addedTags[type] = newTags; - removedTags[type] = oldTags; + tagsAdded[type] = newTags; + tagsRemoved[type] = oldTags; } } return { - addedTags: addedTags, - removedTags: removedTags + tagsAdded: tagsAdded, + tagsRemoved: tagsRemoved }; } - function _refresh() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var appsMetaInfo; + function addApp(rootVm, appId, options) { + return { + set: function set(metaInfo) { + return setMetaInfo(rootVm, appId, options, metaInfo); + }, + remove: function remove() { + return removeMetaInfo(rootVm, appId, options); + } + }; + } + function setMetaInfo(rootVm, appId, options, metaInfo) { + // if a vm exists _and_ its mounted then immediately update + if (rootVm && rootVm.$el) { + return updateClientMetaInfo(appId, options, metaInfo); + } // store for later, the info + // will be set on the first refresh - /** - * When called, will update the current meta info with new meta info. - * Useful when updating meta info as the result of an asynchronous - * action that resolves after the initial render takes place. - * - * Credit to [Sébastien Chopin](https://github.com/Atinux) for the suggestion - * to implement this method. - * - * @return {Object} - new meta info - */ - return function refresh() { - // collect & aggregate all metaInfo $options - var rawInfo = getComponentMetaInfo(options, this.$root); - var metaInfo = getMetaInfo(options, rawInfo, clientSequences, this.$root); - 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); + appsMetaInfo = appsMetaInfo || {}; + appsMetaInfo[appId] = metaInfo; + } + function removeMetaInfo(rootVm, appId, options) { + if (rootVm && rootVm.$el) { + var tags = {}; + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = metaInfoAttributeKeys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var type = _step.value; + var tagName = type.substr(0, 4); + updateAttribute(appId, options, type, {}, getTag(tags, tagName)); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return != null) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } } - return { - vm: this, - metaInfo: metaInfo, - tags: tags + return removeElementsByAppId(options, appId); + } + + if (appsMetaInfo[appId]) { + delete appsMetaInfo[appId]; + clearAppsMetaInfo(); + } + } + function getAppsMetaInfo() { + return appsMetaInfo; + } + function clearAppsMetaInfo(force) { + if (force || !Object.keys(appsMetaInfo).length) { + appsMetaInfo = undefined; + } + } + + /** + * Returns the correct meta info for the given component + * (child components will overwrite parent meta info) + * + * @param {Object} component - the Vue instance to get meta info from + * @return {Object} - returned meta info + */ + + function getMetaInfo(options, info, escapeSequences, component) { + options = options || {}; + escapeSequences = escapeSequences || []; + var _options = options, + tagIDKeyName = _options.tagIDKeyName; // 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 + + + 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 + // as the other tags + + + if (info.base) { + info.base = Object.keys(info.base).length ? [info.base] : []; + } + + if (info.meta) { + // remove meta items with duplicate vmid's + info.meta = info.meta.filter(function (metaItem, index, arr) { + var hasVmid = !!metaItem[tagIDKeyName]; + + if (!hasVmid) { + return true; + } + + var isFirstItemForVmid = index === findIndex(arr, function (item) { + return item[tagIDKeyName] === metaItem[tagIDKeyName]; + }); + return isFirstItemForVmid; + }); // apply templates if needed + + info.meta.forEach(function (metaObject) { + return applyTemplate(options, metaObject); + }); + } + + return escapeMetaInfo(options, info, escapeSequences); + } + + /** + * When called, will update the current meta info with new meta info. + * Useful when updating meta info as the result of an asynchronous + * action that resolves after the initial render takes place. + * + * Credit to [Sébastien Chopin](https://github.com/Atinux) for the suggestion + * to implement this method. + * + * @return {Object} - new meta info + */ + + function refresh(rootVm, options) { + options = options || {}; // make sure vue-meta was initiated + + if (!rootVm[rootConfigKey]) { + showWarningNotSupported(); + return {}; + } // collect & aggregate all metaInfo $options + + + var rawInfo = getComponentMetaInfo(options, rootVm); + var metaInfo = getMetaInfo(options, rawInfo, clientSequences, rootVm); + var appId = rootVm[rootConfigKey].appId; + var tags = updateClientMetaInfo(appId, options, metaInfo); // emit "event" with new info + + if (tags && isFunction(metaInfo.changed)) { + metaInfo.changed(metaInfo, tags.tagsAdded, tags.tagsRemoved); + tags = { + addedTags: tags.tagsAdded, + removedTags: tags.tagsRemoved }; + } + + var appsMetaInfo = getAppsMetaInfo(); + + if (appsMetaInfo) { + for (var additionalAppId in appsMetaInfo) { + updateClientMetaInfo(additionalAppId, options, appsMetaInfo[additionalAppId]); + delete appsMetaInfo[additionalAppId]; + } + + clearAppsMetaInfo(true); + } + + return { + vm: rootVm, + metaInfo: metaInfo, + // eslint-disable-line object-shorthand + tags: tags }; } - function _$meta() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - - var _refresh$1 = _refresh(options); - - var inject = function inject() {}; + function $meta(options) { + options = options || {}; /** * Returns an injector for server-side rendering. * @this {Object} - the Vue instance (a root component) * @return {Object} - injector */ + var $root = this.$root; + return { + 'getOptions': function getOptions$1() { + return getOptions(options); + }, + 'setOptions': function setOptions(newOptions) { + var refreshNavKey = 'refreshOnceOnNavigation'; - return function $meta() { - if (!this.$root._vueMeta) { - return { - getOptions: showWarningNotSupported, - refresh: showWarningNotSupported, - inject: showWarningNotSupported, - pause: showWarningNotSupported, - resume: showWarningNotSupported - }; + if (newOptions && newOptions[refreshNavKey]) { + options.refreshOnceOnNavigation = !!newOptions[refreshNavKey]; + addNavGuards($root); + } + + var debounceWaitKey = 'debounceWait'; + + if (newOptions && debounceWaitKey in newOptions) { + var debounceWait = parseInt(newOptions[debounceWaitKey]); + + if (!isNaN(debounceWait)) { + options.debounceWait = debounceWait; + } + } + + var waitOnDestroyedKey = 'waitOnDestroyed'; + + if (newOptions && waitOnDestroyedKey in newOptions) { + options.waitOnDestroyed = !!newOptions[waitOnDestroyedKey]; + } + }, + 'refresh': function refresh$1() { + return refresh($root, options); + }, + 'inject': function inject() { + return showWarningNotSupportedInBrowserBundle('inject'); + }, + 'pause': function pause$1() { + return pause($root); + }, + 'resume': function resume$1() { + return resume($root); + }, + 'addApp': function addApp$1(appId) { + return addApp($root, appId, options); } - - return { - getOptions: function getOptions$1() { - return getOptions(options); - }, - refresh: _refresh$1.bind(this), - inject: inject, - pause: pause.bind(this), - resume: resume.bind(this) - }; }; } @@ -1472,31 +1555,30 @@ * @param {Function} Vue - the Vue constructor. */ - function install(Vue) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - + function install(Vue, options) { if (Vue.__vuemeta_installed) { return; } Vue.__vuemeta_installed = true; options = setOptions(options); - Vue.prototype.$meta = _$meta(options); + + Vue.prototype.$meta = function () { + return $meta.call(this, options); + }; + Vue.mixin(createMixin(Vue, options)); - } // automatic install - - - if (!isUndefined(window) && !isUndefined(window.Vue)) { - /* istanbul ignore next */ - install(window.Vue); } - var browser = { + var index = { version: version, install: install, + generate: function generate(metaInfo) { + return showWarningNotSupportedInBrowserBundle('generate'); + }, hasMetaInfo: hasMetaInfo }; - return browser; + return index; })); diff --git a/dist/vue-meta.min.js b/dist/vue-meta.min.js index ed4a86e..c15da15 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){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:10;clearTimeout(e),e=setTimeout(function(){t()},n)}(function(){return t.$meta().refresh()})}function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function r(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=[],r=!0,o=!1,i=void 0;try{for(var a,u=e[Symbol.iterator]();!(r=(a=u.next()).done)&&(n.push(a.value),!t||n.length!==t);r=!0);}catch(e){o=!0,i=e}finally{try{r||null==u.return||u.return()}finally{if(o)throw i}}return n}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}function o(e){return Array.isArray(e)}function i(e){return void 0===e}function a(e){return"object"===n(e)}function u(e){return"object"===n(e)&&null!==e}function c(e){return"function"==typeof e}function s(e,t){return t&&a(e)?(o(e[t])||(e[t]=[]),e):o(e)?e:[]}function f(e,t,n){s(e,t),e[t].push(n)}function l(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this;return e&&(!0===e._vueMeta||a(e._vueMeta))}function d(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&&c(e.afterNavigation)&&e.afterNavigation(e)})}}var v=(function(){try{return!i(window)}catch(e){return!1}}()?window:global).console||{};function h(e){v&&v.warn&&v.warn(e)}var p=function(){return h("This vue app/component has no vue-meta configuration")},m=1;var y={title:void 0,titleChunk:"",titleTemplate:"%s",htmlAttrs:{},bodyAttrs:{},headAttrs:{},base:[],link:[],meta:[],style:[],script:[],noscript:[],__dangerouslyDisableSanitizers:[],__dangerouslyDisableSanitizersByTagID:{}},g={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"],$=["__dangerouslyDisableSanitizers","__dangerouslyDisableSanitizersByTagID"],_=["htmlAttrs","headAttrs","bodyAttrs"],M=["link","style","script"],A=["once","template"],T=["body","pbody"],I=["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 w(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];return this.$root._vueMeta.paused=!0,function(){return N(e)}}function N(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];if(this.$root._vueMeta.paused=!1,e)return this.$root.$meta().refresh()}function S(e,t){if(!Array.prototype.findIndex){for(var n=0;n/g,">"],[/"/g,'"'],[/'/g,"'"]];function k(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],i={doEscape:function(e){return n.reduce(function(e,t){var n=r(t,2),o=n[0],i=n[1];return e.replace(o,i)},e)}};return $.forEach(function(e,n){if(0===n)s(t,e);else if(1===n)for(var r in t[e])s(t[e],r);i[e]=t[e]}),function e(t,n,i,a){var c=n.tagIDKeyName,s=i.doEscape,f=void 0===s?function(e){return e}:s,l={};for(var d in t){var v=t[d];if(E(b,d))l[d]=v;else{var h=r($,1)[0];if(i[h]&&E(i[h],d))l[d]=v;else{var p=t[c];if(p&&(h=$[1],i[h]&&i[h][p]&&E(i[h][p],d)))l[d]=v;else if("string"==typeof v?l[d]=f(v):o(v)?l[d]=v.map(function(t){return u(t)?e(t,n,i,!0):f(t)}):u(v)?l[d]=e(v,n,i,!0):l[d]=v,a){var m=f(d);d!==m&&(l[m]=l[d],delete l[d])}}}}return l}(t,e,i)}var D=function(e){return function(e){return!!e&&"object"===n(e)}(e)&&!function(e){var t=Object.prototype.toString.call(e);return"[object RegExp]"===t||"[object Date]"===t||function(e){return e.$$typeof===z}(e)}(e)};var z="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function K(e,t){return!1!==t.clone&&t.isMergeableObject(e)?L((n=e,Array.isArray(n)?[]:{}),e,t):e;var n}function x(e,t,n){return e.concat(t).map(function(e){return K(e,n)})}function P(e){return Object.keys(e).concat(function(e){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e).filter(function(t){return e.propertyIsEnumerable(t)}):[]}(e))}function C(e,t,n){var r={};return n.isMergeableObject(e)&&P(e).forEach(function(t){r[t]=K(e[t],n)}),P(t).forEach(function(o){n.isMergeableObject(t[o])&&e[o]?r[o]=function(e,t){if(!t.customMerge)return L;var n=t.customMerge(e);return"function"==typeof n?n:L}(o,n)(e[o],t[o],n):r[o]=K(t[o],n)}),r}function L(e,t,n){(n=n||{}).arrayMerge=n.arrayMerge||x,n.isMergeableObject=n.isMergeableObject||D;var r=Array.isArray(t);return r===Array.isArray(e)?r?n.arrayMerge(e,t,n):C(e,t,n):K(t,n)}L.all=function(e,t){if(!Array.isArray(e))throw new Error("first argument should be an array");return e.reduce(function(e,n){return L(e,n,t)},{})};var H=L;function B(e,t,n,r){var o=e.component,a=e.metaTemplateKeyName,u=e.contentKeyName;return!0!==n&&!0!==t[a]&&(i(n)&&t[a]&&(n=t[a],t[a]=!0),n?(i(r)&&(r=t[u]),t[u]=c(n)?n.call(o,r):n.replace(/%s/g,r),!0):(t.hasOwnProperty(a)&&delete t[a],!1))}function V(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return t.hasOwnProperty("title")&&void 0===t.title&&delete t.title,_.forEach(function(e){if(t[e])for(var n in t[e])t[e].hasOwnProperty(n)&&void 0===t[e][n]&&(E(I,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])}),H(e,t,{arrayMerge:function(e,t){return function(e,t,n){var r=e.component,o=e.tagIDKeyName,i=e.metaTemplateKeyName,a=e.contentKeyName,u=[];return t.length||n.length?(t.forEach(function(e,t){if(e[o]){var c=S(n,function(t){return t[o]===e[o]}),s=n[c];if(-1!==c){if(s.hasOwnProperty(a)&&void 0===s[a]||s.hasOwnProperty("innerHTML")&&void 0===s.innerHTML)return u.push(e),void n.splice(c,1);if(null!==s[a]&&null!==s.innerHTML){var f=e[i];if(f){if(!s[i])return B({component:r,metaTemplateKeyName:i,contentKeyName:a},s,f),void(s.template=!0);s[a]||B({component:r,metaTemplateKeyName:i,contentKeyName:a},s,void 0,e[a])}}else n.splice(c,1)}else u.push(e)}else u.push(e)}),u.concat(n)):u}(n,e,t)}})}function q(){return function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};var n=arguments.length>1?arguments[1]:void 0;var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};var o=t.keyName;var u=n.$options,s=n.$children;if(n._inactive)return r;if(u[o]){var f=u[o];if(c(f)&&(f=f.call(n)),!a(f))return r;r=V(r,f,t)}s.length&&s.forEach(function(n){(function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this;return e&&!i(e._vueMeta)})(n)&&(r=e(t,n,r))});return r}(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},arguments.length>1?arguments[1]:void 0,y)}function W(e,t){return e[t]||(e[t]=document.getElementsByTagName(t)[0]),e[t]}function G(e,t){var n=t.appId,r=t.attribute,o=t.type,i=t.tagIDKeyName,a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},u=["".concat(o,"[").concat(r,'="').concat(n,'"]'),"".concat(o,"[data-").concat(i,"]")].map(function(e){for(var t in a){var n=a[t],r=n&&!0!==n?'="'.concat(n,'"'):"";e+="[data-".concat(t).concat(r,"]")}return e});return O(e.querySelectorAll(u.join(", ")))}var J=[];function R(e,t,n,r){var o=e.tagIDKeyName,i=!1;return n.forEach(function(e){e[o]&&e.callback&&(i=!0,function(e,t){1===arguments.length&&(t=e,e=""),J.push([e,t])}("".concat(t,"[data-").concat(o,'="').concat(e[o],'"]'),e.callback))}),r&&i?F():i}function F(){!function(){return"complete"===(arguments.length>0&&void 0!==arguments[0]?arguments[0]:document).readyState}()?document.onreadystatechange=function(){Q()}:Q()}function Q(e){J.forEach(function(t){var n=r(t,2),o=n[0],i=n[1],a="".concat(o,'[onload="this.__vm_l=1"]'),u=[];e||(u=O(document.querySelectorAll(a))),e&&e.matches(a)&&(u=[e]),u.forEach(function(e){if(!e.__vm_cb){var t=function(){e.__vm_cb=!0,e.removeAttribute("onload"),i(e)};e.__vm_l?t():e.__vm_ev||(e.__vm_ev=!0,e.addEventListener("load",t))}})})}function U(){var e=(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}).attribute,t=arguments.length>1?arguments[1]:void 0,n=arguments.length>2?arguments[2]:void 0,r=n.getAttribute(e),i=r?r.split(","):[],a=O(i),u=[];for(var c in t)if(t.hasOwnProperty(c)){var s=E(I,c)?"":o(t[c])?t[c].join(" "):t[c];n.setAttribute(c,s||""),E(i,c)||i.push(c),u.push(a.indexOf(c))}var f=a.filter(function(e,t){return!E(u,t)}).reduce(function(e,t){return n.removeAttribute(t),e+1},0);i.length===f?n.removeAttribute(e):n.setAttribute(e,i.sort().join(","))}function X(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,r=arguments.length>3?arguments[3]:void 0,o=arguments.length>4?arguments[4]:void 0,i=arguments.length>5?arguments[5]:void 0,a=t.attribute,u=t.tagIDKeyName,c=T.slice();c.push(u);var s=[],f={appId:e,attribute:a,type:n,tagIDKeyName:u},l={head:G(o,f),pbody:G(i,f,{pbody:!0}),body:G(i,f,{body:!0})};if(r.length>1){var d=[];r=r.filter(function(e){var t=JSON.stringify(e),n=!E(d,t);return d.push(t),n})}r.forEach(function(t){if(!t.skip){var r=document.createElement(n);r.setAttribute(a,e);var o=function(e){if(!t.hasOwnProperty(e)||E(A,e))return"continue";if("innerHTML"===e)return r.innerHTML=t.innerHTML,"continue";if("json"===e)return r.innerHTML=JSON.stringify(t.json),"continue";if("cssText"===e)return r.styleSheet?r.styleSheet.cssText=t.cssText:r.appendChild(document.createTextNode(t.cssText)),"continue";if("callback"===e)return r.onload=function(){return t[e](r)},"continue";var n=E(c,e)?"data-".concat(e):e,o=E(I,e);if(o&&!t[e])return"continue";var i=o?"":t[e];r.setAttribute(n,i)};for(var i in t)o(i);var u,f=l[function(e){var t=e.body,n=e.pbody;return t?"body":n?"pbody":"head"}(t)];f.some(function(e,t){return u=t,r.isEqualNode(e)})&&(u||0===u)?f.splice(u,1):s.push(r)}});var v=[];for(var h in l)Array.prototype.push.apply(v,l[h]);return v.forEach(function(e){e.parentNode.removeChild(e)}),s.forEach(function(e){e.hasAttribute("data-body")?i.appendChild(e):e.hasAttribute("data-pbody")?i.insertBefore(e,i.firstChild):o.appendChild(e)}),{oldTags:v,newTags:s}}function Y(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return function(){var t=q(e,this.$root),n=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1?arguments[1]:void 0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=arguments.length>3?arguments[3]:void 0,o=e.tagIDKeyName;return t.title&&(t.titleChunk=t.title),t.titleTemplate&&"%s"!==t.titleTemplate&&B({component:r,contentKeyName:"title"},t,t.titleTemplate,t.titleChunk||""),t.base&&(t.base=Object.keys(t.base).length?[t.base]:[]),t.meta&&(t.meta=t.meta.filter(function(e,t,n){return!e.hasOwnProperty(o)||t===S(n,function(t){return t[o]===e[o]})}),t.meta.forEach(function(t){return B(e,t)})),k(e,t,n)}(e,t,j,this.$root),r=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,r=t.ssrAttribute,i=t.ssrAppId,a={},u=W(a,"html");if(e===i&&u.hasAttribute(r)){u.removeAttribute(r);var c=!1;return M.forEach(function(e){n[e]&&R(t,e,n[e])&&(c=!0)}),c&&F(),!1}var s,f={},l={};for(var d in n)if(!E(b,d))if("title"!==d){if(E(_,d)){var v=d.substr(0,4);U(t,n[d],W(a,v))}else if(o(n[d])){var h=X(e,t,d,n[d],W(a,"head"),W(a,"body")),p=h.oldTags,m=h.newTags;m.length&&(f[d]=m,l[d]=p)}}else((s=n.title)||""===s)&&(document.title=s);return{addedTags:f,removedTags:l}}(this.$root._vueMeta.appId,e,n);return r&&c(n.changed)&&n.changed(n,r.addedTags,r.removedTags),{vm:this,metaInfo:n,tags:r}}}function Z(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};e.__vuemeta_installed||(e.__vuemeta_installed=!0,n=function(e){for(var t in e=a(e)?e:{},g)e[t]||(e[t]=g[t]);return e}(n),e.prototype.$meta=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=Y(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:w.bind(this),resume:N.bind(this)}:{getOptions:p,refresh:p,inject:p,pause:p,resume:p}}}(n),e.mixin(function(e,n){var r=["activated","deactivated","beforeMount"];return{beforeCreate:function(){var o=this;if(Object.defineProperty(this,"_hasMetaInfo",{configurable:!0,get:function(){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),l(this)}}),!i(this.$options[n.keyName])&&null!==this.$options[n.keyName]){if(this.$root._vueMeta||(this.$root._vueMeta={appId:m},m++),!this._vueMeta){this._vueMeta=!0;for(var a=this.$parent;a&&a!==this.$root;)i(a._vueMeta)&&(a._vueMeta=!1),a=a.$parent}c(this.$options[n.keyName])&&(this.$options.computed||(this.$options.computed={}),this.$options.computed.$metaInfo=this.$options[n.keyName],this.$isServer||f(this.$options,"created",function(){o.$watch("$metaInfo",function(){o.__metaInfo=void 0,t(o,"watcher")})})),i(this.$root._vueMeta.initialized)&&(this.$root._vueMeta.initialized=this.$isServer,this.$root._vueMeta.initialized||(f(this.$options,"beforeMount",function(){o.$root.$el&&o.$root.$el.hasAttribute&&o.$root.$el.hasAttribute("data-server-rendered")&&(o.$root._vueMeta.appId=n.ssrAppId)}),f(this.$options,"mounted",function(){o.$root._vueMeta.initialized||(o.$root._vueMeta.initializing=!0,o.$nextTick(function(){var e=this,r=this.$root.$meta().refresh(),o=r.tags,i=r.metaInfo;!1===o&&null===this.$root._vueMeta.initialized&&this.$nextTick(function(){return t(e,"initializing")}),this.$root._vueMeta.initialized=!0,delete this.$root._vueMeta.initializing,!n.refreshOnceOnNavigation&&i.afterNavigation&&d(this)}))}),n.refreshOnceOnNavigation&&d(this))),this.$isServer||r.forEach(function(e){f(o.$options,e,function(){return t(o,e)})})}},destroyed:function(){var e=this;if(!this.$isServer&&this.$parent&&l(this))var n=setInterval(function(){e.$el&&null!==e.$el.offsetParent||(clearInterval(n),t(e,"destroyed"))},50)}}}(e,n)))}return i(window)||i(window.Vue)||Z(window.Vue),{version:"2.2.2",install:Z,hasMetaInfo:l}}); +!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(n=n||self).VueMeta=t()}(this,(function(){"use strict";function n(t){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(n){return typeof n}:function(n){return n&&"function"==typeof Symbol&&n.constructor===Symbol&&n!==Symbol.prototype?"symbol":typeof n})(t)}function t(n){return Array.isArray(n)}function e(n){return void 0===n}function r(t){return"object"===n(t)}function i(t){return"object"===n(t)&&null!==t}function o(n){return"function"==typeof n}var u=(function(){try{return!e(window)}catch(n){return!1}}()?window:global).console||{};function a(n){u&&u.warn&&u.warn(n)}var f=function(n){return a("".concat(n," is not supported in browser builds"))},c=function(){return a("This vue app/component has no vue-meta configuration")},s={title:void 0,titleChunk:"",titleTemplate:"%s",htmlAttrs:{},bodyAttrs:{},headAttrs:{},base:[],link:[],meta:[],style:[],script:[],noscript:[],__dangerouslyDisableSanitizers:[],__dangerouslyDisableSanitizersByTagID:{}},d="_vueMeta",v={t:"metaInfo",i:"data-vue-meta",o:"data-vue-meta-server-rendered",u:"vmid",s:"content",v:"template",l:!0,m:10,p:"ssr"},l=Object.keys(s),m=[l[12],l[13]],y=[l[1],l[2],"changed"].concat(m),p=[l[3],l[4],l[5]],b=["link","style","script"],h=["once","template"],g=["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"],A=null;function I(n,t,e){var r=n.m;t[d].h||!t[d].g&&"watcher"!==e||(t[d].h=null),t[d].h&&!t[d].N&&function(n,t){if(!(t=void 0===t?10:t))return void n();clearTimeout(A),A=setTimeout((function(){n()}),t)}((function(){t.$meta().refresh()}),r)}function O(n,e){return e&&r(n)?(t(n[e])||(n[e]=[]),n):t(n)?n:[]}function j(n,t,e){O(n,t),n[t].push(e)}function T(n){return(n=n||this)&&(!0===n[d]||r(n[d]))}function w(n,t){return n[d].N=!0,function(){return K(n,t)}}function K(n,t){if(n[d].N=!1,t||void 0===t)return n.$meta().refresh()}function M(n){var t=n.$router;!n[d].A&&t&&(n[d].A=!0,t.beforeEach((function(t,e,r){w(n),r()})),t.afterEach((function(){var t=K(n).metaInfo;t&&o(t.afterNavigation)&&t.afterNavigation(t)})))}var D=1;function S(n,t,e){if(!Array.prototype.findIndex){for(var r=0;r/g,">"],[/"/g,'"'],[/'/g,"'"]];function W(n,e,r){r=r||[];var o={I:function(n){return r.reduce((function(n,t){return n.replace(t[0],t[1])}),n)}};return m.forEach((function(n,t){if(0===t)O(e,n);else if(1===t)for(var r in e[n])O(e[n],r);o[n]=e[n]})),function n(e,r,o,u){var a=r.u,f=o.I,c=void 0===f?function(n){return n}:f,s={};for(var d in e){var v=e[d];if(k(y,d))s[d]=v;else{var l=m[0];if(o[l]&&k(o[l],d))s[d]=v;else{var p=e[a];if(p&&(l=m[1],o[l]&&o[l][p]&&k(o[l][p],d)))s[d]=v;else if("string"==typeof v?s[d]=c(v):t(v)?s[d]=v.map((function(t){return i(t)?n(t,r,o,!0):c(t)})):i(v)?s[d]=n(v,r,o,!0):s[d]=v,u){var b=c(d);d!==b&&(s[b]=s[d],delete s[d])}}}}return s}(e,n,o)}var z=function(t){return function(t){return!!t&&"object"===n(t)}(t)&&!function(n){var t=Object.prototype.toString.call(n);return"[object RegExp]"===t||"[object Date]"===t||!1}(t)};function B(n,t){return n}function J(n){return Object.keys(n)}function R(n,t,e){var r={};return e.O(n)&&J(n).forEach((function(t){r[t]=B(n[t])})),J(t).forEach((function(i){e.O(t[i])&&n[i]?r[i]=C(n[i],t[i],e):r[i]=B(t[i])})),r}function C(n,t,e){(e=e||{}).j=e.j,e.O=e.O||z;var r=Array.isArray(t);return r===Array.isArray(n)?r?e.j(n,t,e):R(n,t,e):B(t)}var E=C;function H(n,t,r,i){var u=n.component,a=n.v,f=n.s;return!0!==r&&!0!==t[a]&&(e(r)&&t[a]&&(r=t[a],t[a]=!0),r?(e(i)&&(i=t[f]),t[f]=o(r)?r.call(u,i):r.replace(/%s/g,i),!0):(delete t[a],!1))}var L=!1;function P(n,t,e){return e=e||{},void 0===t.title&&delete t.title,p.forEach((function(n){if(t[n])for(var e in t[n])e in t[n]&&void 0===t[n][e]&&(k(N,e)&&!L&&(a("VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details"),L=!0),delete t[n][e])})),E(n,t,{j:function(n,t){return function(n,t,e){var r=n.component,i=n.u,o=n.v,u=n.s,a=[];return t.length||e.length?(t.forEach((function(n,t){if(n[i]){var f=S(e,(function(t){return t[i]===n[i]})),c=e[f];if(-1!==f){if(u in c&&void 0===c[u]||"innerHTML"in c&&void 0===c.innerHTML)return a.push(n),void e.splice(f,1);if(null!==c[u]&&null!==c.innerHTML){var s=n[o];if(s){if(!c[o])return H({component:r,v:o,s:u},c,s),void(c.template=!0);c[u]||H({component:r,v:o,s:u},c,void 0,n[u])}}else e.splice(f,1)}else a.push(n)}else a.push(n)})),a.concat(e)):a}(e,n,t)}})}function V(n,t){return function n(t,i,o){o=o||{};if(i._inactive)return o;t=t||{};var u=t,a=u.t;var f=i.$metaInfo,c=i.$options,s=i.$children;if(c[a]){var v=f||c[a];if(!r(v))return o;o=P(o,v,t)}s.length&&s.forEach((function(r){(function(n){return(n=n||this)&&!e(n[d])})(r)&&(o=n(t,r,o))}));return o}(n||{},t,s)}var q=function(n,t){return(t||document).querySelectorAll(n)};function U(n,t){return n[t]||(n[t]=document.getElementsByTagName(t)[0]),n[t]}function $(n,t,e){var r=t.T,i=t.i,o=t.type,u=t.u;e=e||{};var a=["".concat(o,"[").concat(i,'="').concat(r,'"]'),"".concat(o,"[data-").concat(u,"]")].map((function(n){for(var t in e){var r=e[t],i=r&&!0!==r?'="'.concat(r,'"'):"";n+="[data-".concat(t).concat(i,"]")}return n}));return _(q(a.join(", "),n))}function F(n,t){n.removeAttribute(t)}var G=[];function Q(n,t,e,r){var i=n.u,o=!1;return e.forEach((function(n){n[i]&&n.callback&&(o=!0,function(n,t){1===arguments.length&&(t=n,n=""),G.push([n,t])}("".concat(t,"[data-").concat(i,'="').concat(n[i],'"]'),n.callback))})),r&&o?X():o}function X(){var n;"complete"!==(n||document).readyState?document.onreadystatechange=function(){Y()}:Y()}function Y(n){G.forEach((function(t){var e=t[0],r=t[1],i="".concat(e,'[onload="this.__vm_l=1"]'),o=[];n||(o=_(q(i))),n&&n.matches(i)&&(o=[n]),o.forEach((function(n){if(!n.__vm_cb){var t=function(){n.__vm_cb=!0,F(n,"onload"),r(n)};n.__vm_l?t():n.__vm_ev||(n.__vm_ev=!0,n.addEventListener("load",t))}}))}))}var Z,nn={};function tn(n,t,e,r,i){var o=(t||{}).i,u=i.getAttribute(o);u&&(nn[e]=JSON.parse(decodeURI(u)),F(i,o));var a=nn[e]||{},f=[];for(var c in a)a[c]&&n in a[c]&&(f.push(c),r[c]||delete a[c][n]);for(var s in r){var d=a[s];d&&d[n]===r[s]||(f.push(s),r[s]&&(a[s]=a[s]||{},a[s][n]=r[s]))}for(var v=0,l=f;v1){var l=[];r=r.filter((function(n){var t=JSON.stringify(n),e=!k(l,t);return l.push(t),e}))}r.forEach((function(t){if(!t.skip){var r=document.createElement(e);r.setAttribute(a,n),Object.keys(t).forEach((function(n){if(!k(h,n))if("innerHTML"!==n)if("json"!==n)if("cssText"!==n)if("callback"!==n){var e=k(c,n)?"data-".concat(n):n,i=k(N,n);if(!i||t[n]){var o=i?"":t[n];r.setAttribute(e,o)}}else r.onload=function(){return t[n](r)};else r.styleSheet?r.styleSheet.cssText=t.cssText:r.appendChild(document.createTextNode(t.cssText));else r.innerHTML=JSON.stringify(t.json);else r.innerHTML=t.innerHTML}));var i,o=v[function(n){var t=n.body,e=n.pbody;return t?"body":e?"pbody":"head"}(t)];o.some((function(n,t){return i=t,r.isEqualNode(n)}))&&(i||0===i)?o.splice(i,1):s.push(r)}}));var m=[];for(var y in v)Array.prototype.push.apply(m,v[y]);return m.forEach((function(n){n.parentNode.removeChild(n)})),s.forEach((function(n){n.hasAttribute("data-body")?o.appendChild(n):n.hasAttribute("data-pbody")?o.insertBefore(n,o.firstChild):i.appendChild(n)})),{oldTags:m,newTags:s}}function rn(n,e,r){var i=e=e||{},o=i.o,u=i.p,a={},f=U(a,"html");if(n===u&&f.hasAttribute(o)){F(f,o);var c=!1;return b.forEach((function(n){r[n]&&Q(e,n,r[n])&&(c=!0)})),c&&X(),!1}var s,d={},v={};for(var l in r)if(!k(y,l))if("title"!==l){if(k(p,l)){var m=l.substr(0,4);tn(n,e,l,r[l],U(a,m))}else if(t(r[l])){var h=en(n,e,l,r[l],U(a,"head"),U(a,"body")),g=h.oldTags,N=h.newTags;N.length&&(d[l]=N,v[l]=g)}}else((s=r.title)||""===s)&&(document.title=s);return{K:d,M:v}}function on(n,t,e){return{set:function(r){return function(n,t,e,r){if(n&&n.$el)return rn(t,e,r);(Z=Z||{})[t]=r}(n,t,e,r)},remove:function(){return function(n,t,e){if(n&&n.$el){var r={},i=!0,o=!1,u=void 0;try{for(var a,f=p[Symbol.iterator]();!(i=(a=f.next()).done);i=!0){var c=a.value,s=c.substr(0,4);tn(t,e,c,{},U(r,s))}}catch(n){o=!0,u=n}finally{try{i||null==f.return||f.return()}finally{if(o)throw u}}return function(n,t){var e=n.i;_(q("[".concat(e,'="').concat(t,'"]'))).map((function(n){return n.remove()}))}(e,t)}Z[t]&&(delete Z[t],an())}(n,t,e)}}}function un(){return Z}function an(n){!n&&Object.keys(Z).length||(Z=void 0)}function fn(n,t){if(t=t||{},!n[d])return c(),{};var e=function(n,t,e,r){e=e||[];var i=(n=n||{}).u;return t.title&&(t.titleChunk=t.title),t.titleTemplate&&"%s"!==t.titleTemplate&&H({component:r,s:"title"},t,t.titleTemplate,t.titleChunk||""),t.base&&(t.base=Object.keys(t.base).length?[t.base]:[]),t.meta&&(t.meta=t.meta.filter((function(n,t,e){return!n[i]||t===S(e,(function(t){return t[i]===n[i]}))})),t.meta.forEach((function(t){return H(n,t)}))),W(n,t,e)}(t,V(t,n),x,n),r=rn(n[d].T,t,e);r&&o(e.changed)&&(e.changed(e,r.K,r.M),r={addedTags:r.K,removedTags:r.M});var i=un();if(i){for(var u in i)rn(u,t,i[u]),delete i[u];an(!0)}return{vm:n,metaInfo:e,tags:r}}function cn(n){n=n||{};var t=this.$root;return{getOptions:function(){return function(n){var t={};for(var e in n)t[e]=n[e];return t}(n)},setOptions:function(e){e&&e.D&&(n.D=!!e.D,M(t));if(e&&"debounceWait"in e){var r=parseInt(e.m);isNaN(r)||(n.m=r)}e&&"waitOnDestroyed"in e&&(n.l=!!e.l)},refresh:function(){return fn(t,n)},inject:function(){return f("inject")},pause:function(){return w(t)},resume:function(){return K(t)},addApp:function(e){return on(t,e,n)}}}return{version:"2.3.0-beta.0",install:function(n,t){n.__vuemeta_installed||(n.__vuemeta_installed=!0,t=function(n){return{t:(n=r(n)?n:{}).keyName||v.t,i:n.attribute||v.i,o:n.ssrAttribute||v.o,u:n.tagIDKeyName||v.u,s:n.contentKeyName||v.s,v:n.metaTemplateKeyName||v.v,m:e(n.debounceWait)?v.m:n.debounceWait,l:e(n.waitOnDestroyed)?v.l:n.waitOnDestroyed,p:n.ssrAppId||v.p,D:!!n.refreshOnceOnNavigation}}(t),n.prototype.$meta=function(){return cn.call(this,t)},n.mixin(function(n,t){var r=["activated","deactivated","beforeMount"];return{beforeCreate:function(){var i=this.$root,u=this.$options;if(Object.defineProperty(this,"_hasMetaInfo",{configurable:!0,get:function(){return n.config.devtools&&!i[d].S&&(a("VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead"),i[d].S=!0),T(this)}}),!e(u[t.t])&&null!==u[t.t]){if(i[d]||(i[d]={T:D},D++),!this[d]){this[d]=!0;for(var f=this.$parent;f&&f!==i;)e(f[d])&&(f[d]=!1),f=f.$parent}o(u[t.t])&&(u.computed=u.computed||{},u.computed.$metaInfo=u[t.t],this.$isServer||j(u,"created",(function(){this.$watch("$metaInfo",(function(){I(t,this.$root,"watcher")}))}))),e(i[d].h)&&(i[d].h=this.$isServer,i[d].h||(j(u,"beforeMount",(function(){var n=this.$root;n.$el&&1===n.$el.nodeType&&n.$el.hasAttribute("data-server-rendered")&&(n[d].T=t.p)})),j(u,"mounted",(function(){var n=this.$root;n[d].h||(n[d].g=!0,this.$nextTick((function(){var e=n.$meta().refresh(),r=e.tags,i=e.metaInfo;!1===r&&null===n[d].h&&this.$nextTick((function(){return I(t,n,"init")})),n[d].h=!0,delete n[d].g,!t.D&&i.afterNavigation&&M(n)})))})),t.D&&M(i))),this.$isServer||r.forEach((function(n){j(u,n,(function(){I(t,this.$root,n)}))}))}},destroyed:function(){var n=this;this.$parent&&T(this)&&this.$nextTick((function(){if(t.l&&n.$el&&n.$el.offsetParent)var e=setInterval((function(){n.$el&&null!==n.$el.offsetParent||(clearInterval(e),I(t,n.$root,"destroyed"))}),50);else I(t,n.$root,"destroyed")}))}}}(n,t)))},generate:function(n){return f("generate")},hasMetaInfo:T}})); diff --git a/package.json b/package.json index d91463d..b17e7a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-meta", - "version": "2.2.2", + "version": "2.3.0-beta.0", "description": "Manage HTML metadata in Vue.js components with ssr support", "keywords": [ "attribute",