@@ -3,10 +3,7 @@ use crate::{templates::docs::TocLink, utils::config};
33use std:: cell:: RefCell ;
44use std:: collections:: { HashMap , HashSet } ;
55use std:: path:: { Path , PathBuf } ;
6- use std:: sync:: {
7- atomic:: { AtomicUsize , Ordering } ,
8- Arc ,
9- } ;
6+ use std:: sync:: Arc ;
107
118use anyhow:: Result ;
129use comrak:: {
@@ -15,25 +12,27 @@ use comrak::{
1512 nodes:: { Ast , AstNode , NodeValue } ,
1613 parse_document, Arena , ComrakExtensionOptions , ComrakOptions , ComrakRenderOptions ,
1714} ;
15+ use convert_case;
1816use itertools:: Itertools ;
1917use regex:: Regex ;
2018use tantivy:: collector:: TopDocs ;
2119use tantivy:: query:: { QueryParser , RegexQuery } ;
2220use tantivy:: schema:: * ;
2321use tantivy:: tokenizer:: { LowerCaser , NgramTokenizer , TextAnalyzer } ;
2422use tantivy:: { Index , IndexReader , SnippetGenerator } ;
25- use url:: Url ;
23+
24+ use std:: sync:: Mutex ;
2625
2726use std:: fmt;
2827
2928pub struct MarkdownHeadings {
30- counter : Arc < AtomicUsize > ,
29+ header_map : Arc < Mutex < HashMap < String , usize > > > ,
3130}
3231
3332impl Default for MarkdownHeadings {
3433 fn default ( ) -> Self {
3534 Self {
36- counter : Arc :: new ( AtomicUsize :: new ( 0 ) ) ,
35+ header_map : Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ,
3736 }
3837 }
3938}
@@ -44,31 +43,42 @@ impl MarkdownHeadings {
4443 }
4544}
4645
46+ /// Sets the document headers
47+ ///
48+ /// uses toclink to ensure header id matches what the TOC expects
49+ ///
4750impl HeadingAdapter for MarkdownHeadings {
4851 fn enter ( & self , meta : & HeadingMeta ) -> String {
49- // let id = meta.content.to_case(convert_case::Case::Kebab);
50- let id = self . counter . fetch_add ( 1 , Ordering :: SeqCst ) ;
51- let id = format ! ( "header-{}" , id) ;
52+ let conv = convert_case:: Converter :: new ( ) . to_case ( convert_case:: Case :: Kebab ) ;
53+ let id = conv. convert ( meta. content . to_string ( ) ) ;
54+
55+ let index = match self . header_map . lock ( ) . unwrap ( ) . get ( & id) {
56+ Some ( value) => value + 1 ,
57+ _ => 0 ,
58+ } ;
59+ self . header_map . lock ( ) . unwrap ( ) . insert ( id. clone ( ) , index) ;
60+
61+ let id = TocLink :: new ( & id, index) . id ;
5262
5363 match meta. level {
54- 1 => format ! ( r#"<h1 class="h1 mb-5" id="{id}">" # ) ,
55- 2 => format ! ( r#"<h2 class="h2 mb-4 mt-5" id="{id}">" # ) ,
56- 3 => format ! ( r#"<h3 class="h3 mb-4 mt-5" id="{id}">" # ) ,
57- 4 => format ! ( r#"<h4 class="h5 mb-3 mt-3" id="{id}">" # ) ,
58- 5 => format ! ( r#"<h5 class="h6 mb-2 mt-4" id="{id}">" # ) ,
59- 6 => format ! ( r#"<h6 class="h6 mb-1 mt-1" id="{id}">" # ) ,
64+ 1 => format ! ( r## "<h1 class="h1 mb-5" id="{id}"><a href="#{id}">"# # ) ,
65+ 2 => format ! ( r## "<h2 class="h2 mb-4 mt-5" id="{id}"><a href="#{id}">"# # ) ,
66+ 3 => format ! ( r## "<h3 class="h3 mb-4 mt-5" id="{id}"><a href="#{id}">"# # ) ,
67+ 4 => format ! ( r## "<h4 class="h5 mb-3 mt-3" id="{id}"><a href="#{id}">"# # ) ,
68+ 5 => format ! ( r## "<h5 class="h6 mb-2 mt-4" id="{id}"><a href="#{id}">"# # ) ,
69+ 6 => format ! ( r## "<h6 class="h6 mb-1 mt-1" id="{id}"><a href="#{id}">"# # ) ,
6070 _ => unreachable ! ( ) ,
6171 }
6272 }
6373
6474 fn exit ( & self , meta : & HeadingMeta ) -> String {
6575 match meta. level {
66- 1 => r#"</h1>"# ,
67- 2 => r#"</h2>"# ,
68- 3 => r#"</h3>"# ,
69- 4 => r#"</h4>"# ,
70- 5 => r#"</h5>"# ,
71- 6 => r#"</h6>"# ,
76+ 1 => r#"</a></ h1>"# ,
77+ 2 => r#"</a></ h2>"# ,
78+ 3 => r#"</a></ h3>"# ,
79+ 4 => r#"</a></ h4>"# ,
80+ 5 => r#"</a></ h5>"# ,
81+ 6 => r#"</a></ h6>"# ,
7282 _ => unreachable ! ( ) ,
7383 }
7484 . into ( )
@@ -335,38 +345,6 @@ where
335345 Ok ( ( ) )
336346}
337347
338- pub fn nest_relative_links ( node : & mut markdown:: mdast:: Node , path : & PathBuf ) {
339- let _ = iter_mut_all ( node, & mut |node| {
340- if let markdown:: mdast:: Node :: Link ( ref mut link) = node {
341- match Url :: parse ( & link. url ) {
342- Ok ( url) => {
343- if !url. has_host ( ) {
344- let mut url_path = url. path ( ) . to_string ( ) ;
345- let url_path_path = Path :: new ( & url_path) ;
346- match url_path_path. extension ( ) {
347- Some ( ext) => {
348- if ext. to_str ( ) == Some ( ".md" ) {
349- let base = url_path_path. with_extension ( "" ) ;
350- url_path = base. into_os_string ( ) . into_string ( ) . unwrap ( ) ;
351- }
352- }
353- _ => {
354- warn ! ( "not markdown path: {:?}" , path)
355- }
356- }
357- link. url = path. join ( url_path) . into_os_string ( ) . into_string ( ) . unwrap ( ) ;
358- }
359- }
360- Err ( e) => {
361- warn ! ( "could not parse url in markdown: {}" , e)
362- }
363- }
364- }
365-
366- Ok ( ( ) )
367- } ) ;
368- }
369-
370348/// Get the title of the article.
371349///
372350/// # Arguments
@@ -462,11 +440,10 @@ pub fn wrap_tables<'a>(root: &'a AstNode<'a>, arena: &'a Arena<AstNode<'a>>) ->
462440///
463441pub fn get_toc < ' a > ( root : & ' a AstNode < ' a > ) -> anyhow:: Result < Vec < TocLink > > {
464442 let mut links = Vec :: new ( ) ;
465- let mut header_counter = 0 ;
443+ let mut header_count : HashMap < String , usize > = HashMap :: new ( ) ;
466444
467445 iter_nodes ( root, & mut |node| {
468446 if let NodeValue :: Heading ( header) = & node. data . borrow ( ) . value {
469- header_counter += 1 ;
470447 if header. level != 1 {
471448 let sibling = match node. first_child ( ) {
472449 Some ( child) => child,
@@ -476,7 +453,14 @@ pub fn get_toc<'a>(root: &'a AstNode<'a>) -> anyhow::Result<Vec<TocLink>> {
476453 }
477454 } ;
478455 if let NodeValue :: Text ( text) = & sibling. data . borrow ( ) . value {
479- links. push ( TocLink :: new ( text, header_counter - 1 ) . level ( header. level ) ) ;
456+ let index = match header_count. get ( text) {
457+ Some ( index) => index + 1 ,
458+ _ => 0 ,
459+ } ;
460+
461+ header_count. insert ( text. clone ( ) , index) ;
462+
463+ links. push ( TocLink :: new ( text, index) . level ( header. level ) ) ;
480464 return Ok ( false ) ;
481465 }
482466 }
@@ -753,11 +737,25 @@ pub fn mkdocs<'a>(root: &'a AstNode<'a>, arena: &'a Arena<AstNode<'a>>) -> anyho
753737 let path = Path :: new ( link. url . as_str ( ) ) ;
754738
755739 if path. is_relative ( ) {
740+ let fragment = match link. url . find ( "#" ) {
741+ Some ( index) => link. url [ index + 1 ..link. url . len ( ) ] . to_string ( ) ,
742+ _ => "" . to_string ( ) ,
743+ } ;
744+
745+ for _ in 0 ..fragment. len ( ) + 1 {
746+ link. url . pop ( ) ;
747+ }
748+
756749 if link. url . ends_with ( ".md" ) {
757750 for _ in 0 ..".md" . len ( ) {
758751 link. url . pop ( ) ;
759752 }
760753 }
754+
755+ let header_id = TocLink :: from_fragment ( fragment) . id ;
756+ for c in header_id. chars ( ) {
757+ link. url . push ( c)
758+ }
761759 }
762760
763761 Ok ( true )
0 commit comments