import Vue from 'vue'; import { isSamePath as _isSamePath, joinURL, normalizeURL, withQuery, withoutTrailingSlash } from 'ufo'; // window.{{globals.loadedCallback}} hook // Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading) if (process.client) { window.onNuxtReadyCbs = []; window.onNuxtReady = (cb) => { window.onNuxtReadyCbs.push(cb); }; } export function createGetCounter(counterObject, defaultKey = '') { return function getCounter(id = defaultKey) { if (counterObject[id] === undefined) { counterObject[id] = 0; } return counterObject[id]++; }; } export function empty() {} export function globalHandleError(error) { if (Vue.config.errorHandler) { Vue.config.errorHandler(error); } } export function interopDefault(promise) { return promise.then((m) => m.default || m); } export function hasFetch(vm) { return vm.$options && typeof vm.$options.fetch === 'function' && !vm.$options.fetch.length; } export function purifyData(data) { if (process.env.NODE_ENV === 'production') { return data; } return Object.entries(data).filter( ([key, value]) => { const valid = !(value instanceof Function) && !(value instanceof Promise); if (!valid) { console.warn(`${ key } is not able to be stringified. This will break in a production environment.`); // eslint-disable-line no-console } return valid; } ).reduce((obj, [key, value]) => { obj[key] = value; return obj; }, {}); } export function getChildrenComponentInstancesUsingFetch(vm, instances = []) { const children = vm.$children || []; for (const child of children) { if (child.$fetch) { instances.push(child); continue; // Don't get the children since it will reload the template } if (child.$children) { getChildrenComponentInstancesUsingFetch(child, instances); } } return instances; } export function applyAsyncData(Component, asyncData) { if ( // For SSR, we once all this function without second param to just apply asyncData // Prevent doing this for each SSR request !asyncData && Component.options.__hasNuxtData ) { return; } const ComponentData = Component.options._originDataFn || Component.options.data || function() { return {}; }; Component.options._originDataFn = ComponentData; Component.options.data = function() { const data = ComponentData.call(this, this); if (this.$ssrContext) { asyncData = this.$ssrContext.asyncData[Component.cid]; } return { ...data, ...asyncData }; }; Component.options.__hasNuxtData = true; if (Component._Ctor && Component._Ctor.options) { Component._Ctor.options.data = Component.options.data; } } export function sanitizeComponent(Component) { // If Component already sanitized if (Component.options && Component._Ctor === Component) { return Component; } if (!Component.options) { Component = Vue.extend(Component); // fix issue #6 Component._Ctor = Component; } else { Component._Ctor = Component; Component.extendOptions = Component.options; } // If no component name defined, set file path as name, (also fixes #5703) if (!Component.options.name && Component.options.__file) { Component.options.name = Component.options.__file; } return Component; } export function getMatchedComponents(route, matches = false, prop = 'components') { return Array.prototype.concat.apply([], route.matched.map((m, index) => { return Object.keys(m[prop]).map((key) => { matches && matches.push(index); return m[prop][key]; }); })); } export function getMatchedComponentsInstances(route, matches = false) { return getMatchedComponents(route, matches, 'instances'); } export function flatMapComponents(route, fn) { return Array.prototype.concat.apply([], route.matched.map((m, index) => { return Object.keys(m.components).reduce((promises, key) => { if (m.components[key]) { promises.push(fn(m.components[key], m.instances[key], m, key, index)); } else { delete m.components[key]; } return promises; }, []); })); } export function resolveRouteComponents(route, fn) { return Promise.all( flatMapComponents(route, async(Component, instance, match, key) => { // If component is a function, resolve it if (typeof Component === 'function' && !Component.options) { try { Component = await Component(); } catch (error) { // Handle webpack chunk loading errors // This may be due to a new deployment or a network problem if ( error && error.name === 'ChunkLoadError' && typeof window !== 'undefined' && window.sessionStorage ) { const timeNow = Date.now(); const previousReloadTime = parseInt(window.sessionStorage.getItem('nuxt-reload')); // check for previous reload time not to reload infinitely if (!previousReloadTime || previousReloadTime + 60000 < timeNow) { window.sessionStorage.setItem('nuxt-reload', timeNow); window.location.reload(true /* skip cache */); } } throw error; } } match.components[key] = Component = sanitizeComponent(Component); return typeof fn === 'function' ? fn(Component, instance, match, key) : Component; }) ); } export async function getRouteData(route) { if (!route) { return; } // Make sure the components are resolved (code-splitting) await resolveRouteComponents(route); // Send back a copy of route with meta based on Component definition return { ...route, meta: getMatchedComponents(route).map((Component, index) => { return { ...Component.options.meta, ...(route.matched[index] || {}).meta }; }) }; } export async function setContext(app, context) { // If context not defined, create it if (!app.context) { app.context = { isStatic: process.static, isDev: true, isHMR: false, app, store: app.store, payload: context.payload, error: context.error, base: app.router.options.base, env: { commit: 'head', version: '0.1.2', dev: true, pl: 1, perfTest: false, rancherEnv: 'web', api: 'http://localhost:8989' } }; // Only set once if (context.req) { app.context.req = context.req; } if (context.res) { app.context.res = context.res; } if (context.ssrContext) { app.context.ssrContext = context.ssrContext; } app.context.redirect = (status, path, query) => { if (!status) { return; } app.context._redirected = true; // if only 1 or 2 arguments: redirect('/') or redirect('/', { foo: 'bar' }) let pathType = typeof path; if (typeof status !== 'number' && (pathType === 'undefined' || pathType === 'object')) { query = path || {}; path = status; pathType = typeof path; status = 302; } if (pathType === 'object') { path = app.router.resolve(path).route.fullPath; } // "/absolute/route", "./relative/route" or "../relative/route" if (/(^[.]{1,2}\/)|(^\/(?!\/))/.test(path)) { app.context.next({ path, query, status }); } else { path = withQuery(path, query); if (process.server) { app.context.next({ path, status }); } if (process.client) { // https://developer.mozilla.org/en-US/docs/Web/API/Location/replace window.location.replace(path); // Throw a redirect error throw new Error('ERR_REDIRECT'); } } }; if (process.server) { app.context.beforeNuxtRender = (fn) => context.beforeRenderFns.push(fn); } if (process.client) { app.context.nuxtState = window.__NUXT__; } } // Dynamic keys const [currentRouteData, fromRouteData] = await Promise.all([ getRouteData(context.route), getRouteData(context.from) ]); if (context.route) { app.context.route = currentRouteData; } if (context.from) { app.context.from = fromRouteData; } app.context.next = context.next; app.context._redirected = false; app.context._errored = false; app.context.isHMR = Boolean(context.isHMR); app.context.params = app.context.route.params || {}; app.context.query = app.context.route.query || {}; } export function middlewareSeries(promises, appContext) { if (!promises.length || appContext._redirected || appContext._errored) { return Promise.resolve(); } return promisify(promises[0], appContext) .then(() => { return middlewareSeries(promises.slice(1), appContext); }); } export function promisify(fn, context) { let promise; if (fn.length === 2) { console.warn('Callback-based asyncData, fetch or middleware calls are deprecated. Please switch to promises or async/await syntax'); // eslint-disable-line no-console // fn(context, callback) promise = new Promise((resolve) => { fn(context, (err, data) => { if (err) { context.error(err); } data = data || {}; resolve(data); }); }); } else { promise = fn(context); } if (promise && promise instanceof Promise && typeof promise.then === 'function') { return promise; } return Promise.resolve(promise); } // Imported from vue-router export function getLocation(base, mode) { if (mode === 'hash') { return window.location.hash.replace(/^#\//, ''); } base = decodeURI(base).slice(0, -1); // consideration is base is normalized with trailing slash let path = decodeURI(window.location.pathname); if (base && path.startsWith(base)) { path = path.slice(base.length); } const fullPath = (path || '/') + window.location.search + window.location.hash; return normalizeURL(fullPath); } // Imported from path-to-regexp /** * Compile a string to a template function for the path. * * @param {string} str * @param {Object=} options * @return {!function(Object=, Object=)} */ export function compile(str, options) { return tokensToFunction(parse(str, options), options); } export function getQueryDiff(toQuery, fromQuery) { const diff = {}; const queries = { ...toQuery, ...fromQuery }; for (const k in queries) { if (String(toQuery[k]) !== String(fromQuery[k])) { diff[k] = true; } } return diff; } export function normalizeError(err) { let message; if (!(err.message || typeof err === 'string')) { try { message = JSON.stringify(err, null, 2); } catch (e) { message = `[${ err.constructor.name }]`; } } else { message = err.message || err; } return { ...err, message, statusCode: (err.statusCode || err.status || (err.response && err.response.status) || 500) }; } /** * The main path matching regexp utility. * * @type {RegExp} */ const PATH_REGEXP = new RegExp([ // Match escaped characters that would otherwise appear in future matches. // This allows the user to escape special characters that won't transform. '(\\\\.)', // Match Express-style parameters and un-named parameters with a prefix // and optional suffixes. Matches appear as: // // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined] // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined] // "/*" => ["/", undefined, undefined, undefined, undefined, "*"] '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))' ].join('|'), 'g'); /** * Parse a string for the raw tokens. * * @param {string} str * @param {Object=} options * @return {!Array} */ function parse(str, options) { const tokens = []; let key = 0; let index = 0; let path = ''; const defaultDelimiter = (options && options.delimiter) || '/'; let res; while ((res = PATH_REGEXP.exec(str)) !== null) { const m = res[0]; const escaped = res[1]; const offset = res.index; path += str.slice(index, offset); index = offset + m.length; // Ignore already escaped sequences. if (escaped) { path += escaped[1]; continue; } const next = str[index]; const prefix = res[2]; const name = res[3]; const capture = res[4]; const group = res[5]; const modifier = res[6]; const asterisk = res[7]; // Push the current path onto the tokens. if (path) { tokens.push(path); path = ''; } const partial = prefix !== null && next !== null && next !== prefix; const repeat = modifier === '+' || modifier === '*'; const optional = modifier === '?' || modifier === '*'; const delimiter = res[2] || defaultDelimiter; const pattern = capture || group; tokens.push({ name: name || key++, prefix: prefix || '', delimiter, optional, repeat, partial, asterisk: Boolean(asterisk), pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : `[^${ escapeString(delimiter) }]+?`) }); } // Match any characters still remaining. if (index < str.length) { path += str.substr(index); } // If the path exists, push it onto the end. if (path) { tokens.push(path); } return tokens; } /** * Prettier encoding of URI path segments. * * @param {string} * @return {string} */ function encodeURIComponentPretty(str, slashAllowed) { const re = slashAllowed ? /[?#]/g : /[/?#]/g; return encodeURI(str).replace(re, (c) => { return `%${ c.charCodeAt(0).toString(16).toUpperCase() }`; }); } /** * Encode the asterisk parameter. Similar to `pretty`, but allows slashes. * * @param {string} * @return {string} */ function encodeAsterisk(str) { return encodeURIComponentPretty(str, true); } /** * Escape a regular expression string. * * @param {string} str * @return {string} */ function escapeString(str) { return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1'); } /** * Escape the capturing group by escaping special characters and meaning. * * @param {string} group * @return {string} */ function escapeGroup(group) { return group.replace(/([=!:$/()])/g, '\\$1'); } /** * Expose a method for transforming tokens into the path function. */ function tokensToFunction(tokens, options) { // Compile all the tokens into regexps. const matches = new Array(tokens.length); // Compile all the patterns before compilation. for (let i = 0; i < tokens.length; i++) { if (typeof tokens[i] === 'object') { matches[i] = new RegExp(`^(?:${ tokens[i].pattern })$`, flags(options)); } } return function(obj, opts) { let path = ''; const data = obj || {}; const options = opts || {}; const encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent; for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; if (typeof token === 'string') { path += token; continue; } const value = data[token.name || 'pathMatch']; let segment; if (value === null) { if (token.optional) { // Prepend partial segment prefixes. if (token.partial) { path += token.prefix; } continue; } else { throw new TypeError(`Expected "${ token.name }" to be defined`); } } if (Array.isArray(value)) { if (!token.repeat) { throw new TypeError(`Expected "${ token.name }" to not repeat, but received \`${ JSON.stringify(value) }\``); } if (value.length === 0) { if (token.optional) { continue; } else { throw new TypeError(`Expected "${ token.name }" to not be empty`); } } for (let j = 0; j < value.length; j++) { segment = encode(value[j]); if (!matches[i].test(segment)) { throw new TypeError(`Expected all "${ token.name }" to match "${ token.pattern }", but received \`${ JSON.stringify(segment) }\``); } path += (j === 0 ? token.prefix : token.delimiter) + segment; } continue; } segment = token.asterisk ? encodeAsterisk(value) : encode(value); if (!matches[i].test(segment)) { throw new TypeError(`Expected "${ token.name }" to match "${ token.pattern }", but received "${ segment }"`); } path += token.prefix + segment; } return path; }; } /** * Get the flags for a regexp from the options. * * @param {Object} options * @return {string} */ function flags(options) { return options && options.sensitive ? '' : 'i'; } export function addLifecycleHook(vm, hook, fn) { if (!vm.$options[hook]) { vm.$options[hook] = []; } if (!vm.$options[hook].includes(fn)) { vm.$options[hook].push(fn); } } export const urlJoin = joinURL; export const stripTrailingSlash = withoutTrailingSlash; export const isSamePath = _isSamePath; export function setScrollRestoration(newVal) { try { window.history.scrollRestoration = newVal; } catch (e) {} }