Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9f8c3c9

Browse files
committedJul 17, 2023
refactor: move some functions and module-level state into classes as private methods and properties to start to encapsulate Docsify
Also some small tweaks: - move initGlobalAPI out of Docsify.js to start to encapsulate Docsify - move ajax to utils folder - fix some type definitions and improve content in some JSDoc comments - use concise class field syntax - consolidate duplicate docsify-ignore comment removal code This handles a task in [Simplify and modernize Docsify](#2104), as well as works towards [Encapsulating Docsify](#2135).
1 parent b9fe1ce commit 9f8c3c9

24 files changed

+663
-638
lines changed
 

‎src/core/Docsify.js

+2-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { Render } from './render/index.js';
33
import { Fetch } from './fetch/index.js';
44
import { Events } from './event/index.js';
55
import { VirtualRoutes } from './virtual-routes/index.js';
6-
import initGlobalAPI from './global-api.js';
76

87
import config from './config.js';
98
import { isFn } from './util/core.js';
@@ -16,11 +15,11 @@ export class Docsify extends Fetch(
1615
// eslint-disable-next-line new-cap
1716
Events(Render(VirtualRoutes(Router(Lifecycle(Object)))))
1817
) {
18+
config = config(this);
19+
1920
constructor() {
2021
super();
2122

22-
this.config = config(this);
23-
2423
this.initLifecycle(); // Init hooks
2524
this.initPlugin(); // Install plugins
2625
this.callHook('init');
@@ -46,8 +45,3 @@ export class Docsify extends Fetch(
4645
});
4746
}
4847
}
49-
50-
/**
51-
* Global API
52-
*/
53-
initGlobalAPI();

‎src/core/config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { hyphenate, isPrimitive } from './util/core.js';
33

44
const currentScript = document.currentScript;
55

