# 画面設計書 95-記事編集画面

## 概要

本ドキュメントは、Chatwootのヘルプセンター機能における「記事編集画面」の設計仕様を記述したものである。

### 本画面の処理概要

記事編集画面は、既存のヘルプセンター記事を編集・更新するためのエディタ画面である。記事のタイトル・本文の編集に加え、ステータス変更（公開・下書き・アーカイブ）、カテゴリ変更、メタ情報の編集、プレビュー機能を提供する。

**業務上の目的・背景**：ナレッジベースの記事は、製品の更新やフィードバックに応じて継続的に改善する必要がある。本画面では、記事の内容編集だけでなく、公開ステータスの管理やSEO対策のためのメタ情報設定も行える。自動保存機能により、編集中のデータ損失を防止する。

**画面へのアクセス方法**：記事一覧画面で記事カードをクリック、記事新規作成後の自動遷移、または直接URL `/app/accounts/:accountId/portals/:portalSlug/:locale/articles/:tab/edit/:articleSlug` にアクセスする。

**主要な操作・処理内容**：
1. 記事詳細を取得して表示
2. 記事タイトル・本文を編集
3. カテゴリ・著者を変更
4. メタ情報（メタタイトル・メタ説明・メタタグ）を編集
5. ステータス変更（Publish/Draft/Archive）
6. プレビュー機能で公開後の表示を確認
7. 自動保存と手動保存

**画面遷移**：
- 遷移元：記事一覧画面、記事新規作成画面
- 遷移先：記事一覧画面（戻るボタン）、プレビューページ（新規タブ）

**権限による表示制御**：administrator、agent、knowledge_base_manage権限を持つユーザーがアクセス可能。HELP_CENTERフィーチャーフラグが有効である必要がある。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 154 | ポータル記事 | 主機能 | 既存記事の編集 |
| 155 | 記事順序変更 | 補助機能 | 記事の順序変更 |

## 画面種別

編集

## URL/ルーティング

| 項目 | 値 |
|------|-----|
| パス | `/app/accounts/:accountId/portals/:portalSlug/:locale/:categorySlug?/articles/:tab?/edit/:articleSlug` |
| ルート名 | `portals_articles_edit` |
| コンポーネント | `PortalsArticlesEditPage.vue` |

## 入出力項目

### 入力項目（URLパラメータ）

| 項目名 | 型 | 必須 | 説明 |
|--------|-----|------|------|
| accountId | Number | Yes | アカウントID |
| portalSlug | String | Yes | ポータルのスラッグ |
| locale | String | Yes | ロケールコード |
| categorySlug | String | No | カテゴリスラッグ |
| tab | String | No | タブ種別 |
| articleSlug | String | Yes | 記事のスラッグ（ID） |

### 入力項目（フォーム）

| 項目名 | 型 | 必須 | バリデーション | 説明 |
|--------|-----|------|--------------|------|
| title | String | Yes | 必須入力 | 記事タイトル |
| content | String | Yes | 必須入力 | 記事本文（HTML形式） |
| categoryId | Number | No | - | カテゴリID |
| authorId | Number | No | - | 著者ID |
| status | String | No | - | ステータス（draft/published/archived） |
| meta.title | String | No | - | メタタイトル |
| meta.description | String | No | - | メタ説明 |
| meta.tags | Array | No | - | メタタグ |

## 表示項目

### エディタ画面

| 項目名 | 型 | 説明 |
|--------|-----|------|
| タイトル入力フィールド | Input | 記事タイトル入力欄 |
| 本文エディタ | RichTextEditor | WYSIWYG形式の本文入力欄 |
| 戻るボタン | Button | 記事一覧への戻るボタン |
| 保存状態インジケータ | Text | "Saving..." / "Saved" |
| プレビューボタン | Button | 記事プレビュー |
| ステータス変更ドロップダウン | Dropdown | Publish/Draft/Archive |

### サイドバー（記事設定）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| カテゴリセレクト | Select | カテゴリ選択 |
| 著者セレクト | Select | 著者選択 |
| メタタイトル | Input | SEO用タイトル |
| メタ説明 | Textarea | SEO用説明文 |
| メタタグ | Input | カンマ区切りタグ |

## イベント仕様

### 1-画面初期表示

**トリガー**: コンポーネントのマウント時（onMounted）

