# 機能設計書 46-Lexicalエディタ

## 概要

本ドキュメントは、Payload CMSにおけるLexicalリッチテキストエディタ（richtext-lexical）の設計を記載する。本パッケージは、Facebookが開発したLexicalフレームワークをPayloadのリッチテキストフィールドアダプターとして統合し、高度にカスタマイズ可能なWYSIWYGエディタを提供する。

### 本機能の処理概要

**業務上の目的・背景**：リッチテキスト編集はCMSの中核機能である。Lexicalは高いパフォーマンス、アクセシビリティ、拡張性を備えた最新のテキストエディタフレームワークであり、本パッケージはPayloadの標準リッチテキストエディタとしてLexicalを統合する。

**機能の利用シーン**：
- コンテンツのWYSIWYG編集
- 構造化されたブロックコンテンツの作成
- リンク、画像、動画などのメディア埋め込み
- カスタムブロック（コードブロック、カラム等）の挿入
- マークダウンやHTMLとの相互変換
- リレーションシップやアップロードの埋め込み

**主要な処理内容**：
1. Feature（機能）ベースのモジュラーアーキテクチャ
2. 各Featureによるエディタ機能の追加（太字、リスト、ブロック等）
3. エディタ状態（SerializedEditorState）の永続化
4. beforeValidate/beforeChange/afterChange/afterReadフックの実行
5. GraphQL Population Promisesによるデータ取得
6. JSON Schemaの動的生成
7. HTML/Markdown変換機能
8. インポートマップとスキーママップの生成

**関連システム・外部連携**：
- Lexical（@lexical/react等）
- Admin UI（リッチテキストフィールドコンポーネント）
- GraphQL API（population promises）

**権限による制御**：
- Payloadの標準フィールドアクセス制御に従う

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 10 | ドキュメント編集画面 | 主画面 | リッチテキストフィールドの編集 |
| 9 | 一覧画面 | 参照画面 | セル表示コンポーネント |

## 機能種別

UIコンポーネント / データ変換 / CRUD操作

## 入力仕様

### 入力パラメータ（lexicalEditor設定）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| features | FeatureProviderServer[] \| function | No | 有効化するFeature | デフォルト: defaultEditorFeatures |
| lexical | LexicalConfig | No | Lexical設定 | - |
| admin | object | No | 管理画面設定 | - |

### 入力パラメータ（エディタ値）

SerializedEditorState形式のJSONデータ：
```typescript
{
  root: {
    type: 'root',
    children: SerializedLexicalNode[],
    direction: 'ltr' | 'rtl' | null,
    format: ElementFormatType,
    indent: number,
    version: number
  }
}
```

### 入力データソース

- ドキュメント編集画面でのユーザー入力
- APIからのJSON形式データ
- Markdownからの変換（convertMarkdownToLexical）
- HTMLからの変換（convertHTMLToLexical）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| root | object | SerializedEditorState形式のルートノード |
| html | string | HTML変換結果（lexicalHTMLField使用時） |

### 出力先

- データベース（SerializedEditorState形式）
- HTML出力（lexicalHTML使用時）
- Markdown出力（convertLexicalToMarkdown使用時）

## 処理フロー

### 処理シーケンス

```
1. lexicalEditorプロバイダー初期化
   └─ features未指定の場合はdefaultEditorFeaturesを使用
2. エディタ設定のサニタイズ
   ├─ featuresInputToEditorConfigで設定変換
   └─ resolvedFeatureMapの生成
3. 各Featureの初期化
   └─ ノード、プラグイン、ツールバー項目の登録
4. エディタコンポーネントの描画
   └─ FieldComponent: RscEntryLexicalField
5. フック実行（データ保存時）
   ├─ beforeValidate: ノードIDマップ生成
   ├─ beforeChange: ノードフック実行
   └─ afterChange: ノードフック実行
6. フック実行（データ読み取り時）
   └─ afterRead: ノードフック実行
7. GraphQL取得時
   └─ graphQLPopulationPromisesでリレーション解決
```

