@bbki.ng/site 5.8.1 → 5.8.3
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/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/core/components/SlotComp.tsx +4 -17
- package/src/core/hooks/index.ts +2 -2
- package/src/core/hooks/useMiddlewareTransData.ts +13 -82
- package/src/core/hooks/useSlotComp.ts +3 -26
- package/src/core/hooks/use_plugins.ts +4 -3
- package/src/core/plugin-system/pluginManager.ts +5 -2
- package/src/core/plugin-system/pluginManifestService.ts +2 -18
- package/src/core/plugin-system/pluginStore.ts +1 -0
- package/src/core/plugin-system/registry.ts +1 -106
- package/src/core/plugin-system/services/systemUIService.ts +12 -101
- package/src/core/shared-service/contract/IUIService.ts +13 -12
- package/src/core/shared-service/factory/createUIService.ts +121 -0
- package/src/core/shared-service/factory/createUIServiceReactKit.tsx +149 -0
- package/src/plugins/blog/components/BlogSlotCom.tsx +4 -17
- package/src/plugins/blog/hooks/useMiddlewareTransData.ts +11 -76
- package/src/plugins/blog/hooks/use_blog_slot_com.ts +2 -24
- package/src/plugins/blog/index.ts +6 -1
- package/src/plugins/blog/services/BlogUIService.ts +10 -103
- package/src/plugins/blog/services/IBlogUIService.ts +1 -16
- package/src/plugins/notification/components/index.tsx +4 -0
- package/tsconfig.json +1 -1
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,27 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { SystemUIService } from '#/core/plugin-system/services/systemUIService';
|
|
4
|
+
import { createSlotComponent } from '#/core/shared-service/factory/createUIServiceReactKit';
|
|
5
|
+
|
|
4
6
|
import { SystemSlotName } from '../shared-service/contract/IUIService';
|
|
5
7
|
|
|
6
8
|
export interface ISlotProps {
|
|
7
9
|
name: SystemSlotName;
|
|
8
10
|
data?: unknown;
|
|
9
|
-
|
|
10
11
|
placeholder?: React.ReactNode;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export const Slot
|
|
14
|
-
const components = useSlotComp(name);
|
|
15
|
-
|
|
16
|
-
if (components.length === 0) {
|
|
17
|
-
return <>{placeholder}</>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<>
|
|
22
|
-
{components.map((Component, index) => (
|
|
23
|
-
<Component key={index} data={data} />
|
|
24
|
-
))}
|
|
25
|
-
</>
|
|
26
|
-
);
|
|
27
|
-
};
|
|
14
|
+
export const Slot = createSlotComponent<SystemSlotName>(SystemUIService.getInstance());
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { useSlotComp } from './useSlotComp';
|
|
2
2
|
export { useMiddlewareRunner } from './useMiddlewareTransData';
|
|
3
3
|
export type {
|
|
4
|
-
UseMiddlewareTransDataOptions,
|
|
5
|
-
UseMiddlewareTransDataResult,
|
|
4
|
+
UseMiddlewareRunnerOptions as UseMiddlewareTransDataOptions,
|
|
5
|
+
UseMiddlewareRunnerResult as UseMiddlewareTransDataResult,
|
|
6
6
|
} from './useMiddlewareTransData';
|
|
@@ -1,82 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
run: (inputData: T) => Promise<T>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function useMiddlewareRunner<T>({
|
|
18
|
-
hookPoint,
|
|
19
|
-
onMiddlewareChange,
|
|
20
|
-
}: UseMiddlewareTransDataOptions): UseMiddlewareTransDataResult<T> {
|
|
21
|
-
const [loading, setLoading] = useState(false);
|
|
22
|
-
const [error, setError] = useState<Error | null>(null);
|
|
23
|
-
|
|
24
|
-
const run = useCallback(
|
|
25
|
-
async (inputData: T) => {
|
|
26
|
-
setLoading(true);
|
|
27
|
-
setError(null);
|
|
28
|
-
try {
|
|
29
|
-
const result = await SystemUIService.getInstance().runMiddlewares(hookPoint, inputData);
|
|
30
|
-
return result;
|
|
31
|
-
} catch (err) {
|
|
32
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
33
|
-
setError(error);
|
|
34
|
-
throw error;
|
|
35
|
-
} finally {
|
|
36
|
-
setLoading(false);
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
[hookPoint]
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
// 仅通知,不执行
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
const unsubscribe = SystemUIService.getInstance().subscribeMiddlewareChange(hook => {
|
|
45
|
-
if (hook === hookPoint) {
|
|
46
|
-
onMiddlewareChange?.();
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
return () => {
|
|
51
|
-
unsubscribe();
|
|
52
|
-
};
|
|
53
|
-
}, [hookPoint, onMiddlewareChange]);
|
|
54
|
-
|
|
55
|
-
return { loading, error, run };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export const useMiddlewareTransformedData = <T>(
|
|
59
|
-
hookPoint: SystemDataHookPoint,
|
|
60
|
-
defaultValue: T
|
|
61
|
-
) => {
|
|
62
|
-
const [result, setResult] = useState<T>(defaultValue);
|
|
63
|
-
|
|
64
|
-
const runRef = useRef<(input: T) => Promise<T>>(() => Promise.resolve(defaultValue));
|
|
65
|
-
|
|
66
|
-
const onMiddlewareChange = useCallback(() => {
|
|
67
|
-
runRef.current(defaultValue).then(setResult);
|
|
68
|
-
}, [defaultValue]);
|
|
69
|
-
|
|
70
|
-
const { run } = useMiddlewareRunner<T>({
|
|
71
|
-
hookPoint,
|
|
72
|
-
onMiddlewareChange,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
runRef.current = run;
|
|
76
|
-
|
|
77
|
-
useEffect(() => {
|
|
78
|
-
run(defaultValue).then(setResult);
|
|
79
|
-
}, [defaultValue, run]);
|
|
80
|
-
|
|
81
|
-
return result;
|
|
82
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
createMiddlewareRunnerHook,
|
|
3
|
+
createMiddlewareTransformedDataHook,
|
|
4
|
+
} from '#/core/shared-service/factory/createUIServiceReactKit';
|
|
5
|
+
import { SystemUIService } from '#/core/plugin-system/services/systemUIService';
|
|
6
|
+
|
|
7
|
+
export const useMiddlewareRunner = createMiddlewareRunnerHook(SystemUIService.getInstance());
|
|
8
|
+
export const useMiddlewareTransformedData =
|
|
9
|
+
createMiddlewareTransformedDataHook(useMiddlewareRunner);
|
|
10
|
+
export type {
|
|
11
|
+
UseMiddlewareRunnerOptions,
|
|
12
|
+
UseMiddlewareRunnerResult,
|
|
13
|
+
} from '#/core/shared-service/factory/createUIServiceReactKit';
|
|
@@ -1,27 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createSlotHook } from '#/core/shared-service/factory/createUIServiceReactKit';
|
|
2
|
+
import { SystemUIService } from '#/core/plugin-system/services/systemUIService';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { SystemUIService } from '../plugin-system/services/systemUIService';
|
|
6
|
-
import { SystemSlotName } from '../shared-service/contract/IUIService';
|
|
7
|
-
|
|
8
|
-
export const useSlotComp = (slotName: SystemSlotName) => {
|
|
9
|
-
const [components, setComponents] = useState<React.ComponentType<IComPropsRegisteredToSlot>[]>(
|
|
10
|
-
() => SystemUIService.getInstance().getComponents(slotName)
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
setComponents(SystemUIService.getInstance().getComponents(slotName));
|
|
15
|
-
|
|
16
|
-
const unsubscribe = SystemUIService.getInstance().subscribeSlotChange(() => {
|
|
17
|
-
const comps = SystemUIService.getInstance().getComponents(slotName);
|
|
18
|
-
setComponents(comps);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
return () => {
|
|
22
|
-
unsubscribe();
|
|
23
|
-
};
|
|
24
|
-
}, [slotName]);
|
|
25
|
-
|
|
26
|
-
return components;
|
|
27
|
-
};
|
|
4
|
+
export const useSlotComp = createSlotHook(SystemUIService.getInstance());
|
|
@@ -16,14 +16,15 @@ const usePluginsLoading = () => {
|
|
|
16
16
|
}, [isLoading]);
|
|
17
17
|
|
|
18
18
|
useEffect(() => {
|
|
19
|
-
let
|
|
19
|
+
let unregisters = new Map<string, () => void>();
|
|
20
|
+
|
|
20
21
|
const unsubscribe = CoreService.getInstance().subscribePluginLoading(payload => {
|
|
21
|
-
|
|
22
|
+
unregisters.set(payload.id, setGlobalLoading(payload.id, payload.loading));
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
return () => {
|
|
25
26
|
unsubscribe();
|
|
26
|
-
unregister
|
|
27
|
+
unregisters.forEach(unregister => unregister());
|
|
27
28
|
};
|
|
28
29
|
}, [setGlobalLoading]);
|
|
29
30
|
};
|
|
@@ -78,8 +78,7 @@ class PluginManager {
|
|
|
78
78
|
|
|
79
79
|
const perm = p.getMeta().perm || 'guest';
|
|
80
80
|
if (perm === 'admin' && !AdminPluginIDSet.has(pluginId)) {
|
|
81
|
-
|
|
82
|
-
return;
|
|
81
|
+
throw new Error(`Plugin ${pluginId} requires admin permission, but it's not trusted.`);
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
const ctx = this.createHostContext(perm);
|
|
@@ -108,6 +107,10 @@ class PluginManager {
|
|
|
108
107
|
await plugin.onDisable();
|
|
109
108
|
}
|
|
110
109
|
|
|
110
|
+
if (plugin.onDestroy) {
|
|
111
|
+
plugin.onDestroy();
|
|
112
|
+
}
|
|
113
|
+
|
|
111
114
|
this.activePlugins.delete(pluginId);
|
|
112
115
|
|
|
113
116
|
SystemUIService.getInstance().unregisterAllByPluginId(plugin.id);
|
|
@@ -2,7 +2,7 @@ import { cfApiFetcher } from '#/app/utils';
|
|
|
2
2
|
import { FALLBACK_MANIFEST } from '#/core/plugin-system/manifest';
|
|
3
3
|
import { IPluginManifestEntry, PluginID } from '#/types/plugin';
|
|
4
4
|
|
|
5
|
-
const KnownPluginIDSet = new Set<string>(FALLBACK_MANIFEST.map(entry => entry.id));
|
|
5
|
+
// const KnownPluginIDSet = new Set<string>(FALLBACK_MANIFEST.map(entry => entry.id));
|
|
6
6
|
|
|
7
7
|
interface PluginsApiResponse {
|
|
8
8
|
status: string;
|
|
@@ -48,22 +48,6 @@ class PluginManifestService {
|
|
|
48
48
|
if (res.status === 'success' && Array.isArray(res.data)) {
|
|
49
49
|
const validated = res.data
|
|
50
50
|
.map((item): IPluginManifestEntry | null => {
|
|
51
|
-
if (!KnownPluginIDSet.has(item.id)) {
|
|
52
|
-
console.warn(`[PluginManifestService] Unknown plugin id from server: ${item.id}`);
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let dependencies: PluginID[] | undefined;
|
|
57
|
-
if (item.dependencies) {
|
|
58
|
-
try {
|
|
59
|
-
dependencies = item.dependencies.filter(p => KnownPluginIDSet.has(p));
|
|
60
|
-
} catch {
|
|
61
|
-
console.warn(
|
|
62
|
-
`[PluginManifestService] Failed to parse dependencies for plugin: ${item.id}`
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
51
|
return {
|
|
68
52
|
id: item.id as PluginID,
|
|
69
53
|
name: item.name,
|
|
@@ -71,7 +55,7 @@ class PluginManifestService {
|
|
|
71
55
|
description: item.description,
|
|
72
56
|
perm: (item.perm as 'guest' | 'admin' | undefined) || 'guest',
|
|
73
57
|
icon: item.icon,
|
|
74
|
-
dependencies,
|
|
58
|
+
dependencies: item.dependencies || [],
|
|
75
59
|
};
|
|
76
60
|
})
|
|
77
61
|
.filter((item): item is IPluginManifestEntry => item !== null);
|
|
@@ -12,6 +12,7 @@ export class PluginStore {
|
|
|
12
12
|
this.parse();
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
// TODO: check circular dependency when install plugin, and also check if all dependencies are installed before install a plugin
|
|
15
16
|
private checkDep = (id: PluginID): boolean => {
|
|
16
17
|
const manifest = PluginManifestService.getInstance().getPlugin(id);
|
|
17
18
|
if (!manifest) {
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
import { createEventBus } from '../utils/eventBus';
|
|
3
|
+
import type { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
6
4
|
|
|
7
5
|
export interface ISlotEntry {
|
|
8
6
|
id: string;
|
|
@@ -17,106 +15,3 @@ export interface IMiddlewareEntry<T> {
|
|
|
17
15
|
pluginId: string;
|
|
18
16
|
weight?: number;
|
|
19
17
|
}
|
|
20
|
-
|
|
21
|
-
type RegistryEvents = {
|
|
22
|
-
'components:changed': void;
|
|
23
|
-
'middleware:changed': HookPoint;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export class Registry {
|
|
27
|
-
private slots = new Map<SlotName, ISlotEntry[]>();
|
|
28
|
-
|
|
29
|
-
private bus = createEventBus<RegistryEvents>();
|
|
30
|
-
|
|
31
|
-
private middlewares = new Map<HookPoint, IMiddlewareEntry<unknown>[]>();
|
|
32
|
-
|
|
33
|
-
subscribeMiddleware(hookPoint: HookPoint, listener: () => void) {
|
|
34
|
-
return this.bus.on('middleware:changed', point => {
|
|
35
|
-
if (point === hookPoint) {
|
|
36
|
-
listener();
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
subscribe(listener: () => void) {
|
|
42
|
-
return this.bus.on('components:changed', listener);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
registerComponent(
|
|
46
|
-
slot: SlotName,
|
|
47
|
-
component: React.ComponentType<IComPropsRegisteredToSlot>,
|
|
48
|
-
pluginId: string,
|
|
49
|
-
weight = 0
|
|
50
|
-
) {
|
|
51
|
-
const existing = this.slots.get(slot) || [];
|
|
52
|
-
|
|
53
|
-
const newEntry: ISlotEntry = {
|
|
54
|
-
id: `${pluginId}-${component.name || 'comp'}`,
|
|
55
|
-
pluginId,
|
|
56
|
-
component,
|
|
57
|
-
weight,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// 插入并按权重排序(权重大的在前)
|
|
61
|
-
const newList = [...existing, newEntry].sort((a, b) => b.weight - a.weight);
|
|
62
|
-
this.slots.set(slot, newList);
|
|
63
|
-
|
|
64
|
-
this.bus.emit('components:changed', undefined);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
unregisterAllByPluginId(pluginId: string) {
|
|
68
|
-
// 1. 清理 UI 槽位
|
|
69
|
-
this.slots.forEach((entries, slotName) => {
|
|
70
|
-
const filtered = entries.filter(entry => entry.pluginId !== pluginId);
|
|
71
|
-
this.slots.set(slotName, filtered);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// 2. 清理中间件
|
|
75
|
-
this.middlewares.forEach((entries, point) => {
|
|
76
|
-
const filtered = entries.filter(entry => entry.pluginId !== pluginId);
|
|
77
|
-
this.middlewares.set(point, filtered);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
console.log(`[Registry] All resources for plugin "${pluginId}" have been cleared.`);
|
|
81
|
-
|
|
82
|
-
this.bus.emit('components:changed', undefined);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
registerMiddleware<T>(
|
|
86
|
-
hookPoint: HookPoint,
|
|
87
|
-
fn: (data: T) => Promise<T> | T,
|
|
88
|
-
pluginId: string,
|
|
89
|
-
weight = 0
|
|
90
|
-
) {
|
|
91
|
-
const existing = this.middlewares.get(hookPoint) || [];
|
|
92
|
-
|
|
93
|
-
const newEntry: IMiddlewareEntry<T> = {
|
|
94
|
-
id: `${pluginId}-${fn.name || 'middleware'}`,
|
|
95
|
-
pluginId,
|
|
96
|
-
fn,
|
|
97
|
-
weight,
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// 插入并按权重排序(权重大的在前)
|
|
101
|
-
const newList = [...existing, newEntry as IMiddlewareEntry<unknown>].sort(
|
|
102
|
-
(a, b) => (b.weight || 0) - (a.weight || 0)
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
this.middlewares.set(hookPoint, newList);
|
|
106
|
-
|
|
107
|
-
this.bus.emit('middleware:changed', hookPoint);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
getComponents(slotName: SlotName): React.ComponentType<IComPropsRegisteredToSlot>[] {
|
|
111
|
-
return (this.slots.get(slotName) || []).map(entry => entry.component);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async runMiddleware<T>(point: HookPoint, data: T): Promise<T> {
|
|
115
|
-
const fns = (this.middlewares.get(point) || []).map(entry => entry.fn);
|
|
116
|
-
let result = data;
|
|
117
|
-
for (const fn of fns) {
|
|
118
|
-
result = (await fn(result)) as T;
|
|
119
|
-
}
|
|
120
|
-
return result;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
@@ -1,66 +1,22 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
1
|
import { PathRouteProps } from 'react-router-dom';
|
|
3
2
|
import { LinkProps } from '@bbki.ng/ui';
|
|
4
3
|
|
|
4
|
+
import { buildEntrySlotCom } from '#/utils';
|
|
5
|
+
import { createUIService } from '#/core/shared-service/factory/createUIService';
|
|
5
6
|
import {
|
|
6
7
|
ISystemUIService,
|
|
7
8
|
SystemDataHookPoint,
|
|
8
9
|
SystemSlotName,
|
|
9
10
|
} from '#/core/shared-service/contract/IUIService';
|
|
10
11
|
import { IPluginEntry, PluginID } from '#/types/plugin';
|
|
11
|
-
import { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
12
|
-
import { createEventBus } from '#/core/utils/eventBus';
|
|
13
|
-
import { buildEntrySlotCom } from '#/utils';
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
type RegistryEvents = {
|
|
18
|
-
'system:slots:changed': void;
|
|
19
|
-
'system:middleware:changed': SystemDataHookPoint;
|
|
20
|
-
};
|
|
13
|
+
const baseService = createUIService<SystemSlotName, SystemDataHookPoint>('system');
|
|
21
14
|
|
|
22
15
|
export class SystemUIService implements ISystemUIService {
|
|
23
16
|
private static instance: SystemUIService;
|
|
24
17
|
|
|
25
|
-
private slots = new Map<SystemSlotName, Array<ISlotEntry>>();
|
|
26
|
-
private middlewares = new Map<SystemDataHookPoint, IMiddlewareEntry<unknown>[]>();
|
|
27
|
-
|
|
28
|
-
private bus = createEventBus<RegistryEvents>();
|
|
29
|
-
|
|
30
18
|
private constructor() {}
|
|
31
19
|
|
|
32
|
-
subscribeSlotChange(listener: () => void) {
|
|
33
|
-
return this.bus.on('system:slots:changed', listener);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
registerMiddleware: <T>(
|
|
37
|
-
hookPoint: SystemDataHookPoint,
|
|
38
|
-
fn: (data: T) => T | Promise<T>,
|
|
39
|
-
pluginId: PluginID,
|
|
40
|
-
weight?: number
|
|
41
|
-
) => void = <T>(
|
|
42
|
-
hookPoint: SystemDataHookPoint,
|
|
43
|
-
fn: (data: T) => T | Promise<T>,
|
|
44
|
-
pluginId: PluginID,
|
|
45
|
-
weight = 0
|
|
46
|
-
) => {
|
|
47
|
-
const existing = this.middlewares.get(hookPoint) || [];
|
|
48
|
-
|
|
49
|
-
const newEntry: IMiddlewareEntry<T> = {
|
|
50
|
-
id: `${pluginId}-${hookPoint}-middleware`,
|
|
51
|
-
pluginId,
|
|
52
|
-
fn,
|
|
53
|
-
weight,
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const updated = [...existing, newEntry as IMiddlewareEntry<unknown>].sort(
|
|
57
|
-
(a, b) => (b.weight || 0) - (a.weight || 0)
|
|
58
|
-
);
|
|
59
|
-
this.middlewares.set(hookPoint, updated);
|
|
60
|
-
|
|
61
|
-
this.bus.emit('system:middleware:changed', hookPoint);
|
|
62
|
-
};
|
|
63
|
-
|
|
64
20
|
static getInstance(): SystemUIService {
|
|
65
21
|
if (!SystemUIService.instance) {
|
|
66
22
|
SystemUIService.instance = new SystemUIService();
|
|
@@ -68,6 +24,15 @@ export class SystemUIService implements ISystemUIService {
|
|
|
68
24
|
return SystemUIService.instance;
|
|
69
25
|
}
|
|
70
26
|
|
|
27
|
+
registerSlot = baseService.registerSlot.bind(baseService);
|
|
28
|
+
registerMiddleware = baseService.registerMiddleware.bind(baseService);
|
|
29
|
+
unregisterAllByPluginId = baseService.unregisterAllByPluginId.bind(baseService);
|
|
30
|
+
getSlotEntries = baseService.getSlotEntries.bind(baseService);
|
|
31
|
+
getComponents = baseService.getComponents.bind(baseService);
|
|
32
|
+
subscribeSlotChange = baseService.subscribeSlotChange.bind(baseService);
|
|
33
|
+
subscribeMiddlewareChange = baseService.subscribeMiddlewareChange.bind(baseService);
|
|
34
|
+
runMiddlewares = baseService.runMiddlewares.bind(baseService);
|
|
35
|
+
|
|
71
36
|
registerPluginEntry: (entry: IPluginEntry, id: PluginID) => void = (entry, id) => {
|
|
72
37
|
this.registerMiddleware(
|
|
73
38
|
'extendedRoutes',
|
|
@@ -102,58 +67,4 @@ export class SystemUIService implements ISystemUIService {
|
|
|
102
67
|
10
|
|
103
68
|
);
|
|
104
69
|
};
|
|
105
|
-
|
|
106
|
-
registerSlot: (
|
|
107
|
-
slotName: SystemSlotName,
|
|
108
|
-
component: React.ComponentType<IComPropsRegisteredToSlot>,
|
|
109
|
-
pluginId: PluginID,
|
|
110
|
-
weight?: number
|
|
111
|
-
) => void = (slotName, component, pluginId, weight = 0) => {
|
|
112
|
-
const existing = this.slots.get(slotName) || [];
|
|
113
|
-
|
|
114
|
-
const newEntry: ISlotEntry = {
|
|
115
|
-
id: `${pluginId}-${component.name || 'comp'}`,
|
|
116
|
-
pluginId,
|
|
117
|
-
component,
|
|
118
|
-
weight,
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// 插入并按权重排序(权重大的在前)
|
|
122
|
-
const updated = [...existing, newEntry].sort((a, b) => b.weight - a.weight);
|
|
123
|
-
this.slots.set(slotName, updated);
|
|
124
|
-
|
|
125
|
-
this.bus.emit('system:slots:changed', undefined);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
unregisterAllByPluginId(pluginId: string) {
|
|
129
|
-
this.slots.forEach((entries, slotName) => {
|
|
130
|
-
const filtered = entries.filter(entry => entry.pluginId !== pluginId);
|
|
131
|
-
this.slots.set(slotName, filtered);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
this.middlewares.forEach((entries, point) => {
|
|
135
|
-
const filtered = entries.filter(entry => entry.pluginId !== pluginId);
|
|
136
|
-
this.middlewares.set(point, filtered);
|
|
137
|
-
this.bus.emit('system:middleware:changed', point);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
this.bus.emit('system:slots:changed', undefined);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
getComponents(slotName: SystemSlotName): React.ComponentType<IComPropsRegisteredToSlot>[] {
|
|
144
|
-
return (this.slots.get(slotName) || []).map(entry => entry.component);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
subscribeMiddlewareChange(listener: (point: SystemDataHookPoint) => void) {
|
|
148
|
-
return this.bus.on('system:middleware:changed', listener);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async runMiddlewares<T>(point: SystemDataHookPoint, data: T): Promise<T> {
|
|
152
|
-
const fns = (this.middlewares.get(point) || []).map(entry => entry.fn);
|
|
153
|
-
let result = data;
|
|
154
|
-
for (const fn of fns) {
|
|
155
|
-
result = (await fn(result)) as T;
|
|
156
|
-
}
|
|
157
|
-
return result;
|
|
158
|
-
}
|
|
159
70
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
|
|
3
|
+
import type { ISlotEntry } from '#/core/plugin-system/registry';
|
|
3
4
|
import { IPluginEntry, PluginID } from '#/types/plugin';
|
|
4
5
|
import { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
5
6
|
|
|
@@ -23,6 +24,18 @@ export interface IBaseUIService<T extends string, K extends string> {
|
|
|
23
24
|
pluginId: PluginID,
|
|
24
25
|
weight?: number
|
|
25
26
|
) => void;
|
|
27
|
+
|
|
28
|
+
unregisterAllByPluginId(pluginId: string): void;
|
|
29
|
+
|
|
30
|
+
getSlotEntries(slotName: T): ISlotEntry[];
|
|
31
|
+
|
|
32
|
+
getComponents(slotName: T): React.ComponentType<IComPropsRegisteredToSlot>[];
|
|
33
|
+
|
|
34
|
+
subscribeSlotChange(listener: () => void): () => void;
|
|
35
|
+
|
|
36
|
+
subscribeMiddlewareChange(listener: (point: K) => void): () => void;
|
|
37
|
+
|
|
38
|
+
runMiddlewares: <S>(point: K, data: S) => Promise<S>;
|
|
26
39
|
}
|
|
27
40
|
|
|
28
41
|
export type SystemSlotName = 'leftCol' | 'rightCol' | 'logo' | 'route' | 'pageFooter';
|
|
@@ -33,17 +46,5 @@ export type SystemDataHookPoint =
|
|
|
33
46
|
| 'transformCoverEntry';
|
|
34
47
|
|
|
35
48
|
export interface ISystemUIService extends IBaseUIService<SystemSlotName, SystemDataHookPoint> {
|
|
36
|
-
registerMiddleware: <S>(
|
|
37
|
-
hookPoint: SystemDataHookPoint,
|
|
38
|
-
fn: (data: S) => S | Promise<S>,
|
|
39
|
-
pluginId: PluginID,
|
|
40
|
-
weight?: number
|
|
41
|
-
) => void;
|
|
42
|
-
registerSlot: (
|
|
43
|
-
slotName: SystemSlotName,
|
|
44
|
-
component: React.ComponentType<IComPropsRegisteredToSlot>,
|
|
45
|
-
pluginId: PluginID,
|
|
46
|
-
weight?: number
|
|
47
|
-
) => void;
|
|
48
49
|
registerPluginEntry: (entry: IPluginEntry, id: PluginID) => void;
|
|
49
50
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { IMiddlewareEntry, ISlotEntry } from '#/core/plugin-system/registry';
|
|
4
|
+
import { createEventBus } from '#/core/utils/eventBus';
|
|
5
|
+
import type { PluginID } from '#/types/plugin';
|
|
6
|
+
import type { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
7
|
+
|
|
8
|
+
export interface IUIService<TSlot extends string, THook extends string> {
|
|
9
|
+
registerSlot(
|
|
10
|
+
slotName: TSlot,
|
|
11
|
+
component: React.ComponentType<IComPropsRegisteredToSlot>,
|
|
12
|
+
pluginId: PluginID,
|
|
13
|
+
weight?: number
|
|
14
|
+
): void;
|
|
15
|
+
|
|
16
|
+
registerMiddleware<T>(
|
|
17
|
+
hookPoint: THook,
|
|
18
|
+
fn: (data: T) => T | Promise<T>,
|
|
19
|
+
pluginId: PluginID,
|
|
20
|
+
weight?: number
|
|
21
|
+
): void;
|
|
22
|
+
|
|
23
|
+
unregisterAllByPluginId(pluginId: string): void;
|
|
24
|
+
|
|
25
|
+
getSlotEntries(slotName: TSlot): ISlotEntry[];
|
|
26
|
+
|
|
27
|
+
getComponents(slotName: TSlot): React.ComponentType<IComPropsRegisteredToSlot>[];
|
|
28
|
+
|
|
29
|
+
subscribeSlotChange(listener: () => void): () => void;
|
|
30
|
+
|
|
31
|
+
subscribeMiddlewareChange(listener: (point: THook) => void): () => void;
|
|
32
|
+
|
|
33
|
+
runMiddlewares<T>(point: THook, data: T): Promise<T>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createUIService<TSlot extends string, THook extends string>(
|
|
37
|
+
_: string
|
|
38
|
+
): IUIService<TSlot, THook> {
|
|
39
|
+
const slots = new Map<TSlot, Array<ISlotEntry>>();
|
|
40
|
+
const middlewares = new Map<THook, Array<IMiddlewareEntry<unknown>>>();
|
|
41
|
+
const bus = createEventBus<{
|
|
42
|
+
slotsChanged: void;
|
|
43
|
+
middlewareChanged: THook;
|
|
44
|
+
}>();
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
registerSlot(slotName, component, pluginId, weight = 0) {
|
|
48
|
+
const existing = slots.get(slotName) || [];
|
|
49
|
+
const newEntry: ISlotEntry = {
|
|
50
|
+
id: `${pluginId}-${slotName}-${component.name || 'comp'}`,
|
|
51
|
+
pluginId,
|
|
52
|
+
component,
|
|
53
|
+
weight,
|
|
54
|
+
};
|
|
55
|
+
const updated = [...existing, newEntry].sort((a, b) => (b.weight || 0) - (a.weight || 0));
|
|
56
|
+
slots.set(slotName, updated);
|
|
57
|
+
bus.emit('slotsChanged', undefined);
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
registerMiddleware(hookPoint, fn, pluginId, weight = 0) {
|
|
61
|
+
const existing = middlewares.get(hookPoint) || [];
|
|
62
|
+
const newEntry: IMiddlewareEntry<unknown> = {
|
|
63
|
+
id: `${pluginId}-${hookPoint}-middleware`,
|
|
64
|
+
pluginId,
|
|
65
|
+
fn: fn as (data: unknown) => unknown | Promise<unknown>,
|
|
66
|
+
weight,
|
|
67
|
+
};
|
|
68
|
+
const updated = [...existing, newEntry].sort((a, b) => (b.weight || 0) - (a.weight || 0));
|
|
69
|
+
middlewares.set(hookPoint, updated);
|
|
70
|
+
bus.emit('middlewareChanged', hookPoint);
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
unregisterAllByPluginId(pluginId) {
|
|
74
|
+
let slotsChanged = false;
|
|
75
|
+
slots.forEach((entries, slotName) => {
|
|
76
|
+
const filtered = entries.filter(entry => entry.pluginId !== pluginId);
|
|
77
|
+
if (filtered.length !== entries.length) {
|
|
78
|
+
slots.set(slotName, filtered);
|
|
79
|
+
slotsChanged = true;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
middlewares.forEach((entries, point) => {
|
|
84
|
+
const filtered = entries.filter(entry => entry.pluginId !== pluginId);
|
|
85
|
+
if (filtered.length !== entries.length) {
|
|
86
|
+
middlewares.set(point, filtered);
|
|
87
|
+
bus.emit('middlewareChanged', point);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (slotsChanged) {
|
|
92
|
+
bus.emit('slotsChanged', undefined);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
getSlotEntries(slotName) {
|
|
97
|
+
return slots.get(slotName) || [];
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
getComponents(slotName) {
|
|
101
|
+
return (slots.get(slotName) || []).map(entry => entry.component);
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
subscribeSlotChange(listener) {
|
|
105
|
+
return bus.on('slotsChanged', listener as (data: unknown) => void);
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
subscribeMiddlewareChange(listener) {
|
|
109
|
+
return bus.on('middlewareChanged', listener as (data: unknown) => void);
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
async runMiddlewares(point, data) {
|
|
113
|
+
const fns = (middlewares.get(point) || []).map(entry => entry.fn);
|
|
114
|
+
let result: unknown = data;
|
|
115
|
+
for (const fn of fns) {
|
|
116
|
+
result = await fn(result);
|
|
117
|
+
}
|
|
118
|
+
return result as typeof data;
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
4
|
+
|
|
5
|
+
import type { IUIService } from './createUIService';
|
|
6
|
+
|
|
7
|
+
export interface UseMiddlewareRunnerOptions<THook extends string> {
|
|
8
|
+
hookPoint: THook;
|
|
9
|
+
onMiddlewareChange?: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface UseMiddlewareRunnerResult<T> {
|
|
13
|
+
loading: boolean;
|
|
14
|
+
error: Error | null;
|
|
15
|
+
run: (inputData: T) => Promise<T>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createSlotHook<TSlot extends string>(service: IUIService<TSlot, string>) {
|
|
19
|
+
return function useSlotComp(slotName: TSlot) {
|
|
20
|
+
const [components, setComponents] = useState<React.ComponentType<IComPropsRegisteredToSlot>[]>(
|
|
21
|
+
() => service.getComponents(slotName)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
setComponents(service.getComponents(slotName));
|
|
26
|
+
|
|
27
|
+
const unsubscribe = service.subscribeSlotChange(() => {
|
|
28
|
+
setComponents(service.getComponents(slotName));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return () => {
|
|
32
|
+
unsubscribe();
|
|
33
|
+
};
|
|
34
|
+
}, [slotName]);
|
|
35
|
+
|
|
36
|
+
return components;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createSlotComponent<TSlot extends string>(service: IUIService<TSlot, string>) {
|
|
41
|
+
return function Slot({
|
|
42
|
+
name,
|
|
43
|
+
data,
|
|
44
|
+
placeholder,
|
|
45
|
+
}: {
|
|
46
|
+
name: TSlot;
|
|
47
|
+
data?: unknown;
|
|
48
|
+
placeholder?: React.ReactNode;
|
|
49
|
+
}) {
|
|
50
|
+
const [entries, setEntries] = useState(() => service.getSlotEntries(name));
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
setEntries(service.getSlotEntries(name));
|
|
54
|
+
|
|
55
|
+
const unsubscribe = service.subscribeSlotChange(() => {
|
|
56
|
+
setEntries(service.getSlotEntries(name));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return () => {
|
|
60
|
+
unsubscribe();
|
|
61
|
+
};
|
|
62
|
+
}, [name]);
|
|
63
|
+
|
|
64
|
+
if (entries.length === 0) {
|
|
65
|
+
return <>{placeholder}</>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<>
|
|
70
|
+
{entries.map(({ id, component: Component }) => (
|
|
71
|
+
<Component key={id} data={data} />
|
|
72
|
+
))}
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function createMiddlewareRunnerHook<THook extends string>(
|
|
79
|
+
service: IUIService<string, THook>
|
|
80
|
+
) {
|
|
81
|
+
return function useMiddlewareRunner<T>({
|
|
82
|
+
hookPoint,
|
|
83
|
+
onMiddlewareChange,
|
|
84
|
+
}: UseMiddlewareRunnerOptions<THook>): UseMiddlewareRunnerResult<T> {
|
|
85
|
+
const [loading, setLoading] = useState(false);
|
|
86
|
+
const [error, setError] = useState<Error | null>(null);
|
|
87
|
+
|
|
88
|
+
const run = useCallback(
|
|
89
|
+
async (inputData: T) => {
|
|
90
|
+
setLoading(true);
|
|
91
|
+
setError(null);
|
|
92
|
+
try {
|
|
93
|
+
const result = await service.runMiddlewares(hookPoint, inputData);
|
|
94
|
+
return result;
|
|
95
|
+
} catch (err) {
|
|
96
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
97
|
+
setError(error);
|
|
98
|
+
throw error;
|
|
99
|
+
} finally {
|
|
100
|
+
setLoading(false);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
[hookPoint]
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
const unsubscribe = service.subscribeMiddlewareChange(hook => {
|
|
108
|
+
if (hook === hookPoint) {
|
|
109
|
+
onMiddlewareChange?.();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return () => {
|
|
114
|
+
unsubscribe();
|
|
115
|
+
};
|
|
116
|
+
}, [hookPoint, onMiddlewareChange]);
|
|
117
|
+
|
|
118
|
+
return { loading, error, run };
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function createMiddlewareTransformedDataHook<THook extends string>(
|
|
123
|
+
useMiddlewareRunner: <T>(opts: UseMiddlewareRunnerOptions<THook>) => UseMiddlewareRunnerResult<T>
|
|
124
|
+
) {
|
|
125
|
+
return function useMiddlewareTransformedData<T>(hookPoint: THook, defaultValue: T) {
|
|
126
|
+
const [result, setResult] = useState<T>(defaultValue);
|
|
127
|
+
|
|
128
|
+
const runRef = useRef<(input: T) => Promise<T>>(() => Promise.resolve(defaultValue));
|
|
129
|
+
|
|
130
|
+
const onMiddlewareChange = useCallback(() => {
|
|
131
|
+
runRef.current(defaultValue).then(setResult);
|
|
132
|
+
}, [defaultValue]);
|
|
133
|
+
|
|
134
|
+
const { run } = useMiddlewareRunner<T>({
|
|
135
|
+
hookPoint,
|
|
136
|
+
onMiddlewareChange,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
runRef.current = run;
|
|
141
|
+
}, [run]);
|
|
142
|
+
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
run(defaultValue).then(setResult);
|
|
145
|
+
}, [defaultValue, run]);
|
|
146
|
+
|
|
147
|
+
return result;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -1,27 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { createSlotComponent } from '#/core/shared-service/factory/createUIServiceReactKit';
|
|
4
|
+
|
|
5
|
+
import { BlogUIService } from '../services/BlogUIService';
|
|
4
6
|
import { BlogSlotName } from '../services/IBlogUIService';
|
|
5
7
|
|
|
6
8
|
export interface ISlotProps {
|
|
7
9
|
name: BlogSlotName;
|
|
8
10
|
data?: unknown;
|
|
9
|
-
|
|
10
11
|
placeholder?: React.ReactNode;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export const Slot
|
|
14
|
-
const components = useBlogSlotComp(name);
|
|
15
|
-
|
|
16
|
-
if (components.length === 0) {
|
|
17
|
-
return <>{placeholder}</>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<>
|
|
22
|
-
{components.map((Component, index) => (
|
|
23
|
-
<Component key={index} data={data} />
|
|
24
|
-
))}
|
|
25
|
-
</>
|
|
26
|
-
);
|
|
27
|
-
};
|
|
14
|
+
export const Slot = createSlotComponent<BlogSlotName>(BlogUIService.getInstance());
|
|
@@ -1,79 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createMiddlewareRunnerHook,
|
|
3
|
+
createMiddlewareTransformedDataHook,
|
|
4
|
+
} from '#/core/shared-service/factory/createUIServiceReactKit';
|
|
2
5
|
|
|
3
6
|
import { BlogUIService } from '../services/BlogUIService';
|
|
4
|
-
import { BlogDataHookPoint } from '../services/IBlogUIService';
|
|
5
7
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
error: Error | null;
|
|
14
|
-
run: (inputData: T) => Promise<T>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function useMiddlewareRunner<T>({
|
|
18
|
-
hookPoint,
|
|
19
|
-
onMiddlewareChange,
|
|
20
|
-
}: UseMiddlewareTransDataOptions): UseMiddlewareTransDataResult<T> {
|
|
21
|
-
const [loading, setLoading] = useState(false);
|
|
22
|
-
const [error, setError] = useState<Error | null>(null);
|
|
23
|
-
|
|
24
|
-
const run = useCallback(
|
|
25
|
-
async (inputData: T) => {
|
|
26
|
-
setLoading(true);
|
|
27
|
-
setError(null);
|
|
28
|
-
try {
|
|
29
|
-
const result = await BlogUIService.getInstance().runMiddlewares(hookPoint, inputData);
|
|
30
|
-
return result;
|
|
31
|
-
} catch (err) {
|
|
32
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
33
|
-
setError(error);
|
|
34
|
-
throw error;
|
|
35
|
-
} finally {
|
|
36
|
-
setLoading(false);
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
[hookPoint]
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
// 仅通知,不执行
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
const unsubscribe = BlogUIService.getInstance().subscribeMiddlewareChange(hook => {
|
|
45
|
-
if (hook === hookPoint) {
|
|
46
|
-
onMiddlewareChange?.();
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
return () => {
|
|
51
|
-
unsubscribe();
|
|
52
|
-
};
|
|
53
|
-
}, [hookPoint, onMiddlewareChange]);
|
|
54
|
-
|
|
55
|
-
return { loading, error, run };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export const useMiddlewareTransformedData = <T>(hookPoint: BlogDataHookPoint, defaultValue: T) => {
|
|
59
|
-
const [result, setResult] = useState<T>(defaultValue);
|
|
60
|
-
|
|
61
|
-
const runRef = useRef<(input: T) => Promise<T>>(() => Promise.resolve(defaultValue));
|
|
62
|
-
|
|
63
|
-
const onMiddlewareChange = useCallback(() => {
|
|
64
|
-
runRef.current(defaultValue).then(setResult);
|
|
65
|
-
}, [defaultValue]);
|
|
66
|
-
|
|
67
|
-
const { run } = useMiddlewareRunner<T>({
|
|
68
|
-
hookPoint,
|
|
69
|
-
onMiddlewareChange,
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
runRef.current = run;
|
|
73
|
-
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
run(defaultValue).then(setResult);
|
|
76
|
-
}, [defaultValue, run]);
|
|
77
|
-
|
|
78
|
-
return result;
|
|
79
|
-
};
|
|
8
|
+
export const useMiddlewareRunner = createMiddlewareRunnerHook(BlogUIService.getInstance());
|
|
9
|
+
export const useMiddlewareTransformedData =
|
|
10
|
+
createMiddlewareTransformedDataHook(useMiddlewareRunner);
|
|
11
|
+
export type {
|
|
12
|
+
UseMiddlewareRunnerOptions,
|
|
13
|
+
UseMiddlewareRunnerResult as UseMiddlewareTransformedDataResult,
|
|
14
|
+
} from '#/core/shared-service/factory/createUIServiceReactKit';
|
|
@@ -1,27 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createSlotHook } from '#/core/shared-service/factory/createUIServiceReactKit';
|
|
2
2
|
|
|
3
|
-
import { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
4
|
-
|
|
5
|
-
import { BlogSlotName } from '../services/IBlogUIService';
|
|
6
3
|
import { BlogUIService } from '../services/BlogUIService';
|
|
7
4
|
|
|
8
|
-
export const useBlogSlotComp = (
|
|
9
|
-
const [components, setComponents] = useState<React.ComponentType<IComPropsRegisteredToSlot>[]>(
|
|
10
|
-
() => BlogUIService.getInstance().getComponents(slotName)
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
// setComponents(BlogUIService.getInstance().getComponents(slotName));
|
|
15
|
-
|
|
16
|
-
const unsubscribe = BlogUIService.getInstance().subscribeSlotChange(() => {
|
|
17
|
-
const comps = BlogUIService.getInstance().getComponents(slotName);
|
|
18
|
-
setComponents(comps);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
return () => {
|
|
22
|
-
unsubscribe();
|
|
23
|
-
};
|
|
24
|
-
}, [slotName]);
|
|
25
|
-
|
|
26
|
-
return components;
|
|
27
|
-
};
|
|
5
|
+
export const useBlogSlotComp = createSlotHook(BlogUIService.getInstance());
|
|
@@ -15,6 +15,8 @@ export class BlogPlugin extends BBPlugin {
|
|
|
15
15
|
|
|
16
16
|
private _serviceRegistry?: ServiceRegistry;
|
|
17
17
|
|
|
18
|
+
private _unsubscribePluginUninstall?: () => void;
|
|
19
|
+
|
|
18
20
|
override onInstall = async (ctx: IHostContext) => {
|
|
19
21
|
ctx.service.register('blog:uiService', BlogUIService.getInstance());
|
|
20
22
|
|
|
@@ -28,7 +30,9 @@ export class BlogPlugin extends BBPlugin {
|
|
|
28
30
|
return;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
coreService.subscribePluginUninstall(
|
|
33
|
+
this._unsubscribePluginUninstall = coreService.subscribePluginUninstall(
|
|
34
|
+
this.handlePluginUninstall
|
|
35
|
+
);
|
|
32
36
|
|
|
33
37
|
systemUIService?.registerPluginEntry(
|
|
34
38
|
{
|
|
@@ -50,6 +54,7 @@ export class BlogPlugin extends BBPlugin {
|
|
|
50
54
|
override onDestroy?: (() => void) | undefined = () => {
|
|
51
55
|
BlogUIService.getInstance().unregisterAllByPluginId(this.id);
|
|
52
56
|
this._serviceRegistry?.unregister('blog:uiService');
|
|
57
|
+
this._unsubscribePluginUninstall?.();
|
|
53
58
|
};
|
|
54
59
|
|
|
55
60
|
private handlePluginUninstall = (payload: PluginID) => {
|
|
@@ -1,26 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { IMiddlewareEntry, ISlotEntry } from '#/core/plugin-system/registry';
|
|
4
|
-
import { createEventBus } from '#/core/utils/eventBus';
|
|
5
|
-
import { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
1
|
+
import { createUIService } from '#/core/shared-service/factory/createUIService';
|
|
6
2
|
|
|
7
3
|
import { BlogDataHookPoint, BlogSlotName, IBlogUIService } from './IBlogUIService';
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
'blog:slots:changed': void;
|
|
11
|
-
'blog:middleware:changed': BlogDataHookPoint;
|
|
12
|
-
};
|
|
5
|
+
const baseService = createUIService<BlogSlotName, BlogDataHookPoint>('blog');
|
|
13
6
|
|
|
14
7
|
export class BlogUIService implements IBlogUIService {
|
|
15
8
|
private static instance: BlogUIService;
|
|
16
9
|
|
|
17
10
|
private constructor() {}
|
|
18
11
|
|
|
19
|
-
private slots = new Map<BlogSlotName, Array<ISlotEntry>>();
|
|
20
|
-
private middlewares = new Map<BlogDataHookPoint, IMiddlewareEntry<unknown>[]>();
|
|
21
|
-
|
|
22
|
-
private bus = createEventBus<RegistryEvents>();
|
|
23
|
-
|
|
24
12
|
static getInstance(): BlogUIService {
|
|
25
13
|
if (!BlogUIService.instance) {
|
|
26
14
|
BlogUIService.instance = new BlogUIService();
|
|
@@ -28,93 +16,12 @@ export class BlogUIService implements IBlogUIService {
|
|
|
28
16
|
return BlogUIService.instance;
|
|
29
17
|
}
|
|
30
18
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
registerMiddleware: <S>(
|
|
40
|
-
hookPoint: BlogDataHookPoint,
|
|
41
|
-
fn: (data: S) => S | Promise<S>,
|
|
42
|
-
pluginId: string,
|
|
43
|
-
weight?: number
|
|
44
|
-
) => void = <S>(
|
|
45
|
-
hookPoint: BlogDataHookPoint,
|
|
46
|
-
fn: (data: S) => S | Promise<S>,
|
|
47
|
-
pluginId: string,
|
|
48
|
-
weight = 0
|
|
49
|
-
) => {
|
|
50
|
-
const existing = this.middlewares.get(hookPoint) || [];
|
|
51
|
-
|
|
52
|
-
const newEntry: IMiddlewareEntry<S> = {
|
|
53
|
-
id: `${pluginId}-${hookPoint}-middleware`,
|
|
54
|
-
pluginId,
|
|
55
|
-
fn,
|
|
56
|
-
weight,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const updated = [...existing, newEntry as IMiddlewareEntry<unknown>].sort(
|
|
60
|
-
(a, b) => (b.weight || 0) - (a.weight || 0)
|
|
61
|
-
);
|
|
62
|
-
this.middlewares.set(hookPoint, updated);
|
|
63
|
-
|
|
64
|
-
this.bus.emit('blog:middleware:changed', hookPoint);
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
registerSlot: (
|
|
68
|
-
slotName: BlogSlotName,
|
|
69
|
-
component: React.ComponentType<{ data: unknown }>,
|
|
70
|
-
pluginId: string,
|
|
71
|
-
weight?: number
|
|
72
|
-
) => void = (slotName, component, pluginId, weight = 0) => {
|
|
73
|
-
const existing = this.slots.get(slotName) || [];
|
|
74
|
-
const newEntry: ISlotEntry = {
|
|
75
|
-
id: `${pluginId}-${slotName}-slot`,
|
|
76
|
-
pluginId,
|
|
77
|
-
component,
|
|
78
|
-
weight,
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const updated = [...existing, newEntry].sort((a, b) => (b.weight || 0) - (a.weight || 0));
|
|
82
|
-
this.slots.set(slotName, updated);
|
|
83
|
-
|
|
84
|
-
this.bus.emit('blog:slots:changed', undefined);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
unregisterAllByPluginId: (pluginId: string) => void = pluginId => {
|
|
88
|
-
// 移除中间件
|
|
89
|
-
this.middlewares.forEach((entries, hookPoint) => {
|
|
90
|
-
const filtered = entries.filter(entry => entry.pluginId !== pluginId);
|
|
91
|
-
if (filtered.length !== entries.length) {
|
|
92
|
-
this.middlewares.set(hookPoint, filtered);
|
|
93
|
-
this.bus.emit('blog:middleware:changed', hookPoint);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// 移除插槽组件
|
|
98
|
-
this.slots.forEach((entries, slotName) => {
|
|
99
|
-
const filtered = entries.filter(entry => entry.pluginId !== pluginId);
|
|
100
|
-
if (filtered.length !== entries.length) {
|
|
101
|
-
this.slots.set(slotName, filtered);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
this.bus.emit('blog:slots:changed', undefined);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
getComponents(slotName: BlogSlotName): React.ComponentType<IComPropsRegisteredToSlot>[] {
|
|
109
|
-
return (this.slots.get(slotName) || []).map(entry => entry.component);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async runMiddlewares<T>(point: BlogDataHookPoint, data: T): Promise<T> {
|
|
113
|
-
const fns = (this.middlewares.get(point) || []).map(entry => entry.fn);
|
|
114
|
-
let result = data;
|
|
115
|
-
for (const fn of fns) {
|
|
116
|
-
result = (await fn(result)) as T;
|
|
117
|
-
}
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
19
|
+
registerSlot = baseService.registerSlot.bind(baseService);
|
|
20
|
+
registerMiddleware = baseService.registerMiddleware.bind(baseService);
|
|
21
|
+
unregisterAllByPluginId = baseService.unregisterAllByPluginId.bind(baseService);
|
|
22
|
+
getSlotEntries = baseService.getSlotEntries.bind(baseService);
|
|
23
|
+
getComponents = baseService.getComponents.bind(baseService);
|
|
24
|
+
subscribeSlotChange = baseService.subscribeSlotChange.bind(baseService);
|
|
25
|
+
subscribeMiddlewareChange = baseService.subscribeMiddlewareChange.bind(baseService);
|
|
26
|
+
runMiddlewares = baseService.runMiddlewares.bind(baseService);
|
|
120
27
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type React from 'react';
|
|
2
|
-
|
|
3
1
|
import { type IBaseUIService } from '#/core/shared-service/contract/IUIService';
|
|
4
2
|
|
|
5
3
|
declare module '#/core/shared-service/service-registry' {
|
|
@@ -15,17 +13,4 @@ export type BlogDataHookPoint =
|
|
|
15
13
|
| 'blog:transformPostContent'
|
|
16
14
|
| 'blog:transformTitleList';
|
|
17
15
|
|
|
18
|
-
export interface IBlogUIService extends IBaseUIService<BlogSlotName, BlogDataHookPoint> {
|
|
19
|
-
registerMiddleware: <S>(
|
|
20
|
-
hookPoint: BlogDataHookPoint,
|
|
21
|
-
fn: (data: S) => S | Promise<S>,
|
|
22
|
-
pluginId: string,
|
|
23
|
-
weight?: number
|
|
24
|
-
) => void;
|
|
25
|
-
registerSlot: (
|
|
26
|
-
slotName: BlogSlotName,
|
|
27
|
-
component: React.ComponentType<{ data: unknown }>,
|
|
28
|
-
pluginId: string,
|
|
29
|
-
weight?: number
|
|
30
|
-
) => void;
|
|
31
|
-
}
|
|
16
|
+
export interface IBlogUIService extends IBaseUIService<BlogSlotName, BlogDataHookPoint> {}
|