@bbki.ng/site 5.6.7 → 5.8.0

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 (84) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/index.html +1 -12
  3. package/package.json +1 -1
  4. package/src/app/app.tsx +46 -0
  5. package/src/{blog → app}/components/BaseLayout.tsx +2 -2
  6. package/src/{blog/pages → app/components}/cover/index.tsx +12 -13
  7. package/src/app/constants/index.ts +1 -0
  8. package/src/{blog → app}/context/bbcontext.tsx +2 -2
  9. package/src/{blog/hooks/use_loading.ts → app/hooks/use_global_loading.ts} +1 -1
  10. package/src/{blog → app}/hooks/use_paths.ts +14 -12
  11. package/src/{blog → app}/hooks/use_plugin_entries.ts +1 -3
  12. package/src/{blog → app}/index.tsx +2 -2
  13. package/src/{blog → app}/main.css +3 -3
  14. package/src/{blog → app}/swr.tsx +1 -1
  15. package/src/{blog → app}/utils/index.ts +1 -1
  16. package/src/core/components/SlotComp.tsx +2 -3
  17. package/src/core/hooks/useMiddlewareTransData.ts +12 -7
  18. package/src/core/hooks/useSlotComp.ts +8 -7
  19. package/src/core/hooks/use_plugins.ts +28 -15
  20. package/src/core/{pluginManager.ts → plugin-system/pluginManager.ts} +26 -43
  21. package/src/core/{pluginManifestService.ts → plugin-system/pluginManifestService.ts} +4 -4
  22. package/src/core/plugin-system/pluginStore.ts +190 -0
  23. package/src/core/{registry.ts → plugin-system/registry.ts} +1 -3
  24. package/src/core/plugin-system/services/coreService.ts +56 -0
  25. package/src/core/plugin-system/services/systemUIService.ts +159 -0
  26. package/src/core/shared-service/contract/ICoreService.ts +23 -0
  27. package/src/core/shared-service/contract/IUIService.ts +49 -0
  28. package/src/core/shared-service/service-proxy.ts +26 -0
  29. package/src/core/shared-service/service-registry.ts +52 -0
  30. package/src/index.tsx +3 -3
  31. package/src/plugins/blog/components/BlogSlotCom.tsx +27 -0
  32. package/src/plugins/blog/components/app.tsx +15 -0
  33. package/src/{blog → plugins/blog}/components/article/index.tsx +2 -2
  34. package/src/plugins/blog/constants/index.ts +1 -0
  35. package/src/plugins/blog/context/index.ts +7 -0
  36. package/src/plugins/blog/hooks/useMiddlewareTransData.ts +79 -0
  37. package/src/{blog → plugins/blog}/hooks/use_blog_scroll_pos_restoration.ts +4 -6
  38. package/src/plugins/blog/hooks/use_blog_slot_com.ts +27 -0
  39. package/src/{blog → plugins/blog}/hooks/use_posts.ts +11 -11
  40. package/src/plugins/blog/index.ts +61 -0
  41. package/src/{blog → plugins/blog}/pages/extensions/txt/article.tsx +5 -5
  42. package/src/{blog → plugins/blog}/pages/extensions/txt/index.tsx +6 -3
  43. package/src/plugins/blog/services/BlogUIService.ts +120 -0
  44. package/src/plugins/blog/services/IBlogUIService.ts +31 -0
  45. package/src/plugins/extra-cd/index.ts +13 -2
  46. package/src/plugins/extra-entry/index.ts +8 -4
  47. package/src/plugins/fx/index.ts +18 -5
  48. package/src/plugins/manifest.ts +8 -6
  49. package/src/plugins/now/hooks/use_streaming.ts +3 -1
  50. package/src/plugins/now/index.ts +17 -6
  51. package/src/plugins/sticker/const.ts +2 -2
  52. package/src/plugins/sticker/index.ts +18 -3
  53. package/src/plugins/store/components/storePage.tsx +15 -4
  54. package/src/plugins/store/context/index.ts +1 -0
  55. package/src/plugins/store/index.ts +18 -7
  56. package/src/plugins/xwy/index.ts +24 -19
  57. package/src/plugins/xwy/types/index.ts +0 -18
  58. package/src/types/hostApi.ts +3 -33
  59. package/src/types/plugin.ts +2 -0
  60. package/src/utils/index.tsx +12 -44
  61. package/tsconfig.json +0 -1
  62. package/vite.config.js +1 -1
  63. package/src/blog/app.tsx +0 -52
  64. package/src/blog/constants/index.ts +0 -1
  65. package/src/blog/constants/routes.ts +0 -20
  66. package/src/blog/hooks/index.ts +0 -2
  67. package/src/blog/hooks/use_blog_context.ts +0 -14
  68. package/src/blog/hooks/use_mouse_position.ts +0 -17
  69. package/src/blog/hooks/use_role.ts +0 -14
  70. package/src/blog/hooks/use_sticker.ts +0 -11
  71. package/src/blog/pages/index.tsx +0 -2
  72. package/src/blog/pages/login/index.tsx +0 -24
  73. package/src/blog/types/blog-context.ts +0 -6
  74. package/src/blog/types/font.ts +0 -4
  75. package/src/blog/types/glsl.d.ts +0 -8
  76. package/src/core/pluginStore.ts +0 -70
  77. /package/src/{blog/pages → app/components}/bot/index.tsx +0 -0
  78. /package/src/{blog → app}/context/global_loading_state_provider.tsx +0 -0
  79. /package/src/{blog → app}/context/global_routes_provider.tsx +0 -0
  80. /package/src/{blog → app}/hooks/use_pathname.ts +0 -0
  81. /package/src/{blog → app}/utils/fingerprints.ts +0 -0
  82. /package/src/core/{bbplugin.ts → plugin-system/bbplugin.ts} +0 -0
  83. /package/src/{blog → plugins/blog}/components/index.tsx +0 -0
  84. /package/src/{blog/types → types}/path.ts +0 -0
