# 画面設計書 16-タグ編集画面

## 概要

本ドキュメントは、Ghost管理画面におけるタグ編集画面（新規作成・編集兼用）の設計仕様を定義するものである。

### 本画面の処理概要

タグ編集画面は、タグの新規作成および既存タグの編集を行うための画面である。基本情報、SEO設定、ソーシャルメディア設定、コードインジェクションなど、タグに関する詳細設定が可能。

**業務上の目的・背景**：タグはコンテンツの分類・整理の基盤となる機能である。本画面により、タグの名称、スラッグ、説明文の設定はもちろん、タグページのSEO最適化、ソーシャルシェア時の表示内容のカスタマイズ、タグ固有のカスタムコード挿入が可能となる。

**画面へのアクセス方法**：
- タグ一覧画面からタグをクリック
- タグ一覧画面の「New tag」ボタンをクリック
- URL `/ghost/#/tags/{slug}` または `/ghost/#/tags/new` で直接アクセス

**主要な操作・処理内容**：
1. タグ名の入力（#で始めると内部タグ）
2. アクセントカラーの設定
3. スラッグの設定
4. 説明文の入力（最大500文字）
5. タグ画像のアップロード
6. SEOメタデータの設定
7. Facebook/Twitter設定
8. コードインジェクション設定
9. 保存・削除操作

**画面遷移**：
- 遷移元: タグ一覧画面
- 遷移先: タグ一覧画面、タグページ（外部リンク）

**権限による表示制御**：
- Author/Contributor: 本画面にアクセス不可（ホームへリダイレクト）
- Editor/Administrator/Owner: 全機能利用可能

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | タグ管理 | 主機能 | タグの作成・編集・削除 |
| 38 | SEO設定 | 補助機能 | タグのメタデータ設定 |

## 画面種別

登録 / 編集

## URL/ルーティング

| 項目 | 値 |
|------|-----|
| 新規作成URL | `/ghost/#/tags/new` |
| 編集URL | `/ghost/#/tags/{slug}` |
| ルート名 | tag |
| ルートファイル | `ghost/admin/app/routes/tag.js` |

### ルートパラメータ

| パラメータ名 | 型 | 説明 |
|-------------|-----|------|
| tag_slug | string | タグのスラッグ（新規作成時は'new'） |

## 入出力項目

### 入力項目（基本設定）

| 項目名 | 項目ID | 必須 | 型 | 説明 | バリデーション |
|--------|--------|------|-----|------|---------------|
| 名前 | name | 必須 | text | タグの名称 | 最大191文字 |
| カラー | accentColor | - | hex | アクセントカラー | 6桁のHEXコード |
| スラッグ | slug | 必須 | text | URLスラッグ | 英数字とハイフン |
| 説明 | description | - | textarea | タグの説明 | 最大500文字 |
| タグ画像 | featureImage | - | image | タグのアイキャッチ画像 | |

### 入力項目（SEO設定）

| 項目名 | 項目ID | 必須 | 型 | 説明 |
|--------|--------|------|-----|------|
| メタタイトル | metaTitle | - | text | SEO用タイトル |
| メタ説明 | metaDescription | - | textarea | SEO用説明文 |
| Canonical URL | canonicalUrl | - | url | 正規URL |

### 入力項目（ソーシャル設定）

| 項目名 | 項目ID | 必須 | 型 | 説明 |
|--------|--------|------|-----|------|
| Facebookタイトル | ogTitle | - | text | OG:title |
| Facebook説明 | ogDescription | - | textarea | OG:description |
| Facebook画像 | ogImage | - | image | OG:image |
| Twitterタイトル | twitterTitle | - | text | Twitter用タイトル |
| Twitter説明 | twitterDescription | - | textarea | Twitter用説明文 |
| Twitter画像 | twitterImage | - | image | Twitter用画像 |

### 入力項目（コードインジェクション）

| 項目名 | 項目ID | 必須 | 型 | 説明 |
|--------|--------|------|-----|------|
| ヘッダー | codeinjectionHead | - | code | タグページのhead内に挿入 |
| フッター | codeinjectionFoot | - | code | タグページのbody終了前に挿入 |

## 表示項目

### ヘッダー部

| 項目名 | 説明 |
|--------|------|
| パンくずリスト | Tags > New tag / Edit tag |
| タイトル | 新規時: 「New tag」、編集時: タグ名 |
| Viewボタン | タグページを新規タブで開く |
| 保存ボタン | Cmd/Ctrl+Sで保存可能 |

### フォーム部

| セクション | 内容 |
|-----------|------|
| 基本設定 | 名前、カラー、スラッグ、説明、タグ画像 |
| Expand: Meta data | SEO設定、Google検索プレビュー |
| Expand: X/Twitter card | Twitter設定、プレビュー |
| Expand: Facebook card | Facebook設定、プレビュー |
| Expand: Code injection | ヘッダー/フッターコード |

