## アセットビューアーが役立つ場面

ヘルパースクリプト、構成図、操作動画、PDF 仕様書、配布用アーカイブなど、ドキュメントの本文には埋め込まず、別ファイルのまま説明したい資料があります。アセットビューアーを使うと、ファイルを `public/` に置いたまま専用の閲覧ページを生成し、MDX 内の参照をそのページにつなげられます。

単にファイルを返すだけでは読者に十分な手掛かりを示せない場合に、この機能を有効にします。

```ts title="zfb.config.ts"
import { defineConfig } from "zfb/config";
import { zudoDoc } from "@takazudo/zudo-doc/config";

export default defineConfig(
  zudoDoc({
    assetViewer: true,
    imageEnlarge: true,
  }),
);
```

`imageEnlarge: true` は、ドキュメントに埋め込んだ画像をクリックして拡大する機能を有効にします。画像キャプションからアセットページへ移動するリンクは、拡大表示を無効にしても利用できます。

## ディレクトリと URL の構成

ビューアーで扱うファイルは `public/<assetViewerDir>/` 配下に置きます。デフォルト設定では、次の構成になります。

```text
public/
└── assets/
    └── demo/
        ├── architecture.png
        ├── demo-project.zip
        ├── diagrams/
        │   ├── link-graph.svg
        │   └── link-graph.svg.meta.json
        ├── hmr-demo.mp4
        ├── parse-frontmatter.js
        ├── scripts/
        │   ├── README.txt
        │   └── check-frontmatter.mjs
        └── spec.pdf
```

各ファイルに対して、用途の異なる2つの URL ができます。

- `/assets/demo/parse-frontmatter.js` は公開ファイルそのものを返す URL です。画像、メディアプレーヤー、ダウンロード操作はこの URL を使います。
- `/files/demo/parse-frontmatter.js/` は生成された閲覧ページです。ファイル情報、プレビュー操作、ダウンロード操作、このファイルを参照しているドキュメントへのリンクが加わります。

サイトにルート以外の `base` を設定している場合も、ルート相対 URL のまま記述できます。レンダリング時に zudo-doc が設定済みの base を付与します。

## 設定

`zudoDoc()` のトップレベルで、次の6項目を設定できます。

| フィールド | デフォルト | 用途 |
|-----------|-----------|------|
| `assetViewer` | `false` | 閲覧ページを生成し、マニフェストに基づく MDX の参照機能を有効にする。 |
| `assetViewerDir` | `"assets"` | `public/` 配下のディレクトリと、公開ファイルの URL プレフィックスを指定する。 |
| `assetViewerRoutePrefix` | `"files"` | 閲覧ページの URL プレフィックスを指定する。`assetViewerDir` とは異なる値が必要。 |
| `assetViewerExclude` | `[]` | アセットディレクトリからの相対パスに対して、閲覧ページの生成対象外にする glob を指定する。 |
| `assetViewerIndex` | `false` | ビューアーのルートプレフィックスに一覧ページを生成する。 |
| `assetViewerIndexing` | `false` | 閲覧ページを検索、`llms.txt`、サイトマップの各出力へ個別に追加する。 |

たとえば、ソースマップと下書きを通常の公開ファイルとして残しつつ、閲覧ページだけを生成対象外にするには次のように設定します。

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    assetViewer: true,
    assetViewerExclude: ["**/*.map", "drafts/**"],
  }),
);
```

ビューアーで管理するファイルを `public/<assetViewerDir>/client/` 配下には置かないでください。`client/` は zfb がブラウザー向けアセット用に予約しているパスであり、アセットビューアーは走査時に衝突を報告します。

## アセット一覧ページ

アセットビューアーと一覧を有効にすると、`/<assetViewerRoutePrefix>/` にファイルを見渡せるページが生成されます。

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    assetViewer: true,
    assetViewerIndex: true,
  }),
);
```

一覧には、対象アセットがディレクトリツリーとして表示されます。フォルダー行はリンクではなく、配下を開閉するためのトグルです。フォルダーごとに配下のファイル数と合計サイズが表示されます。ファイル行からは各閲覧ページへ移動でき、ファイル種別やサイズなどの情報も確認できます。対象ファイルが1つもない場合は、ツリーの代わりに空の状態を示すメッセージが表示されます。

`assetViewerExclude`で除外したファイルは閲覧ページと一覧のどちらにも現れませんが、元の公開URLからは引き続き取得できます。メタデータサイドカーも一覧には含まれません。一覧ページは個別の閲覧ページと同様、設定済みの各ロケールに生成されます。

