@bbki.ng/site 2.0.29 → 2.0.31
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 +4 -0
- package/index.html +2 -2
- package/package.json +3 -3
- package/src/blog/app.tsx +42 -49
- package/src/blog/hooks/use_blog_scroll_pos_restoration.ts +73 -0
- package/src/blog/hooks/use_clipboard_content.ts +16 -17
- package/src/blog/main.css +6 -0
- package/src/blog/pages/extensions/txt/article.tsx +4 -6
- package/src/blog/pages/extensions/txt/index.tsx +5 -1
package/CHANGELOG.md
CHANGED
package/index.html
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<html lang="en" class="h-full no-scrollbar">
|
|
3
3
|
<head>
|
|
4
4
|
<title>bbki.ng</title>
|
|
5
|
-
<meta name=
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<meta name="description" content="baby king" />
|
|
7
7
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
8
8
|
<link
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
</head>
|
|
42
42
|
|
|
43
43
|
<body class="h-full m-0 flex flex-col font-mono">
|
|
44
|
-
<div id="blog" class="flex-grow noto-serif"></div>
|
|
44
|
+
<div id="blog" class="flex-grow noto-serif no-scrollbar"></div>
|
|
45
45
|
<script type="module" src="/src/index.tsx"></script>
|
|
46
46
|
<script type="module">
|
|
47
47
|
// Import the functions you need from the SDKs you need
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bbki.ng/site",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.31",
|
|
4
4
|
"description": "code behind bbki.ng",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"react-cusdis": "^2.1.3",
|
|
25
25
|
"react-dom": "^18.0.0",
|
|
26
26
|
"react-hotkeys-hook": "^3.4.3",
|
|
27
|
-
"react-router-dom": "6",
|
|
27
|
+
"react-router-dom": "6.30.2",
|
|
28
28
|
"sonner": "1.4.0",
|
|
29
29
|
"swr": "^2.2.5",
|
|
30
30
|
"vaul": "1.1.2"
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"tailwindcss": "^3.0.7",
|
|
60
60
|
"ts-jest": "^27.1.1",
|
|
61
61
|
"typescript": "^4.5.4",
|
|
62
|
-
"vite": "
|
|
62
|
+
"vite": "7.2.4",
|
|
63
63
|
"vite-plugin-cross-origin-isolation": "0.1.6",
|
|
64
64
|
"vite-plugin-glsl": "1.2.1",
|
|
65
65
|
"vite-plugin-mdx": "^3.5.8",
|
package/src/blog/app.tsx
CHANGED
|
@@ -5,7 +5,6 @@ import { HotKeyNav } from "./components";
|
|
|
5
5
|
import { threeColWrapper } from "@/components/with_wrapper";
|
|
6
6
|
import { Cover } from "./pages";
|
|
7
7
|
|
|
8
|
-
import Png from "@/pages/extensions/png";
|
|
9
8
|
import ArticlePage from "@/pages/extensions/txt/article";
|
|
10
9
|
import NowPage from "@/pages/now";
|
|
11
10
|
import PhotoProjects from "@/pages/extensions/png/png_projects";
|
|
@@ -22,35 +21,32 @@ import { AppCtxMenu } from "@/components/app_ctx_menu";
|
|
|
22
21
|
import { Pochacco, PochaccoPose } from "@/components/Pochacco/Pochacco";
|
|
23
22
|
import { Role, useRole } from "@/hooks/use_role";
|
|
24
23
|
import { BotRedirect } from "@/pages/bot";
|
|
25
|
-
import { PluginInit } from "@/components/plugin/PluginInit";
|
|
26
24
|
import { BBContext } from "@/context/bbcontext";
|
|
27
25
|
import { PluginContentPage } from "@/components/plugin/PluginContentPage";
|
|
28
26
|
import { PluginRoutes } from "@/components/plugin/PluginRoutes";
|
|
29
|
-
import {useClipboardToPost} from "@/hooks/use_clipboard_to_post";
|
|
30
|
-
import {useSharedStringToPost} from "@/hooks/use_shared_string_to_post";
|
|
27
|
+
import { useClipboardToPost } from "@/hooks/use_clipboard_to_post";
|
|
28
|
+
import { useSharedStringToPost } from "@/hooks/use_shared_string_to_post";
|
|
31
29
|
|
|
32
30
|
const Layout = () => {
|
|
33
31
|
const { isLoading, isFontLoading } = useContext(GlobalLoadingContext);
|
|
34
32
|
const role = useRole();
|
|
35
33
|
const isQueen = role === Role.QUEEN;
|
|
36
34
|
return (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
/>
|
|
53
|
-
</>
|
|
35
|
+
<Page
|
|
36
|
+
nav={
|
|
37
|
+
<AppCtxMenu>
|
|
38
|
+
<Nav
|
|
39
|
+
paths={usePaths()}
|
|
40
|
+
className="gradient-blur-cover select-none"
|
|
41
|
+
loading={isLoading}
|
|
42
|
+
customLogo={
|
|
43
|
+
isQueen ? <Pochacco pose={PochaccoPose.Watching} /> : null
|
|
44
|
+
}
|
|
45
|
+
/>
|
|
46
|
+
</AppCtxMenu>
|
|
47
|
+
}
|
|
48
|
+
main={<Outlet />}
|
|
49
|
+
/>
|
|
54
50
|
);
|
|
55
51
|
};
|
|
56
52
|
|
|
@@ -63,43 +59,40 @@ const TagsResultInMidCol = threeColWrapper(TagsResult);
|
|
|
63
59
|
const CoverInMidCol = threeColWrapper(Cover);
|
|
64
60
|
|
|
65
61
|
export const App = () => {
|
|
62
|
+
useClipboardToPost();
|
|
66
63
|
|
|
67
|
-
|
|
64
|
+
useSharedStringToPost();
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return (
|
|
66
|
+
return (
|
|
72
67
|
<SWR>
|
|
73
|
-
{/*<EffectContextProvider>*/}
|
|
74
68
|
<HotKeyNav>
|
|
75
69
|
<BBContext>
|
|
76
|
-
|
|
77
|
-
<
|
|
78
|
-
<Route
|
|
79
|
-
<Route index element={<CoverInMidCol />} />
|
|
70
|
+
<Routes>
|
|
71
|
+
<Route path="/" element={<Layout />}>
|
|
72
|
+
<Route index element={<CoverInMidCol />} />
|
|
80
73
|
|
|
81
|
-
|
|
82
|
-
<Route path="
|
|
83
|
-
<Route path="
|
|
84
|
-
<Route path="tags" element={<TagsInMidCol />} />
|
|
85
|
-
<Route path="tags/:tag" element={<TagsResultInMidCol />} />
|
|
86
|
-
|
|
87
|
-
<Route path="bot" element={<BotRedirect />} />
|
|
88
|
-
<Route path="now" element={<NowInMidCol />} />
|
|
89
|
-
<Route path="login" element={<LoginInMidCol />} />
|
|
90
|
-
<Route path="upload" element={<UploadPage />} />
|
|
91
|
-
<Route path="/plugins" element={<PluginRoutes />} />
|
|
92
|
-
<Route
|
|
93
|
-
path="/plugins/:pluginRoute"
|
|
94
|
-
element={<PluginContentPage />}
|
|
95
|
-
/>
|
|
74
|
+
<Route path="blog" element={<Outlet />}>
|
|
75
|
+
<Route path="" element={<ContentInMidCol />} index />
|
|
76
|
+
<Route path=":title" element={<ArticleInMidCol />} />
|
|
96
77
|
</Route>
|
|
97
|
-
<Route path="
|
|
98
|
-
|
|
99
|
-
|
|
78
|
+
<Route path="blog/:title/:id" element={<PhotoProjects />} />
|
|
79
|
+
<Route path="tags" element={<TagsInMidCol />} />
|
|
80
|
+
<Route path="tags/:tag" element={<TagsResultInMidCol />} />
|
|
81
|
+
|
|
82
|
+
<Route path="bot" element={<BotRedirect />} />
|
|
83
|
+
<Route path="now" element={<NowInMidCol />} />
|
|
84
|
+
<Route path="login" element={<LoginInMidCol />} />
|
|
85
|
+
<Route path="upload" element={<UploadPage />} />
|
|
86
|
+
<Route path="/plugins" element={<PluginRoutes />} />
|
|
87
|
+
<Route
|
|
88
|
+
path="/plugins/:pluginRoute"
|
|
89
|
+
element={<PluginContentPage />}
|
|
90
|
+
/>
|
|
91
|
+
</Route>
|
|
92
|
+
<Route path="*" element={<NotFound />} />
|
|
93
|
+
</Routes>
|
|
100
94
|
</BBContext>
|
|
101
95
|
</HotKeyNav>
|
|
102
|
-
{/*</EffectContextProvider>*/}
|
|
103
96
|
</SWR>
|
|
104
97
|
);
|
|
105
98
|
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useLocation } from "react-router-dom";
|
|
3
|
+
|
|
4
|
+
const SCROLL_STORAGE_KEY = "div-scroll-positions";
|
|
5
|
+
|
|
6
|
+
function getScrollPositions(): Record<string, number> {
|
|
7
|
+
const stored = sessionStorage.getItem(SCROLL_STORAGE_KEY);
|
|
8
|
+
return stored ? JSON.parse(stored) : {};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function saveScrollPosition(key: string, position: number) {
|
|
12
|
+
const positions = getScrollPositions();
|
|
13
|
+
positions[key] = position;
|
|
14
|
+
sessionStorage.setItem(SCROLL_STORAGE_KEY, JSON.stringify(positions));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useBlogScrollReset() {
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const element = document.getElementById("blog");
|
|
20
|
+
if (!element) return;
|
|
21
|
+
|
|
22
|
+
element.scrollTop = 0;
|
|
23
|
+
}, []);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useBlogScrollRestoration(debounceMs: number = 100) {
|
|
27
|
+
const location = useLocation();
|
|
28
|
+
const isFirstRender = useRef(true);
|
|
29
|
+
const scrollTimeoutRef = useRef<number>();
|
|
30
|
+
const element = document.getElementById("blog");
|
|
31
|
+
const scrollKey = `blog`;
|
|
32
|
+
|
|
33
|
+
// Restore scroll position on mount
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!isFirstRender.current || !element) return;
|
|
36
|
+
|
|
37
|
+
const positions = getScrollPositions();
|
|
38
|
+
const savedPosition = positions[scrollKey];
|
|
39
|
+
if (!savedPosition) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
requestAnimationFrame(() => {
|
|
44
|
+
element.scrollTop = savedPosition;
|
|
45
|
+
});
|
|
46
|
+
isFirstRender.current = false;
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
// Save scroll position with debouncing
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!element) return;
|
|
52
|
+
|
|
53
|
+
const handleScroll = () => {
|
|
54
|
+
// Clear previous timeout
|
|
55
|
+
if (scrollTimeoutRef.current) {
|
|
56
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Debounce the save operation
|
|
60
|
+
scrollTimeoutRef.current = window.setTimeout(() => {
|
|
61
|
+
if (element) {
|
|
62
|
+
saveScrollPosition(scrollKey, element.scrollTop);
|
|
63
|
+
}
|
|
64
|
+
}, debounceMs);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
element.addEventListener("scroll", handleScroll, { passive: true });
|
|
68
|
+
|
|
69
|
+
return () => {
|
|
70
|
+
element.removeEventListener("scroll", handleScroll);
|
|
71
|
+
};
|
|
72
|
+
}, []);
|
|
73
|
+
}
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
3
|
export const useClipboardContent = () => {
|
|
4
|
-
|
|
4
|
+
const [clipboardContent, setClipboardContent] = React.useState<string | null>(
|
|
5
|
+
null
|
|
6
|
+
);
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
const handlePaste = (event: ClipboardEvent) => {
|
|
10
|
+
if (event.clipboardData) {
|
|
11
|
+
setClipboardContent(event.clipboardData.getData("text/plain"));
|
|
12
|
+
}
|
|
13
|
+
};
|
|
8
14
|
|
|
9
|
-
|
|
10
|
-
setClipboardContent(event.clipboardData.getData("text/plain"));
|
|
11
|
-
}
|
|
12
|
-
};
|
|
15
|
+
document.addEventListener("paste", handlePaste);
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
return () => {
|
|
18
|
+
document.removeEventListener("paste", handlePaste);
|
|
19
|
+
};
|
|
20
|
+
}, []);
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
};
|
|
19
|
-
}, []);
|
|
20
|
-
|
|
21
|
-
console.log(clipboardContent);
|
|
22
|
-
|
|
23
|
-
return clipboardContent;
|
|
24
|
-
}
|
|
22
|
+
return clipboardContent;
|
|
23
|
+
};
|
package/src/blog/main.css
CHANGED
|
@@ -3,13 +3,14 @@ import { MdxArticleList } from "@/articles";
|
|
|
3
3
|
import { withArticleWrapper } from "@/components";
|
|
4
4
|
import { MdxArticle } from "@/types/articles";
|
|
5
5
|
import { NotFound, DropZone } from "@bbki.ng/components";
|
|
6
|
-
import { useParams } from "react-router-dom";
|
|
6
|
+
import { useLocation, useParams } from "react-router-dom";
|
|
7
7
|
import { usePosts } from "@/hooks/use_posts";
|
|
8
8
|
import { ArticlePage } from "@/components/article";
|
|
9
9
|
import { GlobalLoadingContext } from "@/context/global_loading_state_provider";
|
|
10
10
|
import { useFile2Post } from "@/hooks/use_file_to_post";
|
|
11
11
|
import { useAuthed } from "@/hooks/use_authed";
|
|
12
12
|
import { ArticleCtxMenu } from "@/components/article_ctx_menu";
|
|
13
|
+
import { useBlogScrollReset } from "@/hooks/use_blog_scroll_pos_restoration";
|
|
13
14
|
|
|
14
15
|
type TArticleMap = {
|
|
15
16
|
[key: string]: ReactElement;
|
|
@@ -28,14 +29,11 @@ export default () => {
|
|
|
28
29
|
const { posts, isError, isLoading } = usePosts(title);
|
|
29
30
|
const { setIsLoading } = useContext(GlobalLoadingContext);
|
|
30
31
|
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
// scroll to top
|
|
33
|
-
window.scrollTo(0, 0);
|
|
34
|
-
}, []);
|
|
35
|
-
|
|
36
32
|
const reader = useFile2Post();
|
|
37
33
|
const isKing = useAuthed();
|
|
38
34
|
|
|
35
|
+
useBlogScrollReset();
|
|
36
|
+
|
|
39
37
|
if (!title) {
|
|
40
38
|
return <NotFound />;
|
|
41
39
|
}
|
|
@@ -6,7 +6,9 @@ import { CenterLinkList } from "@/components";
|
|
|
6
6
|
import { useAuthed } from "@/hooks/use_authed";
|
|
7
7
|
import { useFile2Post } from "@/hooks/use_file_to_post";
|
|
8
8
|
import { useSafeArticleLoading } from "@/hooks/use_safe_loading";
|
|
9
|
-
import {useClipboardToPost} from "@/hooks/use_clipboard_to_post";
|
|
9
|
+
import { useClipboardToPost } from "@/hooks/use_clipboard_to_post";
|
|
10
|
+
import { useLocation } from "react-router-dom";
|
|
11
|
+
import { useBlogScrollRestoration } from "@/hooks/use_blog_scroll_pos_restoration";
|
|
10
12
|
|
|
11
13
|
type TxtProps = {
|
|
12
14
|
title?: string;
|
|
@@ -18,6 +20,8 @@ const Posts = (props: TxtProps) => {
|
|
|
18
20
|
|
|
19
21
|
const isGlobalLoading = useSafeArticleLoading(0.2, 5);
|
|
20
22
|
|
|
23
|
+
useBlogScrollRestoration();
|
|
24
|
+
|
|
21
25
|
if (isLoading) {
|
|
22
26
|
return null;
|
|
23
27
|
}
|