diff --git a/crates/mdbook-html/front-end/templates/toc.js.hbs b/crates/mdbook-html/front-end/templates/toc.js.hbs index d5be5b1189..8add441acb 100644 --- a/crates/mdbook-html/front-end/templates/toc.js.hbs +++ b/crates/mdbook-html/front-end/templates/toc.js.hbs @@ -336,10 +336,17 @@ window.customElements.define('mdbook-sidebar-scrollbox', MDBookSidebarScrollbox) }); } - // Takes the nodes from the given head and copies them over to the - // destination, along with some filtering. + // Takes the rendered content of the given heading and copies it over to + // the destination, along with some filtering. function filterHeader(source, dest) { const clone = source.cloneNode(true); + // Unwrap the injected permalink anchor (``), keeping + // its children. When the heading itself is a link, the browser hoists + // that link out as a sibling of the (now empty) permalink anchor, so + // unwrapping here lets the actual heading text come through. + clone.querySelectorAll('a.header').forEach(headerLink => { + headerLink.replaceWith(...headerLink.childNodes); + }); clone.querySelectorAll('mark').forEach(mark => { mark.replaceWith(...mark.childNodes); }); @@ -418,7 +425,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(header, a); a.addEventListener('click', headerThresholdClick); const nextHeader = headers[i + 1]; if (nextHeader !== undefined) { diff --git a/tests/gui/books/heading-nav/src/markup.md b/tests/gui/books/heading-nav/src/markup.md index 6503c0c474..fa1ba8aefe 100644 --- a/tests/gui/books/heading-nav/src/markup.md +++ b/tests/gui/books/heading-nav/src/markup.md @@ -10,6 +10,10 @@ Basic markup should be copied. Probably not super-wise to have headings with links, but at least they shouldn't explode. +## [link-only](../index.html) + +A heading whose entire content is a link should still show its text in the sidebar. + ## Heading with a custom id { #custom-id .custom-class } Make sure navigation works on a custom id. diff --git a/tests/gui/heading-nav-markup.goml b/tests/gui/heading-nav-markup.goml index 6da8f936aa..ce10ac9ca5 100644 --- a/tests/gui/heading-nav-markup.goml +++ b/tests/gui/heading-nav-markup.goml @@ -3,11 +3,18 @@ set-window-size: (1400, 800) go-to: |DOC_PATH| + "heading-nav/markup.html" -assert-count: (".header-item", 4) +assert-count: (".header-item", 5) assert-count: (".current-header", 1) assert-text: (".current-header", "Heading with code or italic or bold or strike") assert-property: (".current-header", {"innerHTML": "Heading with code or italic or bold or strike"}) +// A heading that contains a link should keep its text (and the link) in the sidebar. +assert-text: ("a.header-in-summary[href='#heading-with-a-link']", "Heading with a link") +assert-property: ("a.header-in-summary[href='#heading-with-a-link']", {"innerHTML": "Heading with a link"}) + +// A heading whose entire content is a link should still show its text. +assert-text: ("a.header-in-summary[href='#link-only']", "link-only") + // Clicking the custom one should work and should make it current. click: "a.header-in-summary[href='#custom-id']" assert-count: (".current-header", 1)