このショーケースでは機能を有効にしているため、[実際のアセット一覧](/files/)を確認できます。自分のサイトのヘッダーから一覧へ移動できるようにするには、`headerNav` にバージョン非依存の項目を追加します。

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    assetViewer: true,
    assetViewerIndex: true,
    headerNav: [
      { label: "Assets", labelKey: "nav.assets", path: "/files", versioned: false },
    ],
  }),
);
```

`versioned: false`を指定すると、リンクがアーカイブ版のルートに入りません。ロケール対応ナビゲーションは、現在のロケールプレフィックスを自動的に追加します。項目の設定全般は[ヘッダーナビゲーション](https://zudo-doc.takazudomodular.com/ja/docs/guides/header-navigation#%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3-versioned)を参照してください。

## アセットページをインデックスへ追加する

デフォルトでは、アセット閲覧ページは検索インデックス、`llms.txt`、サイトマップのいずれにも含まれません。`assetViewerIndexing` を使うと、出力先ごとに追加を有効化できます。

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    assetViewer: true,
    assetViewerIndexing: {
      search: true,
      llmsTxt: true,
      sitemap: true,
    },
  }),
);
```

設定値の型は `false | { search?: boolean; llmsTxt?: boolean; sitemap?: boolean }` で、デフォルトは `false` です。各キーは明示的に `true` を指定した場合にだけ有効になるため、一部のキーだけを持つオブジェクトでは、省略した出力先は無効のままです。また、`assetViewer: true` が前提となり、インデックス用のキーだけを有効にしても何も起こりません。

各キーが制御する出力は次のとおりです。

- `search` は、アセット閲覧ページを `search-index.json` に追加します。各ページの ID には `asset:` プレフィックスが付きます。
- `llmsTxt` は、`llms.txt` に `## Files` セクションを追加し、`llms-full.txt` にアセットページを追記します。テキストとして扱えるアセットでは本文を最大 8 KB まで収録し、それを超える場合は末尾に切り詰めを明示するマーカーを付けます。バイナリアセットは本文を含めず、1 行のスタブとして掲載します。
- `sitemap` は、`/files/...` の閲覧ページルートをサイトマップに追加します。

このショーケースでは 3 つのキーをすべて有効にしているため、生成される検索インデックス、LLM 向けファイル、サイトマップで、すべての出力を有効にした場合の挙動を確認できます。

## MDX から参照する方法

以下はすべて、このリポジトリのデモファイルを参照する実際の表示例です。

### Markdown リンク

マニフェストに存在する公開ファイルへの Markdown リンクは閲覧ページの URL に書き換えられ、ファイル種別とサイズが付加されます。

[parse-frontmatter.js](/assets/demo/parse-frontmatter.js) を生成された閲覧ページで開きます。

```mdx
[parse-frontmatter.js](/assets/demo/parse-frontmatter.js)
```

外部リンク、ページ内リンク、除外対象のパス、マニフェストにない公開 URL は通常のリンクのままです。

### アセットカード

そのファイル自体が主要な配布物や参照資料である場合は `Asset` を使います。カードから、説明付きの閲覧ページと元ファイルのダウンロードの両方へ移動できます。

[Dynamic content unavailable in text export]

```mdx
<Asset src="/assets/demo/demo-project.zip" />
```

ドキュメント固有の表現が必要な場合は、`Asset` に任意の `title` と `description` props も指定できます。

### コードの抜粋

ソースファイルの一部を MDX へ複製せずに掲載するには `AssetCode` を使います。

[Dynamic content unavailable in text export]

```mdx
<AssetCode src="/assets/demo/parse-frontmatter.js" lines="27-44" />
```

指定した行はビルド時に抽出され、シンタックスハイライト付きで表示されます。フッターのリンクから、閲覧ページ上の同じ範囲へ移動できます。1つの抜粋で指定できるのは最大200行です。

### 画像キャプションのリンク

単独の画像がアセットマニフェストに含まれている場合、ドキュメント内での通常表示を保ったまま、キャプションに **Open asset page** リンクが加わります。

![Asset pipeline diagram](/assets/demo/architecture.png "Figure 1 — the asset viewer pipeline.")

```mdx
![Asset pipeline diagram](/assets/demo/architecture.png "Figure 1 — the asset viewer pipeline.")
```

