@bbki.ng/site 5.7.0 → 5.8.1

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/index.html +0 -11
  3. package/package.json +3 -2
  4. package/src/app/app.tsx +1 -2
  5. package/src/core/components/SlotComp.tsx +2 -3
  6. package/src/core/hooks/useMiddlewareTransData.ts +12 -7
  7. package/src/core/hooks/useSlotComp.ts +8 -7
  8. package/src/core/hooks/use_plugins.ts +27 -14
  9. package/src/{plugins → core/plugin-system}/manifest.ts +8 -6
  10. package/src/core/plugin-system/pluginManager.ts +118 -0
  11. package/src/core/{pluginManifestService.ts → plugin-system/pluginManifestService.ts} +4 -15
  12. package/src/core/plugin-system/pluginStore.ts +196 -0
  13. package/src/core/{registry.ts → plugin-system/registry.ts} +1 -3
  14. package/src/core/plugin-system/services/coreService.ts +56 -0
  15. package/src/core/plugin-system/services/systemUIService.ts +159 -0
  16. package/src/core/shared-service/contract/ICoreService.ts +23 -0
  17. package/src/core/shared-service/contract/IUIService.ts +49 -0
  18. package/src/core/shared-service/service-proxy.ts +26 -0
  19. package/src/core/shared-service/service-registry.ts +54 -0
  20. package/src/plugins/blog/components/BlogSlotCom.tsx +27 -0
  21. package/src/plugins/blog/components/article/index.tsx +2 -2
  22. package/src/plugins/blog/context/index.ts +0 -4
  23. package/src/plugins/blog/hooks/useMiddlewareTransData.ts +79 -0
  24. package/src/plugins/blog/hooks/use_blog_scroll_pos_restoration.ts +2 -2
  25. package/src/plugins/blog/hooks/use_blog_slot_com.ts +27 -0
  26. package/src/plugins/blog/hooks/use_posts.ts +3 -2
  27. package/src/plugins/blog/index.ts +32 -5
  28. package/src/plugins/blog/pages/extensions/txt/article.tsx +2 -2
  29. package/src/plugins/blog/services/BlogUIService.ts +120 -0
  30. package/src/plugins/blog/services/IBlogUIService.ts +31 -0
  31. package/src/plugins/extra-cd/index.ts +13 -2
  32. package/src/plugins/extra-entry/index.ts +8 -4
  33. package/src/plugins/fx/index.ts +18 -5
  34. package/src/plugins/notification/components/index.tsx +18 -0
  35. package/src/plugins/notification/index.ts +26 -0
  36. package/src/plugins/notification/services/INotificationService.ts +19 -0
  37. package/src/plugins/notification/services/NotificationService.ts +28 -0
  38. package/src/plugins/now/hooks/use_streaming.ts +3 -1
  39. package/src/plugins/now/index.ts +17 -6
  40. package/src/plugins/sticker/const.ts +2 -2
  41. package/src/plugins/sticker/index.ts +18 -3
  42. package/src/plugins/store/components/storePage.tsx +26 -7
  43. package/src/plugins/store/context/index.ts +4 -1
  44. package/src/plugins/store/index.ts +20 -8
  45. package/src/plugins/store/utils/index.ts +26 -0
  46. package/src/plugins/xwy/index.ts +24 -19
  47. package/src/plugins/xwy/types/index.ts +0 -18
  48. package/src/types/hostApi.ts +3 -34
  49. package/src/types/plugin.ts +2 -0
  50. package/src/utils/index.tsx +1 -48
  51. package/vite.config.js +1 -1
  52. package/src/core/pluginManager.ts +0 -191
  53. package/src/core/pluginStore.ts +0 -70
  54. /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.api.registerMiddleware('transformTitleList', this.transformTitleList, this.id, 10);
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 entryCreator = buildEntryCreator(ctx);
13
- entryCreator(
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',
@@ -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.api.registerSlot(
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 ctx.api.getVersionHash(),
17
- deviceId: (await ctx.api.getDeviceId()).id,
29
+ versionHash: await coreService.getVersionHash(),
30
+ deviceId: (await coreService.getDeviceId())?.id,
18
31
  subscribeToLoading: listener => {
19
- return ctx.api.onLoadingChanged(listener);
32
+ return coreService.subscribeLoadingChange(listener);
20
33
  },
21
34
  },
22
35
  FxCom
@@ -0,0 +1,18 @@
1
+ import React, { FC } from 'react';
2
+ import { Toaster } from 'sonner';
3
+
4
+ import { IComPropsRegisteredToSlot } from '#/types/slots';
5
+
6
+ export const NotifyComp: FC<IComPropsRegisteredToSlot> = _ => {
7
+ return (
8
+ <Toaster
9
+ position="bottom-center"
10
+ toastOptions={{
11
+ style: {
12
+ borderRadius: 0,
13
+ border: 'none',
14
+ },
15
+ }}
16
+ />
17
+ );
18
+ };
@@ -0,0 +1,26 @@
1
+ import { BBPlugin } from '#/core/plugin-system/bbplugin';
2
+ import { IHostContext } from '#/types/hostApi';
3
+ import { PluginID } from '#/types/plugin';
4
+
5
+ import { NotifyComp } from './components';
6
+ import { NotificationService } from './services/NotificationService';
7
+
8
+ export class NotificationPlugin extends BBPlugin {
9
+ id: PluginID = 'notification' as const;
10
+
11
+ private _serviceRegistry?: IHostContext['service'];
12
+
13
+ override onInstall = async (ctx: IHostContext) => {
14
+ this._serviceRegistry = ctx.service;
15
+ this._serviceRegistry.register('notification', NotificationService.getInstance());
16
+
17
+ const coreUIService = ctx.service.get('core:uiService');
18
+ coreUIService.registerSlot('pageFooter', NotifyComp, this.id);
19
+ };
20
+
21
+ override onDestroy = () => {
22
+ this._serviceRegistry?.unregister('notification');
23
+ };
24
+ }
25
+
26
+ export default new NotificationPlugin();
@@ -0,0 +1,19 @@
1
+ declare module '#/core/shared-service/service-registry' {
2
+ interface ServiceIdentifierMap {
3
+ notification: INotificationService;
4
+ }
5
+ }
6
+
7
+ export type NotificationType = 'toast' | 'banner' | 'modal';
8
+
9
+ export interface INotificationOptions {
10
+ type?: NotificationType;
11
+ level?: 'info' | 'success' | 'warning' | 'error';
12
+ message: string;
13
+ duration?: number; // 持续时间,单位为毫秒,默认为4000ms
14
+ }
15
+
16
+ export interface INotificationService {
17
+ notify: (options: INotificationOptions) => string;
18
+ dismiss: (id: string) => void;
19
+ }
@@ -0,0 +1,28 @@
1
+ import { toast } from 'sonner';
2
+
3
+ import { INotificationOptions, INotificationService } from './INotificationService';
4
+
5
+ export class NotificationService implements INotificationService {
6
+ private static instance: NotificationService;
7
+
8
+ private constructor() {}
9
+
10
+ static getInstance(): NotificationService {
11
+ if (!NotificationService.instance) {
12
+ NotificationService.instance = new NotificationService();
13
+ }
14
+ return NotificationService.instance;
15
+ }
16
+
17
+ notify(options: INotificationOptions): string {
18
+ const id = toast(options.message, {
19
+ duration: options.duration || 3000,
20
+ });
21
+
22
+ return id as string;
23
+ }
24
+
25
+ dismiss(id: string): void {
26
+ toast.dismiss(id);
27
+ }
28
+ }
@@ -74,7 +74,9 @@ export function useStreaming(options: UseStreamingOptions = {}) {
74
74
  });