6-
/** @param {import('./Docsify').Docsify} vm */
6+
/** @param {import('./Docsify.js').Docsify} vm */
77
export default function (vm) {
88
const config = Object.assign(
99
{

‎src/core/event/index.js

+282-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import Tweezer from 'tweezer.js';
12
import { isMobile } from '../util/env.js';
23
import { body, on } from '../util/dom.js';
3-
import * as sidebar from './sidebar.js';
4-
import { scrollIntoView, scroll2Top } from './scroll.js';
4+
import * as dom from '../util/dom.js';
5+
import { removeParams } from '../router/util.js';
6+
import config from '../config.js';
57

6-
/** @typedef {import('../Docsify').Constructor} Constructor */
8+
/** @typedef {import('../Docsify.js').Constructor} Constructor */
79

810
/**
911
* @template {!Constructor} T
@@ -18,29 +20,300 @@ export function Events(Base) {
1820
if (source !== 'history') {
1921
// Scroll to ID if specified
2022
if (this.route.query.id) {
21-
scrollIntoView(this.route.path, this.route.query.id);
23+
this.#scrollIntoView(this.route.path, this.route.query.id);
2224
}
2325
// Scroll to top if a link was clicked and auto2top is enabled
2426
if (source === 'navigate') {
25-
auto2top && scroll2Top(auto2top);
27+
auto2top && this.#scroll2Top(auto2top);
2628
}
2729
}
2830

2931
if (this.config.loadNavbar) {
30-
sidebar.getAndActive(this.router, 'nav');
32+
this.__getAndActive(this.router, 'nav');
3133
}
3234
}
3335

3436
initEvent() {
3537
// Bind toggle button
36-
sidebar.btn('button.sidebar-toggle', this.router);
37-
sidebar.collapse('.sidebar', this.router);
38+
this.#btn('button.sidebar-toggle', this.router);
39+
this.#collapse('.sidebar', this.router);
3840
// Bind sticky effect
3941
if (this.config.coverpage) {
40-
!isMobile && on('scroll', sidebar.sticky);
42+
!isMobile && on('scroll', this.__sticky);
4143
} else {
4244
body.classList.add('sticky');
4345
}
4446
}
47+
48+
/** @readonly */
49+
#nav = {};
50+
51+
#hoverOver = false;
52+
#scroller = null;
53+
#enableScrollEvent = true;
54+
#coverHeight = 0;
55+
56+
#scrollTo(el, offset = 0) {
57+
if (this.#scroller) {
58+
this.#scroller.stop();
59+
}
60+
61+
this.#enableScrollEvent = false;
62+
this.#scroller = new Tweezer({
63+
start: window.pageYOffset,
64+
end:
65+
Math.round(el.getBoundingClientRect().top) +
66+
window.pageYOffset -
67+
offset,
68+
duration: 500,
69+
})
70+
.on('tick', v => window.scrollTo(0, v))
71+
.on('done', () => {
72+
this.#enableScrollEvent = true;
73+
this.#scroller = null;
74+
})
75+
.begin();
76+
}
77+
78+
#highlight(path) {
79+
if (!this.#enableScrollEvent) {
80+
return;
81+
}
82+
83+
const sidebar = dom.getNode('.sidebar');
84+
const anchors = dom.findAll('.anchor');
85+
const wrap = dom.find(sidebar, '.sidebar-nav');
86+
let active = dom.find(sidebar, 'li.active');
87+
const doc = document.documentElement;
88+
const top =
89+
((doc && doc.scrollTop) || document.body.scrollTop) - this.#coverHeight;
90+
let last;
91+
92+
for (const node of anchors) {
93+
if (node.offsetTop > top) {
94+
if (!last) {
95+
last = node;
96+
}
97+
98+
break;
99+
} else {
100+
last = node;
101+
}
102+
}
103+
104+
if (!last) {
105+
return;
106+
}
107+
108+
const li = this.#nav[this.#getNavKey(path, last.getAttribute('data-id'))];
109+
110+
if (!li || li === active) {
111+
return;
112+
}
113+
114+
active && active.classList.remove('active');
115+
li.classList.add('active');
116+
active = li;
117+
118+
// Scroll into view
119+
// https://github.com/vuejs/vuejs.org/blob/master/themes/vue/source/js/common.js#L282-L297
120+
if (!this.#hoverOver && dom.body.classList.contains('sticky')) {
121+
const height = sidebar.clientHeight;
122+
const curOffset = 0;
123+
const cur = active.offsetTop + active.clientHeight + 40;
124+
const isInView =
125+
active.offsetTop >= wrap.scrollTop && cur <= wrap.scrollTop + height;
126+
const notThan = cur - curOffset < height;
127+
128+
sidebar.scrollTop = isInView
129+
? wrap.scrollTop
130+
: notThan
131+
? curOffset
132+
: cur - height;
133+
}
134+
}
135+
136+
#getNavKey(path, id) {
137+
return `${decodeURIComponent(path)}?id=${decodeURIComponent(id)}`;
138+
}
139+
140+
__scrollActiveSidebar(router) {
141+
const cover = dom.find('.cover.show');
142+
this.#coverHeight = cover ? cover.offsetHeight : 0;
143+
144+
const sidebar = dom.getNode('.sidebar');
145+
let lis = [];
146+
if (sidebar !== null && sidebar !== undefined) {
147+
lis = dom.findAll(sidebar, 'li');
148+
}
149+
150+
for (const li of lis) {
151+
const a = li.querySelector('a');
152+
if (!a) {
153+
continue;
154+
}
155+
156+
let href = a.getAttribute('href');
157+
158+
if (href !== '/') {
159+
const {
160+
query: { id },
161+
path,
162+
} = router.parse(href);
163+
if (id) {
164+
href = this.#getNavKey(path, id);
165+
}
166+
}
167+
168+
if (href) {
169+
this.#nav[decodeURIComponent(href)] = li;
170+
}
171+
}
172+
173+
if (isMobile) {
174+
return;
175+
}
176+
177+
const path = removeParams(router.getCurrentPath());
178+
dom.off('scroll', () => this.#highlight(path));
179+
dom.on('scroll', () => this.#highlight(path));
180+
dom.on(sidebar, 'mouseover', () => {
181+
this.#hoverOver = true;
182+
});
183+
dom.on(sidebar, 'mouseleave', () => {
184+
this.#hoverOver = false;
185+
});
186+
}
187+
188+
#scrollIntoView(path, id) {
189+
if (!id) {
190+
return;
191+
}
192+
const topMargin = config().topMargin;
193+
// Use [id='1234'] instead of #id to handle special cases such as reserved characters and pure number id
194+
// https://stackoverflow.com/questions/37270787/uncaught-syntaxerror-failed-to-execute-queryselector-on-document
195+
const section = dom.find("[id='" + id + "']");
196+
section && this.#scrollTo(section, topMargin);
197+
198+
const li = this.#nav[this.#getNavKey(path, id)];
199+
const sidebar = dom.getNode('.sidebar');
200+
const active = dom.find(sidebar, 'li.active');
201+
active && active.classList.remove('active');
202+
li && li.classList.add('active');
203+
}
204+
205+
#scrollEl = dom.$.scrollingElement || dom.$.documentElement;
206+
207+
#scroll2Top(offset = 0) {
208+
this.#scrollEl.scrollTop = offset === true ? 0 : Number(offset);
209+
}
210+
211+
/** @readonly */
212+
#title = dom.$.title;
213+
214+
/**
215+
* Toggle button
216+
* @param {Element} el Button to be toggled
217+
* @void
218+
*/
219+
#btn(el) {
220+
const toggle = _ => dom.body.classList.toggle('close');
221+
222+
el = dom.getNode(el);
223+
if (el === null || el === undefined) {
224+
return;
225+
}
226+
227+
dom.on(el, 'click', e => {
228+
e.stopPropagation();
229+
toggle();
230+
});
231+
232+
isMobile &&
233+
dom.on(
234+
dom.body,
235+
'click',
236+
_ => dom.body.classList.contains('close') && toggle()
237+
);
238+
}
239+
240+
#collapse(el) {
241+
el = dom.getNode(el);
242+
if (el === null || el === undefined) {
243+
return;
244+
}
245+
246+
dom.on(el, 'click', ({ target }) => {
247+
if (
248+
target.nodeName === 'A' &&
249+
target.nextSibling &&
250+
target.nextSibling.classList &&
251+
target.nextSibling.classList.contains('app-sub-sidebar')
252+
) {
253+
dom.toggleClass(target.parentNode, 'collapse');
254+
}
255+
});
256+
}
257+
258+
__sticky = () => {
259+
const cover = dom.getNode('section.cover');
260+
if (!cover) {
261+
return;
262+
}
263+
264+
const coverHeight = cover.getBoundingClientRect().height;
265+
266+
if (
267+
window.pageYOffset >= coverHeight ||
268+
cover.classList.contains('hidden')
269+
) {
270+
dom.toggleClass(dom.body, 'add', 'sticky');
271+
} else {
272+
dom.toggleClass(dom.body, 'remove', 'sticky');
273+
}
274+
};
275+
276+
/**
277+
* Get and active link
278+
* @param {Object} router Router
279+
* @param {String|Element} el Target element
280+
* @param {Boolean} isParent Active parent
281+
* @param {Boolean} autoTitle Automatically set title
282+
* @return {Element} Active element
283+
*/
284+
__getAndActive(router, el, isParent, autoTitle) {
285+
el = dom.getNode(el);
286+
let links = [];
287+
if (el !== null && el !== undefined) {
288+
links = dom.findAll(el, 'a');
289+
}
290+
291+
const hash = decodeURI(router.toURL(router.getCurrentPath()));
292+
let target;
293+
294+
links
295+
.sort((a, b) => b.href.length - a.href.length)
296+
.forEach(a => {
297+
const href = decodeURI(a.getAttribute('href'));
298+
const node = isParent ? a.parentNode : a;
299+
300+
a.title = a.title || a.innerText;
301+
302+
if (hash.indexOf(href) === 0 && !target) {
303+
target = a;
304+
dom.toggleClass(node, 'add', 'active');
305+
} else {
306+
dom.toggleClass(node, 'remove', 'active');
307+
}
308+
});
309+
310+
if (autoTitle) {
311+
dom.$.title = target
312+
? target.title || `${target.innerText} - ${this.#title}`
313+
: this.#title;
314+
}
315+
316+
return target;
317+
}
45318
};
46319
}

