Skip to content

Commit c841754

Browse files
committed
ディレクトリ階層を表示するようにする
1 parent 1aa8894 commit c841754

File tree

3 files changed

+137
-14
lines changed

3 files changed

+137
-14
lines changed

src/client/components/SidebarArticles.tsx

Lines changed: 135 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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

98219
const 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

src/lib/view-models/items.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export type ItemViewModel = {
55
title: string;
66
updated_at: string;
77
modified: boolean;
8+
parent: string[];
89
};
910

1011
export type ItemsIndexViewModel = {

src/server/api/items.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const itemsIndex = async (req: Express.Request, res: Express.Response) => {
2727
title: item.title,
2828
updated_at: item.updatedAt,
2929
modified: item.modified,
30+
parent: item.name.split("/").slice(0, -1) || [],
3031
};
3132

3233
if (item.id) {

0 commit comments

Comments
 (0)