# 画面設計書 31-メンション画面

## 概要

本ドキュメントは、Ghost管理画面のメンション画面（Webmention一覧表示）の設計内容を記載した画面設計書です。この画面は、外部サイトからの被リンク（Webmention）を一覧表示し、サイトへの言及状況を把握するための機能を提供します。

### 本画面の処理概要

この画面では、Webmention（他サイトからのリンク・言及）の一覧表示と確認を行います。

**業務上の目的・背景**：コンテンツマーケティングにおいて、自サイトが他サイトからどのように参照・言及されているかを把握することは重要です。この画面により、外部からの被リンク状況をリアルタイムで確認でき、コンテンツの影響力測定やエンゲージメント分析に活用できます。また、Webmentionは分散型Webの標準プロトコルであり、IndieWeb運動の一環として他サイトとの相互リンクを可視化する役割も担っています。

**画面へのアクセス方法**：
- ダッシュボードから「Mentions」メニューをクリック（全体のメンション一覧）
- 投稿分析画面（Posts > Analytics）のパンくずリストから「Mentions」をクリック（特定投稿へのメンション一覧）

**主要な操作・処理内容**：
1. メンション一覧の閲覧：外部サイトからの被リンク情報（ソースサイト名、タイトル、著者、抜粋、アイコン、サムネイル画像）を確認
2. メンションの詳細確認：各メンションカードをクリックして、ソースページへ遷移
3. 無限スクロールによる追加データ読み込み：スクロールにより自動的に追加のメンションを読み込み
4. 複数リンクの確認：同一ソースから複数のリンクがある場合、ポップオーバーで詳細を確認

**画面遷移**：
- 遷移元：ダッシュボード、投稿分析画面（posts-x）
- 遷移先：外部サイト（メンションのソースURL）、投稿一覧画面（パンくずリスト経由）

**権限による表示制御**：認証済みスタッフユーザーのみアクセス可能。Webmentions機能フラグが無効な場合は、投稿分析画面にリダイレクトされます。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 58 | Webmention | 主機能 | 投稿へのWebmention一覧表示、メンションデータの取得・表示 |

## 画面種別

一覧

## URL/ルーティング

| URL パターン | 説明 |
|-------------|------|
| `/ghost/mentions` | 全メンション一覧（サイト全体） |
| `/ghost/posts/analytics/:post_id/mentions` | 特定投稿へのメンション一覧 |

## 入出力項目

### 入力項目

| 項目名 | 型 | 必須 | 説明 |
|--------|-----|------|------|
| post_id | String | No | 特定投稿のメンションを表示する場合に指定 |

### パラメータ（API）

| パラメータ | 型 | デフォルト | 説明 |
|-----------|-----|-----------|------|
| limit | Number | 10 | 1ページあたりの取得件数 |
| page | Number | 1 | ページ番号 |
| order | String | 'created_at desc' | ソート順 |
| filter | String | - | NQLフィルタ文字列 |
| unique | Boolean | false | 同一ソースのメンションを1件にまとめるか |

## 表示項目

### メンションカード

| 項目名 | データソース | 説明 |
|--------|-------------|------|
| ソースファビコン | mention.sourceFavicon | ソースサイトのアイコン画像 |
| ソースサイト名 | mention.sourceSiteTitle | メンション元のサイト名 |
| リンク数 | mention.mentions.length | 同一ソースからのリンク数（複数の場合表示） |
| リンク先 | mention.target / mention.resource.name | メンションされた自サイトのURL/リソース名 |
| タイムスタンプ | mention.timestamp | メンション受信日時（相対時間で表示） |
| ソースタイトル | mention.sourceTitle | メンション元ページのタイトル |
| 抜粋 | mention.sourceExcerpt | メンション元ページの抜粋テキスト |
| 著者名 | mention.sourceAuthor | メンション元の著者名 |
| サムネイル画像 | mention.sourceFeaturedImage | メンション元のOGP画像等 |

### パンくずリスト

| 投稿指定時 | 投稿未指定時 |
|-----------|-------------|
| Posts > Analytics > Mentions | Dashboard > Mentions |

## イベント仕様

### 1-メンションカードクリック

**トリガー**: メンションカードをクリック

**処理内容**:
- 新しいタブでソースURL（mention.source）を開く
- `rel="noreferrer noopener"`属性により、セキュリティとプライバシーを保護

**データフロー**:
```
[ユーザークリック] → [window.open(mention.source, '_blank')]
```

### 2-複数リンクポップオーバー表示

**トリガー**: 複数リンクがある場合、リンク数表示部分にホバー

**処理内容**:
- EmberPopoverコンポーネントでポップオーバーを表示
- 同一ソースからの全リンク先を一覧表示

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

**トリガー**: 画面下部から1000pxの位置までスクロール

