1- import { unified } from " unified" ;
2- import remarkParse from " remark-parse" ;
3- import remarkStringify from " remark-stringify" ;
4- import { visit } from " unist-util-visit" ;
5- import { selectAll , select } from " unist-util-select" ;
6- import { find } from " unist-util-find" ;
1+ import { unified } from ' unified' ;
2+ import remarkParse from ' remark-parse' ;
3+ import remarkStringify from ' remark-stringify' ;
4+ import { visit } from ' unist-util-visit' ;
5+ import { selectAll , select } from ' unist-util-select' ;
6+ import { find } from ' unist-util-find' ;
77
88/**
99 * A powerful markdown parser that treats markdown as a manipulable tree structure
@@ -13,9 +13,9 @@ export class MarkdownTreeParser {
1313 constructor ( options = { } ) {
1414 this . options = {
1515 // Default remark-stringify options
16- bullet : "*" ,
17- emphasis : "*" ,
18- strong : "*" ,
16+ bullet : '*' ,
17+ emphasis : '*' ,
18+ strong : '*' ,
1919 ...options ,
2020 } ;
2121
@@ -63,23 +63,26 @@ export class MarkdownTreeParser {
6363 let exactMatchIndex = - 1 ;
6464
6565 // Find the target heading - prefer exact matches over partial matches
66- visit ( tree , "heading" , ( node , index , _parent ) => {
67- const nodeText = this . getHeadingText ( node ) ;
68- const lowerNodeText = nodeText . toLowerCase ( ) ;
69- const lowerSearchText = headingText . toLowerCase ( ) ;
70-
71- if ( level === null || node . depth === level ) {
72- // Check for exact match first
73- if ( lowerNodeText === lowerSearchText ) {
74- exactMatch = node ;
75- exactMatchIndex = index ;
76- } else if ( lowerNodeText . includes ( lowerSearchText ) && ! foundHeading ) {
77- // Only use partial match if no exact match found yet and no other partial match
78- foundHeading = node ;
79- startIndex = index ;
66+ for ( let i = 0 ; i < tree . children . length ; i ++ ) {
67+ const node = tree . children [ i ] ;
68+ if ( node . type === 'heading' ) {
69+ const nodeText = this . getHeadingText ( node ) ;
70+ const lowerNodeText = nodeText . toLowerCase ( ) ;
71+ const lowerSearchText = headingText . toLowerCase ( ) ;
72+
73+ if ( level === null || node . depth === level ) {
74+ // Check for exact match first
75+ if ( lowerNodeText === lowerSearchText ) {
76+ exactMatch = node ;
77+ exactMatchIndex = i ;
78+ } else if ( lowerNodeText . includes ( lowerSearchText ) && ! foundHeading ) {
79+ // Only use partial match if no exact match found yet and no other partial match
80+ foundHeading = node ;
81+ startIndex = i ;
82+ }
8083 }
8184 }
82- } ) ;
85+ }
8386
8487 // Prefer exact match over partial match
8588 if ( exactMatch ) {
@@ -93,16 +96,13 @@ export class MarkdownTreeParser {
9396
9497 // Find where this section ends (next heading of same or higher level)
9598 const targetDepth = foundHeading . depth ;
96- visit ( tree , ( node , index ) => {
97- if (
98- index > startIndex &&
99- node . type === "heading" &&
100- node . depth <= targetDepth
101- ) {
102- endIndex = index ;
103- return "skip" ;
99+ for ( let i = startIndex + 1 ; i < tree . children . length ; i ++ ) {
100+ const node = tree . children [ i ] ;
101+ if ( node . type === 'heading' && node . depth <= targetDepth ) {
102+ endIndex = i ;
103+ break ;
104104 }
105- } ) ;
105+ }
106106
107107 // Create a new tree with just this section
108108 const sectionNodes = tree . children . slice (
@@ -114,7 +114,7 @@ export class MarkdownTreeParser {
114114 const copiedNodes = JSON . parse ( JSON . stringify ( sectionNodes ) ) ;
115115
116116 return {
117- type : " root" ,
117+ type : ' root' ,
118118 children : copiedNodes ,
119119 } ;
120120 }
@@ -132,7 +132,7 @@ export class MarkdownTreeParser {
132132 for ( let i = 0 ; i < tree . children . length ; i ++ ) {
133133 const node = tree . children [ i ] ;
134134
135- if ( node . type === " heading" && node . depth === level ) {
135+ if ( node . type === ' heading' && node . depth === level ) {
136136 // If we have a previous section, save it
137137 if ( startIndex !== - 1 ) {
138138 const sectionNodes = tree . children . slice ( startIndex , i ) ;
@@ -141,7 +141,7 @@ export class MarkdownTreeParser {
141141 sections . push ( {
142142 heading : JSON . parse ( JSON . stringify ( tree . children [ startIndex ] ) ) ,
143143 tree : {
144- type : " root" ,
144+ type : ' root' ,
145145 children : copiedNodes ,
146146 } ,
147147 headingText : this . getHeadingText ( tree . children [ startIndex ] ) ,
@@ -151,7 +151,7 @@ export class MarkdownTreeParser {
151151 // Start new section
152152 startIndex = i ;
153153 } else if (
154- node . type === " heading" &&
154+ node . type === ' heading' &&
155155 node . depth <= level &&
156156 startIndex !== - 1
157157 ) {
@@ -162,7 +162,7 @@ export class MarkdownTreeParser {
162162 sections . push ( {
163163 heading : JSON . parse ( JSON . stringify ( tree . children [ startIndex ] ) ) ,
164164 tree : {
165- type : " root" ,
165+ type : ' root' ,
166166 children : copiedNodes ,
167167 } ,
168168 headingText : this . getHeadingText ( tree . children [ startIndex ] ) ,
@@ -179,7 +179,7 @@ export class MarkdownTreeParser {
179179 sections . push ( {
180180 heading : JSON . parse ( JSON . stringify ( tree . children [ startIndex ] ) ) ,
181181 tree : {
182- type : " root" ,
182+ type : ' root' ,
183183 children : copiedNodes ,
184184 } ,
185185 headingText : this . getHeadingText ( tree . children [ startIndex ] ) ,
@@ -216,11 +216,11 @@ export class MarkdownTreeParser {
216216 * @returns {Object|null } First matching node or null
217217 */
218218 findNode ( tree , condition ) {
219- if ( typeof condition === " string" ) {
219+ if ( typeof condition === ' string' ) {
220220 return find ( tree , condition ) ;
221- } else if ( typeof condition === " function" ) {
221+ } else if ( typeof condition === ' function' ) {
222222 return find ( tree , condition ) ;
223- } else if ( typeof condition === " object" ) {
223+ } else if ( typeof condition === ' object' ) {
224224 return find ( tree , condition ) ;
225225 }
226226 return null ;
@@ -232,8 +232,8 @@ export class MarkdownTreeParser {
232232 * @returns {string } Plain text content of the heading
233233 */
234234 getHeadingText ( headingNode ) {
235- let text = "" ;
236- visit ( headingNode , " text" , ( node ) => {
235+ let text = '' ;
236+ visit ( headingNode , ' text' , ( node ) => {
237237 text += node . value ;
238238 } ) ;
239239 return text ;
@@ -257,7 +257,7 @@ export class MarkdownTreeParser {
257257 */
258258 getHeadingsList ( tree ) {
259259 const headings = [ ] ;
260- visit ( tree , " heading" , ( node ) => {
260+ visit ( tree , ' heading' , ( node ) => {
261261 headings . push ( {
262262 level : node . depth ,
263263 text : this . getHeadingText ( node ) ,
@@ -280,7 +280,7 @@ export class MarkdownTreeParser {
280280 const sectionNodes = [ ] ;
281281
282282 visit ( tree , ( node , _index ) => {
283- if ( node . type === " heading" ) {
283+ if ( node . type === ' heading' ) {
284284 if (
285285 ! collecting &&
286286 this . getHeadingText ( node )
@@ -293,7 +293,7 @@ export class MarkdownTreeParser {
293293 } else if ( collecting ) {
294294 // Stop collecting if we hit a heading of same or higher level
295295 if ( node . depth <= foundHeading . depth ) {
296- return " skip" ;
296+ return ' skip' ;
297297 }
298298 // Stop if we've exceeded max depth
299299 if ( maxDepth && node . depth > foundHeading . depth + maxDepth ) {
@@ -314,7 +314,7 @@ export class MarkdownTreeParser {
314314 const copiedNodes = JSON . parse ( JSON . stringify ( sectionNodes ) ) ;
315315
316316 return {
317- type : " root" ,
317+ type : ' root' ,
318318 children : copiedNodes ,
319319 } ;
320320 }
@@ -337,27 +337,27 @@ export class MarkdownTreeParser {
337337
338338 visit ( tree , ( node ) => {
339339 switch ( node . type ) {
340- case " heading" :
340+ case ' heading' :
341341 stats . headings . total ++ ;
342342 stats . headings . byLevel [ node . depth ] =
343343 ( stats . headings . byLevel [ node . depth ] || 0 ) + 1 ;
344344 break ;
345- case " paragraph" :
345+ case ' paragraph' :
346346 stats . paragraphs ++ ;
347347 break ;
348- case " code" :
348+ case ' code' :
349349 stats . codeBlocks ++ ;
350350 break ;
351- case " list" :
351+ case ' list' :
352352 stats . lists ++ ;
353353 break ;
354- case " link" :
354+ case ' link' :
355355 stats . links ++ ;
356356 break ;
357- case " image" :
357+ case ' image' :
358358 stats . images ++ ;
359359 break ;
360- case " text" :
360+ case ' text' :
361361 stats . wordCount += node . value
362362 . trim ( )
363363 . split ( / \s + / )
@@ -380,19 +380,19 @@ export class MarkdownTreeParser {
380380 const filteredHeadings = headings . filter ( ( h ) => h . level <= maxLevel ) ;
381381
382382 if ( filteredHeadings . length === 0 ) {
383- return "" ;
383+ return '' ;
384384 }
385385
386- let toc = " ## Table of Contents\n\n" ;
386+ let toc = ' ## Table of Contents\n\n' ;
387387
388388 filteredHeadings . forEach ( ( heading ) => {
389- const indent = " " . repeat ( heading . level - 1 ) ;
389+ const indent = ' ' . repeat ( heading . level - 1 ) ;
390390 const link = heading . text
391391 . toLowerCase ( )
392- . replace ( / [ ^ a - z 0 - 9 \s - ] / g, "" )
393- . replace ( / \s + / g, "-" )
394- . replace ( / - + / g, "-" )
395- . replace ( / ^ - | - $ / g, "" ) ;
392+ . replace ( / [ ^ a - z 0 - 9 \s - ] / g, '' )
393+ . replace ( / \s + / g, '-' )
394+ . replace ( / - + / g, '-' )
395+ . replace ( / ^ - | - $ / g, '' ) ;
396396
397397 toc += `${ indent } - [${ heading . text } ](#${ link } )\n` ;
398398 } ) ;
0 commit comments