**処理フロー**:
1. URLパラメータからarticleSlug, portalSlugを取得
2. `articles/show` アクションをディスパッチ
3. GET `/api/v1/accounts/:accountId/portals/:portalSlug/articles/:articleSlug` を呼び出し
4. 取得した記事データをエディタに表示

### 2-記事保存（手動）

**トリガー**: 保存ボタンクリックまたはCtrl+S

**処理フロー**:
1. saveArticle関数を実行（isAsync = false）
2. isUpdatingフラグをtrueに設定
3. `articles/update` アクションをディスパッチ
4. PATCH `/api/v1/accounts/:accountId/portals/:portalSlug/articles/:articleId` を呼び出し
5. 成功時：isSavedをtrueに設定
6. 失敗時：useAlertでエラーメッセージを表示

### 3-記事保存（自動）

**トリガー**: エディタ内容変更後一定時間経過

**処理フロー**:
1. saveArticleAsync関数を実行
2. `articles/updateAsync` アクションをディスパッチ
3. UIの更新なしでバックグラウンド保存

### 4-ステータス変更

**トリガー**: ステータスドロップダウン選択

**処理フロー**:
1. 選択されたステータス（published/draft/archived）を取得
2. saveArticle関数をステータスパラメータ付きで実行
3. 記事のステータスが更新される

### 5-プレビュー

**トリガー**: プレビューボタンクリック

**処理フロー**:
1. previewArticle関数を実行
2. buildPortalArticleURLでプレビューURLを構築
3. window.openで新規タブにプレビューを表示
4. イベントトラッキング（PORTALS_EVENTS.PREVIEW_ARTICLE）

### 6-記事一覧に戻る

**トリガー**: 戻るボタンクリック

**処理フロー**:
1. goBackToArticles関数を実行
2. isCategoryArticlesを確認して遷移先を決定
3. `portals_articles_index` または `portals_categories_articles_index` へ遷移

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 記事取得 | articles | SELECT | 記事詳細を取得 |
| 記事更新 | articles | UPDATE | 記事内容を更新 |

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

#### articles

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | title | フォーム入力値 | オプション |
| UPDATE | content | フォーム入力値（HTML） | オプション |
| UPDATE | category_id | 選択されたカテゴリID | オプション |
| UPDATE | author_id | 選択された著者ID | オプション |
| UPDATE | status | 選択されたステータス | オプション |
| UPDATE | meta | メタ情報JSON | オプション |
| UPDATE | updated_at | 現在日時 | 自動設定 |

## メッセージ仕様

| 種別 | メッセージキー | メッセージ内容 | 表示条件 |
|------|--------------|--------------|----------|
| 保存中 | HELP_CENTER.EDIT_ARTICLE_PAGE.HEADER.STATUS.SAVING | "Saving..." | 保存処理中 |
| 保存完了 | HELP_CENTER.EDIT_ARTICLE_PAGE.HEADER.STATUS.SAVED | "Saved" | 保存完了時 |
| プレビュー | HELP_CENTER.EDIT_ARTICLE_PAGE.HEADER.PREVIEW | "Preview" | 常時 |
| 公開 | HELP_CENTER.EDIT_ARTICLE_PAGE.HEADER.PUBLISH | "Publish" | 常時 |
| 下書き | HELP_CENTER.EDIT_ARTICLE_PAGE.HEADER.DRAFT | "Draft" | 常時 |
| アーカイブ | HELP_CENTER.EDIT_ARTICLE_PAGE.HEADER.ARCHIVE | "Archive" | 常時 |
| エラー | HELP_CENTER.EDIT_ARTICLE_PAGE.API.ERROR | "Error while saving article" | 保存失敗時 |

## 例外処理

| 例外状況 | 処理内容 |
|---------|---------|
| 記事が見つからない | エラー状態を表示 |
| 保存失敗 | useAlertでエラーメッセージを表示 |
| 権限不足 | ルートガードによりアクセス拒否 |

## 備考

- 自動保存機能により、編集中のデータ損失を防止
- プレビューURLはカスタムドメイン設定に対応
- カテゴリ経由でアクセスした場合、戻り先がカテゴリ記事一覧になる
- 保存状態インジケータは1.5秒後に自動的にリセット

---

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

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

### 推奨読解順序

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

記事の更新パラメータとステータス管理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Article (Model) | `app/models/article.rb` | statusのenum定義、metaカラム |
| 1-2 | articles_controller.rb | `app/controllers/api/v1/accounts/articles_controller.rb` | updateアクションのパラメータ |

**読解のコツ**: article_paramsメソッドで許可されているパラメータを確認する。

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

