@@ -2,11 +2,13 @@ use libs::gh_emoji::Replacer as EmojiReplacer;
2
2
use libs:: once_cell:: sync:: Lazy ;
3
3
use libs:: pulldown_cmark as cmark;
4
4
use libs:: tera;
5
+ use std:: fmt:: Write ;
5
6
6
7
use crate :: context:: RenderContext ;
7
8
use crate :: table_of_contents:: { make_table_of_contents, Heading } ;
8
9
use errors:: { Error , Result } ;
9
10
use front_matter:: InsertAnchor ;
11
+ use libs:: pulldown_cmark:: escape:: escape_html;
10
12
use utils:: site:: resolve_internal_link;
11
13
use utils:: slugs:: slugify_anchors;
12
14
use utils:: vec:: InsertMany ;
@@ -37,11 +39,39 @@ struct HeadingRef {
37
39
end_idx : usize ,
38
40
level : u32 ,
39
41
id : Option < String > ,
42
+ classes : Vec < String > ,
40
43
}
41
44
42
45
impl HeadingRef {
43
- fn new ( start : usize , level : u32 ) -> HeadingRef {
44
- HeadingRef { start_idx : start, end_idx : 0 , level, id : None }
46
+ fn new ( start : usize , level : u32 , anchor : Option < String > , classes : & [ String ] ) -> HeadingRef {
47
+ HeadingRef { start_idx : start, end_idx : 0 , level, id : anchor, classes : classes. to_vec ( ) }
48
+ }
49
+
50
+ fn to_html ( & self , id : & str ) -> String {
51
+ let mut buffer = String :: with_capacity ( 100 ) ;
52
+ buffer. write_str ( "<h" ) . unwrap ( ) ;
53
+ buffer. write_str ( & format ! ( "{}" , self . level) ) . unwrap ( ) ;
54
+
55
+ buffer. write_str ( " id=\" " ) . unwrap ( ) ;
56
+ escape_html ( & mut buffer, id) . unwrap ( ) ;
57
+ buffer. write_str ( "\" " ) . unwrap ( ) ;
58
+
59
+ if !self . classes . is_empty ( ) {
60
+ buffer. write_str ( " class=\" " ) . unwrap ( ) ;
61
+ let num_classes = self . classes . len ( ) ;
62
+
63
+ for ( i, class) in self . classes . iter ( ) . enumerate ( ) {
64
+ escape_html ( & mut buffer, class) . unwrap ( ) ;
65
+ if i < num_classes - 1 {
66
+ buffer. write_str ( " " ) . unwrap ( ) ;
67
+ }
68
+ }
69
+
70
+ buffer. write_str ( "\" " ) . unwrap ( ) ;
71
+ }
72
+
73
+ buffer. write_str ( ">" ) . unwrap ( ) ;
74
+ buffer
45
75
}
46
76
}
47
77
@@ -131,10 +161,15 @@ fn get_heading_refs(events: &[Event]) -> Vec<HeadingRef> {
131
161
132
162
for ( i, event) in events. iter ( ) . enumerate ( ) {
133
163
match event {
134
- Event :: Start ( Tag :: Heading ( level) ) => {
135
- heading_refs. push ( HeadingRef :: new ( i, * level) ) ;
164
+ Event :: Start ( Tag :: Heading ( level, anchor, classes) ) => {
165
+ heading_refs. push ( HeadingRef :: new (
166
+ i,
167
+ * level as u32 ,
168
+ anchor. map ( |a| a. to_owned ( ) ) ,
169
+ & classes. iter ( ) . map ( |x| x. to_string ( ) ) . collect :: < Vec < _ > > ( ) ,
170
+ ) ) ;
136
171
}
137
- Event :: End ( Tag :: Heading ( _) ) => {
172
+ Event :: End ( Tag :: Heading ( _, _ , _ ) ) => {
138
173
heading_refs. last_mut ( ) . expect ( "Heading end before start?" ) . end_idx = i;
139
174
}
140
175
_ => ( ) ,
@@ -161,7 +196,6 @@ pub fn markdown_to_html(
161
196
162
197
let mut code_block: Option < CodeBlock > = None ;
163
198
164
- let mut inserted_anchors: Vec < String > = vec ! [ ] ;
165
199
let mut headings: Vec < Heading > = vec ! [ ] ;
166
200
let mut internal_links = Vec :: new ( ) ;
167
201
let mut external_links = Vec :: new ( ) ;
@@ -174,6 +208,7 @@ pub fn markdown_to_html(
174
208
opts. insert ( Options :: ENABLE_FOOTNOTES ) ;
175
209
opts. insert ( Options :: ENABLE_STRIKETHROUGH ) ;
176
210
opts. insert ( Options :: ENABLE_TASKLISTS ) ;
211
+ opts. insert ( Options :: ENABLE_HEADING_ATTRIBUTES ) ;
177
212
178
213
if context. config . markdown . smart_punctuation {
179
214
opts. insert ( Options :: ENABLE_SMART_PUNCTUATION ) ;
@@ -389,45 +424,34 @@ pub fn markdown_to_html(
389
424
} )
390
425
. collect ( ) ;
391
426
392
- let mut heading_refs = get_heading_refs ( & events) ;
427
+ let heading_refs = get_heading_refs ( & events) ;
393
428
394
429
let mut anchors_to_insert = vec ! [ ] ;
395
-
396
- // First heading pass: look for a manually-specified IDs, e.g. `# Heading text {#hash}`
397
- // (This is a separate first pass so that auto IDs can avoid collisions with manual IDs.)
398
- for heading_ref in heading_refs. iter_mut ( ) {
399
- let end_idx = heading_ref. end_idx ;
400
- if let Event :: Text ( ref mut text) = events[ end_idx - 1 ] {
401
- if text. as_bytes ( ) . last ( ) == Some ( & b'}' ) {
402
- if let Some ( mut i) = text. find ( "{#" ) {
403
- let id = text[ i + 2 ..text. len ( ) - 1 ] . to_owned ( ) ;
404
- inserted_anchors. push ( id. clone ( ) ) ;
405
- while i > 0 && text. as_bytes ( ) [ i - 1 ] == b' ' {
406
- i -= 1 ;
407
- }
408
- heading_ref. id = Some ( id) ;
409
- * text = text[ ..i] . to_owned ( ) . into ( ) ;
410
- }
411
- }
430
+ let mut inserted_anchors = vec ! [ ] ;
431
+ for heading in & heading_refs {
432
+ if let Some ( s) = & heading. id {
433
+ inserted_anchors. push ( s. to_owned ( ) ) ;
412
434
}
413
435
}
414
436
415
437
// Second heading pass: auto-generate remaining IDs, and emit HTML
416
- for heading_ref in heading_refs {
438
+ for mut heading_ref in heading_refs {
417
439
let start_idx = heading_ref. start_idx ;
418
440
let end_idx = heading_ref. end_idx ;
419
441
let title = get_text ( & events[ start_idx + 1 ..end_idx] ) ;
420
- let id = heading_ref. id . unwrap_or_else ( || {
421
- find_anchor (
442
+
443
+ if heading_ref. id . is_none ( ) {
444
+ heading_ref. id = Some ( find_anchor (
422
445
& inserted_anchors,
423
446
slugify_anchors ( & title, context. config . slugify . anchors ) ,
424
447
0 ,
425
- )
426
- } ) ;
427
- inserted_anchors. push ( id. clone ( ) ) ;
448
+ ) ) ;
449
+ }
428
450
429
- // insert `id` to the tag
430
- let html = format ! ( "<h{lvl} id=\" {id}\" >" , lvl = heading_ref. level, id = id) ;
451
+ inserted_anchors. push ( heading_ref. id . clone ( ) . unwrap ( ) ) ;
452
+ let id = inserted_anchors. last ( ) . unwrap ( ) ;
453
+
454
+ let html = heading_ref. to_html ( & id) ;
431
455
events[ start_idx] = Event :: Html ( html. into ( ) ) ;
432
456
433
457
// generate anchors and places to insert them
@@ -454,8 +478,13 @@ pub fn markdown_to_html(
454
478
455
479
// record heading to make table of contents
456
480
let permalink = format ! ( "{}#{}" , context. current_page_permalink, id) ;
457
- let h =
458
- Heading { level : heading_ref. level , id, permalink, title, children : Vec :: new ( ) } ;
481
+ let h = Heading {
482
+ level : heading_ref. level ,
483
+ id : id. to_owned ( ) ,
484
+ permalink,
485
+ title,
486
+ children : Vec :: new ( ) ,
487
+ } ;
459
488
headings. push ( h) ;
460
489
}
461
490
0 commit comments