# 機能設計書 64-コードウィジェット

## 概要

本ドキュメントは、Decap CMSのコードウィジェット（Code Widget）の機能設計を定義する。コードウィジェットは、CodeMirrorベースのコードエディタによるシンタックスハイライト付きコード編集機能を提供するUIコンポーネントである。

### 本機能の処理概要

コードウィジェットは、CMSエントリ内でプログラミング言語やマークアップ言語のコードスニペットを編集するための専用エディタを提供する。CodeMirrorライブラリを使用し、シンタックスハイライト、テーマ切替、キーマップ切替、言語モード選択などの豊富な機能を備える。

**業務上の目的・背景**：技術ドキュメントやブログ記事においてコードスニペットを埋め込む需要が高い。プレーンテキストエリアではコードの可読性が低いため、CodeMirrorベースの専用エディタを提供することで、コンテンツ編集者がコードを快適に入力・編集できるようにする。

**機能の利用シーン**：エントリ編集画面でフィールドタイプが`code`に設定されているフィールド、またはMarkdownエディタ内のコードブロックエディタコンポーネントとして利用される。

**主要な処理内容**：
1. CodeMirrorエディタインスタンスの初期化と管理
2. 言語モード選択（100以上の言語対応）
3. テーマ切替（default / material）
4. キーマップ切替（default / vim / sublime / emacs）
5. 設定のlocalStorage永続化（テーマ・キーマップ）
6. Map値（code + lang）またはstring値（codeのみ）の出力切替
7. 設定パネルのUIコンポーネント

**関連システム・外部連携**：CodeMirrorライブラリ（コードエディタエンジン）、react-codemirror2（Reactバインディング）、Markdownエディタコンポーネントとの連携。

**権限による制御**：allow_language_selectionオプションで言語選択の可否を制御可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | エントリ編集画面 | 主画面 | コードフィールドの編集 |
| 7 | エントリ新規作成画面 | 主画面 | コードフィールドの入力 |

## 機能種別

