Skip to content

Conversation

@yuito-it
Copy link

@yuito-it yuito-it commented Nov 19, 2025

What

publicディレクトリ内の全てのディレクトリを再起的に読み込み、記事を管理しやすくした。

How

Why

下記ディスカッションを参照。

Qiita CLIでpublic配下にファイルをいっぱい置いておくのは記事数が多くなってくると少ししんどいものがあるなと...
public配下であれば、その配下のディレクトリも再帰的に読み込んで欲しいなと思いました。
(特にアドカレを毎年完走するとなると、一年で少なくとも25記事以上増えていくわけで、流石に辛いなと)

Originally posted by @yuito-it in increments/qiita-discussions#1031

Refs

@yuito-it yuito-it marked this pull request as ready for review November 19, 2025 06:36
@yuito-it yuito-it requested a review from a team as a code owner November 19, 2025 06:36
@yuito-it yuito-it requested review from Copilot and kimkim0814 and removed request for a team November 19, 2025 06:36
@yuito-it yuito-it changed the title [WIP] publicディレクトリ内でディレクトリ分けができるようにする(ディレクトリを再起的に読み込む) publicディレクトリ内でディレクトリ分けができるようにする(ディレクトリを再起的に読み込む) Nov 19, 2025
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds recursive directory support for the public directory in Qiita CLI, allowing users to organize articles in nested folders for better management. This addresses the pain point of managing large numbers of articles in a flat structure.

Key Changes:

  • Modified file system operations to recursively read directories instead of just the top level
  • Updated the preview UI to display articles in a hierarchical tree structure based on folder organization
  • Fixed React dependency version mismatches in package.json

Reviewed Changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
yarn.lock Updated React, React DOM, and related dependencies from 19.1.x to 19.2.x versions with matching scheduler updates
package.json Updated React and @types/react-dom version requirements to ^19.1.13 for consistency
src/server/app.ts Added chokidar watch options to ignore node_modules and .git directories during development
src/server/api/items.ts Added parent property to item responses, calculated from file path segments
src/lib/view-models/items.ts Added parent: string[] field to ItemViewModel type definition
src/lib/file-system-repo.ts Changed parseFilename to preserve directory paths, added recursive readdir option, and filtered out .remote directory
src/client/components/SidebarArticles.tsx Implemented tree-based rendering of articles with nested folder support and visual guide lines for hierarchy

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@acronhub
Copy link
Contributor

@yuito-it
Code owners による自動割り当てのため、一旦レビュアーを解除させていただきます。
改めて適切なタイミングでレビュアーを指定いたしますので、お待ちいただけますと幸いです 🙇

@acronhub acronhub removed the request for review from kimkim0814 November 20, 2025 05:44
@tomoasleep tomoasleep self-requested a review December 3, 2025 02:51
@yuito-it
Copy link
Author

リベースしました。

@tomoasleep
Copy link
Member

レビューが遅くなりすみません 🙇 。今レビューを行っているところなので少々お待ちください 🙏

@yuito-it
Copy link
Author

なお、使用感としてはこんな感じです。
Qiitaに書いてみました。

https://qiita.com/yuito_it_/items/682eda645d914dd09425

@tomoasleep
Copy link
Member

ありがとうございます 😄 そちらも拝見させていただきますね

@tomoasleep
Copy link
Member

実装見させていただいています。

提案していただいた内容、機能としてとても良いと思います!
要素の左側の padding が深くて、ネストが深くなってきたときに記事タイトルがやや読みにくくなっていそうなので、デザイナーメンバーからもレビューしてもらって更に読みやすくできないか確認してみますね。 (来週ぐらいには見てもらえないか相談してみます)

コードの方は引き続き見させていただいているのでもう少々お待ち下さい。。。 🙏

image

@yuito-it
Copy link
Author

要素の左側の padding が深くて、ネストが深くなってきたときに記事タイトルがやや読みにくくなっていそうなので、デザイナーメンバーからもレビューしてもらって更に読みやすくできないか確認してみますね。 (来週ぐらいには見てもらえないか相談してみます)

ちょうどその部分についてだいぶモヤモヤしていたのです…
ありがとうございます!

Copy link
Member

@tomoasleep tomoasleep left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

修正お願いしたいところをコメントしました。

const watcher = chokidar.watch(watchPath, {
ignored: ["**/.remote/**"],
ignored: [/node_modules|\.git/, "**/.remote/**"],
persistent: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝

persistent (default: true). Indicates whether the process should continue to run as long as files are being watched.
https://github.com/paulmillr/chokidar?tab=readme-ov-file#persistence

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
persistent: true,

persistent は元々 default で true なので、この行を足しても挙動は変わりません。

この Pull Request の本題は再帰的なディレクトリの読み込みを行えるようにすることで、それと関係ない変更は最小限にしたいです。なので、この行は削除してもらっていいでしょうか?

title: item.title,
updated_at: item.updatedAt,
modified: item.modified,
parent: item.name.split("/").slice(0, -1),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lib/entities/qiita-item.ts 側に

import { dirname, sep } from "path"

export class QiitaItem {
   getParentDirNames() {
     return dirname(this.itemPath).split(sep)
   }
}

のようなメソッド定義を追加した上で、

Suggested change
parent: item.name.split("/").slice(0, -1),
parentDirNames: item.parentDirNames(),

とするのが良いと思います。

  • parent だと何を指しているか不明瞭なので parentDirNames など具体的な名前にしたい
  • 親ディレクトリ名の算出処理は router 側に書くのではなく、entities 側のメソッドとして持たせたい
  • Windows 等を考慮して path.sep を使ったほうが良い

という理由です。


const countSubtreeItems = (node: TreeNode): number =>
node.items.length +
Object.values(node.children).reduce((s, c) => s + countSubtreeItems(c), 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SlidebarArticle の定義が長くなっているため、このファイルとは別のファイルで TreeNode は定義してほしいです。

また、React の Component 定義は pure function として書くという原則があるため、 addToTree, countSubtree などは不具合が起きないように Component 内から直接使えないようにしたいです

Ref: コンポーネントを純粋に保つ – React

なので、↓ のように、別ファイル (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
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants