# 画面設計書 15-タグ一覧画面

## 概要

本ドキュメントは、Ghost管理画面におけるタグ一覧画面の設計仕様を定義するものである。

### 本画面の処理概要

タグ一覧画面は、サイトで使用されているタグを一覧表示し、管理するための画面である。React（postsアプリ）で実装されており、公開タグと内部タグをタブで切り替えて表示できる。

**業務上の目的・背景**：タグはコンテンツの分類・整理に不可欠な機能である。本画面により、既存タグの確認、新規タグの作成、タグ詳細への遷移が可能となる。公開タグ（読者に表示）と内部タグ（管理用、#で始まる）を区別して管理できる。

**画面へのアクセス方法**：サイドバーの「Tags」メニューをクリック、またはURL `/ghost/#/tags` で直接アクセス可能。

**主要な操作・処理内容**：
1. タグ一覧の表示（仮想スクロール対応）
2. 公開タグ/内部タグのタブ切り替え
3. タグ詳細画面への遷移
4. 新規タグ作成画面への遷移
5. 関連投稿数の確認
6. タグに関連する投稿一覧への遷移

**画面遷移**：
- 遷移元: サイドバーメニュー
- 遷移先: タグ編集画面、投稿一覧画面（タグフィルタ適用）

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

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | タグ管理 | 主機能 | タグの一覧表示 |

## 画面種別

一覧

## URL/ルーティング

| 項目 | 値 |
|------|-----|
| URL | `/ghost/#/tags` |
| URL（内部タグ） | `/ghost/#/tags?type=internal` |
| ルート名 | tags |
| ルートファイル | `ghost/admin/app/routes/tags.js` |
| Reactコンポーネント | `apps/posts/src/views/Tags/tags.tsx` |

### クエリパラメータ

| パラメータ名 | 型 | デフォルト値 | 説明 |
|-------------|-----|-------------|------|
| type | string | 'public' | タグ種別（public/internal） |

## 入出力項目

### 入力項目

| 項目名 | 項目ID | 必須 | 型 | 説明 |
|--------|--------|------|-----|------|
| タブ選択 | type | - | toggle | 公開タグ/内部タグの切り替え |

## 表示項目

### ヘッダー部

| 項目名 | 説明 |
|--------|------|
| タイトル | 「Tags」固定 |
| タブ切り替え | 「Public tags」/「Internal tags」トグルグループ |
| 新規作成ボタン | 「New tag」ボタン（#/tags/newへ遷移） |

### タグ一覧テーブル

| 項目名 | フィールド | 説明 |
|--------|-----------|------|
| タグ名 | name | タグの名称（リンク付き） |
| 説明 | description | タグの説明文 |
| スラッグ | slug | タグのURLスラッグ |
| 投稿数 | count.posts | 関連する投稿の数（リンク付き） |
| 編集ボタン | - | 鉛筆アイコンボタン |

### 空状態表示

| 条件 | 表示内容 |
|------|---------|
| タグなし | 「Start organizing your content」メッセージと「Create a new tag」ボタン |
| 読み込みエラー | 「Error loading tags」メッセージと「Reload page」ボタン |

## イベント仕様

### 1-タブ切り替え

- 処理: type クエリパラメータを変更し、APIを再フェッチ
- 「Public tags」: `/tags`（type=public）
- 「Internal tags」: `/tags?type=internal`

### 2-タグ行クリック

- 処理: タグ詳細画面へ遷移
- 遷移先URL: `#/tags/{slug}`

### 3-投稿数リンククリック

- 処理: 投稿一覧画面へ遷移（タグフィルタ適用）
- 遷移先URL: `#/posts?tag={slug}`

### 4-新規タグボタンクリック

- 処理: タグ新規作成画面へ遷移
- 遷移先URL: `#/tags/new`

### 5-無限スクロール

- 処理: useInfiniteVirtualScrollによる仮想スクロールと追加ロード
- fetchNextPage()でAPIから追加データ取得

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| タグ一覧表示 | tags | SELECT | タグデータの取得 |

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

#### tags

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, name, slug, description | visibility フィルタ条件 | |
| SELECT | count.posts | 関連投稿数 | include指定 |

## メッセージ仕様

| メッセージID | 種別 | 条件 | メッセージ内容 |
|-------------|------|------|---------------|
| MSG-01 | 情報 | タグなし | Start organizing your content |
| MSG-02 | エラー | 読み込みエラー | Error loading tags |
| MSG-03 | 情報 | エラー説明 | Please reload the page to try again |

## 例外処理

| 例外ケース | 処理内容 |
|-----------|---------|
| 権限不足 | ホーム画面へリダイレクト |
| API通信エラー | エラーメッセージとリロードボタン表示 |

