From a0bba25401375a0702ae6eff0ae42f4831b27967 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Sun, 3 Dec 2023 16:58:55 -0500 Subject: [PATCH] fix: Handle redirects as prefixes --- main/.vuepress/enhanceApp.js | 124 ++++++++++++++++++++++------------- 1 file changed, 80 insertions(+), 44 deletions(-) diff --git a/main/.vuepress/enhanceApp.js b/main/.vuepress/enhanceApp.js index c9e00aab1..41f2d513a 100644 --- a/main/.vuepress/enhanceApp.js +++ b/main/.vuepress/enhanceApp.js @@ -43,50 +43,86 @@ function patchRoutesToPreserveFragments(router) { }); } +// The router assumes everything is site-local, so manually implement external redirection +// to avoid Not Found URLs like 'https://docs.agoric.com/https:/github.com/...'. +// cf. https://github.com/vuejs/vue-router/issues/1280 and +// https://stackoverflow.com/questions/62254666/use-vue-router-for-redirecting-to-absolute-path +const makeExternalRedirect = target => { + const externalRedirect = () => { + location.assign(target); + }; + return externalRedirect; +}; + +const isPathPrefix = (prefix, fullPath) => { + const trimmedPrefix = prefix.replace(/\/+$/, ''); + if (!fullPath.startsWith(trimmedPrefix)) { + return false; + } + if (fullPath.length === prefix.length || fullPath[trimmedPrefix.length] === '/') { + return true; + } + // As a special case, allow /path/to/resource to match /path/to/resource.html + return fullPath.slice(prefix.length).toLowerCase() === '.html'; +}; + export default ({ router }) => { patchRoutesToPreserveFragments(router); - // The router assumes everything is site-local, so manually implement external redirection - // to avoid Not Found URLs like 'https://docs.agoric.com/https:/github.com/...'. - // cf. https://github.com/vuejs/vue-router/issues/1280 and - // https://stackoverflow.com/questions/62254666/use-vue-router-for-redirecting-to-absolute-path - const makeExternalRedirect = target => { - const externalRedirect = () => { - location.assign(target); - }; - return externalRedirect; - }; + const redirects = [ - { path: '/chainlink-integration.html', redirect: '/guides/chainlink-integration/' }, - { path: '/chainlink-integration/', redirect: '/guides/chainlink-integration/' }, - { path: '/dapps/', redirect: '/guides/dapps/' }, - { path: '/distributed-programming.html', redirect: '/guides/js-programming/' }, - { path: '/distributed-programming/', redirect: '/guides/js-programming/' }, - { path: '/ertp/api/', redirect: '/reference/ertp-api/' }, - { path: '/ertp/api/issuer.html', redirect: '/reference/ertp-api/issuer.html' }, - { path: '/ertp/guide/', redirect: '/guides/ertp/' }, - { path: '/ertp/guide/amounts.html', redirect: '/guides/ertp/amounts.html' }, - { path: '/getting-started/agoric-cli-guide.html', redirect: '/guides/agoric-cli/' }, - { path: '/getting-started/agoric-cli-guide/', redirect: '/guides/agoric-cli/' }, - { path: '/getting-started/before-using-agoric.html', redirect: '/guides/getting-started/' }, - { path: '/getting-started/beta.html', redirect: '/guides/getting-started/' }, - { path: '/getting-started/intro-zoe.html', redirect: '/guides/zoe/offer-enforcement.html' }, - { path: '/getting-started/start-a-project.html', redirect: '/guides/getting-started/start-a-project.html' }, - { path: '/guides/agoric-cli/commands.html', redirect: '/guides/agoric-cli/' }, - { path: '/guides/js-programming/ses/lockdown.html', redirect: makeExternalRedirect('https://github.com/endojs/endo/blob/master/packages/ses/docs/lockdown.md') }, - { path: '/guides/js-programming/ses/ses-guide.html', redirect: makeExternalRedirect('https://github.com/endojs/endo/blob/master/packages/ses/docs/guide.md') }, - { path: '/guides/js-programming/ses/ses-reference.html', redirect: makeExternalRedirect('https://github.com/endojs/endo/blob/master/packages/ses/docs/reference.md') }, - { path: '/guides/wallet/api.html', redirect: '/reference/wallet-api.html' }, - { path: '/platform/', redirect: '/guides/platform/' }, - { path: '/repl/', redirect: '/reference/repl/' }, - { path: '/repl/timerServices.html', redirect: '/reference/repl/timerServices.html' }, - { path: '/wallet-api.html', redirect: '/guides/wallet/' }, - { path: '/wallet-api/', redirect: '/guides/wallet/' }, - { path: '/zoe/api/', redirect: '/reference/zoe-api/' }, - { path: '/zoe/api/zoe.html', redirect: '/reference/zoe-api/zoe.html' }, - { path: '/zoe/guide/', redirect: '/guides/zoe/' }, - { path: '/zoe/guide/contracts/', redirect: '/guides/zoe/contracts/' }, - { path: '/zoe/guide/contracts/oracle.html', redirect: '/guides/zoe/contracts/oracle.html' }, - { path: '/zoe/guide/offer-safety.html', redirect: '/guides/zoe/offer-safety.html' }, - ]; - redirects.forEach(config => router.addRoute(config)); -} + { path: '/chainlink-integration', redirect: '/guides/chainlink-integration/' }, + { path: '/dapps', redirect: '/guides/dapps/' }, + { path: '/distributed-programming', redirect: '/guides/js-programming/' }, + { path: '/ertp/api', redirect: '/reference/ertp-api/' }, + { path: '/ertp/guide', redirect: '/guides/ertp/' }, + { path: '/getting-started/agoric-cli-guide', redirect: '/guides/agoric-cli/' }, + { path: '/getting-started/before-using-agoric', redirect: '/guides/getting-started/' }, + { path: '/getting-started/beta', redirect: '/guides/getting-started/' }, + { path: '/getting-started/intro-zoe', redirect: '/guides/zoe/offer-enforcement/' }, + { path: '/getting-started/start-a-project', redirect: '/guides/getting-started/start-a-project/' }, + { path: '/guides/agoric-cli/commands', redirect: '/guides/agoric-cli/' }, + { path: '/guides/js-programming/ses/lockdown', redirect: makeExternalRedirect('https://github.com/endojs/endo/blob/master/packages/ses/docs/lockdown.md') }, + { path: '/guides/js-programming/ses/ses-guide', redirect: makeExternalRedirect('https://github.com/endojs/endo/blob/master/packages/ses/docs/guide.md') }, + { path: '/guides/js-programming/ses/ses-reference', redirect: makeExternalRedirect('https://github.com/endojs/endo/blob/master/packages/ses/docs/reference.md') }, + { path: '/guides/wallet/api', redirect: '/reference/wallet-api/' }, + { path: '/platform', redirect: '/guides/platform/' }, + { path: '/repl', redirect: '/reference/repl/' }, + { path: '/wallet-api', redirect: '/guides/wallet/' }, + { path: '/zoe/api', redirect: '/reference/zoe-api/' }, + { path: '/zoe/guide', redirect: '/guides/zoe/' }, + ].map(redirect => ({ ...redirect, path: redirect.path.replace(/\.html$/i, '') })); + + // Define exact-match redirect routes. + redirects.forEach(redirect => { + router.addRoute(redirect); + }); + + // Also redirect subpaths. + router.beforeEach((to, from, next) => { + const done = (...args) => { next(...args); }; + const target = to.path; + const prefixRedirects = redirects.filter(redirect => isPathPrefix(redirect.path, target)); + if (prefixRedirects.length === 0) { + // There is no covering redirect. + return done(); + } else if (prefixRedirects.some(redirect => redirect.path === target)) { + // There is an exact-match covering redirect. + return done(); + } + // Apply the longest-path covering redirect. + prefixRedirects.sort((a, b) => b.path.length - a.path.length); + const match = prefixRedirects[0]; + if (!target.startsWith(match.path)) { + console.error('unexpected covering redirect', { to, redirect }); + return done(); + } + if (typeof match.redirect === 'function') { + return done(match.redirect(to)); + } + const trimmedPrefix = match.path.replace(/\/+$/, ''); + const trimmedReplacementPrefix = match.redirect.replace(/\/+$/, ''); + const targetSuffix = target.slice(trimmedPrefix.length).replace(/\.html$/i, '/'); + const newPath = trimmedReplacementPrefix + targetSuffix; + return done({ ...to, path: newPath }); + }); +}