# 帳票設計書 8-エディタプレビュー（HTMLレンダリング）

## 概要

本ドキュメントは、Decap CMSのエディタ画面におけるコンテンツエントリのリアルタイムHTMLプレビュー表示機能の設計を記述する。iframeベースのPreviewPaneFrameコンポーネントで、カスタムプレビューテンプレートまたはデフォルトのEditorPreviewコンポーネントを使用してレンダリングする仕組みを定義する。

### 本帳票の処理概要

本帳票は、CMSのエディタ画面でコンテンツを編集中に、右側ペインにリアルタイムでHTML プレビューを表示する機能を定義する。

**業務上の目的・背景**：コンテンツ編集者が入力したデータがどのように最終表示されるかを即座に確認できることは、CMSの使い勝手において極めて重要である。本機能により、Markdownの記法やフィールドの値がどのようにレンダリングされるかをリアルタイムで確認でき、編集作業の効率と品質を向上させる。カスタムプレビューテンプレートにより、サイト固有のデザインでプレビュー表示することも可能である。

**帳票の利用シーン**：エディタ画面でコンテンツを編集中に、エディタ右側のプレビューペインに自動的に表示される。フィールドの値を変更するたびにプレビューがリアルタイムで更新される。registerPreviewTemplateでカスタムテンプレートが登録されている場合はそれが使用され、未登録の場合はデフォルトのEditorPreviewコンポーネントが使用される。

**主要な出力内容**：
1. iframeベースのプレビューフレーム（PreviewPaneFrame）
2. 各フィールドウィジェットのプレビューレンダリング（widgetFor/widgetsFor）
3. 推論フィールド（title、shortTitle、author）のデフォルトプレビュー
4. カスタムプレビュースタイル（registerPreviewStyle経由）
5. ビジュアルエディティング対応（vercel/stega）

**帳票の出力タイミング**：エディタ画面表示時およびフィールド値変更時にリアルタイムで更新される。

**帳票の利用者**：コンテンツ編集者（CMS利用者）

## 帳票種別

画面表示（HTML リアルタイムプレビュー）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | エディタ画面 | /collections/{collection}/entries/{slug} | エディタ右側ペイン（自動表示） |
| - | 新規エントリ作成画面 | /collections/{collection}/new | エディタ右側ペイン（自動表示） |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | HTML（iframe内レンダリング） |
| 用紙サイズ | N/A（ブラウザ画面内表示） |
| 向き | N/A |
| ファイル名 | N/A（画面表示） |
| 出力方法 | ブラウザ上のiframe内にReactコンポーネントとしてレンダリング |
| 文字コード | UTF-8 |

### PDF固有設定

N/A

### Excel固有設定

N/A

## 帳票レイアウト

### レイアウト概要

iframeベースのプレビューフレーム内に、各フィールドのプレビューウィジェットが縦方向に配置される。

