# 画面設計書 13-エディタ画面

## 概要

本ドキュメントは、Ghost管理画面における投稿・ページ編集用のLexicalエディタ画面の設計仕様を定義するものである。

### 本画面の処理概要

エディタ画面は、投稿（Post）およびページ（Page）のコンテンツを作成・編集するためのリッチテキストエディタを提供する中核的な画面である。Lexicalエディタエンジンを使用し、ブロックベースの編集体験を提供する。

**業務上の目的・背景**：コンテンツ管理システムの最も重要な機能として、ユーザーがリッチなコンテンツを直感的に作成できる環境を提供する。タイトル入力、本文編集、アイキャッチ画像設定、メタデータ設定、公開設定など、コンテンツ作成に必要なすべての機能を統合している。下書きの自動保存機能により、データ損失を防ぎ、シームレスな編集体験を実現する。

**画面へのアクセス方法**：
- 投稿一覧/ページ一覧から既存記事をクリック
- 「New post」/「New page」ボタンをクリック
- URL `/ghost/#/editor/post/{id}` または `/ghost/#/editor/page/{id}` で直接アクセス

**主要な操作・処理内容**：
1. タイトルの入力・編集
2. Lexicalエディタによる本文の作成・編集
3. アイキャッチ画像の設定（アップロード、Alt、キャプション）
4. 抜粋（Excerpt）の設定
5. 自動保存（下書き時：3秒後、強制保存：60秒ごと）
6. 手動保存（Cmd/Ctrl+S）
7. 公開フローの開始（公開、予約、メール送信）
8. 投稿設定サイドパネルでの各種設定
9. スニペットの保存・挿入
10. 投稿履歴の表示・復元

**画面遷移**：
- 遷移元: 投稿一覧画面、ページ一覧画面、投稿分析画面
- 遷移先: 投稿一覧画面、ページ一覧画面、投稿分析画面

**権限による表示制御**：
- Contributor: 下書きのみ編集可能、自分の投稿のみ、公開不可
- Author: 自分の投稿のみ編集可能
- Editor: 全投稿編集可能
- Administrator/Owner: 全機能利用可能

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | 記事管理 | 主機能 | 記事の作成・編集・保存・公開 |
| 84 | Lexical Editor | 主機能 | リッチテキスト編集機能の提供 |
| 7 | メディアアップロード | 補助機能 | 記事内の画像・動画のアップロード |
| 3 | タグ管理 | 補助機能 | 記事へのタグ付与 |
| 19 | ニュースレター配信 | 補助機能 | メール配信設定の指定 |
| 38 | SEO設定 | 補助機能 | 記事のメタタイトル・ディスクリプション設定 |
| 6 | 記事リビジョン | 補助機能 | 編集履歴の自動保存 |

## 画面種別

編集

## URL/ルーティング

| 項目 | 値 |
|------|-----|
| 新規作成URL | `/ghost/#/editor/{type}` |
| 編集URL | `/ghost/#/editor/{type}/{id}` |
| ルート名（親） | lexical-editor |
| ルート名（新規） | lexical-editor.new |
| ルート名（編集） | lexical-editor.edit |
| ルートファイル | `ghost/admin/app/routes/lexical-editor.js` |

### ルートパラメータ

| パラメータ名 | 型 | 説明 |
|-------------|-----|------|
| type | string | 'post' または 'page' |
| id | string | 投稿/ページのID（編集時） |

## 入出力項目

### 入力項目（メインエディタ）

| 項目名 | 項目ID | 必須 | 型 | 説明 |
|--------|--------|------|-----|------|
| タイトル | titleScratch | - | text | 投稿/ページのタイトル |
| 本文 | lexicalScratch | - | json | Lexical形式のコンテンツ |
| アイキャッチ画像 | featureImage | - | url | アイキャッチ画像のURL |
| アイキャッチAlt | featureImageAlt | - | text | 画像の代替テキスト |
| アイキャッチキャプション | featureImageCaption | - | html | 画像のキャプション |
| 抜粋 | customExcerpt | - | text | カスタム抜粋文（300文字以内） |

### 入力項目（設定パネル）