### 削除ボタン

| 条件 | 表示 |
|------|------|
| 既存タグ編集時 | 「Delete tag」ボタン表示 |
| 新規作成時 | 非表示 |

## イベント仕様

### 1-名前入力

- 処理: name属性を更新
- 自動処理: #で始まる場合はvisibilityをinternalに設定
- バリデーション: blur時にバリデーション実行

### 2-カラー入力

- 処理: accentColor属性を更新
- 入力方式: HEXコード直接入力またはカラーピッカー
- デバウンス: 入力に対してデバウンス処理

### 3-スラッグ入力

- 処理: slug属性を更新
- バリデーション: blur時にバリデーション実行
- URLプレビュー: 入力に応じてリアルタイムプレビュー

### 4-説明入力

- 処理: description属性を更新
- カウンター: 残り文字数をリアルタイム表示（最大500文字）

### 5-保存ボタン押下

- 処理: saveTaskを実行
- バリデーション: エラーがある場合は保存中止
- 新規作成時: 保存後にスラッグベースのURLにリダイレクト
- 成功時: tagsManagerのInfinityModelに追加（新規時）
- エラー時: APIエラー通知表示

### 6-削除ボタン押下

- 処理: DeleteTagModalを表示
- 確認後: タグを削除し、タグ一覧画面へ遷移

### 7-Viewボタン押下

- 処理: タグページを新規タブで開く
- URL: {blogUrl}/tag/{slug}/ または canonicalUrl

### 8-画面離脱時

- 処理: willTransitionで未保存変更をチェック
- 未保存あり: ConfirmUnsavedChangesModalを表示
- 確認後: rollbackAttributesで変更を破棄して遷移

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 新規作成 | tags | INSERT | タグレコードの作成 |
| 保存 | tags | UPDATE | タグ情報の更新 |
| 削除 | tags | DELETE | タグレコードの削除 |

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

#### tags

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | name | ユーザー入力 | 必須 |
| INSERT/UPDATE | slug | ユーザー入力または自動生成 | 一意 |
| INSERT/UPDATE | description | ユーザー入力 | 最大500文字 |
| INSERT/UPDATE | visibility | public/internal | 名前が#で始まるとinternal |
| INSERT/UPDATE | accent_color | HEXコード | |
| INSERT/UPDATE | feature_image | 画像URL | |
| INSERT/UPDATE | meta_title, meta_description | ユーザー入力 | SEO設定 |
| INSERT/UPDATE | og_title, og_description, og_image | ユーザー入力 | Facebook設定 |
| INSERT/UPDATE | twitter_title, twitter_description, twitter_image | ユーザー入力 | Twitter設定 |
| INSERT/UPDATE | codeinjection_head, codeinjection_foot | ユーザー入力 | コードインジェクション |
| INSERT/UPDATE | canonical_url | ユーザー入力 | 正規URL |

## メッセージ仕様

| メッセージID | 種別 | 条件 | メッセージ内容 |
|-------------|------|------|---------------|
| MSG-01 | 情報 | 名前ヘルプ | Start with # to create internal tags. |
| MSG-02 | 情報 | 説明文制限 | Maximum: 500 characters. You've used {count} |
| MSG-03 | エラー | APIエラー | （APIから返却されるエラーメッセージ） |

## 例外処理

| 例外ケース | 処理内容 |
|-----------|---------|
| 権限不足 | ホーム画面へリダイレクト |
| タグが見つからない | エラー表示 |
| バリデーションエラー | フィールドにエラーメッセージ表示 |
| API保存エラー | APIエラー通知表示 |
| 未保存で離脱 | 確認モーダル表示 |

## 備考

- タグ名が#で始まる場合、自動的に内部タグ（visibility=internal）として設定される
- 内部タグはサイト上では非表示で、管理用途に使用される
- タグ保存時にsearchサービスのコンテンツキャッシュが無効化される
- 新規作成後は自動的に編集URLにリダイレクトされる
- タグ画像はUnsplashからの選択も可能

---

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

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

### 推奨読解順序

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

タグモデルの属性と保存処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | tag.js | `ghost/admin/app/models/tag.js` | 属性定義、updateVisibility、saveフック |

**主要処理フロー**:
- **11-31行目**: 全属性の定義
- **38-41行目**: updateVisibility - #で始まる名前は内部タグ
- **43-61行目**: saveフック - visibility更新、searchキャッシュ無効化

#### Step 2: ルートを理解する

データ取得と未保存確認の仕組みを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | tag.js | `ghost/admin/app/routes/tag.js` | model取得、willTransition、未保存確認 |