UI入力コンポーネント / コードエディタ

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| field | ImmutableMap | Yes | フィールド定義（default_language, allow_language_selection, output_code_only, keys等） | - |
| value | node / Map | No | コード値（string）またはMap({ code, lang }） | - |
| onChange | function | Yes | 値変更コールバック | - |
| forID | string | Yes | フィールド一意識別子 | - |
| widget | object | Yes | ウィジェットインスタンス（codeMirrorConfig含む） | - |
| isEditorComponent | boolean | No | Markdownエディタコンポーネント内での使用フラグ | - |
| isParentListCollapsed | boolean | No | 親リストの折りたたみ状態 | - |

### 入力データソース

- 画面入力: CodeMirrorエディタでのコード入力
- localStorage: テーマとキーマップの永続化された設定
- languages.json: 言語定義データ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| value | Map / string | output_code_only=falseの場合はMap({ code, lang })、trueの場合はstring（コードのみ） |

### 出力先

- Redux Store: エントリのフィールド値として保存
- localStorage: テーマ・キーマップの設定永続化

## 処理フロー

### 処理シーケンス

```
1. ウィジェット初期化
   └─ localStorageからテーマ・キーマップを復元、getInitialLang()で初期言語を取得
2. CodeMirrorインスタンス生成
   └─ ReactCodeMirrorのeditorDidMountでthis.cmに参照保存
3. コード入力
   └─ onChange → handleChange() → toValue('code', newValue) → props.onChange()
4. 言語変更
   └─ SettingsPane → onChangeLang → setState → handleChangeCodeMirrorProps() → コードミラーモード動的読込
5. テーマ/キーマップ変更
   └─ SettingsPane → onChangeTheme/onChangeKeyMap → setState → localStorage永続化 → CodeMirror再初期化
6. 設定パネル表示/非表示
   └─ SettingsButtonクリックで表示/ESCキーまたはクリックで非表示
```

### フローチャート

```mermaid
flowchart TD
    A[CodeControl初期化] --> B[localStorage設定復元]
    B --> C[getInitialLang - 初期言語取得]
    C --> D[ReactCodeMirror描画]
    D --> E{ユーザー操作}
    E -->|コード入力| F[handleChange → onChange]
    E -->|設定ボタン| G[showSettings → SettingsPane表示]
    G --> H{設定変更}
    H -->|言語変更| I[require動的読込 → CodeMirror再初期化]
    H -->|テーマ変更| J[localStorage保存 → CodeMirror再初期化]
    H -->|キーマップ変更| K[localStorage保存 → CodeMirror再初期化]
    I --> D
    J --> D
    K --> D
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-64-01 | 値形式制御 | output_code_onlyがtrueの場合はstring値のみ出力、falseの場合はMap({ code, lang })を出力 | output_code_only設定による |
| BR-64-02 | エディタコンポーネントモード | isEditorComponentがtrueの場合、keysはデフォルト（code, lang）が強制。また値はMap形式 | Markdownエディタコンポーネント内 |
| BR-64-03 | 言語選択制御 | allow_language_selectionがfalseの場合、設定パネルから言語選択UIを非表示 | allow_language_selectionがfalse |
| BR-64-04 | 設定永続化 | テーマとキーマップはlocalStorage（'cms.codemirror.theme', 'cms.codemirror.keymap'）に永続化 | 常時 |
| BR-64-05 | カスタムキー設定 | keysオプションでcode/langのキー名をカスタマイズ可能 | keysが設定されている場合 |
| BR-64-06 | デフォルトテーマ | デフォルトテーマは'material' | 常時 |

### 計算ロジック

- toValue関数: valueIsMap()がtrueの場合は`(this.props.value || Map()).set(this.keys[type], value)`、falseの場合は`type === 'code' ? value : this.props.value`

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

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

該当なし（クライアントサイドのみの処理。localStorageへの設定保存のみ）。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 不明な言語 | getInitialLang()で言語名が解決できない場合 | unknownLangステートに保存して警告 |
| - | CodeMirrorモード読込エラー | require()で言語モードファイルが見つからない場合 | エラーをスロー（コンソール） |

### リトライ仕様

リトライ処理は行わない。

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

該当なし。

## パフォーマンス要件

- shouldComponentUpdateでstateまたはclassNameWrapperの変更時のみ再描画
- CodeMirrorのviewportMargin: Infinityでスクロールなしの全コンテンツ表示
- 親リストが折りたたみから展開された際にrefreshCodeMirrorInstance()で再描画

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

- CodeMirrorはコード表示のみで実行しないため、コードインジェクションリスクは低い
- localStorageへの設定保存は非機密データのみ（テーマ名、キーマップ名）

## 備考

- ウィジェット初期化時にallowMapValueがtrueに設定され、Map型の値が許可される
- codeMirrorConfigプロパティによりCodeMirrorのオプションをカスタマイズ可能

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | schema.js | `packages/decap-cms-widget-code/src/schema.js` | default_language, allow_language_selection, output_code_only, keysのスキーマ定義 |

**読解のコツ**: コードウィジェットの値は2種類ある。output_code_only=trueなら文字列、falseならMap({ code, lang })。この値形式の違いがコード全体に影響する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.js | `packages/decap-cms-widget-code/src/index.js` | allowMapValue: trueの設定とcodeMirrorConfig: {}の初期値に注目 |

**主要処理フロー**:
1. **1-2行目**: CodeControlとCodePreviewをインポート
2. **5-14行目**: Widget関数でname='code'、allowMapValue: trueとして登録

#### Step 3: 制御コンポーネント（CodeControl）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | CodeControl.js | `packages/decap-cms-widget-code/src/CodeControl.js` | 356行の制御コンポーネント。valueIsMap()とtoValue()の値管理ロジックが核心 |

**主要処理フロー**:
- **36-41行目**: languageData.jsonから言語リストを構築
- **60-63行目**: settingsPersistKeysによるlocalStorageキー定義
- **78-88行目**: state初期化（localStorage復元、lastKnownValue設定）
- **160-169行目**: getInitialLang() - field.default_languageまたは値のlangキーから初期言語取得
- **177-179行目**: toValue() - valueIsMap()に応じた値変換関数
- **199-202行目**: valueIsMap() - output_code_onlyとisEditorComponentで値形式判定
- **204-237行目**: handleChangeCodeMirrorProps() - 言語モード動的読込とCodeMirror再初期化
- **239-244行目**: handleChange() - コード入力時の値更新
- **271-354行目**: render() - CodeMirrorエディタとSettingsPane/SettingsButton

#### Step 4: 設定パネルを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | SettingsPane.js | `packages/decap-cms-widget-code/src/SettingsPane.js` | 言語・テーマ・キーマップの選択UI |
| 4-2 | SettingsButton.js | `packages/decap-cms-widget-code/src/SettingsButton.js` | 設定パネルの表示/非表示トグルボタン |

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

```
CodeControl (CodeControl.js)
    │
    ├─ render()
    │   ├─ SettingsButton → showSettings()
    │   ├─ SettingsPane
    │   │   ├─ onChangeLang → setState({ lang })
    │   │   ├─ onChangeTheme → setState({ theme })
    │   │   └─ onChangeKeyMap → setState({ keyMap })
    │   └─ ReactCodeMirror
    │       ├─ editorDidMount → this.cm = cm
    │       ├─ onChange → handleChange()
    │       ├─ onFocus → handleFocus()
    │       └─ onBlur → handleBlur()
    │
    ├─ componentDidUpdate()
    │   ├─ updateCodeMirrorProps() → handleChangeCodeMirrorProps()
    │   └─ refreshCodeMirrorInstance()（初回表示時）
    │
    └─ handleChangeCodeMirrorProps()
        ├─ require(`codemirror/mode/${mode}/${mode}.js`)
        └─ localStorage.setItem()
```

### データフロー図

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

CodeMirror入力 ────────▶ handleChange() ──▶ toValue('code', newValue)
                             │                     │
言語選択 ──────────────▶ handleChangeCodeMirrorProps()     │
                             │                     ▼
テーマ/キーマップ ──────▶ localStorage保存      onChange(Map/string)
                                                    │
languages.json ────────▶ 言語モード動的読込        ▼
                                              Redux Store
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.js | `packages/decap-cms-widget-code/src/index.js` | ソース | ウィジェット登録エントリーポイント |
| CodeControl.js | `packages/decap-cms-widget-code/src/CodeControl.js` | ソース | コードエディタ制御コンポーネント |
| CodePreview.js | `packages/decap-cms-widget-code/src/CodePreview.js` | ソース | コードプレビューコンポーネント |
| SettingsPane.js | `packages/decap-cms-widget-code/src/SettingsPane.js` | ソース | 設定パネルコンポーネント |
| SettingsButton.js | `packages/decap-cms-widget-code/src/SettingsButton.js` | ソース | 設定ボタンコンポーネント |
| languageSelectStyles.js | `packages/decap-cms-widget-code/src/languageSelectStyles.js` | ソース | 言語セレクトのスタイル定義 |
| schema.js | `packages/decap-cms-widget-code/src/schema.js` | ソース | ウィジェットスキーマ定義 |
| languages.json | `packages/decap-cms-widget-code/data/languages.json` | データ | 言語定義データ |
