@bbki.ng/site 5.7.0 → 5.8.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/CHANGELOG.md +6 -0
- package/index.html +0 -11
- package/package.json +1 -1
- package/src/app/app.tsx +1 -2
- package/src/core/components/SlotComp.tsx +2 -3
- package/src/core/hooks/useMiddlewareTransData.ts +12 -7
- package/src/core/hooks/useSlotComp.ts +8 -7
- package/src/core/hooks/use_plugins.ts +27 -14
- package/src/core/plugin-system/pluginManager.ts +129 -0
- package/src/core/{pluginManifestService.ts → plugin-system/pluginManifestService.ts} +2 -3
- package/src/core/plugin-system/pluginStore.ts +190 -0
- package/src/core/{registry.ts → plugin-system/registry.ts} +1 -3
- package/src/core/plugin-system/services/coreService.ts +56 -0
- package/src/core/plugin-system/services/systemUIService.ts +159 -0
- package/src/core/shared-service/contract/ICoreService.ts +23 -0
- package/src/core/shared-service/contract/IUIService.ts +49 -0
- package/src/core/shared-service/service-proxy.ts +26 -0
- package/src/core/shared-service/service-registry.ts +52 -0
- package/src/plugins/blog/components/BlogSlotCom.tsx +27 -0
- package/src/plugins/blog/components/article/index.tsx +2 -2
- package/src/plugins/blog/context/index.ts +0 -4
- package/src/plugins/blog/hooks/useMiddlewareTransData.ts +79 -0
- package/src/plugins/blog/hooks/use_blog_scroll_pos_restoration.ts +2 -2
- package/src/plugins/blog/hooks/use_blog_slot_com.ts +27 -0
- package/src/plugins/blog/hooks/use_posts.ts +3 -2
- package/src/plugins/blog/index.ts +32 -5
- package/src/plugins/blog/pages/extensions/txt/article.tsx +2 -2
- package/src/plugins/blog/services/BlogUIService.ts +120 -0
- package/src/plugins/blog/services/IBlogUIService.ts +31 -0
- package/src/plugins/extra-cd/index.ts +13 -2
- package/src/plugins/extra-entry/index.ts +8 -4
- package/src/plugins/fx/index.ts +18 -5
- package/src/plugins/manifest.ts +2 -6
- package/src/plugins/now/hooks/use_streaming.ts +3 -1
- package/src/plugins/now/index.ts +17 -6
- package/src/plugins/sticker/const.ts +2 -2
- package/src/plugins/sticker/index.ts +18 -3
- package/src/plugins/store/components/storePage.tsx +13 -2
- package/src/plugins/store/context/index.ts +1 -0
- package/src/plugins/store/index.ts +18 -7
- package/src/plugins/xwy/index.ts +24 -19
- package/src/plugins/xwy/types/index.ts +0 -18
- package/src/types/hostApi.ts +3 -34
- package/src/types/plugin.ts +1 -0
- package/src/utils/index.tsx +1 -48
- package/vite.config.js +1 -1
- package/src/core/pluginManager.ts +0 -191
- package/src/core/pluginStore.ts +0 -70
- /package/src/core/{bbplugin.ts → plugin-system/bbplugin.ts} +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
import { IMiddlewareEntry, ISlotEntry } from '#/core/plugin-system/registry';
|
|
4
|
+
import { createEventBus } from '#/core/utils/eventBus';
|
|
5
|
+
import { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
6
|
+
|
|
7
|
+
import { BlogDataHookPoint, BlogSlotName, IBlogUIService } from './IBlogUIService';
|
|
8
|
+
|
|
9
|
+
type RegistryEvents = {
|
|
10
|
+
'blog:slots:changed': void;
|
|
11
|
+
'blog:middleware:changed': BlogDataHookPoint;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export class BlogUIService implements IBlogUIService {
|
|
15
|
+
private static instance: BlogUIService;
|
|
16
|
+
|
|
17
|
+
private constructor() {}
|
|
18
|
+
|
|
19
|
+
private slots = new Map<BlogSlotName, Array<ISlotEntry>>();
|
|
20
|
+
private middlewares = new Map<BlogDataHookPoint, IMiddlewareEntry<unknown>[]>();
|
|
21
|
+
|
|
22
|
+
private bus = createEventBus<RegistryEvents>();
|
|
23
|
+
|
|
24
|
+
static getInstance(): BlogUIService {
|
|
25
|
+
if (!BlogUIService.instance) {
|
|
26
|
+
BlogUIService.instance = new BlogUIService();
|
|
27
|
+
}
|
|
28
|
+
return BlogUIService.instance;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
subscribeSlotChange(listener: () => void) {
|
|
32
|
+
return this.bus.on('blog:slots:changed', listener);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
subscribeMiddlewareChange(listener: (hookPoint: BlogDataHookPoint) => void) {
|
|
36
|
+
return this.bus.on('blog:middleware:changed', listener);
|
|
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
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
import { type IBaseUIService } from '#/core/shared-service/contract/IUIService';
|
|
4
|
+
|
|
5
|
+
declare module '#/core/shared-service/service-registry' {
|
|
6
|
+
interface ServiceIdentifierMap {
|
|
7
|
+
'blog:uiService': IBlogUIService;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type BlogSlotName = 'blog:articleActionRow' | 'blog:articleTitle';
|
|
12
|
+
|
|
13
|
+
export type BlogDataHookPoint =
|
|
14
|
+
| 'blog:filterPosts'
|
|
15
|
+
| 'blog:transformPostContent'
|
|
16
|
+
| 'blog:transformTitleList';
|
|
17
|
+
|
|
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
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BBPlugin } from '#/core/bbplugin';
|
|
1
|
+
import { BBPlugin } from '#/core/plugin-system/bbplugin';
|
|
2
2
|
import { IHostContext } from '#/types/hostApi';
|
|
3
3
|
import { PluginID } from '#/types/plugin';
|
|
4
4
|
import { TitleListItem } from '#/types/posts';
|
|
@@ -7,7 +7,18 @@ class ExtraCd extends BBPlugin {
|
|
|
7
7
|
id: PluginID = 'extra-cd';
|
|
8
8
|
|
|
9
9
|
override onInstall = async (ctx: IHostContext): Promise<void> => {
|
|
10
|
-
ctx.
|
|
10
|
+
const blogUIService = ctx.service.get('blog:uiService');
|
|
11
|
+
if (!blogUIService) {
|
|
12
|
+
console.error('Blog UI service not found. ExtraCd installation failed.');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
blogUIService.registerMiddleware(
|
|
17
|
+
'blog:transformTitleList',
|
|
18
|
+
this.transformTitleList,
|
|
19
|
+
this.id,
|
|
20
|
+
10
|
|
21
|
+
);
|
|
11
22
|
};
|
|
12
23
|
|
|
13
24
|
private transformTitleList = (titleList: Array<TitleListItem>) => {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { BBPlugin } from '#/core/bbplugin';
|
|
1
|
+
import { BBPlugin } from '#/core/plugin-system/bbplugin';
|
|
2
2
|
import { IHostContext } from '#/types/hostApi';
|
|
3
3
|
import { PluginID } from '#/types/plugin';
|
|
4
|
-
import { buildEntryCreator } from '#/utils';
|
|
5
4
|
|
|
6
5
|
import { ExtendedRoutesPage } from './components/page';
|
|
7
6
|
|
|
@@ -9,8 +8,13 @@ export class ExtraEntryPlugin extends BBPlugin {
|
|
|
9
8
|
id: PluginID = 'extra-entry';
|
|
10
9
|
|
|
11
10
|
override onInstall = async (ctx: IHostContext): Promise<void> => {
|
|
12
|
-
const
|
|
13
|
-
|
|
11
|
+
const systemUIService = ctx.service.get('core:uiService');
|
|
12
|
+
if (!systemUIService) {
|
|
13
|
+
console.warn(`[${this.id}] core:uiService not found, cannot register entry`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
systemUIService.registerPluginEntry(
|
|
14
18
|
{
|
|
15
19
|
pageComponent: ExtendedRoutesPage,
|
|
16
20
|
path: '/hello',
|
package/src/plugins/fx/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BBPlugin } from '#/core/bbplugin';
|
|
1
|
+
import { BBPlugin } from '#/core/plugin-system/bbplugin';
|
|
2
2
|
import { IHostContext } from '#/types/hostApi';
|
|
3
3
|
import { PluginID } from '#/types/plugin';
|
|
4
4
|
|
|
@@ -9,14 +9,27 @@ class FxPlugin extends BBPlugin {
|
|
|
9
9
|
id: PluginID = 'fx';
|
|
10
10
|
|
|
11
11
|
override onInstall = async (ctx: IHostContext) => {
|
|
12
|
-
ctx.
|
|
12
|
+
const coreService = ctx.service.get('core:baseService');
|
|
13
|
+
|
|
14
|
+
if (!coreService) {
|
|
15
|
+
console.error('Core service not found. FxPlugin installation failed.');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const systemUIService = ctx.service.get('core:uiService');
|
|
20
|
+
if (!systemUIService) {
|
|
21
|
+
console.error('UI service not found. FxPlugin installation failed.');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
systemUIService.registerSlot(
|
|
13
26
|
'pageFooter',
|
|
14
27
|
FxContext.withCtx(
|
|
15
28
|
{
|
|
16
|
-
versionHash: await
|
|
17
|
-
deviceId: (await
|
|
29
|
+
versionHash: await coreService.getVersionHash(),
|
|
30
|
+
deviceId: (await coreService.getDeviceId())?.id,
|
|
18
31
|
subscribeToLoading: listener => {
|
|
19
|
-
return
|
|
32
|
+
return coreService.subscribeLoadingChange(listener);
|
|
20
33
|
},
|
|
21
34
|
},
|
|
22
35
|
FxCom
|
package/src/plugins/manifest.ts
CHANGED
|
@@ -26,6 +26,7 @@ export const FALLBACK_MANIFEST: Array<IPluginManifestEntry> = [
|
|
|
26
26
|
version: '0.1.0',
|
|
27
27
|
description:
|
|
28
28
|
'提供额外的页面跳转链接。例如在标题列表末尾添加一个 "cd ~" 的选项,点击后跳转到主页',
|
|
29
|
+
dependencies: ['blog'],
|
|
29
30
|
},
|
|
30
31
|
{
|
|
31
32
|
name: '特效',
|
|
@@ -33,17 +34,12 @@ export const FALLBACK_MANIFEST: Array<IPluginManifestEntry> = [
|
|
|
33
34
|
version: '0.1.0',
|
|
34
35
|
description: '提供一些额外的视觉效果。例如版本、设备信息水印,加载状态螺旋线、背景纹理等',
|
|
35
36
|
},
|
|
36
|
-
// {
|
|
37
|
-
// name: 'extra-entry',
|
|
38
|
-
// id: 'extra-entry',
|
|
39
|
-
// version: '0.1.0',
|
|
40
|
-
// description: 'Provides extra entries for cover and extended routes.',
|
|
41
|
-
// },
|
|
42
37
|
{
|
|
43
38
|
name: '小乌鸦',
|
|
44
39
|
id: 'xwy',
|
|
45
40
|
version: '0.1.0',
|
|
46
41
|
description: '为小乌鸦合集实现定制功能或者样式的插件',
|
|
42
|
+
dependencies: ['blog'],
|
|
47
43
|
},
|
|
48
44
|
{
|
|
49
45
|
name: '博客',
|
package/src/plugins/now/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { BBPlugin } from '#/core/bbplugin';
|
|
1
|
+
import { BBPlugin } from '#/core/plugin-system/bbplugin';
|
|
2
2
|
import { IHostContext } from '#/types/hostApi';
|
|
3
3
|
import { PluginID } from '#/types/plugin';
|
|
4
|
-
import { buildEntryCreator } from '#/utils';
|
|
5
4
|
|
|
6
5
|
import { pageNow } from './components';
|
|
7
6
|
import { NowCtx } from './context';
|
|
@@ -10,19 +9,31 @@ export class NowPlugin extends BBPlugin {
|
|
|
10
9
|
id: PluginID = 'now';
|
|
11
10
|
|
|
12
11
|
override onInstall = async (ctx: IHostContext): Promise<void> => {
|
|
13
|
-
const
|
|
12
|
+
const systemUIService = ctx.service.get('core:uiService');
|
|
13
|
+
if (!systemUIService) {
|
|
14
|
+
console.warn(`[${this.id}] core:uiService not found, cannot register entry`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
14
17
|
const { withCtx } = NowCtx;
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
const coreService = ctx.service.get('core:baseService');
|
|
20
|
+
|
|
21
|
+
if (!coreService) {
|
|
22
|
+
console.error('Core service not found. NowPlugin installation failed.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
systemUIService.registerPluginEntry(
|
|
17
27
|
{
|
|
18
28
|
path: '/now',
|
|
19
29
|
label: 'cd ./now',
|
|
20
30
|
pageComponent: withCtx(
|
|
21
31
|
{
|
|
22
32
|
setLoading: loading => {
|
|
23
|
-
ctx.api.setLoading('now', loading);
|
|
33
|
+
// ctx.api.setLoading('now', loading);
|
|
34
|
+
coreService.setLoading('now', loading);
|
|
24
35
|
},
|
|
25
|
-
fetch:
|
|
36
|
+
fetch: coreService.fetch, // 直接使用 coreService 的 fetch 方法
|
|
26
37
|
},
|
|
27
38
|
pageNow
|
|
28
39
|
),
|
|
@@ -5,7 +5,7 @@ export const STICKERS: IStickerConfig[] = [
|
|
|
5
5
|
id: 'copilot',
|
|
6
6
|
imageUrl: '/stickers/copilot.svg',
|
|
7
7
|
rotation: 20,
|
|
8
|
-
maxWidth:
|
|
8
|
+
maxWidth: 60,
|
|
9
9
|
aspectRatio: '505/410',
|
|
10
10
|
xOffset: 0,
|
|
11
11
|
yOffset: 0,
|
|
@@ -41,7 +41,7 @@ export const STICKERS: IStickerConfig[] = [
|
|
|
41
41
|
id: 'vite',
|
|
42
42
|
imageUrl: '/stickers/vitejs.svg',
|
|
43
43
|
rotation: -5,
|
|
44
|
-
|
|
44
|
+
maxWidth: 30,
|
|
45
45
|
aspectRatio: '512/512',
|
|
46
46
|
xOffset: 100,
|
|
47
47
|
yOffset: -30,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BBPlugin } from '#/core/bbplugin';
|
|
1
|
+
import { BBPlugin } from '#/core/plugin-system/bbplugin';
|
|
2
2
|
import { IHostContext } from '#/types/hostApi';
|
|
3
3
|
import { PluginID } from '#/types/plugin';
|
|
4
4
|
|
|
@@ -9,8 +9,17 @@ class Sticker extends BBPlugin {
|
|
|
9
9
|
id: PluginID = 'sticker';
|
|
10
10
|
|
|
11
11
|
private createPluginCtx = async (ctx: IHostContext): Promise<IStickerCtx> => {
|
|
12
|
+
const coreService = ctx.service.get('core:baseService');
|
|
13
|
+
|
|
14
|
+
if (!coreService) {
|
|
15
|
+
console.error('Core service not found. StickerPlugin installation failed.');
|
|
16
|
+
return {
|
|
17
|
+
deviceId: '',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
return {
|
|
13
|
-
deviceId: (await
|
|
22
|
+
deviceId: (await coreService.getDeviceId()).id,
|
|
14
23
|
};
|
|
15
24
|
};
|
|
16
25
|
|
|
@@ -21,7 +30,13 @@ class Sticker extends BBPlugin {
|
|
|
21
30
|
|
|
22
31
|
const Sticker = withCtx(pluginCtx, StickerCom);
|
|
23
32
|
|
|
24
|
-
ctx.
|
|
33
|
+
const systemUIService = ctx.service.get('core:uiService');
|
|
34
|
+
if (!systemUIService) {
|
|
35
|
+
console.error('UI service not found. StickerPlugin installation failed.');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
systemUIService.registerSlot('leftCol', Sticker, this.id);
|
|
25
40
|
};
|
|
26
41
|
}
|
|
27
42
|
|
|
@@ -25,7 +25,7 @@ const ErrorState = ({ error, onRetry }: { error: Error; onRetry: () => void }) =
|
|
|
25
25
|
);
|
|
26
26
|
|
|
27
27
|
export const StorePage = (_: IComPropsRegisteredToSlot) => {
|
|
28
|
-
const { list, setLoading, isInstalled, install, uninstall } = StoreCtx.useCtx();
|
|
28
|
+
const { list, setLoading, isInstalled, install, uninstall, get } = StoreCtx.useCtx();
|
|
29
29
|
const [plugins, setPlugins] = useState<Array<IPluginStoreEntry>>([]);
|
|
30
30
|
const [error, setError] = useState<Error | null>(null);
|
|
31
31
|
|
|
@@ -108,6 +108,7 @@ export const StorePage = (_: IComPropsRegisteredToSlot) => {
|
|
|
108
108
|
<>
|
|
109
109
|
<Table.HCell>功能</Table.HCell>
|
|
110
110
|
<Table.HCell>描述</Table.HCell>
|
|
111
|
+
<Table.HCell>依赖</Table.HCell>
|
|
111
112
|
<Table.HCell style={{ textAlign: 'right' }}>安装/卸载</Table.HCell>
|
|
112
113
|
</>
|
|
113
114
|
);
|
|
@@ -126,6 +127,16 @@ export const StorePage = (_: IComPropsRegisteredToSlot) => {
|
|
|
126
127
|
<>
|
|
127
128
|
<Table.Cell>{plugin.name}</Table.Cell>
|
|
128
129
|
<Table.Cell>{plugin.description}</Table.Cell>
|
|
130
|
+
<Table.Cell>
|
|
131
|
+
{plugin.dependencies && plugin.dependencies.length > 0
|
|
132
|
+
? plugin.dependencies
|
|
133
|
+
.map(dep => {
|
|
134
|
+
const depPlugin = get(dep);
|
|
135
|
+
return depPlugin ? depPlugin.name : dep;
|
|
136
|
+
})
|
|
137
|
+
.join(', ')
|
|
138
|
+
: ''}
|
|
139
|
+
</Table.Cell>
|
|
129
140
|
<Table.Cell style={{ textAlign: 'right' }}>
|
|
130
141
|
{pluginInstalled ? (
|
|
131
142
|
<Button
|
|
@@ -144,7 +155,7 @@ export const StorePage = (_: IComPropsRegisteredToSlot) => {
|
|
|
144
155
|
</>
|
|
145
156
|
);
|
|
146
157
|
},
|
|
147
|
-
[plugins, isInstalled,
|
|
158
|
+
[plugins, isInstalled, get, handleUninstall, handleInstall]
|
|
148
159
|
);
|
|
149
160
|
|
|
150
161
|
if (error) {
|
|
@@ -6,6 +6,7 @@ export interface IStoreCtx {
|
|
|
6
6
|
uninstall: (pid: PluginID) => Promise<void>;
|
|
7
7
|
isInstalled: (pid: PluginID) => boolean;
|
|
8
8
|
list: () => Promise<Array<IPluginStoreEntry>> | Array<IPluginStoreEntry>;
|
|
9
|
+
get: (pid: PluginID) => IPluginStoreEntry | undefined;
|
|
9
10
|
setLoading: (loading: boolean) => void;
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { BBPlugin } from '#/core/bbplugin';
|
|
1
|
+
import { BBPlugin } from '#/core/plugin-system/bbplugin';
|
|
2
2
|
import { IHostContext } from '#/types/hostApi';
|
|
3
3
|
import { PluginID } from '#/types/plugin';
|
|
4
|
-
import { buildEntryCreator } from '#/utils';
|
|
5
4
|
|
|
6
5
|
import { StorePage } from './components/storePage';
|
|
7
6
|
import { StoreCtx } from './context';
|
|
@@ -9,8 +8,19 @@ import { StoreCtx } from './context';
|
|
|
9
8
|
export class StorePlugin extends BBPlugin {
|
|
10
9
|
id: PluginID = 'store';
|
|
11
10
|
override onInstall = async (ctx: IHostContext): Promise<void> => {
|
|
12
|
-
const
|
|
13
|
-
|
|
11
|
+
const coreService = ctx.service.get('core:baseService');
|
|
12
|
+
if (!coreService) {
|
|
13
|
+
console.error('Core service not found. StorePlugin installation failed.');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const systemUIService = ctx.service.get('core:uiService');
|
|
18
|
+
if (!systemUIService) {
|
|
19
|
+
console.error('UI service not found. StorePlugin installation failed.');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
systemUIService.registerPluginEntry(
|
|
14
24
|
{
|
|
15
25
|
path: '/store',
|
|
16
26
|
label: 'cd ./store',
|
|
@@ -19,14 +29,15 @@ export class StorePlugin extends BBPlugin {
|
|
|
19
29
|
install: ctx.store?.installPlugin || (async () => {}),
|
|
20
30
|
uninstall: ctx.store?.disablePlugin || (async () => {}),
|
|
21
31
|
isInstalled: ctx.store?.isPluginInstalled || ((_: PluginID) => false),
|
|
32
|
+
get: ctx.store?.getPlugin || ((_: PluginID) => undefined),
|
|
22
33
|
list: async () => {
|
|
23
|
-
|
|
34
|
+
coreService.setLoading('store', true);
|
|
24
35
|
const all = Array.from((await ctx.store?.getAllPlugins()) || []);
|
|
25
|
-
|
|
36
|
+
coreService.setLoading('store', false);
|
|
26
37
|
return all.filter(({ id }) => id !== this.id);
|
|
27
38
|
},
|
|
28
39
|
setLoading: (loading: boolean) => {
|
|
29
|
-
|
|
40
|
+
coreService.setLoading('store', loading);
|
|
30
41
|
},
|
|
31
42
|
},
|
|
32
43
|
StorePage
|
package/src/plugins/xwy/index.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { IHostContext } from '#/types/hostApi';
|
|
2
|
-
import { BBPlugin } from '#/core/bbplugin';
|
|
3
|
-
import { HookPoint } from '#/types/slots';
|
|
2
|
+
import { BBPlugin } from '#/core/plugin-system/bbplugin';
|
|
4
3
|
import { PluginID } from '#/types/plugin';
|
|
5
4
|
|
|
6
|
-
import { HookPointTypeMap, Transformer } from './types';
|
|
7
5
|
import { SpecialTitle } from './components/article';
|
|
8
6
|
import { XwyLogo } from './components/logo';
|
|
9
7
|
import { FontRules, LOADING_CLASS } from './const';
|
|
@@ -35,27 +33,34 @@ class XwyPlugin extends BBPlugin {
|
|
|
35
33
|
this.batchRegisterMiddlewares(ctx);
|
|
36
34
|
|
|
37
35
|
// 注册logo插槽组件
|
|
38
|
-
ctx.
|
|
36
|
+
const systemUIService = ctx.service.get('core:uiService');
|
|
37
|
+
systemUIService?.registerSlot('logo', XwyLogo, this.id);
|
|
39
38
|
|
|
40
39
|
// 注册「小乌鸦合集」标题组件
|
|
41
|
-
ctx.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
private trasformerMap: {
|
|
45
|
-
[K in Extract<HookPoint, keyof HookPointTypeMap>]?: Array<Transformer<K>>;
|
|
46
|
-
} = {
|
|
47
|
-
transformTitleList: [pinTitle, transformTitleList],
|
|
48
|
-
transformBreadcrumbPath: [transformBreadcrumbPaths],
|
|
49
|
-
transformPostContent: [transformPostContent],
|
|
40
|
+
const blogUIService = ctx.service.get('blog:uiService');
|
|
41
|
+
blogUIService?.registerSlot('blog:articleTitle', SpecialTitle, this.id);
|
|
50
42
|
};
|
|
51
43
|
|
|
52
44
|
private batchRegisterMiddlewares(ctx: IHostContext) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
45
|
+
const blogUIService = ctx.service.get('blog:uiService');
|
|
46
|
+
const systemUIService = ctx.service.get('core:uiService');
|
|
47
|
+
|
|
48
|
+
if (!blogUIService || !systemUIService) {
|
|
49
|
+
console.warn(`[${this.id}] Required UI services not found, skipping middleware registration`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 注册博客相关的中间件
|
|
54
|
+
for (const transformer of [pinTitle, transformTitleList]) {
|
|
55
|
+
blogUIService.registerMiddleware('blog:transformTitleList', transformer, this.id);
|
|
56
|
+
}
|
|
57
|
+
for (const transformer of [transformPostContent]) {
|
|
58
|
+
blogUIService.registerMiddleware('blog:transformPostContent', transformer, this.id);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 注册系统相关的中间件
|
|
62
|
+
for (const transformer of [transformBreadcrumbPaths]) {
|
|
63
|
+
systemUIService.registerMiddleware('transformBreadcrumbPath', transformer, this.id);
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
66
|
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import { PathObj } from '@bbki.ng/ui';
|
|
2
|
-
|
|
3
|
-
import { TitleListItem } from '#/types/posts';
|
|
4
|
-
|
|
5
1
|
export interface FontConfig {
|
|
6
2
|
name: string;
|
|
7
3
|
src: string;
|
|
@@ -19,17 +15,3 @@ export interface FontRule {
|
|
|
19
15
|
extraCls?: string;
|
|
20
16
|
variant?: 'default' | 'special';
|
|
21
17
|
}
|
|
22
|
-
|
|
23
|
-
// 1. 先定义每个 HookPoint 对应的类型映射
|
|
24
|
-
export interface HookPointTypeMap {
|
|
25
|
-
transformTitleList: { input: TitleListItem[]; output: TitleListItem[] };
|
|
26
|
-
transformBreadcrumbPath: { input: PathObj[]; output: PathObj[] };
|
|
27
|
-
transformPostContent: { input: string; output: string };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type HookPoint = keyof HookPointTypeMap;
|
|
31
|
-
|
|
32
|
-
// 2. 定义带类型的 Transformer
|
|
33
|
-
export type Transformer<K extends HookPoint> = (
|
|
34
|
-
baseData: HookPointTypeMap[K]['input']
|
|
35
|
-
) => HookPointTypeMap[K]['output'];
|
package/src/types/hostApi.ts
CHANGED
|
@@ -1,38 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import { type FingerprintData } from '#/app/utils/fingerprints';
|
|
5
|
-
import { PluginStore } from '#/core/pluginStore';
|
|
6
|
-
|
|
7
|
-
import { HookPoint, IComPropsRegisteredToSlot, SlotName } from './slots';
|
|
8
|
-
import { IPluginEntry, PluginEvents, PluginID } from './plugin';
|
|
9
|
-
|
|
10
|
-
export interface IHostApi {
|
|
11
|
-
getDeviceId: () => Promise<{ id: string; fp: FingerprintData }>;
|
|
12
|
-
getVersionHash: () => Promise<string> | string;
|
|
13
|
-
setLoading: (id: PluginID, loading: boolean) => void;
|
|
14
|
-
onLoadingChanged: (
|
|
15
|
-
listener: (payload: PluginEvents['site:loading:changed']) => void
|
|
16
|
-
) => () => void;
|
|
17
|
-
registerMiddleware: <T>(
|
|
18
|
-
point: HookPoint,
|
|
19
|
-
fn: (baseData: T) => T,
|
|
20
|
-
pluginId: string,
|
|
21
|
-
weight?: number
|
|
22
|
-
) => void;
|
|
23
|
-
registerEntry: (entry: IPluginEntry, id: string) => void;
|
|
24
|
-
registerSlot: (
|
|
25
|
-
slotName: SlotName,
|
|
26
|
-
component: React.ComponentType<IComPropsRegisteredToSlot>,
|
|
27
|
-
pluginId: string,
|
|
28
|
-
weight?: number
|
|
29
|
-
) => void;
|
|
30
|
-
fetch: Fetcher;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type PluginInitializer = (api: IHostApi) => void;
|
|
1
|
+
import { PluginStore } from '#/core/plugin-system/pluginStore';
|
|
2
|
+
import { type ServiceRegistry } from '#/core/shared-service/service-registry';
|
|
34
3
|
|
|
35
4
|
export interface IHostContext {
|
|
36
5
|
store?: PluginStore;
|
|
37
|
-
|
|
6
|
+
service: ServiceRegistry;
|
|
38
7
|
}
|