**主要処理フロー**:
- **16-22行目**: beforeModel - 権限チェック
- **24-32行目**: model - スラッグでクエリまたは新規作成
- **54-77行目**: willTransition - 未保存変更の確認処理
- **79-92行目**: confirmUnsavedChanges - 確認モーダル表示

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

保存・削除タスクを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | tag.js | `ghost/admin/app/controllers/tag.js` | saveTask、confirmDeleteTag |

**主要処理フロー**:
- **16-18行目**: tag getter
- **20-31行目**: tagURL getter - タグページURLの生成
- **33-38行目**: confirmDeleteTag - 削除確認モーダル
- **40-63行目**: saveTask - 保存処理、InfinityModel更新

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

画面構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | tag.hbs | `ghost/admin/app/templates/tag.hbs` | 画面レイアウト、フォーム配置 |

**主要処理フロー**:
- **3-28行目**: ヘッダー（パンくず、タイトル、ボタン）
- **30行目**: Tags::TagFormコンポーネント呼び出し
- **33-39行目**: 削除ボタン（既存タグのみ表示）

#### Step 5: フォームコンポーネントを理解する

入力フォームの詳細を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | tag-form.hbs | `ghost/admin/app/components/tags/tag-form.hbs` | フォーム項目の定義 |

**主要処理フロー**:
- **5-25行目**: 名前入力（ヘルプテキスト含む）
- **27-57行目**: カラー入力（HEXとピッカー）
- **60-74行目**: スラッグ入力（URLプレビュー付き）
- **76-89行目**: 説明入力（文字カウント付き）
- **91-100行目**: タグ画像アップロード

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

```
TagRoute (ghost/admin/app/routes/tag.js)
    │
    ├─ beforeModel() - 権限チェック
    │
    ├─ model(params)
    │      ├─ tag_slug あり → store.queryRecord('tag', {slug})
    │      └─ tag_slug なし → store.createRecord('tag')
    │
    └─ willTransition()
           └─ confirmUnsavedChanges()
                  └─ ConfirmUnsavedChangesModal

TagController (ghost/admin/app/controllers/tag.js)
    │
    ├─ tagURL getter - タグページURL生成
    │
    ├─ confirmDeleteTag()
    │      └─ DeleteTagModal
    │
    └─ saveTask()
           ├─ tag.save()
           ├─ tagsManager.tagsScreenInfinityModel.pushObjects()
           └─ replaceRoute('tag', tag)

tag.hbs (テンプレート)
    │
    ├─ GhCanvasHeader
    │      ├─ パンくず（Tags > Edit tag）
    │      ├─ タイトル
    │      ├─ Viewボタン
    │      └─ GhTaskButton（保存）
    │
    ├─ Tags::TagForm
    │      ├─ 名前、カラー、スラッグ、説明
    │      ├─ タグ画像
    │      ├─ Expand: Meta data
    │      ├─ Expand: X/Twitter card
    │      ├─ Expand: Facebook card
    │      └─ Expand: Code injection
    │
    └─ 削除ボタン（既存タグのみ）
```

### データフロー図

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

URLパラメータ ──────────────▶ model() ─────────────────────────▶ タグモデル
(tag_slug)                        │
                                  ├─ queryRecord('tag', {slug})
                                  └─ createRecord('tag')

フォーム入力 ──────────────▶ setTagProperty ─────────────────▶ tag属性更新
                                  │
                                  └─ validateTagProperty（blur時）

保存ボタン ────────────────▶ saveTask ───────────────────────▶ API保存
                                  │
                                  ├─ tag.save()
                                  │      ├─ updateVisibility()
                                  │      └─ search.expireContent()
                                  │
                                  └─ replaceRoute('tag', tag)

画面離脱 ─────────────────▶ willTransition ─────────────────▶ 確認/遷移
                                  │
                                  └─ hasDirtyAttributes?
                                         └─ ConfirmUnsavedChangesModal
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| tag.js | `ghost/admin/app/routes/tag.js` | ルート | データ取得、未保存確認 |
| tag.js | `ghost/admin/app/controllers/tag.js` | コントローラー | 保存・削除処理 |
| tag.hbs | `ghost/admin/app/templates/tag.hbs` | テンプレート | 画面レイアウト |
| tag.js | `ghost/admin/app/models/tag.js` | モデル | タグデータ構造 |
| tag-form.hbs | `ghost/admin/app/components/tags/tag-form.hbs` | テンプレート | フォームUI |
| tag-form.js | `ghost/admin/app/components/tags/tag-form.js` | コンポーネント | フォームロジック |
| delete-tag-modal.js | `ghost/admin/app/components/tags/delete-tag-modal.js` | モーダル | 削除確認 |
| confirm-unsaved-changes.js | `ghost/admin/app/components/modals/confirm-unsaved-changes.js` | モーダル | 未保存確認 |
