diff --git a/crates/mdbook-html/front-end/templates/toc.js.hbs b/crates/mdbook-html/front-end/templates/toc.js.hbs index 1a9f751ccf..c0ebbb6587 100644 --- a/crates/mdbook-html/front-end/templates/toc.js.hbs +++ b/crates/mdbook-html/front-end/templates/toc.js.hbs @@ -23,10 +23,14 @@ class MDBookSidebarScrollbox extends HTMLElement { link.href = path_to_root + href; } // The 'index' page is supposed to alias the first chapter in the book. - if (link.href === current_page - || i === 0 - && path_to_root === '' - && current_page.endsWith('/index.html')) { + const on_book_index = path_to_root === '' + && current_page.endsWith('/index.html'); + const is_index_alias = i === 0 && on_book_index; + // When the book index displays the first chapter, don't also highlight + // other chapters that incorrectly resolve to `index.html` (issue #2883). + const is_exact_match = link.href === current_page + && !(on_book_index && i !== 0); + if (is_exact_match || is_index_alias) { link.classList.add('active'); let parent = link.parentElement; while (parent) { @@ -39,11 +43,19 @@ class MDBookSidebarScrollbox extends HTMLElement { } // Track and set sidebar scroll position this.addEventListener('click', e => { - if (e.target.tagName === 'A') { - const clientRect = e.target.getBoundingClientRect(); - const sidebarRect = this.getBoundingClientRect(); - sessionStorage.setItem('sidebar-scroll-offset', clientRect.top - sidebarRect.top); + const link = e.target.closest('a'); + if (!link || !this.contains(link)) { + return; + } + const href = link.getAttribute('href'); + // Ignore fold toggles and in-page header links; chapter links may + // contain child elements (e.g. ) so e.target is not always . + if (!href || href.startsWith('#') || link.classList.contains('chapter-fold-toggle')) { + return; } + const clientRect = link.getBoundingClientRect(); + const sidebarRect = this.getBoundingClientRect(); + sessionStorage.setItem('sidebar-scroll-offset', clientRect.top - sidebarRect.top); }, { passive: true }); const sidebarScrollOffset = sessionStorage.getItem('sidebar-scroll-offset'); sessionStorage.removeItem('sidebar-scroll-offset'); @@ -335,10 +347,25 @@ window.customElements.define('mdbook-sidebar-scrollbox', MDBookSidebarScrollbox) }); } + // Returns the node to copy header text from. When the heading uses an + // empty `.header` link as its first child (see issue #221), use the full + // heading so the sidebar still shows the title text. + function headerContentRoot(header) { + const first = header.children[0]; + if (first?.tagName === 'A' && first.classList.contains('header')) { + if (first.textContent.trim() === '') { + return header; + } + return first; + } + return first; + } + // Takes the nodes from the given head and copies them over to the // destination, along with some filtering. function filterHeader(source, dest) { const clone = source.cloneNode(true); + clone.querySelectorAll('a.header').forEach(a => a.remove()); clone.querySelectorAll('mark').forEach(mark => { mark.replaceWith(...mark.childNodes); }); @@ -417,7 +444,7 @@ window.customElements.define('mdbook-sidebar-scrollbox', MDBookSidebarScrollbox) span.appendChild(a); a.href = '#' + header.id; a.classList.add('header-in-summary'); - filterHeader(header.children[0], a); + filterHeader(headerContentRoot(header), a); a.addEventListener('click', headerThresholdClick); const nextHeader = headers[i + 1]; if (nextHeader !== undefined) { diff --git a/tests/gui/books/readme-not-first/book.toml b/tests/gui/books/readme-not-first/book.toml new file mode 100644 index 0000000000..6c847796d8 --- /dev/null +++ b/tests/gui/books/readme-not-first/book.toml @@ -0,0 +1,2 @@ +[book] +title = "readme-not-first" diff --git a/tests/gui/books/readme-not-first/src/README.md b/tests/gui/books/readme-not-first/src/README.md new file mode 100644 index 0000000000..e10b99d013 --- /dev/null +++ b/tests/gui/books/readme-not-first/src/README.md @@ -0,0 +1 @@ +# Introduction diff --git a/tests/gui/books/readme-not-first/src/SUMMARY.md b/tests/gui/books/readme-not-first/src/SUMMARY.md new file mode 100644 index 0000000000..2b55b91b2e --- /dev/null +++ b/tests/gui/books/readme-not-first/src/SUMMARY.md @@ -0,0 +1,6 @@ +# Summary + +[Prefix 1](prefix-1.md) +[Prefix 2](prefix-2.md) + +- [Introduction](README.md) diff --git a/tests/gui/books/readme-not-first/src/prefix-1.md b/tests/gui/books/readme-not-first/src/prefix-1.md new file mode 100644 index 0000000000..4ddb4143d8 --- /dev/null +++ b/tests/gui/books/readme-not-first/src/prefix-1.md @@ -0,0 +1 @@ +# Prefix 1 diff --git a/tests/gui/books/readme-not-first/src/prefix-2.md b/tests/gui/books/readme-not-first/src/prefix-2.md new file mode 100644 index 0000000000..11ff4958c7 --- /dev/null +++ b/tests/gui/books/readme-not-first/src/prefix-2.md @@ -0,0 +1 @@ +# Prefix 2 diff --git a/tests/gui/sidebar-readme-not-first.goml b/tests/gui/sidebar-readme-not-first.goml new file mode 100644 index 0000000000..37180a1218 --- /dev/null +++ b/tests/gui/sidebar-readme-not-first.goml @@ -0,0 +1,6 @@ +// README.md not listed first should not double-highlight the sidebar on index.html. + +go-to: |DOC_PATH| + "readme-not-first/index.html" + +assert-count: (".sidebar-scrollbox a.active", 1) +assert-text: (".sidebar-scrollbox a.active", "Prefix 1")