| 項目名 | 項目ID | 必須 | 型 | 説明 |
|--------|--------|------|-----|------|
| URL | slug | - | text | 投稿/ページのスラッグ |
| 公開日時 | publishedAtBlogDate/Time | - | datetime | 公開日時 |
| タグ | tags | - | array | 関連タグの配列 |
| 著者 | authors | - | array | 著者の配列 |
| 公開範囲 | visibility | - | select | public/members/paid/tiers |
| おすすめ | featured | - | boolean | おすすめ投稿フラグ |
| メタタイトル | metaTitleScratch | - | text | SEO用タイトル |
| メタ説明 | metaDescriptionScratch | - | text | SEO用説明文 |
| Canonical URL | canonicalUrlScratch | - | url | 正規URL |
| OGタイトル | ogTitleScratch | - | text | Facebook用タイトル |
| OG説明 | ogDescriptionScratch | - | text | Facebook用説明文 |
| OG画像 | ogImage | - | url | Facebook用画像 |
| Twitterタイトル | twitterTitleScratch | - | text | Twitter用タイトル |
| Twitter説明 | twitterDescriptionScratch | - | text | Twitter用説明文 |
| Twitter画像 | twitterImage | - | url | Twitter用画像 |
| コードインジェクション（Header） | codeinjectionHeadScratch | - | text | ヘッダーに挿入するコード |
| コードインジェクション（Footer） | codeinjectionFootScratch | - | text | フッターに挿入するコード |

## 表示項目

### ヘッダー部

| 項目名 | 説明 |
|--------|------|
| 戻るボタン | 投稿一覧/ページ一覧または分析画面への戻りリンク |
| ステータス表示 | Draft/Published/Scheduled/Sent など |
| 保存状態表示 | Saving.../Saved |
| 公開ボタン | 公開/更新/予約ボタン群 |

### エディタ部

| 項目名 | 説明 |
|--------|------|
| タイトル入力欄 | プレースホルダー「Post title」/「Page title」 |
| 抜粋入力欄 | 機能有効時のみ表示 |
| Lexicalエディタ | ブロックベースのリッチテキストエディタ |
| ワードカウント | 単語数の表示 |
| メールサイズ警告 | メール送信時のサイズ警告 |
| ヘルプリンク | エディタ使用方法へのリンク |

### 設定パネル

| 項目名 | 説明 |
|--------|------|
| URL設定 | スラッグ編集 |
| 公開日時設定 | 日付・時刻ピッカー |
| タグ設定 | タグ選択UI |
| 著者設定 | 著者選択UI |
| 公開範囲設定 | アクセス権限選択 |
| アイキャッチ設定 | 画像アップロード、Alt、キャプション |
| 抜粋設定 | カスタム抜粋入力 |
| SEO設定 | メタタイトル・説明、プレビュー |
| ソーシャル設定 | Facebook/Twitter用メタデータ |
| コードインジェクション | カスタムコード入力 |
| 投稿履歴 | 履歴表示ボタン |
| 削除ボタン | 投稿削除ボタン |

## イベント仕様

### 1-タイトル入力

- 処理: titleScratchを更新、スラッグ自動生成をトリガー
- 自動保存: 下書き時は自動保存タスクを開始

### 2-本文編集

- 処理: lexicalScratchを更新
- 自動保存: 下書き時は3秒後に自動保存、60秒ごとに強制保存
- ローカルリビジョン: localRevisionsサービスに保存をスケジュール

### 3-手動保存（Cmd/Ctrl+S）

- 処理: saveTaskを実行
- 新規投稿時: 保存後に編集URLにリダイレクト
- 通知: 成功/失敗メッセージを表示

### 4-公開フロー開始

- 処理: PublishManagementコンポーネントが公開フローを管理
- 事前処理: beforeSaveTaskでデータを準備
- 事後処理: afterSaveで後処理、成功通知表示

### 5-アイキャッチ画像設定

- 処理: featureImage, featureImageAlt, featureImageCaptionを設定
- 自動保存: 下書き時は自動保存タスクを開始

### 6-設定パネルトグル

- 処理: showSettingsMenuフラグを切り替え
- サイドパネル: GhPostSettingsMenuコンポーネントを表示/非表示

### 7-スニペット作成

- 処理: 選択範囲をスニペットとして保存
- 権限: Admin/Editorのみ利用可能
- 重複時: 既存スニペットの上書き確認モーダル

### 8-投稿履歴表示

