# 機能設計書 89-Markdownエディタ画像コンポーネント

## 概要

本ドキュメントは、Decap CMSのMarkdownエディタでの画像挿入用エディタコンポーネント（`decap-cms-editor-component-image`）の機能設計について記述する。本コンポーネントはMarkdownの画像構文（`![alt](url "title")`）をリッチエディタ上でブロックとして扱い、画像の挿入・編集・プレビューを可能にする。

### 本機能の処理概要

**業務上の目的・背景**：Markdownエディタでは画像はテキスト構文として記述されるが、テキストのみでの画像管理はユーザーにとって直感的でない。本エディタコンポーネントは、Markdownの画像構文をブロックとして認識し、画像のアップロード、alt/titleテキストの編集、プレビュー表示をGUI上で行えるようにすることで、コンテンツ編集者のUXを向上させる。

**機能の利用シーン**：Markdownウィジェットでの画像挿入時。エディタのツールバーから画像を挿入する際、またはMarkdownテキスト内の既存の画像構文を編集する際に使用される。

**主要な処理内容**：
1. `fromBlock` - 正規表現で画像Markdown構文をパースし、image/alt/titleフィールドに分解
2. `toBlock` - image/alt/titleフィールドからMarkdown画像構文を生成
3. `toPreview` - image/alt/titleフィールドからHTMLの`<img>`要素を生成（プレビュー用）
4. `fields` - 画像ウィジェット、altテキスト、titleの3フィールドを定義
5. `pattern` - Markdownの画像構文を検出する正規表現

**関連システム・外部連携**：Markdownウィジェット（decap-cms-widget-markdown）、メディアライブラリ

**権限による制御**：権限制御はない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | エントリ編集画面 | 主画面 | Markdownフィールドでの画像ブロック挿入・編集 |
| 7 | エントリ新規作成画面 | 主画面 | 新規エントリのMarkdownフィールドでの画像挿入 |

## 機能種別

UIコンポーネント / エディタコンポーネント / テキスト変換

## 入力仕様

### 入力パラメータ

#### fromBlock

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| match | RegExpMatchArray | Yes | 正規表現のマッチ結果 | - |

#### toBlock

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| alt | string | No | 画像の代替テキスト | - |
| image | string | No | 画像のURL/パス | - |
| title | string | No | 画像のtitle属性 | - |

#### toPreview

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| alt | string | No | 画像の代替テキスト | - |
| image | string | No | 画像のURL/パス | - |
| title | string | No | 画像のtitle属性 | - |
| getAsset | function | Yes | アセットURL解決関数 | - |
| fields | List | No | フィールド定義リスト | - |

#### fields定義

| フィールド名 | label | widget | 説明 |
|-------------|-------|--------|------|
| image | Image | image | 画像アップロードウィジェット（allow_multiple: false） |
| alt | Alt Text | string（デフォルト） | 代替テキスト入力 |
| title | Title | string（デフォルト） | タイトル入力 |

### 入力データソース

Markdownエディタのテキストコンテンツ、メディアライブラリからの画像選択

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| fromBlock結果 | {image, alt, title} | パースされた画像データ |
| toBlock結果 | string | Markdown画像構文文字列（例: `![alt](url "title")`） |
| toPreview結果 | React.Element | `<img>`要素（プレビュー用） |

### 出力先

Markdownエディタのコンテンツ領域、プレビューペイン

## 処理フロー

### 処理シーケンス

```
1. 画像ブロック検出（pattern）
   └─ 正規表現 /^!\[(.*)\]\((.*?)(\s"(.*)")?\)/ でMarkdown画像構文を検出

2. 画像データ抽出（fromBlock）
   ├─ match[2] → image（URL/パス）
   ├─ match[1] → alt（代替テキスト）
   └─ match[4] → title（タイトル）

3. Markdownテキスト生成（toBlock）
   └─ `![${alt || ''}](${image || ''}${title ? ` "${title}"` : ''})`

4. プレビュー生成（toPreview）
   ├─ getAsset(image, imageField) でアセットURL取得
   └─ <img src={src} alt={alt} title={title} /> を返却
```

### フローチャート

```mermaid
flowchart TD
    A[Markdownテキスト入力] --> B{画像パターンマッチ?}
    B -->|Yes| C[fromBlock: データ抽出]
    B -->|No| D[通常テキストとして処理]
    C --> E[画像ブロックとして表示]
    E --> F{編集操作}
    F -->|画像変更| G[メディアライブラリから選択]
    F -->|テキスト変更| H[alt/title入力]
    G --> I[toBlock: Markdown生成]
    H --> I
    I --> J[エディタに反映]
    E --> K[toPreview: プレビュー表示]
    K --> L[img要素レンダリング]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-89-01 | 画像パターン | `![alt](url "title")`形式のMarkdown構文を画像ブロックとして認識 | pattern正規表現マッチ時 |
| BR-89-02 | titleのダブルクォートエスケープ | titleに含まれるダブルクォートは`\"`にエスケープされる | toBlock実行時、titleにダブルクォートが含まれる場合 |
| BR-89-03 | 単一画像制限 | imageフィールドのmedia_libraryにallow_multiple: falseが設定されている | 画像選択時 |
| BR-89-04 | アセット解決 | toPreviewではgetAsset関数でimageフィールドのURLを解決する | プレビュー表示時 |
| BR-89-05 | デフォルト空文字 | alt、image、titleは未設定時に空文字または空文字列として扱われる | fromBlock/toBlock実行時 |

