@ablogcms/plugins 3.2.28-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/acms.d.ts +11 -0
- package/dist/acms.mjs +1 -0
- package/dist/acms.mjs.map +1 -0
- package/dist/chunk-OWV4U7DT.mjs +13 -0
- package/dist/chunk-OWV4U7DT.mjs.map +1 -0
- package/dist/core/create-plugin-registry.d.ts +37 -0
- package/dist/core/create-registry.d.ts +10 -0
- package/dist/core/create-viewed-registry.d.ts +17 -0
- package/dist/core/global-singletons.d.ts +36 -0
- package/dist/core/index.d.ts +15 -0
- package/dist/core/index.mjs +289 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/core/merge-registry-entries.d.ts +11 -0
- package/dist/core/resolve-fill-entry.d.ts +28 -0
- package/dist/core/types.d.ts +127 -0
- package/dist/core/with-fill-props.d.ts +37 -0
- package/dist/events.d.ts +66 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.mjs +43 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @ablogcms/plugins
|
|
2
|
+
|
|
3
|
+
管理画面 JS 拡張基盤のプラグイン登録レジストリ(`ACMS.plugins`)を提供するパッケージ。
|
|
4
|
+
イベント購読・機能登録(unitEditor/dataview)・UI 差し込み(Slot/Fill)・設定参照を
|
|
5
|
+
1 つの `setup(ctx)` に束ねる。
|
|
6
|
+
|
|
7
|
+
拡張ポイントの一覧(イベント・Slot・レジストリの対応表)は [HOOKS.md](./HOOKS.md) を参照。
|
|
8
|
+
|
|
9
|
+
## 使い方
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { register } from '@ablogcms/plugins';
|
|
13
|
+
|
|
14
|
+
register('acms-plugin-sample', {
|
|
15
|
+
version: '1.0.0',
|
|
16
|
+
setup(ctx) {
|
|
17
|
+
// ゲート: アップロード前の確認
|
|
18
|
+
ctx.events.on('media.upload.before', async (event) => {
|
|
19
|
+
const ok = await ACMS.Library.dialog.confirm('確認…');
|
|
20
|
+
return ok ? event : false;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// 通知: アップロード完了後の処理
|
|
24
|
+
ctx.events.on('media.upload.success', (event) => notifyExternal(event.items));
|
|
25
|
+
|
|
26
|
+
// レジストリ: カスタムユニットタイプ
|
|
27
|
+
ctx.unitEditor.registerUnitDefinition('myUnit', myUnitDef);
|
|
28
|
+
|
|
29
|
+
// UI: ユニットツールバーへのボタン追加
|
|
30
|
+
ctx.ui.fill('unit-editor.unit-toolbar', MyToolbarButton);
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- `register(name, definition)`: プラグインを登録する。同名の重複登録は throw する。
|
|
36
|
+
- `unregister(name)`: `setup(ctx)` 内で行ったイベント購読・レジストリ登録・Fill をすべて自動解除する。
|
|
37
|
+
- `get(name)` / `getAll()`: 登録済みプラグインを取得する(デバッグ用)。
|
|
38
|
+
- `setup()` が同期的に throw した場合、途中まで行われた登録は自動でロールバックされる。
|
package/dist/acms.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AcmsPlugins } from './core/types.js';
|
|
2
|
+
declare module '@ablogcms/types/acms' {
|
|
3
|
+
interface Acms {
|
|
4
|
+
/**
|
|
5
|
+
* プラグイン登録レジストリ。`admin.js` の初期化時に `@ablogcms/plugins/core` の
|
|
6
|
+
* `createPluginRegistry()` で生成されるシングルトン。`@ablogcms/plugins`(ルートエントリ)の
|
|
7
|
+
* グローバル委譲ファサードは、これに処理を委譲する。
|
|
8
|
+
*/
|
|
9
|
+
plugins: AcmsPlugins;
|
|
10
|
+
}
|
|
11
|
+
}
|
package/dist/acms.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=acms.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/core/with-fill-props.ts
|
|
2
|
+
function withFillProps(Component) {
|
|
3
|
+
return { kind: "acms-plugins/fill-component", Component };
|
|
4
|
+
}
|
|
5
|
+
function isFillComponentEntry(entry) {
|
|
6
|
+
return typeof entry === "object" && entry !== null && entry.kind === "acms-plugins/fill-component";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
withFillProps,
|
|
11
|
+
isFillComponentEntry
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=chunk-OWV4U7DT.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/with-fill-props.ts"],"sourcesContent":["import type { FunctionComponent } from 'react';\n\n/**\n * `withFillProps` が返すマーカー付きエントリ。`ctx.ui.fill` の判別(`resolve-fill-entry.ts`)が\n * オブジェクトかどうかで即座に見分けられるよう、`kind` に固定のブランド文字列を持たせる。\n *\n * `Component` を `ComponentType<T>`(関数・クラス両対応)ではなく `FunctionComponent<T>` に\n * 限定している Why not: `createElement(Component, fillProps)` を T についてジェネリックなまま\n * 呼ぶと、`ComponentType<T>` が関数・クラスの合併型であるために `createElement` のオーバーロード\n * 解決が失敗する(クラス側 `getDerivedStateFromProps` の反変位置で `T` の消去に起因する型エラー、\n * 実測で確認)。本リポジトリはクラスコンポーネントの新規追加を禁止する規約\n * (development-guidelines.md)のため、関数コンポーネントのみに絞ることで実用上の制約にならない。\n */\nexport interface FillComponentEntry<T> {\n readonly kind: 'acms-plugins/fill-component';\n readonly Component: FunctionComponent<T>;\n}\n\n/**\n * `Slot` の `fillProps`(例: `unit-editor.unit-toolbar` の `{ editor, unit }`)を受け取る React\n * コンポーネントを `ctx.ui.fill` に登録するためのラッパー。\n *\n * Why not(関数のままでは表現できない理由): `resolveFillEntry` は仮引数の個数\n * (0 個 = props なしコンポーネント、1 個以上 = `DomFill`)で判別しており、この規約は\n * `ctx.ui.fill` の当初からの後方互換として維持する。props を要求するコンポーネント\n * (`({ editor, unit }) => ...`)は仮引数が 1 個になり、`DomFill`(`(el) => ...`)と実行時に\n * 構造的に区別できない(`Function.prototype.length` だけでは両者が衝突する)。\n * `withFillProps` は関数ではなくオブジェクトでラップすることで、既存の arity 判別ロジックに\n * 一切手を入れずにこの衝突を回避する。\n *\n * ```tsx\n * ctx.ui.fill('unit-editor.unit-toolbar', withFillProps<UnitToolbarSlotFillProps>(({ editor, unit }) => (\n * <button type=\"button\">{unit.type}</button>\n * )));\n * ```\n */\nexport function withFillProps<T>(Component: FunctionComponent<T>): FillComponentEntry<T> {\n return { kind: 'acms-plugins/fill-component', Component };\n}\n\n/** `entry` が `withFillProps` で作られたマーカー付きエントリかどうかを判定する */\nexport function isFillComponentEntry<T>(entry: unknown): entry is FillComponentEntry<T> {\n return (\n typeof entry === 'object' && entry !== null && (entry as { kind?: unknown }).kind === 'acms-plugins/fill-component'\n );\n}\n"],"mappings":";AAoCO,SAAS,cAAiB,WAAwD;AACvF,SAAO,EAAE,MAAM,+BAA+B,UAAU;AAC1D;AAGO,SAAS,qBAAwB,OAAgD;AACtF,SACE,OAAO,UAAU,YAAY,UAAU,QAAS,MAA6B,SAAS;AAE1F;","names":[]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AcmsEvents } from '@ablogcms/events';
|
|
2
|
+
import { type SlotFillStore } from '@ablogcms/components/slot-fill';
|
|
3
|
+
import type { Action, BulkAction, ColumnTypeDef, Menu, RowData } from '@ablogcms/components/dataview/types';
|
|
4
|
+
import type { UnitDefInterface } from '@ablogcms/unit-editor/core/types/unit';
|
|
5
|
+
import { type ViewedRegistry } from './create-viewed-registry.js';
|
|
6
|
+
import type { BlockDefinitionFactory, BlockExtensionFactory } from './global-singletons.js';
|
|
7
|
+
import type { AcmsPlugins, Registry } from './types.js';
|
|
8
|
+
export interface CreatePluginRegistryOptions {
|
|
9
|
+
/** 委譲先のイベントバス。省略時は自己完結したインスタンスを新規作成する(テスト用の既定値) */
|
|
10
|
+
eventsBus?: AcmsEvents;
|
|
11
|
+
/** `ctx.ui.fill` の書き込み先。省略時は自己完結したインスタンスを新規作成する(テスト用の既定値) */
|
|
12
|
+
fillStore?: SlotFillStore;
|
|
13
|
+
/** `ctx.unitEditor.registerUnitDefinition` の登録先。省略時は自己完結したインスタンスを新規作成する */
|
|
14
|
+
unitDefinitionRegistry?: Registry<UnitDefInterface>;
|
|
15
|
+
/** `ctx.dataview.registerColumnType` の登録先。省略時は自己完結したインスタンスを新規作成する */
|
|
16
|
+
columnTypeRegistry?: Registry<ColumnTypeDef>;
|
|
17
|
+
/** `ctx.blockEditor.registerBlockDefinition` の登録先。省略時は自己完結したインスタンスを新規作成する */
|
|
18
|
+
blockDefinitionRegistry?: Registry<BlockDefinitionFactory>;
|
|
19
|
+
/** `ctx.blockEditor.registerExtension` の登録先。省略時は自己完結したインスタンスを新規作成する */
|
|
20
|
+
blockExtensionRegistry?: Registry<BlockExtensionFactory>;
|
|
21
|
+
/** `ctx.dataview.registerAction` の登録先。省略時は自己完結したインスタンスを新規作成する */
|
|
22
|
+
dataviewActionRegistry?: ViewedRegistry<Action<RowData>>;
|
|
23
|
+
/** `ctx.dataview.registerBulkAction` の登録先。省略時は自己完結したインスタンスを新規作成する */
|
|
24
|
+
dataviewBulkActionRegistry?: ViewedRegistry<BulkAction<RowData>>;
|
|
25
|
+
/** `ctx.dataview.registerMenu` の登録先。省略時は自己完結したインスタンスを新規作成する */
|
|
26
|
+
dataviewMenuRegistry?: ViewedRegistry<Menu<RowData>>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* `ACMS.plugins` の純粋実装(グローバル非依存)。VS Code の `activate(context)` +
|
|
30
|
+
* `context.subscriptions` を範に、`setup(ctx)` 内でのイベント購読・レジストリ登録・UI 差し込みを
|
|
31
|
+
* すべて内部の subscriptions 配列に集約し、`unregister(name)` で一括自動回収する。
|
|
32
|
+
*
|
|
33
|
+
* 依存(イベントバス・Fill ブリッジ・各レジストリ)はすべて注入可能で、省略時は自己完結した
|
|
34
|
+
* インスタンスを新規作成する(`@ablogcms/events` の `createEventBus()` と同じ設計。グローバル
|
|
35
|
+
* シングルトンへの結線は本番の bootstrap(admin.js)が `global-singletons.ts` 経由で明示的に行う)。
|
|
36
|
+
*/
|
|
37
|
+
export declare function createPluginRegistry(options?: CreatePluginRegistryOptions): AcmsPlugins;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Registry } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* 名前衝突を検知する汎用レジストリの純粋実装。`ctx.unitEditor.registerUnitDefinition` /
|
|
4
|
+
* `ctx.dataview.registerColumnType` の土台(Directus の `defineInterface` 等と同型)。
|
|
5
|
+
*
|
|
6
|
+
* `add()` が返す `Unsubscribe` は、登録時点の値を閉じ込めて比較してから削除する。
|
|
7
|
+
* 「解除 → 同名で再登録」の後に古い `Unsubscribe` が誤って新しい登録を消してしまう事故を防ぐ
|
|
8
|
+
* (`name` は一意だが、`Unsubscribe` のライフサイクルは呼び出し側が保持し続けるため)。
|
|
9
|
+
*/
|
|
10
|
+
export declare function createRegistry<T>(label: string): Registry<T>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Unsubscribe } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* `view`(`'entry' | 'module'` 等、将来拡張可能な文字列)ごとに独立した名前空間を持つレジストリ。
|
|
4
|
+
* `ctx.dataview.registerAction(view, name, def)` の土台(design.md Q4「view キー方式」)。
|
|
5
|
+
*
|
|
6
|
+
* 内部では `view` ごとに素の {@link createRegistry} インスタンスを遅延生成して保持する
|
|
7
|
+
* (文字列連結キー方式にしない理由: view 名に区切り文字が含まれた場合の衝突を避けるため)。
|
|
8
|
+
*/
|
|
9
|
+
export interface ViewedRegistry<T> {
|
|
10
|
+
/** `view` 内で `name` が既に登録済みの場合は throw する */
|
|
11
|
+
add(view: string, name: string, item: T): Unsubscribe;
|
|
12
|
+
remove(view: string, name: string): boolean;
|
|
13
|
+
get(view: string, name: string): T | undefined;
|
|
14
|
+
/** 指定した `view` に登録された項目のみを返す。未登録の `view` は空オブジェクト */
|
|
15
|
+
getAll(view: string): Record<string, T>;
|
|
16
|
+
}
|
|
17
|
+
export declare function createViewedRegistry<T>(label: string): ViewedRegistry<T>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ページ全体で 1 つだけ存在するべきシングルトン群。
|
|
3
|
+
*
|
|
4
|
+
* 管理画面は dispatcher ごとに独立した React ルートを `createRoot` する構成
|
|
5
|
+
* (`@ablogcms/utils/react` の `render()`)のため、単一の `<SlotFillProvider>` でページ全体を
|
|
6
|
+
* 包むことができない。`ctx.ui.fill` が書き込む Fill ブリッジをここでシングルトン化しておき、
|
|
7
|
+
* 各ツリーの `<SlotFillProvider store={getGlobalFillBridge()}>` がこれを購読することで、
|
|
8
|
+
* どの独立ツリーの `<Slot>` にも同じ登録内容が届く(design.md「グローバル Fill ブリッジ」節)。
|
|
9
|
+
*
|
|
10
|
+
* `unitDefs` / `columnTypes` prop の値計算(react-19-migration フェーズ3-1a/3-1b)も同じ理由で
|
|
11
|
+
* dispatch 側の複数箇所から同一レジストリを参照する必要があるため、ここでシングルトン化する
|
|
12
|
+
* (実際の prop 配線はフェーズD)。
|
|
13
|
+
*
|
|
14
|
+
* `createPluginRegistry()` 自体は各テストが独立したインスタンスで動かせるよう、これらに依存しない
|
|
15
|
+
* 素の工場関数のまま保つ(`@ablogcms/events` の `createEventBus()` と同じ設計)。
|
|
16
|
+
* 本番の結線(admin.js)でのみ、ここのシングルトンを明示的に注入する。
|
|
17
|
+
*/
|
|
18
|
+
import { type SlotFillStore } from '@ablogcms/components/slot-fill';
|
|
19
|
+
import type { Action, BulkAction, ColumnTypeDef, Menu, RowData } from '@ablogcms/components/dataview/types';
|
|
20
|
+
import type { UnitDefInterface } from '@ablogcms/unit-editor/core/types/unit';
|
|
21
|
+
import type { BlockDefinition } from '@ablogcms/block-editor/types';
|
|
22
|
+
import type { AnyExtension } from '@ablogcms/block-editor/tiptap/core';
|
|
23
|
+
import type { Tiptap } from '@ablogcms/block-editor/tiptap';
|
|
24
|
+
import { type ViewedRegistry } from './create-viewed-registry.js';
|
|
25
|
+
import type { Registry } from './types.js';
|
|
26
|
+
/** `ctx.blockEditor.registerBlockDefinition` / `registerExtension` が登録するファクトリの型。 */
|
|
27
|
+
export type BlockDefinitionFactory = (runtime: Tiptap) => BlockDefinition;
|
|
28
|
+
export type BlockExtensionFactory = (runtime: Tiptap) => AnyExtension | AnyExtension[];
|
|
29
|
+
export declare function getGlobalFillBridge(): SlotFillStore;
|
|
30
|
+
export declare function getUnitDefinitionRegistry(): Registry<UnitDefInterface>;
|
|
31
|
+
export declare function getColumnTypeRegistry(): Registry<ColumnTypeDef>;
|
|
32
|
+
export declare function getBlockDefinitionRegistry(): Registry<BlockDefinitionFactory>;
|
|
33
|
+
export declare function getBlockExtensionRegistry(): Registry<BlockExtensionFactory>;
|
|
34
|
+
export declare function getDataviewActionRegistry(): ViewedRegistry<Action<RowData>>;
|
|
35
|
+
export declare function getDataviewBulkActionRegistry(): ViewedRegistry<BulkAction<RowData>>;
|
|
36
|
+
export declare function getDataviewMenuRegistry(): ViewedRegistry<Menu<RowData>>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ablogcms/plugins` の純粋実装(グローバル非依存)を提供するサブパス。
|
|
3
|
+
* コアの bootstrap(`admin.js`)と、プラグイン側のユニットテストから利用する。
|
|
4
|
+
*/
|
|
5
|
+
export { createPluginRegistry } from './create-plugin-registry.js';
|
|
6
|
+
export type { CreatePluginRegistryOptions } from './create-plugin-registry.js';
|
|
7
|
+
export { createRegistry } from './create-registry.js';
|
|
8
|
+
export { createViewedRegistry } from './create-viewed-registry.js';
|
|
9
|
+
export type { ViewedRegistry } from './create-viewed-registry.js';
|
|
10
|
+
export { mergeRegistryEntries } from './merge-registry-entries.js';
|
|
11
|
+
export { getBlockDefinitionRegistry, getBlockExtensionRegistry, getColumnTypeRegistry, getDataviewActionRegistry, getDataviewBulkActionRegistry, getDataviewMenuRegistry, getGlobalFillBridge, getUnitDefinitionRegistry, } from './global-singletons.js';
|
|
12
|
+
export type { BlockDefinitionFactory, BlockExtensionFactory } from './global-singletons.js';
|
|
13
|
+
export { resolveFillEntry } from './resolve-fill-entry.js';
|
|
14
|
+
export { withFillProps, isFillComponentEntry } from './with-fill-props.js';
|
|
15
|
+
export * from './types.js';
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isFillComponentEntry,
|
|
3
|
+
withFillProps
|
|
4
|
+
} from "../chunk-OWV4U7DT.mjs";
|
|
5
|
+
|
|
6
|
+
// src/core/create-plugin-registry.ts
|
|
7
|
+
import { createEventBus } from "@ablogcms/events/core";
|
|
8
|
+
import { createSlotFillStore } from "@ablogcms/components/slot-fill";
|
|
9
|
+
|
|
10
|
+
// src/core/create-registry.ts
|
|
11
|
+
function createRegistry(label) {
|
|
12
|
+
const items = /* @__PURE__ */ new Map();
|
|
13
|
+
const add = (name, item) => {
|
|
14
|
+
if (items.has(name)) {
|
|
15
|
+
throw new Error(`[@ablogcms/plugins] ${label} "${name}" \u306F\u65E2\u306B\u767B\u9332\u3055\u308C\u3066\u3044\u307E\u3059\u3002`);
|
|
16
|
+
}
|
|
17
|
+
items.set(name, item);
|
|
18
|
+
return () => {
|
|
19
|
+
if (items.get(name) === item) {
|
|
20
|
+
items.delete(name);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
const remove = (name) => items.delete(name);
|
|
25
|
+
const get = (name) => items.get(name);
|
|
26
|
+
const getAll = () => Object.fromEntries(items);
|
|
27
|
+
return { add, remove, get, getAll };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/core/create-viewed-registry.ts
|
|
31
|
+
function createViewedRegistry(label) {
|
|
32
|
+
const registries = /* @__PURE__ */ new Map();
|
|
33
|
+
const registryFor = (view) => {
|
|
34
|
+
let registry = registries.get(view);
|
|
35
|
+
if (!registry) {
|
|
36
|
+
registry = createRegistry(`${label}(${view})`);
|
|
37
|
+
registries.set(view, registry);
|
|
38
|
+
}
|
|
39
|
+
return registry;
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
add: (view, name, item) => registryFor(view).add(name, item),
|
|
43
|
+
remove: (view, name) => registryFor(view).remove(name),
|
|
44
|
+
get: (view, name) => registryFor(view).get(name),
|
|
45
|
+
getAll: (view) => registries.get(view)?.getAll() ?? {}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/core/resolve-fill-entry.ts
|
|
50
|
+
import { createElement } from "react";
|
|
51
|
+
import { DomFillMount } from "@ablogcms/components/slot-fill";
|
|
52
|
+
function resolveFillEntry(entry) {
|
|
53
|
+
if (isFillComponentEntry(entry)) {
|
|
54
|
+
const Component = entry.Component;
|
|
55
|
+
return (fillProps) => createElement(Component, fillProps);
|
|
56
|
+
}
|
|
57
|
+
if (typeof entry === "function" && entry.length > 0) {
|
|
58
|
+
return createElement(DomFillMount, { mount: entry });
|
|
59
|
+
}
|
|
60
|
+
return createElement(entry);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/core/create-plugin-registry.ts
|
|
64
|
+
var logHandlerError = (pluginName, eventLabel, err) => {
|
|
65
|
+
console.error(`[@ablogcms/plugins] \u30D7\u30E9\u30B0\u30A4\u30F3 "${pluginName}" \u306E\u30CF\u30F3\u30C9\u30E9("${eventLabel}")\u304C\u4F8B\u5916\u3092\u6295\u3052\u307E\u3057\u305F\u3002`, err);
|
|
66
|
+
};
|
|
67
|
+
var toEventLabel = (type) => Array.isArray(type) ? type.join(",") : type;
|
|
68
|
+
var readConfig = (pluginName) => {
|
|
69
|
+
if (typeof window === "undefined") {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
const camelName = pluginName.replace(/[-_](\w)/g, (_match, chr) => chr.toUpperCase());
|
|
73
|
+
const config = window.ACMS?.Config;
|
|
74
|
+
const value = config?.[camelName];
|
|
75
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
76
|
+
};
|
|
77
|
+
function createPluginRegistry(options = {}) {
|
|
78
|
+
const eventsBus = options.eventsBus ?? createEventBus();
|
|
79
|
+
const fillStore = options.fillStore ?? createSlotFillStore();
|
|
80
|
+
const unitDefinitionRegistry2 = options.unitDefinitionRegistry ?? createRegistry("unit-definition");
|
|
81
|
+
const columnTypeRegistry2 = options.columnTypeRegistry ?? createRegistry("column-type");
|
|
82
|
+
const blockDefinitionRegistry2 = options.blockDefinitionRegistry ?? createRegistry("block-definition");
|
|
83
|
+
const blockExtensionRegistry2 = options.blockExtensionRegistry ?? createRegistry("extension");
|
|
84
|
+
const dataviewActionRegistry2 = options.dataviewActionRegistry ?? createViewedRegistry("dataview-action");
|
|
85
|
+
const dataviewBulkActionRegistry2 = options.dataviewBulkActionRegistry ?? createViewedRegistry("dataview-bulk-action");
|
|
86
|
+
const dataviewMenuRegistry2 = options.dataviewMenuRegistry ?? createViewedRegistry("dataview-menu");
|
|
87
|
+
const plugins = /* @__PURE__ */ new Map();
|
|
88
|
+
let fillIdCounter = 0;
|
|
89
|
+
const createContext = (plugin) => {
|
|
90
|
+
const onEvent = (type, handler) => {
|
|
91
|
+
const label = toEventLabel(type);
|
|
92
|
+
const wrapped = async (event) => {
|
|
93
|
+
try {
|
|
94
|
+
return await handler(event);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
logHandlerError(plugin.name, label, err);
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const unsubscribe = eventsBus.on(type, wrapped);
|
|
101
|
+
plugin.subscriptions.push(unsubscribe);
|
|
102
|
+
plugin.registrations.push({ kind: "event", key: label });
|
|
103
|
+
return unsubscribe;
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
name: plugin.name,
|
|
107
|
+
config: readConfig(plugin.name),
|
|
108
|
+
events: {
|
|
109
|
+
on: onEvent
|
|
110
|
+
},
|
|
111
|
+
unitEditor: {
|
|
112
|
+
registerUnitDefinition: (name, def) => {
|
|
113
|
+
const unsubscribe = unitDefinitionRegistry2.add(name, def);
|
|
114
|
+
plugin.subscriptions.push(unsubscribe);
|
|
115
|
+
plugin.registrations.push({ kind: "unit-definition", key: name });
|
|
116
|
+
return unsubscribe;
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
blockEditor: {
|
|
120
|
+
registerBlockDefinition: (name, factory) => {
|
|
121
|
+
const unsubscribe = blockDefinitionRegistry2.add(name, factory);
|
|
122
|
+
plugin.subscriptions.push(unsubscribe);
|
|
123
|
+
plugin.registrations.push({ kind: "block-definition", key: name });
|
|
124
|
+
return unsubscribe;
|
|
125
|
+
},
|
|
126
|
+
registerExtension: (name, factory) => {
|
|
127
|
+
const unsubscribe = blockExtensionRegistry2.add(name, factory);
|
|
128
|
+
plugin.subscriptions.push(unsubscribe);
|
|
129
|
+
plugin.registrations.push({ kind: "extension", key: name });
|
|
130
|
+
return unsubscribe;
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
dataview: {
|
|
134
|
+
registerColumnType: (name, type) => {
|
|
135
|
+
const unsubscribe = columnTypeRegistry2.add(name, type);
|
|
136
|
+
plugin.subscriptions.push(unsubscribe);
|
|
137
|
+
plugin.registrations.push({ kind: "column-type", key: name });
|
|
138
|
+
return unsubscribe;
|
|
139
|
+
},
|
|
140
|
+
registerAction: (view, name, def) => {
|
|
141
|
+
const unsubscribe = dataviewActionRegistry2.add(view, name, def);
|
|
142
|
+
plugin.subscriptions.push(unsubscribe);
|
|
143
|
+
plugin.registrations.push({ kind: "dataview-action", key: `${view}:${name}` });
|
|
144
|
+
return unsubscribe;
|
|
145
|
+
},
|
|
146
|
+
registerBulkAction: (view, name, def) => {
|
|
147
|
+
const unsubscribe = dataviewBulkActionRegistry2.add(view, name, def);
|
|
148
|
+
plugin.subscriptions.push(unsubscribe);
|
|
149
|
+
plugin.registrations.push({ kind: "dataview-bulk-action", key: `${view}:${name}` });
|
|
150
|
+
return unsubscribe;
|
|
151
|
+
},
|
|
152
|
+
registerMenu: (view, name, def) => {
|
|
153
|
+
const unsubscribe = dataviewMenuRegistry2.add(view, name, def);
|
|
154
|
+
plugin.subscriptions.push(unsubscribe);
|
|
155
|
+
plugin.registrations.push({ kind: "dataview-menu", key: `${view}:${name}` });
|
|
156
|
+
return unsubscribe;
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
ui: {
|
|
160
|
+
fill: (slotName, entry) => {
|
|
161
|
+
fillIdCounter += 1;
|
|
162
|
+
const id = `plugin:${plugin.name}:${slotName}:${fillIdCounter}`;
|
|
163
|
+
const render = resolveFillEntry(entry);
|
|
164
|
+
fillStore.upsertFill(slotName, id, render);
|
|
165
|
+
const unsubscribe = () => fillStore.removeFill(slotName, id);
|
|
166
|
+
plugin.subscriptions.push(unsubscribe);
|
|
167
|
+
plugin.registrations.push({ kind: "fill", key: slotName });
|
|
168
|
+
return unsubscribe;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
const register = (name, definition) => {
|
|
174
|
+
if (plugins.has(name)) {
|
|
175
|
+
throw new Error(`[@ablogcms/plugins] \u30D7\u30E9\u30B0\u30A4\u30F3\u540D "${name}" \u306F\u65E2\u306B\u767B\u9332\u3055\u308C\u3066\u3044\u307E\u3059\u3002`);
|
|
176
|
+
}
|
|
177
|
+
const plugin = { name, version: definition.version, registrations: [], subscriptions: [] };
|
|
178
|
+
plugins.set(name, plugin);
|
|
179
|
+
try {
|
|
180
|
+
definition.setup(createContext(plugin));
|
|
181
|
+
} catch (err) {
|
|
182
|
+
plugin.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
183
|
+
plugins.delete(name);
|
|
184
|
+
throw err;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const unregister = (name) => {
|
|
188
|
+
const plugin = plugins.get(name);
|
|
189
|
+
if (!plugin) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
plugin.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
193
|
+
plugins.delete(name);
|
|
194
|
+
return true;
|
|
195
|
+
};
|
|
196
|
+
const toRegisteredPlugin = (plugin) => ({
|
|
197
|
+
name: plugin.name,
|
|
198
|
+
version: plugin.version,
|
|
199
|
+
registrations: plugin.registrations
|
|
200
|
+
});
|
|
201
|
+
const get = (name) => {
|
|
202
|
+
const plugin = plugins.get(name);
|
|
203
|
+
return plugin ? toRegisteredPlugin(plugin) : void 0;
|
|
204
|
+
};
|
|
205
|
+
const getAll = () => Array.from(plugins.values()).map(toRegisteredPlugin);
|
|
206
|
+
return { register, unregister, get, getAll, withFillProps };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/core/merge-registry-entries.ts
|
|
210
|
+
function mergeRegistryEntries(core, registry) {
|
|
211
|
+
return { ...core, ...registry.getAll() };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/core/global-singletons.ts
|
|
215
|
+
import { createSlotFillStore as createSlotFillStore2 } from "@ablogcms/components/slot-fill";
|
|
216
|
+
var fillBridge;
|
|
217
|
+
function getGlobalFillBridge() {
|
|
218
|
+
if (!fillBridge) {
|
|
219
|
+
fillBridge = createSlotFillStore2();
|
|
220
|
+
}
|
|
221
|
+
return fillBridge;
|
|
222
|
+
}
|
|
223
|
+
var unitDefinitionRegistry;
|
|
224
|
+
function getUnitDefinitionRegistry() {
|
|
225
|
+
if (!unitDefinitionRegistry) {
|
|
226
|
+
unitDefinitionRegistry = createRegistry("unit-definition");
|
|
227
|
+
}
|
|
228
|
+
return unitDefinitionRegistry;
|
|
229
|
+
}
|
|
230
|
+
var columnTypeRegistry;
|
|
231
|
+
function getColumnTypeRegistry() {
|
|
232
|
+
if (!columnTypeRegistry) {
|
|
233
|
+
columnTypeRegistry = createRegistry("column-type");
|
|
234
|
+
}
|
|
235
|
+
return columnTypeRegistry;
|
|
236
|
+
}
|
|
237
|
+
var blockDefinitionRegistry;
|
|
238
|
+
function getBlockDefinitionRegistry() {
|
|
239
|
+
if (!blockDefinitionRegistry) {
|
|
240
|
+
blockDefinitionRegistry = createRegistry("block-definition");
|
|
241
|
+
}
|
|
242
|
+
return blockDefinitionRegistry;
|
|
243
|
+
}
|
|
244
|
+
var blockExtensionRegistry;
|
|
245
|
+
function getBlockExtensionRegistry() {
|
|
246
|
+
if (!blockExtensionRegistry) {
|
|
247
|
+
blockExtensionRegistry = createRegistry("extension");
|
|
248
|
+
}
|
|
249
|
+
return blockExtensionRegistry;
|
|
250
|
+
}
|
|
251
|
+
var dataviewActionRegistry;
|
|
252
|
+
function getDataviewActionRegistry() {
|
|
253
|
+
if (!dataviewActionRegistry) {
|
|
254
|
+
dataviewActionRegistry = createViewedRegistry("dataview-action");
|
|
255
|
+
}
|
|
256
|
+
return dataviewActionRegistry;
|
|
257
|
+
}
|
|
258
|
+
var dataviewBulkActionRegistry;
|
|
259
|
+
function getDataviewBulkActionRegistry() {
|
|
260
|
+
if (!dataviewBulkActionRegistry) {
|
|
261
|
+
dataviewBulkActionRegistry = createViewedRegistry("dataview-bulk-action");
|
|
262
|
+
}
|
|
263
|
+
return dataviewBulkActionRegistry;
|
|
264
|
+
}
|
|
265
|
+
var dataviewMenuRegistry;
|
|
266
|
+
function getDataviewMenuRegistry() {
|
|
267
|
+
if (!dataviewMenuRegistry) {
|
|
268
|
+
dataviewMenuRegistry = createViewedRegistry("dataview-menu");
|
|
269
|
+
}
|
|
270
|
+
return dataviewMenuRegistry;
|
|
271
|
+
}
|
|
272
|
+
export {
|
|
273
|
+
createPluginRegistry,
|
|
274
|
+
createRegistry,
|
|
275
|
+
createViewedRegistry,
|
|
276
|
+
getBlockDefinitionRegistry,
|
|
277
|
+
getBlockExtensionRegistry,
|
|
278
|
+
getColumnTypeRegistry,
|
|
279
|
+
getDataviewActionRegistry,
|
|
280
|
+
getDataviewBulkActionRegistry,
|
|
281
|
+
getDataviewMenuRegistry,
|
|
282
|
+
getGlobalFillBridge,
|
|
283
|
+
getUnitDefinitionRegistry,
|
|
284
|
+
isFillComponentEntry,
|
|
285
|
+
mergeRegistryEntries,
|
|
286
|
+
resolveFillEntry,
|
|
287
|
+
withFillProps
|
|
288
|
+
};
|
|
289
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/create-plugin-registry.ts","../../src/core/create-registry.ts","../../src/core/create-viewed-registry.ts","../../src/core/resolve-fill-entry.ts","../../src/core/merge-registry-entries.ts","../../src/core/global-singletons.ts"],"sourcesContent":["import type { AcmsEventType, AcmsEvents, AcmsLooseEvent, Unsubscribe } from '@ablogcms/events';\nimport { createEventBus } from '@ablogcms/events/core';\nimport { createSlotFillStore, type SlotFillStore, type FillRenderer } from '@ablogcms/components/slot-fill';\nimport type { Action, BulkAction, ColumnTypeDef, Menu, RowData } from '@ablogcms/components/dataview/types';\nimport type { UnitDefInterface } from '@ablogcms/unit-editor/core/types/unit';\nimport { createRegistry } from './create-registry';\nimport { createViewedRegistry, type ViewedRegistry } from './create-viewed-registry';\nimport { resolveFillEntry } from './resolve-fill-entry';\nimport { withFillProps } from './with-fill-props';\nimport type { BlockDefinitionFactory, BlockExtensionFactory } from './global-singletons';\nimport type {\n AcmsPlugins,\n FillEntry,\n PluginContext,\n PluginDefinition,\n RegisteredEntry,\n RegisteredPlugin,\n Registry,\n} from './types';\n\nexport interface CreatePluginRegistryOptions {\n /** 委譲先のイベントバス。省略時は自己完結したインスタンスを新規作成する(テスト用の既定値) */\n eventsBus?: AcmsEvents;\n /** `ctx.ui.fill` の書き込み先。省略時は自己完結したインスタンスを新規作成する(テスト用の既定値) */\n fillStore?: SlotFillStore;\n /** `ctx.unitEditor.registerUnitDefinition` の登録先。省略時は自己完結したインスタンスを新規作成する */\n unitDefinitionRegistry?: Registry<UnitDefInterface>;\n /** `ctx.dataview.registerColumnType` の登録先。省略時は自己完結したインスタンスを新規作成する */\n columnTypeRegistry?: Registry<ColumnTypeDef>;\n /** `ctx.blockEditor.registerBlockDefinition` の登録先。省略時は自己完結したインスタンスを新規作成する */\n blockDefinitionRegistry?: Registry<BlockDefinitionFactory>;\n /** `ctx.blockEditor.registerExtension` の登録先。省略時は自己完結したインスタンスを新規作成する */\n blockExtensionRegistry?: Registry<BlockExtensionFactory>;\n /** `ctx.dataview.registerAction` の登録先。省略時は自己完結したインスタンスを新規作成する */\n dataviewActionRegistry?: ViewedRegistry<Action<RowData>>;\n /** `ctx.dataview.registerBulkAction` の登録先。省略時は自己完結したインスタンスを新規作成する */\n dataviewBulkActionRegistry?: ViewedRegistry<BulkAction<RowData>>;\n /** `ctx.dataview.registerMenu` の登録先。省略時は自己完結したインスタンスを新規作成する */\n dataviewMenuRegistry?: ViewedRegistry<Menu<RowData>>;\n}\n\ninterface InternalPlugin {\n name: string;\n version?: string;\n registrations: RegisteredEntry[];\n subscriptions: Unsubscribe[];\n}\n\n// AcmsEvents#on は型引数 K ごとにハンドラの戻り値を絞り込むため、type がユニオン\n// (AcmsEventType | AcmsEventType[])のまま渡すとハンドラの戻り値を厳密に検証できない。\n// create-event-bus.ts と同じく、内部では緩い型で扱い、公開 API の境界(PluginContext)でのみ\n// 厳密な型へキャストする。\ntype LooseOn = (type: AcmsEventType | AcmsEventType[], handler: (event: AcmsLooseEvent) => unknown) => Unsubscribe;\n\n// プラグイン起因の例外をどのプラグインが投げたか特定できるよう帰属を明示する。\n// イベントバス自身も汎用メッセージで同じ例外をログするため、この行は「どのプラグインか」を示す追加の帰属ログになる。\nconst logHandlerError = (pluginName: string, eventLabel: string, err: unknown): void => {\n console.error(`[@ablogcms/plugins] プラグイン \"${pluginName}\" のハンドラ(\"${eventLabel}\")が例外を投げました。`, err);\n};\n\nconst toEventLabel = (type: AcmsEventType | AcmsEventType[]): string => (Array.isArray(type) ? type.join(',') : type);\n\n/**\n * `window.ACMS.Config` からプラグインの設定値を読む。プラグイン名は `-`/`_` 区切りのことが多いため、\n * `jsModule()` 側の慣例(キャメルケースキー)に合わせて変換する。未初期化・未設定時は空オブジェクトを返し、\n * `setup(ctx)` 内で `ctx.config` を参照しただけで例外にならないようにする。\n */\nconst readConfig = (pluginName: string): Record<string, unknown> => {\n if (typeof window === 'undefined') {\n return {};\n }\n const camelName = pluginName.replace(/[-_](\\w)/g, (_match, chr: string) => chr.toUpperCase());\n const config = (window as unknown as { ACMS?: { Config?: Record<string, unknown> } }).ACMS?.Config;\n const value = config?.[camelName];\n return typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : {};\n};\n\n/**\n * `ACMS.plugins` の純粋実装(グローバル非依存)。VS Code の `activate(context)` +\n * `context.subscriptions` を範に、`setup(ctx)` 内でのイベント購読・レジストリ登録・UI 差し込みを\n * すべて内部の subscriptions 配列に集約し、`unregister(name)` で一括自動回収する。\n *\n * 依存(イベントバス・Fill ブリッジ・各レジストリ)はすべて注入可能で、省略時は自己完結した\n * インスタンスを新規作成する(`@ablogcms/events` の `createEventBus()` と同じ設計。グローバル\n * シングルトンへの結線は本番の bootstrap(admin.js)が `global-singletons.ts` 経由で明示的に行う)。\n */\nexport function createPluginRegistry(options: CreatePluginRegistryOptions = {}): AcmsPlugins {\n const eventsBus = options.eventsBus ?? createEventBus();\n const fillStore = options.fillStore ?? createSlotFillStore();\n const unitDefinitionRegistry = options.unitDefinitionRegistry ?? createRegistry<UnitDefInterface>('unit-definition');\n const columnTypeRegistry = options.columnTypeRegistry ?? createRegistry<ColumnTypeDef>('column-type');\n const blockDefinitionRegistry =\n options.blockDefinitionRegistry ?? createRegistry<BlockDefinitionFactory>('block-definition');\n const blockExtensionRegistry = options.blockExtensionRegistry ?? createRegistry<BlockExtensionFactory>('extension');\n const dataviewActionRegistry =\n options.dataviewActionRegistry ?? createViewedRegistry<Action<RowData>>('dataview-action');\n const dataviewBulkActionRegistry =\n options.dataviewBulkActionRegistry ?? createViewedRegistry<BulkAction<RowData>>('dataview-bulk-action');\n const dataviewMenuRegistry = options.dataviewMenuRegistry ?? createViewedRegistry<Menu<RowData>>('dataview-menu');\n\n const plugins = new Map<string, InternalPlugin>();\n let fillIdCounter = 0;\n\n const createContext = (plugin: InternalPlugin): PluginContext => {\n const onEvent = (\n type: AcmsEventType | AcmsEventType[],\n handler: (event: AcmsLooseEvent) => unknown\n ): Unsubscribe => {\n const label = toEventLabel(type);\n const wrapped = async (event: AcmsLooseEvent) => {\n try {\n return await handler(event);\n } catch (err) {\n logHandlerError(plugin.name, label, err);\n throw err;\n }\n };\n const unsubscribe = (eventsBus.on as LooseOn)(type, wrapped);\n plugin.subscriptions.push(unsubscribe);\n plugin.registrations.push({ kind: 'event', key: label });\n return unsubscribe;\n };\n\n return {\n name: plugin.name,\n config: readConfig(plugin.name),\n events: {\n on: onEvent as PluginContext['events']['on'],\n },\n unitEditor: {\n registerUnitDefinition: (name, def) => {\n const unsubscribe = unitDefinitionRegistry.add(name, def);\n plugin.subscriptions.push(unsubscribe);\n plugin.registrations.push({ kind: 'unit-definition', key: name });\n return unsubscribe;\n },\n },\n blockEditor: {\n registerBlockDefinition: (name, factory) => {\n const unsubscribe = blockDefinitionRegistry.add(name, factory);\n plugin.subscriptions.push(unsubscribe);\n plugin.registrations.push({ kind: 'block-definition', key: name });\n return unsubscribe;\n },\n registerExtension: (name, factory) => {\n const unsubscribe = blockExtensionRegistry.add(name, factory);\n plugin.subscriptions.push(unsubscribe);\n plugin.registrations.push({ kind: 'extension', key: name });\n return unsubscribe;\n },\n },\n dataview: {\n registerColumnType: (name, type) => {\n const unsubscribe = columnTypeRegistry.add(name, type);\n plugin.subscriptions.push(unsubscribe);\n plugin.registrations.push({ kind: 'column-type', key: name });\n return unsubscribe;\n },\n registerAction: (view, name, def) => {\n const unsubscribe = dataviewActionRegistry.add(view, name, def);\n plugin.subscriptions.push(unsubscribe);\n plugin.registrations.push({ kind: 'dataview-action', key: `${view}:${name}` });\n return unsubscribe;\n },\n registerBulkAction: (view, name, def) => {\n const unsubscribe = dataviewBulkActionRegistry.add(view, name, def);\n plugin.subscriptions.push(unsubscribe);\n plugin.registrations.push({ kind: 'dataview-bulk-action', key: `${view}:${name}` });\n return unsubscribe;\n },\n registerMenu: (view, name, def) => {\n const unsubscribe = dataviewMenuRegistry.add(view, name, def);\n plugin.subscriptions.push(unsubscribe);\n plugin.registrations.push({ kind: 'dataview-menu', key: `${view}:${name}` });\n return unsubscribe;\n },\n },\n ui: {\n fill: <T>(slotName: string, entry: FillEntry<T>) => {\n fillIdCounter += 1;\n const id = `plugin:${plugin.name}:${slotName}:${fillIdCounter}`;\n // fillStore(Slot 名ごとに異なる T を型消去した FillRenderer = FillRenderer<unknown> で\n // 保持する)へ渡す境界。Fill コンポーネント(@ablogcms/components/slot-fill)の\n // eraseFillRendererType と同じ理由・同じ境界でここでも明示キャストする。\n const render = resolveFillEntry<T>(entry) as FillRenderer;\n fillStore.upsertFill(slotName, id, render);\n const unsubscribe = () => fillStore.removeFill(slotName, id);\n plugin.subscriptions.push(unsubscribe);\n plugin.registrations.push({ kind: 'fill', key: slotName });\n return unsubscribe;\n },\n },\n };\n };\n\n const register = (name: string, definition: PluginDefinition): void => {\n if (plugins.has(name)) {\n throw new Error(`[@ablogcms/plugins] プラグイン名 \"${name}\" は既に登録されています。`);\n }\n\n const plugin: InternalPlugin = { name, version: definition.version, registrations: [], subscriptions: [] };\n // setup() 実行前に登録しておく。setup() の途中で ctx 経由の登録(例: 名前衝突での throw)が\n // 失敗した場合に catch 節でロールバックできるようにするため。\n plugins.set(name, plugin);\n\n try {\n definition.setup(createContext(plugin));\n } catch (err) {\n // 途中まで行われた登録(イベント購読・レジストリ登録・Fill)をすべて解除し、ゾンビ状態の\n // 半登録プラグインを残さない。呼び出し元は同じ名前で再度 register し直せる。\n plugin.subscriptions.forEach((unsubscribe) => unsubscribe());\n plugins.delete(name);\n throw err;\n }\n };\n\n const unregister = (name: string): boolean => {\n const plugin = plugins.get(name);\n if (!plugin) {\n return false;\n }\n plugin.subscriptions.forEach((unsubscribe) => unsubscribe());\n plugins.delete(name);\n return true;\n };\n\n const toRegisteredPlugin = (plugin: InternalPlugin): RegisteredPlugin => ({\n name: plugin.name,\n version: plugin.version,\n registrations: plugin.registrations,\n });\n\n const get = (name: string): RegisteredPlugin | undefined => {\n const plugin = plugins.get(name);\n return plugin ? toRegisteredPlugin(plugin) : undefined;\n };\n\n const getAll = (): RegisteredPlugin[] => Array.from(plugins.values()).map(toRegisteredPlugin);\n\n return { register, unregister, get, getAll, withFillProps };\n}\n","import type { Registry, Unsubscribe } from './types';\n\n/**\n * 名前衝突を検知する汎用レジストリの純粋実装。`ctx.unitEditor.registerUnitDefinition` /\n * `ctx.dataview.registerColumnType` の土台(Directus の `defineInterface` 等と同型)。\n *\n * `add()` が返す `Unsubscribe` は、登録時点の値を閉じ込めて比較してから削除する。\n * 「解除 → 同名で再登録」の後に古い `Unsubscribe` が誤って新しい登録を消してしまう事故を防ぐ\n * (`name` は一意だが、`Unsubscribe` のライフサイクルは呼び出し側が保持し続けるため)。\n */\nexport function createRegistry<T>(label: string): Registry<T> {\n const items = new Map<string, T>();\n\n const add = (name: string, item: T): Unsubscribe => {\n if (items.has(name)) {\n throw new Error(`[@ablogcms/plugins] ${label} \"${name}\" は既に登録されています。`);\n }\n items.set(name, item);\n return () => {\n if (items.get(name) === item) {\n items.delete(name);\n }\n };\n };\n\n const remove = (name: string): boolean => items.delete(name);\n\n const get = (name: string): T | undefined => items.get(name);\n\n const getAll = (): Record<string, T> => Object.fromEntries(items);\n\n return { add, remove, get, getAll };\n}\n","import type { Registry, Unsubscribe } from './types';\nimport { createRegistry } from './create-registry';\n\n/**\n * `view`(`'entry' | 'module'` 等、将来拡張可能な文字列)ごとに独立した名前空間を持つレジストリ。\n * `ctx.dataview.registerAction(view, name, def)` の土台(design.md Q4「view キー方式」)。\n *\n * 内部では `view` ごとに素の {@link createRegistry} インスタンスを遅延生成して保持する\n * (文字列連結キー方式にしない理由: view 名に区切り文字が含まれた場合の衝突を避けるため)。\n */\nexport interface ViewedRegistry<T> {\n /** `view` 内で `name` が既に登録済みの場合は throw する */\n add(view: string, name: string, item: T): Unsubscribe;\n remove(view: string, name: string): boolean;\n get(view: string, name: string): T | undefined;\n /** 指定した `view` に登録された項目のみを返す。未登録の `view` は空オブジェクト */\n getAll(view: string): Record<string, T>;\n}\n\nexport function createViewedRegistry<T>(label: string): ViewedRegistry<T> {\n const registries = new Map<string, Registry<T>>();\n\n const registryFor = (view: string): Registry<T> => {\n let registry = registries.get(view);\n if (!registry) {\n registry = createRegistry<T>(`${label}(${view})`);\n registries.set(view, registry);\n }\n return registry;\n };\n\n return {\n add: (view, name, item) => registryFor(view).add(name, item),\n remove: (view, name) => registryFor(view).remove(name),\n get: (view, name) => registryFor(view).get(name),\n getAll: (view) => registries.get(view)?.getAll() ?? {},\n };\n}\n","import { createElement, type ComponentType, type FunctionComponent, type ReactNode } from 'react';\nimport { DomFillMount } from '@ablogcms/components/slot-fill';\nimport type { FillRenderer } from '@ablogcms/components/slot-fill';\nimport { isFillComponentEntry } from './with-fill-props';\nimport type { DomFill, FillEntry } from './types';\n\n/**\n * `ctx.ui.fill(slotName, entry)` の `entry` を、Fill ストア(`@ablogcms/components/slot-fill`)に\n * そのまま渡せる `FillRenderer`(`ReactNode`、または `Slot` の `fillProps` を受け取る render-prop\n * 関数)に変換する。\n *\n * 判別は 3 種類:\n * 1. `withFillProps(Component)` が返すマーカーオブジェクト(`FillComponentEntry`)→ `fillProps` を\n * 受け取る render-prop 関数を返す(下記参照)。\n * 2. 仮引数 1 個以上の関数 → `DomFill` として `DomFillMount` でラップする。\n * 3. それ以外(仮引数 0 個の関数)→ props なしの React コンポーネントとして描画する。\n *\n * 2/3 の判別は Why not: 実行時には両者とも「関数」という点で構造的に区別できず\n * (`React.ComponentType` も `DomFill` も型注釈はコンパイル時に消える)、関数名(`.name`)も\n * 拠り所にならない(`vi.fn()` 等のラッパーを経由すると `.name` が `\"Mock\"` 等に上書きされ、\n * テストのモックが大文字始まりになって誤判定するのを実測で確認した)。代わりに\n * `Function.prototype.length`(宣言された仮引数の個数)で判別する(`vi.fn()` 等でラップされても\n * 元関数の `.length` は保たれる)。この 0/1 個の判別は既存の後方互換のため変更しない。\n *\n * 1 のケースをこの判別の外に出している Why not: props を要求するコンポーネント\n * (`({ editor, unit }) => ...`)は仮引数が 1 個になり、`DomFill`(`(el) => ...`)と実行時に\n * 区別できない。`withFillProps` はオブジェクトでラップするため、`typeof entry === 'function'` の\n * 分岐に到達する前に見分けがつき、既存の 0/1 個判別と衝突しない。\n */\nexport function resolveFillEntry(entry: ComponentType | DomFill): ReactNode;\nexport function resolveFillEntry<T>(entry: FillEntry<T>): FillRenderer<T>;\nexport function resolveFillEntry<T>(entry: FillEntry<T>): FillRenderer<T> {\n if (isFillComponentEntry<T>(entry)) {\n // createElement<P>(FunctionComponent<P>, props) はここで P=T を推論できない(T は\n // resolveFillEntry 自身のジェネリックで、createElement 側から見ると「{} と無関係かもしれない\n // 任意の型」にしか見えないため、TS2769 になる。呼び出し境界でのみ型消去する\n // (fill.tsx の eraseFillRendererType と同じ「型消去の境界であることを明示するキャスト」)。\n const Component = entry.Component as FunctionComponent<Record<string, unknown>>;\n // Slot 側(renderFill)は render が関数なら fillProps を渡して呼ぶ実装を既に持つため、\n // ここでは呼び出しを fillProps が判明する Slot の描画タイミングまで遅延させるだけでよい。\n // eslint-disable-next-line react/display-name -- コンポーネント定義ではなく FillRenderer を返す関数(createElement 呼び出しを含むアロー関数がヒューリスティックに誤検知される)\n return (fillProps: T) => createElement(Component, fillProps as Record<string, unknown>);\n }\n if (typeof entry === 'function' && entry.length > 0) {\n return createElement(DomFillMount, { mount: entry as DomFill });\n }\n return createElement(entry as ComponentType);\n}\n","import type { Registry } from './types';\n\n/**\n * コア側の組み込み定義(`core`)と、プラグインが `Registry.add`(`ctx.unitEditor.registerUnitDefinition` /\n * `ctx.dataview.registerColumnType` 経由)で登録した内容をマージする。\n *\n * react-19-migration フェーズ3-1a/3-1b で設計した `unitDefs` / `columnTypes` prop の値計算を\n * レジストリ経由に変える配線(design.md「レジストリ(機能登録)」節)の共通実装。\n * キー衝突時はレジストリ(プラグイン)側を優先する(design.md の例\n * `{ ...coreUnitDefs, ...unitDefinitionRegistry.getAll() }` と同じマージ順)。\n */\nexport function mergeRegistryEntries<T>(core: Record<string, T>, registry: Registry<T>): Record<string, T> {\n return { ...core, ...registry.getAll() };\n}\n","/**\n * ページ全体で 1 つだけ存在するべきシングルトン群。\n *\n * 管理画面は dispatcher ごとに独立した React ルートを `createRoot` する構成\n * (`@ablogcms/utils/react` の `render()`)のため、単一の `<SlotFillProvider>` でページ全体を\n * 包むことができない。`ctx.ui.fill` が書き込む Fill ブリッジをここでシングルトン化しておき、\n * 各ツリーの `<SlotFillProvider store={getGlobalFillBridge()}>` がこれを購読することで、\n * どの独立ツリーの `<Slot>` にも同じ登録内容が届く(design.md「グローバル Fill ブリッジ」節)。\n *\n * `unitDefs` / `columnTypes` prop の値計算(react-19-migration フェーズ3-1a/3-1b)も同じ理由で\n * dispatch 側の複数箇所から同一レジストリを参照する必要があるため、ここでシングルトン化する\n * (実際の prop 配線はフェーズD)。\n *\n * `createPluginRegistry()` 自体は各テストが独立したインスタンスで動かせるよう、これらに依存しない\n * 素の工場関数のまま保つ(`@ablogcms/events` の `createEventBus()` と同じ設計)。\n * 本番の結線(admin.js)でのみ、ここのシングルトンを明示的に注入する。\n */\nimport { createSlotFillStore, type SlotFillStore } from '@ablogcms/components/slot-fill';\nimport type { Action, BulkAction, ColumnTypeDef, Menu, RowData } from '@ablogcms/components/dataview/types';\nimport type { UnitDefInterface } from '@ablogcms/unit-editor/core/types/unit';\nimport type { BlockDefinition } from '@ablogcms/block-editor/types';\nimport type { AnyExtension } from '@ablogcms/block-editor/tiptap/core';\nimport type { Tiptap } from '@ablogcms/block-editor/tiptap';\nimport { createRegistry } from './create-registry';\nimport { createViewedRegistry, type ViewedRegistry } from './create-viewed-registry';\nimport type { Registry } from './types';\n\n/** `ctx.blockEditor.registerBlockDefinition` / `registerExtension` が登録するファクトリの型。 */\nexport type BlockDefinitionFactory = (runtime: Tiptap) => BlockDefinition;\nexport type BlockExtensionFactory = (runtime: Tiptap) => AnyExtension | AnyExtension[];\n\nlet fillBridge: SlotFillStore | undefined;\n\nexport function getGlobalFillBridge(): SlotFillStore {\n if (!fillBridge) {\n fillBridge = createSlotFillStore();\n }\n return fillBridge;\n}\n\nlet unitDefinitionRegistry: Registry<UnitDefInterface> | undefined;\n\nexport function getUnitDefinitionRegistry(): Registry<UnitDefInterface> {\n if (!unitDefinitionRegistry) {\n unitDefinitionRegistry = createRegistry<UnitDefInterface>('unit-definition');\n }\n return unitDefinitionRegistry;\n}\n\nlet columnTypeRegistry: Registry<ColumnTypeDef> | undefined;\n\nexport function getColumnTypeRegistry(): Registry<ColumnTypeDef> {\n if (!columnTypeRegistry) {\n columnTypeRegistry = createRegistry<ColumnTypeDef>('column-type');\n }\n return columnTypeRegistry;\n}\n\nlet blockDefinitionRegistry: Registry<BlockDefinitionFactory> | undefined;\n\nexport function getBlockDefinitionRegistry(): Registry<BlockDefinitionFactory> {\n if (!blockDefinitionRegistry) {\n blockDefinitionRegistry = createRegistry<BlockDefinitionFactory>('block-definition');\n }\n return blockDefinitionRegistry;\n}\n\nlet blockExtensionRegistry: Registry<BlockExtensionFactory> | undefined;\n\nexport function getBlockExtensionRegistry(): Registry<BlockExtensionFactory> {\n if (!blockExtensionRegistry) {\n blockExtensionRegistry = createRegistry<BlockExtensionFactory>('extension');\n }\n return blockExtensionRegistry;\n}\n\nlet dataviewActionRegistry: ViewedRegistry<Action<RowData>> | undefined;\n\nexport function getDataviewActionRegistry(): ViewedRegistry<Action<RowData>> {\n if (!dataviewActionRegistry) {\n dataviewActionRegistry = createViewedRegistry<Action<RowData>>('dataview-action');\n }\n return dataviewActionRegistry;\n}\n\nlet dataviewBulkActionRegistry: ViewedRegistry<BulkAction<RowData>> | undefined;\n\nexport function getDataviewBulkActionRegistry(): ViewedRegistry<BulkAction<RowData>> {\n if (!dataviewBulkActionRegistry) {\n dataviewBulkActionRegistry = createViewedRegistry<BulkAction<RowData>>('dataview-bulk-action');\n }\n return dataviewBulkActionRegistry;\n}\n\nlet dataviewMenuRegistry: ViewedRegistry<Menu<RowData>> | undefined;\n\nexport function getDataviewMenuRegistry(): ViewedRegistry<Menu<RowData>> {\n if (!dataviewMenuRegistry) {\n dataviewMenuRegistry = createViewedRegistry<Menu<RowData>>('dataview-menu');\n }\n return dataviewMenuRegistry;\n}\n"],"mappings":"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","names":["unitDefinitionRegistry","columnTypeRegistry","blockDefinitionRegistry","blockExtensionRegistry","dataviewActionRegistry","dataviewBulkActionRegistry","dataviewMenuRegistry","createSlotFillStore","createSlotFillStore"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Registry } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* コア側の組み込み定義(`core`)と、プラグインが `Registry.add`(`ctx.unitEditor.registerUnitDefinition` /
|
|
4
|
+
* `ctx.dataview.registerColumnType` 経由)で登録した内容をマージする。
|
|
5
|
+
*
|
|
6
|
+
* react-19-migration フェーズ3-1a/3-1b で設計した `unitDefs` / `columnTypes` prop の値計算を
|
|
7
|
+
* レジストリ経由に変える配線(design.md「レジストリ(機能登録)」節)の共通実装。
|
|
8
|
+
* キー衝突時はレジストリ(プラグイン)側を優先する(design.md の例
|
|
9
|
+
* `{ ...coreUnitDefs, ...unitDefinitionRegistry.getAll() }` と同じマージ順)。
|
|
10
|
+
*/
|
|
11
|
+
export declare function mergeRegistryEntries<T>(core: Record<string, T>, registry: Registry<T>): Record<string, T>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ComponentType, type ReactNode } from 'react';
|
|
2
|
+
import type { FillRenderer } from '@ablogcms/components/slot-fill';
|
|
3
|
+
import type { DomFill, FillEntry } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* `ctx.ui.fill(slotName, entry)` の `entry` を、Fill ストア(`@ablogcms/components/slot-fill`)に
|
|
6
|
+
* そのまま渡せる `FillRenderer`(`ReactNode`、または `Slot` の `fillProps` を受け取る render-prop
|
|
7
|
+
* 関数)に変換する。
|
|
8
|
+
*
|
|
9
|
+
* 判別は 3 種類:
|
|
10
|
+
* 1. `withFillProps(Component)` が返すマーカーオブジェクト(`FillComponentEntry`)→ `fillProps` を
|
|
11
|
+
* 受け取る render-prop 関数を返す(下記参照)。
|
|
12
|
+
* 2. 仮引数 1 個以上の関数 → `DomFill` として `DomFillMount` でラップする。
|
|
13
|
+
* 3. それ以外(仮引数 0 個の関数)→ props なしの React コンポーネントとして描画する。
|
|
14
|
+
*
|
|
15
|
+
* 2/3 の判別は Why not: 実行時には両者とも「関数」という点で構造的に区別できず
|
|
16
|
+
* (`React.ComponentType` も `DomFill` も型注釈はコンパイル時に消える)、関数名(`.name`)も
|
|
17
|
+
* 拠り所にならない(`vi.fn()` 等のラッパーを経由すると `.name` が `"Mock"` 等に上書きされ、
|
|
18
|
+
* テストのモックが大文字始まりになって誤判定するのを実測で確認した)。代わりに
|
|
19
|
+
* `Function.prototype.length`(宣言された仮引数の個数)で判別する(`vi.fn()` 等でラップされても
|
|
20
|
+
* 元関数の `.length` は保たれる)。この 0/1 個の判別は既存の後方互換のため変更しない。
|
|
21
|
+
*
|
|
22
|
+
* 1 のケースをこの判別の外に出している Why not: props を要求するコンポーネント
|
|
23
|
+
* (`({ editor, unit }) => ...`)は仮引数が 1 個になり、`DomFill`(`(el) => ...`)と実行時に
|
|
24
|
+
* 区別できない。`withFillProps` はオブジェクトでラップするため、`typeof entry === 'function'` の
|
|
25
|
+
* 分岐に到達する前に見分けがつき、既存の 0/1 個判別と衝突しない。
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveFillEntry(entry: ComponentType | DomFill): ReactNode;
|
|
28
|
+
export declare function resolveFillEntry<T>(entry: FillEntry<T>): FillRenderer<T>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ablogcms/plugins` の型定義。React・グローバル(window.ACMS)いずれにも依存しない。
|
|
3
|
+
*
|
|
4
|
+
* VS Code の `activate(context)` + `context.subscriptions` を範に、イベント購読・機能登録
|
|
5
|
+
* (unitEditor/dataview)・UI 差し込み(Slot/Fill)・設定参照を 1 つの `setup(ctx)` に束ねる。
|
|
6
|
+
*/
|
|
7
|
+
import type { ComponentType, FunctionComponent } from 'react';
|
|
8
|
+
import type { AcmsEventType, AcmsHandler, Unsubscribe } from '@ablogcms/events';
|
|
9
|
+
import type { Action, BulkAction, ColumnTypeDef, Menu, RowData } from '@ablogcms/components/dataview/types';
|
|
10
|
+
import type { UnitDefInterface } from '@ablogcms/unit-editor/core/types/unit';
|
|
11
|
+
import type { BlockDefinition } from '@ablogcms/block-editor/types';
|
|
12
|
+
import type { AnyExtension } from '@ablogcms/block-editor/tiptap/core';
|
|
13
|
+
import type { Tiptap } from '@ablogcms/block-editor/tiptap';
|
|
14
|
+
import type { FillComponentEntry } from './with-fill-props.js';
|
|
15
|
+
/**
|
|
16
|
+
* dataview アクション/一括アクション/メニューレジストリの名前空間キー。
|
|
17
|
+
* `'entry' | 'module'` を主要値としつつ、将来 `'user'` / `'category'` 等へ拡張できるよう
|
|
18
|
+
* 文字列全般も受け付ける(design.md Q4「view キー方式」)。
|
|
19
|
+
*/
|
|
20
|
+
export type DataviewView = 'entry' | 'module' | (string & {});
|
|
21
|
+
export type { Unsubscribe };
|
|
22
|
+
export type { FillComponentEntry } from './with-fill-props.js';
|
|
23
|
+
/**
|
|
24
|
+
* React を書けない制作者向けの DOM コールバック Fill(kintone の公認マウントポイント方式)。
|
|
25
|
+
* 渡された要素に自由に DOM 操作でき、戻り値(あれば)はアンマウント時のクリーンアップとして呼ばれる。
|
|
26
|
+
*/
|
|
27
|
+
export type DomFill = (el: HTMLElement) => void | (() => void);
|
|
28
|
+
/**
|
|
29
|
+
* `ctx.ui.fill` に渡せるエントリ。実行時は「関数」という点で `ComponentType` と `DomFill` を
|
|
30
|
+
* 構造的に区別できないため、実際の判別は `resolve-fill-entry.ts` が仮引数の個数
|
|
31
|
+
* (0 個 = コンポーネント、1 個以上 = DomFill)に基づいて行う。`FillComponentEntry`
|
|
32
|
+
* (`withFillProps` で作る)はオブジェクトのため、この arity 判別とは独立に見分けられる。
|
|
33
|
+
*/
|
|
34
|
+
export type FillEntry<T = unknown> = ComponentType | DomFill | FillComponentEntry<T>;
|
|
35
|
+
/** `setup(ctx)` に渡されるコンテキスト */
|
|
36
|
+
export interface PluginContext {
|
|
37
|
+
/** プラグイン自身の名前(読み取り専用) */
|
|
38
|
+
readonly name: string;
|
|
39
|
+
/** イベント購読。`unregister` 時に自動 off される */
|
|
40
|
+
events: {
|
|
41
|
+
on<K extends AcmsEventType>(type: K | K[], handler: AcmsHandler<K>): Unsubscribe;
|
|
42
|
+
};
|
|
43
|
+
/** ユニットタイプの登録。`unregister` 時に自動解除される */
|
|
44
|
+
unitEditor: {
|
|
45
|
+
registerUnitDefinition(name: string, def: UnitDefInterface): Unsubscribe;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* ブロックタイプ・Tiptap 拡張の登録。`unregister` 時に自動解除される。
|
|
49
|
+
* どちらも「Tiptap ランタイムを受け取って値を返すファクトリ」を渡す(値そのものではない)。
|
|
50
|
+
* ブロックエディターは lazy チャンクのため、登録時点(プラグイン JS 読み込み時)ではまだ
|
|
51
|
+
* チャンクがロードされておらず Tiptap の値を要求できない。ファクトリはチャンクロード後、
|
|
52
|
+
* 実際にメニュー解決・拡張構築が必要になった時点で呼ばれる。
|
|
53
|
+
*/
|
|
54
|
+
blockEditor: {
|
|
55
|
+
/** BLOCK_DEFINITIONS マスターへの追加(メニューに現れた type の実装)。 */
|
|
56
|
+
registerBlockDefinition(name: string, factory: (runtime: Tiptap) => BlockDefinition): Unsubscribe;
|
|
57
|
+
/** Tiptap スキーマ層への追加(新規 node/mark type、メニューを持たない挙動拡張等)。 */
|
|
58
|
+
registerExtension(name: string, factory: (runtime: Tiptap) => AnyExtension | AnyExtension[]): Unsubscribe;
|
|
59
|
+
};
|
|
60
|
+
/** dataview カラム型・アクション・一括アクション・メニューの登録。`unregister` 時に自動解除される */
|
|
61
|
+
dataview: {
|
|
62
|
+
registerColumnType(name: string, type: ColumnTypeDef): Unsubscribe;
|
|
63
|
+
/**
|
|
64
|
+
* 一覧画面の行アクション(編集/削除ボタン等)を登録する。`view` ごとに名前空間が独立する
|
|
65
|
+
* (`'entry'` と `'module'` で同じ `name` を使っても衝突しない)。
|
|
66
|
+
* 行の型はレジストリ境界で消去される(`RowData` は実質 `unknown`)ため、実際の型は
|
|
67
|
+
* 消費側の feature(`use-entry-admin-actions.tsx` 等)が `Action<EntryType>` 等へキャストする。
|
|
68
|
+
*/
|
|
69
|
+
registerAction(view: DataviewView, name: string, def: Action<RowData>): Unsubscribe;
|
|
70
|
+
/** 一覧画面の一括アクション(複数選択後のフォーム送信)を登録する。`registerAction` と同じ view 名前空間規約 */
|
|
71
|
+
registerBulkAction(view: DataviewView, name: string, def: BulkAction<RowData>): Unsubscribe;
|
|
72
|
+
/** 一覧画面のドロップダウンメニュー項目を登録する。`registerAction` と同じ view 名前空間規約 */
|
|
73
|
+
registerMenu(view: DataviewView, name: string, def: Menu<RowData>): Unsubscribe;
|
|
74
|
+
};
|
|
75
|
+
/** UI 差し込み(React コンポーネント・DOM コールバックの両対応)。`unregister` 時に自動解除される */
|
|
76
|
+
ui: {
|
|
77
|
+
fill<T = unknown>(slotName: string, entry: FillEntry<T>): Unsubscribe;
|
|
78
|
+
};
|
|
79
|
+
/** PHP 側から `jsModule()` 経由で渡された当該プラグインの設定値(キャメルケース変換で対応) */
|
|
80
|
+
readonly config: Record<string, unknown>;
|
|
81
|
+
}
|
|
82
|
+
export interface PluginDefinition {
|
|
83
|
+
version?: string;
|
|
84
|
+
setup(ctx: PluginContext): void;
|
|
85
|
+
}
|
|
86
|
+
/** ctx 経由で行った登録 1 件分の帰属情報(`getAll()` によるデバッグ列挙用) */
|
|
87
|
+
export interface RegisteredEntry {
|
|
88
|
+
kind: 'event' | 'unit-definition' | 'column-type' | 'block-definition' | 'extension' | 'fill' | 'dataview-action' | 'dataview-bulk-action' | 'dataview-menu';
|
|
89
|
+
/**
|
|
90
|
+
* イベント名(複数指定時はカンマ区切り) / unit-definition 名 / column-type 名 /
|
|
91
|
+
* block-definition 名 / extension 名 / slot 名 / `dataview-action` 等は `${view}:${name}`
|
|
92
|
+
*/
|
|
93
|
+
key: string;
|
|
94
|
+
}
|
|
95
|
+
export interface RegisteredPlugin {
|
|
96
|
+
name: string;
|
|
97
|
+
version?: string;
|
|
98
|
+
/** そのプラグインが ctx 経由で行った登録の一覧(登録順) */
|
|
99
|
+
registrations: readonly RegisteredEntry[];
|
|
100
|
+
}
|
|
101
|
+
export interface AcmsPlugins {
|
|
102
|
+
/** プラグインを登録する。`name` が既に登録済みの場合は throw する */
|
|
103
|
+
register(name: string, definition: PluginDefinition): void;
|
|
104
|
+
/** プラグインを登録解除する。ctx 経由の登録をすべて自動回収する。存在した場合のみ true を返す */
|
|
105
|
+
unregister(name: string): boolean;
|
|
106
|
+
get(name: string): RegisteredPlugin | undefined;
|
|
107
|
+
getAll(): RegisteredPlugin[];
|
|
108
|
+
/**
|
|
109
|
+
* `ctx.ui.fill` の `fillProps` を受け取る React コンポーネントを登録するためのラッパー
|
|
110
|
+
* (`with-fill-props.ts` 参照)。外部プラグイン(`InjectTemplate::add()` で読み込まれ、
|
|
111
|
+
* npm パッケージを import できない環境)はここ(`ACMS.plugins.withFillProps`)からのみ
|
|
112
|
+
* 入手できる。内部コード(本体・パッケージ)は `@ablogcms/plugins` からの直接 import も使える。
|
|
113
|
+
*/
|
|
114
|
+
withFillProps<T>(Component: FunctionComponent<T>): FillComponentEntry<T>;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 汎用の名前付きレジストリ。`ctx.unitEditor.registerUnitDefinition` / `ctx.dataview.registerColumnType`
|
|
118
|
+
* の土台。名前衝突を検知し、登録済み項目を列挙できる(Directus の `defineInterface` 等と同型)。
|
|
119
|
+
*/
|
|
120
|
+
export interface Registry<T> {
|
|
121
|
+
/** `name` が既に登録済みの場合は throw する。戻り値の `Unsubscribe` でその登録だけを解除できる */
|
|
122
|
+
add(name: string, item: T): Unsubscribe;
|
|
123
|
+
/** `name` の登録を解除する。存在した場合のみ true を返す */
|
|
124
|
+
remove(name: string): boolean;
|
|
125
|
+
get(name: string): T | undefined;
|
|
126
|
+
getAll(): Record<string, T>;
|
|
127
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { FunctionComponent } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* `withFillProps` が返すマーカー付きエントリ。`ctx.ui.fill` の判別(`resolve-fill-entry.ts`)が
|
|
4
|
+
* オブジェクトかどうかで即座に見分けられるよう、`kind` に固定のブランド文字列を持たせる。
|
|
5
|
+
*
|
|
6
|
+
* `Component` を `ComponentType<T>`(関数・クラス両対応)ではなく `FunctionComponent<T>` に
|
|
7
|
+
* 限定している Why not: `createElement(Component, fillProps)` を T についてジェネリックなまま
|
|
8
|
+
* 呼ぶと、`ComponentType<T>` が関数・クラスの合併型であるために `createElement` のオーバーロード
|
|
9
|
+
* 解決が失敗する(クラス側 `getDerivedStateFromProps` の反変位置で `T` の消去に起因する型エラー、
|
|
10
|
+
* 実測で確認)。本リポジトリはクラスコンポーネントの新規追加を禁止する規約
|
|
11
|
+
* (development-guidelines.md)のため、関数コンポーネントのみに絞ることで実用上の制約にならない。
|
|
12
|
+
*/
|
|
13
|
+
export interface FillComponentEntry<T> {
|
|
14
|
+
readonly kind: 'acms-plugins/fill-component';
|
|
15
|
+
readonly Component: FunctionComponent<T>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* `Slot` の `fillProps`(例: `unit-editor.unit-toolbar` の `{ editor, unit }`)を受け取る React
|
|
19
|
+
* コンポーネントを `ctx.ui.fill` に登録するためのラッパー。
|
|
20
|
+
*
|
|
21
|
+
* Why not(関数のままでは表現できない理由): `resolveFillEntry` は仮引数の個数
|
|
22
|
+
* (0 個 = props なしコンポーネント、1 個以上 = `DomFill`)で判別しており、この規約は
|
|
23
|
+
* `ctx.ui.fill` の当初からの後方互換として維持する。props を要求するコンポーネント
|
|
24
|
+
* (`({ editor, unit }) => ...`)は仮引数が 1 個になり、`DomFill`(`(el) => ...`)と実行時に
|
|
25
|
+
* 構造的に区別できない(`Function.prototype.length` だけでは両者が衝突する)。
|
|
26
|
+
* `withFillProps` は関数ではなくオブジェクトでラップすることで、既存の arity 判別ロジックに
|
|
27
|
+
* 一切手を入れずにこの衝突を回避する。
|
|
28
|
+
*
|
|
29
|
+
* ```tsx
|
|
30
|
+
* ctx.ui.fill('unit-editor.unit-toolbar', withFillProps<UnitToolbarSlotFillProps>(({ editor, unit }) => (
|
|
31
|
+
* <button type="button">{unit.type}</button>
|
|
32
|
+
* )));
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function withFillProps<T>(Component: FunctionComponent<T>): FillComponentEntry<T>;
|
|
36
|
+
/** `entry` が `withFillProps` で作られたマーカー付きエントリかどうかを判定する */
|
|
37
|
+
export declare function isFillComponentEntry<T>(entry: unknown): entry is FillComponentEntry<T>;
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* entry/module admin(一覧画面)のライフサイクル操作(trash/duplicate/一括ステータス変更等)を
|
|
3
|
+
* `AcmsEventMap`(`@ablogcms/events`)へ登録する module augmentation。
|
|
4
|
+
*
|
|
5
|
+
* Why not(`ablogcms/js/src/features/{entry,module}` に置かない理由): `EntryType`/`ModuleType` は
|
|
6
|
+
* それぞれの機能ディレクトリ配下にのみ存在するアプリ側の型だが、実測でアプリ側(ablogcms 配下)の
|
|
7
|
+
* ファイルから `declare module '@ablogcms/events'` で `AcmsEventMap` を拡張すると、他パッケージ
|
|
8
|
+
* (`packages/frontend/media` 等)側の `AcmsEventOf<K>` 解決が緩い `AcmsLooseEvent` に落ちる
|
|
9
|
+
* (`gateResult.event.formData` が `unknown` になる等)ビルド不整合が発生した
|
|
10
|
+
* (コメント記法や型の複雑さとは無関係に、単一の空エントリでも再現する)。行の型を消去した
|
|
11
|
+
* `RowData`(`@ablogcms/components/dataview/types`。`ctx.dataview.registerAction` 等が既に
|
|
12
|
+
* 使っている型)で宣言することで、この問題を避けつつ `@ablogcms/plugins` が dataview 関連の
|
|
13
|
+
* 型をまとめて持つ既存の構成に合わせる。実際の型は消費側の feature
|
|
14
|
+
* (`use-entry-admin-actions.tsx` 等)が `EntryType`/`ModuleType` へキャストする。
|
|
15
|
+
*/
|
|
16
|
+
import type { AcmsGateEvent, AcmsNotifyEvent } from '@ablogcms/events';
|
|
17
|
+
import type { RowData } from '@ablogcms/components/dataview/types';
|
|
18
|
+
declare module '@ablogcms/events' {
|
|
19
|
+
interface AcmsEventMap {
|
|
20
|
+
/** 単発の「ゴミ箱へ移動」実行直前のゲート。`use-entry-admin-actions.tsx` の trash アクション */
|
|
21
|
+
'entry.trash.before': AcmsGateEvent<{
|
|
22
|
+
entry: RowData;
|
|
23
|
+
}>;
|
|
24
|
+
/** 上記の実行成功後の通知 */
|
|
25
|
+
'entry.trash.success': AcmsNotifyEvent<{
|
|
26
|
+
entry: RowData;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* 単発の複製実行成功後の通知。`use-entry-admin-actions.tsx` の duplicate アクション。
|
|
30
|
+
* 複製確認ダイアログが無く取り消し不能な操作のため before ゲートは設けない
|
|
31
|
+
*/
|
|
32
|
+
'entry.duplicate.success': AcmsNotifyEvent<{
|
|
33
|
+
entry: RowData;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* 一括ステータス変更(公開/非公開切替)実行直前のゲート。`use-entry-admin-bulk-actions.tsx`。
|
|
37
|
+
* `formData` を差し替えて return すると実際に送信される内容を変えられる
|
|
38
|
+
*/
|
|
39
|
+
'entry.bulk-status.before': AcmsGateEvent<{
|
|
40
|
+
formData: FormData;
|
|
41
|
+
}>;
|
|
42
|
+
/** 上記の実行成功後の通知 */
|
|
43
|
+
'entry.bulk-status.success': AcmsNotifyEvent<{
|
|
44
|
+
formData: FormData;
|
|
45
|
+
}>;
|
|
46
|
+
/** 一括「ゴミ箱へ移動」実行成功後の通知。`use-entry-admin-bulk-actions.tsx` */
|
|
47
|
+
'entry.bulk-trash.success': AcmsNotifyEvent<{
|
|
48
|
+
formData: FormData;
|
|
49
|
+
}>;
|
|
50
|
+
/** 一括複製実行成功後の通知。`use-entry-admin-bulk-actions.tsx` */
|
|
51
|
+
'entry.bulk-duplicate.success': AcmsNotifyEvent<{
|
|
52
|
+
formData: FormData;
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* 一括ステータス変更実行直前のゲート。`use-module-admin-bulk-actions.tsx`。
|
|
56
|
+
* entry 側の `entry.bulk-status.*` と同型の横展開
|
|
57
|
+
*/
|
|
58
|
+
'module.bulk-status.before': AcmsGateEvent<{
|
|
59
|
+
formData: FormData;
|
|
60
|
+
}>;
|
|
61
|
+
/** 上記の実行成功後の通知 */
|
|
62
|
+
'module.bulk-status.success': AcmsNotifyEvent<{
|
|
63
|
+
formData: FormData;
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ablogcms/plugins` のルートエントリ(グローバル委譲ファサード)。
|
|
3
|
+
*
|
|
4
|
+
* `window.ACMS.plugins`(管理画面コアが `/core` の `createPluginRegistry()` で生成したシングルトン)に
|
|
5
|
+
* 処理を委譲する薄いラッパー。プラグイン・テーマの JS バンドルに同梱されても、実体は常にコアが
|
|
6
|
+
* 保持する単一インスタンスに到達するため、別インスタンス問題(登録が黙って無効になる事故)が起きない。
|
|
7
|
+
*
|
|
8
|
+
* グローバル未初期化(管理画面外・初期化前に読み込まれた等)の場合は明確なエラーを投げる。
|
|
9
|
+
* レジストリ本体の純粋実装・グローバル非依存のユニットテストは `@ablogcms/plugins/core` を使う。
|
|
10
|
+
*/
|
|
11
|
+
import type { AcmsPlugins, PluginDefinition, RegisteredPlugin } from './core/types.js';
|
|
12
|
+
export * from './core/types.js';
|
|
13
|
+
import { withFillProps } from './core/with-fill-props.js';
|
|
14
|
+
export { withFillProps };
|
|
15
|
+
export declare function register(name: string, definition: PluginDefinition): void;
|
|
16
|
+
export declare function unregister(name: string): boolean;
|
|
17
|
+
export declare function get(name: string): RegisteredPlugin | undefined;
|
|
18
|
+
export declare function getAll(): RegisteredPlugin[];
|
|
19
|
+
declare const plugins: AcmsPlugins;
|
|
20
|
+
export default plugins;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withFillProps
|
|
3
|
+
} from "./chunk-OWV4U7DT.mjs";
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
var getGlobalPlugins = () => {
|
|
7
|
+
if (typeof window === "undefined") {
|
|
8
|
+
return void 0;
|
|
9
|
+
}
|
|
10
|
+
return window.ACMS?.plugins;
|
|
11
|
+
};
|
|
12
|
+
var requireGlobalPlugins = () => {
|
|
13
|
+
const plugins2 = getGlobalPlugins();
|
|
14
|
+
if (!plugins2) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"[@ablogcms/plugins] window.ACMS.plugins \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u7BA1\u7406\u753B\u9762\u306E\u521D\u671F\u5316\u524D\u3001\u307E\u305F\u306F\u7BA1\u7406\u753B\u9762\u5916\uFF08\u516C\u958B\u5074\u30C6\u30FC\u30DE\u7B49\uFF09\u3067\u8AAD\u307F\u8FBC\u307E\u308C\u3066\u3044\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002"
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return plugins2;
|
|
20
|
+
};
|
|
21
|
+
function register(name, definition) {
|
|
22
|
+
requireGlobalPlugins().register(name, definition);
|
|
23
|
+
}
|
|
24
|
+
function unregister(name) {
|
|
25
|
+
return requireGlobalPlugins().unregister(name);
|
|
26
|
+
}
|
|
27
|
+
function get(name) {
|
|
28
|
+
return requireGlobalPlugins().get(name);
|
|
29
|
+
}
|
|
30
|
+
function getAll() {
|
|
31
|
+
return requireGlobalPlugins().getAll();
|
|
32
|
+
}
|
|
33
|
+
var plugins = { register, unregister, get, getAll, withFillProps };
|
|
34
|
+
var index_default = plugins;
|
|
35
|
+
export {
|
|
36
|
+
index_default as default,
|
|
37
|
+
get,
|
|
38
|
+
getAll,
|
|
39
|
+
register,
|
|
40
|
+
unregister,
|
|
41
|
+
withFillProps
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * `@ablogcms/plugins` のルートエントリ(グローバル委譲ファサード)。\n *\n * `window.ACMS.plugins`(管理画面コアが `/core` の `createPluginRegistry()` で生成したシングルトン)に\n * 処理を委譲する薄いラッパー。プラグイン・テーマの JS バンドルに同梱されても、実体は常にコアが\n * 保持する単一インスタンスに到達するため、別インスタンス問題(登録が黙って無効になる事故)が起きない。\n *\n * グローバル未初期化(管理画面外・初期化前に読み込まれた等)の場合は明確なエラーを投げる。\n * レジストリ本体の純粋実装・グローバル非依存のユニットテストは `@ablogcms/plugins/core` を使う。\n */\nimport type { AcmsPlugins, PluginDefinition, RegisteredPlugin } from './core/types';\n\nexport * from './core/types';\n// withFillProps はグローバル(window.ACMS.plugins)非依存の純粋なヘルパー(マーカーオブジェクトを\n// 作るだけ)なので、他の register/unregister 等と違いグローバル委譲のラップを必要としない。\n// プラグイン作者が setup(ctx) 内で ctx.ui.fill(...) と組み合わせて直接使うため、\n// `@ablogcms/plugins/core`(コア bootstrap・テスト用)だけでなくこちらからも公開する。\n// 外部プラグイン(import 不可)向けには `ACMS.plugins.withFillProps` としても公開する\n// (`create-plugin-registry.ts` の返り値に同じ関数を生やす。確定事項 Q3)。\nimport { withFillProps } from './core/with-fill-props';\nexport { withFillProps };\n\ninterface AcmsGlobal {\n plugins?: AcmsPlugins;\n}\n\nconst getGlobalPlugins = (): AcmsPlugins | undefined => {\n if (typeof window === 'undefined') {\n return undefined;\n }\n return (window as unknown as { ACMS?: AcmsGlobal }).ACMS?.plugins;\n};\n\nconst requireGlobalPlugins = (): AcmsPlugins => {\n const plugins = getGlobalPlugins();\n if (!plugins) {\n throw new Error(\n '[@ablogcms/plugins] window.ACMS.plugins が見つかりません。管理画面の初期化前、または管理画面外(公開側テーマ等)で読み込まれている可能性があります。'\n );\n }\n return plugins;\n};\n\nexport function register(name: string, definition: PluginDefinition): void {\n requireGlobalPlugins().register(name, definition);\n}\n\nexport function unregister(name: string): boolean {\n return requireGlobalPlugins().unregister(name);\n}\n\nexport function get(name: string): RegisteredPlugin | undefined {\n return requireGlobalPlugins().get(name);\n}\n\nexport function getAll(): RegisteredPlugin[] {\n return requireGlobalPlugins().getAll();\n}\n\nconst plugins: AcmsPlugins = { register, unregister, get, getAll, withFillProps };\n\nexport default plugins;\n"],"mappings":";;;;;AA0BA,IAAM,mBAAmB,MAA+B;AACtD,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO;AAAA,EACT;AACA,SAAQ,OAA4C,MAAM;AAC5D;AAEA,IAAM,uBAAuB,MAAmB;AAC9C,QAAMA,WAAU,iBAAiB;AACjC,MAAI,CAACA,UAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAOA;AACT;AAEO,SAAS,SAAS,MAAc,YAAoC;AACzE,uBAAqB,EAAE,SAAS,MAAM,UAAU;AAClD;AAEO,SAAS,WAAW,MAAuB;AAChD,SAAO,qBAAqB,EAAE,WAAW,IAAI;AAC/C;AAEO,SAAS,IAAI,MAA4C;AAC9D,SAAO,qBAAqB,EAAE,IAAI,IAAI;AACxC;AAEO,SAAS,SAA6B;AAC3C,SAAO,qBAAqB,EAAE,OAAO;AACvC;AAEA,IAAM,UAAuB,EAAE,UAAU,YAAY,KAAK,QAAQ,cAAc;AAEhF,IAAO,gBAAQ;","names":["plugins"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ablogcms/plugins",
|
|
3
|
+
"version": "3.2.28-beta.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "管理画面 JS 拡張基盤のプラグイン登録レジストリ",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://www.a-blogcms.jp",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+ssh://git@bitbucket.org:appleple/ablogcms.git",
|
|
11
|
+
"directory": "packages/frontend/plugins"
|
|
12
|
+
},
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@ablogcms/block-editor": "3.2.28-beta.0",
|
|
16
|
+
"@ablogcms/events": "3.2.28-beta.0",
|
|
17
|
+
"@ablogcms/types": "3.2.28-beta.0",
|
|
18
|
+
"@ablogcms/components": "3.2.28-beta.0",
|
|
19
|
+
"@ablogcms/unit-editor": "3.2.28-beta.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@testing-library/react": "^16.3.2",
|
|
23
|
+
"vitest": "^4.1.9"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": "^19",
|
|
27
|
+
"@types/react": "^19.2.17"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"@types/react": {
|
|
31
|
+
"optional": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"import": "./dist/index.mjs",
|
|
38
|
+
"default": "./dist/index.mjs"
|
|
39
|
+
},
|
|
40
|
+
"./acms": {
|
|
41
|
+
"types": "./dist/acms.d.ts",
|
|
42
|
+
"import": "./dist/acms.mjs",
|
|
43
|
+
"default": "./dist/acms.mjs"
|
|
44
|
+
},
|
|
45
|
+
"./core": {
|
|
46
|
+
"types": "./dist/core/index.d.ts",
|
|
47
|
+
"import": "./dist/core/index.mjs",
|
|
48
|
+
"default": "./dist/core/index.mjs"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"dist"
|
|
56
|
+
],
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "node ../../../tools/build-package.mjs"
|
|
59
|
+
}
|
|
60
|
+
}
|