@bbki.ng/site 5.5.33 → 5.5.35

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,18 @@
1
1
  # @bbki.ng/site
2
2
 
3
+ ## 5.5.35
4
+
5
+ ### Patch Changes
6
+
7
+ - 86a9e69: fix padding
8
+
9
+ ## 5.5.34
10
+
11
+ ### Patch Changes
12
+
13
+ - 648530c: fix lineheight
14
+ - 346f6c7: plugin status sync to react
15
+
3
16
  ## 5.5.33
4
17
 
5
18
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbki.ng/site",
3
- "version": "5.5.33",
3
+ "version": "5.5.35",
4
4
  "description": "code behind bbki.ng",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/src/blog/app.tsx CHANGED
@@ -15,37 +15,39 @@ import { Cover, Streaming } from './pages';
15
15
  import { usePluginEntries } from './hooks/use_plugin_entries';
16
16
  import { BaseLayout } from './components/BaseLayout';
17
17
 
18
- const APP_PLUGIN_IDS: Array<PluginID> = ['sticker', 'xwy', 'extra-cd' /*'extra-entry'*/];
18
+ const APP_PLUGIN_IDS: Array<PluginID> = [/*'sticker',*/ 'xwy', 'extra-cd' /*'extra-entry'*/];
19
19
 
