mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-10-31 12:45:41 +08:00
- docs/dev/plugins.md 改为总览与导航,旧内容拆入 docs/dev/plugins/ - 新增核心概念、配置表单、事件、前端挂载点、命令行、签名与分发、内置插件、示例页 - 每个扩展点一页,侧边栏由页面表生成 - 改正旧文档中下载器配置、hardlink path_map 与补链按钮的过时描述
86 lines
7.4 KiB
Markdown
86 lines
7.4 KiB
Markdown
# フロントエンドスロット
|
||
|
||
プラグインは WebUI の決まった場所に、独自の UI をマウントできます。UI は標準の Web Component(custom element)です。ES モジュールが要素を定義し、AB が要素を作成して `host` と `context` を渡します。フロントエンドフレームワークは不要です。Vue や Lit などを使いたい場合は、モジュールにバンドルしてください。
|
||
|
||
## マニフェスト
|
||
|
||
```toml
|
||
[[plugin.ui]]
|
||
slot = "bangumi.detail.tab"
|
||
element = "ab-plugin-manual-pick"
|
||
entry = "web/index.js"
|
||
title = { zh-CN = "手动选种", en-US = "Manual pick" }
|
||
```
|
||
|
||
- `slot`:スロット。下の表を参照してください。
|
||
- `element`:custom element 名。`ab-plugin-<プラグイン id>` か、`ab-plugin-<プラグイン id>-` で始まる名前にします。この名前空間はプラグインのものです。他のプラグインの要素名は定義できません。
|
||
- `entry`:要素を定義する ES モジュール。`web/` の下に置きます。
|
||
- `title`:言語ごとのタイトル(1 言語以上)。タブ名、ページタイトル、設定セクション名に使われます。
|
||
|
||
マニフェストが不正だと、AB はプラグイン全体を拒否します。`ab-plugin validate` はエントリファイルの有無を検査します。
|
||
|
||
## スロット
|
||
|
||
| `slot` | 位置 | `context` |
|
||
| --- | --- | --- |
|
||
| `settings.section` | 設定ページのセクション一覧の末尾。サイドバーと検索で見えます | `{}` |
|
||
| `bangumi.detail.tab` | 作品編集ダイアログのタブ(プラグインのタブがあるときだけセグメントコントロールを表示) | `{ bangumiId }` |
|
||
| `bangumi.card.action` | 作品カードのタイトル下の操作バー | `{ bangumiId }` |
|
||
| `page` | サイドバーの項目とルート `/plugins/<id>`。ページタイトルはマニフェストのタイトル | `{}` |
|
||
| `dashboard.widget` | 作品一覧ページ上部のグリッド | `{}` |
|
||
|
||
モバイルの下部ナビゲーションにはプラグインページの項目がありません。スマートフォンではアドレスを直接開いてください。
|
||
|
||
## コンポーネントと AbHost
|
||
|
||
AB は、要素を挿入する前に `host` と `context` プロパティを設定します。`connectedCallback` の中でそのまま使えます。
|
||
|
||
```js
|
||
class MyWidget extends HTMLElement {
|
||
async connectedCallback() {
|
||
const root = this.attachShadow({ mode: 'open' });
|
||
root.innerHTML = `<button>${this.host.i18n.locale}</button>`;
|
||
root.querySelector('button').onclick = async () => {
|
||
const data = await this.host.api.get('stats'); // /api/v1/plugins/<id>/stats
|
||
this.host.toast(JSON.stringify(data));
|
||
};
|
||
this.off = this.host.events.on('my-plugin.changed', () => this.refresh());
|
||
}
|
||
disconnectedCallback() { this.off?.(); }
|
||
}
|
||
customElements.define('ab-plugin-my-plugin', MyWidget);
|
||
```
|
||
|
||
| メンバー | 説明 |
|
||
| --- | --- |
|
||
| `host.pluginId` | プラグイン id |
|
||
| `host.api.get/post/put/delete` | ログイン状態付きのリクエスト。解析済みの JSON を返し、2xx 以外では reject します。`/` で始まらないパスは `/api/v1/plugins/<id>/` からの相対です。`/api/v1/` で始まるパスはホストの API で、GET のみ許可されます(公開の読み取り専用。他のプラグインの GET ルートを含む) |
|
||
| `host.i18n.locale` | 現在の言語。例:`zh-CN`、`en-US` |
|
||
| `host.i18n.t(key, params?)` | ホストの文言を翻訳します。見つからないときは key を返します |
|
||
| `host.theme.mode` / `host.theme.tokens` | 現在のライト / ダークモードと `--ab-*` 変数の値。テーマに合わせてリアルタイムに変わります |
|
||
| `host.toast(message, kind?)` | メッセージを表示します(`info` または `error`) |
|
||
| `host.events.on(kind, callback)` | イベントバスのイベントを購読します。コールバックはイベントのフィールドを受け取ります。購読解除関数を返します。コンポーネントがアンマウントされると AB も購読を解除します |
|
||
|
||
プラグインの範囲を出るパス(`..`、`//host`、完全な URL)は拒否されます。これは誤用を防ぐ仕組みであり、セキュリティ境界ではありません。コンポーネントはメインサイトと同一オリジンなので、プラグインはすべての権限を持ちます。リクエストは既定でエラーメッセージを表示しません。エラーの見せ方はプラグインが決めます。
|
||
|
||
## スタイル
|
||
|
||
AB はコンポーネントを Shadow DOM のラッパーに入れ、グローバルスタイルから分離します。ラッパーにはテーマ変数(`--ab-color-primary`、`--ab-color-surface`、`--ab-color-text`、`--ab-radius-sm`、`--ab-font-mono` など。全一覧は `@autobangumi/plugin-ui` の `tokens.css`)が注入されます。ビルドしないコンポーネントは `var(--ab-…)` をそのまま使えます。ライト / ダークは自動で切り替わります。
|
||
|
||
## ビルドテンプレート
|
||
|
||
ネイティブモジュールを手書きしたくない場合は、ワークスペースパッケージ `@autobangumi/plugin-ui`(`webui/packages/plugin-ui/`、npm には公開しません)を使います。
|
||
|
||
- `src/index.ts`:`AbHost`、`AbPluginElement`、`AbSlotContext` などの型。
|
||
- `tokens.css`:テーマ変数。
|
||
- `template/`:Vite のライブラリモードのテンプレート。自己完結した単一の ES モジュールを `../web/index.js` に出力し、`tokens.css` を Shadow DOM にインライン化します。プラグインにコピーして、要素名を変えて使います。
|
||
|
||
## 制約
|
||
|
||
- **CSP**:AB のページには `Content-Security-Policy: script-src 'self'` が付きます。モジュールは AB のアドレス(`/api/v1/plugins/<id>/web/...`)から読み込む必要があります。インラインスクリプト、`eval`、サードパーティ CDN のスクリプトは使えません。画像、スタイル、ネットワークリクエストは制限されません。
|
||
- **静的ファイル**:`web/` ディレクトリは `GET /api/v1/plugins/<id>/web/<パス>` で配信されます。ログインが必要で、有効なプラグインだけを配信し、ディレクトリの外に出るパスとシンボリックリンクは拒否します。このため、プラグインの `api_router` は `web/` のプレフィックスを使えません。
|
||
- **エラー境界**:モジュールの import 失敗、未定義の要素、コンポーネント内の未捕捉エラーがあると、そのプラグインのスロットだけが「プラグインコンポーネントの読み込みに失敗」と表示します。ページと他のプラグインには影響しません。再試行ボタンはありません。ページを開き直すと再度 import します。
|
||
- **キャッシュ**:モジュールには `Cache-Control: no-cache` が付きます。プラグインの更新後は、ブラウザが ETag で再検証します。
|
||
|
||
動作する完全な例は `examples/plugins/manual-pick/` です。詳細ページのタブで、ホストの読み取り専用 API でトレントを一覧し、プラグイン自身のルートで選択を保存し、別の場所で選択されたときは `host.events.on` で更新します。
|