@bbki.ng/site 5.6.7 → 5.7.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 (53) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/index.html +1 -1
  3. package/package.json +1 -1
  4. package/src/{blog → app}/app.tsx +12 -17
  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/hooks/use_plugins.ts +1 -1
  17. package/src/core/pluginManager.ts +64 -19
  18. package/src/core/pluginManifestService.ts +2 -1
  19. package/src/index.tsx +3 -3
  20. package/src/plugins/blog/components/app.tsx +15 -0
  21. package/src/plugins/blog/constants/index.ts +1 -0
  22. package/src/plugins/blog/context/index.ts +11 -0
  23. package/src/{blog → plugins/blog}/hooks/use_blog_scroll_pos_restoration.ts +2 -4
  24. package/src/{blog → plugins/blog}/hooks/use_posts.ts +8 -9
  25. package/src/plugins/blog/index.ts +34 -0
  26. package/src/{blog → plugins/blog}/pages/extensions/txt/article.tsx +3 -3
  27. package/src/{blog → plugins/blog}/pages/extensions/txt/index.tsx +6 -3
  28. package/src/plugins/manifest.ts +6 -0
  29. package/src/plugins/store/components/storePage.tsx +2 -2
  30. package/src/types/hostApi.ts +3 -2
  31. package/src/types/plugin.ts +1 -0
  32. package/src/utils/index.tsx +15 -0
  33. package/tsconfig.json +0 -1
  34. package/src/blog/constants/index.ts +0 -1
  35. package/src/blog/constants/routes.ts +0 -20
  36. package/src/blog/hooks/index.ts +0 -2
  37. package/src/blog/hooks/use_blog_context.ts +0 -14
  38. package/src/blog/hooks/use_mouse_position.ts +0 -17
  39. package/src/blog/hooks/use_role.ts +0 -14
  40. package/src/blog/hooks/use_sticker.ts +0 -11
  41. package/src/blog/pages/index.tsx +0 -2
  42. package/src/blog/pages/login/index.tsx +0 -24
  43. package/src/blog/types/blog-context.ts +0 -6
  44. package/src/blog/types/font.ts +0 -4
  45. package/src/blog/types/glsl.d.ts +0 -8
  46. /package/src/{blog/pages → app/components}/bot/index.tsx +0 -0
  47. /package/src/{blog → app}/context/global_loading_state_provider.tsx +0 -0
  48. /package/src/{blog → app}/context/global_routes_provider.tsx +0 -0
  49. /package/src/{blog → app}/hooks/use_pathname.ts +0 -0
  50. /package/src/{blog → app}/utils/fingerprints.ts +0 -0
  51. /package/src/{blog → plugins/blog}/components/article/index.tsx +0 -0
  52. /package/src/{blog → plugins/blog}/components/index.tsx +0 -0
  53. /package/src/{blog/types → types}/path.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @bbki.ng/site
2
2
 
3
+ ## 5.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 58170e5: blog as a plugin
8
+
3
9
  ## 5.6.7
4
10
 
5
11
  ### Patch Changes
package/index.html CHANGED
@@ -45,7 +45,7 @@
45
45
  </head>
46
46
 
47
47
  <body class="h-full m-0 flex flex-col">
48
- <div id="blog" class="grow no-scrollbar font-mono"></div>
48
+ <div id="app" class="grow no-scrollbar font-mono"></div>
49
49
  <script type="module" src="/src/index.tsx"></script>
50
50
  </body>
51
51
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbki.ng/site",
3
- "version": "5.6.7",
3
+ "version": "5.7.0",
4
4
  "description": "code behind bbki.ng",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -1,18 +1,15 @@
1
1
  import React from 'react';
2
- import { Outlet, Route, Routes } from 'react-router-dom';
2
+ import { Route, Routes } from 'react-router-dom';
3
3
  import { NotFound } from '@bbki.ng/ui';
4
4
 
