2626use github_slugger:: Slugger ;
2727use 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.
3337pub 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.
3944pub 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.
4652pub enum AnchorPosition {
4753 Start ,
4854 End ,
4955 None ,
5056}
5157
5258#[ derive( Debug ) ]
59+ /// Options for the heading anchor plugin.
5360pub struct HeadingAnchorOptions {
5461 /// Minimum heading level to add anchors to.
5562 pub min_level : u8 ,
@@ -85,20 +92,20 @@ impl Default for HeadingAnchorOptions {
8592impl 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