リンクには画像の寸法も表示されます。`imageEnlarge: true` の場合は、同じ画像を拡大ダイアログでも確認できます。操作方法と記述規則は [Image Enlarge](https://zudo-doc.takazudomodular.com/ja/docs/components/image-enlarge)を参照してください。

## 任意のメタデータサイドカー

閲覧ページのタイトルを変えたり説明を追加したりするには、アセットと同じ場所に `<file>.meta.json` を置きます。デモファイルには、`diagrams/link-graph.svg` と同じ場所に `link-graph.svg.meta.json` が用意されています。

```json title="public/assets/demo/diagrams/link-graph.svg.meta.json"
{
  "title": "Asset link graph",
  "description": "How a document link maps to the raw asset URL and its generated viewer page."
}
```

どちらも任意の文字列フィールドです。サイドカーは正しい JSON 形式で、2 KB 以下にしてください。ビルド時のメタデータとしてだけ使われるため、`.meta.json` 自体の閲覧ページは生成されません。

## ファイル種別ごとの表示

どの閲覧ページにも、ファイル名とメタデータ、元ファイルに対する操作、詳細情報、このファイルを参照しているドキュメントへのリンクが表示されます。1024px 以上のビューポートでは、詳細情報と参照元リンクがプレビューの右隣のサイドレールに並びます。これはコードとテキストを含むすべてのファイル種別で共通です。プレビューを全幅で見たいときは、ビューポート右端に固定されたシェブロンタブでサイドレールを畳めます。1024px 未満では、同じ内容がプレビューの下に縦に並び、タブは表示されません。主となるプレビューは、検出されたファイル種別に応じて変わります。

- **[コードとテキスト](/assets/demo/parse-frontmatter.js)**: 行アンカー付きのソース、コピー操作、折り返し操作を表示します。1 MiB 以下のファイルはシンタックスハイライトされ、それを超えるテキストはプレーン表示になります。プレーン表示は最大2,000行で、5 MiB を超えるテキストはダウンロード専用になります。
- **[画像](/assets/demo/architecture.png)**: Fit と 1:1 の表示倍率、チェッカー背景と暗色背景、画像寸法、拡大ダイアログを利用できます。
- **[動画](/assets/demo/hmr-demo.mp4)**: ブラウザー標準の操作 UI を使い、取得できた場合は寸法と再生時間も表示します。
- **[PDF](/assets/demo/spec.pdf)**: ブラウザーの PDF ビューアーを埋め込み、その横にダウンロード手段も残します。
- **[アーカイブなど未対応の形式](/assets/demo/demo-project.zip)**: 安全性や正確性に欠けるプレビューは試みず、ダウンロードパネルを表示します。

形式の判定では拡張子だけでなくファイル内容も確認します。両者が一致しない場合は、申告されたメディア形式として埋め込まず、安全な表示へ切り替えます。

サイドレールの開閉状態はブラウザーに保存され、ページの描画前に復元されます。そのため再読み込みしても状態は保たれ、次に開いたアセットページにも引き継がれます。サイドレールとタブはビューアーに標準で備わっており、これらを有効化または無効化する設定項目はありません。

## 開発時の挙動

`pnpm dev` の実行中に既存アセットを編集すると、閲覧ページにも変更が反映されます。一方、監視対象はプラグイン起動時に列挙されるため、アセットを追加、削除、または名前変更した場合は `pnpm dev` の再起動が必要です。再起動するまで、アセット一覧にも古い内容が残ります。ドキュメントから初めてアセットを参照した場合も、リンク関係と抜粋を再構築するために再起動してください。

ファイルの追加や整理を繰り返す間は、ビルドしてから配信する開発モードの `pnpm dev:stable` を使うこともできます。

## zfb を使わずにレンダリングする

`./asset-page` と `./asset-index-page` は zfb に依存したままです。`createAssetPageView(ctx)` と `createAssetIndexPageView(ctx)` はどちらも `ChromeContext` を受け取り、出力を `DocLayoutWithDefaults` でラップするため、zfb ページの内部でのみ動作します。

zfb に依存しないホスト(別のフレームワーク、静的書き出しステップ、プレビューツールなど)向けには、代わりにプレーンな props を受け取るボディモジュールをインポートしてください。これらは `ChromeContext` を必要とせず、`@takazudo/zfb*` パッケージを一切取り込みません。

```tsx title="render-asset-page.tsx"
import { AssetPageBody, resolveAssetPageLabels, renderAssetDetailsPrepaintScript } from "@takazudo/zudo-doc/asset-page/body";
import { AssetIndexPageBody, resolveAssetIndexPageLabels } from "@takazudo/zudo-doc/asset-index-page/body";

// Resolve labels ahead of time from your own translator + locale.
const pageLabels = resolveAssetPageLabels(t, locale);
const indexLabels = resolveAssetIndexPageLabels(t, locale);

// Render a single asset's viewer body.
<AssetPageBody entry={asset} locale={locale} rawUrl={rawUrl} labels={pageLabels} showViewSource={false} />;

// Render the directory-tree index body.
<AssetIndexPageBody entries={entries} labels={indexLabels} base="/" routePrefix="files" dir="assets" />;
```

どちらのボディも、ビューアーの内容だけをレンダリングします — ヘッダー、アクション、ステージ/詳細のグリッド(インデックスの場合はディレクトリツリー)、そしてそれぞれ自身のインラインブートストラップ `<script>` です。どちらも `DocLayout` を提供しません。外側のページシェル — コンテンツのタイポグラフィを与える `.zd-content` ラッパー、`<html>`/`<head>`/`<body>` の構造、ヘッダー/サイドバー/フッターなどのクローム — は呼び出し側が用意します。

`AssetPageBody` を使う場合は、もう一つページの `<head>` に配置すべきマークアップがあります。それは詳細サイドレールのプリペイントスクリプトで、保存済みの開閉状態を最初の描画より前に復元し、再読み込み時に一瞬だけ誤った状態が表示されるのを防ぎます。これは `renderAssetDetailsPrepaintScript()` を呼び出して取得します — このヘルパーは呼び出すごとに新しい `<script>` VNode を返すため、同じインスタンスを複数ページで再利用してはいけません。`<head>` の他の内容より前に置いてください。

```tsx
<head>
  {renderAssetDetailsPrepaintScript()}
  {/* the rest of your <head> content */}
</head>
```

`AssetPageBody` と `AssetIndexPageBody` はそれぞれ自身のブートストラップスクリプトを内部に埋め込んでいるため、コピー/折り返しボタン、詳細サイドレールのトグル、インデックスの全展開/全折りたたみといった操作を有効にするために別途インポートする必要はありません。個々のリーフ部品を組み合わせて独自レイアウトを構築する場合にのみ、`./asset-page/components` や `./asset-index-page/tree` を直接インポートしてください。

## 生成ルートの挙動

個別のアセット閲覧ページはリーフページであり、任意で有効にできるルートプレフィックス直下のページはアセット一覧です。どちらも設定済みの各ロケールに存在し、生成されたドキュメントリンクと一覧リンクはそのロケールを保持します。たとえば、日本語ページからは`/files/.../`ではなく`/ja/files/.../`が開きます。これらのルートは、ドキュメントのサイドバーには掲載されません。検索インデックス、`llms.txt`、サイトマップにもデフォルトでは含まれず、対応する `assetViewerIndexing` のキーを明示的に有効化した場合にだけ追加されます。

ビューアーのクロームは`asset.*`翻訳名前空間で解決され、詳細、種類、全体表示、チェッカーなどのラベルに加えて、サイドレールの開閉タブが状態に応じて切り替えるアクセシブルな名前、`asset.detailsCollapse`と`asset.detailsExpand`も含まれます。パッケージが同梱するのは英語と日本語の文字列で、他のロケールは通常の翻訳フォールバックに従います。各文字列は`ZudoDocConfig.translations`で上書きできます。以前のデフォルトロケール限定の挙動に戻すには、`/${assetViewerRoutePrefix}/`を`defaultLocaleOnlyPrefixes`に追加してください。詳しくは[国際化](https://zudo-doc.takazudomodular.com/ja/docs/guides/i18n#%E7%94%9F%E6%88%90%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%A8%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E3%83%AD%E3%82%B1%E3%83%BC%E3%83%AB%E3%81%AE%E6%9C%AC%E6%96%87)を参照してください。

閲覧ページはサイトの `noindex` 設定に従います。ヘッダーとフッターなどのドキュメント共通 UI は保ちつつ、ドキュメント用のサイドバーと目次は表示しません。前述のシェブロンタブが操作するのはアセットページ自身の詳細サイドレールだけで、ドキュメントの目次が戻るわけではありません。目次は[`tocToggle`](https://zudo-doc.takazudomodular.com/ja/docs/guides/configuration#%E3%82%B5%E3%82%A4%E3%83%88%E8%A8%AD%E5%AE%9A-toctoggle)を有効にしていても、閲覧ページでは表示されません。