fix: correct loadSider=false render structure issue (#2470)

This commit is contained in:
Koy Zhuang 2024-07-28 16:18:03 +08:00 committed by GitHub
parent 4bc5062fc1
commit 7cbd5322d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 169 additions and 43 deletions

View File

@ -67,17 +67,19 @@ export function Fetch(Base) {
_loadSideAndNav(path, qs, loadSidebar, cb) {
return () => {
if (!loadSidebar) {
return cb();
}
const fn = result => {
const renderSidebar = result => {
this._renderSidebar(result);
cb();
};
// Load sidebar
this.#loadNested(path, qs, loadSidebar, fn, this, true);
if (!loadSidebar) {
// Although, we don't load sidebar from sidebar file, we still need call the render to auto generate sidebar from headings toc
renderSidebar();
return;
}
// Load sidebar from the sidebar file
this.#loadNested(path, qs, loadSidebar, renderSidebar, this, true);
};
}

View File

@ -251,8 +251,8 @@ export class Compiler {
}
/**
* Compile sidebar
* @param {String} text Text content
* Compile sidebar, it uses _sidebar.md ( or specific file) or the content's headings toc to render sidebar.
* @param {String} text Text content from the sidebar file, maybe empty
* @param {Number} level Type of heading (h<level> tag)
* @returns {String} Sidebar element
*/
@ -261,50 +261,53 @@ export class Compiler {
const currentPath = this.router.getCurrentPath();
let html = '';
// compile sidebar from _sidebar.md
if (text) {
html = this.compile(text);
} else {
for (let i = 0; i < toc.length; i++) {
if (toc[i].ignoreSubHeading) {
const deletedHeaderLevel = toc[i].level;
toc.splice(i, 1);
// Remove headers who are under current header
for (
let j = i;
j < toc.length && deletedHeaderLevel < toc[j].level;
j++
) {
toc.splice(j, 1) && j-- && i++;
}
i--;
return this.compile(text);
}
// compile sidebar from content's headings toc
for (let i = 0; i < toc.length; i++) {
if (toc[i].ignoreSubHeading) {
const deletedHeaderLevel = toc[i].depth;
toc.splice(i, 1);
// Remove headers who are under current header
for (
let j = i;
j < toc.length && deletedHeaderLevel < toc[j].depth;
j++
) {
toc.splice(j, 1) && j-- && i++;
}
}
const tree = this.cacheTree[currentPath] || genTree(toc, level);
html = treeTpl(tree, /* html */ '<ul>{inner}</ul>');
this.cacheTree[currentPath] = tree;
i--;
}
}
const tree = this.cacheTree[currentPath] || genTree(toc, level);
html = treeTpl(tree);
this.cacheTree[currentPath] = tree;
return html;
}
/**
* When current content redirect to a new path file, clean pre content headings toc
*/
resetToc() {
this.toc = [];
}
/**
* Compile sub sidebar
* @param {Number} level Type of heading (h<level> tag)
* @returns {String} Sub-sidebar element
*/
subSidebar(level) {
if (!level) {
this.toc = [];
return;
}
const currentPath = this.router.getCurrentPath();
const { cacheTree, toc } = this;
toc[0] && toc[0].ignoreAllSubs && toc.splice(0);
toc[0] && toc[0].level === 1 && toc.shift();
// remove the first heading from the toc if it is a top-level heading
toc[0] && toc[0].depth === 1 && toc.shift();
for (let i = 0; i < toc.length; i++) {
toc[i].ignoreSubHeading && toc.splice(i, 1) && i--;

View File

@ -10,7 +10,7 @@ export function genTree(toc, maxLevel) {
const last = {};
toc.forEach(headline => {
const level = headline.level || 1;
const level = headline.depth || 1;
const len = level - 1;
if (level > maxLevel) {

View File

@ -84,9 +84,6 @@ export function Render(Base) {
this._renderTo(markdownElm, html);
// Render sidebar with the TOC
!docsifyConfig.loadSidebar && this._renderSidebar();
// Execute markdown <script>
if (
docsifyConfig.executeScript ||
@ -298,6 +295,7 @@ export function Render(Base) {
}
this._renderTo('.sidebar-nav', this.compiler.sidebar(text, maxLevel));
sidebarToggleEl.setAttribute('aria-expanded', !isMobile());
const activeElmHref = this.router.toURL(this.route.path);
@ -309,8 +307,7 @@ export function Render(Base) {
activeEl.parentNode.innerHTML +=
this.compiler.subSidebar(subMaxLevel) || '';
} else {
// Reset toc
this.compiler.subSidebar();
this.compiler.resetToc();
}
// Bind event

View File

@ -101,10 +101,13 @@ export function tree(
let innerHTML = '';
toc.forEach(node => {
const title = node.title.replace(/(<([^>]+)>)/g, '');
innerHTML += /* html */ `<li><a class="section-link" href="${node.slug}" title="${title}">${node.title}</a></li>`;
let current = `<li><a class="section-link" href="${node.slug}" title="${title}">${node.title}</a></li>`;
if (node.children) {
innerHTML += tree(node.children, tpl);
// when current node has children, we need put them all in parent's <li> block without the `class="app-sub-sidebar"` attribute
const children = tree(node.children, '<ul>{inner}</ul>');
current = `<li><a class="section-link" href="${node.slug}" title="${title}">${node.title}</a>${children}</li>`;
}
innerHTML += current;
});
return tpl.replace('{inner}', innerHTML);
}

View File

@ -0,0 +1,121 @@
import docsifyInit from '../helpers/docsify-init.js';
describe('Test sidebar render toc structure', function () {
test('Render sidebar with loadSidebar=true and the _sidebar.md file', async () => {
await docsifyInit({
config: {
loadSidebar: '_sidebar.md',
},
markdown: {
homepage: '# Hello World',
sidebar: `
- Getting started
- [Level1](QuickStart.md)
- [Level2](QuickStart2.md)
`,
},
waitForSelector: '.sidebar-nav > ul',
});
const sidebarElm = document.querySelector('.sidebar');
/**
* Expected render result
* ==========================
*<ul>
* <li>
* Getting started
* <ul>
* <li>
* <a href="#/QuickStart" title="Level1">Level1</a>
* <ul>
* <li><a href="#/QuickStart2" title="Level2">Level2</a></li>
* </ul>
* </li>
* </ul>
* </li>
* </ul>
*/
const GettingStarted = document.querySelector('.sidebar-nav > ul > li');
const level1_Elm = document.querySelector(
'.sidebar-nav > ul > li > ul> li',
);
const level1_A_tag = level1_Elm.querySelector('a');
const level2_Elm = level1_Elm.querySelector(' ul > li ');
const level2_A_tag = level2_Elm.querySelector('a');
expect(sidebarElm).not.toBeNull();
expect(GettingStarted).not.toBeNull();
expect(level1_Elm).not.toBeNull();
expect(level1_A_tag).not.toBeNull();
expect(level2_Elm).not.toBeNull();
expect(level2_A_tag).not.toBeNull();
expect(level1_A_tag.textContent).toContain('Level1');
expect(level2_A_tag.textContent).toContain('Level2');
});
test('Render sidebar with loadSidebar=false should be same to loadSidebar=true sidebar structure', async () => {
await docsifyInit({
config: {
loadSidebar: false,
},
markdown: {
homepage: `
# Getting started
some thing
## Level1
foo
### Level2
bar
`,
},
waitForSelector: '.sidebar-nav > ul',
});
const sidebarElm = document.querySelector('.sidebar');
/**
* Expected render result
* ==========================
*<ul>
* <li>
* Getting started
* <ul>
* <li>
* <a href="#/QuickStart" title="Level1">Level1</a>
* <ul>
* <li><a href="#/QuickStart2" title="Level2">Level2</a></li>
* </ul>
* </li>
* </ul>
* </li>
* </ul>
*/
const appSubSidebarTargetElm = document.querySelector('.sidebar-nav > ul');
expect(appSubSidebarTargetElm).not.toBeNull();
const ulClass = appSubSidebarTargetElm.className;
// the sidebar-nav > ul should have the class app-sub-sidebar
expect(ulClass).toContain('app-sub-sidebar');
const GettingStarted = document.querySelector('.sidebar-nav > ul > li');
const level1_Elm = document.querySelector(
'.sidebar-nav > ul > li > ul> li',
);
const level1_A_tag = level1_Elm.querySelector('a');
const level2_Elm = level1_Elm.querySelector(' ul > li ');
const level2_A_tag = level2_Elm.querySelector('a');
expect(sidebarElm).not.toBeNull();
expect(GettingStarted).not.toBeNull();
expect(level1_Elm).not.toBeNull();
expect(level1_A_tag).not.toBeNull();
expect(level2_Elm).not.toBeNull();
expect(level2_A_tag).not.toBeNull();
expect(level1_A_tag.textContent).toContain('Level1');
expect(level2_A_tag.textContent).toContain('Level2');
});
});