# 機能設計書 11-エントリ複製

## 概要

本ドキュメントは、Decap CMSにおけるエントリ複製機能の設計を記述する。既存エントリを基に新規ドラフトを作成する処理の仕様、データフロー、関連コンポーネントを網羅する。

### 本機能の処理概要

エントリ複製機能は、既存のエントリ（コンテンツ）のデータを元に、新規のドラフトエントリを作成する機能である。複製されたエントリはスラッグやパスが空の状態で作成され、`newRecord`として扱われる。

**業務上の目的・背景**：CMSでのコンテンツ作成において、類似したコンテンツを効率的に作成するために、既存エントリを雛形として再利用するニーズがある。手動で全フィールドを再入力するのは非効率であり、複製機能によりコンテンツ作成の生産性を向上させる。

**機能の利用シーン**：エディタ画面で既存のエントリを編集中に、ツールバーの複製ボタンを押下することで利用する。記事テンプレートとして既存の記事構造を再利用したい場合や、シリーズ記事の作成時に活用される。

**主要な処理内容**：
1. 既存エントリのデータ（data）、i18nデータ、メディアファイル情報を抽出
2. スラッグとパスを空にした新規EntryValueオブジェクトを`createEntry`で生成
3. `waitUntil`ミドルウェアを使って、空ドラフト作成（`DRAFT_CREATE_EMPTY`）完了後に複製データを適用
4. リデューサーが`DRAFT_CREATE_DUPLICATE_FROM_ENTRY`アクションを処理し、新規レコードとしてドラフト状態を設定

**関連システム・外部連携**：バックエンドAPI（Git系バックエンド）への直接的な通信は複製時には発生しない。複製はクライアントサイドのRedux状態操作のみで完結する。保存時に初めてバックエンドとの通信が発生する。

**権限による制御**：エントリの作成権限を持つユーザーのみが複製可能。Open Authoringモードでは、フォーク先への書き込み権限が必要となる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | エントリ編集画面 | 主画面 | 既存エントリを基にした複製処理。createDraftDuplicateFromEntryアクションにより新規ドラフトを作成 |

## 機能種別

CRUD操作（Create） - 既存エントリデータを基にした新規ドラフト作成

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| entry | EntryMap (Immutable.Map) | Yes | 複製元のエントリオブジェクト | エントリが存在し、data、i18n、mediaFilesプロパティを持つこと |

### 入力データソース

画面上で現在編集中のエントリ（Redux状態の`entryDraft.entry`）。エントリ編集画面のツールバーから取得される。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| collection | string | 複製元と同じコレクション名 |
| slug | string | 空文字列（新規エントリのため） |
| path | string | 空文字列（新規エントリのため） |
| data | object | 複製元エントリのデータ（フィールド値のコピー） |
| i18n | object | 複製元エントリのi18nデータ（多言語フィールドのコピー） |
| mediaFiles | MediaFile[] | 複製元エントリのメディアファイル情報（JS配列に変換） |
| newRecord | boolean | true（新規レコードとして扱う） |
| hasChanged | boolean | true（変更ありとして扱う） |

### 出力先

Redux Store（`entryDraft`ステート）。エディタ画面のコントロールペインに反映される。

## 処理フロー

### 処理シーケンス

```
1. ユーザーがエディタツールバーの「複製」ボタンを押下
   └─ EditorToolbarからcreateDraftDuplicateFromEntryアクションをディスパッチ
2. createDraftDuplicateFromEntry thunkアクションが実行される
   └─ waitUntilミドルウェアを使い、DRAFT_CREATE_EMPTYアクションを待機条件として設定
3. エディタ画面が新規エントリ作成URLへ遷移
   └─ createEmptyDraftが呼ばれ、空ドラフトが作成される
4. DRAFT_CREATE_EMPTYアクションがディスパッチされる
   └─ waitUntilの条件が満たされ、draftDuplicateEntryが実行される
5. draftDuplicateEntry関数が実行される
   └─ createEntryで複製元データを含む新規EntryValueを生成
6. DRAFT_CREATE_DUPLICATE_FROM_ENTRYアクションがディスパッチされる
   └─ entryDraftリデューサーが新規レコードとしてドラフト状態を設定
```

### フローチャート

