# 機能設計書 8-エントリ作成・編集

## 概要

本ドキュメントは、Decap CMSのエントリ作成・編集機能の設計を記載する。新規エントリの空ドラフト作成、既存エントリからのドラフト作成、フィールド値の変更管理を行う。

### 本機能の処理概要

本機能は、CMSコンテンツの新規作成と編集のためのドラフト管理を行う。新規エントリの場合はコレクション定義に基づいた空のドラフトを生成し、既存エントリの場合はバックエンドから取得したデータをドラフトに変換する。編集中のフィールド変更はReduxストアのentryDraft状態で管理される。

**業務上の目的・背景**：CMSのコンテンツ編集において、ドラフト（下書き）状態の管理は必須である。ユーザーが編集中のデータはまだ保存されていない状態であり、フィールドごとの変更追跡、バリデーション、i18nデータの管理が必要となる。

**機能の利用シーン**：新規エントリ作成画面でのフォーム初期化、エントリ編集画面でのデータ読み込み後のドラフト作成、フィールド値変更時のリアルタイム状態更新、URLクエリパラメータによるデフォルト値の設定。

**主要な処理内容**：
1. `createEmptyDraft`: コレクション定義のフィールドからデフォルト値を持つ空ドラフトを生成
2. `createDraftFromEntry`: 既存エントリデータからドラフトを作成
3. `changeDraftField`: フィールド値の変更をReduxストアに反映
4. `changeDraftFieldValidation`: バリデーションエラーの管理
5. `discardDraft`: ドラフトの破棄
6. `createEmptyDraftData`: フィールド定義に基づくデフォルトデータの再帰的生成
7. URLクエリパラメータからのデフォルト値設定

**関連システム・外部連携**：バックエンドのprocessEntry（エントリの前処理）。

**権限による制御**：認証済みユーザーのみがドラフトを作成・編集できる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | コレクション一覧画面 | 遷移元 | 新規エントリ作成ボタン押下時にnewEntryUrlへ遷移し、エントリ新規作成画面を開く |
| 6 | エントリ編集画面 | 主画面 | 既存エントリからのドラフト作成とフィールド値の変更管理 |
| 7 | エントリ新規作成画面 | 主画面 | 空のドラフト作成処理。createEmptyDraftアクションにより新規エントリの空フォームを生成 |
| 8 | ワークフロー画面 | 遷移元 | ワークフロー画面のNew Postドロップダウンからのコレクション選択による新規エントリ作成画面への遷移 |

## 機能種別

CRUD操作（Create / Update） / 状態管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| collection | Collection | Yes | 対象コレクション | - |
| search | string | No（createEmptyDraft） | URLクエリパラメータ文字列 | - |
| entry | EntryValue | Yes（createDraftFromEntry） | 既存エントリデータ | - |
| field | EntryField | Yes（changeDraftField） | 変更対象フィールド | - |
| value | string | Yes（changeDraftField） | 新しいフィールド値 | - |
| metadata | Record<string, unknown> | Yes（changeDraftField） | メタデータ | - |
| i18n | object | No（changeDraftField） | i18nコンテキスト（currentLocale, defaultLocale, locales） | - |

### 入力データソース

- コレクション定義（fields, defaults）
- URLクエリパラメータ
- ユーザーのフォーム入力
- バックエンドから取得した既存エントリデータ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| entryDraft.entry | EntryMap | ドラフトエントリデータ |
| entryDraft.fieldsErrors | Map | フィールドごとのバリデーションエラー |

### 出力先

- Reduxストア（entryDraft状態スライス）

## 処理フロー

### 処理シーケンス

```
[createEmptyDraft]
1. URLクエリパラメータの解析
   └─ 各パラメータをフィールドのデフォルト値として設定（processValue）
2. コレクションのfields取得
3. データフィールドとメタフィールドの分離
4. createEmptyDraftData(dataFields)で空のドラフトデータ生成
   └─ フィールドのdefault値を再帰的に展開
   └─ リストウィジェット: [subDefaults] の形式
   └─ オブジェクトウィジェット: {subDefaults} の形式
5. createEmptyDraftI18nData()でi18nデータ生成
6. createEntry()でEntryValueを生成
7. backend.processEntry()でバックエンド固有の前処理
8. DRAFT_CREATE_EMPTYをディスパッチ

[changeDraftField]
1. DRAFT_CHANGE_FIELDをディスパッチ
   └─ field, value, metadata, entries, i18n情報を含む
```