5
- import ArticlePage from '@/pages/extensions/txt/article';
6
- import Txt from '@/pages/extensions/txt';
7
- import { BotRedirect } from '@/pages/bot';
8
- import { BBContext } from '@/context/bbcontext';
5
+ import { BotRedirect } from '#/app/components/bot';
6
+ import { BBContext } from '#/app/context/bbcontext';
9
7
  import { Slot } from '#/core/components/SlotComp';
10
8
  import { usePlugins } from '#/core/hooks/use_plugins';
11
- import { SWR } from '@/swr';
12
-
13
- import { Cover } from './pages';
14
- import { usePluginEntries } from './hooks/use_plugin_entries';
15
- import { BaseLayout } from './components/BaseLayout';
9
+ import { Cover } from '#/app/components/cover';
10
+ import { SWR } from '#/app/swr';
11
+ import { usePluginEntries } from '#/app/hooks/use_plugin_entries';
12
+ import { BaseLayout } from '#/app/components/BaseLayout';
16
13
 
17
14
  const AppRoutes = () => {
18
15
  usePlugins();
@@ -23,15 +20,13 @@ const AppRoutes = () => {
23
20
  <Routes>
24
21
  <Route path="/" element={<BaseLayout />}>
25
22
  <Route index element={<Cover />} />
26
-
27
- <Route path="blog" element={<Outlet />}>
28
- <Route path="" element={<Txt />} />
29
- <Route path=":title" element={<ArticlePage />} />
30
- </Route>
31
-
32
23
  <Route path="bot" element={<BotRedirect />} />
33
24
  {pluginEntries?.map(route => (
34
- <Route key={route.path} path={route.path} element={<Slot name="route" data={route} />} />
25
+ <Route
26
+ key={route.path}
27
+ path={`${route.path}/*`}
28
+ element={<Slot name="route" data={route} />}
29
+ />
35
30
  ))}
36
31
  <Route path="*" element={pluginEntries?.length ? <NotFound /> : null} />
37
32
  </Route>
@@ -3,8 +3,8 @@ import { useNavigate, Outlet } from 'react-router-dom';
3
3
  import { Logo, Nav, Page, Grid, ErrorBoundary, Container } from '@bbki.ng/ui';
4
4
 
5
5
  import { Slot } from '#/core/components/SlotComp';
6
- import { usePaths } from '@/hooks';
7
- import { GlobalLoadingContext } from '@/context/global_loading_state_provider';
6
+ import { usePaths } from '#/app/hooks/use_paths';
7
+ import { GlobalLoadingContext } from '#/app/context/global_loading_state_provider';
8
8
  import { useMiddlewareTransformedData } from '#/core/hooks/useMiddlewareTransData';
9
9
 
10
10
  export const BaseLayout = () => {
@@ -1,20 +1,19 @@
1
- import React, { useEffect, useMemo } from 'react';
2
- import { LinkProps } from '@bbki.ng/ui';
1
+ import React, { useEffect } from 'react';
2
+ import { LinkProps, LinkListProps, LinkList } from '@bbki.ng/ui';
3
3
 
4
- import { CenterLinkList } from '@/components';
5
4
  import { useMiddlewareRunner } from '#/core/hooks';
6
5
 
7
- export const Cover = (_: { className?: string }) => {
8
- const baseEntries: Array<LinkProps> = useMemo(
9
- () => [
10
- {
11
- to: '/blog',
12
- children: 'cd ./blog',
13
- },
14
- ],
15
- []
6
+ const CenterLinkList = (props: LinkListProps) => {
7
+ return (
8
+ <div className="flex justify-center relative h-full">
9
+ <LinkList {...props} />
10
+ </div>
16
11
  );
12
+ };
17
13
 
14
+ const baseEntries: Array<LinkProps> = [];
15
+
16
+ export const Cover = (_: { className?: string }) => {
18
17
  const [entries, setEntries] = React.useState<Array<LinkProps>>(baseEntries);
19
18
 
20
19
  const { run } = useMiddlewareRunner<Array<LinkProps>>({
@@ -26,7 +25,7 @@ export const Cover = (_: { className?: string }) => {
26
25
 
27
26
  useEffect(() => {
28
27
  run(baseEntries).then(setEntries);
29
- }, [baseEntries, run]);
28
+ }, [run]);
30
29
 
31
30
  return (
32
31
  <>
@@ -0,0 +1 @@
1
+ export const API_ENDPOINT = 'https://cf.bbki.ng';
@@ -1,7 +1,7 @@
1
1
  import React, { ReactNode } from 'react';
2
2
 
3
- import { GlobalLoadingStateProvider } from '@/context/global_loading_state_provider';
4
- import { GlobalRoutesProvider } from '@/context/global_routes_provider';
3
+ import { GlobalLoadingStateProvider } from '#/app/context/global_loading_state_provider';
4
+ import { GlobalRoutesProvider } from '#/app/context/global_routes_provider';
5
5
 
6
6
  export const BBContext = (props: { children: ReactNode }) => {
7
7
  return (
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useContext, useEffect } from 'react';
2
2
 
3
- import { GlobalLoadingContext } from '@/context/global_loading_state_provider';
3
+ import { GlobalLoadingContext } from '#/app/context/global_loading_state_provider';
4
4
 
5
5
  /**
6
6
  * Hook to register a loading state with the global loading context.
@@ -1,21 +1,23 @@
1
- import { useLocation } from 'react-router-dom';
2
1
  import { useMemo } from 'react';
3
2
 
4
- import { usePathName } from '@/hooks/use_pathname';
5
- import { pathObj } from '@/types/path';
3
+ import { usePathName } from '#/app/hooks/use_pathname';
4
+ import { pathObj } from '#/types/path';
6
5
 
7
6
  export const usePaths = (): pathObj[] => {
8
7
  const pathname = usePathName();
9
- const { pathname: locationPathname } = useLocation();
10
8
 
11
- const pathNameArr = pathname.split('/');
9
+ const pathNameArr = useMemo(() => pathname.split('/'), [pathname]);
12
10
 
13
- const pathsArr: string[] = pathNameArr.map((p: string, index: number) => {
14
- return pathNameArr
15
- .slice(0, index + 1)
16
- .join('/')
17
- .replace(/^$/, '/');
18
- });
11
+ const pathsArr: string[] = useMemo(
12
+ () =>
13
+ pathNameArr.map((p: string, index: number) => {
14
+ return pathNameArr
15
+ .slice(0, index + 1)
16
+ .join('/')
17
+ .replace(/^$/, '/');
18
+ }),
19
+ [pathNameArr]
20
+ );
19
21
 
20
22
  const result = useMemo(() => {
21
23
  return pathsArr.map((path, index) => {
@@ -28,7 +30,7 @@ export const usePaths = (): pathObj[] => {
28
30
  path,
29
31
  };
30
32
  });
31
- }, [locationPathname]);
33
+ }, [pathNameArr, pathsArr]);
32
34
 
33
35
  if (pathname === '/') {
34
36
  return [{ name: '~' }];
@@ -3,7 +3,7 @@ import { type PathRouteProps } from 'react-router-dom';
3
3
 
4
4
  import { useMiddlewareRunner } from '#/core/hooks';
5
5
 
6
- const protectedRoutesSet = new Set<string>(['/bot', '/', '/blog', '/blog/:title', 'default']);
6
+ const protectedRoutesSet = new Set<string>(['/bot', '/', 'default']);
7
7
 
8
8
  type PluginRoute = Omit<PathRouteProps, 'element'>;
9
9
 
@@ -35,7 +35,5 @@ export const usePluginEntries = () => {
35
35
  return false;
36
36
  }) as Array<PluginRoute>;
37
37
 
38
- console.log('safe plugin routes:', safeRoutes);
39
-
40
38
  return safeRoutes ?? [];
41
39
  };
@@ -2,10 +2,10 @@ import React from 'react';
2
2
  import { createRoot } from 'react-dom/client';
3
3
  import { BrowserRouter as Router } from 'react-router-dom';
4
4
 
5
- import App from './app';
5
+ import { App } from './app';
6
6
  import './main.css';
7
7
 
8
- export const RenderBlogInto = (ele: Element) => {
8
+ export const RenderApp = (ele: Element) => {
9
9
  const root = createRoot(ele);
10
10
  root.render(
11
11
  <React.StrictMode>
@@ -58,7 +58,7 @@ body {
58
58
  }
59
59
 
60
60
  body,
61
- #blog {
61
+ #app {
62
62
  height: 100lvh;
63
63
  }
64
64
 
@@ -77,7 +77,7 @@ a {
77
77
  }
78
78
  }
79
79
 
80
- #blog {
80
+ #app {
81
81
  overflow: auto;
82
82
  -ms-overflow-style: none; /* Internet Explorer 10+ */
83
83
  scrollbar-width: none;
@@ -86,7 +86,7 @@ a {
86
86
  box-sizing: border-box;
87
87
  }
88
88
 
89
- #blog::-webkit-scrollbar {
89
+ #app::-webkit-scrollbar {
90
90
  display: none;
91
91
  }
92
92
 
@@ -1,7 +1,7 @@
1
1
  import React, { ReactNode } from 'react';
2
2
  import { SWRConfig } from 'swr';
3
3
 
4
- import { cfApiFetcher } from '@/utils';
4
+ import { cfApiFetcher } from '#/app/utils';
5
5
 
6
6
  export const SWR = (props: { children: ReactNode }) => {
7
7
  return (
@@ -1,4 +1,4 @@
1
- import { API_ENDPOINT } from '@/constants/routes';
1
+ import { API_ENDPOINT } from '#/app/constants';
2
2
 
3
3
  import { getStableDeviceId } from './fingerprints';
4
4
 
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useMemo, useState } from 'react';
2
2
 
3
- import { useGlobalLoading } from '@/hooks';
3
+ import { useGlobalLoading } from '#/app/hooks/use_global_loading';
4
4
  import { pluginManager } from '#/core/pluginManager';
5
5
  import { PluginID } from '#/types/plugin';
6
6
 
@@ -1,10 +1,13 @@
1
1
  import React from 'react';
2
+ import { PathRouteProps } from 'react-router-dom';
3
+ import { LinkProps } from '@bbki.ng/ui';
2
4
 
3
5
  import { IHostContext } from '#/types/hostApi';
4
6
  import type { SlotName, HookPoint, IComPropsRegisteredToSlot } from '#/types/slots';
5
- import { IPlugin, PluginEvents, PluginID, PluginPerm } from '#/types/plugin';
6
- import { getStableDeviceId } from '@/utils/fingerprints';
7
- import { cfApiFetcher } from '@/utils';
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';
8
11
 
9
12
  import { registry } from './registry';
10
13
  import { createEventBus } from './utils/eventBus';
@@ -18,6 +21,59 @@ class PluginManager {
18
21
 
19
22
  private bus = createEventBus<PluginEvents>();
20
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
+
21
77
  private createHostContext(perm: PluginPerm = 'guest'): IHostContext {
22
78
  const adminCtx =
23
79
  perm === 'admin'
@@ -35,28 +91,17 @@ class PluginManager {
35
91
  onLoadingChanged: (listener: (payload: PluginEvents['site:loading:changed']) => void) => {
36
92
  return this.bus.on('site:loading:changed', listener);
37
93
  },
38
- registerMiddleware: <T>(
39
- point: HookPoint,
40
- fn: (data: T) => Promise<T> | T,
41
- pluginId: string,
42
- weight = 0
43
- ) => {
44
- registry.registerMiddleware(point, fn, pluginId, weight);
45
- },
94
+
46
95
  getDeviceId: getStableDeviceId,
47
96
  getVersionHash: () => {
48
97
  const hashStr: string =
49
98
  typeof GLOBAL_COMMIT_HASH === 'string' ? GLOBAL_COMMIT_HASH : '0000000';
50
99
  return hashStr;
51
100
  },
52
- registerSlot: (
53
- slotName: SlotName,
54
- component: React.ComponentType<IComPropsRegisteredToSlot>,
55
- pluginId: string,
56
- weight = 0
57
- ) => {
58
- registry.registerComponent(slotName, component, pluginId, weight);
59
- },
101
+
102
+ registerSlot: this.registerSlot,
103
+ registerMiddleware: this.registerMiddleware,
104
+ registerEntry: this.registryEntry,
60
105
  },
61
106
  };
62
107
  }
@@ -1,4 +1,4 @@
1
- import { cfApiFetcher } from '@/utils';
1
+ import { cfApiFetcher } from '#/app/utils';
2
2
  import { FALLBACK_MANIFEST } from '#/plugins/manifest';
3
3
  import { IPluginManifestEntry, PluginID } from '#/types/plugin';
4
4
 
@@ -11,6 +11,7 @@ const KnownPluginIDSet = new Set<string>([
11
11
  'now',
12
12
  'default',
13
13
  'fx',
14
+ 'blog',
14
15
  ]);
15
16
 
16
17
  interface PluginsApiResponse {
package/src/index.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import { RenderBlogInto } from './blog';
1
+ import { RenderApp } from './app';
2
2
 
3
- const blogContainer = document.getElementById('blog') as Element;
3
+ const appContainer = document.getElementById('app') as Element;
4
4
 
5
- RenderBlogInto(blogContainer);
5
+ RenderApp(appContainer);
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import { useRoutes } from 'react-router-dom';
3
+
4
+ import ArticlePage from '#/plugins/blog/pages/extensions/txt/article';
5
+ import Txt from '#/plugins/blog/pages/extensions/txt';
6
+ import { IComPropsRegisteredToSlot } from '#/types/slots';
7
+
8
+ export const BlogPageApp = (_: IComPropsRegisteredToSlot) => {
9
+ const element = useRoutes([
10
+ { path: '/', element: <Txt /> },
11
+ { path: '/:title', element: <ArticlePage /> },
12
+ ]);
13
+
14
+ return <>{element}</>;
15
+ };
@@ -0,0 +1 @@
1
+ export const PLUGIN_NAME = 'blog';
@@ -0,0 +1,11 @@
1
+ import type React from 'react';
2
+
3
+ import { ISlotProps } from '#/core/components/SlotComp';
4
+ import { createPluginCtx } from '#/core/context';
5
+
6
+ export interface IBlogContext {
7
+ Slot: React.FC<ISlotProps>;
8
+ setLoading: (loading: boolean) => void;
9
+ }
10
+
11
+ export const BlogContext = createPluginCtx<IBlogContext>();
@@ -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
 
@@ -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
  }
@@ -1,14 +1,10 @@
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
5
  import { useMiddlewareRunner } from '#/core/hooks/useMiddlewareTransData';
9
6
 
10
- const isProd = true;
11
- const POSTS_API = !isProd ? '/api/posts' : `${API_ENDPOINT}/posts`;
7
+ import { BlogContext } from '../context';
12
8
 
13
9
  interface PostsApiResponse {
14
10
  data: IPost[];
@@ -23,11 +19,13 @@ export interface TitleListItem {
23
19
  }
24
20
 
25
21
  export const usePosts = (name: string = '', suspense?: boolean) => {
26
- const { data: response, error: swrError } = useSWR<PostsApiResponse>(POSTS_API, baseFetcher, {
22
+ const { data: response, error: swrError } = useSWR<PostsApiResponse>('posts', {
27
23
  revalidateOnFocus: false,
28
24
  suspense,
29
25
  });
30
26
 
27
+ const { setLoading } = BlogContext.useCtx();
28
+
31
29
  const data = response?.data;
32
30
  const isDataLoading = !data && !swrError;
33
31
 
@@ -43,7 +41,6 @@ export const usePosts = (name: string = '', suspense?: boolean) => {
43
41
 
44
42
  const [fullTitleList, setFullTitleList] = useState<TitleListItem[]>(baseTitleList);
45
43
 
46
- // Use middleware hook to transform title list
47
44
  const {
48
45
  loading: isTransforming,
49
46
  error: transformError,
@@ -58,7 +55,9 @@ export const usePosts = (name: string = '', suspense?: boolean) => {
58
55
  }
59
56
  }, [baseTitleList, run]);
60
57
 
61
- const { isLoading: gLoading } = useGlobalLoading('posts', isDataLoading || isTransforming);
58
+ useEffect(() => {
59
+ setLoading(isDataLoading || isTransforming);
60
+ }, [isDataLoading, isTransforming, setLoading]);
62
61
 
63
62
  const posts =
64
63
  isDataLoading || name === '' || swrError || !data
@@ -69,6 +68,6 @@ export const usePosts = (name: string = '', suspense?: boolean) => {
69
68
  posts,
70
69
  titleList: fullTitleList ?? [],
71
70
  isError: swrError || transformError,
72
- isLoading: gLoading,
71
+ isLoading: isDataLoading || isTransforming,
73
72
  };
74
73
  };
@@ -0,0 +1,34 @@
1
+ import { BBPlugin } from '#/core/bbplugin';
2
+ import { Slot } from '#/core/components/SlotComp';
3
+ import { IHostContext } from '#/types/hostApi';
4
+ import { PluginID } from '#/types/plugin';
5
+
6
+ import { BlogPageApp } from './components/app';
7
+ import { BlogContext } from './context';
8
+
9
+ const { withCtx } = BlogContext;
10
+
11
+ export class BlogPlugin extends BBPlugin {
12
+ id: PluginID = 'blog';
13
+
14
+ override onInstall = async (ctx: IHostContext) => {
15
+ ctx.api.registerEntry(
16
+ {
17
+ path: '/blog',
18
+ label: 'cd ./blog',
19
+ pageComponent: withCtx(
20
+ {
21
+ setLoading: loading => {
22
+ ctx.api.setLoading('blog', loading);
23
+ },
24
+ Slot: Slot,
25
+ },
26
+ BlogPageApp
27
+ ),
28
+ },
29
+ this.id
30
+ );
31
+ };
32
+ }
33
+
34
+ export default new BlogPlugin();
@@ -2,9 +2,9 @@ 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';
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
8
  import { useMiddlewareTransformedData } from '#/core/hooks/useMiddlewareTransData';
9
9
  import { IPost } from '#/types/posts';
10
10
 
@@ -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;
@@ -45,4 +45,10 @@ export const FALLBACK_MANIFEST: Array<IPluginManifestEntry> = [
45
45
  version: '0.1.0',
46
46
  description: '为小乌鸦合集实现定制功能或者样式的插件',
47
47
  },
48
+ {
49
+ name: '博客',
50
+ id: 'blog',
51
+ version: '0.1.0',
52
+ description: '一个简单的博客系统,支持 Markdown 格式的文章',
53
+ },
48
54
  ];
@@ -106,8 +106,8 @@ export const StorePage = (_: IComPropsRegisteredToSlot) => {
106
106
  const headerRenderer = useCallback(() => {
107
107
  return (
108
108
  <>
109
- <Table.HCell>插件名称</Table.HCell>
110
- <Table.HCell>插件描述</Table.HCell>
109
+ <Table.HCell>功能</Table.HCell>
110
+ <Table.HCell>描述</Table.HCell>
111
111
  <Table.HCell style={{ textAlign: 'right' }}>安装/卸载</Table.HCell>
112
112
  </>
113
113
  );
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
2
  import { Fetcher } from 'swr';
3
3
 
4
- import { type FingerprintData } from '@/utils/fingerprints';
4
+ import { type FingerprintData } from '#/app/utils/fingerprints';
5
5
  import { PluginStore } from '#/core/pluginStore';
6
6
 
7
7
  import { HookPoint, IComPropsRegisteredToSlot, SlotName } from './slots';
8
- import { PluginEvents, PluginID } from './plugin';
8
+ import { IPluginEntry, PluginEvents, PluginID } from './plugin';
9
9
 
10
10
  export interface IHostApi {
11
11
  getDeviceId: () => Promise<{ id: string; fp: FingerprintData }>;
@@ -20,6 +20,7 @@ export interface IHostApi {
20
20
  pluginId: string,
21
21
  weight?: number
22
22
  ) => void;
23
+ registerEntry: (entry: IPluginEntry, id: string) => void;
23
24
  registerSlot: (
24
25
  slotName: SlotName,
25
26
  component: React.ComponentType<IComPropsRegisteredToSlot>,
@@ -21,6 +21,7 @@ export type PluginID =
21
21
  | 'store'
22
22
  | 'now'
23
23
  | 'default'
24
+ | 'blog'
24
25
  | 'fx';
25
26
 
26
27
  export interface IPluginEntry {
@@ -5,6 +5,21 @@ import { IHostContext } from '#/types/hostApi';
5
5
  import { IPluginEntry } from '#/types/plugin';
6
6
  import { IComPropsRegisteredToSlot } from '#/types/slots';
7
7
 
8
+ export const buildEntrySlotCom = (entry: IPluginEntry) => {
9
+ const Component = (props: IComPropsRegisteredToSlot) => {
10
+ const route = props.data as Omit<PathRouteProps, 'element'>;
11
+ if (route.path === entry.path) {
12
+ const Com = entry.pageComponent;
13
+ return <Com data={props.data} />;
14
+ }
15
+ return null;
16
+ };
17
+
18
+ Component.displayName = `EntrySlotCom(${entry.path})`;
19
+
20
+ return Component;
21
+ };
22
+
8
23
  export const buildEntryCreator = (ctx: IHostContext) => (entry: IPluginEntry, id: string) => {
9
24
  ctx.api.registerMiddleware(
10
25
  'extendedRoutes',
package/tsconfig.json CHANGED
@@ -6,7 +6,6 @@
6
6
  "noEmit": true,
7
7
  "moduleResolution": "bundler",
8
8
  "paths": {
9
- "@/*": ["./src/blog/*"],
10
9
  "#/*": ["./src/*"]
11
10
  }
12
11
  },
@@ -1 +0,0 @@
1
- export { ROUTES, ROUTE_NAME, GITHUB_REPO_ADDRESS } from './routes';
@@ -1,20 +0,0 @@
1
- export const ROUTES = {
2
- INDEX: '/',
3
- CONTENT: '/blog',
4
- HELP: '/blog/说明书',
5
- TAGS: '/tags',
6
- LOGIN: '/login',
7
- BLOG: '/blog',
8
- };
9
-
10
- export const ROUTE_NAME = {
11
- [ROUTES.CONTENT]: '目录',
12
- [ROUTES.BLOG]: '文章',
13
- unknown: '未知',
14
- };
15
-
16
- export const GITHUB_REPO_ADDRESS = 'https://github.com/bbbottle/bottle/tree/main/apps/site';
17
- export const API_ENDPOINT = 'https://cf.bbki.ng';
18
- export const API = {
19
- POSTS: 'posts',
20
- };
@@ -1,2 +0,0 @@
1
- export { usePaths } from './use_paths';
2
- export { useGlobalLoading } from './use_loading';
@@ -1,14 +0,0 @@
1
- import { GlobalLoadingContext } from '@/context/global_loading_state_provider';
2
- import { IBlogContext } from '@/types/blog-context';
3
- import { useContext } from 'react';
4
- import { useLocation } from 'react-router-dom';
5
-
6
- export const useBlogContext = (): IBlogContext => {
7
- const globalLoading = useContext(GlobalLoadingContext);
8
- const location = useLocation();
9
-
10
- return {
11
- gloalLoading: globalLoading.isLoading,
12
- location,
13
- };
14
- };
@@ -1,17 +0,0 @@
1
- import { useEffect, useRef } from 'react';
2
-
3
- export const useMousePosition = () => {
4
- const posRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
5
- useEffect(() => {
6
- const updateMousePosition = (e: MouseEvent) => {
7
- posRef.current = {
8
- x: e.clientX,
9
- y: e.clientY,
10
- };
11
- };
12
- window.addEventListener('mousemove', updateMousePosition);
13
- return () => window.removeEventListener('mousemove', updateMousePosition);
14
- }, []);
15
-
16
- return posRef;
17
- };
@@ -1,14 +0,0 @@
1
- export enum Role {
2
- KING = 'king',
3
- QUEEN = 'queen',
4
- ANNO = 'anno',
5
- }
6
-
7
- /**
8
- * Returns user role
9
- * Currently always returns ANNO as role-based authentication is now handled via API keys in CLI
10
- * Frontend role management may be re-implemented in the future
11
- */
12
- export const useRole = (): Role => {
13
- return Role.ANNO;
14
- };
@@ -1,11 +0,0 @@
1
- import { pluginManager } from '#/core/pluginManager';
2
- import { useEffect } from 'react';
3
- export const useSticker = () => {
4
- useEffect(() => {
5
- pluginManager.loadPlugin('sticker');
6
-
7
- return () => {
8
- pluginManager.disablePlugin('sticker');
9
- };
10
- }, []);
11
- };
@@ -1,2 +0,0 @@
1
- export { Cover } from './cover';
2
- export { default as Streaming } from '../../plugins/now/components/streaming';
@@ -1,24 +0,0 @@
1
- import React from 'react';
2
- import { ArticlePage } from '@/components/article';
3
-
4
- /**
5
- * Login page
6
- * Note: OAuth authentication has been removed. Authentication is now handled
7
- * via API keys in the CLI tool. Frontend authentication may be re-implemented
8
- * in the future.
9
- */
10
- export const Login = () => {
11
- return (
12
- <ArticlePage title="登录">
13
- <div className="prose dark:prose-invert">
14
- <p className="text-gray-600 dark:text-gray-400">网页登录功能已暂时禁用。</p>
15
- <p className="text-gray-600 dark:text-gray-400">
16
- 如需管理内容,请使用 CLI 工具并通过 API Key 进行认证:
17
- </p>
18
- <pre className="bg-gray-100 dark:bg-gray-800 p-4 rounded">
19
- <code>bbking login</code>
20
- </pre>
21
- </div>
22
- </ArticlePage>
23
- );
24
- };
@@ -1,6 +0,0 @@
1
- import { Location } from 'react-router-dom';
2
-
3
- export interface IBlogContext {
4
- gloalLoading: boolean;
5
- location: Location;
6
- }
@@ -1,4 +0,0 @@
1
- export enum FontType {
2
- NotoSerifSC = 'noto-serif',
3
- Mono = 'font-mono',
4
- }
@@ -1,8 +0,0 @@
1
- declare module '*.frag' {
2
- const file: string;
3
- export default file;
4
- }
5
- declare module '*.vert' {
6
- const file: string;
7
- export default file;
8
- }
File without changes
File without changes
File without changes
File without changes