@bbki.ng/site 5.8.11 → 6.0.1
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 +30 -0
- package/package.json +6 -3
- package/src/app/app.tsx +8 -7
- package/src/app/components/BaseLayout.tsx +6 -4
- package/src/app/components/cover/index.tsx +9 -5
- package/src/app/context/bbcontext.tsx +1 -1
- package/src/app/index.tsx +18 -1
- package/src/types/hostApi.ts +1 -2
- package/src/app/context/global_loading_state_provider.tsx +0 -65
- package/src/app/hooks/use_global_loading.ts +0 -47
- package/src/app/hooks/use_plugin_entries.ts +0 -34
- package/src/core/components/SlotComp.tsx +0 -14
- package/src/core/const/index.ts +0 -3
- package/src/core/context/createPluginCtx.tsx +0 -60
- package/src/core/context/index.ts +0 -1
- package/src/core/hooks/index.ts +0 -6
- package/src/core/hooks/useMiddlewareTransData.ts +0 -13
- package/src/core/hooks/useSlotComp.ts +0 -4
- package/src/core/hooks/use_plugins.ts +0 -115
- package/src/core/plugin-system/bbplugin.ts +0 -15
- package/src/core/plugin-system/manifest.ts +0 -56
- package/src/core/plugin-system/pluginManager.ts +0 -126
- package/src/core/plugin-system/pluginManifestService.ts +0 -97
- package/src/core/plugin-system/pluginStore.ts +0 -200
- package/src/core/plugin-system/registry.ts +0 -17
- package/src/core/plugin-system/services/coreService.ts +0 -75
- package/src/core/plugin-system/services/systemUIService.ts +0 -64
- package/src/core/shared-service/contract/ICoreService.ts +0 -28
- package/src/core/shared-service/contract/IUIService.ts +0 -53
- package/src/core/shared-service/factory/createUIService.ts +0 -121
- package/src/core/shared-service/factory/createUIServiceReactKit.tsx +0 -149
- package/src/core/shared-service/service-proxy.ts +0 -28
- package/src/core/shared-service/service-registry.ts +0 -54
- package/src/core/utils/eventBus.ts +0 -29
- package/src/plugins/blog/components/BlogLink.tsx +0 -11
- package/src/plugins/blog/components/BlogSlotCom.tsx +0 -14
- package/src/plugins/blog/components/app.tsx +0 -15
- package/src/plugins/blog/components/article/index.tsx +0 -51
- package/src/plugins/blog/components/index.tsx +0 -10
- package/src/plugins/blog/constants/index.ts +0 -1
- package/src/plugins/blog/context/index.ts +0 -7
- package/src/plugins/blog/hooks/useMiddlewareTransData.ts +0 -14
- package/src/plugins/blog/hooks/use_blog_scroll_pos_restoration.ts +0 -89
- package/src/plugins/blog/hooks/use_blog_slot_com.ts +0 -5
- package/src/plugins/blog/hooks/use_posts.ts +0 -74
- package/src/plugins/blog/index.ts +0 -79
- package/src/plugins/blog/pages/extensions/txt/article.tsx +0 -42
- package/src/plugins/blog/pages/extensions/txt/index.tsx +0 -58
- package/src/plugins/blog/services/BlogUIService.ts +0 -27
- package/src/plugins/blog/services/IBlogUIService.ts +0 -16
- package/src/plugins/extra-cd/index.ts +0 -36
- package/src/plugins/extra-entry/components/page.tsx +0 -14
- package/src/plugins/extra-entry/index.ts +0 -28
- package/src/plugins/fx/components/index.tsx +0 -45
- package/src/plugins/fx/context/index.ts +0 -12
- package/src/plugins/fx/hooks/useTextEffects.ts +0 -39
- package/src/plugins/fx/index.ts +0 -53
- package/src/plugins/fx/services/FxService.ts +0 -47
- package/src/plugins/fx/services/IFxService.ts +0 -15
- package/src/plugins/notification/components/index.tsx +0 -26
- package/src/plugins/notification/index.ts +0 -26
- package/src/plugins/notification/services/INotificationService.ts +0 -24
- package/src/plugins/notification/services/NotificationService.ts +0 -29
- package/src/plugins/now/components/NowLink.tsx +0 -7
- package/src/plugins/now/components/index.tsx +0 -9
- package/src/plugins/now/components/streaming/arrow-down.tsx +0 -26
- package/src/plugins/now/components/streaming/index.tsx +0 -99
- package/src/plugins/now/components/streaming/useScrollBtnVisibility.ts +0 -28
- package/src/plugins/now/context/index.ts +0 -10
- package/src/plugins/now/hooks/use_streaming.ts +0 -88
- package/src/plugins/now/index.ts +0 -62
- package/src/plugins/now/utils/streaming.ts +0 -33
- package/src/plugins/sticker/components/StickerCom.tsx +0 -45
- package/src/plugins/sticker/const.ts +0 -76
- package/src/plugins/sticker/context.ts +0 -7
- package/src/plugins/sticker/index.ts +0 -43
- package/src/plugins/sticker/types.ts +0 -10
- package/src/plugins/store/components/ArrowCom.tsx +0 -34
- package/src/plugins/store/components/ArrowSvg.tsx +0 -1133
- package/src/plugins/store/components/storeIcon.tsx +0 -16
- package/src/plugins/store/components/storePage.tsx +0 -202
- package/src/plugins/store/context/index.ts +0 -15
- package/src/plugins/store/index.ts +0 -57
- package/src/plugins/store/utils/index.ts +0 -26
- package/src/plugins/version/index.ts +0 -28
- package/src/plugins/xwy/components/XwyLink.tsx +0 -7
- package/src/plugins/xwy/components/article.tsx +0 -13
- package/src/plugins/xwy/components/logo.tsx +0 -28
- package/src/plugins/xwy/const/index.ts +0 -28
- package/src/plugins/xwy/index.ts +0 -103
- package/src/plugins/xwy/transformers/index.ts +0 -67
- package/src/plugins/xwy/types/index.ts +0 -17
- package/src/plugins/xwy/utils/index.ts +0 -43
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import type React from 'react';
|
|
2
|
-
|
|
3
|
-
import type { IMiddlewareEntry, ISlotEntry } from '#/core/plugin-system/registry';
|
|
4
|
-
import { createEventBus } from '#/core/utils/eventBus';
|
|
5
|
-
import type { PluginID } from '#/types/plugin';
|
|
6
|
-
import type { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
7
|
-
|
|
8
|
-
export interface IUIService<TSlot extends string, THook extends string> {
|
|
9
|
-
registerSlot(
|
|
10
|
-
slotName: TSlot,
|
|
11
|
-
component: React.ComponentType<IComPropsRegisteredToSlot>,
|
|
12
|
-
pluginId: PluginID,
|
|
13
|
-
weight?: number
|
|
14
|
-
): void;
|
|
15
|
-
|
|
16
|
-
registerMiddleware<T>(
|
|
17
|
-
hookPoint: THook,
|
|
18
|
-
fn: (data: T) => T | Promise<T>,
|
|
19
|
-
pluginId: PluginID,
|
|
20
|
-
weight?: number
|
|
21
|
-
): void;
|
|
22
|
-
|
|
23
|
-
unregisterAllByPluginId(pluginId: string): void;
|
|
24
|
-
|
|
25
|
-
getSlotEntries(slotName: TSlot): ISlotEntry[];
|
|
26
|
-
|
|
27
|
-
getComponents(slotName: TSlot): React.ComponentType<IComPropsRegisteredToSlot>[];
|
|
28
|
-
|
|
29
|
-
subscribeSlotChange(listener: () => void): () => void;
|
|
30
|
-
|
|
31
|
-
subscribeMiddlewareChange(listener: (point: THook) => void): () => void;
|
|
32
|
-
|
|
33
|
-
runMiddlewares<T>(point: THook, data: T): Promise<T>;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function createUIService<TSlot extends string, THook extends string>(
|
|
37
|
-
_: string
|
|
38
|
-
): IUIService<TSlot, THook> {
|
|
39
|
-
const slots = new Map<TSlot, Array<ISlotEntry>>();
|
|
40
|
-
const middlewares = new Map<THook, Array<IMiddlewareEntry<unknown>>>();
|
|
41
|
-
const bus = createEventBus<{
|
|
42
|
-
slotsChanged: void;
|
|
43
|
-
middlewareChanged: THook;
|
|
44
|
-
}>();
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
registerSlot(slotName, component, pluginId, weight = 0) {
|
|
48
|
-
const existing = slots.get(slotName) || [];
|
|
49
|
-
const newEntry: ISlotEntry = {
|
|
50
|
-
id: `${pluginId}-${slotName}-${component.name || 'comp'}`,
|
|
51
|
-
pluginId,
|
|
52
|
-
component,
|
|
53
|
-
weight,
|
|
54
|
-
};
|
|
55
|
-
const updated = [...existing, newEntry].sort((a, b) => (b.weight || 0) - (a.weight || 0));
|
|
56
|
-
slots.set(slotName, updated);
|
|
57
|
-
bus.emit('slotsChanged', undefined);
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
registerMiddleware(hookPoint, fn, pluginId, weight = 0) {
|
|
61
|
-
const existing = middlewares.get(hookPoint) || [];
|
|
62
|
-
const newEntry: IMiddlewareEntry<unknown> = {
|
|
63
|
-
id: `${pluginId}-${hookPoint}-middleware`,
|
|
64
|
-
pluginId,
|
|
65
|
-
fn: fn as (data: unknown) => unknown | Promise<unknown>,
|
|
66
|
-
weight,
|
|
67
|
-
};
|
|
68
|
-
const updated = [...existing, newEntry].sort((a, b) => (b.weight || 0) - (a.weight || 0));
|
|
69
|
-
middlewares.set(hookPoint, updated);
|
|
70
|
-
bus.emit('middlewareChanged', hookPoint);
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
unregisterAllByPluginId(pluginId) {
|
|
74
|
-
let slotsChanged = false;
|
|
75
|
-
slots.forEach((entries, slotName) => {
|
|
76
|
-
const filtered = entries.filter(entry => entry.pluginId !== pluginId);
|
|
77
|
-
if (filtered.length !== entries.length) {
|
|
78
|
-
slots.set(slotName, filtered);
|
|
79
|
-
slotsChanged = true;
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
middlewares.forEach((entries, point) => {
|
|
84
|
-
const filtered = entries.filter(entry => entry.pluginId !== pluginId);
|
|
85
|
-
if (filtered.length !== entries.length) {
|
|
86
|
-
middlewares.set(point, filtered);
|
|
87
|
-
bus.emit('middlewareChanged', point);
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
if (slotsChanged) {
|
|
92
|
-
bus.emit('slotsChanged', undefined);
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
getSlotEntries(slotName) {
|
|
97
|
-
return slots.get(slotName) || [];
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
getComponents(slotName) {
|
|
101
|
-
return (slots.get(slotName) || []).map(entry => entry.component);
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
subscribeSlotChange(listener) {
|
|
105
|
-
return bus.on('slotsChanged', listener as (data: unknown) => void);
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
subscribeMiddlewareChange(listener) {
|
|
109
|
-
return bus.on('middlewareChanged', listener as (data: unknown) => void);
|
|
110
|
-
},
|
|
111
|
-
|
|
112
|
-
async runMiddlewares(point, data) {
|
|
113
|
-
const fns = (middlewares.get(point) || []).map(entry => entry.fn);
|
|
114
|
-
let result: unknown = data;
|
|
115
|
-
for (const fn of fns) {
|
|
116
|
-
result = await fn(result);
|
|
117
|
-
}
|
|
118
|
-
return result as typeof data;
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
import type { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
4
|
-
|
|
5
|
-
import type { IUIService } from './createUIService';
|
|
6
|
-
|
|
7
|
-
export interface UseMiddlewareRunnerOptions<THook extends string> {
|
|
8
|
-
hookPoint: THook;
|
|
9
|
-
onMiddlewareChange?: () => void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface UseMiddlewareRunnerResult<T> {
|
|
13
|
-
loading: boolean;
|
|
14
|
-
error: Error | null;
|
|
15
|
-
run: (inputData: T) => Promise<T>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function createSlotHook<TSlot extends string>(service: IUIService<TSlot, string>) {
|
|
19
|
-
return function useSlotComp(slotName: TSlot) {
|
|
20
|
-
const [components, setComponents] = useState<React.ComponentType<IComPropsRegisteredToSlot>[]>(
|
|
21
|
-
() => service.getComponents(slotName)
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
setComponents(service.getComponents(slotName));
|
|
26
|
-
|
|
27
|
-
const unsubscribe = service.subscribeSlotChange(() => {
|
|
28
|
-
setComponents(service.getComponents(slotName));
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
return () => {
|
|
32
|
-
unsubscribe();
|
|
33
|
-
};
|
|
34
|
-
}, [slotName]);
|
|
35
|
-
|
|
36
|
-
return components;
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function createSlotComponent<TSlot extends string>(service: IUIService<TSlot, string>) {
|
|
41
|
-
return function Slot({
|
|
42
|
-
name,
|
|
43
|
-
data,
|
|
44
|
-
placeholder,
|
|
45
|
-
}: {
|
|
46
|
-
name: TSlot;
|
|
47
|
-
data?: unknown;
|
|
48
|
-
placeholder?: React.ReactNode;
|
|
49
|
-
}) {
|
|
50
|
-
const [entries, setEntries] = useState(() => service.getSlotEntries(name));
|
|
51
|
-
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
setEntries(service.getSlotEntries(name));
|
|
54
|
-
|
|
55
|
-
const unsubscribe = service.subscribeSlotChange(() => {
|
|
56
|
-
setEntries(service.getSlotEntries(name));
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
return () => {
|
|
60
|
-
unsubscribe();
|
|
61
|
-
};
|
|
62
|
-
}, [name]);
|
|
63
|
-
|
|
64
|
-
if (entries.length === 0) {
|
|
65
|
-
return <>{placeholder}</>;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
<>
|
|
70
|
-
{entries.map(({ id, component: Component }) => (
|
|
71
|
-
<Component key={id} data={data} />
|
|
72
|
-
))}
|
|
73
|
-
</>
|
|
74
|
-
);
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function createMiddlewareRunnerHook<THook extends string>(
|
|
79
|
-
service: IUIService<string, THook>
|
|
80
|
-
) {
|
|
81
|
-
return function useMiddlewareRunner<T>({
|
|
82
|
-
hookPoint,
|
|
83
|
-
onMiddlewareChange,
|
|
84
|
-
}: UseMiddlewareRunnerOptions<THook>): UseMiddlewareRunnerResult<T> {
|
|
85
|
-
const [loading, setLoading] = useState(false);
|
|
86
|
-
const [error, setError] = useState<Error | null>(null);
|
|
87
|
-
|
|
88
|
-
const run = useCallback(
|
|
89
|
-
async (inputData: T) => {
|
|
90
|
-
setLoading(true);
|
|
91
|
-
setError(null);
|
|
92
|
-
try {
|
|
93
|
-
const result = await service.runMiddlewares(hookPoint, inputData);
|
|
94
|
-
return result;
|
|
95
|
-
} catch (err) {
|
|
96
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
97
|
-
setError(error);
|
|
98
|
-
throw error;
|
|
99
|
-
} finally {
|
|
100
|
-
setLoading(false);
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
[hookPoint]
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
const unsubscribe = service.subscribeMiddlewareChange(hook => {
|
|
108
|
-
if (hook === hookPoint) {
|
|
109
|
-
onMiddlewareChange?.();
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
return () => {
|
|
114
|
-
unsubscribe();
|
|
115
|
-
};
|
|
116
|
-
}, [hookPoint, onMiddlewareChange]);
|
|
117
|
-
|
|
118
|
-
return { loading, error, run };
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function createMiddlewareTransformedDataHook<THook extends string>(
|
|
123
|
-
useMiddlewareRunner: <T>(opts: UseMiddlewareRunnerOptions<THook>) => UseMiddlewareRunnerResult<T>
|
|
124
|
-
) {
|
|
125
|
-
return function useMiddlewareTransformedData<T>(hookPoint: THook, defaultValue: T) {
|
|
126
|
-
const [result, setResult] = useState<T>(defaultValue);
|
|
127
|
-
|
|
128
|
-
const runRef = useRef<(input: T) => Promise<T>>(() => Promise.resolve(defaultValue));
|
|
129
|
-
|
|
130
|
-
const onMiddlewareChange = useCallback(() => {
|
|
131
|
-
runRef.current(defaultValue).then(setResult);
|
|
132
|
-
}, [defaultValue]);
|
|
133
|
-
|
|
134
|
-
const { run } = useMiddlewareRunner<T>({
|
|
135
|
-
hookPoint,
|
|
136
|
-
onMiddlewareChange,
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
runRef.current = run;
|
|
141
|
-
}, [run]);
|
|
142
|
-
|
|
143
|
-
useEffect(() => {
|
|
144
|
-
run(defaultValue).then(setResult);
|
|
145
|
-
}, [defaultValue, run]);
|
|
146
|
-
|
|
147
|
-
return result;
|
|
148
|
-
};
|
|
149
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { ServiceIdentifierMap, ServiceRegistry } from '#/core/shared-service/service-registry';
|
|
2
|
-
|
|
3
|
-
const empty = {};
|
|
4
|
-
|
|
5
|
-
// core/ServiceProxy.ts
|
|
6
|
-
export function createServiceProxy<T extends object, K extends keyof ServiceIdentifierMap>(
|
|
7
|
-
serviceId: K,
|
|
8
|
-
container: ServiceRegistry
|
|
9
|
-
): T {
|
|
10
|
-
return new Proxy({} as T, {
|
|
11
|
-
get(target, prop, receiver) {
|
|
12
|
-
const actualService = container.getRawInstance(serviceId);
|
|
13
|
-
|
|
14
|
-
if (!actualService) {
|
|
15
|
-
return (..._: unknown[]) => {
|
|
16
|
-
console.warn(
|
|
17
|
-
`[System] Service ${serviceId} is currently unavailable. Call to ${String(prop)} ignored.`
|
|
18
|
-
);
|
|
19
|
-
return empty;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// 3. 正常转发调用
|
|
24
|
-
const value = Reflect.get(actualService, prop, receiver);
|
|
25
|
-
return typeof value === 'function' ? value.bind(actualService) : value;
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { createServiceProxy } from './service-proxy';
|
|
2
|
-
|
|
3
|
-
export interface ServiceIdentifierMap {}
|
|
4
|
-
|
|
5
|
-
export class ServiceRegistry {
|
|
6
|
-
private services: Map<string, unknown> = new Map();
|
|
7
|
-
|
|
8
|
-
private constructor() {}
|
|
9
|
-
|
|
10
|
-
private static instance: ServiceRegistry;
|
|
11
|
-
|
|
12
|
-
static getInstance(): ServiceRegistry {
|
|
13
|
-
if (!ServiceRegistry.instance) {
|
|
14
|
-
ServiceRegistry.instance = new ServiceRegistry();
|
|
15
|
-
}
|
|
16
|
-
return ServiceRegistry.instance;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
register<T extends keyof ServiceIdentifierMap>(name: T, service: ServiceIdentifierMap[T]): void {
|
|
20
|
-
if (this.services.has(name)) {
|
|
21
|
-
console.warn(`Service with name ${name} is already registered. It will be overwritten.`);
|
|
22
|
-
}
|
|
23
|
-
this.services.set(name, service);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
getRawInstance<T extends keyof ServiceIdentifierMap>(
|
|
27
|
-
name: T
|
|
28
|
-
): ServiceIdentifierMap[T] | undefined {
|
|
29
|
-
const target = this.services.get(name);
|
|
30
|
-
if (!target) {
|
|
31
|
-
console.warn(`Service with name ${name} is not registered.`);
|
|
32
|
-
return undefined;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return target as ServiceIdentifierMap[T];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
get<T extends keyof ServiceIdentifierMap>(
|
|
39
|
-
name: T
|
|
40
|
-
): ServiceIdentifierMap[T] | Record<string, never> {
|
|
41
|
-
// const target = this.services.get(name);
|
|
42
|
-
// if (!target) {
|
|
43
|
-
// console.warn(`Service with name ${name} is not registered.`);
|
|
44
|
-
// return undefined;
|
|
45
|
-
// }
|
|
46
|
-
|
|
47
|
-
// return target as ServiceIdentifierMap[T];
|
|
48
|
-
return createServiceProxy(name, this) as ServiceIdentifierMap[T];
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
unregister<T extends keyof ServiceIdentifierMap>(name: T): void {
|
|
52
|
-
this.services.delete(name);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
import { createSlotComponent } from '#/core/shared-service/factory/createUIServiceReactKit';
|
|
4
|
-
|
|
5
|
-
import { BlogUIService } from '../services/BlogUIService';
|
|
6
|
-
import { BlogSlotName } from '../services/IBlogUIService';
|
|
7
|
-
|
|
8
|
-
export interface ISlotProps {
|
|
9
|
-
name: BlogSlotName;
|
|
10
|
-
data?: unknown;
|
|
11
|
-
placeholder?: React.ReactNode;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const Slot = createSlotComponent<BlogSlotName>(BlogUIService.getInstance());
|
|
@@ -1,15 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import React, { ReactElement, ReactNode } from 'react';
|
|
2
|
-
import { Article, Link } from '@bbki.ng/ui';
|
|
3
|
-
import classNames from 'classnames';
|
|
4
|
-
|
|
5
|
-
import { Slot } from '../BlogSlotCom';
|
|
6
|
-
|
|
7
|
-
export type ArticlePageProps = {
|
|
8
|
-
tags?: string[];
|
|
9
|
-
title: string;
|
|
10
|
-
date?: string;
|
|
11
|
-
description?: ReactNode;
|
|
12
|
-
headless?: boolean;
|
|
13
|
-
className?: string;
|
|
14
|
-
children: ReactElement;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const ArticlePage = (props: ArticlePageProps) => {
|
|
18
|
-
const { title, description, headless } = props;
|
|
19
|
-
// const navgation = useNavigate();
|
|
20
|
-
|
|
21
|
-
if (headless) {
|
|
22
|
-
return props.children;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const articleCls = classNames('prose', 'mb-16');
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<>
|
|
29
|
-
<Article
|
|
30
|
-
title={<Slot name="blog:articleTitle" data={title} placeholder={<>{title}</>} />}
|
|
31
|
-
date={props.date}
|
|
32
|
-
description={description}
|
|
33
|
-
className={`${props.className || ''}`}
|
|
34
|
-
loading={false}
|
|
35
|
-
>
|
|
36
|
-
<article className={articleCls}>{props.children}</article>
|
|
37
|
-
{/*<div className="relative -left-8">
|
|
38
|
-
<Reaction title={title} url={window.location.href} />
|
|
39
|
-
</div>*/}
|
|
40
|
-
</Article>
|
|
41
|
-
<div className="flex flex-col gap-4" style={{ position: 'relative', left: -4 }}>
|
|
42
|
-
<Link className="w-fit" to="/blog">
|
|
43
|
-
cd ..
|
|
44
|
-
</Link>
|
|
45
|
-
<Link className="w-fit" to="/">
|
|
46
|
-
cd ~
|
|
47
|
-
</Link>
|
|
48
|
-
</div>
|
|
49
|
-
</>
|
|
50
|
-
);
|
|
51
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const PLUGIN_NAME = 'blog';
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createMiddlewareRunnerHook,
|
|
3
|
-
createMiddlewareTransformedDataHook,
|
|
4
|
-
} from '#/core/shared-service/factory/createUIServiceReactKit';
|
|
5
|
-
|
|
6
|
-
import { BlogUIService } from '../services/BlogUIService';
|
|
7
|
-
|
|
8
|
-
export const useMiddlewareRunner = createMiddlewareRunnerHook(BlogUIService.getInstance());
|
|
9
|
-
export const useMiddlewareTransformedData =
|
|
10
|
-
createMiddlewareTransformedDataHook(useMiddlewareRunner);
|
|
11
|
-
export type {
|
|
12
|
-
UseMiddlewareRunnerOptions,
|
|
13
|
-
UseMiddlewareRunnerResult as UseMiddlewareTransformedDataResult,
|
|
14
|
-
} from '#/core/shared-service/factory/createUIServiceReactKit';
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
-
|
|
3
|
-
const SCROLL_STORAGE_KEY = 'div-scroll-positions';
|
|
4
|
-
|
|
5
|
-
function getScrollPositions(): Record<string, number> {
|
|
6
|
-
const stored = sessionStorage.getItem(SCROLL_STORAGE_KEY);
|
|
7
|
-
return stored ? JSON.parse(stored) : {};
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function saveScrollPosition(key: string, position: number) {
|
|
11
|
-
const positions = getScrollPositions();
|
|
12
|
-
positions[key] = position;
|
|
13
|
-
sessionStorage.setItem(SCROLL_STORAGE_KEY, JSON.stringify(positions));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function useBlogScrollReset() {
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
const element = document.getElementById('app');
|
|
19
|
-
if (!element) return;
|
|
20
|
-
|
|
21
|
-
element.scrollTop = 0;
|
|
22
|
-
}, []);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function useBlogScroll() {
|
|
26
|
-
const element = document.getElementById('app');
|
|
27
|
-
|
|
28
|
-
const gotoTop = useCallback(() => {
|
|
29
|
-
if (!element) return;
|
|
30
|
-
|
|
31
|
-
const id = setTimeout(() => {
|
|
32
|
-
element.scrollTo({ top: 0, behavior: 'smooth' });
|
|
33
|
-
}, 150);
|
|
34
|
-
|
|
35
|
-
return () => clearTimeout(id);
|
|
36
|
-
}, [element]);
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
gotoTop,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function useBlogScrollRestoration(debounceMs: number = 100) {
|
|
44
|
-
const isFirstRender = useRef(true);
|
|
45
|
-
const scrollTimeoutRef = useRef<number>();
|
|
46
|
-
const element = document.getElementById('blog');
|
|
47
|
-
const scrollKey = `blog`;
|
|
48
|
-
|
|
49
|
-
// Restore scroll position on mount
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
if (!isFirstRender.current || !element) return;
|
|
52
|
-
|
|
53
|
-
const positions = getScrollPositions();
|
|
54
|
-
const savedPosition = positions[scrollKey];
|
|
55
|
-
if (!savedPosition) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
requestAnimationFrame(() => {
|
|
60
|
-
element.scrollTop = savedPosition;
|
|
61
|
-
});
|
|
62
|
-
isFirstRender.current = false;
|
|
63
|
-
}, [element, scrollKey]);
|
|
64
|
-
|
|
65
|
-
// Save scroll position with debouncing
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
if (!element) return;
|
|
68
|
-
|
|
69
|
-
const handleScroll = () => {
|
|
70
|
-
// Clear previous timeout
|
|
71
|
-
if (scrollTimeoutRef.current) {
|
|
72
|
-
clearTimeout(scrollTimeoutRef.current);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Debounce the save operation
|
|
76
|
-
scrollTimeoutRef.current = window.setTimeout(() => {
|
|
77
|
-
if (element) {
|
|
78
|
-
saveScrollPosition(scrollKey, element.scrollTop);
|
|
79
|
-
}
|
|
80
|
-
}, debounceMs);
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
element.addEventListener('scroll', handleScroll, { passive: true });
|
|
84
|
-
|
|
85
|
-
return () => {
|
|
86
|
-
element.removeEventListener('scroll', handleScroll);
|
|
87
|
-
};
|
|
88
|
-
}, [debounceMs, element, scrollKey]);
|
|
89
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import useSWR from 'swr';
|
|
2
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
-
|
|
4
|
-
import { IPost } from '#/types/posts';
|
|
5
|
-
|
|
6
|
-
import { BlogContext } from '../context';
|
|
7
|
-
|
|
8
|
-
import { useMiddlewareRunner } from './useMiddlewareTransData';
|
|
9
|
-
|
|
10
|
-
interface PostsApiResponse {
|
|
11
|
-
data: IPost[];
|
|
12
|
-
// 可能还有其他字段如 status, message 等
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface TitleListItem {
|
|
16
|
-
name: string;
|
|
17
|
-
to: string;
|
|
18
|
-
children: string;
|
|
19
|
-
className?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const usePosts = (name: string = '', suspense?: boolean) => {
|
|
23
|
-
const { data: response, error: swrError } = useSWR<PostsApiResponse>('posts', {
|
|
24
|
-
revalidateOnFocus: false,
|
|
25
|
-
suspense,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const { setLoading } = BlogContext.useCtx();
|
|
29
|
-
|
|
30
|
-
const data = response?.data;
|
|
31
|
-
const isDataLoading = !data && !swrError;
|
|
32
|
-
|
|
33
|
-
const baseTitleList: TitleListItem[] = useMemo(() => {
|
|
34
|
-
if (!data || swrError) return [];
|
|
35
|
-
|
|
36
|
-
return data.map((p: IPost) => ({
|
|
37
|
-
name: p.title,
|
|
38
|
-
to: p.title,
|
|
39
|
-
children: p.title,
|
|
40
|
-
}));
|
|
41
|
-
}, [data, swrError]);
|
|
42
|
-
|
|
43
|
-
const [fullTitleList, setFullTitleList] = useState<TitleListItem[]>(baseTitleList);
|
|
44
|
-
|
|
45
|
-
const {
|
|
46
|
-
loading: isTransforming,
|
|
47
|
-
error: transformError,
|
|
48
|
-
run,
|
|
49
|
-
} = useMiddlewareRunner<TitleListItem[]>({
|
|
50
|
-
hookPoint: 'blog:transformTitleList',
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
if (baseTitleList.length > 0) {
|
|
55
|
-
run(baseTitleList).then(setFullTitleList);
|
|
56
|
-
}
|
|
57
|
-
}, [baseTitleList, run]);
|
|
58
|
-
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
setLoading(isDataLoading || isTransforming);
|
|
61
|
-
}, [isDataLoading, isTransforming, setLoading]);
|
|
62
|
-
|
|
63
|
-
const posts =
|
|
64
|
-
isDataLoading || name === '' || swrError || !data
|
|
65
|
-
? data
|
|
66
|
-
: data.find((p: IPost) => p.title === name);
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
posts,
|
|
70
|
-
titleList: fullTitleList ?? [],
|
|
71
|
-
isError: swrError || transformError,
|
|
72
|
-
isLoading: isDataLoading || isTransforming,
|
|
73
|
-
};
|
|
74
|
-
};
|