### フローチャート

```mermaid
flowchart TD
    A[エントリ作成・編集開始] --> B{新規 or 既存?}
    B -->|新規| C[createEmptyDraft]
    B -->|既存| D[createDraftFromEntry]
    C --> E[URLパラメータ解析]
    E --> F[フィールドデフォルト値設定]
    F --> G[createEmptyDraftData]
    G --> H{サブフィールド<br/>あり?}
    H -->|Yes| I[再帰的にデータ生成]
    H -->|No| J[default値を設定]
    I --> K[i18nデータ生成]
    J --> K
    K --> L[createEntry]
    L --> M[backend.processEntry]
    M --> N[DRAFT_CREATE_EMPTY dispatch]
    D --> O[DRAFT_CREATE_FROM_ENTRY dispatch]
    N --> P[フィールド編集]
    O --> P
    P --> Q[changeDraftField]
    Q --> R[DRAFT_CHANGE_FIELD dispatch]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | デフォルト値展開 | フィールドのdefaultプロパティが指定されている場合、その値を初期値として使用 | createEmptyDraft |
| BR-02 | URLパラメータオーバーライド | URLのクエリパラメータで指定されたキーのフィールドデフォルト値を上書き | createEmptyDraft |
| BR-03 | 真偽値変換 | URLパラメータの'true'/'True'/'TRUE'はboolean trueに、'false'/'False'/'FALSE'はfalseに変換 | URLパラメータ処理時 |
| BR-04 | HTMLエスケープ | URLパラメータの値は`&<>"'`がHTMLエスケープされる | URLパラメータ処理時 |
| BR-05 | リストフィールドの空デフォルト | リストウィジェットのサブフィールドがある場合、デフォルト値は[{subDefaults}]形式 | createEmptyDraftData |
| BR-06 | 空オブジェクト検出 | `[{}]`や`{}`のような空デフォルト値はスキップされる | createEmptyDraftData |
| BR-07 | i18nフィールドスキップ | i18nが有効な場合、DUPLICATE/TRANSLATEではないフィールドはi18nデータから除外 | createEmptyDraftI18nData |
| BR-08 | メディアライブラリ待機 | コレクションにmedia_folderが未設定の場合、メディアライブラリの読み込み完了を待機 | createEmptyDraft |

### 計算ロジック

processValue: `true/True/TRUE` -> `true`, `false/False/FALSE` -> `false`, その他 -> HTMLエスケープ文字列

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

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

| 操作 | 対象ストア | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 空ドラフト作成 | entryDraft | UPDATE | DRAFT_CREATE_EMPTY: 空のドラフトを設定 |
| 既存からドラフト | entryDraft | UPDATE | DRAFT_CREATE_FROM_ENTRY: エントリデータからドラフト作成 |
| フィールド変更 | entryDraft | UPDATE | DRAFT_CHANGE_FIELD: フィールド値の更新 |
| バリデーション | entryDraft | UPDATE | DRAFT_VALIDATION_ERRORS: エラー情報の設定 |
| エラークリア | entryDraft | UPDATE | DRAFT_CLEAR_ERRORS: 特定フィールドのエラークリア |
| ドラフト破棄 | entryDraft | UPDATE | DRAFT_DISCARD: ドラフト状態のリセット |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | バックエンドエラー | backend.processEntry()がエラーを返す | 例外がスローされる |

### リトライ仕様

ドラフト操作にリトライ機構はない。

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

クライアントサイド処理のためトランザクション管理は不要。entryDraft状態の更新はRedux dispatchで行われる。

## パフォーマンス要件

- createEmptyDraftDataは再帰的に動作するため、深くネストされたフィールド構造ではパフォーマンスに影響がある可能性
- changeDraftFieldはフィールド変更のたびにディスパッチされるため、頻繁なキー入力に対応できる必要がある

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

- URLクエリパラメータの値はHTMLエスケープされる（escapeHtml関数）
- ドラフトデータはReduxストア（インメモリ）に保持され、明示的に保存操作を行わない限りバックエンドには送信されない

## 備考

