-
Notifications
You must be signed in to change notification settings - Fork 22
publicディレクトリ内でディレクトリ分けができるようにする(ディレクトリを再起的に読み込む) #340
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a02d368
b163ad3
cfebd05
f657b38
86cd396
d3694a2
b84fdd4
b732de9
5be9fe2
4d8014e
bc51381
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,26 +59,111 @@ export const SidebarArticles = ({ items, sortType, articleState }: Props) => { | |
| localStorage.setItem(StorageName[articleState], isDetailsOpen.toString()); | ||
| }, [isDetailsOpen]); | ||
|
|
||
| // build recursive tree from item.parent (segments array) | ||
| const topLevelItems: ItemViewModel[] = []; | ||
|
|
||
| type TreeNode = { | ||
| name: string; | ||
| items: ItemViewModel[]; | ||
| children: { [name: string]: TreeNode }; | ||
| }; | ||
|
|
||
| const roots: { [name: string]: TreeNode } = {}; | ||
|
|
||
| const addToTree = (segments: string[], item: ItemViewModel) => { | ||
| const rootName = segments[0]; | ||
| if (!roots[rootName]) | ||
| roots[rootName] = { name: rootName, items: [], children: {} }; | ||
| let node = roots[rootName]; | ||
| const rest = segments.slice(1); | ||
| if (rest.length === 0) { | ||
| node.items.push(item); | ||
| return; | ||
| } | ||
| for (const seg of rest) { | ||
| if (!node.children[seg]) | ||
| node.children[seg] = { name: seg, items: [], children: {} }; | ||
| node = node.children[seg]; | ||
| } | ||
| node.items.push(item); | ||
| }; | ||
|
|
||
| items.forEach((item) => { | ||
| if (!item.parent || item.parent.length === 0) { | ||
| topLevelItems.push(item); | ||
| } else { | ||
| addToTree(item.parent, item); | ||
| } | ||
| }); | ||
|
|
||
| const countSubtreeItems = (node: TreeNode): number => | ||
| node.items.length + | ||
| Object.values(node.children).reduce((s, c) => s + countSubtreeItems(c), 0); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SlidebarArticle の定義が長くなっているため、このファイルとは別のファイルで TreeNode は定義してほしいです。 また、React の Component 定義は pure function として書くという原則があるため、 addToTree, countSubtree などは不具合が起きないように Component 内から直接使えないようにしたいです なので、↓ のように、別ファイル (client/lib/fileTree.ts などで OK です) に切り出してください。 export type FileTreeNodeMap = { [name: string]: TreeNode }
export type type FileTreeNode = {
name: string;
items: ItemViewModel[];
children: FileTreeNodeMap;
};
// SidebarArticles からはこの function を呼び出す
export const fileTreeFromItemViewModels(items: ItemViewModel[]): FileTreeNode {
const topLevelItems: ItemViewModel[] = []
const roots: FileTreeNodeMap = {};
const addToTree = (segments: string[], item: ItemViewModel) => {
const rootName = segments[0];
if (!roots[rootName])
roots[rootName] = { name: rootName, items: [], children: {} };
let node = roots[rootName];
const rest = segments.slice(1);
if (rest.length === 0) {
node.items.push(item);
return;
}
for (const seg of rest) {
if (!node.children[seg])
node.children[seg] = { name: seg, items: [], children: {} };
node = node.children[seg];
}
node.items.push(item);
};
items.forEach((item) => {
if (!item.parent || item.parent.length === 0) {
topLevelItems.push(item);
} else {
addToTree(item.parent, item);
}
});
return {
name: "root"
items: topLevelItems,
children: roots
}
} |
||
|
|
||
| const renderNode = (node: TreeNode, path: string) => { | ||
| const cmp = compare[sortType]; | ||
| return ( | ||
| <li key={path}> | ||
| <details css={articleDetailsStyle} open> | ||
| <summary css={articleSummaryStyle}> | ||
| {node.name} | ||
| <span css={articleSectionTitleCountStyle}> | ||
| {countSubtreeItems(node)} | ||
| </span> | ||
| </summary> | ||
| <ul> | ||
| {Object.values(node.children) | ||
| .sort((a, b) => a.name.localeCompare(b.name)) | ||
| .map((child) => renderNode(child, `${path}/${child.name}`))} | ||
|
|
||
| {[...node.items].sort(cmp).map((item) => ( | ||
| <li key={item.items_show_path}> | ||
| <Link css={articlesListItemStyle} to={item.items_show_path}> | ||
| <MaterialSymbol | ||
| fill={item.modified && articleState !== "Draft"} | ||
| > | ||
| note | ||
| </MaterialSymbol> | ||
| <span css={articleListItemInnerStyle}> | ||
| {item.modified && articleState !== "Draft" && "(差分あり) "} | ||
| {item.title} | ||
| </span> | ||
| </Link> | ||
| </li> | ||
| ))} | ||
| </ul> | ||
| </details> | ||
| </li> | ||
| ); | ||
| }; | ||
|
|
||
| return ( | ||
| <details css={articleDetailsStyle} open={isDetailsOpen}> | ||
| <summary css={articleSummaryStyle} onClick={toggleAccordion}> | ||
| {ArticleState[articleState]} | ||
| <span css={articleSectionTitleCountStyle}>{items.length}</span> | ||
| </summary> | ||
| <ul> | ||
| {items.sort(compare[sortType]).map((item) => ( | ||
| <li key={item.items_show_path}> | ||
| <Link css={articlesListItemStyle} to={item.items_show_path}> | ||
| <MaterialSymbol fill={item.modified && articleState !== "Draft"}> | ||
| note | ||
| </MaterialSymbol> | ||
| <span css={articleListItemInnerStyle}> | ||
| {item.modified && articleState !== "Draft" && "(差分あり) "} | ||
| {item.title} | ||
| </span> | ||
| </Link> | ||
| </li> | ||
| ))} | ||
| {Object.values(roots) | ||
| .sort((a, b) => a.name.localeCompare(b.name)) | ||
| .map((r) => renderNode(r, r.name))} | ||
|
|
||
| {topLevelItems.length > 0 && | ||
| [...topLevelItems].sort(compare[sortType]).map((item) => ( | ||
| <li key={item.items_show_path}> | ||
| <Link css={articlesListItemStyle} to={item.items_show_path}> | ||
| <MaterialSymbol | ||
| fill={item.modified && articleState !== "Draft"} | ||
| > | ||
| note | ||
| </MaterialSymbol> | ||
| <span css={articleListItemInnerStyle}> | ||
| {item.modified && articleState !== "Draft" && "(差分あり) "} | ||
| {item.title} | ||
| </span> | ||
| </Link> | ||
| </li> | ||
| ))} | ||
| </ul> | ||
| </details> | ||
| ); | ||
|
|
@@ -93,6 +178,44 @@ const articleDetailsStyle = css({ | |
| "&[open] > summary::before": { | ||
| content: "'expand_more'", | ||
| }, | ||
| // nested lists: draw vertical guide lines inside the padded area | ||
| "& ul": { | ||
| listStyle: "none", | ||
| margin: 0, | ||
| paddingLeft: getSpace(1), | ||
| }, | ||
| "& ul ul": { | ||
| position: "relative", | ||
| paddingLeft: getSpace(3), | ||
| }, | ||
| "& ul ul::before": { | ||
| content: "''", | ||
| position: "absolute", | ||
| left: getSpace(3), | ||
| top: 0, | ||
| bottom: 0, | ||
| width: 1, | ||
| backgroundColor: Colors.gray20, | ||
| }, | ||
| "& ul ul > li": { | ||
| paddingLeft: getSpace(1.5), | ||
| }, | ||
| "& ul ul ul": { | ||
| position: "relative", | ||
| paddingLeft: getSpace(4), | ||
| }, | ||
| "& ul ul ul::before": { | ||
| content: "''", | ||
| position: "absolute", | ||
| left: getSpace(3), | ||
| top: 0, | ||
| bottom: 0, | ||
| width: 1, | ||
| backgroundColor: Colors.gray20, | ||
| }, | ||
| "& ul ul ul > li": { | ||
| paddingLeft: getSpace(1.5), | ||
| }, | ||
yuito-it marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }); | ||
|
|
||
| const articleSummaryStyle = css({ | ||
|
|
@@ -137,9 +260,9 @@ const articlesListItemStyle = css({ | |
| fontSize: Typography.body2, | ||
| gap: getSpace(1), | ||
| lineHeight: LineHeight.bodyDense, | ||
| padding: `${getSpace(3 / 4)}px ${getSpace(5 / 2)}px ${getSpace( | ||
| 3 / 4, | ||
| )}px ${getSpace(3 / 2)}px`, | ||
| padding: `${getSpace(3 / 4)}px ${getSpace(5 / 2)}px ${getSpace(3 / 4)}px ${getSpace( | ||
| 3, | ||
| )}px`, | ||
| whiteSpace: "nowrap", | ||
| textOverflow: "ellipsis", | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -27,6 +27,7 @@ const itemsIndex = async (req: Express.Request, res: Express.Response) => { | |||||
| title: item.title, | ||||||
| updated_at: item.updatedAt, | ||||||
| modified: item.modified, | ||||||
| parent: item.name.split("/").slice(0, -1), | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
import { dirname, sep } from "path"
export class QiitaItem {
getParentDirNames() {
return dirname(this.itemPath).split(sep)
}
}のようなメソッド定義を追加した上で、
Suggested change
とするのが良いと思います。
という理由です。 |
||||||
| }; | ||||||
|
|
||||||
| if (item.id) { | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -66,7 +66,8 @@ export function startLocalChangeWatcher({ | |||
| }) { | ||||
| const wsServer = new WebSocketServer({ server }); | ||||
| const watcher = chokidar.watch(watchPath, { | ||||
| ignored: ["**/.remote/**"], | ||||
| ignored: [/node_modules|\.git/, "**/.remote/**"], | ||||
| persistent: true, | ||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
persistent は元々 default で true なので、この行を足しても挙動は変わりません。 この Pull Request の本題は再帰的なディレクトリの読み込みを行えるようにすることで、それと関係ない変更は最小限にしたいです。なので、この行は削除してもらっていいでしょうか? |
||||
| }); | ||||
| watcher.on("change", () => { | ||||
| wsServer.clients.forEach((client) => { | ||||
|
|
||||
Uh oh!
There was an error while loading. Please reload this page.