**処理内容**:
- GhInfinityLoaderコンポーネントが発火
- ember-infinityライブラリが次ページのメンションをAPI経由で取得
- 取得したデータを既存リストに追加表示

**API呼び出し**:
```
GET /ghost/api/admin/mentions/
  ?limit=10
  &page={nextPage}
  &order=created_at%20desc
  [&filter=resource_id:'{post_id}'+resource_type:post]
  [&unique=true]
```

### 4-パンくずリストナビゲーション

**トリガー**: パンくずリスト内のリンクをクリック

**処理内容**:
- 投稿指定時：Posts（投稿一覧）、Analytics（投稿分析画面）へ遷移
- 投稿未指定時：Dashboard（ダッシュボード）へ遷移

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 画面表示時 | mentions | SELECT | メンション一覧の取得 |
| 無限スクロール | mentions | SELECT | 追加メンションの取得 |

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

#### mentions（読み取りのみ）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id | 主キー | |
| SELECT | source | メンション元URL | 最大2000文字 |
| SELECT | source_title | メンション元ページタイトル | nullable |
| SELECT | source_site_title | メンション元サイト名 | nullable |
| SELECT | source_excerpt | メンション元抜粋 | nullable |
| SELECT | source_author | メンション元著者名 | nullable |
| SELECT | source_featured_image | メンション元サムネイル | nullable |
| SELECT | source_favicon | メンション元ファビコン | nullable |
| SELECT | target | メンション先URL（自サイト） | 最大2000文字 |
| SELECT | resource_id | リソースID（投稿ID等） | nullable |
| SELECT | resource_type | リソースタイプ（post等） | nullable |
| SELECT | created_at | 作成日時 | |
| SELECT | payload | 追加ペイロードデータ | JSON形式 |
| SELECT | deleted | 削除フラグ | デフォルト: false |
| SELECT | verified | 検証済みフラグ | デフォルト: false |

## メッセージ仕様

| メッセージ種別 | メッセージ内容 | 表示条件 |
|--------------|---------------|---------|
| 空状態 | No mentions yet | メンションが存在しない場合 |
| 空状態（補足） | When other sites mention your posts, they'll appear here. | メンションが存在しない場合 |
| 投稿メンション説明 | This post was mentioned in: | 特定投稿のメンション一覧で、メンションが存在する場合 |

## 例外処理

| 例外状態 | 処理内容 |
|---------|---------|
| Webmentions機能無効 | 投稿分析画面（posts-x）へリダイレクト |
| 未認証 | サインイン画面へリダイレクト（AuthenticatedRouteの処理） |
| API通信エラー | Emberのデフォルトエラーハンドリングに委譲 |
| 投稿が存在しない | 404エラーまたは空のメンション一覧を表示 |

## 備考

- Webmention機能はfeatureフラグ（`webmentions`）で制御されており、無効な場合は画面にアクセスできません
- メンションデータは外部サイトからWebmentionプロトコルで送信され、`/webmentions/receive`エンドポイントで受信・保存されます
- 同一ソースからの複数メンションは、`unique=true`パラメータで1件にグループ化され、`mentions`プロパティに複数のリンク情報が格納されます
- メンションの説明サイドバーには、Mentions機能の紹介テキストと背景画像が表示されます

---

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

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

### 推奨読解順序

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

まず、メンション（Webmention）のデータ構造を理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | mention.js | `ghost/admin/app/models/mention.js` | Ember Dataモデルの定義。source, target, timestamp等の属性を確認 |
| 1-2 | schema.js | `ghost/core/core/server/data/schema/schema.js` (L1059-1075) | DBスキーマ。mentionsテーブルの構造を確認 |

**読解のコツ**: Ember Dataモデルの`attr()`はフィールド定義、`Model.extend({})`はレガシー記法です。

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

処理の起点となるルーティングとルートファイルを確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | router.js | `ghost/admin/app/router.js` | L95: `this.route('mentions')`、L37: `posts.mentions`のルーティング定義 |
| 2-2 | mentions.js (route) | `ghost/admin/app/routes/mentions.js` | ルートクラス。モデルフック、フィーチャーフラグチェックを確認 |

**主要処理フロー**:
1. **L25-30**: `beforeModel`でwebmentionsフィーチャーフラグをチェック。無効なら`posts-x`にリダイレクト
2. **L33-56**: `model`フックでInfinityModelを使用してページネーション付きでメンションを取得
3. **L44-50**: post_id指定時はフィルタを適用、未指定時はunique=trueでグルーピング

#### Step 3: コントローラーとテンプレートを理解する

ビューロジックとUIを確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | mentions.js (controller) | `ghost/admin/app/controllers/mentions.js` | シンプルなコントローラー。modelからmentionsとpostを取得するgetter |
| 3-2 | mentions.hbs | `ghost/admin/app/templates/mentions.hbs` | Handlebarsテンプレート。メンションカードのレンダリングロジック |