### フローチャート

```mermaid
flowchart TD
    A[lexicalEditor呼び出し] --> B{features指定?}
    B -->|Yes| C[featuresInputToEditorConfig]
    B -->|No| D[getDefaultSanitizedEditorConfig]
    C --> E[finalSanitizedEditorConfig]
    D --> E
    E --> F[Feature i18nのマージ]
    F --> G[RichTextAdapterオブジェクト返却]
    G --> H[FieldComponent登録]
    G --> I[hooks登録]
    G --> J[validate関数登録]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | デフォルトFeature | features未指定時はdefaultEditorFeaturesを使用 | args未指定または空 |
| BR-02 | ノードID必須 | フック実行にはノードIDが必要 | beforeChange/afterChange時 |
| BR-03 | Lexicalバージョン整合性 | lexicalTargetVersion (0.35.0) との互換性が必要 | 依存パッケージ検証 |
| BR-04 | サブフィールド処理 | BlocksFeature等でサブフィールドのトラバースが必要 | ブロック使用時 |
| BR-05 | ノードフック順序 | beforeValidate → beforeChange → afterChange の順で実行 | データ保存時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ドキュメント保存 | リッチテキスト含むコレクション | INSERT/UPDATE | SerializedEditorState保存 |
| ドキュメント読み取り | リッチテキスト含むコレクション | SELECT | SerializedEditorState取得 |
| Population | リレーション先コレクション | SELECT | 埋め込みリレーションの解決 |

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

リッチテキストフィールドはSerializedEditorState形式のJSON/JSONBとして保存される。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ValidationError | 不正なエディタ状態 | richTextValidateHOCで検証 |
| - | Warning | ノードIDマップでID未発見 | console.warn出力、処理継続 |

### リトライ仕様

特になし

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

Payloadの標準トランザクション挙動に従う。

## パフォーマンス要件

- Lexicalの依存パッケージバージョンの整合性検証（開発環境のみ）
- ノードフックの効率的な実行（IDベースのマッピング）
- 大量ノードの処理にはrecurseNodeTreeを使用

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

- XSS対策: Lexicalの標準エスケープ機構を使用
- HTML変換時の適切なサニタイズ
- リレーション解決時のアクセス制御適用

## 備考

- Lexical 0.35.0との互換性を保証
- Slateエディタからの移行ガイドあり
- カスタムFeatureの作成が可能（createServerFeature）

---

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

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

### 推奨読解順序

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

エディタ設定とFeatureの型を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.ts | `packages/richtext-lexical/src/types.ts` | LexicalEditorProps、LexicalRichTextAdapter |
| 1-2 | typesServer.ts | `packages/richtext-lexical/src/features/typesServer.ts` | FeatureProviderServer、ServerFeature |
| 1-3 | config/types.ts | `packages/richtext-lexical/src/lexical/config/types.ts` | SanitizedServerEditorConfig |

**読解のコツ**: `FeatureProviderServer`がFeatureのプロバイダーインターフェース。`ServerFeature`が実際のFeature実装。`SanitizedServerEditorConfig`がサニタイズ後のエディタ設定。

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

lexicalEditor関数の全体フローを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/richtext-lexical/src/index.ts` | lexicalEditor関数 |

**主要処理フロー**:
1. **34-60行目**: 依存パッケージの検証（開発環境）
2. **61-86行目**: エディタ設定のサニタイズ
3. **88-97行目**: i18n翻訳のマージ
4. **99-111行目**: RichTextAdapterオブジェクトの構築
5. **112-117行目**: generateImportMap/generateSchemaMapの設定
6. **118-154行目**: graphQLPopulationPromisesの設定
7. **155-777行目**: フック（beforeValidate/beforeChange/afterChange/afterRead）の実装
8. **778-857行目**: outputSchemaの生成
9. **858-861行目**: validateの設定

#### Step 3: Featureシステムを理解する

