@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.
Files changed (117) hide show
  1. package/.github/workflows/deploy.yml +44 -0
  2. package/.husky/pre-commit +4 -0
  3. package/.prettierignore +1 -0
  4. package/.prettierrc.json +1 -0
  5. package/.rush/temp/shrinkwrap-deps.json +908 -0
  6. package/CODE_OF_CONDUCT.md +45 -0
  7. package/CONTRIBUTING.md +11 -0
  8. package/LICENSE +21 -0
  9. package/README.md +21 -0
  10. package/index.html +33 -0
  11. package/jest.config.js +15 -0
  12. package/package.json +69 -0
  13. package/postcss.config.cjs +6 -0
  14. package/public/Logo.svg +9 -0
  15. package/public/apple-touch-icon.png +0 -0
  16. package/public/favicon-16x16.png +0 -0
  17. package/public/favicon-32x32.png +0 -0
  18. package/public/favicon.ico +0 -0
  19. package/public/favicon.svg +9 -0
  20. package/public/pwa-192x192.png +0 -0
  21. package/public/pwa-512x512.png +0 -0
  22. package/public/robots.txt +2 -0
  23. package/src/__test__/utils/index.test.ts +90 -0
  24. package/src/app.tsx +97 -0
  25. package/src/articles/anti-logic.mdx +61 -0
  26. package/src/articles/bbking-manual.mdx +88 -0
  27. package/src/articles/black-river.mdx +8 -0
  28. package/src/articles/cooldown.mdx +12 -0
  29. package/src/articles/fall.mdx +8 -0
  30. package/src/articles/img.mdx +104 -0
  31. package/src/articles/index.ts +35 -0
  32. package/src/articles/loading.mdx +129 -0
  33. package/src/articles/major-cold.mdx +14 -0
  34. package/src/articles/men-without-women.mdx +19 -0
  35. package/src/articles/movie-day.mdx +15 -0
  36. package/src/articles/now.mdx +15 -0
  37. package/src/articles/projects.mdx +9 -0
  38. package/src/articles/quote.mdx +27 -0
  39. package/src/articles/spring-cooldown.mdx +7 -0
  40. package/src/articles/spring-rain.mdx +9 -0
  41. package/src/articles/travel.mdx +21 -0
  42. package/src/articles/warming-up.mdx +10 -0
  43. package/src/articles/web-burnning.mdx +10 -0
  44. package/src/auth_required.tsx +17 -0
  45. package/src/components/Spinner.tsx +33 -0
  46. package/src/components/article/index.tsx +31 -0
  47. package/src/components/aspect_ratio_box/index.tsx +29 -0
  48. package/src/components/blur_cover/index.tsx +28 -0
  49. package/src/components/book_list/index.tsx +50 -0
  50. package/src/components/comment/index.tsx +70 -0
  51. package/src/components/comment/use_cusdis_event.ts +37 -0
  52. package/src/components/corner_prompt_box/index.tsx +63 -0
  53. package/src/components/disabled_text/index.tsx +23 -0
  54. package/src/components/fade_out_cover/index.tsx +37 -0
  55. package/src/components/footer/footer_links.ts +13 -0
  56. package/src/components/footer/index.tsx +21 -0
  57. package/src/components/hotkey_nav/index.tsx +50 -0
  58. package/src/components/img_list/index.tsx +43 -0
  59. package/src/components/index.tsx +27 -0
  60. package/src/components/movie_list/index.tsx +50 -0
  61. package/src/components/my_suspense.tsx +14 -0
  62. package/src/components/progress_bar/index.tsx +31 -0
  63. package/src/components/reload_prompt/index.tsx +36 -0
  64. package/src/components/stickers/index.tsx +46 -0
  65. package/src/components/table_skeleton/index.tsx +40 -0
  66. package/src/components/tags/index.tsx +52 -0
  67. package/src/components/video_player/index.tsx +81 -0
  68. package/src/components/with_wrapper/index.tsx +13 -0
  69. package/src/constants/cusdis.ts +6 -0
  70. package/src/constants/index.ts +16 -0
  71. package/src/constants/photo_projects.ts +54 -0
  72. package/src/constants/photos.ts +270 -0
  73. package/src/constants/routes.ts +24 -0
  74. package/src/constants/video_logs.ts +16 -0
  75. package/src/demo/DemoBox.tsx +15 -0
  76. package/src/demo/ImgDemo.tsx +34 -0
  77. package/src/demo/SpinnerDemo.tsx +17 -0
  78. package/src/global/mdx.d.ts +8 -0
  79. package/src/global_loading_state_provider.tsx +27 -0
  80. package/src/hooks/index.ts +15 -0
  81. package/src/hooks/useScrollToTop.ts +24 -0
  82. package/src/hooks/useTransitionCls.ts +36 -0
  83. package/src/hooks/use_img_loading.ts +16 -0
  84. package/src/hooks/use_pathname.ts +6 -0
  85. package/src/hooks/use_paths.ts +30 -0
  86. package/src/hooks/use_projects.ts +56 -0
  87. package/src/hooks/use_route_name.ts +7 -0
  88. package/src/hooks/use_supa_session.ts +30 -0
  89. package/src/hooks/use_uploader.ts +34 -0
  90. package/src/hooks/use_video_controls.ts +71 -0
  91. package/src/main.css +156 -0
  92. package/src/main.tsx +19 -0
  93. package/src/pages/cover/index.tsx +10 -0
  94. package/src/pages/extensions/png/consts.ts +9 -0
  95. package/src/pages/extensions/png/index.tsx +41 -0
  96. package/src/pages/extensions/png/png_projects.tsx +63 -0
  97. package/src/pages/extensions/txt/article.tsx +26 -0
  98. package/src/pages/extensions/txt/consts.ts +8 -0
  99. package/src/pages/extensions/txt/index.tsx +21 -0
  100. package/src/pages/index.tsx +14 -0
  101. package/src/pages/login/index.tsx +33 -0
  102. package/src/pages/now/index.tsx +7 -0
  103. package/src/pages/tags/index.tsx +28 -0
  104. package/src/pages/tags/tag_result.tsx +19 -0
  105. package/src/swr.tsx +18 -0
  106. package/src/types/articles.ts +6 -0
  107. package/src/types/color.ts +21 -0
  108. package/src/types/cusdis.ts +4 -0
  109. package/src/types/oss.ts +15 -0
  110. package/src/types/path.ts +11 -0
  111. package/src/types/photo.ts +17 -0
  112. package/src/types/supabase.ts +9 -0
  113. package/src/utils/index.ts +143 -0
  114. package/src/utils/tags.ts +21 -0
  115. package/tailwind.config.cjs +10 -0
  116. package/tsconfig.json +24 -0
  117. 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,6 @@