- waitUntilミドルウェアを使用してDRAFT_CREATE_EMPTYアクションの完了を待ってからdraftDuplicateEntryを実行する仕組みがある（createDraftDuplicateFromEntry）
- processValueでの真偽値判定は大文字小文字の各パターンに対応
- getDataFieldsとgetMetaFieldsでフィールドをデータフィールドとメタフィールドに分離

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Entry.ts | `packages/decap-cms-core/src/valueObjects/Entry.ts` | EntryValue型、createEntry関数 |
| 1-2 | types/redux.ts | `packages/decap-cms-core/src/types/redux.ts` | EntryField, EntryFields型 |

**読解のコツ**: DraftEntryData interfaceが再帰的な構造を持つ。リストやオブジェクトのネストに対応するため、string | DraftEntryData | DraftEntryData[]のunion型となっている。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | entries.ts (actions) | `packages/decap-cms-core/src/actions/entries.ts` | ドラフト関連のアクションクリエーターとthunkアクション |

**主要処理フロー**:
1. **66-74行目**: ドラフト関連アクションタイプ定数
2. **371-376行目**: emptyDraftCreated - 空ドラフト作成完了
3. **380-385行目**: createDraftFromEntry - 既存エントリからドラフト作成
4. **387-396行目**: draftDuplicateEntry - エントリ複製
5. **398-400行目**: discardDraft - ドラフト破棄
6. **402-423行目**: changeDraftField - フィールド変更
7. **425-433行目**: changeDraftFieldValidation - バリデーションエラー設定
8. **740-776行目**: createEmptyDraft - 空ドラフト作成（メイン処理）
9. **789-842行目**: createEmptyDraftData - デフォルトデータの再帰的生成
10. **712-730行目**: escapeHtml, processValue - HTMLエスケープと値変換

#### Step 3: i18nドラフトデータを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | entries.ts (actions) | `packages/decap-cms-core/src/actions/entries.ts` | createEmptyDraftI18nData |

**主要処理フロー**:
- **844-855行目**: createEmptyDraftI18nData - i18n用フィールドスキップ条件とduplicateDefaultI18nFields呼び出し

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

```
createEmptyDraft(collection, search)
    |
    +-- URLSearchParams(search).forEach()
    |       +-- updateFieldByKey(collection, key, ...)
    |       +-- processValue(value)
    +-- getDataFields(fields)
    +-- getMetaFields(fields)
    +-- createEmptyDraftData(dataFields)
    |       +-- [再帰] createEmptyDraftData(subfields)
    +-- createEmptyDraftI18nData(collection, dataFields)
    |       +-- createEmptyDraftData(dataFields, skipField)
    |       +-- duplicateDefaultI18nFields(collection, data)
    +-- createEntry(name, '', '', {data, i18n, mediaFiles, meta})
    +-- backend.processEntry(state, collection, entry)
    +-- emptyDraftCreated(entry)  [dispatch]

changeDraftField({field, value, metadata, entries, i18n})
    +-- DRAFT_CHANGE_FIELD  [dispatch]
```

### データフロー図

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

コレクション定義 ----------> createEmptyDraft() ------> Reduxストア(entryDraft)
URLパラメータ

既存エントリデータ --------> createDraftFromEntry() ---> Reduxストア(entryDraft)

ユーザー入力 ------------> changeDraftField() -------> Reduxストア(entryDraft.entry.data)
                                                        Reduxストア(entryDraft.entry.i18n)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| entries.ts (actions) | `packages/decap-cms-core/src/actions/entries.ts` | ソース | ドラフト作成・変更アクション |
| entryDraft.ts (reducer) | `packages/decap-cms-core/src/reducers/entryDraft.ts` | ソース | ドラフト状態のリデューサー |
| Entry.ts | `packages/decap-cms-core/src/valueObjects/Entry.ts` | ソース | createEntry関数 |
| collections.ts (reducer) | `packages/decap-cms-core/src/reducers/collections.ts` | ソース | updateFieldByKey, selectFields |
| i18n.ts | `packages/decap-cms-core/src/lib/i18n.ts` | ソース | hasI18n, duplicateDefaultI18nFields, I18N_FIELD |
| backend.ts | `packages/decap-cms-core/src/backend.ts` | ソース | processEntry |
