@bbki.ng/site 5.8.9 → 5.8.11

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,19 @@
1
1
  # @bbki.ng/site
2
2
 
3
+ ## 5.8.11
4
+
5
+ ### Patch Changes
6
+
7
+ - f631db2: add version plugin
8
+ - Updated dependencies [f631db2]
9
+ - @bbki.ng/ui@0.2.22
10
+
11
+ ## 5.8.10
12
+
13
+ ### Patch Changes
14
+
15
+ - 28580cc: update manual install prompt
16
+
3
17
  ## 5.8.9
4
18
 
5
19
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbki.ng/site",
3
- "version": "5.8.9",
3
+ "version": "5.8.11",
4
4
  "description": "code behind bbki.ng",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -15,7 +15,7 @@
15
15
  "react-router-dom": "6",
16
16
  "sonner": "^2.0.7",
17
17
  "swr": "^2.2.5",
18
- "@bbki.ng/ui": "0.2.21"
18
+ "@bbki.ng/ui": "0.2.22"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@eslint/compat": "^1.0.0",
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useMemo, useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
2
3
 
3
4
  import { useGlobalLoading } from '#/app/hooks/use_global_loading';
4
5
  import { pluginManager } from '#/core/plugin-system/pluginManager';
@@ -52,6 +53,9 @@ export const usePlugins = () => {
52
53
 
53
54
  usePluginsLoading();
54
55
 
56
+ const nav = useNavigate();
57
+ CoreService.getInstance().injectNavigate(nav);
58
+
55
59
  useEffect(() => {
56
60
  const unregister = setGlobalLoading('plugins', !done);
57
61
 
@@ -8,6 +8,7 @@ export class BBPlugin implements IPlugin {
8
8
  onInstall?: ((ctx: IHostContext) => void | Promise<void>) | undefined;
9
9
  onDisable?: (() => Promise<void> | void) | undefined;
10
10
  onDestroy?: (() => void) | undefined;
11
+ onManualInstall?: (() => void) | undefined;
11
12
  getMeta(): IPluginManifestEntry {
12
13
  return PluginManifestService.getInstance().getPlugin(this.id) as IPluginManifestEntry;
13
14
  }
@@ -43,7 +43,7 @@ class PluginManager {
43
43
  }
44
44
 
45
45
  // enable abort ctrl
46
- async loadPlugin(pluginId: PluginID) {
46
+ async loadPlugin(pluginId: PluginID): Promise<IPlugin | undefined> {
47
47
  if (this.activePlugins.has(pluginId)) {
48
48
  console.warn(`Plugin ${pluginId} is already loaded.`);
49
49
  return;
@@ -88,6 +88,7 @@ class PluginManager {
88
88
  }
89
89
 
90
90
  this.activePlugins.set(pluginId, p);
91
+ return p;
91
92
  } catch (error) {
92
93
  console.error(`Failed to load plugin ${pluginId}:`, error);
93
94
  } finally {
@@ -96,6 +97,10 @@ class PluginManager {
96
97
  }
97
98
  }
98
99
 
100
+ getPlugin(pluginId: PluginID): IPlugin | undefined {
101
+ return this.activePlugins.get(pluginId);
102
+ }
103
+
99
104
  async disablePlugin(pluginId: PluginID) {
100
105
  const plugin = this.activePlugins.get(pluginId);
101
106
  if (!plugin) {
@@ -164,7 +164,7 @@ export class PluginStore {
164
164
  };
165
165
  };
166
166
 
167
- installPlugin: (id: PluginID) => Promise<boolean> = async id => {
167
+ installPlugin: (id: PluginID, manual?: boolean) => Promise<boolean> = async (id, manual) => {
168
168
  if (!this.checkDep(id)) {
169
169
  // throw new Error(`Cannot install plugin ${id} due to missing dependencies`);
170
170
  console.warn(`Cannot install plugin ${id} due to missing dependencies`);
@@ -172,7 +172,10 @@ export class PluginStore {
172
172
  }
173
173
 
174
174
  try {
175
- await pluginManager.loadPlugin(id);
175
+ const plugin = await pluginManager.loadPlugin(id);
176
+ if (manual && plugin?.onManualInstall) {
177
+ plugin.onManualInstall();
178
+ }
176
179
  this.installedSet.add(id);
177
180
  this.stringify();
178
181
  return true;
@@ -1,4 +1,5 @@
1
1
  import { Fetcher } from 'swr';
2
+ import { NavigateFunction } from 'react-router-dom';
2
3
 
3
4
  import { getStableDeviceId } from '#/app/utils/fingerprints';
4
5
  import { cfApiFetcher } from '#/app/utils';
@@ -13,9 +14,27 @@ export class CoreService implements ICoreService {
13
14
  private constructor() {}
14
15
 
15
16
  private bus = createEventBus<PluginEvents>();
17
+ private navigate?: NavigateFunction;
16
18
 
17
19
  getDeviceId = getStableDeviceId;
18
20
 
21
+ injectNavigate(nav: NavigateFunction) {
22
+ this.navigate = nav;
23
+ }
24
+
25
+ open = (url: string, options?: { newTab?: boolean }) => {
26
+ // use navigate
27
+ if (this.navigate) {
28
+ this.navigate(url);
29
+ } else {
30
+ if (options?.newTab) {
31
+ window.open(url, '_blank');
32
+ } else {
33
+ window.location.href = url;
34
+ }
35
+ }
36
+ };
37
+
19
38
  getVersionHash() {
20
39
  const hashStr: string = typeof GLOBAL_COMMIT_HASH === 'string' ? GLOBAL_COMMIT_HASH : '0000000';
21
40
  return hashStr;
@@ -1,4 +1,5 @@
1
1
  import { Fetcher } from 'swr';
2
+ import { NavigateFunction } from 'react-router-dom';
2
3
 
3
4
  import { FingerprintData } from '#/app/utils/fingerprints';
4
5
  import { PluginID } from '#/types/plugin';
@@ -19,5 +20,9 @@ export interface ICoreService {
19
20
  subscribePluginUninstall(listener: (payload: PluginID) => void): () => void;
20
21
  notifyPluginUninstall(id: PluginID): void;
21
22
 
23
+ injectNavigate: (nav: NavigateFunction) => void;
24
+
25
+ open: (url: string, options?: { newTab?: boolean }) => void;
26
+
22
27
  fetch: Fetcher;
23
28
  }
@@ -0,0 +1,11 @@
1
+ import { Link } from '@bbki.ng/ui';
2
+
3
+ const LinkCom = () => {
4
+ return (
5
+ <Link to="/blog" className="ml-[var(--toast-button-margin-start)]">
6
+ 打开
7
+ </Link>
8
+ );
9
+ };
10
+
11
+ export const BlogLink = <LinkCom />;
@@ -5,6 +5,7 @@ import { IHostContext } from '#/types/hostApi';
5
5
  import { PluginID } from '#/types/plugin';
6
6
 
7
7
  import { BlogPageApp } from './components/app';
8
+ import { BlogLink } from './components/BlogLink';
8
9
  import { BlogContext } from './context';
9
10
  import { BlogUIService } from './services/BlogUIService';
10
11
 
@@ -51,6 +52,18 @@ export class BlogPlugin extends BBPlugin {
51
52
  );
52
53
  };
53
54
 
55
+ override onManualInstall = () => {
56
+ this.promptToOpen();
57
+ };
58
+
59
+ private promptToOpen = () => {
60
+ const notificationService = this._serviceRegistry?.get('notification');
61
+ notificationService?.notify({
62
+ message: '博客安装成功.',
63
+ action: BlogLink,
64
+ });
65
+ };
66
+
54
67
  override onDestroy?: (() => void) | undefined = () => {
55
68
  BlogUIService.getInstance().unregisterAllByPluginId(this.id);
56
69
  this._serviceRegistry?.unregister('blog:uiService');
@@ -22,14 +22,14 @@ const useLoadingState = () => {
22
22
 
23
23
  export const FxCom = (_: IComPropsRegisteredToSlot) => {
24
24
  const ctx = FxContext.useCtx();
25
- const hashStr = ctx.versionHash;
25
+ // const hashStr = ctx.versionHash;
26
26
  const deviceId = ctx.deviceId;
27
27
  const isLoading = useLoadingState();
28
28
 
29
29
  const textEffects = useTextEffects();
30
30
 
31
31
  const effects: Effect[] = useMemo(() => {
32
- const wmLines = [hashStr];
32
+ const wmLines = [];
33
33
  if (deviceId) wmLines.push(deviceId);
34
34
 
35
35
  return [
@@ -39,7 +39,7 @@ export const FxCom = (_: IComPropsRegisteredToSlot) => {
39
39
  watermark({ lines: wmLines }),
40
40
  ...textEffects,
41
41
  ];
42
- }, [isLoading, deviceId, hashStr, textEffects]);
42
+ }, [isLoading, deviceId, textEffects]);
43
43
 
44
44
  return <EffectLayer effects={effects} />;
45
45
  };
@@ -16,6 +16,10 @@ export const NotifyComp: FC<IComPropsRegisteredToSlot> = _ => {
16
16
  borderRadius: 0,
17
17
  border: 'none',
18
18
  },
19
+ classNames: {
20
+ actionButton: 'rounded-none!',
21
+ toast: 'font-mono',
22
+ },
19
23
  }}
20
24
  />
21
25
  );
@@ -1,3 +1,6 @@
1
+ import type React from 'react';
2
+ import { ToastT } from 'sonner';
3
+
1
4
  declare module '#/core/shared-service/service-registry' {
2
5
  interface ServiceIdentifierMap {
3
6
  notification: INotificationService;
@@ -6,10 +9,12 @@ declare module '#/core/shared-service/service-registry' {
6
9
 
7
10
  export type NotificationType = 'toast' | 'banner' | 'modal';
8
11
 
9
- export interface INotificationOptions {
10
- type?: NotificationType;
11
- level?: 'info' | 'success' | 'warning' | 'error';
12
- message: string;
12
+ export interface INotificationOptions extends Omit<
13
+ ToastT,
14
+ 'message' | 'id' | 'type' | 'title' | 'jsx' | 'delete' | 'promise'
15
+ > {
16
+ uiType?: NotificationType;
17
+ message: (() => React.ReactNode) | React.ReactNode;
13
18
  duration?: number; // 持续时间,单位为毫秒,默认为4000ms
14
19
  }
15
20
 
@@ -17,6 +17,7 @@ export class NotificationService implements INotificationService {
17
17
  notify(options: INotificationOptions): string {
18
18
  const id = toast(options.message, {
19
19
  duration: options.duration || 3000,
20
+ ...options,
20
21
  });
21
22
 
22
23
  return id as string;
@@ -0,0 +1,7 @@
1
+ import { Link } from '@bbki.ng/ui';
2
+
3
+ export const NowLink = (
4
+ <Link to="/now" className="ml-[var(--toast-button-margin-start)]">
5
+ 打开
6
+ </Link>
7
+ );
@@ -3,13 +3,17 @@ import { IHostContext } from '#/types/hostApi';
3
3
  import { PluginID } from '#/types/plugin';
4
4
 
5
5
  import { pageNow } from './components';
6
+ import { NowLink } from './components/NowLink';
6
7
  import { NowCtx } from './context';
7
8
 
8
9
  export class NowPlugin extends BBPlugin {
9
10
  id: PluginID = 'now';
10
11
 
12
+ private _serviceRegistry?: IHostContext['service'];
13
+
11
14
  override onInstall = async (ctx: IHostContext): Promise<void> => {
12
15
  const systemUIService = ctx.service.get('core:uiService');
16
+ this._serviceRegistry = ctx.service;
13
17
  if (!systemUIService) {
14
18
  console.warn(`[${this.id}] core:uiService not found, cannot register entry`);
15
19
  return;
@@ -41,6 +45,18 @@ export class NowPlugin extends BBPlugin {
41
45
  this.id
42
46
  );
43
47
  };
48
+
49
+ override onManualInstall = () => {
50
+ this.promptToOpen();
51
+ };
52
+
53
+ private promptToOpen = () => {
54
+ const notificationService = this._serviceRegistry?.get('notification');
55
+ notificationService?.notify({
56
+ message: '安装成功.',
57
+ action: NowLink,
58
+ });
59
+ };
44
60
  }
45
61
 
46
62
  export default new NowPlugin();
@@ -17,7 +17,7 @@ const EmptyState = () => (
17
17
 
18
18
  // 错误状态展示
19
19
  const ErrorState = ({ error, onRetry }: { error: Error; onRetry: () => void }) => (
20
- <div className="flex flex-col items-center justify-center p-8">
20
+ <div className="flex flex-col items-start justify-center p-8">
21
21
  <p className="text-content-danger text-sm mb-4">加载失败: {error.message}</p>
22
22
  <Button onClick={onRetry} size="sm">
23
23
  重试
@@ -77,18 +77,16 @@ export const StorePage = (_: IComPropsRegisteredToSlot) => {
77
77
  setError(null);
78
78
 
79
79
  try {
80
- const success = await install(id);
80
+ const success = await install(id, true);
81
81
  if (success) {
82
82
  await refreshList();
83
83
  } else {
84
84
  notificationService.notify({
85
- level: 'error',
86
85
  message: `安装插件失败,依赖缺失或其他错误`,
87
86
  });
88
87
  }
89
88
  } catch (err) {
90
89
  notificationService.notify({
91
- level: 'error',
92
90
  message: `安装插件失败: ${err instanceof Error ? err.message : String(err)}`,
93
91
  });
94
92
  } finally {
@@ -3,7 +3,7 @@ import { NotificationService } from '#/plugins/notification/services/Notificatio
3
3
  import { IPluginStoreEntry, PluginID } from '#/types/plugin';
4
4
 
5
5
  export interface IStoreCtx {
6
- install: (pid: PluginID) => Promise<boolean>;
6
+ install: (pid: PluginID, manual?: boolean) => Promise<boolean>;
7
7
  uninstall: (pid: PluginID) => Promise<void>;
8
8
  isInstalled: (pid: PluginID) => boolean;
9
9
  list: () => Promise<Array<IPluginStoreEntry>> | Array<IPluginStoreEntry>;
@@ -0,0 +1,28 @@
1
+ import { BBPlugin } from '#/core/plugin-system/bbplugin';
2
+ import { IHostContext } from '#/types/hostApi';
3
+ import { PluginID } from '#/types/plugin';
4
+
5
+ export class VersionPlugin extends BBPlugin {
6
+ id: PluginID = 'version';
7
+
8
+ private _clear = () => {};
9
+
10
+ override onInstall?: ((ctx: IHostContext) => void | Promise<void>) | undefined = async ctx => {
11
+ const fxService = ctx.service.get('fx:service');
12
+
13
+ const hash = await ctx.service.get('core:baseService')?.getVersionHash();
14
+
15
+ this._clear = fxService.drawText({
16
+ text: `version: ${hash}`,
17
+ color: '#f8babc',
18
+ y: window.innerHeight - 50,
19
+ x: 16,
20
+ });
21
+ };
22
+
23
+ override onDestroy?: (() => void) | undefined = () => {
24
+ this._clear?.();
25
+ };
26
+ }
27
+
28
+ export default new VersionPlugin();
@@ -0,0 +1,7 @@
1
+ import { Link } from '@bbki.ng/ui';
2
+
3
+ export const XwyLink = (
4
+ <Link to="/blog/小乌鸦合集" className="ml-[var(--toast-button-margin-start)]">
5
+ 打开
6
+ </Link>
7
+ );
@@ -12,14 +12,18 @@ import {
12
12
  transformPostContent,
13
13
  pinTitle,
14
14
  } from './transformers';
15
+ import { XwyLink } from './components/XwyLink';
15
16
 
16
17
  class XwyPlugin extends BBPlugin {
17
18
  id: PluginID = 'xwy';
18
19
 
19
20
  private loadedFonts = new Set<string>();
20
21
 
22
+ private _serviceRegistry?: IHostContext['service'];
23
+
21
24
  override onInstall = (ctx: IHostContext) => {
22
25
  // add font loading listener
26
+ this._serviceRegistry = ctx.service;
23
27
  this.initFontLoadingListener();
24
28
 
25
29
  // 加载所有需要的字体文件
@@ -64,6 +68,18 @@ class XwyPlugin extends BBPlugin {
64
68
  }
65
69
  }
66
70
 
71
+ override onManualInstall = () => {
72
+ this.promptToOpen();
73
+ };
74
+
75
+ private promptToOpen = () => {
76
+ const notificationService = this._serviceRegistry?.get('notification');
77
+ notificationService?.notify({
78
+ message: '小乌鸦安装成功.',
79
+ action: XwyLink,
80
+ });
81
+ };
82
+
67
83
  private initFontLoadingListener() {
68
84
  // listen font loading status and add class to body for styling
69
85
  console.log('Initializing font loading listener');
@@ -11,12 +11,15 @@ export interface IPlugin {
11
11
  onInstall?: (ctx: IHostContext) => void | Promise<void>;
12
12
  onDisable?: () => Promise<void> | void;
13
13
  onDestroy?: () => void;
14
+
15
+ onManualInstall?: () => void;
14
16
  }
15
17
 
16
18
  export type PluginID =
17
19
  | 'sticker'
18
20
  | 'xwy'
19
21
  | 'extra-cd'
22
+ | 'version'
20
23
  | 'extra-entry'
21
24
  | 'store'
22
25
  | 'now'