# 機能設計書 49-プレビューテンプレート

## 概要

本ドキュメントは、Decap CMSのプレビューテンプレート機能の設計を記述する。コレクションごとにカスタムのプレビューテンプレート（Reactコンポーネント）を登録・表示できる機能であり、レジストリ（`registry.js`）とプレビューペイン（`EditorPreviewPane.js`）で実現される。

### 本機能の処理概要

**業務上の目的・背景**：CMSでコンテンツを編集する際、実際のサイトでの表示に近いプレビューを確認したいというニーズがある。プレビューテンプレート機能により、コレクションごとにカスタムのReactコンポーネントを登録し、エントリデータを使ったリッチなプレビュー表示を実現できる。テンプレートが登録されていない場合はデフォルトのEditorPreviewコンポーネントが使用される。

**機能の利用シーン**：CMS初期化時に`CMS.registerPreviewTemplate('blog', BlogPreview)`のようにコレクション名とReactコンポーネントを登録する。エントリ編集画面のプレビューペインで、登録されたテンプレートが自動的に使用される。

**主要な処理内容**：
1. `registerPreviewTemplate(name, component)`: コレクション名をキーにReactコンポーネントをレジストリに登録
2. `getPreviewTemplate(name)`: 登録されたテンプレートを取得
3. EditorPreviewPaneでの`selectTemplateName()`によるテンプレート名解決
4. `widgetFor(name)`: テンプレート内でフィールドウィジェットを名前で取得するヘルパー
5. `widgetsFor(name)`: リスト/オブジェクトフィールドのネストウィジェットを取得するヘルパー
6. `getCollection(collectionName, slug)`: 他コレクションのデータを取得するヘルパー
7. iframeベースのプレビュー表示（react-frame-component）

**関連システム・外部連携**：レジストリ（No.75）、プレビュースタイル（No.50）

**権限による制御**：プレビューの有効/無効はコレクション設定の`editor.preview`で制御される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | エントリ編集画面 | 補助機能 | EditorPreviewPaneでのカスタムプレビューテンプレートの表示 |

## 機能種別

UI表示 / プラグインシステム

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| name | string | Yes | コレクション名（テンプレートキー） | - |
| component | React.Component | Yes | プレビュー用Reactコンポーネント | - |

### 入力データソース

- CMS API（`CMS.registerPreviewTemplate()`）: テンプレート登録
- レジストリ（`registry.templates`）: テンプレート取得
- Redux Store: エントリデータ、フィールド定義、メタデータ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| previewComponent | React.Component | プレビュー表示用コンポーネント（カスタムまたはデフォルト） |
| widgetFor | function | フィールドウィジェット取得ヘルパー |
| widgetsFor | function | ネストフィールドウィジェット取得ヘルパー |
| getCollection | function | コレクションデータ取得ヘルパー |

### 出力先

- ブラウザDOM: iframe内のプレビュー表示

## 処理フロー

### 処理シーケンス

```
1. テンプレート登録（初期化時）
   └─ CMS.registerPreviewTemplate(name, component)
   └─ registry.templates[name] = component
2. テンプレート解決（プレビュー表示時）
   └─ selectTemplateName(collection, slug) でテンプレート名を決定
   └─ getPreviewTemplate(name) で登録済みテンプレートを取得
   └─ 未登録の場合はEditorPreview（デフォルト）を使用
3. プレビュー描画
   └─ PreviewPaneFrame（iframe）内にprops付きでコンポーネントをレンダリング
   └─ widgetFor/widgetsFor/getCollectionをpropsとして渡す
   └─ プレビュースタイル（styleEls）をhead要素に挿入
4. Visual Editing対応
   └─ collection.editor.visualEditingが有効な場合
   └─ encodeEntry()でステガノグラフィデータを埋め込み
```

### フローチャート