**主要処理フロー**:
- **L4-22**: パンくずリストの条件分岐（投稿指定時/未指定時）
- **L44-96**: メンションカードの繰り返し表示（`{{#each}}`）
- **L53-66**: 複数リンク時のポップオーバー表示
- **L98-104**: 空状態のCTA表示
- **L105-108**: 無限スクロールローダー

#### Step 4: サービスとAPIを理解する

バックエンドのデータ取得ロジックを確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | mention-utils.js | `ghost/admin/app/services/mention-utils.js` | 同一ソースのメンションをグループ化するユーティリティ |
| 4-2 | mention.js (adapter) | `ghost/admin/app/adapters/mention.js` | APIエンドポイント（`/ghost/api/admin/mentions/`）の定義 |
| 4-3 | mentions-api.js | `ghost/core/core/server/services/mentions/mentions-api.js` | サーバーサイドのMentionsAPI。listMentionsメソッドを確認 |

**主要処理フロー**:
- **mention-utils.js L6-18**: `loadGroupedMentions`で同一ソースのメンションを取得し、`mentions`プロパティにセット

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

```
[ブラウザ] /ghost/mentions or /ghost/posts/analytics/:post_id/mentions
    │
    ├─ ghost/admin/app/router.js
    │      └─ route('mentions') / route('posts.mentions')
    │
    ├─ ghost/admin/app/routes/mentions.js
    │      ├─ beforeModel(): フィーチャーフラグチェック
    │      └─ model():
    │             ├─ this.infinity.model('mention', ...)
    │             │      └─ LoadSourceMentions.afterInfinityModel()
    │             │             └─ MentionUtilsService.loadGroupedMentions()
    │             └─ this.store.findRecord('post', post_id)
    │
    ├─ ghost/admin/app/adapters/mention.js
    │      └─ GET /ghost/api/admin/mentions/
    │
    ├─ [Ghost Core API]
    │      └─ ghost/core/core/server/services/mentions/mentions-api.js
    │             └─ listMentions() → MentionRepository.getPage()
    │
    └─ ghost/admin/app/templates/mentions.hbs
           └─ GhInfinityLoader (無限スクロール)
```

### データフロー図

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

                     ┌─────────────────────────────┐
URL params ─────────▶│ MentionsRoute.model()       │
(post_id)            │   - infinity.model()        │
                     │   - store.findRecord()      │
                     └────────────┬────────────────┘
                                  │
                     ┌────────────▼────────────────┐
                     │ Mention Adapter             │
                     │   GET /api/admin/mentions/  │
                     └────────────┬────────────────┘
                                  │
                     ┌────────────▼────────────────┐
                     │ MentionsAPI.listMentions()  │
                     │   - filter適用              │
                     │   - ページネーション        │
                     └────────────┬────────────────┘
                                  │
                     ┌────────────▼────────────────┐
[DB: mentions] ◀────▶│ MentionRepository.getPage() │
                     └────────────┬────────────────┘
                                  │
                     ┌────────────▼────────────────┐
                     │ MentionUtils.loadGrouped... │───▶ [Controller.model]
                     │   - 同一ソースをグループ化  │
                     └─────────────────────────────┘
                                                              │
                                                              ▼
                                              ┌───────────────────────────┐
                                              │ mentions.hbs テンプレート  │
                                              │   - メンションカード表示   │
                                              │   - 無限スクロール         │
                                              └───────────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| router.js | `ghost/admin/app/router.js` | ソース | ルーティング定義 |
| mentions.js | `ghost/admin/app/routes/mentions.js` | ソース | ルートクラス（データ取得） |
| mentions.js | `ghost/admin/app/controllers/mentions.js` | ソース | コントローラー |
| mentions.hbs | `ghost/admin/app/templates/mentions.hbs` | テンプレート | 画面テンプレート |
| mention.js | `ghost/admin/app/models/mention.js` | ソース | Ember Dataモデル |
| mention.js | `ghost/admin/app/adapters/mention.js` | ソース | APIアダプター |
| mention-utils.js | `ghost/admin/app/services/mention-utils.js` | ソース | グループ化ユーティリティ |
| mentions.css | `ghost/admin/app/styles/layouts/mentions.css` | スタイル | 画面固有のCSS |
| mentions.js | `ghost/admin/app/routes/posts/mentions.js` | ソース | 投稿別メンションルート |
| mentions-api.js | `ghost/core/core/server/services/mentions/mentions-api.js` | ソース | サーバーサイドAPI |
| routes.js | `ghost/core/core/server/web/webmentions/routes.js` | ソース | Webmention受信ルート |
| schema.js | `ghost/core/core/server/data/schema/schema.js` | ソース | DBスキーマ定義 |
