Merge pull request #2893 from ehuss/fix-fold-sub-chapter

Fix heading nav with folded chapters
This commit is contained in:
Eric Huss 2025-10-21 00:37:48 +00:00 committed by GitHub
commit 7b7dee4a4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 212 additions and 127 deletions

View file

@ -571,17 +571,18 @@ html:not(.sidebar-resizing) .sidebar {
line-height: 2.2em; line-height: 2.2em;
} }
.chapter ol {
width: 100%;
}
.chapter li { .chapter li {
display: flex;
color: var(--sidebar-non-existant); color: var(--sidebar-non-existant);
} }
/* This is a span wrapping the chapter link and the fold chevron. */
.chapter-link-wrapper {
/* Used to position the chevron to the right, allowing the text to wrap before it. */
display: flex;
}
.chapter li a { .chapter li a {
display: block; /* Remove underlines. */
padding: 0;
text-decoration: none; text-decoration: none;
color: var(--sidebar-fg); color: var(--sidebar-fg);
} }
@ -594,21 +595,22 @@ html:not(.sidebar-resizing) .sidebar {
color: var(--sidebar-active); color: var(--sidebar-active);
} }
.chapter li > a.toggle { /* This is the toggle chevron. */
.chapter-fold-toggle {
cursor: pointer; cursor: pointer;
display: block; /* Positions the chevron to the side. */
margin-inline-start: auto; margin-inline-start: auto;
padding: 0 10px; padding: 0 10px;
user-select: none; user-select: none;
opacity: 0.68; opacity: 0.68;
} }
.chapter li > a.toggle div { .chapter-fold-toggle div {
transition: transform 0.5s; transition: transform 0.5s;
} }
/* collapse the section */ /* collapse the section */
.chapter li:not(.expanded) + li > ol { .chapter li:not(.expanded) > ol {
display: none; display: none;
} }
@ -617,10 +619,12 @@ html:not(.sidebar-resizing) .sidebar {
margin-block-start: 0.6em; margin-block-start: 0.6em;
} }
.chapter li.expanded > a.toggle div { /* When expanded, rotate the chevron to point down. */
.chapter li.expanded > span > .chapter-fold-toggle div {
transform: rotate(90deg); transform: rotate(90deg);
} }
/* Horizontal line in chapter list. */
.spacer { .spacer {
width: 100%; width: 100%;
height: 3px; height: 3px;
@ -630,6 +634,7 @@ html:not(.sidebar-resizing) .sidebar {
background-color: var(--sidebar-spacer); background-color: var(--sidebar-spacer);
} }
/* On touch devices, add more vertical spacing to make it easier to tap links. */
@media (-moz-touch-enabled: 1), (pointer: coarse) { @media (-moz-touch-enabled: 1), (pointer: coarse) {
.chapter li a { padding: 5px 0; } .chapter li a { padding: 5px 0; }
.spacer { margin: 10px 0; } .spacer { margin: 10px 0; }
@ -741,7 +746,6 @@ html:not(.sidebar-resizing) .sidebar {
content: ''; content: '';
position: absolute; position: absolute;
left: -16px; left: -16px;
top: 0;
margin-top: 10px; margin-top: 10px;
width: 8px; width: 8px;
height: 8px; height: 8px;

View file

@ -29,14 +29,9 @@ class MDBookSidebarScrollbox extends HTMLElement {
&& current_page.endsWith('/index.html')) { && current_page.endsWith('/index.html')) {
link.classList.add('active'); link.classList.add('active');
let parent = link.parentElement; let parent = link.parentElement;
if (parent && parent.classList.contains('chapter-item')) {
parent.classList.add('expanded');
}
while (parent) { while (parent) {
if (parent.tagName === 'LI' && parent.previousElementSibling) { if (parent.tagName === 'LI' && parent.classList.contains('chapter-item')) {
if (parent.previousElementSibling.classList.contains('chapter-item')) { parent.classList.add('expanded');
parent.previousElementSibling.classList.add('expanded');
}
} }
parent = parent.parentElement; parent = parent.parentElement;
} }
@ -62,9 +57,9 @@ class MDBookSidebarScrollbox extends HTMLElement {
} }
} }
// Toggle buttons // Toggle buttons
const sidebarAnchorToggles = document.querySelectorAll('#mdbook-sidebar a.toggle'); const sidebarAnchorToggles = document.querySelectorAll('.chapter-fold-toggle');
function toggleSection(ev) { function toggleSection(ev) {
ev.currentTarget.parentElement.classList.toggle('expanded'); ev.currentTarget.parentElement.parentElement.classList.toggle('expanded');
} }
Array.from(sidebarAnchorToggles).forEach(el => { Array.from(sidebarAnchorToggles).forEach(el => {
el.addEventListener('click', toggleSection); el.addEventListener('click', toggleSection);
@ -237,17 +232,12 @@ window.customElements.define('mdbook-sidebar-scrollbox', MDBookSidebarScrollbox)
// be expanded. // be expanded.
function updateHeaderExpanded(currentA) { function updateHeaderExpanded(currentA) {
// Add expanded to all header-item li ancestors. // Add expanded to all header-item li ancestors.
let current = currentA.parentElement.parentElement.parentElement; let current = currentA.parentElement;
while (current.tagName === 'LI') { while (current) {
const prevSibling = current.previousElementSibling; if (current.tagName === 'LI' && current.classList.contains('header-item')) {
if (prevSibling !== null current.classList.add('expanded');
&& prevSibling.tagName === 'LI'
&& prevSibling.classList.contains('header-item')) {
prevSibling.classList.add('expanded');
current = prevSibling.parentElement.parentElement;
} else {
break;
} }
current = current.parentElement;
} }
} }
@ -343,19 +333,6 @@ window.customElements.define('mdbook-sidebar-scrollbox', MDBookSidebarScrollbox)
if (activeSection === null) { if (activeSection === null) {
return; return;
} }
const activeItem = activeSection.parentElement;
const activeList = activeItem.parentElement;
// Build a tree of headers in the sidebar.
const rootLi = document.createElement('li');
rootLi.classList.add('header-item');
rootLi.classList.add('expanded');
const rootOl = document.createElement('ol');
rootOl.classList.add('section');
rootLi.appendChild(rootOl);
const stack = [{ level: 0, ol: rootOl }];
// The level where it will start folding deeply nested headers.
const foldLevel = 3;
const main = document.getElementsByTagName('main')[0]; const main = document.getElementsByTagName('main')[0];
headers = Array.from(main.querySelectorAll('h2, h3, h4, h5, h6')) headers = Array.from(main.querySelectorAll('h2, h3, h4, h5, h6'))
@ -365,57 +342,90 @@ window.customElements.define('mdbook-sidebar-scrollbox', MDBookSidebarScrollbox)
return; return;
} }
// Build a tree of headers in the sidebar.
const stack = [];
const firstLevel = parseInt(headers[0].tagName.charAt(1));
for (let i = 1; i < firstLevel; i++) {
const ol = document.createElement('ol');
ol.classList.add('section');
if (stack.length > 0) {
stack[stack.length - 1].ol.appendChild(ol);
}
stack.push({level: i + 1, ol: ol});
}
// The level where it will start folding deeply nested headers.
const foldLevel = 3;
for (let i = 0; i < headers.length; i++) { for (let i = 0; i < headers.length; i++) {
const header = headers[i]; const header = headers[i];
const level = parseInt(header.tagName.charAt(1)); const level = parseInt(header.tagName.charAt(1));
const currentLevel = stack[stack.length - 1].level;
if (level > currentLevel) {
// Begin nesting to this level.
for (let nextLevel = currentLevel + 1; nextLevel <= level; nextLevel++) {
const ol = document.createElement('ol');
ol.classList.add('section');
const last = stack[stack.length - 1];
const lastChild = last.ol.lastChild;
// Handle the case where jumping more than one nesting
// level, which doesn't have a list item to place this new
// list inside of.
if (lastChild) {
lastChild.appendChild(ol);
} else {
last.ol.appendChild(ol);
}
stack.push({level: nextLevel, ol: ol});
}
} else if (level < currentLevel) {
while (stack.length > 1 && stack[stack.length - 1].level >= level) {
stack.pop();
}
}
const li = document.createElement('li'); const li = document.createElement('li');
li.classList.add('header-item'); li.classList.add('header-item');
li.classList.add('expanded'); li.classList.add('expanded');
if (level < foldLevel) { if (level < foldLevel) {
li.classList.add('expanded'); li.classList.add('expanded');
} }
const span = document.createElement('span');
span.classList.add('chapter-link-wrapper');
const a = document.createElement('a'); const a = document.createElement('a');
span.appendChild(a);
a.href = '#' + header.id; a.href = '#' + header.id;
a.classList.add('header-in-summary'); a.classList.add('header-in-summary');
a.innerHTML = header.children[0].innerHTML; a.innerHTML = header.children[0].innerHTML;
a.addEventListener('click', headerThresholdClick); a.addEventListener('click', headerThresholdClick);
li.appendChild(a);
const nextHeader = headers[i + 1]; const nextHeader = headers[i + 1];
if (nextHeader !== undefined) { if (nextHeader !== undefined) {
const nextLevel = parseInt(nextHeader.tagName.charAt(1)); const nextLevel = parseInt(nextHeader.tagName.charAt(1));
if (nextLevel > level && level >= foldLevel) { if (nextLevel > level && level >= foldLevel) {
const div = document.createElement('div');
div.textContent = '❱';
const toggle = document.createElement('a'); const toggle = document.createElement('a');
toggle.classList.add('toggle'); toggle.classList.add('chapter-fold-toggle');
toggle.classList.add('header-toggle'); toggle.classList.add('header-toggle');
toggle.appendChild(div);
toggle.addEventListener('click', () => { toggle.addEventListener('click', () => {
li.classList.toggle('expanded'); li.classList.toggle('expanded');
}); });
li.appendChild(toggle); const toggleDiv = document.createElement('div');
toggleDiv.textContent = '❱';
toggle.appendChild(toggleDiv);
span.appendChild(toggle);
headerToggles.push(li); headerToggles.push(li);
} }
} }
li.appendChild(span);
// Find the appropriate parent level.
while (stack.length > 1 && stack[stack.length - 1].level >= level) {
stack.pop();
}
const currentParent = stack[stack.length - 1]; const currentParent = stack[stack.length - 1];
currentParent.ol.appendChild(li); currentParent.ol.appendChild(li);
// Create new nested ol for potential children.
const nestedOl = document.createElement('ol');
nestedOl.classList.add('section');
const nestedLi = document.createElement('li');
nestedLi.appendChild(nestedOl);
currentParent.ol.appendChild(nestedLi);
stack.push({ level: level, ol: nestedOl });
} }
activeList.insertBefore(rootLi, activeItem.nextSibling); const activeItemSpan = activeSection.parentElement;
activeItemSpan.after(stack[0].ol);
}); });
document.addEventListener('DOMContentLoaded', reloadCurrentHeader); document.addEventListener('DOMContentLoaded', reloadCurrentHeader);