```mermaid
flowchart TD
    A[ユーザーが複製ボタン押下] --> B[createDraftDuplicateFromEntry dispatch]
    B --> C[waitUntilでDRAFT_CREATE_EMPTY待機を登録]
    C --> D[新規エントリ作成画面へ遷移]
    D --> E[createEmptyDraft実行]
    E --> F[DRAFT_CREATE_EMPTY dispatch]
    F --> G[waitUntil条件成立]
    G --> H[draftDuplicateEntry実行]
    H --> I[createEntryで複製EntryValue生成]
    I --> J[DRAFT_CREATE_DUPLICATE_FROM_ENTRY dispatch]
    J --> K[entryDraftリデューサーが状態更新]
    K --> L[エディタ画面に複製データが表示]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-11-01 | スラッグ初期化 | 複製エントリのスラッグは空文字列にリセットされる | 常時 |
| BR-11-02 | パス初期化 | 複製エントリのパスは空文字列にリセットされる | 常時 |
| BR-11-03 | 新規レコードフラグ | 複製されたエントリはnewRecord=trueとして扱われる | 常時 |
| BR-11-04 | 変更フラグ | 複製後のドラフトはhasChanged=trueに設定される | 常時 |
| BR-11-05 | データコピー | data、i18n、mediaFilesが複製元からコピーされる | 常時 |
| BR-11-06 | メディアファイル変換 | mediaFilesはImmutable.ListからJSの配列に変換される | 常時 |

### 計算ロジック

特になし。データのコピーと初期化のみで、計算処理は含まれない。

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

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

本機能ではデータベース操作は発生しない。Redux Storeへの状態更新のみである。複製後にエントリを保存する場合は、エントリ保存（永続化）機能（No.9）またはワークフロー保存機能（No.18）の仕様に従う。

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

該当なし（クライアントサイド操作のみ）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 実行時エラー | 複製元エントリのmediaFilesがtoJS()できない場合 | Immutable.Listでない場合にエラーが発生する可能性がある。正常なエントリであれば発生しない |
| - | タイムアウト | waitUntilの待機条件が30秒以内に満たされない場合 | コンソールに警告を出力し、複製が実行されない |

### リトライ仕様

リトライは不要。エラー発生時はユーザーが再度複製操作を実行する。

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

クライアントサイドのRedux状態操作のため、従来のデータベーストランザクションは適用されない。Reduxのディスパッチは同期的に処理され、状態の一貫性はImmutable.jsにより保証される。

## パフォーマンス要件

- クライアントサイド操作のため、ネットワーク通信は発生しない
- 処理時間はエントリのデータサイズに依存するが、通常のコンテンツサイズであれば即時完了する
- メディアファイルの実体はコピーされず、参照のみがコピーされるため、大容量メディアがあっても影響は軽微

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

- 複製元エントリの閲覧・作成権限を持つユーザーのみが操作可能
- 複製時にはバックエンドAPIへの通信は発生しないため、認証トークンの検証は保存時に行われる
- 複製されたエントリに含まれるメディアファイルのURL情報は、元エントリと同一の参照を持つ

## 備考

- `waitUntil`ミドルウェアを使った非同期的な2段階処理パターンは、Decap CMS特有の実装パターンである。まず空のドラフトを作成し、そのアクションの完了を検知してからデータを上書きするという流れになっている。
- 複製機能はファイルベースコレクションでは利用できない場合がある（UIレベルの制御）。

---

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

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

### 推奨読解順序

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

まず、エントリの値オブジェクトとReduxの型定義を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Entry.ts | `packages/decap-cms-core/src/valueObjects/Entry.ts` | EntryValueインターフェース（23-42行目）とcreateEntry関数（44-63行目）の構造を理解する |

**読解のコツ**: `createEntry`関数は、collection名・slug・pathとオプショナルなプロパティを受け取り、EntryValueオブジェクトを生成するファクトリ関数である。複製時にはslugとpathが空文字列になる点に注目。

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

複製処理の起点となるアクションクリエータを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | entries.ts | `packages/decap-cms-core/src/actions/entries.ts` | createDraftDuplicateFromEntry関数（472-481行目）がthunkアクションとして複製のエントリーポイント |

**主要処理フロー**:
1. **472-481行目**: `createDraftDuplicateFromEntry` - waitUntilを使いDRAFT_CREATE_EMPTYを待機条件として設定し、条件成立後にdraftDuplicateEntryを実行
2. **387-396行目**: `draftDuplicateEntry` - createEntryで複製元エントリのdata、i18n、mediaFilesをコピーした新規EntryValueを生成し、DRAFT_CREATE_DUPLICATE_FROM_ENTRYアクションをディスパッチ

#### Step 3: waitUntilミドルウェアを理解する

複製処理が2段階で実行される仕組みを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | waitUntil.ts | `packages/decap-cms-core/src/actions/waitUntil.ts` | waitUntil関数（8-14行目）がWAIT_UNTIL_ACTIONを返し、ミドルウェアが条件判定を行う |

**主要処理フロー**:
- **8-14行目**: predicateが`DRAFT_CREATE_EMPTY`アクションのタイプを検知し、run関数としてdraftDuplicateEntryのディスパッチが実行される

#### Step 4: リデューサーの状態更新を理解する

複製アクションがどのようにRedux状態を変更するかを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | entryDraft.js | `packages/decap-cms-core/src/reducers/entryDraft.js` | DRAFT_CREATE_DUPLICATE_FROM_ENTRYケース（80-89行目）でのドラフト状態の初期化処理 |

**主要処理フロー**:
- **80-89行目**: `DRAFT_CREATE_DUPLICATE_FROM_ENTRY`ケース - entryをfromJS(action.payload)で設定、newRecord=true、hasChanged=true、fieldsMetaData/fieldsErrorsをリセット

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

```
EditorToolbar（UIコンポーネント）
    │
    ├─ createDraftDuplicateFromEntry(entry)  [actions/entries.ts:472-481]
    │      │
    │      └─ waitUntil({ predicate, run })  [actions/waitUntil.ts:8-14]
    │             │
    │             ├─ predicate: type === DRAFT_CREATE_EMPTY を待機
    │             │
    │             └─ run: draftDuplicateEntry(entry)  [actions/entries.ts:387-396]
    │                    │
    │                    └─ createEntry(collection, '', '', { data, i18n, mediaFiles })
    │                           [valueObjects/Entry.ts:44-63]
    │
    └─ (新規エントリ画面へ遷移)
           │
           └─ createEmptyDraft(collection, search)  [actions/entries.ts:740-776]
                  │
                  └─ emptyDraftCreated(newEntry)  → DRAFT_CREATE_EMPTY dispatch
                         │
                         └─ waitUntil条件成立 → draftDuplicateEntry実行
                                │
                                └─ DRAFT_CREATE_DUPLICATE_FROM_ENTRY dispatch
                                       │
                                       └─ entryDraftReducer  [reducers/entryDraft.js:80-89]