20
- export const App = () => {
20
+ const AppRoutes = () => {
21
21
  usePlugins(APP_PLUGIN_IDS);
22
22
 
23
23
  const pluginEntries = usePluginEntries();
24
24
 
25
+ return (
26
+ <Routes>
27
+ <Route path="/" element={<BaseLayout />}>
28
+ <Route index element={<Cover />} />
29
+
30
+ <Route path="blog" element={<Outlet />}>
31
+ <Route path="" element={<Txt />} />
32
+ <Route path=":title" element={<ArticlePage />} />
33
+ </Route>
34
+
35
+ <Route path="bot" element={<BotRedirect />} />
36
+ <Route path="now" element={<Streaming />} />
37
+ {pluginEntries?.map(route => (
38
+ <Route key={route.path} path={route.path} element={<Slot name="route" data={route} />} />
39
+ ))}
40
+ </Route>
41
+ <Route path="*" element={<NotFound />} />
42
+ </Routes>
43
+ );
44
+ };
45
+
46
+ export const App = () => {
25
47
  return (
26
48
  <SWR>
27
49
  <BBContext>
28
- <Routes>
29
- <Route path="/" element={<BaseLayout />}>
30
- <Route index element={<Cover />} />
31
-
32
- <Route path="blog" element={<Outlet />}>
33
- <Route path="" element={<Txt />} />
34
- <Route path=":title" element={<ArticlePage />} />
35
- </Route>
36
-
37
- <Route path="bot" element={<BotRedirect />} />
38
- <Route path="now" element={<Streaming />} />
39
- {pluginEntries?.map(route => (
40
- <Route
41
- key={route.path}
42
- path={route.path}
43
- element={<Slot name="route" data={route} />}
44
- />
45
- ))}
46
- </Route>
47
- <Route path="*" element={<NotFound />} />
48
- </Routes>
50
+ <AppRoutes />
49
51
  </BBContext>
50
52
  </SWR>
51
53
  );
@@ -27,7 +27,7 @@ export const BaseLayout = () => {
27
27
  loading={isLoading}
28
28
  customLogo={<Slot name="logo" data={defaultLogo} placeholder={defaultLogo} />}
29
29
  style={{
30
- paddingTop: 'var(--safe-top)',
30
+ paddingTop: 'calc(var(--safe-top) + 4px)',
31
31
  transition: 'all .2s ease-in-out',
32
32
  }}
33
33
  />
@@ -45,7 +45,7 @@ export const BaseLayout = () => {
45
45
  </div>
46
46
  }
47
47
  >
48
- <Container className="py-32">
48
+ <Container className="py-48">
49
49
  <ErrorBoundary>
50
50
  <Outlet />
51
51
  </ErrorBoundary>
@@ -1,4 +1,4 @@
1
- import { useContext, useEffect } from 'react';
1
+ import { useCallback, useContext, useEffect } from 'react';
2
2
 
3
3
  import { GlobalLoadingContext } from '@/context/global_loading_state_provider';
4
4
 
@@ -9,7 +9,13 @@ import { GlobalLoadingContext } from '@/context/global_loading_state_provider';
9
9
  * @param id - Unique identifier for this loading state
10
10
  * @param loading - Whether this specific loading state is active
11
11
  */
12
- export function useGlobalLoading(id: string | undefined, loading: boolean | undefined) {
12
+ export function useGlobalLoading(
13
+ id?: string | undefined,
14
+ loading?: boolean | undefined
15
+ ): {
16
+ isLoading: boolean;
17
+ setGlobalLoading: (key: string, loadingFromManual: boolean) => () => void;
18
+ } {
13
19
  const { register, setLoading, unregister, isLoading } = useContext(GlobalLoadingContext);
14
20
 
15
21
  useEffect(() => {
@@ -22,5 +28,20 @@ export function useGlobalLoading(id: string | undefined, loading: boolean | unde
22
28
  };
23
29
  }, [id, loading, register, setLoading, unregister]);
24
30
 
25
- return isLoading;
31
+ const setGlobalLoading = useCallback(
32
+ (key: string, loadingFromManual: boolean) => {
33
+ register(key);
34
+ setLoading(key, loadingFromManual);
35
+
36
+ return () => {
37
+ unregister(key);
38
+ };
39
+ },
40
+ [register, setLoading, unregister]
41
+ );
42
+
43
+ return {
44
+ isLoading,
45
+ setGlobalLoading,
46
+ };
26
47
  }
@@ -53,7 +53,7 @@ export const usePosts = (name: string = '', suspense?: boolean) => {
53
53
  }
54
54
  }, [baseTitleList, run]);
55
55
 
56
- const gLoading = useGlobalLoading('posts', isDataLoading || isTransforming);
56
+ const { isLoading: gLoading } = useGlobalLoading('posts', isDataLoading || isTransforming);
57
57
 
58
58
  const posts =
59
59
  isDataLoading || name === '' || swrError || !data
package/src/blog/main.css CHANGED
@@ -73,7 +73,7 @@ a {
73
73
 
74
74
  @supports (padding-top: env(safe-area-inset-top)) {
75
75
  :root {
76
- --safe-top: calc(env(safe-area-inset-top) + 4px);
76
+ --safe-top: calc(env(safe-area-inset-top));
77
77
  }
78
78
  }
79
79
 
@@ -82,7 +82,7 @@ a {
82
82
  -ms-overflow-style: none; /* Internet Explorer 10+ */
83
83
  scrollbar-width: none;
84
84
  transition: all 0.2s ease-in-out;
85
- padding-top: var(--safe-top);
85
+ /* padding-top: var(--safe-top); */
86
86
  box-sizing: border-box;
87
87
  }
88
88
 
@@ -1,8 +1,24 @@
1
1
  import { useCallback, useEffect, useState } from 'react';
2
2
 
3
+ import { useGlobalLoading } from '@/hooks';
3
4
  import { pluginManager } from '#/core/pluginManager';
4
5
  import { PluginID } from '#/types/plugin';
5
6
 
7
+ const usePluginsLoading = () => {
8
+ const { setGlobalLoading } = useGlobalLoading();
9
+
10
+ useEffect(() => {
11
+ let unregister: (() => void) | undefined;
12
+ const unscribe = pluginManager.subscribePluginLoading(payload => {
13
+ unregister = setGlobalLoading(payload.id, payload.loading);
14
+ });
15
+ return () => {
16
+ unscribe();
17
+ unregister?.();
18
+ };
19
+ }, [setGlobalLoading]);
20
+ };
21
+
6
22
  /**
7
23
  * 通用插件管理 hook
8
24
  * 根据传入的插件ID列表加载和卸载插件
@@ -15,6 +31,8 @@ import { PluginID } from '#/types/plugin';
15
31
  export const usePlugins = (pluginIds: Array<PluginID>) => {
16
32
  const [done, setDone] = useState(false);
17
33
 
34
+ usePluginsLoading();
35
+
18
36
  const loadAllPlugins = useCallback(async () => {
19
37
  try {
20
38
  await Promise.all(pluginIds.map(id => pluginManager.loadPlugin(id)));
@@ -2,19 +2,25 @@ import React from 'react';
2
2
 
3
3
  import { IHostContext } from '#/types/hostApi';
4
4
  import type { SlotName, HookPoint, IComPropsRegisteredToSlot } from '#/types/slots';
5
- import { IPlugin } from '#/types/plugin';
5
+ import { IPlugin, PluginEvents, PluginID } from '#/types/plugin';
6
6
  import { getStableDeviceId } from '@/utils/fingerprints';
7
7
 
8
8
  import { registry } from './registry';
9
+ import { createEventBus } from './utils/eventBus';
9
10
 
10
11
  const pluginModules = import.meta.glob('../plugins/*/index.ts');
11
12
 
12
13
  class PluginManager {
13
14
  private activePlugins: Map<string, IPlugin> = new Map();
14
15
 
16
+ private bus = createEventBus<PluginEvents>();
17
+
15
18
  private createHostContext(): IHostContext {
16
19
  return {
17
20
  api: {
21
+ setLoading: (id: PluginID, loading: boolean) => {
22
+ this.bus.emit('plugin:loading:changed', { id, loading });
23
+ },
18
24
  registerMiddleware: <T>(
19
25
  point: HookPoint,
20
26
  fn: (data: T) => Promise<T> | T,
@@ -38,8 +44,12 @@ class PluginManager {
38
44
 
39
45
  private loading = new Set<string>();
40
46
 
47
+ subscribePluginLoading(listener: (payload: PluginEvents['plugin:loading:changed']) => void) {
48
+ return this.bus.on('plugin:loading:changed', listener);
49
+ }
50
+
41
51
  // enable abort ctrl
42
- async loadPlugin(pluginId: string) {
52
+ async loadPlugin(pluginId: PluginID) {
43
53
  if (this.activePlugins.has(pluginId)) {
44
54
  console.warn(`Plugin ${pluginId} is already loaded.`);
45
55
  return;
@@ -52,11 +62,16 @@ class PluginManager {
52
62
 
53
63
  // try load plugin
54
64
  this.loading.add(pluginId);
65
+ this.bus.emit('plugin:loading:changed', { id: pluginId, loading: true });
55
66
 
56
67
  const modulePath = `../plugins/${pluginId}/index.ts`;
57
68
  const moduleLoader = pluginModules[modulePath];
58
69
  if (!moduleLoader) {
59
70
  console.error(`Plugin ${pluginId} not found in registry`);
71
+
72
+ this.loading.delete(pluginId);
73
+ this.bus.emit('plugin:loading:changed', { id: pluginId, loading: false });
74
+
60
75
  return;
61
76
  }
62
77
 
@@ -78,10 +93,11 @@ class PluginManager {
78
93
  console.error(`Failed to load plugin ${pluginId}:`, error);
79
94
  } finally {
80
95
  this.loading.delete(pluginId);
96
+ this.bus.emit('plugin:loading:changed', { id: pluginId, loading: false });
81
97
  }
82
98
  }
83
99
 
84
- async disablePlugin(pluginId: string) {
100
+ async disablePlugin(pluginId: PluginID) {
85
101
  const plugin = this.activePlugins.get(pluginId);
86
102
  if (!plugin) {
87
103
  console.warn(`Plugin ${pluginId} is not loaded.`);
@@ -2,6 +2,8 @@ import React from 'react';
2
2
 
3
3
  import type { SlotName, HookPoint, IComPropsRegisteredToSlot } from '#/types/slots';
4
4
 
5
+ import { createEventBus } from './utils/eventBus';
6
+
5
7
  export interface ISlotEntry {
6
8
  id: string;
7
9
  component: React.ComponentType<IComPropsRegisteredToSlot>;
@@ -16,37 +18,28 @@ export interface IMiddlewareEntry<T> {
16
18
  weight?: number;
17
19
  }
18
20
 
21
+ type RegistryEvents = {
22
+ 'components:changed': void;
23
+ 'middleware:changed': HookPoint;
24
+ };
25
+
19
26
  export class Registry {
20
27
  private slots = new Map<SlotName, ISlotEntry[]>();
21
28
 
22
- private listeners = new Set<() => void>();
23
- private middlewareListenerMap = new Map<HookPoint, Set<() => void>>();
29
+ private bus = createEventBus<RegistryEvents>();
24
30
 
25
31
  private middlewares = new Map<HookPoint, IMiddlewareEntry<unknown>[]>();
26
32
 
27
- private broadcastChange() {
28
- this.listeners.forEach(listener => listener());
29
- }
30
-
31
- private broadcastMiddlewareChange(point: HookPoint) {
32
- const listeners = this.middlewareListenerMap.get(point);
33
- if (listeners) {
34
- listeners.forEach(listener => listener());
35
- }
36
- }
37
-
38
33
  subscribeMiddleware(hookPoint: HookPoint, listener: () => void) {
39
- if (!this.middlewareListenerMap.has(hookPoint)) {
40
- this.middlewareListenerMap.set(hookPoint, new Set());
41
- }
42
- const listeners = this.middlewareListenerMap.get(hookPoint)!;
43
- listeners.add(listener);
44
- return () => listeners.delete(listener);
34
+ return this.bus.on('middleware:changed', point => {
35
+ if (point === hookPoint) {
36
+ listener();
37
+ }
38
+ });
45
39
  }
46
40
 
47
41
  subscribe(listener: () => void) {
48
- this.listeners.add(listener);
49
- return () => this.listeners.delete(listener);
42
+ return this.bus.on('components:changed', listener);
50
43
  }
51
44
 
52
45
  registerComponent(
@@ -68,7 +61,7 @@ export class Registry {
68
61
  const newList = [...existing, newEntry].sort((a, b) => b.weight - a.weight);
69
62
  this.slots.set(slot, newList);
70
63
 
71
- this.broadcastChange();
64
+ this.bus.emit('components:changed', undefined);
72
65
  }
73
66
 
74
67
  unregisterAllByPluginId(pluginId: string) {
@@ -86,7 +79,7 @@ export class Registry {
86
79
 
87
80
  console.log(`[Registry] All resources for plugin "${pluginId}" have been cleared.`);
88
81
 
89
- this.broadcastChange();
82
+ this.bus.emit('components:changed', undefined);
90
83
  }
91
84
 
92
85
  registerMiddleware<T>(
@@ -111,7 +104,7 @@ export class Registry {
111
104
 
112
105
  this.middlewares.set(hookPoint, newList);
113
106
 
114
- this.broadcastMiddlewareChange(hookPoint);
107
+ this.bus.emit('middleware:changed', hookPoint);
115
108
  }
116
109
 
117
110
  getComponents(slotName: SlotName): React.ComponentType<IComPropsRegisteredToSlot>[] {
@@ -0,0 +1,29 @@
1
+ export interface EventBus<Events extends Record<string, unknown>> {
2
+ on<K extends keyof Events>(event: K, callback: (data: Events[K]) => void): () => void;
3
+ emit<K extends keyof Events>(event: K, data: Events[K]): void;
4
+ }
5
+
6
+ export function createEventBus<Events extends Record<string, unknown>>(): EventBus<Events> {
7
+ const listeners = new Map<keyof Events, Set<(data: Events[keyof Events]) => void>>();
8
+
9
+ return {
10
+ on<K extends keyof Events>(event: K, callback: (data: Events[K]) => void): () => void {
11
+ if (!listeners.has(event)) {
12
+ listeners.set(event, new Set());
13
+ }
14
+ const set = listeners.get(event)!;
15
+ const cb = callback as (data: Events[keyof Events]) => void;
16
+ set.add(cb);
17
+
18
+ return () => {
19
+ set.delete(cb);
20
+ };
21
+ },
22
+
23
+ emit<K extends keyof Events>(event: K, data: Events[K]): void {
24
+ const set = listeners.get(event);
25
+ if (!set) return;
26
+ set.forEach(callback => callback(data as Events[keyof Events]));
27
+ },
28
+ };
29
+ }
@@ -75,11 +75,11 @@ class XwyPlugin implements IPlugin {
75
75
  return content
76
76
  .replace(
77
77
  /小乌鸦/g,
78
- `<span class="font-xwy text-content-special text-[1.6rem] align-middle leading-[28px]">小乌鸦</span>`
78
+ `<span class="font-xwy text-content-special text-[1.6rem] align-middle leading-[1.09]">小乌鸦</span>`
79
79
  )
80
80
  .replace(
81
81
  /公园/,
82
- `公园 <span class="font-xwy-icon text-[#679867] text-[1.6rem] align-middle leading-[28px]">&#xE01A&#xE003</span>`
82
+ `公园 <span class="font-xwy-icon text-[#679867] text-[1.6rem] align-middle leading-[1.09]">&#xE01A&#xE003</span>`
83
83
  );
84
84
  };
85
85
 
@@ -3,9 +3,11 @@ import React from 'react';
3
3
  import { type FingerprintData } from '@/utils/fingerprints';
4
4
 
5
5
  import { HookPoint, IComPropsRegisteredToSlot, SlotName } from './slots';
6
+ import { PluginID } from './plugin';
6
7
 
7
8
  export interface IHostApi {
8
9
  getDeviceId: () => Promise<{ id: string; fp: FingerprintData }>;
10
+ setLoading: (id: PluginID, loading: boolean) => void;
9
11
  registerMiddleware: <T>(
10
12
  point: HookPoint,
11
13
  fn: (baseData: T) => T,
@@ -23,3 +23,12 @@ export interface IPluginEntry {
23
23
  label?: string;
24
24
  pageComponent: React.ComponentType<IComPropsRegisteredToSlot>;
25
25
  }
26
+
27
+ export type PluginEvents = {
28
+ 'plugin:loading:changed': IPluginLoadingPayload;
29
+ };
30
+
31
+ export interface IPluginLoadingPayload {
32
+ id: PluginID;
33
+ loading: boolean;
34
+ }