@@ -535,8 +535,8 @@ For more information, visit: https://github.com/ksylvan/markdown-tree-parser
535535 for ( const section of sections ) {
536536 const headingText = section . headingText ;
537537
538- // Convert the heading to level 1 and preserve all original content
539- const sectionLines = [ `# ${ headingText } ` , ...section . lines ] ;
538+ // Keep the heading at level 2 for proper document structure
539+ const sectionLines = [ `## ${ headingText } ` , ...section . lines ] ;
540540 const sectionContent = sectionLines . join ( '\n' ) ;
541541
542542 // Generate filename without numbered prefix
@@ -552,8 +552,12 @@ For more information, visit: https://github.com/ksylvan/markdown-tree-parser
552552 console . log ( `✅ ${ headingText } → ${ filename } ` ) ;
553553 }
554554
555- // Generate index.md with original title and TOC pointing to files
556- const indexContent = await this . generateIndexContentTextBased ( content , sectionFiles ) ;
555+ // Parse content with AST to generate rich TOC with all subsections
556+ const tree = await this . parser . parse ( content ) ;
557+ const indexContent = await this . generateIndexContentWithSubsections (
558+ tree ,
559+ sectionFiles
560+ ) ;
557561 const indexPath = path . join ( outputDir , 'index.md' ) ;
558562 await this . writeFile ( indexPath , indexContent ) ;
559563 console . log ( `✅ Table of Contents → index.md` ) ;
@@ -564,10 +568,90 @@ For more information, visit: https://github.com/ksylvan/markdown-tree-parser
564568 }
565569
566570 async generateIndexContent ( tree , sectionFiles ) {
567- // Use the text-based approach for consistency
568- // Convert the tree back to text to get the original content
569- const originalContent = await this . parser . stringify ( tree ) ;
570- return await this . generateIndexContentTextBased ( originalContent , sectionFiles ) ;
571+ // Use the enhanced AST-based approach to include all subsections
572+ return await this . generateIndexContentWithSubsections ( tree , sectionFiles ) ;
573+ }
574+
575+ // Enhanced index generation with all subsections using AST
576+ async generateIndexContentWithSubsections ( tree , sectionFiles ) {
577+ const headings = this . parser . getHeadingsList ( tree ) ;
578+ const mainTitle = headings . find ( ( h ) => h . level === 1 ) ;
579+
580+ if ( ! mainTitle ) {
581+ return await this . generateIndexContentTextBased (
582+ await this . parser . stringify ( tree ) ,
583+ sectionFiles
584+ ) ;
585+ }
586+
587+ // Create a map of section names to filenames for quick lookup
588+ const sectionMap = new Map ( ) ;
589+ sectionFiles . forEach ( ( file ) => {
590+ sectionMap . set ( file . headingText . toLowerCase ( ) , file . filename ) ;
591+ } ) ;
592+
593+ // Start with title and TOC heading
594+ let toc = `# ${ mainTitle . text } \n\n## Table of Contents\n\n` ;
595+
596+ // Add the main title link
597+ toc += `- [${ mainTitle . text } ](#table-of-contents)\n` ;
598+
599+ // Process all headings to create nested TOC
600+ let currentLevel2Filename = null ;
601+
602+ for ( const heading of headings ) {
603+ // Skip the main title (level 1)
604+ if ( heading . level === 1 ) {
605+ continue ;
606+ }
607+
608+ if ( heading . level === 2 ) {
609+ // This is a main section
610+ currentLevel2Filename = sectionMap . get ( heading . text . toLowerCase ( ) ) ;
611+
612+ if ( currentLevel2Filename ) {
613+ toc += ` - [${ heading . text } ](./${ currentLevel2Filename } )\n` ;
614+ } else {
615+ toc += ` - [${ heading . text } ](#${ this . createAnchor ( heading . text ) } )\n` ;
616+ }
617+ } else if ( heading . level > 2 && currentLevel2Filename ) {
618+ // This is a subsection within a level 2 section
619+ const indent = ' ' . repeat ( heading . level - 2 ) ;
620+ const anchor = this . createAnchor ( heading . text ) ;
621+ toc += `${ indent } - [${ heading . text } ](./${ currentLevel2Filename } #${ anchor } )\n` ;
622+ }
623+ }
624+
625+ return toc ;
626+ }
627+
628+ // Helper to create URL-friendly anchors
629+ createAnchor ( text ) {
630+ return text
631+ . toLowerCase ( )
632+ . replace ( / [ ^ a - z 0 - 9 \s - ] / g, '' )
633+ . replace ( / \s + / g, '-' )
634+ . replace ( / - + / g, '-' )
635+ . replace ( / ^ - | - $ / g, '' ) ;
636+ }
637+
638+ // Method to decrement ALL heading levels in text (shift all headings down one level)
639+ decrementAllHeadingLevelsInText ( content ) {
640+ const lines = content . split ( '\n' ) ;
641+
642+ const adjustedLines = lines . map ( ( line ) => {
643+ // Check if line is a heading (starts with #)
644+ const headingMatch = line . match ( / ^ ( # { 1 , 5 } ) ( \s + .* ) $ / ) ;
645+ if ( headingMatch ) {
646+ const [ , hashes , rest ] = headingMatch ;
647+ // Add one more # to decrease the level (level 1 becomes level 2, etc.)
648+ // Only do this for levels 1-5 to avoid going beyond level 6
649+ return '#' + hashes + rest ;
650+ }
651+ return line ;
652+ } ) ;
653+
654+ return adjustedLines . join ( '\n' ) ;
571655 }
572656
573657 // Generate index content preserving original spacing
@@ -714,14 +798,10 @@ For more information, visit: https://github.com/ksylvan/markdown-tree-parser
714798 try {
715799 const sectionContent = await this . readFile ( filePath ) ;
716800
717- // Work directly with text to preserve formatting
718- const adjustedContent =
719- this . incrementHeadingLevelsInText ( sectionContent ) ;
720-
721- // Add the section content:
801+ // Add the section content directly - exploded files already have correct heading levels
722802 // - After main title: blank line then content (original has blank line after title)
723803 // - Between sections: direct concatenation (original has no spacing between sections)
724- assembledContent += '\n' + adjustedContent ;
804+ assembledContent += '\n' + sectionContent ;
725805 } catch {
726806 console . error (
727807 `⚠️ Warning: Could not read ${ sectionFile . filename } , skipping...`
@@ -732,25 +812,21 @@ For more information, visit: https://github.com/ksylvan/markdown-tree-parser
732812 // Write the assembled document
733813 await this . writeFile ( outputFile , assembledContent ) ;
734814 console . log ( `\n✨ Document assembled to ${ outputFile } ` ) ;
735- } // New method to increment heading levels directly in text without AST roundtrip
815+ } // Method to increment ALL heading levels directly in text (shift all headings up one level)
736816 incrementHeadingLevelsInText ( content ) {
737817 const lines = content . split ( '\n' ) ;
738- let isFirstHeading = true ;
739818
740819 const adjustedLines = lines . map ( ( line ) => {
741820 // Check if line is a heading (starts with #)
742821 const headingMatch = line . match ( / ^ ( # { 1 , 6 } ) ( \s + .* ) $ / ) ;
743822 if ( headingMatch ) {
744823 const [ , hashes , rest ] = headingMatch ;
745-
746- // Only increment the first heading (the main section heading)
747- // This converts the level 1 section heading back to level 2
748- if ( isFirstHeading && hashes === '#' ) {
749- isFirstHeading = false ;
750- return '##' + rest ;
824+ // Remove one # to increase the level (level 2 becomes level 1, etc.)
825+ // Only do this for levels 2-6 to avoid going below level 1
826+ if ( hashes . length > 1 ) {
827+ return hashes . slice ( 1 ) + rest ;
751828 }
752-
753- // All other headings remain at their current level
829+ // If it's already level 1, keep it at level 1 (can't go higher)
754830 return line ;
755831 }
756832 return line ;
0 commit comments