```

### データフロー図

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

既存エントリ(EntryMap)  ───▶  createDraftDuplicateFromEntry  ───▶  Redux entryDraft状態
  ├─ data                       │                                    ├─ entry
  ├─ i18n                       ├─ waitUntilで遅延実行登録            │    ├─ data (コピー)
  ├─ mediaFiles                 ├─ draftDuplicateEntry               │    ├─ i18n (コピー)
  └─ collection                 │   └─ createEntry                   │    ├─ mediaFiles (コピー)
                                │       (slug='', path='')           │    ├─ slug = ''
                                └─ entryDraftReducer                 │    ├─ path = ''
                                    (状態更新)                        │    └─ newRecord = true
                                                                     └─ hasChanged = true
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| entries.ts | `packages/decap-cms-core/src/actions/entries.ts` | ソース | 複製アクションクリエータ（createDraftDuplicateFromEntry, draftDuplicateEntry） |
| Entry.ts | `packages/decap-cms-core/src/valueObjects/Entry.ts` | ソース | EntryValue型定義とcreateEntryファクトリ関数 |
| entryDraft.js | `packages/decap-cms-core/src/reducers/entryDraft.js` | ソース | ドラフト状態のリデューサー（DRAFT_CREATE_DUPLICATE_FROM_ENTRYケース） |
| waitUntil.ts | `packages/decap-cms-core/src/actions/waitUntil.ts` | ソース | waitUntilアクションクリエータ（非同期条件実行） |
| EditorToolbar.js | `packages/decap-cms-core/src/components/Editor/EditorToolbar.js` | ソース | 複製ボタンのUIコンポーネント |
