docsify
Version:
A magical documentation generator.
115 lines (99 loc) • 3.44 kB
JavaScript
import { makeExactMatcher } from './exact-match.js';
import { createNextFunction } from './next.js';
/** @typedef {import('../Docsify.js').Constructor} Constructor */
/** @typedef {Record<string, string | VirtualRouteHandler>} VirtualRoutesMap */
/** @typedef {(route: string, match: RegExpMatchArray | null, next?: (content: string | void | Promise<string | void>) => void) => string | void | Promise<string | void> } VirtualRouteHandler */
/**
* Allows users/plugins to introduce dynamically created content into their docsify
* websites. https://github.com/docsifyjs/docsify/issues/1737
*
* For instance:
*
* ```js
* window.$docsify = {
* routes: {
* '/items/(.+)': function (route, matched) {
* return `
* # Item Page: ${matched[1]}
* This is an item
* `;
* }
* }
* }
* ```
*
* @template {Constructor} T
* @param {T} Base - The class to extend
*/
export function VirtualRoutes(Base) {
return class VirtualRoutes extends Base {
/**
* Gets the Routes object from the configuration
* @returns {VirtualRoutesMap}
*/
routes() {
return this.config.routes || {};
}
/**
* Attempts to match the given path with a virtual route.
* @param {string} path the path of the route to match
* @returns {PromiseLike<string | null>} resolves to string if route was matched, otherwise null
*/
matchVirtualRoute(path) {
const virtualRoutes = this.routes();
const virtualRoutePaths = Object.keys(virtualRoutes);
/** @type {(value: string | null) => any} */
let done = () => null;
/**
* This is a tail recursion that iterates over all the available routes.
* It can result in one of two ways:
* 1. Call itself (essentially reviewing the next route)
* 2. Call the "done" callback with the result (either the contents, or "null" if no match was found)
*/
function asyncMatchNextRoute() {
const virtualRoutePath = virtualRoutePaths.shift();
if (!virtualRoutePath) {
return done(null);
}
const matcher = makeExactMatcher(virtualRoutePath);
const matched = path.match(matcher);
if (!matched) {
return asyncMatchNextRoute();
}
const virtualRouteContentOrFn = virtualRoutes[virtualRoutePath];
if (typeof virtualRouteContentOrFn === 'string') {
const contents = virtualRouteContentOrFn;
return done(contents);
}
if (typeof virtualRouteContentOrFn === 'function') {
const fn = virtualRouteContentOrFn;
const [next, onNext] = createNextFunction();
onNext(contents => {
if (typeof contents === 'string') {
return done(contents);
} else if (contents === false) {
return done(null);
} else {
return asyncMatchNextRoute();
}
});
if (fn.length <= 2) {
const returnedValue = fn(path, matched);
return next(returnedValue);
} else {
return fn(path, matched, next);
}
}
return asyncMatchNextRoute();
}
return {
// @ts-expect-error types are screwed here
then(cb) {
// @ts-expect-error types are screwed here
done = cb;
asyncMatchNextRoute();
},
};
}
};
}