# 画面設計書 100-検索モーダル

## 概要

本ドキュメントは、Ghost公開画面における「検索モーダル」（Sodo Search）の画面設計書です。サイト内のコンテンツ（投稿、著者、タグ）を検索するためのモーダルウィジェットです。

### 本画面の処理概要

検索モーダルは、公開サイトに埋め込まれるサイト内検索機能を提供するウィジェットです。Flexsearchを使用したクライアントサイド検索により、高速なインクリメンタル検索を実現します。

**業務上の目的・背景**：読者がサイト内のコンテンツを素早く見つけられるようにすることで、サイトのユーザビリティとエンゲージメントを向上させます。投稿、著者、タグの3種類のコンテンツを横断的に検索できるため、読者は目的のコンテンツに効率的にアクセスできます。

**画面へのアクセス方法**：
- URLハッシュ `#/search` または `#/search/` でアクセス
- `data-ghost-search` 属性を持つカスタムトリガー要素のクリック
- キーボードショートカット Cmd/Ctrl + K

**主要な操作・処理内容**：
1. 検索キーワードの入力
2. リアルタイムでの検索結果表示（投稿、著者、タグ）
3. キーボード操作による結果の選択（上下矢印キー）
4. Enterキーまたはクリックで結果ページへ遷移
5. ESCキーまたは背景クリックでモーダルを閉じる
6. 「Show more results」で追加の投稿を表示

**画面遷移**：
- 検索結果クリック/Enter: 該当コンテンツのページへ遷移
- ESC/背景クリック: モーダルを閉じる

**権限による表示制御**：
- 全ての訪問者が利用可能（認証不要）

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 49 | サイト内検索 | 主機能 | サイト内コンテンツの検索 |
| 74 | Search（Sodo Search） | 補助機能 | 検索UIウィジェットの表示 |

## 画面種別

検索（モーダル）

## URL/ルーティング

- ハッシュルート: `#/search` または `#/search/`
- トリガー属性: `data-ghost-search`

## 入出力項目

| 項目名 | 入出力 | データ型 | 必須 | 説明 |
|--------|--------|----------|------|------|
| searchValue | 入力 | string | - | 検索キーワード |
| posts | 出力 | Post[] | - | 検索結果（投稿） |
| authors | 出力 | Author[] | - | 検索結果（著者） |
| tags | 出力 | Tag[] | - | 検索結果（タグ） |

## 表示項目

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| searchValue | string | 現在の検索キーワード |
| post.title | string | 投稿タイトル |
| post.excerpt | string | 投稿の抜粋 |
| post.url | string | 投稿のURL |
| author.name | string | 著者名 |
| author.profile_image | string | 著者のプロフィール画像 |
| author.url | string | 著者ページのURL |
| tag.name | string | タグ名 |
| tag.url | string | タグページのURL |
| indexComplete | boolean | インデックス構築完了フラグ |

## イベント仕様

### 1-検索キーワード入力

検索フィールドへの入力でリアルタイム検索を実行します。

- 処理: `dispatch('update', {searchValue})` → `searchIndex.search(value)`
- 結果: 投稿、著者、タグの検索結果が表示される
- インデックス未完了時: ローディングアニメーション表示

### 2-検索結果の選択（キーボード）

上下矢印キーで検索結果を選択します。

- ArrowUp: 前の結果を選択
- ArrowDown: 次の結果を選択
- Enter: 選択された結果のURLへ遷移

### 3-検索結果のクリック

検索結果をクリックして該当ページへ遷移します。

- 処理: `window.location.href = url`
- 結果: 該当コンテンツのページへ遷移

### 4-モーダルを閉じる

ESCキーまたは背景クリックでモーダルを閉じます。

- 処理: `dispatch('update', {showPopup: false})`
- 結果: モーダルが閉じ、検索値がクリアされる

### 5-検索クリア

検索フィールドのクリアアイコンをクリックして検索をクリアします。

- 処理: `dispatch('update', {searchValue: ''})`
- 結果: 検索フィールドがクリアされ、結果が非表示になる

### 6-さらに表示（Show more results）

投稿結果の「Show more results」ボタンで追加結果を表示します。

- 処理: `setMaxPosts(maxPosts + STEP_MAX_POSTS)`
- STEP_MAX_POSTS: 10
- 初期表示: DEFAULT_MAX_POSTS（10件）

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| インデックス構築 | posts | SELECT | 投稿データの取得 |
| インデックス構築 | users | SELECT | 著者データの取得 |
| インデックス構築 | tags | SELECT | タグデータの取得 |

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

本画面はデータの更新を行わず、検索用インデックスの構築と検索のみを行う。

## メッセージ仕様

| メッセージID | 種別 | 表示条件 | メッセージ内容 |
|-------------|------|---------|--------------|
| MSG001 | プレースホルダー | 常時 | "Search posts, tags and authors" |
| MSG002 | セクション | 投稿結果あり | "Posts" |
| MSG003 | セクション | 著者結果あり | "Authors" |
| MSG004 | セクション | タグ結果あり | "Tags" |
| MSG005 | 結果なし | 検索結果0件 | "No matches found" |
| MSG006 | ボタン | さらに結果あり | "Show more results" |
| MSG007 | ボタン | モバイル | "Cancel" |

## 例外処理

| 例外種別 | 発生条件 | 対応処理 |
|---------|---------|---------|
| インデックス構築エラー | API呼び出し失敗 | コンソールにエラーを出力、空配列を返す |
| 無効なURL | 404ページのURL | 検索結果からフィルタリングで除外 |

## 備考