```
┌─────────────────────────────────────┐
│ PreviewPaneFrame (iframe)            │
│ ┌─────────────────────────────────┐ │
│ │ <style> / <link> (プレビュー用)  │ │
│ ├─────────────────────────────────┤ │
│ │ ErrorBoundary                    │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ EditorPreviewContent         │ │ │
│ │ │ ┌─────────────────────────┐ │ │ │
│ │ │ │ PreviewComponent         │ │ │ │
│ │ │ │   field1プレビュー       │ │ │ │
│ │ │ │   field2プレビュー       │ │ │ │
│ │ │ │   ...                   │ │ │ │
│ │ │ └─────────────────────────┘ │ │ │
│ │ └─────────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | プレビュースタイル | 登録済みのCSSスタイル/外部スタイルシート | registry.previewStyles | `<style>`タグまたは`<link>`タグ |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | フィールドプレビュー | 各フィールドのウィジェットプレビュー | entry.data[fieldName] | ウィジェット固有のHTML | N/A |
| 2 | 推論フィールド | title/shortTitle/authorの推論プレビュー | INFERABLE_FIELDS設定 | デフォルトプレビュー関数の出力 | N/A |
| 3 | ラベル付き値 | 50文字未満の短いstring/text/number値 | entry.data[fieldName] | `<strong>{label}:</strong> {value}` | N/A |

### フッター部

N/A

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| エントリデータ | entry.get('data')が存在すること | Yes |
| コレクション定義 | collectionオブジェクトが存在すること | Yes |
| フィールド定義 | fields（ImmutableList）が存在すること | Yes |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | collection.fieldsの定義順 | 定義順（先頭から順にレンダリング） |

### 改ページ条件

N/A（スクロールベースの画面表示）

## データベース参照仕様

### 参照テーブル一覧

N/A（Redux storeからデータ取得）

### テーブル別参照項目詳細

N/A

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| テンプレート選択 | `getPreviewTemplate(selectTemplateName(collection, slug)) \|\| EditorPreview` | N/A | カスタムテンプレート登録済みならそれを使用、なければデフォルト |
| フィールド推論 | `selectInferredField(collection, 'title')` | N/A | title/shortTitle/authorフィールドの推論 |
| ウィジェット解決 | `resolveWidget(field.get('widget'))` | N/A | フィールドのwidget属性からウィジェットコンポーネントを取得 |
| ビジュアルエディティング | `encodeEntry(entry.get('data'), fields)` | N/A | visualEditing有効時にstegaエンコーディングを適用 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[エディタ画面表示] --> B{entryデータ存在?}
    B -->|No| C[null返却]
    B -->|Yes| D[プレビューテンプレート取得]
    D --> E[フィールド推論]
    E --> F{visualEditing有効?}
    F -->|Yes| G[stegaエンコーディング]
    F -->|No| H[通常entry使用]
    G --> I[previewProps構築]
    H --> I
    I --> J[プレビュースタイル取得]
    J --> K[PreviewPaneFrame iframe生成]
    K --> L[EditorPreviewContent レンダリング]
    L --> M[プレビューコンポーネント実行]
    M --> N[各フィールドのwidgetForレンダリング]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| データなし | entry.get('data')がnull/undefinedの場合 | 表示なし（null返却） | エディタにデータを入力 |
| レンダリングエラー | プレビューコンポーネント内でエラーが発生した場合 | ErrorBoundaryによるフォールバック表示 | プレビューテンプレートの実装を確認 |
| ウィジェット未登録 | field.get('widget')に対応するウィジェットが存在しない場合 | resolveWidgetが'unknown'ウィジェットを返却 | ウィジェット名を確認 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 1エントリの全フィールド（数十〜数百フィールド） |
| 目標出力時間 | リアルタイム（Reactの差分レンダリング） |
| 同時出力数上限 | 1件（エディタ画面につき1プレビュー） |

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

- プレビューはiframe内でレンダリングされるため、メインページとの分離が確保される
- iframeの`<base target="_blank"/>`により、プレビュー内のリンクは新しいタブで開かれる
- カスタムプレビューテンプレートは任意のReactコンポーネントを実行可能であるため、信頼性の確認が必要
- ビジュアルエディティング機能はvercel/stegaを使用し、テキスト内に不可視の識別子を埋め込む

## 備考

- PreviewPaneFrameはreact-frame-componentライブラリのFrameコンポーネントをスタイル付きで使用
- PreviewHOCコンポーネントがshouldComponentUpdateを制御し、不要な再レンダリングを防止
- widgetFor関数はネストされたフィールド（object/list型）に対して再帰的にプレビューを生成
- widgetsFor関数は可変型リスト（variable types）にも対応
- getCollection関数でエディタ外のコレクションデータを取得可能（カスタムプレビューテンプレート用）
- ScrollSyncPaneにより、エディタとプレビューのスクロール位置を同期（ビジュアルエディティング無効時）
- ビジュアルエディティング有効時はスクロール同期の代わりにクリックイベントベースのフィールド選択が動作

---

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

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

### 推奨読解順序

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

プレビューに関わるデータ構造とpropsを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | EditorPreviewPane.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js` | PreviewPane.propTypes（304-311行目）で受け取るpropsの構造を確認。collection, fields, entry, fieldsMetaData, getAsset, onFieldClickを理解 |

**読解のコツ**: PreviewPaneはReact ClassコンポーネントでImmutable.jsのMap/Listを多用する。Redux connectでstate/dispatchをpropsにマッピングしている。

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