1
+ import { useLocation } from "react-router-dom";
2
+
3
+ export const usePathName = () => {
4
+ const { pathname } = useLocation();
5
+ return pathname.replace(/\/$/, "");
6
+ };
@@ -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,7 @@
1
+ import { ROUTE_NAME } from "@/constants";
2
+ import { usePathName } from "@/hooks/use_pathname";
3
+
4
+ export const useRouteName = () => {
5
+ const pathname = usePathName();
6
+ return ROUTE_NAME[pathname.replace(/\/$/, "")] || ROUTE_NAME.unknown;
7
+ };
@@ -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,9 @@
1
+ import { ImgList } from "@/components";
2
+ import { PHOTO_PROJECTS, ROUTES } from "@/constants";
3
+
4
+ export const ProjectList = PHOTO_PROJECTS.map(
5
+ ({ name, images: imgList, description }) => ({
6
+ path: name,
7
+ name,
8
+ })
9
+ );
@@ -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,8 @@
1
+ import { MdxArticleList } from "@/articles";
2
+
3
+ export const ArticleList = MdxArticleList.map(({ meta }) => {
4
+ return {
5
+ to: meta.title,
6
+ name: meta.title,
7
+ };
8
+ });
@@ -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
+ };
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ import { default as MdxArticleComp, meta } from "@/articles/now.mdx";
3
+ import { withArticleWrapper } from "@/components";
4
+
5
+ const Article = withArticleWrapper(MdxArticleComp);
6
+
7
+ export default () => <Article {...meta} />;