import Vue from 'vue' import store from '~/store' import Meta from 'vue-meta' import routes from './routes' import Router from 'vue-router' import {sync} from 'vuex-router-sync' Vue.use(Meta) Vue.use(Router) // The middleware for every page of the application. const globalMiddleware = ['locale', 'check-auth', 'notion-connection'] // Load middleware modules dynamically. const requireContext = import.meta.glob('../middleware/**/*.js', { eager: true }) const routeMiddleware = resolveMiddleware( requireContext ) const router = createRouter() sync(store, router) export default router /** * Create a new router instance. * * @return {Router} */ function createRouter () { const router = new Router({ scrollBehavior, mode: 'history', routes }) router.beforeEach(beforeEach) router.afterEach(afterEach) return router } /** * Global router guard. * * @param {Route} to * @param {Route} from * @param {Function} next */ async function beforeEach (to, from, next) { let components = [] // External redirect if (to.matched.some((record) => record.meta.externalUrl)) { const url = to.meta.externalUrl window.location.replace(url) return } try { // Get the matched components and resolve them. components = await resolveComponents( router.getMatchedComponents({ ...to }) ) } catch (error) { if (/^Loading( CSS)? chunk (\d)+ failed\./.test(error.message)) { window.location.reload(true) return } } if (components.length === 0) { return next() } // Start the loading bar. if (components[components.length - 1].loading !== false) { router.app.$nextTick(() => router.app.$loading.start()) } // Get the middleware for all the matched components. const middleware = getMiddleware(components) // Call each middleware. callMiddleware(middleware, to, from, (...args) => { // Set the application layout only if "next()" was called with no args. if (args.length === 0) { router.app.setLayout(components[0].layout || '') } next(...args) }) } /** * Global after hook. * * @param {Route} to * @param {Route} from * @param {Function} next */ async function afterEach (to, from, next) { await router.app.$nextTick() router.app.$loading.finish() } /** * Call each middleware. * * @param {Array} middleware * @param {Route} to * @param {Route} from * @param {Function} next */ function callMiddleware (middleware, to, from, next) { const stack = middleware.reverse() const _next = (...args) => { // Stop if "_next" was called with an argument or the stack is empty. if (args.length > 0 || stack.length === 0) { if (args.length > 0) { router.app.$loading.finish() } return next(...args) } const { middleware, params } = parseMiddleware(stack.pop()) if (typeof middleware === 'function') { middleware(to, from, _next, params) } else if (routeMiddleware[middleware]) { routeMiddleware[middleware](to, from, _next, params) } else { throw Error(`Undefined middleware [${middleware}]`) } } _next() } /** * @param {String|Function} middleware * @return {Object} */ function parseMiddleware (middleware) { if (typeof middleware === 'function') { return { middleware } } const [name, params] = middleware.split(':') return { middleware: name, params } } /** * Resolve async components. * * @param {Array} components * @return {Array} */ function resolveComponents (components) { return Promise.all(components.map(component => { return typeof component === 'function' ? component() : component })) } /** * Merge the the global middleware with the components middleware. * * @param {Array} components * @return {Array} */ function getMiddleware (components) { const middleware = [...globalMiddleware] components.filter(c => c.middleware).forEach(component => { if (Array.isArray(component.middleware)) { middleware.push(...component.middleware) } else { middleware.push(component.middleware) } }) return middleware } /** * Scroll Behavior * * @link https://router.vuejs.org/en/advanced/scroll-behavior.html * * @param {Route} to * @param {Route} from * @param {Object|undefined} savedPosition * @return {Object} */ function scrollBehavior (to, from, savedPosition) { if (savedPosition) { return savedPosition } if (to.hash) { return { selector: to.hash } } const [component] = router.getMatchedComponents({ ...to }).slice(-1) if (component && component.scrollToTop === false) { return {} } return new Promise((resolve, reject) => { setTimeout(() => { resolve({ x: 0, y: 0 }) }, 190) }) } /** * @param {Object} requireContext * @return {Object} */ function resolveMiddleware(requireContext) { const middlewares = {} Object.keys(requireContext) .map(file => [file.match(/[^/]*(?=\.[^.]*$)/)[0], requireContext[file]] ).forEach(([name, middleware]) => { middlewares[name] = middleware.default || middleware }) return middlewares }