@@ -14,39 +14,70 @@ const STARTER_SUBSCRIPTIONS_URL =
1414
1515function parseMarkdownHeadings ( md ) {
1616 const tree = [ ] ;
17- let currentSection = null ;
17+
18+ let currentH2 = null ;
19+ let currentH3 = null ;
1820 let currentItem = null ;
1921 let readingDescription = false ;
2022
21- md . split ( '\n' ) . forEach ( line => {
23+ const isIgnoredSection = title =>
24+ title === 'Table of Contents' || title === 'More APIs' || title === 'More on Streams' ;
25+
26+ md . split ( '\n' ) . forEach ( rawLine => {
27+ const line = rawLine . trimEnd ( ) ;
28+
2229 if ( line . startsWith ( '## ' ) ) {
2330 const sectionTitle = line . slice ( 3 ) . trim ( ) ;
24- if (
25- sectionTitle !== 'Table of Contents' &&
26- sectionTitle !== 'More APIs' &&
27- sectionTitle !== 'More on Streams'
28- ) {
29- currentSection = { title : sectionTitle , children : [ ] } ;
30- tree . push ( currentSection ) ;
31- } else currentSection = null ;
31+ if ( ! isIgnoredSection ( sectionTitle ) ) {
32+ currentH2 = { title : sectionTitle , children : [ ] } ;
33+ tree . push ( currentH2 ) ;
34+ } else {
35+ currentH2 = null ;
36+ }
37+ currentH3 = null ;
38+ currentItem = null ;
3239 readingDescription = false ;
40+ return ;
41+ }
42+
43+ if ( ! currentH2 ) return ;
44+
45+ if ( line . startsWith ( '### ' ) ) {
46+ const subsectionTitle = line . slice ( 4 ) . trim ( ) ;
47+ currentH3 = { title : subsectionTitle , children : [ ] } ;
48+ currentH2 . children . push ( currentH3 ) ;
3349 currentItem = null ;
34- } else if ( currentSection && line . startsWith ( '### ' ) ) {
35- currentItem = { title : line . slice ( 4 ) . trim ( ) , description : '' , url : '' } ;
36- currentSection . children . push ( currentItem ) ;
50+ readingDescription = false ;
51+ return ;
52+ }
53+
54+ if ( line . startsWith ( '#### ' ) ) {
55+ const itemTitle = line . slice ( 5 ) . trim ( ) ;
56+ const parent = currentH3 || currentH2 ;
57+ if ( ! parent ) return ;
58+
59+ currentItem = { title : itemTitle , description : '' , url : '' } ;
60+ if ( ! parent . children ) parent . children = [ ] ;
61+ parent . children . push ( currentItem ) ;
3762 readingDescription = true ;
38- } else if ( readingDescription && currentItem ) {
63+ return ;
64+ }
65+
66+ if ( readingDescription && currentItem ) {
3967 const text = line . trim ( ) ;
68+ if ( ! text ) return ;
69+
4070 if ( / ^ [ ▶ ► ] / . test ( text ) ) {
4171 const match = text . match ( / \[ ( .+ ?) \] \( ( .+ ?) \) / ) ;
4272 if ( match ) currentItem . url = match [ 2 ] ;
4373 readingDescription = false ;
4474 currentItem = null ;
45- } else if ( text ) {
75+ } else {
4676 currentItem . description += ( currentItem . description ? ' ' : '' ) + text ;
4777 }
4878 }
4979 } ) ;
80+
5081 return tree ;
5182}
5283
@@ -72,12 +103,19 @@ const StarterQueriesPanel = observer(({ type }) => {
72103 const { user } = UserStore ;
73104 const url = type === 'queries' ? STARTER_QUERIES_URL : STARTER_SUBSCRIPTIONS_URL ;
74105 const tree = useMarkdownTree ( url ) ;
75- const [ expandedKey , setExpandedKey ] = useState ( null ) ;
106+ const [ expandedKeys , setExpandedKeys ] = useState ( ( ) => new Set ( ) ) ;
76107
77- const toggleSection = useCallback ( ( index ) => {
78- const key = `${ type } -${ index } ` ;
79- setExpandedKey ( prev => ( prev === key ? null : key ) ) ;
80- } , [ type ] ) ;
108+ const toggleNode = useCallback ( ( key ) => {
109+ setExpandedKeys ( prev => {
110+ const next = new Set ( prev ) ;
111+ if ( next . has ( key ) ) {
112+ next . delete ( key ) ;
113+ } else {
114+ next . add ( key ) ;
115+ }
116+ return next ;
117+ } ) ;
118+ } , [ ] ) ;
81119
82120 const extractId = url => url . split ( '/' ) . pop ( ) ;
83121
@@ -107,43 +145,45 @@ const StarterQueriesPanel = observer(({ type }) => {
107145 [ history , updateQuery , index , user , query , setQuery ]
108146 ) ;
109147
110- const renderTree = ( treeToRender ) => (
111- < ul className = "tree-root" >
112- { treeToRender . map ( ( section , idx ) => {
113- const key = `${ type } -${ idx } ` ;
114- const isOpen = expandedKey === key ;
115- return (
116- < li key = { key } className = "tree-section" >
117- < div className = { `tree-node ${ isOpen ? 'node-open' : '' } ` } onClick = { ( ) => toggleSection ( idx ) } >
118- { section . children . length > 0 && (
148+ const renderTree = ( nodes , parentKey = '' ) => (
149+ < ul className = { parentKey ? 'tree-children' : 'tree-root' } >
150+ { nodes . map ( ( node , idx ) => {
151+ const key = parentKey ? `${ parentKey } -${ idx } ` : `${ type } -${ idx } ` ;
152+ const hasChildren = Array . isArray ( node . children ) && node . children . length > 0 ;
153+
154+ if ( hasChildren ) {
155+ const isOpen = expandedKeys . has ( key ) ;
156+ return (
157+ < li key = { key } className = "tree-section" >
158+ < div
159+ className = { `tree-node ${ isOpen ? 'node-open' : '' } ` }
160+ onClick = { ( ) => toggleNode ( key ) }
161+ >
119162 < span className = "tree-toggle" > { isOpen ? '▾' : '▸' } </ span >
120- ) }
121- { section . title }
122- </ div >
123- { isOpen && (
124- < ul className = "tree-children" >
125- { section . children . map ( ( item , j ) => {
126- const tipId = `tooltip-${ type } -${ idx } -${ j } ` ;
127- return (
128- < li key = { `${ key } -${ j } ` } className = "tree-child"
129- onClick = { ( ) => handleItemClick ( item . url , item . title ) }
130- >
131- < OverlayTrigger
132- placement = "bottom"
133- delay = { { show : 250 , hide : 100 } }
134- overlay = { < Tooltip id = { tipId } > { item . description } </ Tooltip > }
135- >
136- < span
137- className = "child-link"
138- > < i className = "bi bi-terminal me-2" > </ i >
139- { item . title }
140- </ span >
141- </ OverlayTrigger >
142- </ li >
143- ) ;
144- } ) }
145- </ ul >
146- ) }
163+ { node . title }
164+ </ div >
165+ { isOpen && renderTree ( node . children , key ) }
166+ </ li >
167+ ) ;
168+ }
169+
170+ const tipId = `tooltip-${ key } ` ;
171+ return (
172+ < li
173+ key = { key }
174+ className = "tree-child"
175+ onClick = { ( ) => handleItemClick ( node . url , node . title ) }
176+ >
177+ < OverlayTrigger
178+ placement = "bottom"
179+ delay = { { show : 250 , hide : 100 } }
180+ overlay = { < Tooltip id = { tipId } > { node . description } </ Tooltip > }
181+ >
182+ < span className = "child-link" >
183+ < i className = "bi bi-terminal me-2" > </ i >
184+ { node . title }
185+ </ span >
186+ </ OverlayTrigger >
147187 </ li >
148188 ) ;
149189 } ) }
0 commit comments