@@ -0,0 +1,79 @@
1
+ import { useState, useEffect, useCallback, useRef } from 'react';
2
+
3
+ import { BlogUIService } from '../services/BlogUIService';
4
+ import { BlogDataHookPoint } from '../services/IBlogUIService';
5
+
6
+ export interface UseMiddlewareTransDataOptions {
7
+ hookPoint: BlogDataHookPoint;
8
+ onMiddlewareChange?: () => void; // 通知外部,由外部决定是否 run
9
+ }
10
+
11
+ export interface UseMiddlewareTransDataResult<T> {
12
+ loading: boolean;
13
+ error: Error | null;
14
+ run: (inputData: T) => Promise<T>;
15
+ }
16
+
17
+ export function useMiddlewareRunner<T>({
18
+ hookPoint,
19
+ onMiddlewareChange,
20
+ }: UseMiddlewareTransDataOptions): UseMiddlewareTransDataResult<T> {
21
+ const [loading, setLoading] = useState(false);
22
+ const [error, setError] = useState<Error | null>(null);
23
+
24
+ const run = useCallback(
25
+ async (inputData: T) => {
26
+ setLoading(true);
27
+ setError(null);
28
+ try {
29
+ const result = await BlogUIService.getInstance().runMiddlewares(hookPoint, inputData);
30
+ return result;
31
+ } catch (err) {
32
+ const error = err instanceof Error ? err : new Error(String(err));
33
+ setError(error);
34
+ throw error;
35
+ } finally {
36
+ setLoading(false);
37
+ }
38
+ },
39
+ [hookPoint]
40
+ );
41
+
42
+ // 仅通知,不执行
43
+ useEffect(() => {
44
+ const unsubscribe = BlogUIService.getInstance().subscribeMiddlewareChange(hook => {
45
+ if (hook === hookPoint) {
46
+ onMiddlewareChange?.();
47
+ }
48
+ });
49
+
50
+ return () => {
51
+ unsubscribe();
52
+ };
53
+ }, [hookPoint, onMiddlewareChange]);
54
+
55
+ return { loading, error, run };
56
+ }
57
+
58
+ export const useMiddlewareTransformedData = <T>(hookPoint: BlogDataHookPoint, defaultValue: T) => {
59
+ const [result, setResult] = useState<T>(defaultValue);
60
+
61
+ const runRef = useRef<(input: T) => Promise<T>>(() => Promise.resolve(defaultValue));
62
+
63
+ const onMiddlewareChange = useCallback(() => {
64
+ runRef.current(defaultValue).then(setResult);
65
+ }, [defaultValue]);
66
+
67
+ const { run } = useMiddlewareRunner<T>({
68
+ hookPoint,
69
+ onMiddlewareChange,
70
+ });
71
+
72
+ runRef.current = run;
73
+
74
+ useEffect(() => {
75
+ run(defaultValue).then(setResult);
76
+ }, [defaultValue, run]);
77
+
78
+ return result;
79
+ };
@@ -1,5 +1,4 @@
1
1
  import { useCallback, useEffect, useRef } from 'react';
