@bbki.ng/site 5.5.18 → 5.5.20
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 +14 -0
- package/index.d.ts +7 -6
- package/package.json +3 -3
- package/src/blog/app.tsx +18 -54
- package/src/blog/components/BaseLayout.tsx +55 -0
- package/src/blog/components/index.tsx +2 -4
- package/src/blog/context/global_loading_state_provider.tsx +3 -9
- package/src/blog/hooks/use_loading.ts +1 -1
- package/src/blog/hooks/use_plugin_entries.ts +48 -0
- package/src/blog/hooks/use_posts.ts +16 -9
- package/src/blog/index.tsx +1 -0
- package/src/blog/pages/cover/index.tsx +33 -24
- package/src/blog/pages/extensions/txt/article.tsx +6 -3
- package/src/blog/pages/extensions/txt/index.tsx +29 -25
- package/src/blog/swr.tsx +3 -2
- package/src/blog/types/path.ts +0 -7
- package/src/blog/utils/index.ts +0 -21
- package/src/core/context/createPluginCtx.tsx +12 -7
- package/src/core/hooks/index.ts +1 -1
- package/src/core/hooks/useMiddlewareTransData.ts +32 -46
- package/src/core/hooks/useSlotComp.ts +7 -3
- package/src/core/hooks/use_plugins.ts +16 -5
- package/src/core/pluginManager.ts +15 -6
- package/src/core/registry.ts +45 -11
- package/src/plugins/extra-entry/components/page.tsx +14 -0
- package/src/plugins/extra-entry/index.ts +32 -0
- package/src/plugins/manifest.ts +6 -0
- package/src/plugins/sticker/components/StickerCom.tsx +6 -8
- package/src/plugins/xwy/components/logo.tsx +6 -2
- package/src/plugins/xwy/const/index.ts +1 -1
- package/src/types/hostApi.ts +3 -3
- package/src/types/plugin.ts +11 -1
- package/src/types/posts.ts +9 -0
- package/src/types/slots.ts +12 -2
- package/src/utils/index.tsx +52 -0
- package/tsconfig.json +2 -4
- package/src/blog/components/Auth.tsx +0 -13
- package/src/blog/components/DelayFadeIn/DelayFadeIn.tsx +0 -28
- package/src/blog/components/Spinner.tsx +0 -10
- package/src/blog/components/my_suspense.tsx +0 -11
- package/src/blog/components/share/share-btn.tsx +0 -28
- package/src/blog/components/share/share-icon.tsx +0 -19
- package/src/blog/hooks/use_font_loading.ts +0 -41
package/src/core/hooks/index.ts
CHANGED
|
@@ -1,68 +1,54 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
|
|
3
3
|
import { registry } from '#/core/registry';
|
|
4
4
|
import type { HookPoint } from '#/types/slots';
|
|
5
5
|
|
|
6
|
-
export interface UseMiddlewareTransDataOptions
|
|
6
|
+
export interface UseMiddlewareTransDataOptions {
|
|
7
7
|
hookPoint: HookPoint;
|
|
8
|
-
|
|
9
|
-
immediate?: boolean;
|
|
8
|
+
onMiddlewareChange?: () => void; // 通知外部,由外部决定是否 run
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
export interface UseMiddlewareTransDataResult<T> {
|
|
13
|
-
data: T | undefined;
|
|
14
12
|
loading: boolean;
|
|
15
13
|
error: Error | null;
|
|
16
|
-
run: () => Promise<T>;
|
|
14
|
+
run: (inputData: T) => Promise<T>;
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
export function
|
|
17
|
+
export function useMiddlewareRunner<T>({
|
|
20
18
|
hookPoint,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}: UseMiddlewareTransDataOptions<T>): UseMiddlewareTransDataResult<T> {
|
|
24
|
-
const [data, setData] = useState<T | undefined>(undefined);
|
|
19
|
+
onMiddlewareChange,
|
|
20
|
+
}: UseMiddlewareTransDataOptions): UseMiddlewareTransDataResult<T> {
|
|
25
21
|
const [loading, setLoading] = useState(false);
|
|
26
22
|
const [error, setError] = useState<Error | null>(null);
|
|
27
23
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
24
|
+
const run = useCallback(
|
|
25
|
+
async (inputData: T) => {
|
|
26
|
+
setLoading(true);
|
|
27
|
+
setError(null);
|
|
28
|
+
try {
|
|
29
|
+
const result = await registry.runMiddleware(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
|
+
// 仅通知,不执行
|
|
48
43
|
useEffect(() => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
run()
|
|
54
|
-
.then(result => {
|
|
55
|
-
if (cancelled) return;
|
|
56
|
-
setData(result);
|
|
57
|
-
})
|
|
58
|
-
.catch(() => {
|
|
59
|
-
// 错误已在 run 中设置到 state
|
|
60
|
-
});
|
|
44
|
+
const unsubscribe = registry.subscribeMiddleware(hookPoint, () => {
|
|
45
|
+
onMiddlewareChange?.();
|
|
46
|
+
});
|
|
61
47
|
|
|
62
48
|
return () => {
|
|
63
|
-
|
|
49
|
+
unsubscribe();
|
|
64
50
|
};
|
|
65
|
-
}, [hookPoint,
|
|
51
|
+
}, [hookPoint, onMiddlewareChange]);
|
|
66
52
|
|
|
67
|
-
return {
|
|
53
|
+
return { loading, error, run };
|
|
68
54
|
}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
|
-
import { SlotName } from '#/types/slots';
|
|
3
|
+
import { IComPropsRegisteredToSlot, SlotName } from '#/types/slots';
|
|
4
4
|
|
|
5
5
|
import { registry } from '../registry';
|
|
6
6
|
|
|
7
7
|
export const useSlotComp = (slotName: SlotName) => {
|
|
8
|
-
const [components, setComponents] = useState<React.ComponentType<
|
|
8
|
+
const [components, setComponents] = useState<React.ComponentType<IComPropsRegisteredToSlot>[]>(
|
|
9
|
+
() => registry.getComponents(slotName)
|
|
10
|
+
);
|
|
9
11
|
|
|
10
12
|
useEffect(() => {
|
|
13
|
+
setComponents(registry.getComponents(slotName));
|
|
14
|
+
|
|
11
15
|
const unsubscribe = registry.subscribe(() => {
|
|
12
16
|
const comps = registry.getComponents(slotName);
|
|
13
17
|
setComponents(comps);
|
|
@@ -16,7 +20,7 @@ export const useSlotComp = (slotName: SlotName) => {
|
|
|
16
20
|
return () => {
|
|
17
21
|
unsubscribe();
|
|
18
22
|
};
|
|
19
|
-
}, []);
|
|
23
|
+
}, [slotName]);
|
|
20
24
|
|
|
21
25
|
return components;
|
|
22
26
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { pluginManager } from '#/core/pluginManager';
|
|
4
4
|
import { PluginID } from '#/types/plugin';
|
|
@@ -13,11 +13,20 @@ import { PluginID } from '#/types/plugin';
|
|
|
13
13
|
* usePlugins(['sticker', 'fontstyler']);
|
|
14
14
|
*/
|
|
15
15
|
export const usePlugins = (pluginIds: Array<PluginID>) => {
|
|
16
|
+
const [done, setDone] = useState(false);
|
|
17
|
+
|
|
18
|
+
const loadAllPlugins = useCallback(async () => {
|
|
19
|
+
try {
|
|
20
|
+
await Promise.all(pluginIds.map(id => pluginManager.loadPlugin(id)));
|
|
21
|
+
setDone(true);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error('Error loading plugins:', error);
|
|
24
|
+
}
|
|
25
|
+
}, [pluginIds]);
|
|
26
|
+
|
|
16
27
|
useEffect(() => {
|
|
17
28
|
// 加载指定的插件
|
|
18
|
-
|
|
19
|
-
pluginManager.loadPlugin(id);
|
|
20
|
-
});
|
|
29
|
+
loadAllPlugins();
|
|
21
30
|
|
|
22
31
|
return () => {
|
|
23
32
|
// 卸载所有指定的插件
|
|
@@ -25,5 +34,7 @@ export const usePlugins = (pluginIds: Array<PluginID>) => {
|
|
|
25
34
|
pluginManager.disablePlugin(id);
|
|
26
35
|
});
|
|
27
36
|
};
|
|
28
|
-
}, [pluginIds]);
|
|
37
|
+
}, [loadAllPlugins, pluginIds]);
|
|
38
|
+
|
|
39
|
+
return done;
|
|
29
40
|
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
3
|
import { IHostContext } from '#/types/hostApi';
|
|
4
|
-
import {
|
|
5
|
-
import type { SlotName, HookPoint } from '#/types/slots';
|
|
4
|
+
import type { SlotName, HookPoint, IComPropsRegisteredToSlot } from '#/types/slots';
|
|
6
5
|
import { IPlugin } from '#/types/plugin';
|
|
7
6
|
import { getStableDeviceId } from '@/utils/fingerprints';
|
|
7
|
+
|
|
8
|
+
import { registry } from './registry';
|
|
9
|
+
|
|
8
10
|
const pluginModules = import.meta.glob('../plugins/*/index.ts');
|
|
9
11
|
|
|
10
12
|
class PluginManager {
|
|
@@ -13,13 +15,18 @@ class PluginManager {
|
|
|
13
15
|
private createHostContext(): IHostContext {
|
|
14
16
|
return {
|
|
15
17
|
api: {
|
|
16
|
-
registerMiddleware: (
|
|
18
|
+
registerMiddleware: <T>(
|
|
19
|
+
point: HookPoint,
|
|
20
|
+
fn: (data: T) => Promise<T> | T,
|
|
21
|
+
pluginId: string,
|
|
22
|
+
weight = 0
|
|
23
|
+
) => {
|
|
17
24
|
registry.registerMiddleware(point, fn, pluginId, weight);
|
|
18
25
|
},
|
|
19
26
|
getDeviceId: getStableDeviceId,
|
|
20
27
|
registerSlot: (
|
|
21
28
|
slotName: SlotName,
|
|
22
|
-
component: React.ComponentType<
|
|
29
|
+
component: React.ComponentType<IComPropsRegisteredToSlot>,
|
|
23
30
|
pluginId: string,
|
|
24
31
|
weight = 0
|
|
25
32
|
) => {
|
|
@@ -54,14 +61,16 @@ class PluginManager {
|
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
try {
|
|
57
|
-
const module = await moduleLoader()
|
|
64
|
+
const module = (await moduleLoader()) as {
|
|
65
|
+
default: IPlugin;
|
|
66
|
+
};
|
|
58
67
|
|
|
59
68
|
const p: IPlugin = module.default;
|
|
60
69
|
|
|
61
70
|
const ctx = this.createHostContext();
|
|
62
71
|
|
|
63
72
|
if (p.onInstall) {
|
|
64
|
-
p.onInstall(ctx);
|
|
73
|
+
await p.onInstall(ctx);
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
this.activePlugins.set(pluginId, p);
|
package/src/core/registry.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { SlotName, HookPoint, IComPropsRegisteredToSlot } from '#/types/slots';
|
|
2
4
|
|
|
3
5
|
export interface ISlotEntry {
|
|
4
6
|
id: string;
|
|
5
|
-
component: React.ComponentType<
|
|
7
|
+
component: React.ComponentType<IComPropsRegisteredToSlot>;
|
|
6
8
|
pluginId: string;
|
|
7
9
|
weight: number;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
export interface IMiddlewareEntry {
|
|
12
|
+
export interface IMiddlewareEntry<T> {
|
|
11
13
|
id: string;
|
|
12
|
-
fn:
|
|
14
|
+
fn: (data: T) => Promise<T> | T;
|
|
13
15
|
pluginId: string;
|
|
14
16
|
weight?: number;
|
|
15
17
|
}
|
|
@@ -18,19 +20,41 @@ export class Registry {
|
|
|
18
20
|
private slots = new Map<SlotName, ISlotEntry[]>();
|
|
19
21
|
|
|
20
22
|
private listeners = new Set<() => void>();
|
|
23
|
+
private middlewareListenerMap = new Map<HookPoint, Set<() => void>>();
|
|
21
24
|
|
|
22
|
-
private middlewares = new Map<HookPoint, IMiddlewareEntry[]>();
|
|
25
|
+
private middlewares = new Map<HookPoint, IMiddlewareEntry<unknown>[]>();
|
|
23
26
|
|
|
24
27
|
private broadcastChange() {
|
|
25
28
|
this.listeners.forEach(listener => listener());
|
|
26
29
|
}
|
|
27
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
|
+
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);
|
|
45
|
+
}
|
|
46
|
+
|
|
28
47
|
subscribe(listener: () => void) {
|
|
29
48
|
this.listeners.add(listener);
|
|
30
49
|
return () => this.listeners.delete(listener);
|
|
31
50
|
}
|
|
32
51
|
|
|
33
|
-
registerComponent(
|
|
52
|
+
registerComponent(
|
|
53
|
+
slot: SlotName,
|
|
54
|
+
component: React.ComponentType<IComPropsRegisteredToSlot>,
|
|
55
|
+
pluginId: string,
|
|
56
|
+
weight = 0
|
|
57
|
+
) {
|
|
34
58
|
const existing = this.slots.get(slot) || [];
|
|
35
59
|
|
|
36
60
|
const newEntry: ISlotEntry = {
|
|
@@ -65,10 +89,15 @@ export class Registry {
|
|
|
65
89
|
this.broadcastChange();
|
|
66
90
|
}
|
|
67
91
|
|
|
68
|
-
registerMiddleware(
|
|
92
|
+
registerMiddleware<T>(
|
|
93
|
+
hookPoint: HookPoint,
|
|
94
|
+
fn: (data: T) => Promise<T> | T,
|
|
95
|
+
pluginId: string,
|
|
96
|
+
weight = 0
|
|
97
|
+
) {
|
|
69
98
|
const existing = this.middlewares.get(hookPoint) || [];
|
|
70
99
|
|
|
71
|
-
const newEntry: IMiddlewareEntry = {
|
|
100
|
+
const newEntry: IMiddlewareEntry<T> = {
|
|
72
101
|
id: `${pluginId}-${fn.name || 'middleware'}`,
|
|
73
102
|
pluginId,
|
|
74
103
|
fn,
|
|
@@ -76,11 +105,16 @@ export class Registry {
|
|
|
76
105
|
};
|
|
77
106
|
|
|
78
107
|
// 插入并按权重排序(权重大的在前)
|
|
79
|
-
const newList = [...existing, newEntry].sort(
|
|
108
|
+
const newList = [...existing, newEntry as IMiddlewareEntry<unknown>].sort(
|
|
109
|
+
(a, b) => (b.weight || 0) - (a.weight || 0)
|
|
110
|
+
);
|
|
111
|
+
|
|
80
112
|
this.middlewares.set(hookPoint, newList);
|
|
113
|
+
|
|
114
|
+
this.broadcastMiddlewareChange(hookPoint);
|
|
81
115
|
}
|
|
82
116
|
|
|
83
|
-
getComponents(slotName: SlotName): React.ComponentType<
|
|
117
|
+
getComponents(slotName: SlotName): React.ComponentType<IComPropsRegisteredToSlot>[] {
|
|
84
118
|
return (this.slots.get(slotName) || []).map(entry => entry.component);
|
|
85
119
|
}
|
|
86
120
|
|
|
@@ -88,7 +122,7 @@ export class Registry {
|
|
|
88
122
|
const fns = (this.middlewares.get(point) || []).map(entry => entry.fn);
|
|
89
123
|
let result = data;
|
|
90
124
|
for (const fn of fns) {
|
|
91
|
-
result = await fn(result);
|
|
125
|
+
result = (await fn(result)) as T;
|
|
92
126
|
}
|
|
93
127
|
return result;
|
|
94
128
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { PathRouteProps } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
import { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
5
|
+
|
|
6
|
+
export const ExtendedRoutesPage = ({ data }: IComPropsRegisteredToSlot) => {
|
|
7
|
+
const route = data as Omit<PathRouteProps, 'element'>;
|
|
8
|
+
|
|
9
|
+
if (route.path === '/hello') {
|
|
10
|
+
return <div className="p-4 text-center">Hello from Extra Entry Plugin!</div>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return <></>;
|
|
14
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { IHostContext } from '#/types/hostApi';
|
|
2
|
+
import { IPlugin } from '#/types/plugin';
|
|
3
|
+
import { buildEntryCreator } from '#/utils';
|
|
4
|
+
|
|
5
|
+
import { ExtendedRoutesPage } from './components/page';
|
|
6
|
+
|
|
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';
|
|
13
|
+
|
|
14
|
+
onInstall?: ((ctx: IHostContext) => void | Promise<void>) | undefined = async (
|
|
15
|
+
ctx: IHostContext
|
|
16
|
+
) => {
|
|
17
|
+
const entryCreator = buildEntryCreator(ctx);
|
|
18
|
+
entryCreator(
|
|
19
|
+
{
|
|
20
|
+
pageComponent: ExtendedRoutesPage,
|
|
21
|
+
path: '/hello',
|
|
22
|
+
label: 'cd ./hello',
|
|
23
|
+
},
|
|
24
|
+
this.id
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
onDisable?: (() => Promise<void> | void) | undefined;
|
|
29
|
+
onDestroy?: (() => void) | undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default new ExtraEntryPlugin();
|
package/src/plugins/manifest.ts
CHANGED
|
@@ -12,6 +12,12 @@ export const PLUGIN_MANIFEST = [
|
|
|
12
12
|
description:
|
|
13
13
|
'Provides additional change directory functionalities for enhanced user experience.',
|
|
14
14
|
},
|
|
15
|
+
{
|
|
16
|
+
name: 'extra-entry',
|
|
17
|
+
id: 'extra-entry',
|
|
18
|
+
version: '0.1.0',
|
|
19
|
+
description: 'Provides extra entries for cover and extended routes.',
|
|
20
|
+
},
|
|
15
21
|
{
|
|
16
22
|
name: 'xwy',
|
|
17
23
|
id: 'xwy',
|
|
@@ -1,23 +1,21 @@
|
|
|
1
|
-
import { PathObj } from '@bbki.ng/ui';
|
|
2
1
|
import React from 'react';
|
|
2
|
+
import { PathObj } from '@bbki.ng/ui';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
5
5
|
|
|
6
|
-
export const StickerCom = ({ data }:
|
|
7
|
-
const
|
|
6
|
+
export const StickerCom = ({ data }: IComPropsRegisteredToSlot) => {
|
|
7
|
+
const paths = data as PathObj[];
|
|
8
8
|
|
|
9
|
-
if (
|
|
9
|
+
if (paths.length === 0) {
|
|
10
10
|
return null;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const lastPath =
|
|
13
|
+
const lastPath = paths[paths.length - 1];
|
|
14
14
|
|
|
15
15
|
if (lastPath.name !== '~') {
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
console.log(ctx.deviceId);
|
|
20
|
-
|
|
21
19
|
return (
|
|
22
20
|
<div className="fixed bottom-32 left-16">
|
|
23
21
|
<div className="-rotate-17 inline-flex">
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import { useLocation } from 'react-router-dom';
|
|
3
3
|
|
|
4
|
+
import { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
5
|
+
|
|
4
6
|
export const Crows = () => {
|
|
5
7
|
return (
|
|
6
8
|
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
@@ -13,9 +15,11 @@ export const Crows = () => {
|
|
|
13
15
|
);
|
|
14
16
|
};
|
|
15
17
|
|
|
16
|
-
export const XwyLogo = ({ data
|
|
18
|
+
export const XwyLogo = ({ data }: IComPropsRegisteredToSlot) => {
|
|
17
19
|
const location = useLocation();
|
|
18
20
|
|
|
21
|
+
const defaultLogo = data as React.ReactNode;
|
|
22
|
+
|
|
19
23
|
if (decodeURIComponent(location.pathname).includes('小乌鸦')) {
|
|
20
24
|
return <Crows />;
|
|
21
25
|
}
|
package/src/types/hostApi.ts
CHANGED
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { type FingerprintData } from '@/utils/fingerprints';
|
|
4
4
|
|
|
5
|
-
import { HookPoint, SlotName } from './slots';
|
|
5
|
+
import { HookPoint, IComPropsRegisteredToSlot, SlotName } from './slots';
|
|
6
6
|
|
|
7
7
|
export interface IHostApi {
|
|
8
8
|
getDeviceId: () => Promise<{ id: string; fp: FingerprintData }>;
|
|
@@ -12,9 +12,9 @@ export interface IHostApi {
|
|
|
12
12
|
pluginId: string,
|
|
13
13
|
weight?: number
|
|
14
14
|
) => void;
|
|
15
|
-
registerSlot:
|
|
15
|
+
registerSlot: (
|
|
16
16
|
slotName: SlotName,
|
|
17
|
-
component: React.ComponentType<
|
|
17
|
+
component: React.ComponentType<IComPropsRegisteredToSlot>,
|
|
18
18
|
pluginId: string,
|
|
19
19
|
weight?: number
|
|
20
20
|
) => void;
|
package/src/types/plugin.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
import { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
4
|
+
|
|
1
5
|
import { IHostContext } from './hostApi';
|
|
2
6
|
|
|
3
7
|
export interface IPlugin {
|
|
@@ -12,4 +16,10 @@ export interface IPlugin {
|
|
|
12
16
|
onDestroy?: () => void;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
export type PluginID = 'sticker' | 'xwy' | 'extra-cd';
|
|
19
|
+
export type PluginID = 'sticker' | 'xwy' | 'extra-cd' | 'extra-entry';
|
|
20
|
+
|
|
21
|
+
export interface IPluginEntry {
|
|
22
|
+
path: string;
|
|
23
|
+
label?: string;
|
|
24
|
+
pageComponent: React.ComponentType<IComPropsRegisteredToSlot>;
|
|
25
|
+
}
|
package/src/types/posts.ts
CHANGED
package/src/types/slots.ts
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
export type SlotName = 'leftCol' | 'rightCol' | 'articleActionRow' | 'logo';
|
|
2
|
-
|
|
1
|
+
export type SlotName = 'leftCol' | 'rightCol' | 'articleActionRow' | 'logo' | 'route';
|
|
2
|
+
|
|
3
|
+
export type HookPoint =
|
|
4
|
+
| 'filterPosts'
|
|
5
|
+
| 'transformPostContent'
|
|
6
|
+
| 'transformTitleList'
|
|
7
|
+
| 'extendedRoutes'
|
|
8
|
+
| 'transformCoverEntry';
|
|
9
|
+
|
|
10
|
+
export interface IComPropsRegisteredToSlot {
|
|
11
|
+
data: unknown;
|
|
12
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { LinkProps, PathRouteProps } from 'react-router-dom';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { IHostContext } from '#/types/hostApi';
|
|
5
|
+
import { IPluginEntry } from '#/types/plugin';
|
|
6
|
+
import { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
7
|
+
|
|
8
|
+
export const buildEntryCreator = (ctx: IHostContext) => (entry: IPluginEntry, id: string) => {
|
|
9
|
+
ctx.api.registerMiddleware(
|
|
10
|
+
'extendedRoutes',
|
|
11
|
+
(routes: Array<Omit<PathRouteProps, 'element'>>) => {
|
|
12
|
+
return [
|
|
13
|
+
...routes,
|
|
14
|
+
{
|
|
15
|
+
path: entry.path,
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
},
|
|
19
|
+
id,
|
|
20
|
+
10
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
ctx.api.registerSlot(
|
|
24
|
+
'route',
|
|
25
|
+
(props: IComPropsRegisteredToSlot) => {
|
|
26
|
+
const route = props.data as Omit<PathRouteProps, 'element'>;
|
|
27
|
+
if (route.path === entry.path) {
|
|
28
|
+
const Com = entry.pageComponent;
|
|
29
|
+
return <Com data={props.data} />;
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
},
|
|
33
|
+
id
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if (!entry.label) return;
|
|
37
|
+
|
|
38
|
+
ctx.api.registerMiddleware(
|
|
39
|
+
'transformCoverEntry',
|
|
40
|
+
(entries: Array<LinkProps>) => {
|
|
41
|
+
return [
|
|
42
|
+
...entries,
|
|
43
|
+
{
|
|
44
|
+
to: entry.path,
|
|
45
|
+
children: entry.label,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
},
|
|
49
|
+
id,
|
|
50
|
+
10
|
|
51
|
+
);
|
|
52
|
+
};
|
package/tsconfig.json
CHANGED
|
@@ -5,11 +5,9 @@
|
|
|
5
5
|
"jsx": "react",
|
|
6
6
|
"noEmit": true,
|
|
7
7
|
"moduleResolution": "bundler",
|
|
8
|
-
"ignoreDeprecations": "6.0",
|
|
9
|
-
"baseUrl": ".",
|
|
10
8
|
"paths": {
|
|
11
|
-
"@/*": ["src/blog/*"],
|
|
12
|
-
"#/*": ["src/*"]
|
|
9
|
+
"@/*": ["./src/blog/*"],
|
|
10
|
+
"#/*": ["./src/*"]
|
|
13
11
|
}
|
|
14
12
|
},
|
|
15
13
|
"include": ["./src", "vite.config.js", "index.d.ts"]
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Role, useRole } from '@/hooks/use_role';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { Navigate } from 'react-router-dom';
|
|
4
|
-
|
|
5
|
-
export const Auth = (props: { children: any; shouldRedirect?: boolean; role?: Role[] }) => {
|
|
6
|
-
const myRole = useRole();
|
|
7
|
-
|
|
8
|
-
if (props.role && !props.role.includes(myRole)) {
|
|
9
|
-
return props.shouldRedirect ? <Navigate to={'/login'} /> : null;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return props.children;
|
|
13
|
-
};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import React, { useEffect } from 'react';
|
|
2
|
-
import cls from 'classnames';
|
|
3
|
-
|
|
4
|
-
export type DelayFadeInProps = {
|
|
5
|
-
children: any;
|
|
6
|
-
delay: number;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export const DelayFadeIn = (props: DelayFadeInProps) => {
|
|
10
|
-
const [show, setShow] = React.useState(false);
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
const id = setTimeout(() => {
|
|
14
|
-
setShow(true);
|
|
15
|
-
}, props.delay);
|
|
16
|
-
|
|
17
|
-
return () => {
|
|
18
|
-
clearTimeout(id);
|
|
19
|
-
};
|
|
20
|
-
}, []);
|
|
21
|
-
|
|
22
|
-
const className = cls('transition-opacity', {
|
|
23
|
-
'opacity-100': show,
|
|
24
|
-
'opacity-0': !show,
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
return <div className={className}>{props.children}</div>;
|
|
28
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { useGlobalLoading } from '@/hooks';
|
|
3
|
-
|
|
4
|
-
export const Spinner = (props: { disableDotIndicator?: boolean }) => {
|
|
5
|
-
const { disableDotIndicator } = props;
|
|
6
|
-
|
|
7
|
-
useGlobalLoading('spinner', !disableDotIndicator);
|
|
8
|
-
|
|
9
|
-
return <div className="h-full w-full grid place-items-center"></div>;
|
|
10
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import React, { ReactElement, ReactNode, Suspense } from 'react';
|
|
2
|
-
import { ErrorBoundary } from '@bbki.ng/ui';
|
|
3
|
-
import { Spinner } from './Spinner';
|
|
4
|
-
|
|
5
|
-
export const MySuspense = (props: { children: ReactNode; fallback?: ReactElement }) => {
|
|
6
|
-
return (
|
|
7
|
-
<ErrorBoundary>
|
|
8
|
-
<Suspense fallback={<Spinner />}>{props.children}</Suspense>
|
|
9
|
-
</ErrorBoundary>
|
|
10
|
-
);
|
|
11
|
-
};
|