# 機能設計書 23-ネストコレクション

## 概要

本ドキュメントは、Decap CMSにおけるネストコレクション機能について、ツリー構造の階層的なコレクション表示の処理フロー・データ構造・UIコンポーネントを詳細に記述した機能設計書である。

### 本機能の処理概要

フォルダベースのコレクションにおいて、サブフォルダ構造を持つエントリをツリー構造のナビゲーションとして表示する機能である。NestedCollectionコンポーネントがエントリのパスからディレクトリ階層を解析し、展開可能なツリーノードとして描画する。

**業務上の目的・背景**：大量のコンテンツをフォルダ構造で整理している場合、フラットなリスト表示ではコンテンツの発見が困難になる。ネストコレクション機能により、ディレクトリ階層をツリーナビゲーションとして視覚化し、特定のサブフォルダ内のエントリにすばやくアクセスできるようにする。

**機能の利用シーン**：
- コレクション設定で`nested`プロパティが定義されている場合のサイドバー表示
- フォルダ構造を持つコンテンツ（例: ドキュメントサイトのセクション）の階層的ナビゲーション
- ネストコレクションのフィルタパスに基づくサブフォルダの絞り込み表示

**主要な処理内容**：
1. `getTreeData`関数でエントリのパスからツリー構造のデータを生成
2. `TreeNode`コンポーネントでツリーの各ノードを再帰的に描画
3. ノードのクリックで展開/折り畳みの状態を`updateNode`関数で管理
4. `filterTerm`パラメータによるサブフォルダの自動展開
5. EntriesCollectionの`filterNestedEntries`で選択フォルダ内のエントリのみ表示

**関連システム・外部連携**：特になし。フロントエンドのみで動作するUI機能。

**権限による制御**：特になし。コレクション設定の`nested`プロパティの有無で機能の有効/無効が制御される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | コレクション一覧画面 | 主画面 | サイドバーでのツリー構造のネストコレクション表示。NestedCollectionコンポーネントによる階層的ナビゲーション |
| 5 | コレクションフィルター画面 | 参照画面 | ネストコレクションのフィルタリングパスに基づくツリー構造のナビゲーション |

## 機能種別

表示処理 / ナビゲーション

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| collection | ImmutableMap | Yes | コレクション定義（nested設定を含む） | nestedプロパティが存在すること |
| entries | ImmutableList | Yes | コレクションの全エントリ一覧 | Redux Storeから取得 |
| filterTerm | string | No | フィルタパス（サブフォルダパス） | URLパラメータから取得 |

### 入力データソース

- Redux Store: state.entries（selectEntriesで取得）
- React Router: URLパラメータのfilterTerm
- コレクション設定: collection.nested（サブフォルダ設定）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| treeData | Array<TreeNode> | ツリー構造のノードデータ |
| TreeNavLink | ReactElement | 各ノードのナビゲーションリンク |

### 出力先

- 画面表示（サイドバーのツリーナビゲーション）
- URL遷移（`/collections/${collectionName}/filter${node.path}`）

## 処理フロー

### 処理シーケンス

```
1. NestedCollectionコンポーネントのコンストラクタでgetTreeData()を実行
   └─ エントリのパスからツリー構造データを生成
2. getTreeData()の処理:
   a. エントリのパスからcollectionFolderを除去した相対パスを取得
   b. 全エントリのディレクトリパスを抽出し、親-子のマッピングを生成
   c. nested.summaryがある場合はコレクションのsummaryを上書き
   d. flatDataをparentsToChildrenマッピングから再帰的にツリー構造を構築
3. TreeNodeコンポーネントで再帰的にツリーノードを描画
   └─ sortBy(treeData, getNodeTitle)でアルファベット順にソート
4. リーフノード（子なし）は非表示にする
   └─ subfolders設定により判定ロジックが異なる
5. ノードクリック時にonToggle()を実行
   └─ updateNode()で展開/折り畳み状態を更新
6. componentDidUpdateでfilterTermの変更を検知
   └─ filterTermに一致するパスのノードを自動展開
```