```mermaid
flowchart TD
    A[EditorPreviewPane.render] --> B[selectTemplateName]
    B --> C[getPreviewTemplate]
    C --> D{テンプレート登録済み?}
    D -->|Yes| E[登録済みコンポーネント使用]
    D -->|No| F[EditorPreview デフォルト使用]
    E --> G[inferFields - フィールド推論]
    F --> G
    G --> H{visualEditing有効?}
    H -->|Yes| I[encodeEntry - ステガノグラフィ埋め込み]
    H -->|No| J[通常のエントリデータ]
    I --> K[PreviewPaneFrame iframe]
    J --> K
    K --> L[EditorPreviewContent]
    L --> M[previewComponent描画]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-49-01 | テンプレート名解決 | selectTemplateNameでコレクション名をキーにテンプレートを解決。ファイルコレクションはファイルのnameも考慮 | プレビュー表示時 |
| BR-49-02 | デフォルトフォールバック | 登録テンプレートが見つからない場合はEditorPreviewをデフォルト使用 | getPreviewTemplate結果がfalsy時 |
| BR-49-03 | フィールド推論 | title/shortTitle/authorフィールドを自動推論し、デフォルトプレビューに適用 | inferFields() |
| BR-49-04 | widgetForのnull安全性 | valueがnullの場合はnullを返す | widgetFor() |
| BR-49-05 | リスト/オブジェクト対応 | widgetsForはList/Object両方のネスト構造に対応。Variable Type Listにも対応 | widgetsFor() |
| BR-49-06 | iframe分離 | プレビューはiframe内で描画され、メインDOMから分離される | PreviewPaneFrame |
| BR-49-07 | Visual Editing | visualEditing有効時はエントリデータにステガノグラフィ情報を埋め込む | encodeEntry() |

### 計算ロジック

特筆すべき計算ロジックなし。

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

本機能はデータベースを操作しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ErrorBoundary | プレビューコンポーネントのレンダリングエラー | ErrorBoundaryコンポーネントでキャッチ |

### リトライ仕様

リトライは不要。

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

フロントエンドコンポーネントのため、トランザクション制御は不要。

## パフォーマンス要件

- PreviewHOCでconditional updatesを提供し、不要なプレビュー再描画を抑制
- getCollectionは非同期処理でコレクションデータを取得

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

- iframeでプレビューをメインDOMから分離
- iframe内の`<base target="_blank"/>`でリンクを新しいタブで開く

## 備考

- `registry.templates`はコレクション名をキーとするプレーンオブジェクト
- テンプレートコンポーネントには`entry`, `widgetFor`, `widgetsFor`, `getCollection`, `document`, `window`がpropsとして渡される
- PreviewHOCはReact.Componentを継承し、shouldComponentUpdateでパフォーマンス最適化を行う

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | registry.js | `packages/decap-cms-core/src/lib/registry.js` | registryオブジェクトのtemplatesプロパティ（25行目） |

**読解のコツ**: `registry.templates`は`{ [collectionName: string]: React.Component }`形式のプレーンオブジェクト。`registerPreviewTemplate`で追加し、`getPreviewTemplate`で取得する。

#### Step 2: テンプレート登録/取得API

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | registry.js | `packages/decap-cms-core/src/lib/registry.js` | registerPreviewTemplate（84-86行目）、getPreviewTemplate（87-89行目） |

**主要処理フロー**:
- **84-86行目**: `registerPreviewTemplate(name, component)` - `registry.templates[name] = component`でシンプルに登録
- **87-89行目**: `getPreviewTemplate(name)` - `registry.templates[name]`を返却

#### Step 3: プレビューペインでのテンプレート使用

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | EditorPreviewPane.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js` | renderメソッド（238-301行目）でのテンプレート解決と描画 |

**主要処理フロー**:
- **245-246行目**: テンプレートコンポーネント解決。`getPreviewTemplate(selectTemplateName())` || EditorPreview
- **248行目**: `inferFields()` - title/author等のフィールド推論
- **250-255行目**: Visual Editing対応。`encodeEntry()`でステガノグラフィデータ埋め込み
- **266-271行目**: プレビュースタイルをstyleEl/linkElとして構築
- **286-299行目**: PreviewPaneFrame（iframe）内にEditorPreviewContentを描画

#### Step 4: widgetFor/widgetsForヘルパー

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | EditorPreviewPane.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js` | widgetFor（83-132行目）、widgetsFor（168-219行目）、getCollection（225-236行目） |

**主要処理フロー**:
- **83-132行目**: `widgetFor()` - フィールド名からウィジェットコンポーネントを取得。ネストフィールド対応
- **168-219行目**: `widgetsFor()` - リスト/オブジェクト/Variable Typeのネストウィジェットを取得
- **225-236行目**: `getCollection()` - 他コレクションのエントリデータを取得

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

```
CMS.registerPreviewTemplate(name, component)
    |
    +-- registry.templates[name] = component

EditorPreviewPane.render()
    |
    +-- getPreviewTemplate(selectTemplateName(collection, slug))
    |       +-- registry.templates[name] || EditorPreview
    |
    +-- inferFields()
    |       +-- selectInferredField(collection, 'title')
    |       +-- selectInferredField(collection, 'author')
    |
    +-- [visualEditing] encodeEntry(entry.data, fields)
    |
    +-- getPreviewStyles() [スタイル取得]
    |
    +-- PreviewPaneFrame (iframe)
            +-- EditorPreviewContent
                +-- previewComponent
                    +-- widgetFor(name) [テンプレート内で使用]
                    +-- widgetsFor(name) [テンプレート内で使用]
                    +-- getCollection(name, slug) [テンプレート内で使用]
```

### データフロー図

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

テンプレート登録   ──▶  registerPreviewTemplate     ──▶  registry.templates
(name, component)        registry格納                      (Map<string, Component>)

エントリデータ     ──▶  EditorPreviewPane           ──▶  iframe内プレビュー
(entry, fields)          テンプレート解決 + props構築      (カスタムUI表示)

プレビュースタイル ──▶  getPreviewStyles()          ──▶  iframe head
(CSS/URLリスト)          styleEl/linkEl構築                (スタイル適用)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| registry.js | `packages/decap-cms-core/src/lib/registry.js` | ソース | テンプレート登録/取得API |
| EditorPreviewPane.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js` | ソース | プレビューペイン（テンプレート使用） |
| EditorPreview.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreview.js` | ソース | デフォルトプレビューコンポーネント |
| EditorPreviewContent.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewContent.js` | ソース | プレビューコンテンツラッパー |
| PreviewHOC.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/PreviewHOC.js` | ソース | プレビュー最適化HOC |
| stega.ts | `packages/decap-cms-core/src/lib/stega.ts` | ソース | ステガノグラフィエンコーディング |
