Skip to content

Commit deb2dc2

Browse files
authored
Merge branch 'develop' into search-filter-md-pure
2 parents 3428ebd + 7cbd532 commit deb2dc2

File tree

7 files changed

+272
-52
lines changed

7 files changed

+272
-52
lines changed

src/core/fetch/index.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,19 @@ export function Fetch(Base) {
6767

6868
_loadSideAndNav(path, qs, loadSidebar, cb) {
6969
return () => {
70-
if (!loadSidebar) {
71-
return cb();
72-
}
73-
74-
const fn = result => {
70+
const renderSidebar = result => {
7571
this._renderSidebar(result);
7672
cb();
7773
};
7874

79-
// Load sidebar
80-
this.#loadNested(path, qs, loadSidebar, fn, this, true);
75+
if (!loadSidebar) {
76+
// Although, we don't load sidebar from sidebar file, we still need call the render to auto generate sidebar from headings toc
77+
renderSidebar();
78+
return;
79+
}
80+
81+
// Load sidebar from the sidebar file
82+
this.#loadNested(path, qs, loadSidebar, renderSidebar, this, true);
8183
};
8284
}
8385

src/core/render/compiler.js

Lines changed: 48 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,16 @@ export class Compiler {
7474
this.linkTarget === '_blank' ? config.externalLinkRel || 'noopener' : '';
7575
this.contentBase = router.getBasePath();
7676

77-
const renderer = this._initRenderer();
78-
this.heading = renderer.heading;
77+
this.renderer = this._initRenderer();
7978
let compile;
8079
const mdConf = config.markdown || {};
8180

8281
if (isFn(mdConf)) {
83-
compile = mdConf(marked, renderer);
82+
compile = mdConf(marked, this.renderer);
8483
} else {
8584
marked.setOptions(
8685
Object.assign(mdConf, {
87-
renderer: Object.assign(renderer, mdConf.renderer),
86+
renderer: Object.assign(this.renderer, mdConf.renderer),
8887
}),
8988
);
9089
compile = marked;
@@ -252,8 +251,8 @@ export class Compiler {
252251
}
253252

254253
/**
255-
* Compile sidebar
256-
* @param {String} text Text content
254+
* Compile sidebar, it uses _sidebar.md ( or specific file) or the content's headings toc to render sidebar.
255+
* @param {String} text Text content from the sidebar file, maybe empty
257256
* @param {Number} level Type of heading (h<level> tag)
258257
* @returns {String} Sidebar element
259258
*/
@@ -262,50 +261,53 @@ export class Compiler {
262261
const currentPath = this.router.getCurrentPath();
263262
let html = '';
264263

264+
// compile sidebar from _sidebar.md
265265
if (text) {
266-
html = this.compile(text);
267-
} else {
268-
for (let i = 0; i < toc.length; i++) {
269-
if (toc[i].ignoreSubHeading) {
270-
const deletedHeaderLevel = toc[i].level;
271-
toc.splice(i, 1);
272-
// Remove headers who are under current header
273-
for (
274-
let j = i;
275-
j < toc.length && deletedHeaderLevel < toc[j].level;
276-
j++
277-
) {
278-
toc.splice(j, 1) && j-- && i++;
279-
}
280-
281-
i--;
266+
return this.compile(text);
267+
}
268+
// compile sidebar from content's headings toc
269+
for (let i = 0; i < toc.length; i++) {
270+
if (toc[i].ignoreSubHeading) {
271+
const deletedHeaderLevel = toc[i].depth;
272+
toc.splice(i, 1);
273+
// Remove headers who are under current header
274+
for (
275+
let j = i;
276+
j < toc.length && deletedHeaderLevel < toc[j].depth;
277+
j++
278+
) {
279+
toc.splice(j, 1) && j-- && i++;
282280
}
283-
}
284281

285-
const tree = this.cacheTree[currentPath] || genTree(toc, level);
286-
html = treeTpl(tree, /* html */ '<ul>{inner}</ul>');
287-
this.cacheTree[currentPath] = tree;
282+
i--;
283+
}
288284
}
289285

286+
const tree = this.cacheTree[currentPath] || genTree(toc, level);
287+
html = treeTpl(tree);
288+
this.cacheTree[currentPath] = tree;
290289
return html;
291290
}
292291

292+
/**
293+
* When current content redirect to a new path file, clean pre content headings toc
294+
*/
295+
resetToc() {
296+
this.toc = [];
297+
}
298+
293299
/**
294300
* Compile sub sidebar
295301
* @param {Number} level Type of heading (h<level> tag)
296302
* @returns {String} Sub-sidebar element
297303
*/
298304
subSidebar(level) {
299-
if (!level) {
300-
this.toc = [];
301-
return;
302-
}
303-
304305
const currentPath = this.router.getCurrentPath();
305306
const { cacheTree, toc } = this;
306307

307308
toc[0] && toc[0].ignoreAllSubs && toc.splice(0);
308-
toc[0] && toc[0].level === 1 && toc.shift();
309+
// remove the first heading from the toc if it is a top-level heading
310+
toc[0] && toc[0].depth === 1 && toc.shift();
309311

310312
for (let i = 0; i < toc.length; i++) {
311313
toc[i].ignoreSubHeading && toc.splice(i, 1) && i--;
@@ -318,12 +320,21 @@ export class Compiler {
318320
return treeTpl(tree);
319321
}
320322

323+
/**
324+
* Compile the text to generate HTML heading element based on the level
325+
* @param {*} text Text content, for now it is only from the _sidebar.md file
326+
* @param {*} level Type of heading (h<level> tag), for now it is always 1
327+
* @returns
328+
*/
321329
header(text, level) {
322-
return this.heading(text, level);
323-
}
324-
325-
article(text) {
326-
return this.compile(text);
330+
const tokenHeading = {
331+
type: 'heading',
332+
raw: text,
333+
depth: level,
334+
text: text,
335+
tokens: [{ type: 'text', raw: text, text: text }],
336+
};
337+
return this.renderer.heading(tokenHeading);
327338
}
328339

329340
/**

src/core/render/gen-tree.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export function genTree(toc, maxLevel) {
1010
const last = {};
1111

1212
toc.forEach(headline => {
13-
const level = headline.level || 1;
13+
const level = headline.depth || 1;
1414
const len = level - 1;
1515

1616
if (level > maxLevel) {

src/core/render/index.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,6 @@ export function Render(Base) {
8484

8585
this._renderTo(markdownElm, html);
8686

87-
// Render sidebar with the TOC
88-
!docsifyConfig.loadSidebar && this._renderSidebar();
89-
9087
// Execute markdown <script>
9188
if (
9289
docsifyConfig.executeScript ||
@@ -298,6 +295,7 @@ export function Render(Base) {
298295
}
299296

300297
this._renderTo('.sidebar-nav', this.compiler.sidebar(text, maxLevel));
298+
301299
sidebarToggleEl.setAttribute('aria-expanded', !isMobile());
302300

303301
const activeElmHref = this.router.toURL(this.route.path);
@@ -309,8 +307,7 @@ export function Render(Base) {
309307
activeEl.parentNode.innerHTML +=
310308
this.compiler.subSidebar(subMaxLevel) || '';
311309
} else {
312-
// Reset toc
313-
this.compiler.subSidebar();
310+
this.compiler.resetToc();
314311
}
315312

316313
// Bind event

src/core/render/tpl.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,13 @@ export function tree(
101101
let innerHTML = '';
102102
toc.forEach(node => {
103103
const title = node.title.replace(/(<([^>]+)>)/g, '');
104-
innerHTML += /* html */ `<li><a class="section-link" href="${node.slug}" title="${title}">${node.title}</a></li>`;
104+
let current = `<li><a class="section-link" href="${node.slug}" title="${title}">${node.title}</a></li>`;
105105
if (node.children) {
106-
innerHTML += tree(node.children, tpl);
106+
// when current node has children, we need put them all in parent's <li> block without the `class="app-sub-sidebar"` attribute
107+
const children = tree(node.children, '<ul>{inner}</ul>');
108+
current = `<li><a class="section-link" href="${node.slug}" title="${title}">${node.title}</a>${children}</li>`;
107109
}
110+
innerHTML += current;
108111
});
109112
return tpl.replace('{inner}', innerHTML);
110113
}

test/e2e/sidebar.test.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,89 @@ test.describe('Sidebar Tests', () => {
6969
expect(page.url()).toMatch(/\/test%3Efoo$/);
7070
});
7171
});
72+
73+
test.describe('Configuration: autoHeader', () => {
74+
test('autoHeader=false', async ({ page }) => {
75+
const docsifyInitConfig = {
76+
config: {
77+
loadSidebar: '_sidebar.md',
78+
autoHeader: false,
79+
},
80+
markdown: {
81+
sidebar: `
82+
- [QuickStartAutoHeader](quickstart.md)
83+
`,
84+
},
85+
routes: {
86+
'/quickstart.md': `
87+
the content of quickstart space
88+
## In the main content there is no h1
89+
`,
90+
},
91+
};
92+
93+
await docsifyInit(docsifyInitConfig);
94+
95+
await page.click('a[href="#/quickstart"]');
96+
expect(page.url()).toMatch(/\/quickstart$/);
97+
// not heading
98+
await expect(page.locator('#quickstart')).toBeHidden();
99+
});
100+
101+
test('autoHeader=true', async ({ page }) => {
102+
const docsifyInitConfig = {
103+
config: {
104+
loadSidebar: '_sidebar.md',
105+
autoHeader: true,
106+
},
107+
markdown: {
108+
sidebar: `
109+
- [QuickStartAutoHeader](quickstart.md )
110+
`,
111+
},
112+
routes: {
113+
'/quickstart.md': `
114+
the content of quickstart space
115+
## In the main content there is no h1
116+
`,
117+
},
118+
};
119+
120+
await docsifyInit(docsifyInitConfig);
121+
122+
await page.click('a[href="#/quickstart"]');
123+
expect(page.url()).toMatch(/\/quickstart$/);
124+
125+
// auto generate default heading id
126+
const autoHeader = page.locator('#quickstartautoheader');
127+
expect(await autoHeader.innerText()).toContain('QuickStartAutoHeader');
128+
});
129+
130+
test('autoHeader=true and custom headingId', async ({ page }) => {
131+
const docsifyInitConfig = {
132+
config: {
133+
loadSidebar: '_sidebar.md',
134+
autoHeader: true,
135+
},
136+
markdown: {
137+
sidebar: `
138+
- [QuickStartAutoHeader](quickstart.md ":id=quickstartId")
139+
`,
140+
},
141+
routes: {
142+
'/quickstart.md': `
143+
the content of quickstart space
144+
## In the main content there is no h1
145+
`,
146+
},
147+
};
148+
149+
await docsifyInit(docsifyInitConfig);
150+
151+
await page.click('a[href="#/quickstart"]');
152+
expect(page.url()).toMatch(/\/quickstart$/);
153+
// auto generate custom heading id
154+
const autoHeader = page.locator('#quickstartId');
155+
expect(await autoHeader.innerText()).toContain('QuickStartAutoHeader');
156+
});
157+
});

0 commit comments

Comments
 (0)