@bbki.ng/site 5.5.37 → 5.6.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @bbki.ng/site
2
2
 
3
+ ## 5.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - e4f8846: refactor
8
+
9
+ ## 5.6.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 3eab4b5: add plugin store
14
+
3
15
  ## 5.5.37
4
16
 
5
17
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbki.ng/site",
3
- "version": "5.5.37",
3
+ "version": "5.6.1",
4
4
  "description": "code behind bbki.ng",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/src/blog/app.tsx CHANGED
@@ -9,16 +9,13 @@ import { BBContext } from '@/context/bbcontext';
9
9
  import { Slot } from '#/core/components/SlotComp';
10
10
  import { usePlugins } from '#/core/hooks/use_plugins';
11
11
  import { SWR } from '@/swr';
12
- import type { PluginID } from '#/types/plugin';
13
12
 
14
13
  import { Cover } from './pages';
15
14
  import { usePluginEntries } from './hooks/use_plugin_entries';
16
15
  import { BaseLayout } from './components/BaseLayout';
17
16
 
18
- const APP_PLUGIN_IDS: Array<PluginID> = [/*'sticker',*/ 'xwy', 'extra-cd', 'now'];
19
-
20
17
  const AppRoutes = () => {
21
- usePlugins(APP_PLUGIN_IDS);
18
+ usePlugins();
22
19
 
23
20
  const pluginEntries = usePluginEntries();
24
21
 
@@ -0,0 +1,13 @@
1
+ import { ManifestMap } from '#/plugins/manifest';
2
+ import { IHostContext } from '#/types/hostApi';
3
+ import { IPlugin, IPluginManifestEntry, PluginID } from '#/types/plugin';
4
+
5
+ export class BBPlugin implements IPlugin {
6
+ id: PluginID = 'default';
7
+ onInstall?: ((ctx: IHostContext) => void | Promise<void>) | undefined;
8
+ onDisable?: (() => Promise<void> | void) | undefined;
9
+ onDestroy?: (() => void) | undefined;
10
+ getMeta(): IPluginManifestEntry {
11
+ return ManifestMap.get(this.id) as IPluginManifestEntry;
12
+ }
13
+ }
@@ -1,9 +1,11 @@
1
- import { useCallback, useEffect, useState } from 'react';
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
2
 
3
3
  import { useGlobalLoading } from '@/hooks';
4
4
  import { pluginManager } from '#/core/pluginManager';
5
5
  import { PluginID } from '#/types/plugin';
6
6
 
7
+ import { PluginStore } from '../pluginStore';
8
+
7
9
  const usePluginsLoading = () => {
8
10
  const { setGlobalLoading } = useGlobalLoading();
9
11
 
@@ -28,9 +30,16 @@ const usePluginsLoading = () => {
28
30
  * @example
29
31
  * usePlugins(['sticker', 'fontstyler']);
30
32
  */
31
- export const usePlugins = (pluginIds: Array<PluginID>) => {
33
+ export const usePlugins = () => {
32
34
  const [done, setDone] = useState(false);
33
35
 
36
+ const installedPlugins = PluginStore.getInstance().getInstalledPlugins();
37
+
38
+ const pluginIds: Array<PluginID> = useMemo(
39
+ () => ['store', ...Array.from(installedPlugins)],
40
+ [installedPlugins]
41
+ );
42
+
34
43
  usePluginsLoading();
35
44
 
36
45
  const loadAllPlugins = useCallback(async () => {
@@ -8,6 +8,7 @@ import { getStableDeviceId } from '@/utils/fingerprints';
8
8
  import { registry } from './registry';
9
9
  import { createEventBus } from './utils/eventBus';
10
10
  import { AdminPluginIDSet } from './const';
11
+ import { PluginStore } from './pluginStore';
11
12
 
12
13
  const pluginModules = import.meta.glob('../plugins/*/index.ts');
13
14
 
@@ -17,14 +18,15 @@ class PluginManager {
17
18
  private bus = createEventBus<PluginEvents>();
18
19
 
19
20
  private createHostContext(perm: PluginPerm = 'guest'): IHostContext {
20
- const adminOnlyApi = {
21
- installPlugin: (id: PluginID) => this.loadPlugin(id),
22
- disablePlugin: (id: PluginID) => this.disablePlugin(id),
23
- };
24
-
21
+ const adminCtx =
22
+ perm === 'admin'
23
+ ? {
24
+ store: PluginStore.getInstance(),
25
+ }
26
+ : {};
25
27
  return {
28
+ ...adminCtx,
26
29
  api: {
27
- ...(perm === 'admin' ? adminOnlyApi : {}),
28
30
  setLoading: (id: PluginID, loading: boolean) => {
29
31
  this.bus.emit('plugin:loading:changed', { id, loading });
30
32
  },
@@ -89,7 +91,7 @@ class PluginManager {
89
91
 
90
92
  const p: IPlugin = module.default;
91
93
 
92
- const perm = (p as IPlugin & { perm?: PluginPerm }).perm || 'guest';
94
+ const perm = p.getMeta().perm || 'guest';
93
95
  if (perm === 'admin' && !AdminPluginIDSet.has(pluginId)) {
94
96
  console.error(`Plugin ${pluginId} requires admin permission, but it's not trusted.`);
95
97
  return;
@@ -0,0 +1,68 @@
1
+ import { PLUGIN_MANIFEST } from '#/plugins/manifest';
2
+ import { IPluginStoreEntry, PluginID } from '#/types/plugin';
3
+
4
+ import { pluginManager } from './pluginManager';
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
+ getAllPlugins: () => Array<IPluginStoreEntry> = () => {
39
+ const installedPlugins = this.getInstalledPlugins();
40
+ return PLUGIN_MANIFEST.map(plugin => ({
41
+ ...plugin,
42
+ enabled: installedPlugins.has(plugin.id),
43
+ }));
44
+ };
45
+
46
+ isPluginInstalled: (id: PluginID) => boolean = id => {
47
+ return this.getInstalledPlugins().has(id);
48
+ };
49
+
50
+ installPlugin: (id: PluginID) => Promise<void> = async id => {
51
+ await pluginManager.loadPlugin(id);
52
+ this.installedSet.add(id);
53
+ this.stringify();
54
+ };
55
+
56
+ disablePlugin: (id: PluginID) => Promise<void> = async id => {
57
+ await pluginManager.disablePlugin(id);
58
+ this.installedSet.delete(id);
59
+ this.stringify();
60
+ };
61
+
62
+ static getInstance() {
63
+ if (!PluginStore.instance) {
64
+ PluginStore.instance = new PluginStore();
65
+ }
66
+ return PluginStore.instance;
67
+ }
68
+ }
@@ -1,27 +1,14 @@
1
+ import { BBPlugin } from '#/core/bbplugin';
1
2
  import { IHostContext } from '#/types/hostApi';
2
- import { IPlugin } from '#/types/plugin';
3
+ import { PluginID } from '#/types/plugin';
3
4
  import { TitleListItem } from '#/types/posts';
4
5
 
5
- class ExtraCd implements IPlugin {
6
- id: string = 'extra-cd';
7
- name: string = 'Extra CD';
8
- description: string =
9
- 'Provides additional change directory functionalities for enhanced user experience.';
10
- version: string = '1.0.0';
11
- author: string = 'bbki.ng';
6
+ class ExtraCd extends BBPlugin {
7
+ id: PluginID = 'extra-cd';
12
8
 
13
- async onInstall(ctx: IHostContext): Promise<void> {
14
- // Initialize any required resources or settings
9
+ override onInstall = async (ctx: IHostContext): Promise<void> => {
15
10
  ctx.api.registerMiddleware('transformTitleList', this.transformTitleList, this.id, 10);
16
- }
17
-
18
- async onDisable(): Promise<void> {
19
- // Clean up active operations
20
- }
21
-
22
- onDestroy(): void {
23
- // Release all resources
24
- }
11
+ };
25
12
 
26
13
  private transformTitleList = (titleList: Array<TitleListItem>) => {
27
14
  return [
@@ -1,19 +1,14 @@
1
+ import { BBPlugin } from '#/core/bbplugin';
1
2
  import { IHostContext } from '#/types/hostApi';
2
- import { IPlugin } from '#/types/plugin';
3
+ import { PluginID } from '#/types/plugin';
3
4
  import { buildEntryCreator } from '#/utils';
4
5
 
5
6
  import { ExtendedRoutesPage } from './components/page';
6
7
 
7
- export class ExtraEntryPlugin implements IPlugin {
8
- id: string = 'extra-entry';
9
- name: string = 'Extra Entry';
10
- description?: string | undefined;
11
- version: string = '1.0.0';
12
- author?: string | undefined = 'bbki.ng';
8
+ export class ExtraEntryPlugin extends BBPlugin {
9
+ id: PluginID = 'extra-entry';
13
10
 
14
- onInstall?: ((ctx: IHostContext) => void | Promise<void>) | undefined = async (
15
- ctx: IHostContext
16
- ) => {
11
+ override onInstall = async (ctx: IHostContext): Promise<void> => {
17
12
  const entryCreator = buildEntryCreator(ctx);
18
13
  entryCreator(
19
14
  {
@@ -24,9 +19,6 @@ export class ExtraEntryPlugin implements IPlugin {
24
19
  this.id
25
20
  );
26
21
  };
27
-
28
- onDisable?: (() => Promise<void> | void) | undefined;
29
- onDestroy?: (() => void) | undefined;
30
22
  }
31
23
 
32
24
  export default new ExtraEntryPlugin();
@@ -1,40 +1,47 @@
1
- export const PLUGIN_MANIFEST = [
1
+ import { IPluginManifestEntry } from '#/types/plugin';
2
+
3
+ export const PLUGIN_MANIFEST: Array<IPluginManifestEntry> = [
2
4
  {
3
- name: 'sticker',
5
+ name: '贴纸',
4
6
  id: 'sticker',
5
7
  version: '0.1.0',
6
- description: 'A sticker plugin',
8
+ description: '在页面一些地方显示奇怪的贴纸',
7
9
  },
8
10
  {
9
- name: 'store',
11
+ name: '插件商店',
10
12
  id: 'store',
11
13
  version: '0.1.0',
12
- description: 'A plugin for state management and data persistence.',
14
+ description: '一个管理 bbki.ng 插件的商店',
13
15
  perm: 'admin',
14
16
  },
15
17
  {
16
- name: 'now',
18
+ name: '最近',
17
19
  id: 'now',
18
20
  version: '1.0.0',
19
- description: 'show real-time site status and logs',
21
+ description: '显示网站最近更新的版本,以及一些文字动态',
20
22
  },
21
23
  {
22
- name: 'extra-cd',
24
+ name: '捷径',
23
25
  id: 'extra-cd',
24
26
  version: '0.1.0',
25
27
  description:
26
- 'Provides additional change directory functionalities for enhanced user experience.',
28
+ '提供额外的快速切换页面的捷径,例如在标题列表末尾添加一个 "cd ~" 的选项,点击后跳转到主页',
27
29
  },
30
+ // {
31
+ // name: 'extra-entry',
32
+ // id: 'extra-entry',
33
+ // version: '0.1.0',
34
+ // description: 'Provides extra entries for cover and extended routes.',
35
+ // },
28
36
  {
29
- name: 'extra-entry',
30
- id: 'extra-entry',
31
- version: '0.1.0',
32
- description: 'Provides extra entries for cover and extended routes.',
33
- },
34
- {
35
- name: 'xwy',
37
+ name: '小乌鸦',
36
38
  id: 'xwy',
37
39
  version: '0.1.0',
38
- description: 'customize for xwy',
40
+ description: '为小乌鸦合集实现定制功能或者样式的插件',
39
41
  },
40
42
  ];
43
+
44
+ export const ManifestMap = PLUGIN_MANIFEST.reduce((map, entry) => {
45
+ map.set(entry.id, entry);
46
+ return map;
47
+ }, new Map<string, IPluginManifestEntry>());
@@ -1,16 +1,9 @@
1
1
  import React from 'react';
2
- import { PathRouteProps } from 'react-router-dom';
3
2
 
4
3
  import { IComPropsRegisteredToSlot } from '#/types/slots';
5
4
 
6
5
  import Streaming from './streaming';
7
6
 
8
- export const pageNow = ({ data }: IComPropsRegisteredToSlot) => {
9
- const route = data as Omit<PathRouteProps, 'element'>;
10
-
11
- if (route.path !== '/now') {
12
- return <></>;
13
- }
14
-
7
+ export const pageNow = (_: IComPropsRegisteredToSlot) => {
15
8
  return <Streaming />;
16
9
  };
@@ -1,18 +1,15 @@
1
+ import { BBPlugin } from '#/core/bbplugin';
1
2
  import { IHostContext } from '#/types/hostApi';
2
- import { IPlugin } from '#/types/plugin';
3
+ import { PluginID } from '#/types/plugin';
3
4
  import { buildEntryCreator } from '#/utils';
4
5
 
5
6
  import { pageNow } from './components';
6
7
  import { NowCtx } from './context';
7
8
 
8
- export class NowPlugin implements IPlugin {
9
- id: string = 'now';
10
- name: string = 'Now';
11
- description?: string | undefined = 'Manages real-time site status and logs';
12
- version: string = '1.0.0';
13
- author?: string | undefined = 'bbki.ng';
9
+ export class NowPlugin extends BBPlugin {
10
+ id: PluginID = 'now';
14
11
 
15
- onInstall = (ctx: IHostContext) => {
12
+ override onInstall = async (ctx: IHostContext): Promise<void> => {
16
13
  const nowEntryCreator = buildEntryCreator(ctx);
17
14
  const { withCtx } = NowCtx;
18
15
 
@@ -1,18 +1,12 @@
1
+ import { BBPlugin } from '#/core/bbplugin';
1
2
  import { IHostContext } from '#/types/hostApi';
2
- import { IPlugin } from '#/types/plugin';
3
+ import { PluginID } from '#/types/plugin';
3
4
 
4
5
  import { StickerCom } from './components/StickerCom';
5
6
  import { IStickerCtx, StickerCtx } from './context';
6
7
 
7
- class Sticker implements IPlugin {
8
- id: string;
9
- constructor() {
10
- this.id = 'sticker';
11
- this.name = 'Sticker';
12
- this.description = 'A plugin for managing stickers and decorative elements';
13
- this.version = '1.0.0';
14
- this.author = 'bbki.ng';
15
- }
8
+ class Sticker extends BBPlugin {
9
+ id: PluginID = 'sticker';
16
10
 
17
11
  private createPluginCtx = async (ctx: IHostContext): Promise<IStickerCtx> => {
18
12
  return {
@@ -20,7 +14,7 @@ class Sticker implements IPlugin {
20
14
  };
21
15
  };
22
16
 
23
- onInstall = async (ctx: IHostContext) => {
17
+ override onInstall = async (ctx: IHostContext): Promise<void> => {
24
18
  const { withCtx } = StickerCtx;
25
19
 
26
20
  const pluginCtx = await this.createPluginCtx(ctx);
@@ -29,14 +23,6 @@ class Sticker implements IPlugin {
29
23
 
30
24
  ctx.api.registerSlot('leftCol', Sticker, this.id);
31
25
  };
32
-
33
- onDisable = async () => {};
34
-
35
- onDestroy = () => {};
36
- name: string;
37
- description?: string | undefined;
38
- version: string;
39
- author?: string | undefined;
40
26
  }
41
27
 
42
28
  export default new Sticker();
@@ -0,0 +1,175 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import { Button, Table } from '@bbki.ng/ui';
3
+
4
+ import { IComPropsRegisteredToSlot } from '#/types/slots';
5
+ import { IPluginStoreEntry, PluginID } from '#/types/plugin';
6
+
7
+ import { StoreCtx } from '../context';
8
+
9
+ // 空状态展示
10
+ const EmptyState = () => (
11
+ <div className="flex flex-col items-center justify-center p-8 text-content-secondary">
12
+ <p className="text-sm">暂无可用插件</p>
13
+ </div>
14
+ );
15
+
16
+ // 错误状态展示
17
+ const ErrorState = ({ error, onRetry }: { error: Error; onRetry: () => void }) => (
18
+ <div className="flex flex-col items-center justify-center p-8">
19
+ <p className="text-content-danger text-sm mb-4">加载失败: {error.message}</p>
20
+ <Button onClick={onRetry} size="sm">
21
+ 重试
22
+ </Button>
23
+ </div>
24
+ );
25
+
26
+ export const StorePage = (_: IComPropsRegisteredToSlot) => {
27
+ const { list, setLoading, isInstalled, install, uninstall } = StoreCtx.useCtx();
28
+ const [plugins, setPlugins] = useState<Array<IPluginStoreEntry>>([]);
29
+ const [error, setError] = useState<Error | null>(null);
30
+
31
+ useEffect(() => {
32
+ let cancelled = false;
33
+
34
+ const fetchPlugins = async () => {
35
+ setLoading(true);
36
+ setError(null);
37
+
38
+ try {
39
+ const pluginList = await list();
40
+ if (!cancelled) {
41
+ setPlugins(pluginList);
42
+ }
43
+ } catch (err) {
44
+ if (!cancelled) {
45
+ setError(err instanceof Error ? err : new Error('获取插件列表失败'));
46
+ }
47
+ } finally {
48
+ if (!cancelled) {
49
+ setLoading(false);
50
+ }
51
+ }
52
+ };
53
+
54
+ fetchPlugins();
55
+
56
+ return () => {
57
+ cancelled = true;
58
+ };
59
+ }, [list, setLoading]);
60
+
61
+ const refreshList = useCallback(async () => {
62
+ try {
63
+ const pluginList = await list();
64
+ setPlugins(pluginList);
65
+ setError(null);
66
+ } catch (err) {
67
+ setError(err instanceof Error ? err : new Error('刷新列表失败'));
68
+ }
69
+ }, [list]);
70
+
71
+ const handleInstall = useCallback(
72
+ async (id: PluginID) => {
73
+ setLoading(true);
74
+ setError(null);
75
+
76
+ try {
77
+ await install(id);
78
+ await refreshList();
79
+ } catch (err) {
80
+ setError(err instanceof Error ? err : new Error('安装失败'));
81
+ } finally {
82
+ setLoading(false);
83
+ }
84
+ },
85
+ [install, refreshList, setLoading]
86
+ );
87
+
88
+ const handleUninstall = useCallback(
89
+ async (id: PluginID) => {
90
+ setLoading(true);
91
+ setError(null);
92
+
93
+ try {
94
+ await uninstall(id);
95
+ await refreshList();
96
+ } catch (err) {
97
+ setError(err instanceof Error ? err : new Error('卸载失败'));
98
+ } finally {
99
+ setLoading(false);
100
+ }
101
+ },
102
+ [uninstall, refreshList, setLoading]
103
+ );
104
+
105
+ const headerRenderer = useCallback(() => {
106
+ return (
107
+ <>
108
+ <Table.HCell>插件名称</Table.HCell>
109
+ <Table.HCell>插件描述</Table.HCell>
110
+ <Table.HCell style={{ textAlign: 'right' }}>安装/卸载</Table.HCell>
111
+ </>
112
+ );
113
+ }, []);
114
+
115
+ const rowRenderer = useCallback(
116
+ (index: number) => {
117
+ if (index < 0 || index >= plugins.length) {
118
+ return null;
119
+ }
120
+
121
+ const plugin = plugins[index];
122
+ const pluginInstalled = isInstalled(plugin.id);
123
+
124
+ return (
125
+ <>
126
+ <Table.Cell>{plugin.name}</Table.Cell>
127
+ <Table.Cell>{plugin.description}</Table.Cell>
128
+ <Table.Cell style={{ textAlign: 'right' }}>
129
+ {pluginInstalled ? (
130
+ <Button
131
+ variant="ghost"
132
+ className="text-content-danger"
133
+ onClick={() => handleUninstall(plugin.id)}
134
+ >
135
+ 卸载
136
+ </Button>
137
+ ) : (
138
+ <Button color="primary" onClick={() => handleInstall(plugin.id)}>
139
+ 安装
140
+ </Button>
141
+ )}
142
+ </Table.Cell>
143
+ </>
144
+ );
145
+ },
146
+ [plugins, isInstalled, handleInstall, handleUninstall]
147
+ );
148
+
149
+ if (error) {
150
+ return (
151
+ <div className="prose">
152
+ <ErrorState error={error} onRetry={refreshList} />
153
+ </div>
154
+ );
155
+ }
156
+
157
+ if (plugins.length === 0) {
158
+ return (
159
+ <div className="prose">
160
+ <EmptyState />
161
+ </div>
162
+ );
163
+ }
164
+
165
+ return (
166
+ <div className="prose">
167
+ <Table
168
+ className="w-full"
169
+ rowCount={plugins.length}
170
+ headerRenderer={headerRenderer}
171
+ rowRenderer={rowRenderer}
172
+ />
173
+ </div>
174
+ );
175
+ };
@@ -0,0 +1,12 @@
1
+ import { createPluginCtx } from '#/core/context';
2
+ import { IPluginStoreEntry, PluginID } from '#/types/plugin';
3
+
4
+ export interface IStoreCtx {
5
+ install: (pid: PluginID) => Promise<void>;
6
+ uninstall: (pid: PluginID) => Promise<void>;
7
+ isInstalled: (pid: PluginID) => boolean;
8
+ list: () => Promise<Array<IPluginStoreEntry>> | Array<IPluginStoreEntry>;
9
+ setLoading: (loading: boolean) => void;
10
+ }
11
+
12
+ export const StoreCtx = createPluginCtx<IStoreCtx>();
@@ -0,0 +1,38 @@
1
+ import { BBPlugin } from '#/core/bbplugin';
2
+ import { IHostContext } from '#/types/hostApi';
3
+ import { PluginID } from '#/types/plugin';
4
+ import { buildEntryCreator } from '#/utils';
5
+
6
+ import { StorePage } from './components/storePage';
7
+ import { StoreCtx } from './context';
8
+
9
+ export class PluginStore extends BBPlugin {
10
+ id: PluginID = 'store';
11
+ override onInstall = async (ctx: IHostContext): Promise<void> => {
12
+ const entryCreator = buildEntryCreator(ctx);
13
+ entryCreator(
14
+ {
15
+ path: '/store',
16
+ label: 'cd ./store',
17
+ pageComponent: StoreCtx.withCtx(
18
+ {
19
+ install: ctx.store?.installPlugin || (async () => {}),
20
+ uninstall: ctx.store?.disablePlugin || (async () => {}),
21
+ isInstalled: ctx.store?.isPluginInstalled || ((_: PluginID) => false),
22
+ list: () => {
23
+ const installed = Array.from(ctx.store?.getAllPlugins() || []);
24
+ return installed.filter(({ id }) => id !== this.id);
25
+ },
26
+ setLoading: (loading: boolean) => {
27
+ ctx.api.setLoading('store', loading);
28
+ },
29
+ },
30
+ StorePage
31
+ ),
32
+ },
33
+ this.id
34
+ );
35
+ };
36
+ }
37
+
38
+ export default new PluginStore();
@@ -1,6 +1,7 @@
1
1
  import { IHostContext } from '#/types/hostApi';
2
- import { IPlugin } from '#/types/plugin';
2
+ import { BBPlugin } from '#/core/bbplugin';
3
3
  import { HookPoint } from '#/types/slots';
4
+ import { PluginID } from '#/types/plugin';
4
5
 
5
6
  import { HookPointTypeMap, Transformer } from './types';
6
7
  import { SpecialTitle } from './components/article';
@@ -14,16 +15,12 @@ import {
14
15
  pinTitle,
15
16
  } from './transformers';
16
17
 
17
- class XwyPlugin implements IPlugin {
18
- id = 'fontstyler';
19
- name = 'Font Styler';
20
- description = 'Apply custom fonts to specific post titles';
21
- version = '1.0.0';
22
- author = 'bbki.ng';
18
+ class XwyPlugin extends BBPlugin {
19
+ id: PluginID = 'xwy';
23
20
 
24
21
  private loadedFonts = new Set<string>();
25
22
 
26
- onInstall = (ctx: IHostContext) => {
23
+ override onInstall = (ctx: IHostContext) => {
27
24
  // add font loading listener
28
25
  this.initFontLoadingListener();
29
26
 
@@ -71,7 +68,7 @@ class XwyPlugin implements IPlugin {
71
68
  });
72
69
  }
73
70
 
74
- onDisable = async () => {
71
+ override onDisable = async () => {
75
72
  this.loadedFonts.forEach(fontName => {
76
73
  const styleEl = document.getElementById(`font-${fontName}`);
77
74
  if (styleEl) {
@@ -80,8 +77,6 @@ class XwyPlugin implements IPlugin {
80
77
  });
81
78
  this.loadedFonts.clear();
82
79
  };
83
-
84
- onDestroy = () => {};
85
80
  }
86
81
 
87
82
  export default new XwyPlugin();
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
 
3
3
  import { type FingerprintData } from '@/utils/fingerprints';
4
+ import { PluginStore } from '#/core/pluginStore';
4
5
 
5
6
  import { HookPoint, IComPropsRegisteredToSlot, SlotName } from './slots';
6
7
  import { PluginID } from './plugin';
@@ -20,13 +21,11 @@ export interface IHostApi {
20
21
  pluginId: string,
21
22
  weight?: number
22
23
  ) => void;
23
-
24
- installPlugin?: (id: PluginID) => void;
25
- disablePlugin?: (id: PluginID) => void;
26
24
  }
27
25
 
28
26
  export type PluginInitializer = (api: IHostApi) => void;
29
27
 
30
28
  export interface IHostContext {
29
+ store?: PluginStore;
31
30
  api: IHostApi;
32
31
  }
@@ -5,18 +5,15 @@ import { IComPropsRegisteredToSlot } from '#/types/slots';
5
5
  import { IHostContext } from './hostApi';
6
6
 
7
7
  export interface IPlugin {
8
- id: string;
9
- name: string;
10
- description?: string;
11
- version: string;
12
- author?: string;
8
+ id: PluginID;
13
9
 
10
+ getMeta: () => IPluginManifestEntry;
14
11
  onInstall?: (ctx: IHostContext) => void | Promise<void>;
15
12
  onDisable?: () => Promise<void> | void;
16
13
  onDestroy?: () => void;
17
14
  }
18
15
 
19
- export type PluginID = 'sticker' | 'xwy' | 'extra-cd' | 'extra-entry' | 'store' | 'now';
16
+ export type PluginID = 'sticker' | 'xwy' | 'extra-cd' | 'extra-entry' | 'store' | 'now' | 'default';
20
17
 
21
18
  export interface IPluginEntry {
22
19
  path: string;
@@ -42,3 +39,7 @@ export interface IPluginManifestEntry {
42
39
  description?: string;
43
40
  perm?: PluginPerm;
44
41
  }
42
+
43
+ export interface IPluginStoreEntry extends IPluginManifestEntry {
44
+ enabled: boolean;
45
+ }