@bbki.ng/site 5.5.34 → 5.5.36
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 +12 -0
- package/package.json +1 -1
- package/src/blog/app.tsx +2 -3
- package/src/blog/components/BaseLayout.tsx +2 -2
- package/src/blog/hooks/index.ts +0 -2
- package/src/blog/hooks/use_plugin_entries.ts +1 -8
- package/src/blog/main.css +2 -2
- package/src/blog/pages/cover/index.tsx +0 -4
- package/src/blog/pages/index.tsx +1 -1
- package/src/blog/utils/index.ts +1 -1
- package/src/core/const/index.ts +3 -0
- package/src/core/pluginManager.ts +16 -3
- package/src/plugins/manifest.ts +13 -0
- package/src/plugins/now/components/index.tsx +16 -0
- package/src/{blog/pages → plugins/now/components}/streaming/arrow-down.tsx +3 -3
- package/src/{blog/pages → plugins/now/components}/streaming/index.tsx +7 -7
- package/src/{blog/pages → plugins/now/components}/streaming/useScrollBtnVisibility.ts +1 -0
- package/src/plugins/now/context/index.ts +7 -0
- package/src/{blog → plugins/now}/hooks/use_streaming.ts +5 -4
- package/src/plugins/now/index.ts +37 -0
- package/src/{blog → plugins/now}/utils/streaming.ts +7 -3
- package/src/plugins/xwy/index.ts +24 -78
- package/src/plugins/xwy/transformers/index.ts +67 -0
- package/src/types/hostApi.ts +3 -0
- package/src/types/plugin.ts +11 -1
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/src/blog/app.tsx
CHANGED
|
@@ -11,11 +11,11 @@ import { usePlugins } from '#/core/hooks/use_plugins';
|
|
|
11
11
|
import { SWR } from '@/swr';
|
|
12
12
|
import type { PluginID } from '#/types/plugin';
|
|
13
13
|
|
|
14
|
-
import { Cover
|
|
14
|
+
import { Cover } from './pages';
|
|
15
15
|
import { usePluginEntries } from './hooks/use_plugin_entries';
|
|
16
16
|
import { BaseLayout } from './components/BaseLayout';
|
|
17
17
|
|
|
18
|
-
const APP_PLUGIN_IDS: Array<PluginID> = [/*'sticker',*/ 'xwy', 'extra-cd'
|
|
18
|
+
const APP_PLUGIN_IDS: Array<PluginID> = [/*'sticker',*/ 'xwy', 'extra-cd', 'now'];
|
|
19
19
|
|
|
20
20
|
const AppRoutes = () => {
|
|
21
21
|
usePlugins(APP_PLUGIN_IDS);
|
|
@@ -33,7 +33,6 @@ const AppRoutes = () => {
|
|
|
33
33
|
</Route>
|
|
34
34
|
|
|
35
35
|
<Route path="bot" element={<BotRedirect />} />
|
|
36
|
-
<Route path="now" element={<Streaming />} />
|
|
37
36
|
{pluginEntries?.map(route => (
|
|
38
37
|
<Route key={route.path} path={route.path} element={<Slot name="route" data={route} />} />
|
|
39
38
|
))}
|
|
@@ -27,7 +27,7 @@ export const BaseLayout = () => {
|
|
|
27
27
|
loading={isLoading}
|
|
28
28
|
customLogo={<Slot name="logo" data={defaultLogo} placeholder={defaultLogo} />}
|
|
29
29
|
style={{
|
|
30
|
-
paddingTop: 'var(--safe-top)',
|
|
30
|
+
paddingTop: 'calc(var(--safe-top) + 4px)',
|
|
31
31
|
transition: 'all .2s ease-in-out',
|
|
32
32
|
}}
|
|
33
33
|
/>
|
|
@@ -45,7 +45,7 @@ export const BaseLayout = () => {
|
|
|
45
45
|
</div>
|
|
46
46
|
}
|
|
47
47
|
>
|
|
48
|
-
<Container className="py-
|
|
48
|
+
<Container className="py-48">
|
|
49
49
|
<ErrorBoundary>
|
|
50
50
|
<Outlet />
|
|
51
51
|
</ErrorBoundary>
|
package/src/blog/hooks/index.ts
CHANGED
|
@@ -3,14 +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>([
|
|
7
|
-
'/bot',
|
|
8
|
-
'/now',
|
|
9
|
-
'/',
|
|
10
|
-
'/blog',
|
|
11
|
-
'/blog/:title',
|
|
12
|
-
'default',
|
|
13
|
-
]);
|
|
6
|
+
const protectedRoutesSet = new Set<string>(['/bot', '/', '/blog', '/blog/:title', 'default']);
|
|
14
7
|
|
|
15
8
|
type PluginRoute = Omit<PathRouteProps, 'element'>;
|
|
16
9
|
|
package/src/blog/main.css
CHANGED
|
@@ -73,7 +73,7 @@ a {
|
|
|
73
73
|
|
|
74
74
|
@supports (padding-top: env(safe-area-inset-top)) {
|
|
75
75
|
:root {
|
|
76
|
-
--safe-top: calc(env(safe-area-inset-top)
|
|
76
|
+
--safe-top: calc(env(safe-area-inset-top));
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -82,7 +82,7 @@ a {
|
|
|
82
82
|
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
|
83
83
|
scrollbar-width: none;
|
|
84
84
|
transition: all 0.2s ease-in-out;
|
|
85
|
-
padding-top: var(--safe-top);
|
|
85
|
+
/* padding-top: var(--safe-top); */
|
|
86
86
|
box-sizing: border-box;
|
|
87
87
|
}
|
|
88
88
|
|
package/src/blog/pages/index.tsx
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Cover } from './cover';
|
|
2
|
-
export { default as Streaming } from '
|
|
2
|
+
export { default as Streaming } from '../../plugins/now/components/streaming';
|
package/src/blog/utils/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { API_ENDPOINT } from '@/constants/routes';
|
|
|
2
2
|
|
|
3
3
|
import { getStableDeviceId } from './fingerprints';
|
|
4
4
|
|
|
5
|
-
type Fetcher = (resource: string, init?:
|
|
5
|
+
type Fetcher = (resource: string, init?: RequestInit) => Promise<unknown>;
|
|
6
6
|
|
|
7
7
|
export const floatNumberToPercentageString = (num: number): string => {
|
|
8
8
|
return `${num * 100}%`;
|
|
@@ -2,11 +2,12 @@ import React from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { IHostContext } from '#/types/hostApi';
|
|
4
4
|
import type { SlotName, HookPoint, IComPropsRegisteredToSlot } from '#/types/slots';
|
|
5
|
-
import { IPlugin, PluginEvents, PluginID } from '#/types/plugin';
|
|
5
|
+
import { IPlugin, PluginEvents, PluginID, PluginPerm } from '#/types/plugin';
|
|
6
6
|
import { getStableDeviceId } from '@/utils/fingerprints';
|
|
7
7
|
|
|
8
8
|
import { registry } from './registry';
|
|
9
9
|
import { createEventBus } from './utils/eventBus';
|
|
10
|
+
import { AdminPluginIDSet } from './const';
|
|
10
11
|
|
|
11
12
|
const pluginModules = import.meta.glob('../plugins/*/index.ts');
|
|
12
13
|
|
|
@@ -15,9 +16,15 @@ class PluginManager {
|
|
|
15
16
|
|
|
16
17
|
private bus = createEventBus<PluginEvents>();
|
|
17
18
|
|
|
18
|
-
private createHostContext(): IHostContext {
|
|
19
|
+
private createHostContext(perm: PluginPerm = 'guest'): IHostContext {
|
|
20
|
+
const adminOnlyApi = {
|
|
21
|
+
installPlugin: (id: PluginID) => this.loadPlugin(id),
|
|
22
|
+
disablePlugin: (id: PluginID) => this.disablePlugin(id),
|
|
23
|
+
};
|
|
24
|
+
|
|
19
25
|
return {
|
|
20
26
|
api: {
|
|
27
|
+
...(perm === 'admin' ? adminOnlyApi : {}),
|
|
21
28
|
setLoading: (id: PluginID, loading: boolean) => {
|
|
22
29
|
this.bus.emit('plugin:loading:changed', { id, loading });
|
|
23
30
|
},
|
|
@@ -82,7 +89,13 @@ class PluginManager {
|
|
|
82
89
|
|
|
83
90
|
const p: IPlugin = module.default;
|
|
84
91
|
|
|
85
|
-
const
|
|
92
|
+
const perm = (p as IPlugin & { perm?: PluginPerm }).perm || 'guest';
|
|
93
|
+
if (perm === 'admin' && !AdminPluginIDSet.has(pluginId)) {
|
|
94
|
+
console.error(`Plugin ${pluginId} requires admin permission, but it's not trusted.`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const ctx = this.createHostContext(perm);
|
|
86
99
|
|
|
87
100
|
if (p.onInstall) {
|
|
88
101
|
await p.onInstall(ctx);
|
package/src/plugins/manifest.ts
CHANGED
|
@@ -5,6 +5,19 @@ export const PLUGIN_MANIFEST = [
|
|
|
5
5
|
version: '0.1.0',
|
|
6
6
|
description: 'A sticker plugin',
|
|
7
7
|
},
|
|
8
|
+
{
|
|
9
|
+
name: 'store',
|
|
10
|
+
id: 'store',
|
|
11
|
+
version: '0.1.0',
|
|
12
|
+
description: 'A plugin for state management and data persistence.',
|
|
13
|
+
perm: 'admin',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'now',
|
|
17
|
+
id: 'now',
|
|
18
|
+
version: '1.0.0',
|
|
19
|
+
description: 'show real-time site status and logs',
|
|
20
|
+
},
|
|
8
21
|
{
|
|
9
22
|
name: 'extra-cd',
|
|
10
23
|
id: 'extra-cd',
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { PathRouteProps } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
import { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
5
|
+
|
|
6
|
+
import Streaming from './streaming';
|
|
7
|
+
|
|
8
|
+
export const pageNow = ({ data }: IComPropsRegisteredToSlot) => {
|
|
9
|
+
const route = data as Omit<PathRouteProps, 'element'>;
|
|
10
|
+
|
|
11
|
+
if (route.path !== '/now') {
|
|
12
|
+
return <></>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return <Streaming />;
|
|
16
|
+
};
|
|
@@ -5,7 +5,7 @@ export const ArrowDownIcon = ({ show }: { show: boolean }) => (
|
|
|
5
5
|
<svg
|
|
6
6
|
data-testid="geist-icon"
|
|
7
7
|
height="16"
|
|
8
|
-
|
|
8
|
+
strokeLinejoin="round"
|
|
9
9
|
viewBox="0 0 16 16"
|
|
10
10
|
width="16"
|
|
11
11
|
style={{
|
|
@@ -17,8 +17,8 @@ export const ArrowDownIcon = ({ show }: { show: boolean }) => (
|
|
|
17
17
|
})}
|
|
18
18
|
>
|
|
19
19
|
<path
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
fillRule="evenodd"
|
|
21
|
+
clipRule="evenodd"
|
|
22
22
|
d="M8.70711 1.39644C8.31659 1.00592 7.68342 1.00592 7.2929 1.39644L2.21968 6.46966L1.68935 6.99999L2.75001 8.06065L3.28034 7.53032L7.25001 3.56065V14.25V15H8.75001V14.25V3.56065L12.7197 7.53032L13.25 8.06065L14.3107 6.99999L13.7803 6.46966L8.70711 1.39644Z"
|
|
23
23
|
fill="currentColor"
|
|
24
24
|
></path>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { formatStreamingData } from '@/utils/streaming';
|
|
4
|
-
import { Button, Panel } from '@bbki.ng/ui';
|
|
5
|
-
import { useScrollBtnVisibility } from './useScrollBtnVisibility';
|
|
6
|
-
import { Link } from '@bbki.ng/ui';
|
|
7
|
-
import { ArrowDownIcon } from './arrow-down';
|
|
2
|
+
import { Panel, Link } from '@bbki.ng/ui';
|
|
8
3
|
import classNames from 'classnames';
|
|
9
4
|
|
|
10
|
-
|
|
5
|
+
import { formatStreamingData } from '#/plugins/now/utils/streaming';
|
|
6
|
+
|
|
7
|
+
import { useStreaming } from '../../hooks/use_streaming';
|
|
8
|
+
|
|
9
|
+
import { useScrollBtnVisibility } from './useScrollBtnVisibility';
|
|
10
|
+
|
|
11
11
|
declare global {
|
|
12
12
|
namespace JSX {
|
|
13
13
|
interface IntrinsicElements {
|
|
@@ -2,14 +2,13 @@ import useSWR from 'swr';
|
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
|
|
4
4
|
import { baseFetcher } from '@/utils';
|
|
5
|
-
import { API_ENDPOINT } from '@/constants/routes';
|
|
6
5
|
|
|
7
|
-
import {
|
|
6
|
+
import { NowCtx } from '../context';
|
|
8
7
|
|
|
9
8
|
// In dev, use /api prefix to leverage Vite proxy to localhost:8787
|
|
10
9
|
// const isProd = typeof window !== 'undefined' && /^https:\/\/bbki.ng/.test(window.location.href);
|
|
11
10
|
const isProd = true;
|
|
12
|
-
const API_BASE = !isProd ? '/api' :
|
|
11
|
+
const API_BASE = !isProd ? '/api' : 'https://cf.bbki.ng';
|
|
13
12
|
|
|
14
13
|
export type StreamingItem = {
|
|
15
14
|
id: string;
|
|
@@ -93,6 +92,8 @@ export function useStreaming(options: UseStreamingOptions = {}) {
|
|
|
93
92
|
|
|
94
93
|
const isLoading = !data && !error;
|
|
95
94
|
|
|
95
|
+
const nowCtx = NowCtx.useCtx();
|
|
96
|
+
|
|
96
97
|
const [, forceUpdate] = useState(0);
|
|
97
98
|
|
|
98
99
|
// make rerender when customElement defined
|
|
@@ -102,7 +103,7 @@ export function useStreaming(options: UseStreamingOptions = {}) {
|
|
|
102
103
|
});
|
|
103
104
|
}, []);
|
|
104
105
|
|
|
105
|
-
|
|
106
|
+
nowCtx.setLoading(isLoading);
|
|
106
107
|
|
|
107
108
|
return {
|
|
108
109
|
streaming: data?.data ?? [],
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { IHostContext } from '#/types/hostApi';
|
|
2
|
+
import { IPlugin } from '#/types/plugin';
|
|
3
|
+
import { buildEntryCreator } from '#/utils';
|
|
4
|
+
|
|
5
|
+
import { pageNow } from './components';
|
|
6
|
+
import { NowCtx } from './context';
|
|
7
|
+
|
|
8
|
+
export class NowPlugin implements IPlugin {
|
|
9
|
+
id: string = 'now';
|
|
10
|
+
name: string = 'Now';
|
|
11
|
+
description?: string | undefined = 'Manages real-time site status and logs';
|
|
12
|
+
version: string = '1.0.0';
|
|
13
|
+
author?: string | undefined = 'bbki.ng';
|
|
14
|
+
|
|
15
|
+
onInstall = (ctx: IHostContext) => {
|
|
16
|
+
const nowEntryCreator = buildEntryCreator(ctx);
|
|
17
|
+
const { withCtx } = NowCtx;
|
|
18
|
+
|
|
19
|
+
nowEntryCreator(
|
|
20
|
+
{
|
|
21
|
+
path: '/now',
|
|
22
|
+
label: 'cd ./now',
|
|
23
|
+
pageComponent: withCtx(
|
|
24
|
+
{
|
|
25
|
+
setLoading: loading => {
|
|
26
|
+
ctx.api.setLoading('now', loading);
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
pageNow
|
|
30
|
+
),
|
|
31
|
+
},
|
|
32
|
+
this.id
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default new NowPlugin();
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
export type StreamingItem = {
|
|
2
|
+
id: string;
|
|
3
|
+
author: string;
|
|
4
|
+
content: string;
|
|
5
|
+
type?: string;
|
|
6
|
+
createdAt: string;
|
|
7
|
+
};
|
|
2
8
|
|
|
3
|
-
// Format: "author: content" (with optional timestamp)
|
|
4
|
-
// Timestamp format: YYYY-mm-dd HH:MM:SS
|
|
5
9
|
export const formatStreamingData = (items: StreamingItem[]): string => {
|
|
6
10
|
if (!items || items.length === 0) {
|
|
7
11
|
return '';
|
package/src/plugins/xwy/index.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import { PathObj } from '@bbki.ng/ui';
|
|
2
|
-
|
|
3
1
|
import { IHostContext } from '#/types/hostApi';
|
|
4
2
|
import { IPlugin } from '#/types/plugin';
|
|
5
|
-
import {
|
|
3
|
+
import { HookPoint } from '#/types/slots';
|
|
6
4
|
|
|
7
5
|
import { SpecialTitle } from './components/article';
|
|
8
6
|
import { XwyLogo } from './components/logo';
|
|
9
7
|
import { FontRules, LOADING_CLASS } from './const';
|
|
10
8
|
import { loadFont } from './utils';
|
|
9
|
+
import {
|
|
10
|
+
transformBreadcrumbPaths,
|
|
11
|
+
transformTitleList,
|
|
12
|
+
transformPostContent,
|
|
13
|
+
pinTitle,
|
|
14
|
+
} from './transformers';
|
|
11
15
|
|
|
12
16
|
class XwyPlugin implements IPlugin {
|
|
13
17
|
id = 'fontstyler';
|
|
@@ -30,29 +34,7 @@ class XwyPlugin implements IPlugin {
|
|
|
30
34
|
}
|
|
31
35
|
});
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
ctx.api.registerMiddleware(
|
|
35
|
-
'transformTitleList',
|
|
36
|
-
this.transformTitleList,
|
|
37
|
-
this.id,
|
|
38
|
-
10 // weight
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
// 注册中间件,修改面包屑路径显示
|
|
42
|
-
ctx.api.registerMiddleware(
|
|
43
|
-
'transformBreadcrumbPath',
|
|
44
|
-
this.transformBreadcrumbPaths,
|
|
45
|
-
this.id,
|
|
46
|
-
10 // weight
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
// 注册中间件,修改文章内容
|
|
50
|
-
ctx.api.registerMiddleware(
|
|
51
|
-
'transformPostContent',
|
|
52
|
-
this.transformPostContent,
|
|
53
|
-
this.id,
|
|
54
|
-
10 // weight
|
|
55
|
-
);
|
|
37
|
+
this.batchRegisterMiddlewares(ctx);
|
|
56
38
|
|
|
57
39
|
// 注册logo插槽组件
|
|
58
40
|
ctx.api.registerSlot('logo', XwyLogo, this.id);
|
|
@@ -61,6 +43,22 @@ class XwyPlugin implements IPlugin {
|
|
|
61
43
|
ctx.api.registerSlot('articleTitle', SpecialTitle, this.id);
|
|
62
44
|
};
|
|
63
45
|
|
|
46
|
+
private trasformerMap: Partial<Record<HookPoint, Array<(baseData: any) => any>>> = {
|
|
47
|
+
transformTitleList: [pinTitle, transformTitleList],
|
|
48
|
+
transformBreadcrumbPath: [transformBreadcrumbPaths],
|
|
49
|
+
transformPostContent: [transformPostContent],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
private batchRegisterMiddlewares(ctx: IHostContext) {
|
|
53
|
+
for (const [point, fns] of Object.entries(this.trasformerMap) as Array<
|
|
54
|
+
[HookPoint, Array<(baseData: unknown) => unknown>]
|
|
55
|
+
>) {
|
|
56
|
+
fns.forEach(fn => {
|
|
57
|
+
ctx.api.registerMiddleware(point, fn, this.id, 10);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
64
62
|
private initFontLoadingListener() {
|
|
65
63
|
// listen font loading status and add class to body for styling
|
|
66
64
|
console.log('Initializing font loading listener');
|
|
@@ -70,58 +68,6 @@ class XwyPlugin implements IPlugin {
|
|
|
70
68
|
});
|
|
71
69
|
}
|
|
72
70
|
|
|
73
|
-
private transformPostContent = (content: string = '') => {
|
|
74
|
-
// 在文章内容中替换特定关键词为特殊样式
|
|
75
|
-
return content
|
|
76
|
-
.replace(
|
|
77
|
-
/小乌鸦/g,
|
|
78
|
-
`<span class="font-xwy text-content-special text-[1.6rem] align-middle leading-[1.09]">小乌鸦</span>`
|
|
79
|
-
)
|
|
80
|
-
.replace(
|
|
81
|
-
/公园/,
|
|
82
|
-
`公园 <span class="font-xwy-icon text-[#679867] text-[1.6rem] align-middle leading-[1.09]"></span>`
|
|
83
|
-
);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
private transformBreadcrumbPaths = (paths: PathObj[]) => {
|
|
87
|
-
const result = paths.map(p => {
|
|
88
|
-
if (p.name === '小乌鸦合集') {
|
|
89
|
-
return {
|
|
90
|
-
...p,
|
|
91
|
-
name: '小乌鸦合集',
|
|
92
|
-
className: 'font-xwy text-content-special text-[1.2rem] align-middle relative top-[1px]',
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return p;
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
console.log('Transformed breadcrumb paths:', result);
|
|
100
|
-
|
|
101
|
-
return result;
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* 转换标题列表,为匹配的标题添加字体类名
|
|
106
|
-
*/
|
|
107
|
-
private transformTitleList = (titleList: TitleListItem[]): TitleListItem[] => {
|
|
108
|
-
return titleList.map(item => {
|
|
109
|
-
for (const rule of FontRules) {
|
|
110
|
-
const match =
|
|
111
|
-
typeof rule.match === 'string' ? item.name === rule.match : rule.match.test(item.name);
|
|
112
|
-
|
|
113
|
-
if (match) {
|
|
114
|
-
return {
|
|
115
|
-
...item,
|
|
116
|
-
variant: rule.variant || 'default',
|
|
117
|
-
className: `font-${rule.fontFamily} ${rule.extraCls || ''}`.trim(),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return item;
|
|
122
|
-
});
|
|
123
|
-
};
|
|
124
|
-
|
|
125
71
|
onDisable = async () => {
|
|
126
72
|
this.loadedFonts.forEach(fontName => {
|
|
127
73
|
const styleEl = document.getElementById(`font-${fontName}`);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { PathObj } from '@bbki.ng/ui';
|
|
2
|
+
|
|
3
|
+
import { TitleListItem } from '#/types/posts';
|
|
4
|
+
|
|
5
|
+
import { FontRules } from '../const';
|
|
6
|
+
|
|
7
|
+
export const transformPostContent = (content: string = '') => {
|
|
8
|
+
// 在文章内容中替换特定关键词为特殊样式
|
|
9
|
+
return content
|
|
10
|
+
.replace(
|
|
11
|
+
/小乌鸦/g,
|
|
12
|
+
`<span class="font-xwy text-content-special text-[1.6rem] align-middle leading-[1.09]">小乌鸦</span>`
|
|
13
|
+
)
|
|
14
|
+
.replace(
|
|
15
|
+
/公园/,
|
|
16
|
+
`公园 <span class="font-xwy-icon text-[#679867] text-[1.6rem] align-middle leading-[1.09]"></span>`
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const pinTitle = (titleList: TitleListItem[]) => {
|
|
21
|
+
const pinnedTitle = '小乌鸦合集';
|
|
22
|
+
const index = titleList.findIndex(item => item.name === pinnedTitle);
|
|
23
|
+
if (index > -1) {
|
|
24
|
+
const [item] = titleList.splice(index, 1);
|
|
25
|
+
titleList.unshift(item);
|
|
26
|
+
}
|
|
27
|
+
return titleList;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const transformBreadcrumbPaths = (paths: PathObj[]) => {
|
|
31
|
+
const result = paths.map(p => {
|
|
32
|
+
if (p.name === '小乌鸦合集') {
|
|
33
|
+
return {
|
|
34
|
+
...p,
|
|
35
|
+
name: '小乌鸦合集',
|
|
36
|
+
className: 'font-xwy text-content-special text-[1.2rem] align-middle relative top-[1px]',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return p;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log('Transformed breadcrumb paths:', result);
|
|
44
|
+
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 转换标题列表,为匹配的标题添加字体类名
|
|
50
|
+
*/
|
|
51
|
+
export const transformTitleList = (titleList: TitleListItem[]): TitleListItem[] => {
|
|
52
|
+
return titleList.map(item => {
|
|
53
|
+
for (const rule of FontRules) {
|
|
54
|
+
const match =
|
|
55
|
+
typeof rule.match === 'string' ? item.name === rule.match : rule.match.test(item.name);
|
|
56
|
+
|
|
57
|
+
if (match) {
|
|
58
|
+
return {
|
|
59
|
+
...item,
|
|
60
|
+
variant: rule.variant || 'default',
|
|
61
|
+
className: `font-${rule.fontFamily} ${rule.extraCls || ''}`.trim(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return item;
|
|
66
|
+
});
|
|
67
|
+
};
|
package/src/types/hostApi.ts
CHANGED
package/src/types/plugin.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface IPlugin {
|
|
|
16
16
|
onDestroy?: () => void;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export type PluginID = 'sticker' | 'xwy' | 'extra-cd' | 'extra-entry';
|
|
19
|
+
export type PluginID = 'sticker' | 'xwy' | 'extra-cd' | 'extra-entry' | 'store' | 'now';
|
|
20
20
|
|
|
21
21
|
export interface IPluginEntry {
|
|
22
22
|
path: string;
|
|
@@ -32,3 +32,13 @@ export interface IPluginLoadingPayload {
|
|
|
32
32
|
id: PluginID;
|
|
33
33
|
loading: boolean;
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
export type PluginPerm = 'guest' | 'admin';
|
|
37
|
+
|
|
38
|
+
export interface IPluginManifestEntry {
|
|
39
|
+
id: PluginID;
|
|
40
|
+
name: string;
|
|
41
|
+
version: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
perm?: PluginPerm;
|
|
44
|
+
}
|