View file

@ -57,13 +57,13 @@ impl HelperDef for RenderToc {
out.write("<ol class=\"chapter\">")?; out.write("<ol class=\"chapter\">")?;
let mut current_level = 1; let mut current_level = 1;
let mut first = true;
for item in chapters { for item in chapters {
let (_section, level) = if let Some(s) = item.get("section") { let level = item
(s.as_str(), s.matches('.').count()) .get("section")
} else { .map(|s| s.matches('.').count())
("", 1) .unwrap_or(1);
};
// Expand if folding is disabled, or if levels that are larger than this would not // Expand if folding is disabled, or if levels that are larger than this would not
// be folded. // be folded.
@ -71,25 +71,31 @@ impl HelperDef for RenderToc {
match level.cmp(&current_level) { match level.cmp(&current_level) {
Ordering::Greater => { Ordering::Greater => {
while level > current_level { // There is an assumption that when descending, it can
out.write("<li>")?; // only go one level down at a time. This should be
out.write("<ol class=\"section\">")?; // enforced by the nature of markdown lists and the
current_level += 1; // summary parser.
} assert_eq!(level, current_level + 1);
write_li_open_tag(out, is_expanded, false)?; current_level += 1;
out.write("<ol class=\"section\">")?;
write_li_open_tag(out, is_expanded)?;
} }
Ordering::Less => { Ordering::Less => {
while level < current_level { while level < current_level {
out.write("</ol>")?;
out.write("</li>")?; out.write("</li>")?;
out.write("</ol>")?;
current_level -= 1; current_level -= 1;
} }
write_li_open_tag(out, is_expanded, false)?; write_li_open_tag(out, is_expanded)?;
} }
Ordering::Equal => { Ordering::Equal => {
write_li_open_tag(out, is_expanded, !item.contains_key("section"))?; if !first {
out.write("</li>")?;
}
write_li_open_tag(out, is_expanded)?;
} }
} }
first = false;
// Spacer // Spacer
if item.contains_key("spacer") { if item.contains_key("spacer") {
@ -105,6 +111,8 @@ impl HelperDef for RenderToc {
continue; continue;
} }
out.write("<span class=\"chapter-link-wrapper\">")?;
// Link // Link
let path_exists = match item.get("path") { let path_exists = match item.get("path") {
Some(path) if !path.is_empty() => { Some(path) if !path.is_empty() => {
@ -121,7 +129,7 @@ impl HelperDef for RenderToc {
true true
} }
_ => { _ => {
out.write("<div>")?; out.write("<span>")?;
false false
} }
}; };
@ -142,41 +150,35 @@ impl HelperDef for RenderToc {
if path_exists { if path_exists {
out.write("</a>")?; out.write("</a>")?;
} else { } else {
out.write("</div>")?; out.write("</span>")?;
} }
// Render expand/collapse toggle // Render expand/collapse toggle
if let Some(flag) = item.get("has_sub_items") { if let Some(flag) = item.get("has_sub_items") {
let has_sub_items = flag.parse::<bool>().unwrap_or_default(); let has_sub_items = flag.parse::<bool>().unwrap_or_default();
if fold_enable && has_sub_items { if fold_enable && has_sub_items {
out.write("<a class=\"toggle\"><div>❱</div></a>")?; // The <div> here is to manage rotating the element when
// the chapter title is long and word-wraps.
out.write("<a class=\"chapter-fold-toggle\"><div>❱</div></a>")?;
} }
} }
out.write("</li>")?; out.write("</span>")?;
} }
while current_level > 1 { while current_level > 0 {
out.write("</ol>")?;
out.write("</li>")?; out.write("</li>")?;
out.write("</ol>")?;
current_level -= 1; current_level -= 1;
} }
out.write("</ol>")?;
Ok(()) Ok(())
} }
} }
fn write_li_open_tag( fn write_li_open_tag(out: &mut dyn Output, is_expanded: bool) -> Result<(), std::io::Error> {
out: &mut dyn Output,
is_expanded: bool,
is_affix: bool,
) -> Result<(), std::io::Error> {
let mut li = String::from("<li class=\"chapter-item "); let mut li = String::from("<li class=\"chapter-item ");
if is_expanded { if is_expanded {
li.push_str("expanded "); li.push_str("expanded ");
} }
if is_affix {
li.push_str("affix ");
}
li.push_str("\">"); li.push_str("\">");
out.write(&li) out.write(&li)
} }

View file

@ -0,0 +1,6 @@
[book]
title = "heading-nav-folded"
[output.html.fold]
enable = true
level = 0

View file

@ -0,0 +1,7 @@
# Summary
- [Introduction](./intro.md)
- [Sub chapter](./sub/index.md)
- [Sub inner](./sub/inner/index.md)
- [Sub second chapter](./sub/second.md)
- [Next main chapter](./next-main.md)

View file

@ -0,0 +1,9 @@
# Introduction
## Heading A
### Heading A2
### Heading A3
## Heading B

View file

@ -0,0 +1 @@
# Next main chapter

View file

@ -0,0 +1,3 @@
# Sub chapter
## Sub-chapter heading

View file

@ -0,0 +1,3 @@
# Sub inner
## Inner chapter heading

View file

@ -0,0 +1,3 @@
# Sub second chapter
## Second chapter heading

View file

@ -6,3 +6,4 @@
- [Collapsed headings](collapsed.md) - [Collapsed headings](collapsed.md)
- [Headings with markup](markup.md) - [Headings with markup](markup.md)
- [Current scrolls to bottom](current-to-bottom.md) - [Current scrolls to bottom](current-to-bottom.md)
- [Unusual heading levels](unusual-heading-levels.md)

View file

@ -0,0 +1,9 @@
# Unusual heading levels
### Heading 3
## Heading 2
#### Heading 5
#### Heading 5.1

View file

@ -3,49 +3,54 @@
set-window-size: (1400, 800) set-window-size: (1400, 800)
go-to: |DOC_PATH| + "heading-nav/collapsed.html" go-to: |DOC_PATH| + "heading-nav/collapsed.html"
assert-count: (".header-item", 12) assert-count: (".header-item", 11)
assert-count: (".current-header", 1) assert-count: (".current-header", 1)
assert-text: (".current-header", "Heading 1") assert-text: (".current-header", "Heading 1")
// Collapsed elements do not have "expanded" class. // Collapsed elements do not have "expanded" class.
assert-attribute: ("li:has(> a[href='#heading-12'])", {"class": "header-item"}) assert-attribute: ("li:has(> span > a[href='#heading-12'])", {"class": "header-item"})
assert-attribute: ("li:has(> a[href='#heading-21'])", {"class": "header-item"}) assert-attribute: ("li:has(> span > a[href='#heading-21'])", {"class": "header-item"})
// Click 1.2, doesn't change expanded. // Click 1.2, expands it.
click: "a.header-in-summary[href='#heading-12']" click: "a.header-in-summary[href='#heading-12']"
assert-attribute: ("li:has(> a[href='#heading-12'])", {"class": "header-item"}) assert-attribute: ("li:has(> span > a[href='#heading-12'])", {"class": "header-item expanded"})
assert-attribute: ("li:has(> a[href='#heading-21'])", {"class": "header-item"}) assert-attribute: ("li:has(> span > a[href='#heading-21'])", {"class": "header-item"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-12']]]/ol", {"display": "none"}) assert-css: ("//a[@href='#heading-12']/../following-sibling::ol", {"display": "block"})
// Click expand chevron.
// 1.2.1 and 1.2.2 should be visible // Click 1.1, should collapse it.
click: "a.header-in-summary[href='#heading-11']"
assert-attribute: ("li:has(> span > a[href='#heading-12'])", {"class": "header-item"})
assert-attribute: ("li:has(> span > a[href='#heading-21'])", {"class": "header-item"})
assert-css: ("//a[@href='#heading-12']/../following-sibling::ol", {"display": "none"})
// Click the chevron, should expand it.
click: "a.header-in-summary[href='#heading-12'] ~ a.header-toggle" click: "a.header-in-summary[href='#heading-12'] ~ a.header-toggle"
assert-attribute: ("li:has(> a[href='#heading-12'])", {"class": "header-item expanded"}) assert-attribute: ("li:has(> span > a[href='#heading-12'])", {"class": "header-item expanded"})
assert-attribute: ("li:has(> a[href='#heading-21'])", {"class": "header-item"}) assert-attribute: ("li:has(> span > a[href='#heading-21'])", {"class": "header-item"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-12']]]/ol", {"display": "block"}) assert-css: ("//a[@href='#heading-12']/../following-sibling::ol", {"display": "block"})
// Click 1.3 // Click 1.3
click: "a.header-in-summary[href='#heading-13']" click: "a.header-in-summary[href='#heading-13']"
// Everything should be collapsed // Everything should be collapsed
assert-attribute: ("li:has(> a[href='#heading-12'])", {"class": "header-item"}) assert-attribute: ("li:has(> span > a[href='#heading-12'])", {"class": "header-item"})
assert-attribute: ("li:has(> a[href='#heading-21'])", {"class": "header-item"}) assert-attribute: ("li:has(> span > a[href='#heading-21'])", {"class": "header-item"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-12']]]/ol", {"display": "none"}) assert-css: ("//a[@href='#heading-12']/../following-sibling::ol", {"display": "none"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-21']]]/ol", {"display": "none"}) assert-css: ("//a[@href='#heading-21']/../following-sibling::ol", {"display": "none"})
assert-attribute: ("li:has(> span > a[href='#heading-12'])", {"class": "header-item"})
assert-attribute: ("li:has(> span > a[href='#heading-21'])", {"class": "header-item"})
assert-attribute: ("li:has(> span > a[href='#heading-211'])", {"class": "header-item"})
assert-attribute: ("li:has(> span > a[href='#heading-2111'])", {"class": "header-item"})
assert-attribute: ("li:has(> a[href='#heading-12'])", {"class": "header-item"})
assert-attribute: ("li:has(> a[href='#heading-21'])", {"class": "header-item"})
assert-attribute: ("li:has(> a[href='#heading-211'])", {"class": "header-item"})
assert-attribute: ("li:has(> a[href='#heading-2111'])", {"class": "header-item"})
// Scroll to bottom of page // Scroll to bottom of page
press-key: 'PageDown' press-key: 'PageDown'
press-key: 'PageDown' press-key: 'PageDown'
press-key: 'PageDown' press-key: 'PageDown'
press-key: 'PageDown' press-key: 'PageDown'
// 2.1.1.1.1 should be visible, and all the chevrons should be open, and expanded should be on each one // 2.1.1.1.1 should be visible, and all the chevrons should be open, and expanded should be on each one
assert-attribute: ("li:has(> a[href='#heading-12'])", {"class": "header-item"}) assert-attribute: ("li:has(> span > a[href='#heading-12'])", {"class": "header-item"})
assert-attribute: ("li:has(> a[href='#heading-21'])", {"class": "header-item expanded"}) assert-attribute: ("li:has(> span > a[href='#heading-21'])", {"class": "header-item expanded"})
assert-attribute: ("li:has(> a[href='#heading-211'])", {"class": "header-item expanded"}) assert-attribute: ("li:has(> span > a[href='#heading-211'])", {"class": "header-item expanded"})
assert-attribute: ("li:has(> a[href='#heading-2111'])", {"class": "header-item expanded"}) assert-attribute: ("li:has(> span > a[href='#heading-2111'])", {"class": "header-item expanded"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-12']]]/ol", {"display": "none"}) assert-css: ("//a[@href='#heading-12']/../following-sibling::ol", {"display": "none"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-21']]]/ol", {"display": "block"}) assert-css: ("//a[@href='#heading-21']/../following-sibling::ol", {"display": "block"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-211']]]/ol", {"display": "block"}) assert-css: ("//a[@href='#heading-211']/../following-sibling::ol", {"display": "block"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-2111']]]/ol", {"display": "block"}) assert-css: ("//a[@href='#heading-2111']/../following-sibling::ol", {"display": "block"})

View file

@ -0,0 +1,3 @@
// Tests when chapter folding is enabled.
go-to: |DOC_PATH| + "heading-nav-folded/index.html"

View file

@ -3,7 +3,7 @@
set-window-size: (1400, 800) set-window-size: (1400, 800)
go-to: |DOC_PATH| + "heading-nav/large-intro.html" go-to: |DOC_PATH| + "heading-nav/large-intro.html"
assert-count: (".header-item", 2) assert-count: (".header-item", 1)
assert-count: (".current-header", 0) assert-count: (".current-header", 0)
scroll-to: "#first-header" scroll-to: "#first-header"

View file

@ -3,7 +3,7 @@
set-window-size: (1400, 800) set-window-size: (1400, 800)
go-to: |DOC_PATH| + "heading-nav/markup.html" go-to: |DOC_PATH| + "heading-nav/markup.html"
assert-count: (".header-item", 5) assert-count: (".header-item", 4)
assert-count: (".current-header", 1) assert-count: (".current-header", 1)
assert-text: (".current-header", "Heading with code or italic or bold or strike") assert-text: (".current-header", "Heading with code or italic or bold or strike")
assert-property: (".current-header", {"innerHTML": "Heading with <code>code</code> or <em>italic</em> or <strong>bold</strong> or <del>strike</del>"}) assert-property: (".current-header", {"innerHTML": "Heading with <code>code</code> or <em>italic</em> or <strong>bold</strong> or <del>strike</del>"})

View file

@ -3,7 +3,7 @@
set-window-size: (1400, 800) set-window-size: (1400, 800)
go-to: |DOC_PATH| + "heading-nav/normal-intro.html" go-to: |DOC_PATH| + "heading-nav/normal-intro.html"
assert-count: (".header-item", 4) assert-count: (".header-item", 3)
assert-count: (".current-header", 1) assert-count: (".current-header", 1)
assert-text: (".current-header", "The first heading") assert-text: (".current-header", "The first heading")

View file

@ -0,0 +1,7 @@
// Tests for unusual heading levels
set-window-size: (1400, 800)
go-to: |DOC_PATH| + "heading-nav/unusual-heading-levels.html"
assert-property: ("//a[@href='unusual-heading-levels.html']/../following-sibling::ol", {"innerHTML": '<ol class="section"><li class="header-item expanded"><span class="chapter-link-wrapper"><a href="#heading-3" class="header-in-summary current-header">Heading 3</a></span></li></ol><li class="header-item expanded"><span class="chapter-link-wrapper"><a href="#heading-2" class="header-in-summary">Heading 2</a></span><ol class="section"><ol class="section"><li class="header-item expanded"><span class="chapter-link-wrapper"><a href="#heading-5" class="header-in-summary">Heading 5</a></span></li><li class="header-item expanded"><span class="chapter-link-wrapper"><a href="#heading-51" class="header-in-summary">Heading 5.1</a></span></li></ol></ol></li>'})

View file

@ -20,16 +20,22 @@ fn readme_to_index() {
) )
.check_toc_js(str![[r#" .check_toc_js(str![[r#"
<ol class="chapter"> <ol class="chapter">
<li class="chapter-item expanded affix "> <li class="chapter-item expanded ">
<span class="chapter-link-wrapper">
<a href="index.html">Intro</a> <a href="index.html">Intro</a>
</span>
</li> </li>
<li class="chapter-item expanded "> <li class="chapter-item expanded ">
<span class="chapter-link-wrapper">
<a href="first/index.html"> <a href="first/index.html">
<strong aria-hidden="true">1.</strong> First</a> <strong aria-hidden="true">1.</strong> First</a>
</span>
</li> </li>
<li class="chapter-item expanded "> <li class="chapter-item expanded ">
<span class="chapter-link-wrapper">
<a href="second/index.html"> <a href="second/index.html">
<strong aria-hidden="true">2.</strong> Second</a> <strong aria-hidden="true">2.</strong> Second</a>
</span>
</li> </li>
</ol> </ol>
"#]]); "#]]);

View file

@ -148,16 +148,22 @@ fn summary_with_markdown_formatting() {
.check_toc_js(str![[r#" .check_toc_js(str![[r#"
<ol class="chapter"> <ol class="chapter">
<li class="chapter-item expanded "> <li class="chapter-item expanded ">
<span class="chapter-link-wrapper">
<a href="formatted-summary.html"> <a href="formatted-summary.html">
<strong aria-hidden="true">1.</strong> Italic code *escape* `escape2`</a> <strong aria-hidden="true">1.</strong> Italic code *escape* `escape2`</a>
</span>
</li> </li>
<li class="chapter-item expanded "> <li class="chapter-item expanded ">
<span class="chapter-link-wrapper">
<a href="soft.html"> <a href="soft.html">
<strong aria-hidden="true">2.</strong> Soft line break</a> <strong aria-hidden="true">2.</strong> Soft line break</a>
</span>
</li> </li>
<li class="chapter-item expanded "> <li class="chapter-item expanded ">
<span class="chapter-link-wrapper">
<a href="escaped-tag.html"> <a href="escaped-tag.html">
<strong aria-hidden="true">3.</strong> &lt;escaped tag&gt;</a> <strong aria-hidden="true">3.</strong> &lt;escaped tag&gt;</a>
</span>
</li> </li>
</ol> </ol>
"#]]) "#]])