renderメソッドの処理フローを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | EditorPreviewPane.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js` | renderメソッド（238-301行目）の処理フローを確認。entry/collectionの存在チェック、テンプレート選択、ビジュアルエディティング判定、iframe生成 |

**主要処理フロー**:
1. **241行目**: entry/entry.get('data')の存在チェック - なければnull返却
2. **245-246行目**: `getPreviewTemplate(selectTemplateName(...)) || EditorPreview` でテンプレート選択
3. **248行目**: `this.inferFields()` で推論フィールドを設定
4. **250行目**: `collection.getIn(['editor', 'visualEditing'], false)` でビジュアルエディティング判定
5. **253-255行目**: visualEditing時はencodeEntryでstegaエンコーディング
6. **266-271行目**: プレビュースタイルをstyleタグ/linkタグとして生成
7. **285-299行目**: PreviewPaneFrame内にEditorPreviewContentをレンダリング

#### Step 3: ウィジェットプレビュー生成を理解する

widgetFor/widgetsForの処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | EditorPreviewPane.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js` | widgetForメソッド（83-132行目）でフィールド名からプレビューウィジェットを生成する処理を確認 |
| 3-2 | EditorPreviewPane.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js` | getWidgetメソッド（40-62行目）でPreviewHOCを介してウィジェットのプレビューコンポーネントを返す処理を確認 |

**主要処理フロー**:
- **91行目**: フィールド名からフィールドオブジェクトを検索
- **92行目**: valuesからフィールド値を取得
- **97-107行目**: ネストされたフィールド（fields/field属性）に対して再帰的にウィジェットを生成
- **117-118行目**: 推論フィールドの場合、defaultPreview関数で値を変換
- **119-129行目**: 50文字未満のstring/text/number値の場合、ラベル付き表示

#### Step 4: プレビューコンテンツレンダリングを理解する

EditorPreviewContentとEditorPreviewの処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | EditorPreviewContent.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewContent.js` | ScrollSyncPane/ビジュアルエディティングの切り替え、プレビューコンポーネントのレンダリング |
| 4-2 | EditorPreview.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreview.js` | デフォルトプレビューコンポーネント。hiddenウィジェットをフィルタリングし、各フィールドのwidgetForを呼び出す |

**主要処理フロー**:
- EditorPreviewContent.js **34-43行目**: renderPreview()でpreviewComponentをReact.createElement/cloneElementで生成
- EditorPreviewContent.js **47-48行目**: visualEditing無効時はScrollSyncPane、有効時は直接レンダリング
- EditorPreview.js **26-28行目**: fields.filter(isVisible).map(field => widgetFor(field.get('name')))で全可視フィールドのプレビューを生成

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

```
PreviewPane.render (EditorPreviewPane.js:238)
    |
    +-- getPreviewTemplate / EditorPreview (テンプレート選択)
    +-- inferFields (EditorPreviewPane.js:66)
    +-- encodeEntry (stega.js) [visualEditing時のみ]
    +-- getPreviewStyles (registry.js)
    |
    +-- PreviewPaneFrame (react-frame-component)
            |
            +-- EditorPreviewContent (EditorPreviewContent.js)
                    |
                    +-- previewComponent (カスタムまたはEditorPreview)
                            |
                            +-- widgetFor (EditorPreviewPane.js:83)
                            |       +-- resolveWidget (registry.js)
                            |       +-- getWidget (EditorPreviewPane.js:40)
                            |       |       +-- PreviewHOC
                            |       +-- getNestedWidgets (再帰)
                            |       +-- getSingleNested (再帰)
                            |
                            +-- widgetsFor (EditorPreviewPane.js:168)
```

### データフロー図

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

Redux Store ────────────┐
  |                     |
  +-- entry.data -------+-> PreviewPane.render -------> iframe内
  +-- collection -------+     |                         HTMLプレビュー
  +-- fields -----------+     +-- widgetFor
  +-- fieldsMetaData ---+     |     +-- resolveWidget
                              |     +-- PreviewHOC
                              |
  registry ─────────────+     +-- getPreviewTemplate
  +-- templates --------+     +-- getPreviewStyles
  +-- previewStyles ----+
  +-- widgets ----------+
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| EditorPreviewPane.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js` | ソース | メインプレビューペインコンポーネント（widgetFor/widgetsFor含む） |
| EditorPreviewContent.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewContent.js` | ソース | プレビューコンテンツレンダラー（ScrollSync/ビジュアルエディティング） |
| EditorPreview.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreview.js` | ソース | デフォルトプレビューコンポーネント |
| PreviewHOC.js | `packages/decap-cms-core/src/components/Editor/EditorPreviewPane/PreviewHOC.js` | ソース | プレビューウィジェットのHOC（shouldComponentUpdate制御） |
| registry.js | `packages/decap-cms-core/src/lib/registry.js` | ソース | プレビューテンプレート・スタイル・ウィジェットの登録・取得 |
| stega.js | `packages/decap-cms-core/src/lib/stega.js` | ソース | ビジュアルエディティング用stegaエンコーディング |