## 備考

- タグ一覧画面はReact（postsアプリ）で実装されている
- Emberルートは権限チェックのみ行い、実際のUIはAdminX::Postsコンポーネント経由でReactをレンダリング
- useBrowseTagsフックでAPIからタグを取得
- 仮想スクロールでパフォーマンスを最適化
- 内部タグは名前が#で始まるタグで、管理用に使用される

---

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

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

### 推奨読解順序

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

タグモデルの属性定義を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | tag.js | `ghost/admin/app/models/tag.js` | Ember Dataモデル定義 |

**主要処理フロー**:
- **11-31行目**: タグの属性定義（name, slug, description, visibility等）
- **33-34行目**: isInternal/isPublic computed
- **38-41行目**: updateVisibility - #で始まる名前は内部タグ

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

権限チェックとReact埋め込みの仕組みを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | tags.js | `ghost/admin/app/routes/tags.js` | 権限チェック、model=null |
| 2-2 | tags.hbs | `ghost/admin/app/templates/tags.hbs` | AdminX::Postsコンポーネント呼び出し |

**主要処理フロー（tags.js）**:
- **5-10行目**: beforeModel - Author/Contributorはホームへリダイレクト
- **13-15行目**: model - nullを返す（ReactがAPIを直接呼び出す）

#### Step 3: Reactコンポーネントを理解する

メインのUIロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | tags.tsx | `apps/posts/src/views/Tags/tags.tsx` | メインコンポーネント、useBrowseTags |
| 3-2 | tags-header.tsx | `apps/posts/src/views/Tags/components/tags-header.tsx` | ヘッダー、タブ切り替え |
| 3-3 | tags-list.tsx | `apps/posts/src/views/Tags/components/tags-list.tsx` | リスト表示、仮想スクロール |

**主要処理フロー（tags.tsx）**:
- **11-13行目**: URLからtypeパラメータ取得（デフォルト: public）
- **15-26行目**: useBrowseTags - APIからタグ取得
- **32-68行目**: 状態別レンダリング（ローディング、エラー、空、リスト）

**主要処理フロー（tags-list.tsx）**:
- **41-62行目**: TagsListコンポーネントprops定義
- **55-62行目**: useInfiniteVirtualScroll使用
- **84-144行目**: テーブル行のレンダリング

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

```
TagsRoute (ghost/admin/app/routes/tags.js)
    │
    ├─ beforeModel() - 権限チェック
    │      └─ Author/Contributor → home へリダイレクト
    │
    └─ model() → null

tags.hbs (テンプレート)
    │
    └─ AdminX::Posts
           └─ React App (apps/posts)

Tags (apps/posts/src/views/Tags/tags.tsx)
    │
    ├─ useLocation() - URLからtype取得
    │
    ├─ useBrowseTags({filter: {visibility: type}})
    │      └─ API GET /tags?filter=visibility:{type}
    │
    └─ レンダリング分岐
           ├─ isLoading → LoadingIndicator
           ├─ isError → エラーメッセージ
           ├─ !data?.tags.length → EmptyIndicator
           └─ TagsList
                  └─ useInfiniteVirtualScroll
                         └─ 仮想スクロール + 追加ロード
```

### データフロー図

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

URLクエリパラメータ ───────▶ useLocation() ───────────────────▶ type ('public'/'internal')
(?type=internal)

type ──────────────────────▶ useBrowseTags() ────────────────▶ タグ配列
                                  │
                                  └─ API GET /tags
                                       filter=visibility:{type}
                                       include=count.posts

タグ配列 ─────────────────▶ TagsList ────────────────────────▶ テーブル表示
                                  │
                                  └─ useInfiniteVirtualScroll
                                         └─ 仮想スクロール
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| tags.js | `ghost/admin/app/routes/tags.js` | ルート | 権限チェック |
| tags.hbs | `ghost/admin/app/templates/tags.hbs` | テンプレート | React埋め込み |
| posts.js | `ghost/admin/app/components/admin-x/posts.js` | コンポーネント | Reactアプリラッパー |
| tags.tsx | `apps/posts/src/views/Tags/tags.tsx` | React | メインコンポーネント |
| tags-header.tsx | `apps/posts/src/views/Tags/components/tags-header.tsx` | React | ヘッダー |
| tags-list.tsx | `apps/posts/src/views/Tags/components/tags-list.tsx` | React | リスト表示 |
| tags-content.tsx | `apps/posts/src/views/Tags/components/tags-content.tsx` | React | コンテンツラッパー |
| tags-layout.tsx | `apps/posts/src/views/Tags/components/tags-layout.tsx` | React | レイアウト |
| tag.js | `ghost/admin/app/models/tag.js` | モデル | タグデータ構造（Ember） |