- 処理: showPostHistoryフラグを切り替え
- モーダル: post-historyモーダルを表示

### 9-投稿削除

- 処理: DeletePostModalを表示
- 確認後: 投稿を削除し、一覧画面へ遷移

### 10-画面離脱時

- 処理: willTransitionで未保存変更をチェック
- 未保存あり: 自動保存または確認モーダル表示
- 確認後: 変更を破棄して遷移

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 新規作成 | posts | INSERT | 新規投稿レコードの作成 |
| 保存 | posts | UPDATE | 投稿内容の更新 |
| 公開 | posts | UPDATE | statusをpublishedに更新 |
| 予約 | posts | UPDATE | statusをscheduledに更新 |
| 削除 | posts | DELETE | 投稿レコードの削除 |
| タグ設定 | posts_tags | INSERT/DELETE | タグ関連付けの更新 |
| 著者設定 | posts_authors | INSERT/DELETE | 著者関連付けの更新 |
| リビジョン保存 | post_revisions | INSERT | 編集履歴の保存 |
| スニペット作成 | snippets | INSERT | スニペットの新規作成 |
| スニペット更新 | snippets | UPDATE | スニペットの更新 |

### テーブル別更新項目詳細

#### posts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | title | titleScratchの値 | 空の場合は(Untitled) |
| INSERT/UPDATE | lexical | lexicalScratchのJSON | Lexical形式 |
| INSERT/UPDATE | status | draft/published/scheduled/sent | ステータス遷移 |
| INSERT/UPDATE | slug | 自動生成またはユーザー入力 | 一意性確保 |
| INSERT/UPDATE | visibility | public/members/paid/tiers | アクセス制御 |
| INSERT/UPDATE | featured | true/false | おすすめフラグ |
| INSERT/UPDATE | feature_image | 画像URL | アイキャッチ |
| INSERT/UPDATE | custom_excerpt | ユーザー入力 | 抜粋文 |
| INSERT/UPDATE | meta_title, meta_description | ユーザー入力 | SEO設定 |
| INSERT/UPDATE | published_at | 日時 | 公開/予約日時 |

## メッセージ仕様

### 保存成功メッセージ

| メッセージID | 種別 | 条件 | メッセージ内容 |
|-------------|------|------|---------------|
| MSG-01 | 成功 | 下書き保存 | Post saved / Page saved |
| MSG-02 | 成功 | 公開 | Post published / Page published |
| MSG-03 | 成功 | 更新 | Post updated / Page updated |
| MSG-04 | 成功 | 予約 | Post scheduled / Page scheduled |
| MSG-05 | 成功 | 送信 | Post sent |

### 保存エラーメッセージ

| メッセージID | 種別 | 条件 | メッセージ内容 |
|-------------|------|------|---------------|
| MSG-10 | エラー | 下書き保存失敗 | Saving failed |
| MSG-11 | エラー | 公開失敗 | Publish failed |
| MSG-12 | エラー | 予約失敗 | Scheduling failed |
| MSG-13 | エラー | 更新失敗 | Update failed |
| MSG-14 | エラー | 接続エラー | Unable to connect, please check your internet connection and try again |

### その他メッセージ

| メッセージID | 種別 | 条件 | メッセージ内容 |
|-------------|------|------|---------------|
| MSG-20 | 情報 | スニペット保存 | Snippet saved as "{name}" |
| MSG-21 | エラー | スニペット保存失敗 | Snippet save failed: {error} |
| MSG-22 | エラー | エディタクラッシュ | Editor has crashed. Please copy your content and start a new post. |

## 例外処理

| 例外ケース | 処理内容 |
|-----------|---------|
| 権限不足（他者の投稿） | 投稿一覧へリダイレクト |
| 権限不足（Contributor公開） | 投稿一覧へリダイレクト |
| 投稿が削除済み | エラーメッセージ表示、コンテンツコピーを促す |
| API通信エラー | エラーメッセージ表示、再試行を促す |
| バリデーションエラー | フィールドにエラー表示 |
| セッション切れ | 再認証モーダル表示 |
| ホスト制限エラー | アップグレードモーダル表示 |

## 備考

