# 機能設計書 84-Lexical Editor

## 概要

本ドキュメントは、Ghostのリッチテキストエディタ「Lexical Editor」の機能設計を定義する。Lexical EditorはFacebookが開発したLexicalフレームワークをベースとし、Ghost固有の拡張（Koenigカード）を含むエディタで、記事・ページの作成・編集に使用される。

### 本機能の処理概要

Lexical Editorは、WYSIWYG形式のリッチテキスト編集機能を提供し、テキストフォーマット、画像・動画の埋め込み、カスタムカード（Koenigカード）の挿入をサポートする。

**業務上の目的・背景**：Ghost Adminでの記事・ページ作成において、直感的で高機能なテキスト編集体験を提供する。従来のMobiledocエディタを置き換え、より現代的で拡張性の高いエディタを実現する。

**機能の利用シーン**：
- 記事（Post）の作成・編集
- ページ（Page）の作成・編集
- ニュースレター用コンテンツの編集
- コメントの投稿

**主要な処理内容**：
1. リッチテキスト編集（見出し、段落、リスト、引用、コードブロック等）
2. 画像・動画・ファイルの埋め込み
3. Koenigカードの挿入（ギャラリー、ブックマーク、埋め込み等）
4. Lexical JSON形式でのシリアライズ/デシリアライズ
5. HTML出力の生成
6. コラボレーティブ編集（将来機能）

**関連システム・外部連携**：
- @tryghost/koenig-lexical - Ghost固有のLexical実装（npm依存パッケージ）
- Unsplash API - 画像検索・挿入
- oEmbed API - 外部コンテンツの埋め込み

**権限による制御**：エディタ自体には権限制御はないが、記事の編集権限は上位のコントローラで管理される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 13 | エディタ画面 | 主画面 | 記事の作成・編集・保存・公開 |

## 機能種別

WYSIWYG エディタ / リッチテキスト編集

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| lexical | string (JSON) | No | 初期コンテンツ（Lexical JSON形式） | 有効なJSON |
| onChange | Function | Yes | コンテンツ変更時コールバック | - |
| editorAPI | object | No | エディタAPI参照 | - |
| cardConfig | object | No | カード設定（Unsplash等） | - |

### 入力データソース

- データベース（posts.lexical カラム）
- ユーザーのキーボード/マウス入力

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| lexical | string (JSON) | Lexical形式のシリアライズされたコンテンツ |
| html | string | 生成されたHTML |
| plaintext | string | プレーンテキスト抽出 |

### 出力先

- データベース（posts.lexical, posts.html, posts.plaintext カラム）
- フロントエンドでのプレビュー表示

## 処理フロー

### 処理シーケンス

```
1. エディタ初期化
   └─ lexical JSONからエディタ状態を復元
2. ユーザー入力の処理
   └─ キーボード/マウスイベントのハンドリング
   └─ ノードの作成・更新・削除
3. 変更のコールバック
   └─ onChangeでシリアライズされたコンテンツを通知
4. 保存処理
   └─ lexical JSON、HTML、plaintextをDBに保存
```

### フローチャート

```mermaid
flowchart TD
    A[エディタ初期化] --> B[Lexical JSONパース]
    B --> C[EditorState構築]
    C --> D[ユーザー入力待機]
    D --> E{入力種別}
    E -->|テキスト| F[TextNode更新]
    E -->|カード挿入| G[Koenigカード挿入]
    E -->|フォーマット| H[スタイル適用]
    F --> I[onChange発火]
    G --> I
    H --> I
    I --> J[Lexical JSONシリアライズ]
    J --> K[HTML生成]
    K --> L[保存]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-84-01 | 自動保存 | 編集内容は一定間隔で自動保存 | 編集中 |
| BR-84-02 | リビジョン保存 | 編集履歴を保存し復元可能 | コンテンツ変更時 |
| BR-84-03 | 画像最適化 | アップロード画像は自動リサイズ | 画像挿入時 |
| BR-84-04 | oEmbed展開 | URLからカード情報を自動取得 | URL貼り付け時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 保存 | posts | UPDATE | lexical, html, plaintext カラムの更新 |
| リビジョン保存 | post_revisions | INSERT | 編集履歴の保存 |

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

#### posts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | lexical | Lexical JSONシリアライズ値 | 1GB上限 |
| UPDATE | html | 生成されたHTML | 1GB上限 |
| UPDATE | plaintext | プレーンテキスト | 1GB上限 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ParseError | 不正なLexical JSON | エラー表示、空エディタで開始 |
| - | UploadError | 画像アップロード失敗 | リトライまたはエラー通知 |
| - | oEmbedError | oEmbed取得失敗 | リンクとして表示 |

### リトライ仕様

画像アップロードは自動リトライ（3回まで）

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

保存処理はGhost Core側でトランザクション管理

## パフォーマンス要件

- 大規模ドキュメント（10,000ワード以上）でも快適な編集体験
- 画像遅延読み込み対応

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

- XSS対策：HTML出力時にサニタイズ
- ファイルアップロードのMIMEタイプ検証

## 備考

- @tryghost/koenig-lexical はnpm依存パッケージとして管理
- Mobiledocからの自動マイグレーションをサポート

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | schema.js | `ghost/core/core/server/data/schema/schema.js` | posts.lexical カラム定義（66-67行目） |

**読解のコツ**: postsテーブルのlexical, html, plaintextカラムの定義を確認。maxlength: 1000000000（約1GB）の大きなテキスト領域。

#### Step 2: モデル層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | post.js | `ghost/core/core/server/models/post.js` | Postモデルのlexical処理 |

#### Step 3: フロントエンド統合を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | koenig-editor-base.tsx | `apps/admin-x-design-system/src/global/form/koenig-editor-base.tsx` | エディタ基盤コンポーネント |

**主要処理フロー**:
- Lexicalエディタの初期化とマウント
- onChangeコールバックの設定
- カード設定の受け渡し

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

```
Ghost Admin (Ember)
    │
    └─ Koenig Lexical Editor (@tryghost/koenig-lexical)
           │
           ├─ LexicalEditor (Core)
           │      ├─ Plugins
           │      │      ├─ ToolbarPlugin
           │      │      ├─ FloatingMenuPlugin
           │      │      └─ CardPlugin
           │      │
           │      └─ Nodes
           │             ├─ TextNode
           │             ├─ HeadingNode
           │             └─ KoenigCard (Custom)
           │
           └─ HTML Renderer
                  └─ lexical → HTML 変換
```

### データフロー図

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

lexical JSON    ───▶ LexicalEditor          ───▶ EditorState
  (DB/API)           (Parse & Render)

User Input      ───▶ Node Updates           ───▶ lexical JSON
  (Keyboard)         (Transform)                 (Serialize)

EditorState     ───▶ HTML Renderer          ───▶ HTML string
                     (Generate)                  (posts.html)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| schema.js | `ghost/core/core/server/data/schema/schema.js` | スキーマ | posts.lexicalカラム定義 |
| post.js | `ghost/core/core/server/models/post.js` | モデル | Postモデル |
| koenig-editor-base.tsx | `apps/admin-x-design-system/src/global/form/koenig-editor-base.tsx` | ソース | エディタ統合 |
| package.json | 各apps配下 | 設定 | @tryghost/koenig-lexical依存 |
