Skip to content

Commit

Permalink
fix: correct loadSider=false render structure issue (#2470)
Browse files Browse the repository at this point in the history
  • Loading branch information
Koooooo-7 authored Jul 28, 2024
1 parent 4bc5062 commit 7cbd532
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 43 deletions.
16 changes: 9 additions & 7 deletions src/core/fetch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
}

Expand Down
59 changes: 31 additions & 28 deletions src/core/render/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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--;
Expand Down
2 changes: 1 addition & 1 deletion src/core/render/gen-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 2 additions & 5 deletions src/core/render/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 ||
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down
7 changes: 5 additions & 2 deletions src/core/render/tpl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
121 changes: 121 additions & 0 deletions test/integration/sidebar.test.js
Original file line number Diff line number Diff line change
@@ -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');
});
});

0 comments on commit 7cbd532

Please sign in to comment.