- 自動保存は下書き（isDraft）時のみ有効
- 自動保存タイムアウト：最後の編集から3秒（AUTOSAVE_TIMEOUT）
- 強制保存タイムアウト：連続入力時60秒ごと（TIMEDSAVE_TIMEOUT）
- TK（To Come）マーカーの検出機能あり（公開前の確認用）
- mobiledoc形式の投稿は編集時に自動でLexical形式に変換される
- ブラウザのonbeforeunloadで未保存時の離脱防止
- localRevisionsサービスでローカルにリビジョンを保存

---

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

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

### 推奨読解順序

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

投稿データモデルの属性と関連を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | post.js | `ghost/admin/app/models/post.js` | 属性定義、scratch値、computed properties |

**主要処理フロー**:
- **83-118行目**: 投稿の属性定義
- **134-159行目**: scratch値（編集中の一時値）の定義
- **161-168行目**: ステータス判定computed

#### Step 2: ルーティングを理解する

新規作成と編集の2つのサブルートを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | lexical-editor.js | `ghost/admin/app/routes/lexical-editor.js` | 親ルート、フルスクリーン設定、willTransition |
| 2-2 | new.js | `ghost/admin/app/routes/lexical-editor/new.js` | 新規作成時のmodel生成 |
| 2-3 | edit.js | `ghost/admin/app/routes/lexical-editor/edit.js` | 編集時のmodel取得、権限チェック |

**主要処理フロー（lexical-editor.js）**:
- **14-17行目**: activate - フルスクリーンモード設定
- **19-28行目**: setupController - 分析画面からの遷移を記録
- **34-37行目**: deactivate - フルスクリーンモード解除
- **51-59行目**: willTransition - 離脱時の処理呼び出し

**主要処理フロー（new.js）**:
- **8-16行目**: model - 新規レコード作成、著者を現在ユーザーに設定
- **21-29行目**: setupController - エディタコントローラーにpost設定

**主要処理フロー（edit.js）**:
- **20-45行目**: model - IDで投稿を取得、mobiledoc→lexical変換
- **51-64行目**: afterModel - 権限チェック

#### Step 3: コントローラーを理解する

エディタの中核ロジックを担当する大規模コントローラー。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | lexical-editor.js | `ghost/admin/app/controllers/lexical-editor.js` | 状態管理、保存タスク、自動保存 |

**主要処理フロー**:
- **33-44行目**: 定数定義（AUTOSAVE_TIMEOUT=3000ms, TIMEDSAVE_TIMEOUT=60000ms）
- **310-324行目**: updateScratch - 本文更新、自動保存トリガー
- **331-339行目**: updateTitleScratch - タイトル更新
- **576-585行目**: autosaveTask - 自動保存タスク
- **589-721行目**: saveTask - メイン保存タスク
- **731-767行目**: beforeSaveTask - 保存前処理
- **1068-1105行目**: setPost - 投稿設定時の初期化
- **1110-1247行目**: willTransition - 離脱時の確認処理
- **1250-1287行目**: reset - 状態リセット
- **1362-1465行目**: _hasDirtyAttributes - 未保存変更の検出

#### Step 4: テンプレートを理解する

画面構造とコンポーネント配置を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | lexical-editor.hbs | `ghost/admin/app/templates/lexical-editor.hbs` | 画面レイアウト |

**主要処理フロー**:
- **3-7行目**: GhEditorコンポーネント
- **9-56行目**: ヘッダー（PublishManagement、ステータス表示、ボタン）
- **62-103行目**: GhKoenigEditorLexical（メインエディタ）
- **105-111行目**: ワードカウント、メールサイズ警告
- **136-146行目**: 設定パネル（GhPostSettingsMenu）
- **149-155行目**: 設定トグルボタン
- **157-163行目**: 投稿履歴モーダル

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

投稿設定のサイドパネルを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | gh-post-settings-menu.js | `ghost/admin/app/components/gh-post-settings-menu.js` | 設定パネルのロジック |

**主要処理フロー**:
- **30-67行目**: 各scratch値へのalias定義
- **121-146行目**: SEO表示用computed
- **148-170行目**: 投稿履歴表示可否判定

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

