@bbki.ng/site 0.0.17
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/.github/workflows/deploy.yml +44 -0
- package/.husky/pre-commit +4 -0
- package/.prettierignore +1 -0
- package/.prettierrc.json +1 -0
- package/.rush/temp/shrinkwrap-deps.json +908 -0
- package/CODE_OF_CONDUCT.md +45 -0
- package/CONTRIBUTING.md +11 -0
- package/LICENSE +21 -0
- package/README.md +21 -0
- package/index.html +33 -0
- package/jest.config.js +15 -0
- package/package.json +69 -0
- package/postcss.config.cjs +6 -0
- package/public/Logo.svg +9 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +9 -0
- package/public/pwa-192x192.png +0 -0
- package/public/pwa-512x512.png +0 -0
- package/public/robots.txt +2 -0
- package/src/__test__/utils/index.test.ts +90 -0
- package/src/app.tsx +97 -0
- package/src/articles/anti-logic.mdx +61 -0
- package/src/articles/bbking-manual.mdx +88 -0
- package/src/articles/black-river.mdx +8 -0
- package/src/articles/cooldown.mdx +12 -0
- package/src/articles/fall.mdx +8 -0
- package/src/articles/img.mdx +104 -0
- package/src/articles/index.ts +35 -0
- package/src/articles/loading.mdx +129 -0
- package/src/articles/major-cold.mdx +14 -0
- package/src/articles/men-without-women.mdx +19 -0
- package/src/articles/movie-day.mdx +15 -0
- package/src/articles/now.mdx +15 -0
- package/src/articles/projects.mdx +9 -0
- package/src/articles/quote.mdx +27 -0
- package/src/articles/spring-cooldown.mdx +7 -0
- package/src/articles/spring-rain.mdx +9 -0
- package/src/articles/travel.mdx +21 -0
- package/src/articles/warming-up.mdx +10 -0
- package/src/articles/web-burnning.mdx +10 -0
- package/src/auth_required.tsx +17 -0
- package/src/components/Spinner.tsx +33 -0
- package/src/components/article/index.tsx +31 -0
- package/src/components/aspect_ratio_box/index.tsx +29 -0
- package/src/components/blur_cover/index.tsx +28 -0
- package/src/components/book_list/index.tsx +50 -0
- package/src/components/comment/index.tsx +70 -0
- package/src/components/comment/use_cusdis_event.ts +37 -0
- package/src/components/corner_prompt_box/index.tsx +63 -0
- package/src/components/disabled_text/index.tsx +23 -0
- package/src/components/fade_out_cover/index.tsx +37 -0
- package/src/components/footer/footer_links.ts +13 -0
- package/src/components/footer/index.tsx +21 -0
- package/src/components/hotkey_nav/index.tsx +50 -0
- package/src/components/img_list/index.tsx +43 -0
- package/src/components/index.tsx +27 -0
- package/src/components/movie_list/index.tsx +50 -0
- package/src/components/my_suspense.tsx +14 -0
- package/src/components/progress_bar/index.tsx +31 -0
- package/src/components/reload_prompt/index.tsx +36 -0
- package/src/components/stickers/index.tsx +46 -0
- package/src/components/table_skeleton/index.tsx +40 -0
- package/src/components/tags/index.tsx +52 -0
- package/src/components/video_player/index.tsx +81 -0
- package/src/components/with_wrapper/index.tsx +13 -0
- package/src/constants/cusdis.ts +6 -0
- package/src/constants/index.ts +16 -0
- package/src/constants/photo_projects.ts +54 -0
- package/src/constants/photos.ts +270 -0
- package/src/constants/routes.ts +24 -0
- package/src/constants/video_logs.ts +16 -0
- package/src/demo/DemoBox.tsx +15 -0
- package/src/demo/ImgDemo.tsx +34 -0
- package/src/demo/SpinnerDemo.tsx +17 -0
- package/src/global/mdx.d.ts +8 -0
- package/src/global_loading_state_provider.tsx +27 -0
- package/src/hooks/index.ts +15 -0
- package/src/hooks/useScrollToTop.ts +24 -0
- package/src/hooks/useTransitionCls.ts +36 -0
- package/src/hooks/use_img_loading.ts +16 -0
- package/src/hooks/use_pathname.ts +6 -0
- package/src/hooks/use_paths.ts +30 -0
- package/src/hooks/use_projects.ts +56 -0
- package/src/hooks/use_route_name.ts +7 -0
- package/src/hooks/use_supa_session.ts +30 -0
- package/src/hooks/use_uploader.ts +34 -0
- package/src/hooks/use_video_controls.ts +71 -0
- package/src/main.css +156 -0
- package/src/main.tsx +19 -0
- package/src/pages/cover/index.tsx +10 -0
- package/src/pages/extensions/png/consts.ts +9 -0
- package/src/pages/extensions/png/index.tsx +41 -0
- package/src/pages/extensions/png/png_projects.tsx +63 -0
- package/src/pages/extensions/txt/article.tsx +26 -0
- package/src/pages/extensions/txt/consts.ts +8 -0
- package/src/pages/extensions/txt/index.tsx +21 -0
- package/src/pages/index.tsx +14 -0
- package/src/pages/login/index.tsx +33 -0
- package/src/pages/now/index.tsx +7 -0
- package/src/pages/tags/index.tsx +28 -0
- package/src/pages/tags/tag_result.tsx +19 -0
- package/src/swr.tsx +18 -0
- package/src/types/articles.ts +6 -0
- package/src/types/color.ts +21 -0
- package/src/types/cusdis.ts +4 -0
- package/src/types/oss.ts +15 -0
- package/src/types/path.ts +11 -0
- package/src/types/photo.ts +17 -0
- package/src/types/supabase.ts +9 -0
- package/src/utils/index.ts +143 -0
- package/src/utils/tags.ts +21 -0
- package/tailwind.config.cjs +10 -0
- package/tsconfig.json +24 -0
- package/vite.config.ts +108 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export const useImgLoading = (src: string) => {
|
|
4
|
+
const [loading, setLoading] = useState(true);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
let img = new Image();
|
|
7
|
+
img.src = src;
|
|
8
|
+
img.onload = () => {
|
|
9
|
+
setLoading(false);
|
|
10
|
+
};
|
|
11
|
+
return () => {
|
|
12
|
+
img.src = "";
|
|
13
|
+
};
|
|
14
|
+
}, []);
|
|
15
|
+
return loading;
|
|
16
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { pathObj } from "@/types/path";
|
|
2
|
+
import { usePathName } from "@/hooks/use_pathname";
|
|
3
|
+
|
|
4
|
+
export const usePaths = (): pathObj[] => {
|
|
5
|
+
const pathname = usePathName();
|
|
6
|
+
|
|
7
|
+
if (pathname === "/") {
|
|
8
|
+
return [{ name: "~" }];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const pathNameArr = pathname.split("/");
|
|
12
|
+
|
|
13
|
+
const pathsArr: string[] = pathNameArr.map((p: string, index: number) => {
|
|
14
|
+
return pathNameArr
|
|
15
|
+
.slice(0, index + 1)
|
|
16
|
+
.join("/")
|
|
17
|
+
.replace(/^$/, "/");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return pathsArr.map((path, index) => {
|
|
21
|
+
const isLast = index === pathsArr.length - 1;
|
|
22
|
+
const name = decodeURIComponent(pathNameArr[index].replace(/^$/, "~"));
|
|
23
|
+
return isLast
|
|
24
|
+
? { name }
|
|
25
|
+
: {
|
|
26
|
+
name,
|
|
27
|
+
path,
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import useSWR, { useSWRConfig } from "swr";
|
|
2
|
+
import { API } from "@/constants/routes";
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
import { Photo } from "@/types/photo";
|
|
5
|
+
|
|
6
|
+
export const useProjects = (name: string = "", suspense?: boolean) => {
|
|
7
|
+
const URL = `${API.PROJECTS}${name ? "/" : ""}${name}`;
|
|
8
|
+
|
|
9
|
+
const { data, error } = useSWR(URL, {
|
|
10
|
+
revalidateOnFocus: false,
|
|
11
|
+
suspense,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const { mutate, cache } = useSWRConfig();
|
|
15
|
+
|
|
16
|
+
const getCachedProjects = (n: string) => {
|
|
17
|
+
if (!n) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
return cache.get("projects")?.find((p: { name: string }) => p.name === n);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const refresh = useCallback(() => {
|
|
24
|
+
return mutate(URL);
|
|
25
|
+
}, [URL]);
|
|
26
|
+
|
|
27
|
+
const addLocalPhotoImmediately = useCallback(
|
|
28
|
+
(photo: Photo) => {
|
|
29
|
+
if (!name) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!data || error) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return mutate(
|
|
38
|
+
URL,
|
|
39
|
+
{
|
|
40
|
+
...data,
|
|
41
|
+
images: [photo, ...data.images],
|
|
42
|
+
},
|
|
43
|
+
false
|
|
44
|
+
);
|
|
45
|
+
},
|
|
46
|
+
[data, error]
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
refresh,
|
|
51
|
+
addLocalPhotoImmediately,
|
|
52
|
+
projects: data || getCachedProjects(name),
|
|
53
|
+
isError: error,
|
|
54
|
+
isLoading: !data && !error,
|
|
55
|
+
};
|
|
56
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createClient, Session } from "@supabase/supabase-js";
|
|
2
|
+
import { SUPABASE } from "@/constants";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { BBKingSession } from "@/types/supabase";
|
|
5
|
+
|
|
6
|
+
export const useSupabaseSession = (): BBKingSession | null => {
|
|
7
|
+
const supabase = createClient(SUPABASE.URL, SUPABASE.ANNO);
|
|
8
|
+
|
|
9
|
+
const extendSess = (sess: Session | null) => {
|
|
10
|
+
if (!sess) {
|
|
11
|
+
return sess;
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
...sess,
|
|
15
|
+
isKing: sess.user?.id === SUPABASE.BB_KING_ID,
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const [session, setSession] = useState(extendSess(supabase.auth.session()));
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
supabase.auth.onAuthStateChange((event, session) => {
|
|
23
|
+
if (event === "SIGNED_IN") {
|
|
24
|
+
setSession(extendSess(session));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
return session;
|
|
30
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { useSupabaseSession } from "@/hooks/use_supa_session";
|
|
3
|
+
import { apiFetcher, getImageFileSize, withToken } from "@/utils";
|
|
4
|
+
import { Photo } from "@/types/photo";
|
|
5
|
+
import { API } from "@/constants/routes";
|
|
6
|
+
import { UploadResult } from "@/types/oss";
|
|
7
|
+
|
|
8
|
+
export const useUploader = () => {
|
|
9
|
+
const { access_token: token } = useSupabaseSession() || {};
|
|
10
|
+
const authedApiFetcher = withToken(apiFetcher)(token);
|
|
11
|
+
|
|
12
|
+
return useCallback(
|
|
13
|
+
async (pid: string, projectName: string, file: File): Promise<Photo> => {
|
|
14
|
+
const { width, height } = await getImageFileSize(file);
|
|
15
|
+
|
|
16
|
+
const url = `${API.UPLOAD_IMG}?pid=${pid}&fileName=${file.name}&projectName=${projectName}&width=${width}&height=${height}`;
|
|
17
|
+
|
|
18
|
+
const result = (await authedApiFetcher(url, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: {
|
|
21
|
+
"Content-Type": "application/octet-stream",
|
|
22
|
+
},
|
|
23
|
+
body: file,
|
|
24
|
+
})) as UploadResult;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
width,
|
|
28
|
+
height,
|
|
29
|
+
src: result.url,
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
[token]
|
|
33
|
+
);
|
|
34
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ReactEventHandler,
|
|
3
|
+
useRef,
|
|
4
|
+
useState,
|
|
5
|
+
RefObject,
|
|
6
|
+
useEffect,
|
|
7
|
+
} from "react";
|
|
8
|
+
|
|
9
|
+
export const useVideoControls = () => {
|
|
10
|
+
const videoRef = useRef<HTMLVideoElement>(null);
|
|
11
|
+
const [isPlay, setIsPlay] = useState(false);
|
|
12
|
+
|
|
13
|
+
const toggle = async () => {
|
|
14
|
+
const { current: video } = videoRef;
|
|
15
|
+
|
|
16
|
+
if (!video) return;
|
|
17
|
+
|
|
18
|
+
if (video.paused || video.ended) {
|
|
19
|
+
await video.play();
|
|
20
|
+
setIsPlay(true);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
video.pause();
|
|
24
|
+
setIsPlay(false);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const isEnd = videoRef && videoRef.current && videoRef.current.ended;
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
videoRef,
|
|
31
|
+
isPlay: isPlay && !isEnd,
|
|
32
|
+
toggle,
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const useVideoProgress = () => {
|
|
37
|
+
const [progress, setProgress] = useState(0);
|
|
38
|
+
const onTimeUpdate: ReactEventHandler<HTMLVideoElement> = (event) => {
|
|
39
|
+
const video = event.currentTarget;
|
|
40
|
+
if (!video.duration) {
|
|
41
|
+
setProgress(0);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
setProgress(video.currentTime / video.duration);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return { progress, onTimeUpdate };
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const useVideoEleHeight = (
|
|
51
|
+
videoRef: RefObject<HTMLVideoElement>
|
|
52
|
+
): number => {
|
|
53
|
+
const [height, setHeight] = useState(0);
|
|
54
|
+
const updateHeight = () => {
|
|
55
|
+
if (!videoRef.current) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const { height } = videoRef.current.getBoundingClientRect();
|
|
59
|
+
setHeight(height);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
window.addEventListener("resize", updateHeight);
|
|
64
|
+
return () => {
|
|
65
|
+
window.removeEventListener("resize", updateHeight);
|
|
66
|
+
};
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
useEffect(updateHeight);
|
|
70
|
+
return height;
|
|
71
|
+
};
|
package/src/main.css
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
@layer utilities {
|
|
5
|
+
/* Chrome, Safari and Opera */
|
|
6
|
+
.no-scrollbar::-webkit-scrollbar {
|
|
7
|
+
display: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.no-scrollbar {
|
|
11
|
+
-ms-overflow-style: none; /* IE and Edge */
|
|
12
|
+
scrollbar-width: none; /* Firefox */
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@layer base {
|
|
17
|
+
.blur-cover {
|
|
18
|
+
background-color: rgba(255, 255, 255, 0.5);
|
|
19
|
+
backdrop-filter: blur(7px);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.prose pre {
|
|
23
|
+
color: #24292f !important;
|
|
24
|
+
background-color: #f6f8fa !important;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
a:focus {
|
|
28
|
+
@apply outline-none;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* syntax highlight */
|
|
32
|
+
/*!
|
|
33
|
+
Theme: GitHub
|
|
34
|
+
Description: Light theme as seen on github.com
|
|
35
|
+
Author: github.com
|
|
36
|
+
Maintainer: @Hirse
|
|
37
|
+
Updated: 2021-05-15
|
|
38
|
+
Outdated base version: https://github.com/primer/github-syntax-light
|
|
39
|
+
Current colors taken from GitHub's CSS
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
.hljs {
|
|
43
|
+
color: #24292e;
|
|
44
|
+
background: #ffffff;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.hljs-doctag,
|
|
48
|
+
.hljs-keyword,
|
|
49
|
+
.hljs-meta .hljs-keyword,
|
|
50
|
+
.hljs-template-tag,
|
|
51
|
+
.hljs-template-variable,
|
|
52
|
+
.hljs-type,
|
|
53
|
+
.hljs-variable.language_ {
|
|
54
|
+
/* prettylights-syntax-keyword */
|
|
55
|
+
color: #d73a49;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.hljs-title,
|
|
59
|
+
.hljs-title.class_,
|
|
60
|
+
.hljs-title.class_.inherited__,
|
|
61
|
+
.hljs-title.function_ {
|
|
62
|
+
/* prettylights-syntax-entity */
|
|
63
|
+
color: #6f42c1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.hljs-attr,
|
|
67
|
+
.hljs-attribute,
|
|
68
|
+
.hljs-literal,
|
|
69
|
+
.hljs-meta,
|
|
70
|
+
.hljs-number,
|
|
71
|
+
.hljs-operator,
|
|
72
|
+
.hljs-variable,
|
|
73
|
+
.hljs-selector-attr,
|
|
74
|
+
.hljs-selector-class,
|
|
75
|
+
.hljs-selector-id {
|
|
76
|
+
/* prettylights-syntax-constant */
|
|
77
|
+
color: #005cc5;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.hljs-regexp,
|
|
81
|
+
.hljs-string,
|
|
82
|
+
.hljs-meta .hljs-string {
|
|
83
|
+
/* prettylights-syntax-string */
|
|
84
|
+
color: #032f62;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.hljs-built_in,
|
|
88
|
+
.hljs-symbol {
|
|
89
|
+
/* prettylights-syntax-variable */
|
|
90
|
+
color: #e36209;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.hljs-comment,
|
|
94
|
+
.hljs-code,
|
|
95
|
+
.hljs-formula {
|
|
96
|
+
/* prettylights-syntax-comment */
|
|
97
|
+
color: #6a737d;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.hljs-name,
|
|
101
|
+
.hljs-quote,
|
|
102
|
+
.hljs-selector-tag,
|
|
103
|
+
.hljs-selector-pseudo {
|
|
104
|
+
/* prettylights-syntax-entity-tag */
|
|
105
|
+
color: #22863a;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.hljs-subst {
|
|
109
|
+
/* prettylights-syntax-storage-modifier-import */
|
|
110
|
+
color: #24292e;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.hljs-section {
|
|
114
|
+
/* prettylights-syntax-markup-heading */
|
|
115
|
+
color: #005cc5;
|
|
116
|
+
font-weight: bold;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.hljs-bullet {
|
|
120
|
+
/* prettylights-syntax-markup-list */
|
|
121
|
+
color: #735c0f;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.hljs-emphasis {
|
|
125
|
+
/* prettylights-syntax-markup-italic */
|
|
126
|
+
color: #24292e;
|
|
127
|
+
font-style: italic;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.hljs-strong {
|
|
131
|
+
/* prettylights-syntax-markup-bold */
|
|
132
|
+
color: #24292e;
|
|
133
|
+
font-weight: bold;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.hljs-addition {
|
|
137
|
+
/* prettylights-syntax-markup-inserted */
|
|
138
|
+
color: #22863a;
|
|
139
|
+
background-color: #f0fff4;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.hljs-deletion {
|
|
143
|
+
/* prettylights-syntax-markup-deleted */
|
|
144
|
+
color: #b31d28;
|
|
145
|
+
background-color: #ffeef0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.hljs-char.escape_,
|
|
149
|
+
.hljs-link,
|
|
150
|
+
.hljs-params,
|
|
151
|
+
.hljs-property,
|
|
152
|
+
.hljs-punctuation,
|
|
153
|
+
.hljs-tag {
|
|
154
|
+
/* purposely ignored */
|
|
155
|
+
}
|
|
156
|
+
}
|
package/src/main.tsx
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { BrowserRouter as Router } from "react-router-dom";
|
|
4
|
+
import { ReloadPrompt } from "@/components";
|
|
5
|
+
import "@bbki.ng/components/style";
|
|
6
|
+
import App from "./app";
|
|
7
|
+
import "./main.css";
|
|
8
|
+
|
|
9
|
+
const container = document.getElementById("root") as Element;
|
|
10
|
+
const root = createRoot(container);
|
|
11
|
+
|
|
12
|
+
root.render(
|
|
13
|
+
<React.StrictMode>
|
|
14
|
+
<Router>
|
|
15
|
+
<ReloadPrompt />
|
|
16
|
+
<App />
|
|
17
|
+
</Router>
|
|
18
|
+
</React.StrictMode>
|
|
19
|
+
);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Article } from "@bbki.ng/components";
|
|
3
|
+
|
|
4
|
+
export const Cover = (props: { className: string }) => {
|
|
5
|
+
return (
|
|
6
|
+
<Article title="">
|
|
7
|
+
<div className="bg-white">This page intentionally left blank</div>
|
|
8
|
+
</Article>
|
|
9
|
+
);
|
|
10
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React, { useCallback, useEffect } from "react";
|
|
2
|
+
import classnames from "classnames";
|
|
3
|
+
import { useProjects } from "@/hooks/use_projects";
|
|
4
|
+
import { Link, Gallery, ImageRenderer } from "@bbki.ng/components";
|
|
5
|
+
import { MySuspense } from "@/components";
|
|
6
|
+
import { imageFormatter } from "@/utils";
|
|
7
|
+
|
|
8
|
+
const Projects = () => {
|
|
9
|
+
const { projects } = useProjects("", true);
|
|
10
|
+
const renderImage: ImageRenderer = useCallback(
|
|
11
|
+
(img, index, col) => {
|
|
12
|
+
const project = projects[index];
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
className={classnames("mb-128", {
|
|
16
|
+
"md:mr-64": col === 0,
|
|
17
|
+
"md:ml-64": col !== 0,
|
|
18
|
+
})}
|
|
19
|
+
>
|
|
20
|
+
<Link to={`/projects/${project.name}`}>{img}</Link>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
},
|
|
24
|
+
[projects]
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Gallery
|
|
29
|
+
images={projects.map((p: any) => p.cover).map(imageFormatter)}
|
|
30
|
+
imageRenderer={renderImage}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default () => {
|
|
36
|
+
return (
|
|
37
|
+
<MySuspense>
|
|
38
|
+
<Projects />
|
|
39
|
+
</MySuspense>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React, { useContext, useEffect } from "react";
|
|
2
|
+
import { MySuspense } from "@/components";
|
|
3
|
+
import { useParams } from "react-router-dom";
|
|
4
|
+
import { useProjects } from "@/hooks/use_projects";
|
|
5
|
+
import { AuthRequired } from "@/auth_required";
|
|
6
|
+
import { imageFormatter } from "@/utils";
|
|
7
|
+
import { DropImage, Gallery, Nav } from "@bbki.ng/components";
|
|
8
|
+
import { useUploader } from "@/hooks/use_uploader";
|
|
9
|
+
import { GlobalLoadingContext } from "@/global_loading_state_provider";
|
|
10
|
+
import { usePaths } from "@/hooks";
|
|
11
|
+
|
|
12
|
+
const ProjectDetail = () => {
|
|
13
|
+
const { id } = useParams();
|
|
14
|
+
const { setIsLoading } = useContext(GlobalLoadingContext);
|
|
15
|
+
const uploader = useUploader();
|
|
16
|
+
const { projects, refresh } = useProjects(id, true);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
return () => {
|
|
20
|
+
refresh().then(() => {});
|
|
21
|
+
};
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
const renderUploader = () => (
|
|
25
|
+
<AuthRequired shouldBeKing>
|
|
26
|
+
<DropImage
|
|
27
|
+
className="mb-256"
|
|
28
|
+
onUploadFinish={async () => {
|
|
29
|
+
setIsLoading(false);
|
|
30
|
+
await refresh();
|
|
31
|
+
}}
|
|
32
|
+
uploader={async (file) => {
|
|
33
|
+
setIsLoading(true);
|
|
34
|
+
try {
|
|
35
|
+
return await uploader(projects.id || "", id || "", file);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error(e, "failed to upload image.");
|
|
38
|
+
}
|
|
39
|
+
}}
|
|
40
|
+
ghost
|
|
41
|
+
>
|
|
42
|
+
{() => null}
|
|
43
|
+
</DropImage>
|
|
44
|
+
</AuthRequired>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
<Gallery images={projects.images.map(imageFormatter)}>
|
|
50
|
+
{renderUploader()}
|
|
51
|
+
</Gallery>
|
|
52
|
+
<Nav paths={usePaths()} mini className="justify-center py-32 md:hidden" />
|
|
53
|
+
</>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default () => {
|
|
58
|
+
return (
|
|
59
|
+
<MySuspense>
|
|
60
|
+
<ProjectDetail />
|
|
61
|
+
</MySuspense>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React, { ReactElement } from "react";
|
|
2
|
+
import { MdxArticleList } from "@/articles";
|
|
3
|
+
import { withArticleWrapper } from "@/components";
|
|
4
|
+
import { MdxArticle } from "@/types/articles";
|
|
5
|
+
import { Article, NotFound } from "@bbki.ng/components";
|
|
6
|
+
import { useParams } from "react-router-dom";
|
|
7
|
+
|
|
8
|
+
type TArticleMap = {
|
|
9
|
+
[key: string]: ReactElement;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const ArticleMap: TArticleMap = {};
|
|
13
|
+
|
|
14
|
+
MdxArticleList.forEach((article: unknown) => {
|
|
15
|
+
const { meta, default: component } = article as MdxArticle;
|
|
16
|
+
const Article = withArticleWrapper(component);
|
|
17
|
+
ArticleMap[meta.title] = <Article {...meta} />;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export default () => {
|
|
21
|
+
const { title } = useParams();
|
|
22
|
+
if (!title || !ArticleMap[title]) {
|
|
23
|
+
return <NotFound />;
|
|
24
|
+
}
|
|
25
|
+
return ArticleMap[title];
|
|
26
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ArticleList } from "./consts";
|
|
3
|
+
// import Tags from "@/pages/tags";
|
|
4
|
+
import { LinkList, LinkProps } from "@bbki.ng/components";
|
|
5
|
+
import { useRouteName } from "@/hooks";
|
|
6
|
+
|
|
7
|
+
type TxtProps = {
|
|
8
|
+
title?: string;
|
|
9
|
+
articleList?: LinkProps[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default (props: TxtProps) => {
|
|
13
|
+
const name = useRouteName();
|
|
14
|
+
return (
|
|
15
|
+
<LinkList
|
|
16
|
+
links={props.articleList || ArticleList}
|
|
17
|
+
title={props.title || name}
|
|
18
|
+
// description={<Tags inline className="ml-0" withAll />}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React, { lazy, Suspense } from "react";
|
|
2
|
+
import { minDelay } from "@/utils";
|
|
3
|
+
|
|
4
|
+
const NowLazy = lazy(() => minDelay(import("./now")));
|
|
5
|
+
const NowPage = () => {
|
|
6
|
+
return (
|
|
7
|
+
<Suspense fallback={null}>
|
|
8
|
+
<NowLazy />
|
|
9
|
+
</Suspense>
|
|
10
|
+
);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export { Cover } from "./cover";
|
|
14
|
+
export { NowPage };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Button, ButtonType } from "@bbki.ng/components";
|
|
3
|
+
import { OauthProvider } from "@/types/supabase";
|
|
4
|
+
import { supabase } from "@/constants";
|
|
5
|
+
import { ArticlePage } from "@/components/article";
|
|
6
|
+
import { useSupabaseSession } from "@/hooks/use_supa_session";
|
|
7
|
+
import { Navigate } from "react-router-dom";
|
|
8
|
+
|
|
9
|
+
export const Login = () => {
|
|
10
|
+
const [loading, setLoading] = useState(false);
|
|
11
|
+
|
|
12
|
+
const session = useSupabaseSession();
|
|
13
|
+
if (session) {
|
|
14
|
+
return <Navigate to="/" />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<ArticlePage title="第三方账号登录">
|
|
19
|
+
<Button
|
|
20
|
+
type={loading ? ButtonType.DISABLED : ButtonType.PRIMARY}
|
|
21
|
+
className="ml-8"
|
|
22
|
+
onClick={async () => {
|
|
23
|
+
setLoading(true);
|
|
24
|
+
return supabase.auth.signIn({
|
|
25
|
+
provider: OauthProvider.GITHUB,
|
|
26
|
+
});
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
GitHub
|
|
30
|
+
</Button>
|
|
31
|
+
</ArticlePage>
|
|
32
|
+
);
|
|
33
|
+
};
|