Skip to content

Commit d8b6177

Browse files
committed
👌 Improve AST for heading anchors
Move inner html to a HtmlInline node
1 parent dd50e31 commit d8b6177

File tree

1 file changed

+19
-8
lines changed
  • crates/heading_anchors/src

1 file changed

+19
-8
lines changed

crates/heading_anchors/src/lib.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,30 +26,37 @@
2626
use github_slugger::Slugger;
2727
use markdown_it::{
2828
parser::{core::CoreRule, extset::MarkdownItExt, inline::builtin::InlineParserRule},
29-
plugins::cmark::block::{heading::ATXHeading, lheading::SetextHeader},
29+
plugins::{
30+
cmark::block::{heading::ATXHeading, lheading::SetextHeader},
31+
html::html_inline::HtmlInline,
32+
},
3033
MarkdownIt, Node, NodeValue,
3134
};
3235

36+
/// Add the heading anchor plugin to MarkdownIt.
3337
pub fn add(md: &mut MarkdownIt) {
3438
md.ext.get_or_insert_default::<HeadingAnchorOptions>();
3539
md.add_rule::<AddHeadingAnchors>()
3640
.after::<InlineParserRule>();
3741
}
3842

43+
/// Add the heading anchor plugin to MarkdownIt, with options.
3944
pub fn add_with_options(md: &mut MarkdownIt, options: HeadingAnchorOptions) {
4045
md.ext.insert(options);
4146
md.add_rule::<AddHeadingAnchors>()
4247
.after::<InlineParserRule>();
4348
}
4449

4550
#[derive(Debug)]
51+
/// Where to add the anchor, within the heading children.
4652
pub enum AnchorPosition {
4753
Start,
4854
End,
4955
None,
5056
}
5157

5258
#[derive(Debug)]
59+
/// Options for the heading anchor plugin.
5360
pub struct HeadingAnchorOptions {
5461
/// Minimum heading level to add anchors to.
5562
pub min_level: u8,
@@ -85,20 +92,20 @@ impl Default for HeadingAnchorOptions {
8592
impl MarkdownItExt for HeadingAnchorOptions {}
8693

8794
#[derive(Debug)]
88-
pub struct Permalink {
95+
/// AST node for a heading anchor
96+
pub struct HeadingAnchor {
8997
pub href: String,
9098
pub id: Option<String>,
91-
pub inner_html: String,
9299
}
93-
impl NodeValue for Permalink {
100+
impl NodeValue for HeadingAnchor {
94101
fn render(&self, node: &Node, fmt: &mut dyn markdown_it::Renderer) {
95102
let mut attrs = node.attrs.clone();
96103
if let Some(id) = &self.id {
97104
attrs.push(("id", id.clone()));
98105
}
99106
attrs.push(("href", format!("#{}", self.href)));
100107
fmt.open("a", &attrs);
101-
fmt.text_raw(&self.inner_html);
108+
fmt.contents(&node.children);
102109
fmt.close("a");
103110
}
104111
}
@@ -109,6 +116,8 @@ impl CoreRule for AddHeadingAnchors {
109116
let options = md.ext.get::<HeadingAnchorOptions>().unwrap();
110117
let mut slugger = Slugger::default();
111118
root.walk_mut(|node, _| {
119+
// TODO should be able to halt recursion for paragraphs etc,
120+
// that cannot contain headings
112121
if let Some(value) = node.cast::<ATXHeading>() {
113122
if value.level < options.min_level || value.level > options.max_level {
114123
return;
@@ -125,7 +134,7 @@ impl CoreRule for AddHeadingAnchors {
125134
if options.id_on_heading {
126135
node.attrs.push(("id", id.clone()));
127136
}
128-
let permalink = Permalink {
137+
let anchor = HeadingAnchor {
129138
href: id.clone(),
130139
id: {
131140
if options.id_on_heading {
@@ -134,10 +143,12 @@ impl CoreRule for AddHeadingAnchors {
134143
Some(id)
135144
}
136145
},
137-
inner_html: options.inner_html.clone(),
138146
};
139-
let mut link_node = Node::new(permalink);
147+
let mut link_node = Node::new(anchor);
140148
link_node.attrs.push(("aria-hidden", String::from("true")));
149+
link_node.children.push(Node::new(HtmlInline {
150+
content: options.inner_html.clone(),
151+
}));
141152
for class in &options.classes {
142153
link_node.attrs.push(("class", class.clone()));
143154
}

0 commit comments

Comments
 (0)