画面のエントリーポイントとコンポーネント構成を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | PortalsArticlesEditPage.vue | `app/javascript/dashboard/routes/dashboard/helpcenter/pages/PortalsArticlesEditPage.vue` | 画面コンポーネントのメイン処理 |
| 2-2 | helpcenter.routes.js | `app/javascript/dashboard/routes/dashboard/helpcenter/helpcenter.routes.js` | ルート定義 |

**主要処理フロー**:
1. **92-97行目**: fetchArticleDetails関数で記事取得
2. **43-63行目**: saveArticle関数で同期保存
3. **65-67行目**: saveArticleAsync関数で非同期保存
4. **99-104行目**: previewArticle関数でプレビュー

#### Step 3: Vuex Actionsを理解する

記事取得・更新のAPI呼び出し処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | actions.js (articles) | `app/javascript/dashboard/store/modules/helpCenterArticles/actions.js` | show, update, updateAsyncアクション |
| 3-2 | articles.js (API) | `app/javascript/dashboard/api/helpCenter/articles.js` | getArticle, updateArticle APIメソッド |

**主要処理フロー**:
- **58-70行目**: showアクション - 記事詳細取得
- **91-118行目**: updateアクション - 同期更新
- **72-89行目**: updateAsyncアクション - 非同期更新

#### Step 4: バックエンドAPIを理解する

RailsコントローラーでのAPI処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | articles_controller.rb | `app/controllers/api/v1/accounts/articles_controller.rb` | show, updateアクション |

**主要処理フロー**:
- **21行目**: showアクション
- **32-35行目**: updateアクション

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

```
PortalsArticlesEditPage.vue
    │
    ├─ onMounted()
    │      └─ fetchArticleDetails()
    │             └─ store.dispatch('articles/show')
    │                    └─ articlesAPI.getArticle()
    │                           └─ GET /api/v1/.../articles/:id
    │
    ├─ ArticleEditor.vue
    │      │
    │      ├─ @save-article
    │      │      └─ saveArticle()
    │      │             └─ store.dispatch('articles/update')
    │      │                    └─ articlesAPI.updateArticle()
    │      │                           └─ PATCH /api/v1/.../articles/:id
    │      │
    │      ├─ @save-article-async
    │      │      └─ saveArticleAsync()
    │      │             └─ store.dispatch('articles/updateAsync')
    │      │
    │      ├─ @preview-article
    │      │      └─ previewArticle()
    │      │             └─ window.open(articleLink)
    │      │
    │      └─ @go-back
    │             └─ goBackToArticles()
    │                    └─ router.push(...)
    │
    └─ computed: articleLink
           └─ buildPortalArticleURL()
```

### データフロー図

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

URLパラメータ ────────▶ fetchArticleDetails() ────▶ article (state)
  - articleSlug             │
  - portalSlug              ▼
                       ArticlesAPI.getArticle()
                            │
                            ▼
                       ArticlesController#show ────▶ article詳細
                            │
                            ▼
                       ArticleEditor.vue（表示）

フォーム編集 ─────────▶ saveArticle() ────────────▶ articles/update
  - title                   │
  - content                 ▼
  - status             ArticlesAPI.updateArticle()
  - meta                    │
                            ▼
                       ArticlesController#update ──▶ articles UPDATE
                            │
                            ▼
                       isSaved = true
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| PortalsArticlesEditPage.vue | `app/javascript/dashboard/routes/dashboard/helpcenter/pages/PortalsArticlesEditPage.vue` | ソース | 画面コンポーネント |
| ArticleEditor.vue | `app/javascript/dashboard/components-next/HelpCenter/Pages/ArticleEditorPage/ArticleEditor.vue` | ソース | エディタUI |
| helpcenter.routes.js | `app/javascript/dashboard/routes/dashboard/helpcenter/helpcenter.routes.js` | ソース | ルート定義 |
| actions.js | `app/javascript/dashboard/store/modules/helpCenterArticles/actions.js` | ソース | Vuexアクション |
| articles.js | `app/javascript/dashboard/api/helpCenter/articles.js` | ソース | APIクライアント |
| articles_controller.rb | `app/controllers/api/v1/accounts/articles_controller.rb` | ソース | APIコントローラー |
| article.rb | `app/models/article.rb` | ソース | モデル定義 |
| portalHelper.js | `app/javascript/dashboard/helper/portalHelper.js` | ソース | URL構築ヘルパー |
