diff --git a/crates/mdbook-html/front-end/templates/toc.js.hbs b/crates/mdbook-html/front-end/templates/toc.js.hbs
index 1a9f751ccf..ae61a45d64 100644
--- a/crates/mdbook-html/front-end/templates/toc.js.hbs
+++ b/crates/mdbook-html/front-end/templates/toc.js.hbs
@@ -335,10 +335,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 +432,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/crates/mdbook-html/src/html/tree.rs b/crates/mdbook-html/src/html/tree.rs
index 5cb97ce378..a085b4f81b 100644
--- a/crates/mdbook-html/src/html/tree.rs
+++ b/crates/mdbook-html/src/html/tree.rs
@@ -932,15 +932,26 @@ where
el.insert_attr("id", id.into());
href
};
- // Insert an element between the heading and its children.
let mut a = Element::new("a");
a.insert_attr("class", "header".into());
a.insert_attr("href", href.into());
- let mut a = self.tree.orphan(Node::Element(a));
- a.reparent_from_id_append(heading);
- let a_id = a.id();
- let mut node = self.tree.get_mut(heading).unwrap();
- node.append_id(a_id);
+
+ let heading_node = self.tree.get(heading).unwrap();
+ if heading_contains_nested_anchor(heading_node) {
+ // Prepend an empty header link so headings may contain other
+ // links without nesting `` elements (issue #221).
+ self.tree
+ .get_mut(heading)
+ .unwrap()
+ .prepend(Node::Element(a));
+ } else {
+ // Insert an `` element between the heading and its children.
+ let mut a = self.tree.orphan(Node::Element(a));
+ a.reparent_from_id_append(heading);
+ let a_id = a.id();
+ let mut node = self.tree.get_mut(heading).unwrap();
+ node.append_id(a_id);
+ }
}
}
@@ -1074,6 +1085,27 @@ where
/// Traverse the given node, emitting any plain text into the output.
///
/// This is used to generate the `id` of a header.
+/// Returns true if the heading contains an `` element from the markdown source.
+fn heading_contains_nested_anchor(node: NodeRef<'_, Node>) -> bool {
+ for child in node.children() {
+ match child.value() {
+ Node::Element(el) if el.name() == "a" => return true,
+ Node::Element(_) => {
+ if heading_contains_nested_anchor(child) {
+ return true;
+ }
+ }
+ Node::Fragment => {
+ if heading_contains_nested_anchor(child) {
+ return true;
+ }
+ }
+ Node::Text(_) | Node::Comment(_) | Node::RawData(_) => {}
+ }
+ }
+ false
+}
+
fn text_in_node(node: NodeRef<'_, Node>, output: &mut String) {
for child in node.children() {
match child.value() {
diff --git a/tests/testsuite/markdown/definition_lists/expected/definition_lists.html b/tests/testsuite/markdown/definition_lists/expected/definition_lists.html
index c647ccb4dd..bcc03a40b4 100644
--- a/tests/testsuite/markdown/definition_lists/expected/definition_lists.html
+++ b/tests/testsuite/markdown/definition_lists/expected/definition_lists.html
@@ -22,7 +22,7 @@ Definition
\ No newline at end of file
+Term with link
+Multi-line term
diff --git a/tests/testsuite/rendering/header_links/expected/header_links.html b/tests/testsuite/rendering/header_links/expected/header_links.html
index 29c35a530d..101ed49951 100644
--- a/tests/testsuite/rendering/header_links/expected/header_links.html
+++ b/tests/testsuite/rendering/header_links/expected/header_links.html
@@ -1,4 +1,5 @@
Header Links
+Header with link.
Foo^bar
@@ -7,4 +8,4 @@
Repeat
Repeat
Repeat
Repeat 1
- With emphasis bold bold_emphasis
code <escaped> html link https://example.com/
code <escaped> html link https://example.com/