‎src/core/event/scroll.js

-163
This file was deleted.

‎src/core/event/sidebar.js

-106
This file was deleted.

‎src/core/fetch/index.js

+65-60
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,70 @@
11
/* eslint-disable no-unused-vars */
22
import { getParentPath, stringifyQuery } from '../router/util.js';
33
import { noop, isExternal } from '../util/core.js';
4-
import { getAndActive } from '../event/sidebar.js';
5-
import { get } from './ajax.js';
6-
7-
function loadNested(path, qs, file, next, vm, first) {
8-
path = first ? path : path.replace(/\/$/, '');
9-
path = getParentPath(path);
10-
11-
if (!path) {
12-
return;
13-
}
14-
15-
get(
16-
vm.router.getFile(path + file) + qs,
17-
false,
18-
vm.config.requestHeaders
19-
).then(next, _error => loadNested(path, qs, file, next, vm));
20-
}
4+
import { get } from '../util/ajax.js';
215

22-
/** @typedef {import('../Docsify').Constructor} Constructor */
6+
/** @typedef {import('../Docsify.js').Constructor} Constructor */
237

248
/**
259
* @template {!Constructor} T
2610
* @param {T} Base - The class to extend
2711
*/
2812
export function Fetch(Base) {
29-
let last;
13+
return class Fetch extends Base {
14+
#loadNested(path, qs, file, next, vm, first) {
15+
path = first ? path : path.replace(/\/$/, '');
16+
path = getParentPath(path);
3017

31-
const abort = () => last && last.abort && last.abort();
32-
const request = (url, requestHeaders) => {
33-
abort();
34-
last = get(url, true, requestHeaders);
35-
return last;
36-
};
18+
if (!path) {
19+
return;
20+
}
3721

38-
const get404Path = (path, config) => {
39-
const { notFoundPage, ext } = config;
40-
const defaultPath = '_404' + (ext || '.md');
41-
let key;
42-
let path404;
43-
44-
switch (typeof notFoundPage) {
45-
case 'boolean':
46-
path404 = defaultPath;
47-
break;
48-
case 'string':
49-
path404 = notFoundPage;
50-
break;
51-
52-
case 'object':
53-
key = Object.keys(notFoundPage)
54-
.sort((a, b) => b.length - a.length)
55-
.filter(k => path.match(new RegExp('^' + k)))[0];
56-
57-
path404 = (key && notFoundPage[key]) || defaultPath;
58-
break;
59-
60-
default:
61-
break;
22+
get(
23+
vm.router.getFile(path + file) + qs,
24+
false,
25+
vm.config.requestHeaders
26+
).then(next, _error => this.#loadNested(path, qs, file, next, vm));
6227
}
6328

64-
return path404;
65-
};
29+
#last;
30+
31+
#abort = () => this.#last && this.#last.abort && this.#last.abort();
32+
33+
#request = (url, requestHeaders) => {
34+
this.#abort();
35+
this.#last = get(url, true, requestHeaders);
36+
return this.#last;
37+
};
38+
39+
#get404Path = (path, config) => {
40+
const { notFoundPage, ext } = config;
41+
const defaultPath = '_404' + (ext || '.md');
42+
let key;
43+
let path404;
44+
45+
switch (typeof notFoundPage) {
46+
case 'boolean':
47+
path404 = defaultPath;
48+
break;
49+
case 'string':
50+
path404 = notFoundPage;
51+
break;
52+
53+
case 'object':
54+
key = Object.keys(notFoundPage)
55+
.sort((a, b) => b.length - a.length)
56+
.filter(k => path.match(new RegExp('^' + k)))[0];
57+
58+
path404 = (key && notFoundPage[key]) || defaultPath;
59+
break;
60+
61+
default:
62+
break;
63+
}
64+
65+
return path404;
66+
};
6667

67-
return class Fetch extends Base {
6868
_loadSideAndNav(path, qs, loadSidebar, cb) {
6969
return () => {
7070
if (!loadSidebar) {
@@ -77,7 +77,7 @@ export function Fetch(Base) {
7777
};
7878

7979
// Load sidebar
80-
loadNested(path, qs, loadSidebar, fn, this, true);
80+
this.#loadNested(path, qs, loadSidebar, fn, this, true);
8181
};
8282
}
8383

@@ -121,23 +121,23 @@ export function Fetch(Base) {
121121
if (typeof contents === 'string') {
122122
contentFetched(contents);
123123
} else {
124-
request(file + qs, requestHeaders).then(
124+
this.#request(file + qs, requestHeaders).then(
125125
contentFetched,
126126
contentFailedToFetch
127127
);
128128
}
129129
});
130130
} else {
131131
// if the requested url is not local, just fetch the file
132-
request(file + qs, requestHeaders).then(
132+
this.#request(file + qs, requestHeaders).then(
133133
contentFetched,
134134
contentFailedToFetch
135135
);
136136
}
137137

138138
// Load nav
139139
loadNavbar &&
140-
loadNested(
140+
this.#loadNested(
141141
path,
142142
qs,
143143
loadNavbar,
@@ -216,7 +216,7 @@ export function Fetch(Base) {
216216
const newPath = this.router.getFile(
217217
path.replace(new RegExp(`^/${local}`), '')
218218
);
219-
const req = request(newPath + qs, requestHeaders);
219+
const req = this.#request(newPath + qs, requestHeaders);
220220

221221
req.then(
222222
(text, opt) =>
@@ -244,9 +244,9 @@ export function Fetch(Base) {
244244

245245
const fnLoadSideAndNav = this._loadSideAndNav(path, qs, loadSidebar, cb);
246246
if (notFoundPage) {
247-
const path404 = get404Path(path, this.config);
247+
const path404 = this.#get404Path(path, this.config);
248248

249-
request(this.router.getFile(path404), requestHeaders).then(
249+
this.#request(this.router.getFile(path404), requestHeaders).then(
250250
(text, opt) => this._renderMain(text, opt, fnLoadSideAndNav),
251251
_error => this._renderMain(null, {}, fnLoadSideAndNav)
252252
);
@@ -262,7 +262,12 @@ export function Fetch(Base) {
262262

263263
// Server-Side Rendering
264264
if (this.rendered) {
265-
const activeEl = getAndActive(this.router, '.sidebar-nav', true, true);
265+
const activeEl = this.__getAndActive(
266+
this.router,
267+
'.sidebar-nav',
268+
true,
269+
true
270+
);
266271
if (loadSidebar && activeEl) {
267272
activeEl.parentNode.innerHTML += window.__SUB_SIDEBAR__;
268273
}

‎src/core/global-api.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import * as util from './util/index.js';
44
import * as dom from './util/dom.js';
55
import { Compiler } from './render/compiler.js';
66
import { slugify } from './render/slugify.js';
7-
import { get } from './fetch/ajax.js';
7+
import { get } from './util/ajax.js';
88

9-
// TODO This is deprecated, kept for backwards compatibility. Remove in next
9+
// TODO This is deprecated, kept for backwards compatibility. Remove in a
1010
// major release. We'll tell people to get everything from the DOCSIFY global
1111
// when using the global build, but we'll highly recommend for them to import
1212
// from the ESM build (f.e. lib/docsify.esm.js and lib/docsify.min.esm.js).
13-
export default function () {
13+
export default function initGlobalAPI() {
1414
window.Docsify = {
1515
util,
1616
dom,

‎src/core/index.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import { documentReady } from './util/dom.js';
22
import { Docsify } from './Docsify.js';
3+
import initGlobalAPI from './global-api.js';
4+
5+
// TODO This global API and auto-running Docsify will be deprecated, and removed
6+
// in a major release. Instead we'll tell users to use `new Docsify()` to create
7+
// and manage their instance(s).
8+
9+
/**
10+
* Global API
11+
*/
12+
initGlobalAPI();
313

414
/**
515
* Run Docsify
616
*/
7-
// eslint-disable-next-line no-unused-vars
8-
documentReady(_ => new Docsify());
17+
documentReady(() => new Docsify());

‎src/core/init/lifecycle.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { noop } from '../util/core.js';
22

3-
/** @typedef {import('../Docsify').Constructor} Constructor */
3+
/** @typedef {import('../Docsify.js').Constructor} Constructor */
44

55
/**
66
* @template {!Constructor} T
77
* @param {T} Base - The class to extend
88
*/
99
export function Lifecycle(Base) {
1010
return class Lifecycle extends Base {
11+
_hooks = {};
12+
_lifecycle = {};
13+
1114
initLifecycle() {
1215
const hooks = [
1316
'init',
@@ -18,9 +21,6 @@ export function Lifecycle(Base) {
1821
'ready',
1922
];
2023

21-
this._hooks = {};
22-
this._lifecycle = {};
23-
2424
hooks.forEach(hook => {
2525
const arr = (this._hooks[hook] = []);
2626
this._lifecycle[hook] = fn => arr.push(fn);

‎src/core/render/compiler.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { emojify } from './emojify.js';
88
import {
99
getAndRemoveConfig,
1010
removeAtag,
11-
getAndRemoveDocisfyIgnorConfig,
11+
getAndRemoveDocisfyIgnoreConfig,
1212
} from './utils.js';
1313
import { imageCompiler } from './compiler/image.js';
1414
import { highlightCodeCompiler } from './compiler/code.js';
@@ -214,7 +214,7 @@ export class Compiler {
214214
const nextToc = { level, title: str };
215215

216216
const { content, ignoreAllSubs, ignoreSubHeading } =
217-
getAndRemoveDocisfyIgnorConfig(str);
217+
getAndRemoveDocisfyIgnoreConfig(str);
218218
str = content.trim();
219219

220220
nextToc.title = removeAtag(str);

‎src/core/render/compiler/headline.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
22
getAndRemoveConfig,
33
removeAtag,
4-
getAndRemoveDocisfyIgnorConfig,
4+
getAndRemoveDocisfyIgnoreConfig,
55
} from '../utils.js';
66
import { slugify } from './slugify.js';
77

@@ -11,7 +11,7 @@ export const headingCompiler = ({ renderer, router, _self }) =>
1111
const nextToc = { level, title: str };
1212

1313
const { content, ignoreAllSubs, ignoreSubHeading } =
14-
getAndRemoveDocisfyIgnorConfig(str);
14+
getAndRemoveDocisfyIgnoreConfig(str);
1515
str = content.trim();
1616

1717
nextToc.title = removeAtag(str);

‎src/core/render/embed.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import stripIndent from 'strip-indent';
2-
import { get } from '../fetch/ajax.js';
2+
import { get } from '../util/ajax.js';
33

44
const cached = {};
55

‎src/core/render/index.js

+218-214
Large diffs are not rendered by default.

‎src/core/render/utils.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*
1717
* @param {string} str The string to parse.
1818
*
19-
* @return {object} The original string and parsed object, { str, config }.
19+
* @return {{str: string, config: object}} The original string formatted, and parsed object, { str, config }.
2020
*/
2121
export function getAndRemoveConfig(str = '') {
2222
const config = {};
@@ -43,19 +43,19 @@ export function getAndRemoveConfig(str = '') {
4343
* Remove the <a> tag from sidebar when the header with link, details see issue 1069
4444
* @param {string} str The string to deal with.
4545
*
46-
* @return {string} str The string after delete the <a> element.
46+
* @return {string} The string after delete the <a> element.
4747
*/
4848
export function removeAtag(str = '') {
4949
return str.replace(/(<\/?a.*?>)/gi, '');
5050
}
5151

5252
/**
5353
* Remove the docsifyIgnore configs and return the str
54-
* @param {string} str The string to deal with.
54+
* @param {string} content The string to deal with.
5555
*
56-
* @return {string} str The string after delete the docsifyIgnore configs.
56+
* @return {{content: string, ignoreAllSubs: boolean, ignoreSubHeading: boolean}} The string after delete the docsifyIgnore configs, and whether to ignore some or all.
5757
*/
58-
export function getAndRemoveDocisfyIgnorConfig(content = '') {
58+
export function getAndRemoveDocisfyIgnoreConfig(content = '') {
5959
let ignoreAllSubs, ignoreSubHeading;
6060
if (/<!-- {docsify-ignore} -->/g.test(content)) {
6161
content = content.replace('<!-- {docsify-ignore} -->', '');

‎src/core/router/history/base.js

+28-23
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,37 @@ import {
88
} from '../util.js';
99
import { noop } from '../../util/core.js';
1010

11-
const cached = {};
12-
13-
function getAlias(path, alias, last) {
14-
const match = Object.keys(alias).filter(key => {
15-
const re = cached[key] || (cached[key] = new RegExp(`^${key}$`));
16-
return re.test(path) && path !== last;
17-
})[0];
18-
19-
return match
20-
? getAlias(path.replace(cached[match], alias[match]), alias, path)
21-
: path;
22-
}
23-
24-
function getFileName(path, ext) {
25-
return new RegExp(`\\.(${ext.replace(/^\./, '')}|html)$`, 'g').test(path)
26-
? path
27-
: /\/$/g.test(path)
28-
? `${path}README${ext}`
29-
: `${path}${ext}`;
30-
}
31-
3211
export class History {
12+
#cached = {};
13+
3314
constructor(config) {
3415
this.config = config;
3516
}
3617

18+
#getAlias(path, alias, last) {
19+
const match = Object.keys(alias).filter(key => {
20+
const re =
21+
this.#cached[key] || (this.#cached[key] = new RegExp(`^${key}$`));
22+
return re.test(path) && path !== last;
23+
})[0];
24+
25+
return match
26+
? this.#getAlias(
27+
path.replace(this.#cached[match], alias[match]),
28+
alias,
29+
path
30+
)
31+
: path;
32+
}
33+
34+
#getFileName(path, ext) {
35+
return new RegExp(`\\.(${ext.replace(/^\./, '')}|html)$`, 'g').test(path)
36+
? path
37+
: /\/$/g.test(path)
38+
? `${path}README${ext}`
39+
: `${path}${ext}`;
40+
}
41+
3742
getBasePath() {
3843
return this.config.basePath;
3944
}
@@ -43,8 +48,8 @@ export class History {
4348
const base = this.getBasePath();
4449
const ext = typeof config.ext === 'string' ? config.ext : '.md';
4550

46-
path = config.alias ? getAlias(path, config.alias) : path;
47-
path = getFileName(path, ext);
51+
path = config.alias ? this.#getAlias(path, config.alias) : path;
52+
path = this.#getFileName(path, ext);
4853
path = path === `/README${ext}` ? config.homepage || path : path;
4954
path = isAbsolutePath(path) ? path : getPath(base, path);
5055

‎src/core/router/history/hash.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@ function replaceHash(path) {
77
const i = location.href.indexOf('#');
88
location.replace(location.href.slice(0, i >= 0 ? i : 0) + '#' + path);
99
}
10+
1011
export class HashHistory extends History {
11-
constructor(config) {
12-
super(config);
13-
this.mode = 'hash';
14-
}
12+
mode = 'hash';
1513

1614
getBasePath() {
1715
const path = window.location.pathname || '';

‎src/core/router/history/html5.js

+1-4
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@ import { parseQuery, getPath } from '../util.js';
44
import { History } from './base.js';
55

66
export class HTML5History extends History {
7-
constructor(config) {
8-
super(config);
9-
this.mode = 'history';
10-
}
7+
mode = 'history';
118

129
getCurrentPath() {
1310
const base = this.getBasePath();

‎src/core/router/index.js

+2-7
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,15 @@ import { HTML5History } from './history/html5.js';
1212
/** @type {Route} */
1313
let lastRoute = {};
1414

15-
/** @typedef {import('../Docsify').Constructor} Constructor */
15+
/** @typedef {import('../Docsify.js').Constructor} Constructor */
1616

1717
/**
1818
* @template {!Constructor} T
1919
* @param {T} Base - The class to extend
2020
*/
2121
export function Router(Base) {
2222
return class Router extends Base {
23-
/** @param {any[]} args */
24-
constructor(...args) {
25-
super(...args);
26-
27-
this.route = {};
28-
}
23+
route = {};
2924

3025
updateRender() {
3126
this.router.normalize();

‎src/core/fetch/ajax.js ‎src/core/util/ajax.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @ts-check
22
/* eslint-disable no-unused-vars */
33
import progressbar from '../render/progressbar.js';
4-
import { noop } from '../util/core.js';
4+
import { noop } from './core.js';
55

66
/** @typedef {{updatedAt: string}} CacheOpt */
77

‎src/core/util/str.js

-8
This file was deleted.

‎src/core/virtual-routes/index.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,30 @@
11
import { makeExactMatcher } from './exact-match.js';
22
import { createNextFunction } from './next.js';
33

4-
/** @typedef {import('../Docsify').Constructor} Constructor */
4+
/** @typedef {import('../Docsify.js').Constructor} Constructor */
55

66
/** @typedef {Record<string, string | VirtualRouteHandler>} VirtualRoutesMap */
77
/** @typedef {(route: string, match: RegExpMatchArray | null) => string | void | Promise<string | void> } VirtualRouteHandler */
88

99
/**
10+
* Allows users/plugins to introduce dynamically created content into their docsify
11+
* websites. https://github.com/docsifyjs/docsify/issues/1737
12+
*
13+
* For instance:
14+
*
15+
* ```js
16+
* window.$docsify = {
17+
* routes: {
18+
* '/items/(.+)': function (route, matched) {
19+
* return `
20+
* # Item Page: ${matched[1]}
21+
* This is an item
22+
* `;
23+
* }
24+
* }
25+
* }
26+
* ```
27+
*
1028
* @template {!Constructor} T
1129
* @param {T} Base - The class to extend
1230
*/

‎src/core/virtual-routes/next.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
/** @typedef {((value: any) => void) => void} OnNext */
1+
/** @typedef {(value: any) => void} CB */
2+
/** @typedef {(cb: CB) => void} OnNext */
23
/** @typedef {(value: any) => void} NextFunction */
34

45
/**
@@ -7,6 +8,7 @@
78
* @returns {[NextFunction, OnNext]}
89
*/
910
export function createNextFunction() {
11+
/** @type {CB} */
1012
let storedCb = () => null;
1113

1214
function next(value) {

‎src/plugins/search/search.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/* eslint-disable no-unused-vars */
2-
import { getAndRemoveConfig } from '../../core/render/utils.js';
3-
import { removeDocsifyIgnoreTag } from '../../core/util/str.js';
2+
import {
3+
getAndRemoveConfig,
4+
getAndRemoveDocisfyIgnoreConfig,
5+
} from '../../core/render/utils.js';
46

57
let INDEXS = {};
68

@@ -88,7 +90,7 @@ export function genIndex(path, content = '', router, depth) {
8890
if (token.type === 'heading' && token.depth <= depth) {
8991
const { str, config } = getAndRemoveConfig(token.text);
9092

91-
const text = removeDocsifyIgnoreTag(token.text);
93+
const text = getAndRemoveDocisfyIgnoreConfig(token.text).content;
9294

9395
if (config.id) {
9496
slug = router.toURL(path, { id: slugify(config.id) });
@@ -97,7 +99,7 @@ export function genIndex(path, content = '', router, depth) {
9799
}
98100

99101
if (str) {
100-
title = removeDocsifyIgnoreTag(str);
102+
title = getAndRemoveDocisfyIgnoreConfig(str).content;
101103
}
102104

103105
index[slug] = { slug, title: title, body: '' };

‎test/unit/render-util.test.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
22
removeAtag,
33
getAndRemoveConfig,
4-
getAndRemoveDocisfyIgnorConfig,
4+
getAndRemoveDocisfyIgnoreConfig,
55
} from '../../src/core/render/utils.js';
66
import { tree } from '../../src/core/render/tpl.js';
77
import { slugify } from '../../src/core/render/slugify.js';
@@ -24,7 +24,7 @@ describe('core/render/utils', () => {
2424
describe('getAndRemoveDocisfyIgnorConfig()', () => {
2525
test('getAndRemoveDocisfyIgnorConfig from <!-- {docsify-ignore} -->', () => {
2626
const { content, ignoreAllSubs, ignoreSubHeading } =
27-
getAndRemoveDocisfyIgnorConfig(
27+
getAndRemoveDocisfyIgnoreConfig(
2828
'My Ignore Title<!-- {docsify-ignore} -->'
2929
);
3030
expect(content).toBe('My Ignore Title');
@@ -34,7 +34,7 @@ describe('core/render/utils', () => {
3434

3535
test('getAndRemoveDocisfyIgnorConfig from <!-- {docsify-ignore-all} -->', () => {
3636
const { content, ignoreAllSubs, ignoreSubHeading } =
37-
getAndRemoveDocisfyIgnorConfig(
37+
getAndRemoveDocisfyIgnoreConfig(
3838
'My Ignore Title<!-- {docsify-ignore-all} -->'
3939
);
4040
expect(content).toBe('My Ignore Title');
@@ -44,15 +44,15 @@ describe('core/render/utils', () => {
4444

4545
test('getAndRemoveDocisfyIgnorConfig from {docsify-ignore}', () => {
4646
const { content, ignoreAllSubs, ignoreSubHeading } =
47-
getAndRemoveDocisfyIgnorConfig('My Ignore Title{docsify-ignore}');
47+
getAndRemoveDocisfyIgnoreConfig('My Ignore Title{docsify-ignore}');
4848
expect(content).toBe('My Ignore Title');
4949
expect(ignoreSubHeading).toBeTruthy();
5050
expect(ignoreAllSubs === undefined).toBeTruthy();
5151
});
5252

5353
test('getAndRemoveDocisfyIgnorConfig from {docsify-ignore-all}', () => {
5454
const { content, ignoreAllSubs, ignoreSubHeading } =
55-
getAndRemoveDocisfyIgnorConfig('My Ignore Title{docsify-ignore-all}');
55+
getAndRemoveDocisfyIgnoreConfig('My Ignore Title{docsify-ignore-all}');
5656
expect(content).toBe('My Ignore Title');
5757
expect(ignoreAllSubs).toBeTruthy();
5858
expect(ignoreSubHeading === undefined).toBeTruthy();

0 commit comments

Comments
 (0)
Please sign in to comment.