```
LexicalEditorRoute (ghost/admin/app/routes/lexical-editor.js)
    │
    ├─ NewRoute (lexical-editor/new.js)
    │      ├─ model() - store.createRecord()
    │      └─ setupController() - editor.setPost()
    │
    └─ EditRoute (lexical-editor/edit.js)
           ├─ model() - store.query() + mobiledoc変換
           ├─ afterModel() - 権限チェック
           └─ setupController() - editor.setPost()

LexicalEditorController (ghost/admin/app/controllers/lexical-editor.js)
    │
    ├─ setPost() - 投稿の初期設定
    │      └─ backgroundLoaderTask - スニペット読み込み
    │
    ├─ updateScratch() / updateTitleScratch()
    │      ├─ localRevisions.scheduleSave()
    │      └─ _autosaveTask / _timedSaveTask
    │
    ├─ saveTask()
    │      ├─ cancelAutosave()
    │      ├─ beforeSaveTask()
    │      │      └─ generateSlugTask()
    │      ├─ _savePostTask()
    │      │      └─ post.save()
    │      └─ afterSave()
    │
    └─ willTransition()
           ├─ hasDirtyAttributes チェック
           ├─ autosaveTask.perform()
           └─ ConfirmEditorLeaveModal

GhKoenigEditorLexical (エディタコンポーネント)
    │
    ├─ タイトル入力 → onTitleChange → updateTitleScratch
    ├─ 本文編集 → onBodyChange → updateScratch
    └─ 画像設定 → setFeatureImage / clearFeatureImage

GhPostSettingsMenu (設定パネル)
    │
    ├─ URL編集 → updateSlugTask
    ├─ タグ設定 → post.tags更新
    ├─ 著者設定 → post.authors更新
    ├─ 公開範囲設定 → post.visibility更新
    ├─ SEO設定 → metaTitleScratch等更新
    └─ 削除 → openDeletePostModal
```

### データフロー図

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

タイトル入力 ─────────────▶ updateTitleScratch ──────────────▶ post.titleScratch
                                  │
                                  └─ saveTitleTask（下書き時）
                                         └─ generateSlugTask
                                         └─ autosaveTask

本文編集 ────────────────▶ updateScratch ────────────────────▶ post.lexicalScratch
                                  │
                                  ├─ localRevisions.scheduleSave
                                  └─ _autosaveTask（3秒後）
                                  └─ _timedSaveTask（60秒ごと）

保存（Cmd+S / ボタン） ───▶ saveTask ─────────────────────────▶ API保存
                                  │
                                  ├─ cancelAutosave
                                  ├─ beforeSaveTask
                                  │      └─ scratch → model値コピー
                                  ├─ _savePostTask
                                  │      └─ post.save()
                                  └─ afterSave
                                         └─ 通知表示

画面離脱 ────────────────▶ willTransition ───────────────────▶ 確認/保存/遷移
                                  │
                                  ├─ hasDirtyAttributes?
                                  ├─ 下書き時: autosaveTask
                                  └─ 確認モーダル or 遷移
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| lexical-editor.js | `ghost/admin/app/routes/lexical-editor.js` | ルート（親） | フルスクリーン設定、離脱処理 |
| new.js | `ghost/admin/app/routes/lexical-editor/new.js` | ルート | 新規投稿作成 |
| edit.js | `ghost/admin/app/routes/lexical-editor/edit.js` | ルート | 既存投稿編集 |
| lexical-editor.js | `ghost/admin/app/controllers/lexical-editor.js` | コントローラー | エディタのメインロジック |
| lexical-editor.hbs | `ghost/admin/app/templates/lexical-editor.hbs` | テンプレート | 画面レイアウト |
| post.js | `ghost/admin/app/models/post.js` | モデル | 投稿データ構造 |
| gh-post-settings-menu.js | `ghost/admin/app/components/gh-post-settings-menu.js` | コンポーネント | 設定パネルロジック |
| gh-koenig-editor-lexical | `ghost/admin/app/components/` | コンポーネント | Lexicalエディタラッパー |
| publish-management | `ghost/admin/app/components/editor/publish-management.js` | コンポーネント | 公開フロー管理 |
| confirm-leave.js | `ghost/admin/app/components/modals/editor/confirm-leave.js` | モーダル | 離脱確認 |
| delete-post.js | `ghost/admin/app/components/modals/delete-post.js` | モーダル | 削除確認 |
| post-history | `ghost/admin/app/components/modal-post-history/` | モーダル | 履歴表示 |
