@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.
- package/CHANGELOG.md +12 -0
- package/index.html +0 -11
- package/package.json +3 -2
- 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/{plugins → core/plugin-system}/manifest.ts +8 -6
- package/src/core/plugin-system/pluginManager.ts +118 -0
- package/src/core/{pluginManifestService.ts → plugin-system/pluginManifestService.ts} +4 -15
- package/src/core/plugin-system/pluginStore.ts +196 -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 +54 -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/notification/components/index.tsx +18 -0
- package/src/plugins/notification/index.ts +26 -0
- package/src/plugins/notification/services/INotificationService.ts +19 -0
- package/src/plugins/notification/services/NotificationService.ts +28 -0
- 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 +26 -7
- package/src/plugins/store/context/index.ts +4 -1
- package/src/plugins/store/index.ts +20 -8
- package/src/plugins/store/utils/index.ts +26 -0
- 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 +2 -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
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
|
}
|
package/src/types/plugin.ts
CHANGED
|
@@ -22,6 +22,7 @@ export type PluginID =
|
|
|
22
22
|
| 'now'
|
|
23
23
|
| 'default'
|
|
24
24
|
| 'blog'
|
|
25
|
+
| 'notification'
|
|
25
26
|
| 'fx';
|
|
26
27
|
|
|
27
28
|
export interface IPluginEntry {
|
|
@@ -31,6 +32,7 @@ export interface IPluginEntry {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export type PluginEvents = {
|
|
35
|
+
'plugin:uninstall': PluginID;
|
|
34
36
|
'plugin:loading:changed': IPluginLoadingPayload;
|
|
35
37
|
'site:loading:changed': boolean;
|
|
36
38
|
};
|
package/src/utils/index.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PathRouteProps } from 'react-router-dom';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
|
-
import { IHostContext } from '#/types/hostApi';
|
|
5
4
|
import { IPluginEntry } from '#/types/plugin';
|
|
6
5
|
import { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
7
6
|
|
|
@@ -19,49 +18,3 @@ export const buildEntrySlotCom = (entry: IPluginEntry) => {
|
|
|
19
18
|
|
|
20
19
|
return Component;
|
|
21
20
|
};
|
|
22
|
-
|
|
23
|
-
export const buildEntryCreator = (ctx: IHostContext) => (entry: IPluginEntry, id: string) => {
|
|
24
|
-
ctx.api.registerMiddleware(
|
|
25
|
-
'extendedRoutes',
|
|
26
|
-
(routes: Array<Omit<PathRouteProps, 'element'>>) => {
|
|
27
|
-
return [
|
|
28
|
-
...routes,
|
|
29
|
-
{
|
|
30
|
-
path: entry.path,
|
|
31
|
-
},
|
|
32
|
-
];
|
|
33
|
-
},
|
|
34
|
-
id,
|
|
35
|
-
10
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
ctx.api.registerSlot(
|
|
39
|
-
'route',
|
|
40
|
-
(props: IComPropsRegisteredToSlot) => {
|
|
41
|
-
const route = props.data as Omit<PathRouteProps, 'element'>;
|
|
42
|
-
if (route.path === entry.path) {
|
|
43
|
-
const Com = entry.pageComponent;
|
|
44
|
-
return <Com data={props.data} />;
|
|
45
|
-
}
|
|
46
|
-
return null;
|
|
47
|
-
},
|
|
48
|
-
id
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
if (!entry.label) return;
|
|
52
|
-
|
|
53
|
-
ctx.api.registerMiddleware(
|
|
54
|
-
'transformCoverEntry',
|
|
55
|
-
(entries: Array<LinkProps>) => {
|
|
56
|
-
return [
|
|
57
|
-
...entries,
|
|
58
|
-
{
|
|
59
|
-
to: entry.path,
|
|
60
|
-
children: entry.label,
|
|
61
|
-
},
|
|
62
|
-
];
|
|
63
|
-
},
|
|
64
|
-
id,
|
|
65
|
-
10
|
|
66
|
-
);
|
|
67
|
-
};
|
package/vite.config.js
CHANGED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { PathRouteProps } from 'react-router-dom';
|
|
3
|
-
import { LinkProps } from '@bbki.ng/ui';
|
|
4
|
-
|
|
5
|
-
import { IHostContext } from '#/types/hostApi';
|
|
6
|
-
import type { SlotName, HookPoint, IComPropsRegisteredToSlot } from '#/types/slots';
|
|
7
|
-
import { IPlugin, IPluginEntry, PluginEvents, PluginID, PluginPerm } from '#/types/plugin';
|
|
8
|
-
import { getStableDeviceId } from '#/app/utils/fingerprints';
|
|
9
|
-
import { cfApiFetcher } from '#/app/utils';
|
|
10
|
-
import { buildEntrySlotCom } from '#/utils';
|
|
11
|
-
|
|
12
|
-
import { registry } from './registry';
|
|
13
|
-
import { createEventBus } from './utils/eventBus';
|
|
14
|
-
import { AdminPluginIDSet } from './const';
|
|
15
|
-
import { PluginStore } from './pluginStore';
|
|
16
|
-
|
|
17
|
-
const pluginModules = import.meta.glob('../plugins/*/index.ts');
|
|
18
|
-
|
|
19
|
-
class PluginManager {
|
|
20
|
-
private activePlugins: Map<string, IPlugin> = new Map();
|
|
21
|
-
|
|
22
|
-
private bus = createEventBus<PluginEvents>();
|
|
23
|
-
|
|
24
|
-
private registryEntry = (entry: IPluginEntry, id: string) => {
|
|
25
|
-
this.registerMiddleware(
|
|
26
|
-
'extendedRoutes',
|
|
27
|
-
(routes: Array<Omit<PathRouteProps, 'element'>>) => {
|
|
28
|
-
return [
|
|
29
|
-
...routes,
|
|
30
|
-
{
|
|
31
|
-
path: entry.path,
|
|
32
|
-
},
|
|
33
|
-
];
|
|
34
|
-
},
|
|
35
|
-
id,
|
|
36
|
-
10
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
this.registerSlot('route', buildEntrySlotCom(entry), id);
|
|
40
|
-
|
|
41
|
-
if (!entry.label) return;
|
|
42
|
-
|
|
43
|
-
this.registerMiddleware(
|
|
44
|
-
'transformCoverEntry',
|
|
45
|
-
(entries: Array<LinkProps>) => {
|
|
46
|
-
return [
|
|
47
|
-
...entries,
|
|
48
|
-
{
|
|
49
|
-
to: entry.path,
|
|
50
|
-
children: entry.label,
|
|
51
|
-
},
|
|
52
|
-
];
|
|
53
|
-
},
|
|
54
|
-
id,
|
|
55
|
-
10
|
|
56
|
-
);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
private registerMiddleware = <T>(
|
|
60
|
-
point: HookPoint,
|
|
61
|
-
fn: (data: T) => Promise<T> | T,
|
|
62
|
-
pluginId: string,
|
|
63
|
-
weight = 0
|
|
64
|
-
) => {
|
|
65
|
-
registry.registerMiddleware(point, fn, pluginId, weight);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
private registerSlot = (
|
|
69
|
-
slotName: SlotName,
|
|
70
|
-
component: React.ComponentType<IComPropsRegisteredToSlot>,
|
|
71
|
-
pluginId: string,
|
|
72
|
-
weight = 0
|
|
73
|
-
) => {
|
|
74
|
-
registry.registerComponent(slotName, component, pluginId, weight);
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
private createHostContext(perm: PluginPerm = 'guest'): IHostContext {
|
|
78
|
-
const adminCtx =
|
|
79
|
-
perm === 'admin'
|
|
80
|
-
? {
|
|
81
|
-
store: PluginStore.getInstance(),
|
|
82
|
-
}
|
|
83
|
-
: {};
|
|
84
|
-
return {
|
|
85
|
-
...adminCtx,
|
|
86
|
-
api: {
|
|
87
|
-
fetch: cfApiFetcher,
|
|
88
|
-
setLoading: (id: PluginID, loading: boolean) => {
|
|
89
|
-
this.bus.emit('plugin:loading:changed', { id, loading });
|
|
90
|
-
},
|
|
91
|
-
onLoadingChanged: (listener: (payload: PluginEvents['site:loading:changed']) => void) => {
|
|
92
|
-
return this.bus.on('site:loading:changed', listener);
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
getDeviceId: getStableDeviceId,
|
|
96
|
-
getVersionHash: () => {
|
|
97
|
-
const hashStr: string =
|
|
98
|
-
typeof GLOBAL_COMMIT_HASH === 'string' ? GLOBAL_COMMIT_HASH : '0000000';
|
|
99
|
-
return hashStr;
|
|
100
|
-
},
|
|
101
|
-
|
|
102
|
-
registerSlot: this.registerSlot,
|
|
103
|
-
registerMiddleware: this.registerMiddleware,
|
|
104
|
-
registerEntry: this.registryEntry,
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
private loading = new Set<string>();
|
|
110
|
-
|
|
111
|
-
notifySiteLoadingChanged(loading: boolean) {
|
|
112
|
-
this.bus.emit('site:loading:changed', loading);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
subscribePluginLoading(listener: (payload: PluginEvents['plugin:loading:changed']) => void) {
|
|
116
|
-
return this.bus.on('plugin:loading:changed', listener);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// enable abort ctrl
|
|
120
|
-
async loadPlugin(pluginId: PluginID) {
|
|
121
|
-
if (this.activePlugins.has(pluginId)) {
|
|
122
|
-
console.warn(`Plugin ${pluginId} is already loaded.`);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (this.loading.has(pluginId)) {
|
|
127
|
-
console.warn(`Plugin ${pluginId} is loading...`);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// try load plugin
|
|
132
|
-
this.loading.add(pluginId);
|
|
133
|
-
this.bus.emit('plugin:loading:changed', { id: pluginId, loading: true });
|
|
134
|
-
|
|
135
|
-
const modulePath = `../plugins/${pluginId}/index.ts`;
|
|
136
|
-
const moduleLoader = pluginModules[modulePath];
|
|
137
|
-
if (!moduleLoader) {
|
|
138
|
-
console.error(`Plugin ${pluginId} not found in registry`);
|
|
139
|
-
|
|
140
|
-
this.loading.delete(pluginId);
|
|
141
|
-
this.bus.emit('plugin:loading:changed', { id: pluginId, loading: false });
|
|
142
|
-
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
const module = (await moduleLoader()) as {
|
|
148
|
-
default: IPlugin;
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const p: IPlugin = module.default;
|
|
152
|
-
|
|
153
|
-
const perm = p.getMeta().perm || 'guest';
|
|
154
|
-
if (perm === 'admin' && !AdminPluginIDSet.has(pluginId)) {
|
|
155
|
-
console.error(`Plugin ${pluginId} requires admin permission, but it's not trusted.`);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const ctx = this.createHostContext(perm);
|
|
160
|
-
|
|
161
|
-
if (p.onInstall) {
|
|
162
|
-
await p.onInstall(ctx);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
this.activePlugins.set(pluginId, p);
|
|
166
|
-
} catch (error) {
|
|
167
|
-
console.error(`Failed to load plugin ${pluginId}:`, error);
|
|
168
|
-
} finally {
|
|
169
|
-
this.loading.delete(pluginId);
|
|
170
|
-
this.bus.emit('plugin:loading:changed', { id: pluginId, loading: false });
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async disablePlugin(pluginId: PluginID) {
|
|
175
|
-
const plugin = this.activePlugins.get(pluginId);
|
|
176
|
-
if (!plugin) {
|
|
177
|
-
console.warn(`Plugin ${pluginId} is not loaded.`);
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (plugin.onDisable) {
|
|
182
|
-
await plugin.onDisable();
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
this.activePlugins.delete(pluginId);
|
|
186
|
-
|
|
187
|
-
registry.unregisterAllByPluginId(plugin.id);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export const pluginManager = new PluginManager();
|
package/src/core/pluginStore.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { IPluginStoreEntry, PluginID } from '#/types/plugin';
|
|
2
|
-
|
|
3
|
-
import { pluginManager } from './pluginManager';
|
|
4
|
-
import { PluginManifestService } from './pluginManifestService';
|
|
5
|
-
|
|
6
|
-
export class PluginStore {
|
|
7
|
-
private static instance: PluginStore;
|
|
8
|
-
|
|
9
|
-
private installedSet: Set<PluginID> = new Set();
|
|
10
|
-
|
|
11
|
-
private constructor() {
|
|
12
|
-
this.parse();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
private parse = () => {
|
|
16
|
-
this.installedSet.clear();
|
|
17
|
-
const installedPluginsStr = localStorage.getItem('installed_plugins');
|
|
18
|
-
if (installedPluginsStr) {
|
|
19
|
-
try {
|
|
20
|
-
const entries = JSON.parse(installedPluginsStr) as Array<PluginID>;
|
|
21
|
-
entries.forEach(id => this.installedSet.add(id));
|
|
22
|
-
} catch (e) {
|
|
23
|
-
console.error('Failed to parse installed plugins from localStorage', e);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return this.installedSet;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
private stringify = () => {
|
|
31
|
-
localStorage.setItem('installed_plugins', JSON.stringify(Array.from(this.installedSet)));
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
getInstalledPlugins: () => Set<PluginID> = () => {
|
|
35
|
-
return this.parse();
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
async getAllPlugins(): Promise<Array<IPluginStoreEntry>> {
|
|
39
|
-
const installedPlugins = this.getInstalledPlugins();
|
|
40
|
-
await PluginManifestService.getInstance().fetch();
|
|
41
|
-
const manifest = PluginManifestService.getInstance().getAllPlugins();
|
|
42
|
-
return manifest.map(plugin => ({
|
|
43
|
-
...plugin,
|
|
44
|
-
enabled: installedPlugins.has(plugin.id),
|
|
45
|
-
}));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
isPluginInstalled: (id: PluginID) => boolean = id => {
|
|
49
|
-
return this.getInstalledPlugins().has(id);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
installPlugin: (id: PluginID) => Promise<void> = async id => {
|
|
53
|
-
await pluginManager.loadPlugin(id);
|
|
54
|
-
this.installedSet.add(id);
|
|
55
|
-
this.stringify();
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
disablePlugin: (id: PluginID) => Promise<void> = async id => {
|
|
59
|
-
await pluginManager.disablePlugin(id);
|
|
60
|
-
this.installedSet.delete(id);
|
|
61
|
-
this.stringify();
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
static getInstance() {
|
|
65
|
-
if (!PluginStore.instance) {
|
|
66
|
-
PluginStore.instance = new PluginStore();
|
|
67
|
-
}
|
|
68
|
-
return PluginStore.instance;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
File without changes
|