- Flexsearchライブラリを使用したクライアントサイド検索
- CJK（中国語、日本語、韓国語）の文字対応（cjkEncoderPresetCodepoint）
- RTL（右から左）言語対応（tokenize: 'reverse'）
- 検索インデックスはモーダル初回表示時に構築（遅延ロード）
- ハイライト機能: 検索キーワードが結果テキスト内でボールド表示
- 背景はbackdrop-blur効果付き

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | search-index.js | `apps/sodo-search/src/search-index.js` | SearchIndexクラス、Flexsearch設定（1-228行目） |

**読解のコツ**:
- Flexsearch.Documentを使用してposts, authors, tagsの3つのインデックスを構築
- CJK対応のためのカスタムエンコーダー（tokenizeCjkByCodePoint）

#### Step 2: アプリケーション構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | app.js | `apps/sodo-search/src/app.js` | アプリケーションルート、状態管理（1-213行目） |

**主要処理フロー**:
1. **16-20行目**: SearchIndexインスタンスの作成
2. **22-33行目**: 初期状態の設定
3. **42-77行目**: componentDidUpdateでモーダル表示/非表示のスクロール制御
4. **79-87行目**: setupSearchIndexでインデックス構築（遅延ロード）
5. **95-99行目**: initSetupでイベントリスナー登録
6. **123-155行目**: setupCustomTriggerButtonでトリガーボタン設定
7. **166-183行目**: addKeyboardShortcutsでCmd+Kショートカット設定
8. **185-212行目**: renderでAppContext.Provider

#### Step 3: 検索UIコンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | popup-modal.js | `apps/sodo-search/src/components/popup-modal.js` | 検索モーダルコンポーネント（1-695行目） |

**主要処理フロー**:
- **73-126行目**: SearchBox - 検索入力フィールド
- **145-170行目**: CancelButton - モバイル用キャンセルボタン
- **172-219行目**: TagResults - タグ検索結果
- **221-248行目**: PostListItem - 投稿検索結果アイテム
- **250-342行目**: HighlightedSection - 検索キーワードハイライト
- **377-407行目**: PostResults - 投稿検索結果リスト
- **409-469行目**: AuthorResults - 著者検索結果
- **471-508行目**: SearchResultBox - 検索結果全体
- **510-581行目**: Results - 結果表示＋キーボードナビゲーション
- **592-614行目**: Search - 検索全体のレイアウト

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

```
App.js
    │
    ├─ constructor()
    │      └─ new SearchIndex({adminUrl, apiKey, dir})
    │
    ├─ componentDidMount()
    │      ├─ initSetup()
    │      │      ├─ handleSearchUrl() - #/search URL対応
    │      │      ├─ addKeyboardShortcuts() - Cmd+K
    │      │      └─ setupCustomTriggerButton() - data-ghost-search
    │      │
    │      └─ hashchange listener
    │
    ├─ componentDidUpdate()
    │      └─ setupSearchIndex() (showPopup && !indexStarted)
    │             └─ searchIndex.init()
    │                    ├─ populatePostIndex()
    │                    ├─ populateAuthorsIndex()
    │                    └─ populateTagsIndex()
    │
    └─ render()
           └─ AppContext.Provider
                  └─ PopupModal
                         │
                         ├─ Search
                         │      │
                         │      ├─ SearchBox
                         │      │      ├─ SearchClearIcon
                         │      │      ├─ Loading
                         │      │      └─ CancelButton
                         │      │
                         │      └─ SearchResultBox
                         │             │
                         │             ├─ Results (hasResults)
                         │             │      ├─ AuthorResults
                         │             │      │      └─ AuthorListItem
                         │             │      ├─ TagResults
                         │             │      │      └─ TagListItem
                         │             │      └─ PostResults
                         │             │             ├─ PostListItem
                         │             │             └─ ShowMoreButton
                         │             │
                         │             └─ NoResultsBox (!hasResults)
                         │
                         └─ Frame (iframe wrapper)
```

### データフロー図

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

モーダル表示 ─────────────▶ searchIndex.init() ──────▶ インデックス構築
                           │
                           ├─ fetchPosts() ──────────▶ postsIndex
                           ├─ fetchAuthors() ─────────▶ authorsIndex
                           └─ fetchTags() ────────────▶ tagsIndex
                           │
searchValue入力 ──────────▶ searchIndex.search()
                           │
                           ├─ postsIndex.search() ───▶ posts結果
                           ├─ authorsIndex.search() ──▶ authors結果
                           └─ tagsIndex.search() ────▶ tags結果
                           │
                           ▼
                        Results Component ────────────▶ UI表示
                           │                          (ハイライト付き)
                           │
結果クリック/Enter ────────▶ window.location.href ───▶ ページ遷移
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| app.js | `apps/sodo-search/src/app.js` | ソース | アプリケーションルート |
| popup-modal.js | `apps/sodo-search/src/components/popup-modal.js` | ソース | 検索モーダルコンポーネント |
| search-index.js | `apps/sodo-search/src/search-index.js` | ソース | Flexsearch検索インデックス |
| app-context.js | `apps/sodo-search/src/app-context.js` | ソース | コンテキスト定義 |
| frame.js | `apps/sodo-search/src/components/frame.js` | ソース | iframe wrapper |
| search.svg | `apps/sodo-search/src/icons/search.svg` | リソース | 検索アイコン |
| clear.svg | `apps/sodo-search/src/icons/clear.svg` | リソース | クリアアイコン |
| circle-anim.svg | `apps/sodo-search/src/icons/circle-anim.svg` | リソース | ローディングアニメーション |
| app.css | `apps/sodo-search/src/app.css` | スタイル | スタイル定義 |