Featureの仕組みを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | default.ts | `packages/richtext-lexical/src/lexical/config/server/default.ts` | defaultEditorFeatures |
| 3-2 | loader.ts | `packages/richtext-lexical/src/lexical/config/server/loader.ts` | loadFeatures |
| 3-3 | sanitize.ts | `packages/richtext-lexical/src/lexical/config/server/sanitize.ts` | sanitizeServerEditorConfig |

**主要処理フロー**:
- **default.ts**: デフォルトで有効なFeature一覧（Bold、Italic、Link等）
- **loader.ts**: Feature依存関係の解決と読み込み順序の最適化
- **sanitize.ts**: Feature設定のサニタイズとresolvedFeatureMapの生成

#### Step 4: フックシステムを理解する

ノードフックの実行フローを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | recurseNodeTree.ts | `packages/richtext-lexical/src/utilities/recurseNodeTree.ts` | ノードツリーの走査 |

**読解のコツ**: `recurseNodeTree`はエディタ状態のノードを再帰的に走査し、`nodeIDMap`と`flattenedNodes`を構築する。フックはこれらのマップを使用してノードレベルの処理を実行。

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

```
lexicalEditor (index.ts)
    │
    ├─ checkDependencies (payload)
    │      └─ Lexicalパッケージのバージョン検証
    │
    ├─ featuresInputToEditorConfig (utilities/editorConfigFactory.ts)
    │      ├─ loadFeatures (lexical/config/server/loader.ts)
    │      └─ sanitizeServerEditorConfig (lexical/config/server/sanitize.ts)
    │
    ├─ hooks
    │      ├─ beforeValidate
    │      │      └─ recurseNodeTree → originalNodeIDMap生成
    │      ├─ beforeChange
    │      │      └─ nodeHooks.beforeChange実行
    │      ├─ afterChange
    │      │      └─ nodeHooks.afterChange実行
    │      └─ afterRead
    │             └─ nodeHooks.afterRead実行
    │
    ├─ graphQLPopulationPromises
    │      └─ populateLexicalPopulationPromises
    │
    ├─ generateImportMap
    │      └─ getGenerateImportMap
    │
    └─ generateSchemaMap
           └─ getGenerateSchemaMap
```

### データフロー図

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

ユーザー入力 ─────────────▶ Lexicalエディタ ──────────▶ SerializedEditorState
                                 │
                                 ▼
                          beforeValidate ─────────▶ ノードIDマップ生成
                                 │
                                 ▼
                          beforeChange ───────────▶ ノードフック実行
                                 │
                                 ▼
                          データベース保存
                                 │
                                 ▼
                          afterChange ────────────▶ ノードフック実行

[読み取り時]
データベース ─────────────▶ afterRead ────────────▶ SerializedEditorState
                                 │
                                 ▼
                          graphQLPopulationPromises
                                 │
                                 ▼
                          population済みデータ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/richtext-lexical/src/index.ts` | ソース | エントリーポイント |
| types.ts | `packages/richtext-lexical/src/types.ts` | ソース | 型定義 |
| typesServer.ts | `packages/richtext-lexical/src/features/typesServer.ts` | ソース | Feature型定義 |
| default.ts | `packages/richtext-lexical/src/lexical/config/server/default.ts` | ソース | デフォルトFeature |
| loader.ts | `packages/richtext-lexical/src/lexical/config/server/loader.ts` | ソース | Feature読み込み |
| sanitize.ts | `packages/richtext-lexical/src/lexical/config/server/sanitize.ts` | ソース | 設定サニタイズ |
| editorConfigFactory.ts | `packages/richtext-lexical/src/utilities/editorConfigFactory.ts` | ソース | 設定ファクトリー |
| recurseNodeTree.ts | `packages/richtext-lexical/src/utilities/recurseNodeTree.ts` | ソース | ノードツリー走査 |
| validate/index.js | `packages/richtext-lexical/src/validate/index.js` | ソース | バリデーション |