### フローチャート

```mermaid
flowchart TD
    A[NestedCollectionマウント] --> B[getTreeData実行]
    B --> C[エントリパスから相対パス生成]
    C --> D[ディレクトリ構造を抽出]
    D --> E[flatDataを生成]
    E --> F[parentsToChildrenマッピング構築]
    F --> G[再帰的にツリー構造を構築]
    G --> H[TreeNodeで描画]
    H --> I{ノードクリック?}
    I -->|Yes| J[onToggle実行]
    J --> K[updateNodeで状態更新]
    K --> H
    I -->|No| L{filterTerm変更?}
    L -->|Yes| M[該当パスのノードを自動展開]
    M --> H
    L -->|No| N[待機]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-23-01 | リーフノード非表示 | depth > 0 で子ノードがない（またはディレクトリ子ノードがない）場合、ノードを非表示にする | サブフォルダ設定により判定が異なる |
| BR-23-02 | ルートノードの特殊処理 | ルートノード（depth === 0）はコレクションラベルを表示し、常にディレクトリとして扱う | 常時 |
| BR-23-03 | タイトルの解決 | ルートノードはnode.titleを使用。非ルートノードはファイルの子ノードのtitleを優先使用 | getNodeTitle関数 |
| BR-23-04 | サブフォルダモード | nested.subfolders !== falseの場合、サブフォルダ構造を階層的に表示。falseの場合は即座の子のみ表示 | コレクション設定のnested.subfolders |
| BR-23-05 | ソート順 | ツリーノードはgetNodeTitleの結果でアルファベット順にソートされる | sortBy(treeData, getNodeTitle) |
| BR-23-06 | 自動展開 | filterTermに一致するパスのノードは自動的に展開される | filterTermが設定されている場合 |

### 計算ロジック

- ノードの相対パス: `entry.path.slice(collectionFolder.length)`
- フィルタURL: `/collections/${collectionName}/filter${node.path}`
- TreeNavLinkのpadding-left: `depth * 16 + 18`px

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| エントリ一覧取得 | Redux Store: state.entries | SELECT | selectEntriesでエントリ一覧を取得 |

### テーブル別操作詳細

#### Redux Store: state.entries

| 操作 | 項目（キー） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | entries | selectEntries(state.entries, collection) | mapStateToPropsで取得 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | エントリ未取得 | エントリデータがまだ読み込まれていない | entries = List()（空リスト）でフォールバック |

### リトライ仕様

特になし。エントリ読み込みはEntriesCollectionコンポーネントが担当する。

## トランザクション仕様

本機能は読み取り専用のUIレンダリングのため、トランザクション管理は不要。

## パフォーマンス要件

- ツリーデータの生成（getTreeData）はエントリ数に比例する計算量
- componentDidUpdateで前回のpropsとの比較を行い、不要な再計算を防止

## セキュリティ考慮事項

特になし。フロントエンドのUI表示機能のため、セキュリティ上の懸念は限定的。

## 備考

- NestedCollectionはRedux connectで包まれ、mapStateToPropsでentriesを取得する
- 展開状態（expanded）はコンポーネントの内部state（this.state.treeData）で管理される
- `walk`、`getTreeData`、`updateNode`はエクスポートされたユーティリティ関数として他所からも利用可能

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

ツリーデータの構造と生成ロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | NestedCollection.js | `packages/decap-cms-core/src/components/Collection/NestedCollection.js` | **151-227行目**: `getTreeData`関数。エントリパスからツリーデータを生成するアルゴリズム |

**読解のコツ**: `getTreeData`は以下の手順でツリーを構築する: (1) エントリの相対パスを計算、(2) ディレクトリ構造をdirsオブジェクトに抽出、(3) flatDataを生成（ルート + ディレクトリ + エントリ）、(4) parentsToChildrenマッピングで親子関係を構築、(5) reducer関数で再帰的にツリー構造を生成。

#### Step 2: ツリーノードの描画を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | NestedCollection.js | `packages/decap-cms-core/src/components/Collection/NestedCollection.js` | **77-131行目**: `TreeNode`関数コンポーネント。再帰的にツリーノードを描画。リーフノード判定（84-91行目）とキャレット表示（98-102行目, 117行目）に注目 |

**主要処理フロー**:
1. **81行目**: `sortBy(treeData, getNodeTitle)`でノードをソート
2. **82行目**: `subfolders`設定を取得
3. **84-91行目**: リーフノード判定（depth > 0 かつ子なし）
4. **92-95行目**: ナビゲーションURLの生成
5. **98-102行目**: hasChildren判定（展開キャレットの表示条件）
6. **106-129行目**: TreeNavLink描画と子ノードの再帰描画

#### Step 3: 状態管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | NestedCollection.js | `packages/decap-cms-core/src/components/Collection/NestedCollection.js` | **250-315行目**: `NestedCollection`クラスコンポーネント。コンストラクタ（257-264行目）でtreeDataを初期化、componentDidUpdate（271-294行目）でfilterTermの変更を検知、onToggle（296-307行目）で展開/折り畳みを制御 |

#### Step 4: ユーティリティ関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | NestedCollection.js | `packages/decap-cms-core/src/components/Collection/NestedCollection.js` | **140-149行目**: `walk`関数（ツリー全ノードの走査）。**229-248行目**: `updateNode`関数（特定ノードの更新） |

#### Step 5: フィルタリングとの連携を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | EntriesCollection.js | `packages/decap-cms-core/src/components/Collection/Entries/EntriesCollection.js` | **175-199行目**: `filterNestedEntries`関数。選択されたフォルダパスに基づくエントリのフィルタリング |

### プログラム呼び出し階層図

```
Sidebar.js (renderLink)
    |
    +-- NestedCollection (collection.has('nested')の場合)
        |
        +-- constructor: getTreeData(collection, entries)
        |       +-- dirname() (パス解析)
        |       +-- selectEntryCollectionTitle() (タイトル解決)
        |       +-- addFileTemplateFields() (テンプレート適用)
        |
        +-- render: TreeNode(collection, treeData, onToggle)
        |       +-- sortBy(treeData, getNodeTitle)
        |       +-- getNodeTitle(node)
        |       +-- TreeNode (再帰呼び出し)
        |
        +-- onToggle: updateNode(treeData, node, callback)
        |
        +-- componentDidUpdate: walk(treeData, callback)
                +-- getTreeData(collection, entries) (再構築)
```

### データフロー図

```
[入力]                    [処理]                           [出力]

Redux Store           getTreeData()                    ツリーナビゲーション
(entries)                |                               (サイドバーUI)
        |                v
        +---------> パス解析 -> ディレクトリ抽出            URL遷移
                         |                               (/collections/name/filter/path)
collection設定            v
(nested)            flatData生成 -> ツリー構築
        |                |
        +---------> TreeNode再帰描画
                         |
filterTerm               v
(URL param)         自動展開処理
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| NestedCollection.js | `packages/decap-cms-core/src/components/Collection/NestedCollection.js` | ソース | ネストコレクションのツリー表示コンポーネント |
| Sidebar.js | `packages/decap-cms-core/src/components/Collection/Sidebar.js` | ソース | サイドバーからNestedCollectionを呼び出す |
| EntriesCollection.js | `packages/decap-cms-core/src/components/Collection/Entries/EntriesCollection.js` | ソース | filterNestedEntriesによるエントリフィルタリング |
| collections.ts | `packages/decap-cms-core/src/reducers/collections.ts` | ソース | selectEntryCollectionTitle関数 |
| entries.ts | `packages/decap-cms-core/src/reducers/entries.ts` | ソース | selectEntries関数 |