75
75
  }, []);
76
76
 
77
- nowCtx.setLoading(isLoading);
77
+ useEffect(() => {
78
+ nowCtx.setLoading(isLoading);
79
+ }, [nowCtx, isLoading]);
78
80
 
79
81
  return {
80
82
  streaming: data?.data ?? [],
@@ -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 nowEntryCreator = buildEntryCreator(ctx);
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
- nowEntryCreator(
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: ctx.api.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: 100,
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
- maxHeight: 180,
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 ctx.api.getDeviceId()).id,
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.api.registerSlot('leftCol', Sticker, this.id);
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
 
@@ -6,6 +6,7 @@ import { IComPropsRegisteredToSlot } from '#/types/slots';
6
6
  import { IPluginStoreEntry, PluginID } from '#/types/plugin';
7
7
 
8
8
  import { StoreCtx } from '../context';
9
+ import { pluginDepDescForHuman } from '../utils';
9
10
 
10
11
  // 空状态展示
11
12
  const EmptyState = () => (
@@ -25,7 +26,8 @@ const ErrorState = ({ error, onRetry }: { error: Error; onRetry: () => void }) =
25
26
  );
26
27
 
27
28
  export const StorePage = (_: IComPropsRegisteredToSlot) => {
28
- const { list, setLoading, isInstalled, install, uninstall } = StoreCtx.useCtx();
29
+ const { list, setLoading, isInstalled, install, uninstall, get, notificationService } =
30
+ StoreCtx.useCtx();
29
31
  const [plugins, setPlugins] = useState<Array<IPluginStoreEntry>>([]);
30
32
  const [error, setError] = useState<Error | null>(null);
31
33
 
@@ -75,15 +77,25 @@ export const StorePage = (_: IComPropsRegisteredToSlot) => {
75
77
  setError(null);
76
78
 
77
79
  try {
78
- await install(id);
79
- await refreshList();
80
+ const success = await install(id);
81
+ if (success) {
82
+ await refreshList();
83
+ } else {
84
+ notificationService.notify({
85
+ level: 'error',
86
+ message: `安装插件失败,依赖缺失或其他错误`,
87
+ });
88
+ }
80
89
  } catch (err) {
81
- setError(err instanceof Error ? err : new Error('安装失败'));
90
+ notificationService.notify({
91
+ level: 'error',
92
+ message: `安装插件失败: ${err instanceof Error ? err.message : String(err)}`,
93
+ });
82
94
  } finally {
83
95
  setLoading(false);
84
96
  }
85
97
  },
86
- [install, refreshList, setLoading]
98
+ [install, refreshList, setLoading, notificationService]
87
99
  );
88
100
 
89
101
  const handleUninstall = useCallback(
@@ -125,7 +137,14 @@ export const StorePage = (_: IComPropsRegisteredToSlot) => {
125
137
  return (
126
138
  <>
127
139
  <Table.Cell>{plugin.name}</Table.Cell>
128
- <Table.Cell>{plugin.description}</Table.Cell>
140
+ <Table.Cell>
141
+ {plugin.description}
142
+ {plugin.dependencies && plugin.dependencies.length > 0 && (
143
+ <div className="mt-4 text-xs text-content-secondary">
144
+ {pluginDepDescForHuman(plugin, get)}
145
+ </div>
146
+ )}
147
+ </Table.Cell>
129
148
  <Table.Cell style={{ textAlign: 'right' }}>
130
149
  {pluginInstalled ? (
131
150
  <Button
@@ -144,7 +163,7 @@ export const StorePage = (_: IComPropsRegisteredToSlot) => {
144
163
  </>
145
164
  );
146
165
  },
147
- [plugins, isInstalled, handleInstall, handleUninstall]
166
+ [plugins, isInstalled, get, handleUninstall, handleInstall]
148
167
  );
149
168
 
150
169
  if (error) {
@@ -1,12 +1,15 @@
1
1
  import { createPluginCtx } from '#/core/context';
2
+ import { NotificationService } from '#/plugins/notification/services/NotificationService';
2
3
  import { IPluginStoreEntry, PluginID } from '#/types/plugin';
3
4
 
4
5
  export interface IStoreCtx {
5
- install: (pid: PluginID) => Promise<void>;
6
+ install: (pid: PluginID) => Promise<boolean>;
6
7
  uninstall: (pid: PluginID) => Promise<void>;
7
8
  isInstalled: (pid: PluginID) => boolean;
8
9
  list: () => Promise<Array<IPluginStoreEntry>> | Array<IPluginStoreEntry>;
10
+ get: (pid: PluginID) => IPluginStoreEntry | undefined;
9
11
  setLoading: (loading: boolean) => void;
12
+ notificationService: NotificationService | Record<string, never>;
10
13
  }
11
14
 
12
15
  export const StoreCtx = createPluginCtx<IStoreCtx>();
@@ -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,24 +8,37 @@ 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 entryCreator = buildEntryCreator(ctx);
13
- entryCreator(
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',
17
27
  pageComponent: StoreCtx.withCtx(
18
28
  {
19
- install: ctx.store?.installPlugin || (async () => {}),
29
+ install: ctx.store?.installPlugin || (async () => false),
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
- ctx.api.setLoading('store', true);
34
+ coreService.setLoading('store', true);
24
35
  const all = Array.from((await ctx.store?.getAllPlugins()) || []);
25
- ctx.api.setLoading('store', false);
36
+ coreService.setLoading('store', false);
26
37
  return all.filter(({ id }) => id !== this.id);
27
38
  },
39
+ notificationService: ctx.service.get('notification'),
28
40
  setLoading: (loading: boolean) => {
29
- ctx.api.setLoading('store', loading);
41
+ coreService.setLoading('store', loading);
30
42
  },
31
43
  },
32
44
  StorePage
@@ -0,0 +1,26 @@
1
+ import { IPluginStoreEntry, PluginID } from '#/types/plugin';
2
+
3
+ export const pluginDepDescForHuman = (
4
+ plugin: IPluginStoreEntry,
5
+ infoGetter: (id: PluginID) => IPluginStoreEntry | undefined
6
+ ): string => {
7
+ if (!plugin.dependencies || plugin.dependencies.length === 0) {
8
+ return '';
9
+ }
10
+
11
+ const desc =
12
+ plugin.dependencies && plugin.dependencies.length > 0
13
+ ? plugin.dependencies
14
+ .map(dep => {
15
+ const depPlugin = infoGetter(dep);
16
+ return depPlugin ? depPlugin.name : dep;
17
+ })
18
+ .join(', ')
19
+ : '';
20
+
21
+ if (!desc) {
22
+ return '';
23
+ }
24
+
25
+ return `需要先安装${desc}。`;
26
+ };