@bbki.ng/site 5.4.14 → 5.4.16
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
CHANGED
package/index.html
CHANGED
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
</style>
|
|
56
56
|
<script src="https://unpkg.com/open-heart-element" type="module"></script>
|
|
57
57
|
<script type="module" src="https://cdn.jsdelivr.net/npm/@bbki.ng/bbimg@0.0.3/dist/index.js"></script>
|
|
58
|
-
<script type="module" src="https://cdn.jsdelivr.net/npm/@bbki.ng/bb-msg-history@0.
|
|
58
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@bbki.ng/bb-msg-history@1.0.0/dist/index.js"></script>
|
|
59
59
|
</head>
|
|
60
60
|
|
|
61
61
|
<body class="h-full m-0 flex flex-col font-mono">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bbki.ng/site",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.16",
|
|
4
4
|
"description": "code behind bbki.ng",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"sonner": "1.4.0",
|
|
21
21
|
"swr": "^2.2.5",
|
|
22
22
|
"vaul": "1.1.2",
|
|
23
|
-
"@bbki.ng/
|
|
24
|
-
"@bbki.ng/
|
|
23
|
+
"@bbki.ng/components": "5.2.7",
|
|
24
|
+
"@bbki.ng/stylebase": "3.1.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@mdx-js/mdx": "2.0.0-next.9",
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import useSWR from "swr";
|
|
2
|
+
import useSWRInfinite from "swr/infinite";
|
|
2
3
|
import { baseFetcher, withBBApi } from "@/utils";
|
|
3
4
|
import { API_CF_ENDPOINT } from "@/constants/routes";
|
|
4
|
-
import { useContext, useEffect, useState } from "react";
|
|
5
|
+
import { useContext, useEffect, useState, useCallback } from "react";
|
|
5
6
|
import { GlobalLoadingContext } from "@/context/global_loading_state_provider";
|
|
6
7
|
|
|
7
8
|
// In dev, use /api prefix to leverage Vite proxy to localhost:8787
|
|
8
9
|
const isProd = typeof window !== "undefined" && /^https:\/\/bbki.ng/.test(window.location.href);
|
|
9
|
-
const
|
|
10
|
-
? (resource: string, init?: RequestInit) => baseFetcher(`/api/${resource}`, init)
|
|
11
|
-
: withBBApi(baseFetcher)(API_CF_ENDPOINT);
|
|
10
|
+
const API_BASE = !isProd ? "/api" : API_CF_ENDPOINT;
|
|
12
11
|
|
|
13
12
|
export type StreamingItem = {
|
|
14
13
|
id: string;
|
|
@@ -18,10 +17,76 @@ export type StreamingItem = {
|
|
|
18
17
|
createdAt: string;
|
|
19
18
|
};
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
interface StreamingResponse {
|
|
21
|
+
status: string;
|
|
22
|
+
data: StreamingItem[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface StreamingQueryParams {
|
|
26
|
+
/** Fetch records older than this ID (for pagination / next page) */
|
|
27
|
+
before?: string;
|
|
28
|
+
/** Fetch records newer than this ID (for polling / new messages) */
|
|
29
|
+
after?: string;
|
|
30
|
+
/** Number of records to fetch (default: 8, max: 100) */
|
|
31
|
+
offset?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Build streaming API URL with query parameters
|
|
36
|
+
*/
|
|
37
|
+
function buildStreamingUrl(params: StreamingQueryParams = {}): string {
|
|
38
|
+
const url = new URL(`${API_BASE}/streaming`, !isProd ? window.location.origin : undefined);
|
|
39
|
+
|
|
40
|
+
if (params.before) {
|
|
41
|
+
url.searchParams.set("before", params.before);
|
|
42
|
+
}
|
|
43
|
+
if (params.after) {
|
|
44
|
+
url.searchParams.set("after", params.after);
|
|
45
|
+
}
|
|
46
|
+
if (params.offset) {
|
|
47
|
+
url.searchParams.set("offset", params.offset.toString());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return !isProd ? url.pathname + url.search : url.toString();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Fetch streaming data from API
|
|
55
|
+
*/
|
|
56
|
+
async function fetchStreaming(params: StreamingQueryParams = {}): Promise<StreamingResponse> {
|
|
57
|
+
const url = buildStreamingUrl(params);
|
|
58
|
+
const response = await baseFetcher(url);
|
|
59
|
+
return response as StreamingResponse;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// SWR key generator for streaming queries
|
|
63
|
+
const getStreamingKey = (params: StreamingQueryParams) => {
|
|
64
|
+
const parts = ["streaming"];
|
|
65
|
+
if (params.before) parts.push(`before=${params.before}`);
|
|
66
|
+
if (params.after) parts.push(`after=${params.after}`);
|
|
67
|
+
if (params.offset) parts.push(`offset=${params.offset}`);
|
|
68
|
+
return parts.join("?");
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
interface UseStreamingOptions {
|
|
72
|
+
/** Polling interval in ms (default: 1000, set to 0 to disable) */
|
|
73
|
+
refreshInterval?: number;
|
|
74
|
+
/** Initial offset for first fetch (default: 8) */
|
|
75
|
+
offset?: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Hook for fetching streaming data with polling
|
|
80
|
+
* Use this for simple cases where you just need the latest data
|
|
81
|
+
*/
|
|
82
|
+
export function useStreaming(options: UseStreamingOptions = {}) {
|
|
83
|
+
const { refreshInterval = 1000, offset = 8 } = options;
|
|
84
|
+
|
|
85
|
+
const key = getStreamingKey({ offset });
|
|
86
|
+
|
|
87
|
+
const { data, error, mutate } = useSWR(key, () => fetchStreaming({ offset }), {
|
|
23
88
|
revalidateOnFocus: true,
|
|
24
|
-
refreshInterval
|
|
89
|
+
refreshInterval,
|
|
25
90
|
});
|
|
26
91
|
|
|
27
92
|
const isLoading = !data && !error;
|
|
@@ -33,7 +98,7 @@ export const useStreaming = () => {
|
|
|
33
98
|
customElements.whenDefined("bb-msg-history").then(() => {
|
|
34
99
|
forceUpdate((prev) => prev + 1);
|
|
35
100
|
});
|
|
36
|
-
}, [])
|
|
101
|
+
}, []);
|
|
37
102
|
|
|
38
103
|
const { setIsLoading } = useContext(GlobalLoadingContext);
|
|
39
104
|
|
|
@@ -42,8 +107,10 @@ export const useStreaming = () => {
|
|
|
42
107
|
}, [isLoading, setIsLoading]);
|
|
43
108
|
|
|
44
109
|
return {
|
|
45
|
-
streaming: data?.data
|
|
110
|
+
streaming: data?.data ?? [],
|
|
46
111
|
isError: error,
|
|
47
112
|
isLoading,
|
|
113
|
+
/** Refetch data manually */
|
|
114
|
+
mutate,
|
|
48
115
|
};
|
|
49
|
-
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import classNames from "classnames";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
export const ArrowDownIcon = ({ show }: { show: boolean }) => (
|
|
5
|
+
<svg
|
|
6
|
+
data-testid="geist-icon"
|
|
7
|
+
height="16"
|
|
8
|
+
stroke-linejoin="round"
|
|
9
|
+
viewBox="0 0 16 16"
|
|
10
|
+
width="16"
|
|
11
|
+
style={{
|
|
12
|
+
rotate: '180deg'
|
|
13
|
+
}}
|
|
14
|
+
className={classNames("transition-opacity duration-200 inline-block", {
|
|
15
|
+
"opacity-0": !show,
|
|
16
|
+
"opacity-100": show,
|
|
17
|
+
})}
|
|
18
|
+
>
|
|
19
|
+
<path
|
|
20
|
+
fill-rule="evenodd"
|
|
21
|
+
clip-rule="evenodd"
|
|
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
|
+
fill="currentColor"
|
|
24
|
+
></path>
|
|
25
|
+
</svg>
|
|
26
|
+
)
|
|
@@ -4,6 +4,7 @@ import { formatStreamingData } from "@/utils/streaming";
|
|
|
4
4
|
import { Article, Button, ButtonType, Panel } from "@bbki.ng/components";
|
|
5
5
|
import { useScrollBtnVisibility } from "./useScrollBtnVisibility";
|
|
6
6
|
import classNames from "classnames";
|
|
7
|
+
import { ArrowDownIcon } from "./arrow-down";
|
|
7
8
|
|
|
8
9
|
// Extend JSX IntrinsicElements for the web component
|
|
9
10
|
declare global {
|
|
@@ -29,16 +30,42 @@ const Streaming = () => {
|
|
|
29
30
|
const { streaming, isLoading, isError } = useStreaming();
|
|
30
31
|
const bbMsgHistoryRef = useRef<BbMsgHistoryElement>(null);
|
|
31
32
|
|
|
33
|
+
const [scrolled, setScrolled] = useState(false);
|
|
34
|
+
|
|
32
35
|
const showScrollBtn = useScrollBtnVisibility(bbMsgHistoryRef.current!);
|
|
33
36
|
|
|
37
|
+
const formattedData = formatStreamingData(streaming || []);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const el = bbMsgHistoryRef.current;
|
|
41
|
+
let timer: NodeJS.Timeout;
|
|
42
|
+
if (!isLoading && el) {
|
|
43
|
+
// 检查自定义元素是否已定义并升级
|
|
44
|
+
if (el.scrollToBottom) {
|
|
45
|
+
requestAnimationFrame(() => {
|
|
46
|
+
el.scrollToBottom();
|
|
47
|
+
});
|
|
48
|
+
} else {
|
|
49
|
+
// 等待 custom element 定义完成
|
|
50
|
+
customElements.whenDefined('bb-msg-history').then(() => {
|
|
51
|
+
el.scrollToBottom?.();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// delay to set scrolled state after scrollToBottom
|
|
56
|
+
timer = setTimeout(() => {
|
|
57
|
+
setScrolled(true);
|
|
58
|
+
}, 500);
|
|
59
|
+
}
|
|
60
|
+
return () => {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
};
|
|
63
|
+
}, [isLoading, formattedData])
|
|
64
|
+
|
|
34
65
|
if (isError) {
|
|
35
66
|
return <div className="p-8 text-center text-gray-500">加载失败</div>;
|
|
36
67
|
}
|
|
37
68
|
|
|
38
|
-
const formattedData = formatStreamingData(streaming || []);
|
|
39
|
-
|
|
40
|
-
console.log("showScrollBtn", showScrollBtn);
|
|
41
|
-
|
|
42
69
|
return (
|
|
43
70
|
<Article title="直播" loading={isLoading}>
|
|
44
71
|
{isLoading ? null : (
|
|
@@ -54,34 +81,18 @@ const Streaming = () => {
|
|
|
54
81
|
{formattedData}
|
|
55
82
|
</bb-msg-history>
|
|
56
83
|
</Panel>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
style={{
|
|
70
|
-
rotate: '180deg'
|
|
71
|
-
}}
|
|
72
|
-
className={classNames("transition-opacity duration-300 inline-block", {
|
|
73
|
-
"opacity-0": !showScrollBtn,
|
|
74
|
-
"opacity-100": showScrollBtn,
|
|
75
|
-
})}
|
|
76
|
-
>
|
|
77
|
-
<path
|
|
78
|
-
fill-rule="evenodd"
|
|
79
|
-
clip-rule="evenodd"
|
|
80
|
-
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"
|
|
81
|
-
fill="currentColor"
|
|
82
|
-
></path>
|
|
83
|
-
</svg>
|
|
84
|
-
</Button>
|
|
84
|
+
{
|
|
85
|
+
scrolled ? (
|
|
86
|
+
<Button
|
|
87
|
+
className="mt-64"
|
|
88
|
+
transparent={!showScrollBtn}
|
|
89
|
+
onClick={() => {
|
|
90
|
+
bbMsgHistoryRef.current?.scrollToBottom();
|
|
91
|
+
}}>
|
|
92
|
+
<ArrowDownIcon show={showScrollBtn} />
|
|
93
|
+
</Button>
|
|
94
|
+
) : null
|
|
95
|
+
}
|
|
85
96
|
</>
|
|
86
97
|
)}
|
|
87
98
|
</Article>
|