2
- import { useLocation } from 'react-router-dom';
3
2
 
4
3
  const SCROLL_STORAGE_KEY = 'div-scroll-positions';
5
4
 
@@ -16,7 +15,7 @@ function saveScrollPosition(key: string, position: number) {
16
15
 
17
16
  export function useBlogScrollReset() {
18
17
  useEffect(() => {
19
- const element = document.getElementById('blog');
18
+ const element = document.getElementById('app');
20
19
  if (!element) return;
21
20
 
22
21
  element.scrollTop = 0;
@@ -24,7 +23,7 @@ export function useBlogScrollReset() {
24
23
  }
25
24
 
26
25
  export function useBlogScroll() {
27
- const element = document.getElementById('blog');
26
+ const element = document.getElementById('app');
28
27
 
29
28
  const gotoTop = useCallback(() => {
30
29
  if (!element) return;
@@ -42,7 +41,6 @@ export function useBlogScroll() {
42
41
  }
43
42
 
44
43
  export function useBlogScrollRestoration(debounceMs: number = 100) {
45
- const location = useLocation();
46
44
  const isFirstRender = useRef(true);
47
45
  const scrollTimeoutRef = useRef<number>();
48
46
  const element = document.getElementById('blog');
@@ -62,7 +60,7 @@ export function useBlogScrollRestoration(debounceMs: number = 100) {
62
60
  element.scrollTop = savedPosition;
63
61
  });
64
62
  isFirstRender.current = false;
65
- }, []);
63
+ }, [element, scrollKey]);
66
64
 
67
65
  // Save scroll position with debouncing
68
66
  useEffect(() => {
@@ -87,5 +85,5 @@ export function useBlogScrollRestoration(debounceMs: number = 100) {
87
85
  return () => {
88
86
  element.removeEventListener('scroll', handleScroll);
89
87
  };
90
- }, []);
88
+ }, [debounceMs, element, scrollKey]);
91
89
  }
@@ -0,0 +1,27 @@
1
+ import React, { useEffect, useState } from 'react';
2
+
3
+ import { IComPropsRegisteredToSlot } from '#/types/slots';
4
+
5
+ import { BlogSlotName } from '../services/IBlogUIService';
6
+ import { BlogUIService } from '../services/BlogUIService';
7
+
8
+ export const useBlogSlotComp = (slotName: BlogSlotName) => {
9
+ const [components, setComponents] = useState<React.ComponentType<IComPropsRegisteredToSlot>[]>(
10
+ () => BlogUIService.getInstance().getComponents(slotName)
11
+ );
12
+
13
+ useEffect(() => {
14
+ // setComponents(BlogUIService.getInstance().getComponents(slotName));
15
+
16
+ const unsubscribe = BlogUIService.getInstance().subscribeSlotChange(() => {
17
+ const comps = BlogUIService.getInstance().getComponents(slotName);
18
+ setComponents(comps);
19
+ });
20
+
21
+ return () => {
22
+ unsubscribe();
23
+ };
24
+ }, [slotName]);
25
+
26
+ return components;
27
+ };
@@ -1,14 +1,11 @@
1
1
  import useSWR from 'swr';
2
2
  import { useEffect, useMemo, useState } from 'react';
3
3
 
4
- import { useGlobalLoading } from '@/hooks';
5
- import { baseFetcher } from '@/utils';
6
- import { API_ENDPOINT } from '@/constants/routes';
7
4
  import { IPost } from '#/types/posts';
8
- import { useMiddlewareRunner } from '#/core/hooks/useMiddlewareTransData';
9
5
 
10
- const isProd = true;
11
- const POSTS_API = !isProd ? '/api/posts' : `${API_ENDPOINT}/posts`;
6
+ import { BlogContext } from '../context';
7
+
8
+ import { useMiddlewareRunner } from './useMiddlewareTransData';
12
9
 
13
10
  interface PostsApiResponse {
14
11
  data: IPost[];
@@ -23,11 +20,13 @@ export interface TitleListItem {
23
20
  }
24
21
 
25
22
  export const usePosts = (name: string = '', suspense?: boolean) => {
26
- const { data: response, error: swrError } = useSWR<PostsApiResponse>(POSTS_API, baseFetcher, {
23
+ const { data: response, error: swrError } = useSWR<PostsApiResponse>('posts', {
27
24
  revalidateOnFocus: false,
28
25
  suspense,
29
26
  });
30
27
 
28
+ const { setLoading } = BlogContext.useCtx();
29
+
31
30
  const data = response?.data;
32
31
  const isDataLoading = !data && !swrError;
33
32
 
@@ -43,13 +42,12 @@ export const usePosts = (name: string = '', suspense?: boolean) => {
43
42
 
44
43
  const [fullTitleList, setFullTitleList] = useState<TitleListItem[]>(baseTitleList);
45
44
 
46
- // Use middleware hook to transform title list
47
45
  const {
48
46
  loading: isTransforming,
49
47
  error: transformError,
50
48
  run,
51
49
  } = useMiddlewareRunner<TitleListItem[]>({
52
- hookPoint: 'transformTitleList',
50
+ hookPoint: 'blog:transformTitleList',
53
51
  });
54
52
 
55
53
  useEffect(() => {
@@ -58,7 +56,9 @@ export const usePosts = (name: string = '', suspense?: boolean) => {
58
56
  }
59
57
  }, [baseTitleList, run]);
60
58
 
61
- const { isLoading: gLoading } = useGlobalLoading('posts', isDataLoading || isTransforming);
59
+ useEffect(() => {
60
+ setLoading(isDataLoading || isTransforming);
61
+ }, [isDataLoading, isTransforming, setLoading]);
62
62
 
63
63
  const posts =
64
64
  isDataLoading || name === '' || swrError || !data
@@ -69,6 +69,6 @@ export const usePosts = (name: string = '', suspense?: boolean) => {
69
69
  posts,
70
70
  titleList: fullTitleList ?? [],
71
71
  isError: swrError || transformError,
72
- isLoading: gLoading,
72
+ isLoading: isDataLoading || isTransforming,
73
73
  };
74
74
  };
@@ -0,0 +1,61 @@
1
+ import { BBPlugin } from '#/core/plugin-system/bbplugin';
2
+ import { SystemUIService } from '#/core/plugin-system/services/systemUIService';
3
+ import { ServiceRegistry } from '#/core/shared-service/service-registry';
4
+ import { IHostContext } from '#/types/hostApi';
5
+ import { PluginID } from '#/types/plugin';
6
+
7
+ import { BlogPageApp } from './components/app';
8
+ import { BlogContext } from './context';
9
+ import { BlogUIService } from './services/BlogUIService';
10
+
11
+ const { withCtx } = BlogContext;
12
+
13
+ export class BlogPlugin extends BBPlugin {
14
+ id: PluginID = 'blog';
15
+
16
+ private _serviceRegistry?: ServiceRegistry;
17
+
18
+ override onInstall = async (ctx: IHostContext) => {
19
+ ctx.service.register('blog:uiService', BlogUIService.getInstance());
20
+
21
+ this._serviceRegistry = ctx.service;
22
+
23
+ const coreService = ctx.service.get('core:baseService');
24
+ const systemUIService = ctx.service.get('core:uiService');
25
+
26
+ if (!coreService) {
27
+ console.error('Core service not found. BlogPlugin installation failed.');
28
+ return;
29
+ }
30
+
31
+ coreService.subscribePluginUninstall(this.handlePluginUninstall);
32
+
33
+ systemUIService?.registerPluginEntry(
34
+ {
35
+ path: '/blog',
36
+ label: 'cd ./blog',
37
+ pageComponent: withCtx(
38
+ {
39
+ setLoading: loading => {
40
+ coreService.setLoading('blog', loading);
41
+ },
42
+ },
43
+ BlogPageApp
44
+ ),
45
+ },
46
+ this.id
47
+ );
48
+ };
49
+
50
+ override onDestroy?: (() => void) | undefined = () => {
51
+ BlogUIService.getInstance().unregisterAllByPluginId(this.id);
52
+ this._serviceRegistry?.unregister('blog:uiService');
53
+ };
54
+
55
+ private handlePluginUninstall = (payload: PluginID) => {
56
+ BlogUIService.getInstance().unregisterAllByPluginId(payload);
57
+ SystemUIService.getInstance().unregisterAllByPluginId(payload);
58
+ };
59
+ }
60
+
61
+ export default new BlogPlugin();
@@ -2,10 +2,10 @@ import React from 'react';
2
2
  import { NotFound } from '@bbki.ng/ui';
3
3
  import { useParams } from 'react-router-dom';
4
4
 
5
- import { usePosts } from '@/hooks/use_posts';
6
- import { ArticlePage } from '@/components/article';
7
- import { useBlogScrollReset } from '@/hooks/use_blog_scroll_pos_restoration';
8
- import { useMiddlewareTransformedData } from '#/core/hooks/useMiddlewareTransData';
5
+ import { usePosts } from '#/plugins/blog/hooks/use_posts';
6
+ import { ArticlePage } from '#/plugins/blog/components/article';
7
+ import { useBlogScrollReset } from '#/plugins/blog/hooks/use_blog_scroll_pos_restoration';
8
+ import { useMiddlewareTransformedData } from '#/plugins/blog/hooks/useMiddlewareTransData';
9
9
  import { IPost } from '#/types/posts';
10
10
 
11
11
  function TxtArticle() {
@@ -16,7 +16,7 @@ function TxtArticle() {
16
16
 
17
17
  const p = posts as IPost;
18
18
 
19
- const transformedContent = useMiddlewareTransformedData('transformPostContent', p?.content);
19
+ const transformedContent = useMiddlewareTransformedData('blog:transformPostContent', p?.content);
20
20
 
21
21
  if (!title) {
22
22
  return <NotFound />;
@@ -1,9 +1,12 @@
1
1
  import React from 'react';
2
2
  import { LinkProps, Button } from '@bbki.ng/ui';
3
3
 
4
- import { usePosts } from '@/hooks/use_posts';
5
- import { CenterLinkList } from '@/components';
6
- import { useBlogScroll, useBlogScrollRestoration } from '@/hooks/use_blog_scroll_pos_restoration';
4
+ import { usePosts } from '#/plugins/blog/hooks/use_posts';
5
+ import { CenterLinkList } from '#/plugins/blog/components';
6
+ import {
7
+ useBlogScroll,
8
+ useBlogScrollRestoration,
9
+ } from '#/plugins/blog/hooks/use_blog_scroll_pos_restoration';
7
10
 
8
11
  type TxtProps = {
9
12
  title?: string;
@@ -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
@@ -26,6 +26,7 @@ export const FALLBACK_MANIFEST: Array<IPluginManifestEntry> = [
26
26
  version: '0.1.0',
27
27
  description:
28
28
  '提供额外的页面跳转链接。例如在标题列表末尾添加一个 "cd ~" 的选项,点击后跳转到主页',
29
+ dependencies: ['blog'],
29
30
  },
30
31
  {
31
32
  name: '特效',
@@ -33,16 +34,17 @@ export const FALLBACK_MANIFEST: Array<IPluginManifestEntry> = [
33
34
  version: '0.1.0',
34
35
  description: '提供一些额外的视觉效果。例如版本、设备信息水印,加载状态螺旋线、背景纹理等',
35
36
  },
36
- // {
37
- // name: 'extra-entry',
38
- // id: 'extra-entry',
39
- // version: '0.1.0',
40
- // description: 'Provides extra entries for cover and extended routes.',
41
- // },
42
37
  {
43
38
  name: '小乌鸦',
44
39
  id: 'xwy',
45
40
  version: '0.1.0',
46
41
  description: '为小乌鸦合集实现定制功能或者样式的插件',
42
+ dependencies: ['blog'],
43
+ },
44
+ {
45
+ name: '博客',
46
+ id: 'blog',
47
+ version: '0.1.0',
48
+ description: '一个简单的博客系统,支持 Markdown 格式的文章',
47
49
  },
48
50
  ];
@@ -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,