### 計算ロジック

- パターン正規表現: `/^!\[(.*)\]\((.*?)(\s"(.*)")?\)/`
  - グループ1: altテキスト
  - グループ2: 画像URL
  - グループ3: titleセクション全体（スペース+引用符付き）
  - グループ4: titleテキスト
- toBlock: `` `![${alt || ''}](${image || ''}${title ? ` "${title.replace(/"/g, '\\"')}"` : ''})` ``

## データベース操作仕様

該当なし

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

なし

## エラー処理

### エラーケース一覧

本コンポーネントは明示的なエラーハンドリングを行わない。matchがnull/undefinedの場合、fromBlockはundefinedを返す（matchの先頭チェック）。

### リトライ仕様

該当なし

## トランザクション仕様

該当なし

## パフォーマンス要件

正規表現ベースのパターンマッチングであり、特別なパフォーマンス要件はない。

## セキュリティ考慮事項

- 画像URLは外部URLの可能性があるため、XSS対策としてsrc属性の検証が必要
- titleのダブルクォートエスケープにより、Markdown構文の破壊を防止

## 備考

- 本コンポーネントのIDは'image'、ラベルは'Image'
- extensions.jsでCMS.registerEditorComponent(image)として登録される
- Markdownウィジェットのリッチテキストエディタ（Slate.js/Plate）と連携して動作
- テストファイル（`__tests__/index.spec.js`）が存在する

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | index.js | `packages/decap-cms-editor-component-image/src/index.js` | エディタコンポーネントの構造（label、id、fromBlock、toBlock、toPreview、pattern、fields） |

**読解のコツ**: エディタコンポーネントはオブジェクトリテラルとして定義されており、特定のインターフェース（id、label、fromBlock、toBlock、toPreview、pattern、fields）に従う必要がある。これはCMSコアのレジストリ（`registerEditorComponent`）で定義された契約に基づく。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.js | `packages/decap-cms-editor-component-image/src/index.js` | 全処理ロジック（1ファイルに集約） |

**主要処理フロー**:
1. **3行目**: label: 'Image'、id: 'image' の定義
2. **5-10行目**: fromBlock関数 - 正規表現マッチ結果からimage/alt/titleを抽出
3. **11-12行目**: toBlock関数 - フィールド値からMarkdown画像構文を組み立て（titleのダブルクォートエスケープ付き）
4. **14-18行目**: toPreview関数 - getAsset関数で画像URLを解決しimgタグを生成
5. **19行目**: pattern正規表現の定義
6. **20-38行目**: fields定義（image、alt、title）

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

```
decap-cms-editor-component-image/src/index.js
    |
    +-- image オブジェクト
           |
           +-- fromBlock(match)
           |      └─ match[1] → alt, match[2] → image, match[4] → title
           |
           +-- toBlock({alt, image, title})
           |      └─ `![alt](url "title")` 形式の文字列生成
           |
           +-- toPreview({alt, image, title}, getAsset, fields)
           |      +-- fields.find(f => f.get('widget') === 'image')
           |      +-- getAsset(image, imageField)
           |      └─ <img src={src} alt={alt} title={title} />
           |
           +-- pattern: /^!\[(.*)\]\((.*?)(\s"(.*)")?\)/
           |
           +-- fields: [image, alt, title]
```

### データフロー図

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

Markdownテキスト ──────▶ pattern.exec() ──────────▶ RegExpMatchArray
                              |
RegExpMatchArray ──────▶ fromBlock ───────────────▶ {image, alt, title}
                              |
{image, alt, title} ───▶ toBlock ────────────────▶ `![alt](url "title")`
                              |
{image, alt, title} ───▶ toPreview ──────────────▶ <img> React要素
 + getAsset                   |
                        getAsset(image) ─────────▶ 解決済みURL
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.js | `packages/decap-cms-editor-component-image/src/index.js` | ソース | 画像エディタコンポーネントの全実装 |
| index.spec.js | `packages/decap-cms-editor-component-image/src/__tests__/index.spec.js` | テスト | 画像コンポーネントのテスト |
| extensions.js | `packages/decap-cms-app/src/extensions.js` | ソース | CMS.registerEditorComponent(image)での登録箇所 |
| registry.js | `packages/decap-cms-core/src/lib/registry.js` | ソース | registerEditorComponent関数の定義 |
