@@ -59,33 +59,84 @@ export const SidebarArticles = ({ items, sortType, articleState }: Props) => {
5959 localStorage . setItem ( StorageName [ articleState ] , isDetailsOpen . toString ( ) ) ;
6060 } , [ isDetailsOpen ] ) ;
6161
62- console . log ( items ) ;
63-
64- const rootGrouped : {
65- [ root : string ] : {
66- direct : ItemViewModel [ ] ;
67- children : { [ child : string ] : ItemViewModel [ ] } ;
68- } ;
69- } = { } ;
62+ // build recursive tree from item.parent (segments array)
7063 const topLevelItems : ItemViewModel [ ] = [ ] ;
64+
65+ type TreeNode = {
66+ name : string ;
67+ items : ItemViewModel [ ] ;
68+ children : { [ name : string ] : TreeNode } ;
69+ } ;
70+
71+ const roots : { [ name : string ] : TreeNode } = { } ;
72+
73+ const addToTree = ( segments : string [ ] , item : ItemViewModel ) => {
74+ const rootName = segments [ 0 ] ;
75+ if ( ! roots [ rootName ] )
76+ roots [ rootName ] = { name : rootName , items : [ ] , children : { } } ;
77+ let node = roots [ rootName ] ;
78+ const rest = segments . slice ( 1 ) ;
79+ if ( rest . length === 0 ) {
80+ node . items . push ( item ) ;
81+ return ;
82+ }
83+ for ( const seg of rest ) {
84+ if ( ! node . children [ seg ] )
85+ node . children [ seg ] = { name : seg , items : [ ] , children : { } } ;
86+ node = node . children [ seg ] ;
87+ }
88+ node . items . push ( item ) ;
89+ } ;
90+
7191 items . forEach ( ( item ) => {
7292 if ( ! item . parent || item . parent . length === 0 ) {
7393 topLevelItems . push ( item ) ;
7494 } else {
75- const root = item . parent [ 0 ] ;
76- const rest = item . parent . slice ( 1 ) ;
77- const child = rest . length === 0 ? null : rest . join ( "/" ) ;
78- if ( ! rootGrouped [ root ] ) rootGrouped [ root ] = { direct : [ ] , children : { } } ;
79- if ( child === null ) {
80- rootGrouped [ root ] . direct . push ( item ) ;
81- } else {
82- if ( ! rootGrouped [ root ] . children [ child ] )
83- rootGrouped [ root ] . children [ child ] = [ ] ;
84- rootGrouped [ root ] . children [ child ] . push ( item ) ;
85- }
95+ addToTree ( item . parent , item ) ;
8696 }
8797 } ) ;
8898
99+ const countSubtreeItems = ( node : TreeNode ) : number =>
100+ node . items . length +
101+ Object . values ( node . children ) . reduce ( ( s , c ) => s + countSubtreeItems ( c ) , 0 ) ;
102+
103+ const renderNode = ( node : TreeNode , path : string ) => {
104+ const cmp = compare [ sortType ] ;
105+ return (
106+ < li key = { path } >
107+ < details css = { articleDetailsStyle } open >
108+ < summary css = { articleSummaryStyle } >
109+ { node . name }
110+ < span css = { articleSectionTitleCountStyle } >
111+ { countSubtreeItems ( node ) }
112+ </ span >
113+ </ summary >
114+ < ul >
115+ { node . items . sort ( cmp ) . map ( ( item ) => (
116+ < li key = { item . items_show_path } >
117+ < Link css = { articlesListItemStyle } to = { item . items_show_path } >
118+ < MaterialSymbol
119+ fill = { item . modified && articleState !== "Draft" }
120+ >
121+ note
122+ </ MaterialSymbol >
123+ < span css = { articleListItemInnerStyle } >
124+ { item . modified && articleState !== "Draft" && "(差分あり) " }
125+ { item . title }
126+ </ span >
127+ </ Link >
128+ </ li >
129+ ) ) }
130+
131+ { Object . values ( node . children )
132+ . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
133+ . map ( ( child ) => renderNode ( child , `${ path } /${ child . name } ` ) ) }
134+ </ ul >
135+ </ details >
136+ </ li >
137+ ) ;
138+ } ;
139+
89140 return (
90141 < details css = { articleDetailsStyle } open = { isDetailsOpen } >
91142 < summary css = { articleSummaryStyle } onClick = { toggleAccordion } >
@@ -110,84 +161,9 @@ export const SidebarArticles = ({ items, sortType, articleState }: Props) => {
110161 </ li >
111162 ) ) }
112163
113- { Object . entries ( rootGrouped ) . map ( ( [ root , group ] ) => (
114- < li key = { root } >
115- < details css = { articleDetailsStyle } open >
116- < summary css = { articleSummaryStyle } >
117- { root }
118- < span css = { articleSectionTitleCountStyle } >
119- { group . direct . length +
120- Object . values ( group . children ) . reduce (
121- ( s , arr ) => s + arr . length ,
122- 0 ,
123- ) }
124- </ span >
125- </ summary >
126- < ul >
127- { group . direct . length > 0 &&
128- group . direct . sort ( compare [ sortType ] ) . map ( ( item ) => (
129- < li key = { item . items_show_path } >
130- < Link
131- css = { articlesListItemStyle }
132- to = { item . items_show_path }
133- >
134- < MaterialSymbol
135- fill = { item . modified && articleState !== "Draft" }
136- >
137- note
138- </ MaterialSymbol >
139- < span css = { articleListItemInnerStyle } >
140- { item . modified &&
141- articleState !== "Draft" &&
142- "(差分あり) " }
143- { item . title }
144- </ span >
145- </ Link >
146- </ li >
147- ) ) }
148-
149- { Object . entries ( group . children ) . map (
150- ( [ childPath , groupedItems ] ) => (
151- < li key = { childPath } >
152- < details css = { articleDetailsStyle } open >
153- < summary css = { articleSummaryStyle } >
154- { childPath }
155- < span css = { articleSectionTitleCountStyle } >
156- { groupedItems . length }
157- </ span >
158- </ summary >
159- < ul >
160- { groupedItems . sort ( compare [ sortType ] ) . map ( ( item ) => (
161- < li key = { item . items_show_path } >
162- < Link
163- css = { articlesListItemStyle }
164- to = { item . items_show_path }
165- >
166- < MaterialSymbol
167- fill = {
168- item . modified && articleState !== "Draft"
169- }
170- >
171- note
172- </ MaterialSymbol >
173- < span css = { articleListItemInnerStyle } >
174- { item . modified &&
175- articleState !== "Draft" &&
176- "(差分あり) " }
177- { item . title }
178- </ span >
179- </ Link >
180- </ li >
181- ) ) }
182- </ ul >
183- </ details >
184- </ li >
185- ) ,
186- ) }
187- </ ul >
188- </ details >
189- </ li >
190- ) ) }
164+ { Object . values ( roots )
165+ . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
166+ . map ( ( r ) => renderNode ( r , r . name ) ) }
191167 </ ul >
192168 </ details >
193169 ) ;
0 commit comments