forked from docsifyjs/docsify
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
83 lines (72 loc) · 2.73 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import { makeExactMatcher } from './exact-match';
import { createNextFunction } from './next';
/** @typedef {import('../Docsify').Constructor} Constructor */
/** @typedef {Record<string, string | VirtualRouteHandler>} VirtualRoutesMap */
/** @typedef {(route: string, match: RegExpMatchArray | null) => string | void | Promise<string | void> } VirtualRouteHandler */
/**
* @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
* @returns {Promise<string | null>} resolves to string if route was matched, otherwise null
*/
matchVirtualRoute(path) {
const virtualRoutes = this.routes();
const virtualRoutePaths = Object.keys(virtualRoutes);
/**
* This is a tail recursion that resolves to the first properly matched route, to itself or to null.
* Used because async\await is not supported, so for loops over promises are out of the question...
* @returns {Promise<string | null>}
*/
function asyncMatchNextRoute() {
const virtualRoutePath = virtualRoutePaths.shift();
if (!virtualRoutePath) {
return Promise.resolve(null);
}
const matcher = makeExactMatcher(virtualRoutePath);
const matched = path.match(matcher);
if (!matched) {
return Promise.resolve().then(asyncMatchNextRoute);
}
const virtualRouteContentOrFn = virtualRoutes[virtualRoutePath];
if (typeof virtualRouteContentOrFn === 'string') {
return Promise.resolve(virtualRouteContentOrFn);
} else if (typeof virtualRouteContentOrFn === 'function') {
return Promise.resolve()
.then(() => {
if (virtualRouteContentOrFn.length <= 2) {
return virtualRouteContentOrFn(path, matched);
} else {
const [resultPromise, next] = createNextFunction();
virtualRouteContentOrFn(path, matched, next);
return resultPromise;
}
})
.then(contents => {
if (typeof contents === 'string') {
return contents;
} else if (contents === false) {
return null;
} else {
return asyncMatchNextRoute();
}
});
} else {
return Promise.resolve().then(asyncMatchNextRoute);
}
}
return asyncMatchNextRoute();
}
};
}