@@ -59,24 +59,133 @@ 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+ } = { } ;
70+ const topLevelItems : ItemViewModel [ ] = [ ] ;
71+ items . forEach ( ( item ) => {
72+ if ( ! item . parent || item . parent . length === 0 ) {
73+ topLevelItems . push ( item ) ;
74+ } 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+ }
86+ }
87+ } ) ;
88+
6289 return (
6390 < details css = { articleDetailsStyle } open = { isDetailsOpen } >
6491 < summary css = { articleSummaryStyle } onClick = { toggleAccordion } >
6592 { ArticleState [ articleState ] }
6693 < span css = { articleSectionTitleCountStyle } > { items . length } </ span >
6794 </ summary >
6895 < ul >
69- { items . sort ( compare [ sortType ] ) . map ( ( item ) => (
70- < li key = { item . items_show_path } >
71- < Link css = { articlesListItemStyle } to = { item . items_show_path } >
72- < MaterialSymbol fill = { item . modified && articleState !== "Draft" } >
73- note
74- </ MaterialSymbol >
75- < span css = { articleListItemInnerStyle } >
76- { item . modified && articleState !== "Draft" && "(差分あり) " }
77- { item . title }
78- </ span >
79- </ Link >
96+ { topLevelItems . length > 0 &&
97+ topLevelItems . sort ( compare [ sortType ] ) . map ( ( item ) => (
98+ < li key = { item . items_show_path } >
99+ < Link css = { articlesListItemStyle } to = { item . items_show_path } >
100+ < MaterialSymbol
101+ fill = { item . modified && articleState !== "Draft" }
102+ >
103+ note
104+ </ MaterialSymbol >
105+ < span css = { articleListItemInnerStyle } >
106+ { item . modified && articleState !== "Draft" && "(差分あり) " }
107+ { item . title }
108+ </ span >
109+ </ Link >
110+ </ li >
111+ ) ) }
112+
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 >
80189 </ li >
81190 ) ) }
82191 </ ul >
@@ -93,6 +202,18 @@ const articleDetailsStyle = css({
93202 "&[open] > summary::before" : {
94203 content : "'expand_more'" ,
95204 } ,
205+ // nested lists: give visual indentation for hierarchy
206+ "& ul" : {
207+ listStyle : "none" ,
208+ margin : 0 ,
209+ paddingLeft : 0 ,
210+ } ,
211+ "& ul ul" : {
212+ paddingLeft : getSpace ( 4 ) ,
213+ } ,
214+ "& ul ul ul" : {
215+ paddingLeft : getSpace ( 4 ) ,
216+ } ,
96217} ) ;
97218
98219const articleSummaryStyle = css ( {
@@ -137,9 +258,9 @@ const articlesListItemStyle = css({
137258 fontSize : Typography . body2 ,
138259 gap : getSpace ( 1 ) ,
139260 lineHeight : LineHeight . bodyDense ,
140- padding : `${ getSpace ( 3 / 4 ) } px ${ getSpace ( 5 / 2 ) } px ${ getSpace (
141- 3 / 4 ,
142- ) } px ${ getSpace ( 3 / 2 ) } px `,
261+ padding : `${ getSpace ( 3 / 4 ) } px ${ getSpace ( 5 / 2 ) } px ${ getSpace ( 3 / 4 ) } px ${ getSpace (
262+ 3 ,
263+ ) } px`,
143264 whiteSpace : "nowrap" ,
144265 textOverflow : "ellipsis" ,
145266
0 commit comments