11import { toString } from 'hast-util-to-string'
22import { visit } from 'unist-util-visit'
33
4- import { isHeading , normalizeComponentName } from './helpers'
4+ import { normalizeComponentName } from './helpers'
55
66type HastNode = {
77 type : string
8- tagName : string
8+ tagName ? : string
99 properties ?: Record < string , unknown >
1010 children ?: HastNode [ ]
11+ value ?: string
1112}
1213
1314type FrameworkCodeBlock = {
1415 title : string
1516 code : string
1617 language : string
17- preNode : HastNode
1818}
1919
2020type FrameworkExtraction = {
2121 codeBlocksByFramework : Record < string , FrameworkCodeBlock [ ] >
2222 contentByFramework : Record < string , HastNode [ ] >
2323}
2424
25- // Helper to extract text from nodes (used for code content)
26- function extractText ( nodes : any [ ] ) : string {
27- let text = ''
28- for ( const node of nodes ) {
29- if ( node . type === 'text' ) {
30- text += node . value
31- } else if ( node . type === 'element' && node . children ) {
32- text += extractText ( node . children )
33- }
34- }
35- return text
36- }
37-
3825/**
3926 * Extract code block data (language, title, code) from a <pre> element.
40- * Extracts title from data-code-title (set by rehypeCodeMeta).
4127 */
4228function extractCodeBlockData ( preNode : HastNode ) : {
4329 language : string
4430 title : string
4531 code : string
4632} | null {
47- // Find the <code> child
4833 const codeNode = preNode . children ?. find (
4934 ( c : HastNode ) => c . type === 'element' && c . tagName === 'code' ,
5035 )
@@ -61,6 +46,7 @@ function extractCodeBlockData(preNode: HastNode): {
6146 }
6247 }
6348
49+ // Extract title from data attributes
6450 let title = ''
6551 const props = preNode . properties || { }
6652 if ( typeof props [ 'dataCodeTitle' ] === 'string' ) {
@@ -73,57 +59,93 @@ function extractCodeBlockData(preNode: HastNode): {
7359 title = props [ 'data-filename' ]
7460 }
7561
76- // Extract code content
62+ // Extract code text
63+ const extractText = ( nodes : HastNode [ ] ) : string => {
64+ let text = ''
65+ for ( const node of nodes ) {
66+ if ( node . type === 'text' && node . value ) {
67+ text += node . value
68+ } else if ( node . type === 'element' && node . children ) {
69+ text += extractText ( node . children )
70+ }
71+ }
72+ return text
73+ }
7774 const code = extractText ( codeNode . children || [ ] )
7875
7976 return { language, title, code }
8077}
8178
82- /**
83- * Extract framework-specific content for framework component.
84- * Groups all content (code blocks and general content) by framework headings.
85- */
8679function extractFrameworkData ( node : HastNode ) : FrameworkExtraction | null {
8780 const children = node . children ?? [ ]
8881 const codeBlocksByFramework : Record < string , FrameworkCodeBlock [ ] > = { }
8982 const contentByFramework : Record < string , HastNode [ ] > = { }
9083
91- let currentFramework : string | null = null
84+ // First pass: find the first H1 to determine the first framework
85+ let firstFramework : string | null = null
86+ for ( const child of children ) {
87+ if ( child . type === 'element' && child . tagName === 'h1' ) {
88+ firstFramework = toString ( child as any ) . trim ( ) . toLowerCase ( )
89+ break
90+ }
91+ }
92+
93+ // If no H1 found at all, return null
94+ if ( ! firstFramework ) {
95+ return null
96+ }
97+
98+ // Second pass: collect content
99+ let currentFramework : string | null = firstFramework // Start with first framework for content before first H1
100+
101+ // Initialize the first framework
102+ contentByFramework [ firstFramework ] = [ ]
103+ codeBlocksByFramework [ firstFramework ] = [ ]
92104
93105 for ( const child of children ) {
94- if ( isHeading ( child ) ) {
106+ // Check if this is an H1 heading (framework divider)
107+ if ( child . type === 'element' && child . tagName === 'h1' ) {
108+ // Extract framework name from H1 text
95109 currentFramework = toString ( child as any )
96110 . trim ( )
97111 . toLowerCase ( )
112+
98113 // Initialize arrays for this framework
99114 if ( currentFramework && ! contentByFramework [ currentFramework ] ) {
100115 contentByFramework [ currentFramework ] = [ ]
101116 codeBlocksByFramework [ currentFramework ] = [ ]
102117 }
118+ // Don't include the H1 itself in content - it's just a divider
103119 continue
104120 }
105121
106- // Skip if no framework heading found yet
107122 if ( ! currentFramework ) continue
108123
109- // Add all content to contentByFramework
110- contentByFramework [ currentFramework ] . push ( child )
124+ // Create a shallow copy of the node
125+ const contentNode = Object . assign ( { } , child ) as HastNode
126+
127+ // Mark all headings (h2-h6) with framework attribute so they appear in TOC only for this framework
128+ if (
129+ contentNode . type === 'element' &&
130+ contentNode . tagName &&
131+ / ^ h [ 2 - 6 ] $ / . test ( contentNode . tagName )
132+ ) {
133+ contentNode . properties = ( contentNode . properties || { } ) as Record < string , unknown >
134+ contentNode . properties [ 'data-framework' ] = currentFramework
135+ }
111136
112- // Look for <pre> elements (code blocks) under current framework
113- if ( ( child as any ) . type === 'element' && ( child as any ) . tagName === 'pre' ) {
114- const codeBlockData = extractCodeBlockData ( child )
115- if ( ! codeBlockData ) continue
137+ contentByFramework [ currentFramework ] . push ( contentNode )
116138
117- codeBlocksByFramework [ currentFramework ] . push ( {
118- title : codeBlockData . title || 'Untitled' ,
119- code : codeBlockData . code ,
120- language : codeBlockData . language ,
121- preNode : child ,
122- } )
139+ // Extract code blocks for this framework
140+ if ( contentNode . type === 'element' && contentNode . tagName === 'pre' ) {
141+ const codeBlockData = extractCodeBlockData ( contentNode )
142+ if ( codeBlockData ) {
143+ codeBlocksByFramework [ currentFramework ] . push ( codeBlockData )
144+ }
123145 }
124146 }
125147
126- // Return null only if no frameworks found at all
148+ // Return null if no frameworks found
127149 if ( Object . keys ( contentByFramework ) . length === 0 ) {
128150 return null
129151 }
@@ -188,3 +210,4 @@ export const rehypeTransformFrameworkComponents = () => {
188210 } )
189211 }
190212}
213+
0 commit comments