@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,70 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import classnames from "classnames";
|
|
3
|
+
import { ReactCusdis } from "react-cusdis";
|
|
4
|
+
import { BgColors } from "@/types/color";
|
|
5
|
+
import { CUSDIS_ATTRS, CUSDIS_OFFICIAL_SITE_ADDRESS } from "@/constants/cusdis";
|
|
6
|
+
import { useCusidsEvent } from "@/components/comment/use_cusdis_event";
|
|
7
|
+
import { CornerPromptBox } from "@/components";
|
|
8
|
+
import { Link, LinkColor } from "@bbki.ng/components";
|
|
9
|
+
|
|
10
|
+
type CommentProps = {
|
|
11
|
+
title: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const Comment = (props: CommentProps) => {
|
|
15
|
+
const path = location.href;
|
|
16
|
+
const cusdisAttrs = Object.assign({}, CUSDIS_ATTRS, {
|
|
17
|
+
pageTitle: props.title,
|
|
18
|
+
pageUrl: path,
|
|
19
|
+
pageId: path,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const [ready, setReady] = useState(false);
|
|
23
|
+
const [showBox, setShowBox] = useState(false);
|
|
24
|
+
|
|
25
|
+
useCusidsEvent({
|
|
26
|
+
handleCommentLoaded: () => {
|
|
27
|
+
setReady(true);
|
|
28
|
+
},
|
|
29
|
+
handleCommentSent: () => {
|
|
30
|
+
setShowBox(true);
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
className={classnames(
|
|
37
|
+
"overflow-hidden",
|
|
38
|
+
"transition-all",
|
|
39
|
+
BgColors.WHITE_GRAY,
|
|
40
|
+
{
|
|
41
|
+
"max-h-0": !ready,
|
|
42
|
+
"p-5": ready,
|
|
43
|
+
"mb-8": ready,
|
|
44
|
+
}
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
<ReactCusdis attrs={cusdisAttrs} />
|
|
48
|
+
<CornerPromptBox
|
|
49
|
+
autoCancelAfter={3000}
|
|
50
|
+
content="评论已发送,等待 18+ 内容审核。"
|
|
51
|
+
showBox={showBox}
|
|
52
|
+
cancelLabel="关闭"
|
|
53
|
+
onCancel={() => {
|
|
54
|
+
console.log("cancel");
|
|
55
|
+
setShowBox(false);
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
<div className="text-center">
|
|
59
|
+
<Link
|
|
60
|
+
to={CUSDIS_OFFICIAL_SITE_ADDRESS}
|
|
61
|
+
external
|
|
62
|
+
color={LinkColor.GRAY}
|
|
63
|
+
className="px-1"
|
|
64
|
+
>
|
|
65
|
+
powered by discus
|
|
66
|
+
</Link>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { cusdisEvent } from "../../types/cusdis";
|
|
3
|
+
|
|
4
|
+
type cusdisEventHandlers = {
|
|
5
|
+
handleCommentLoaded?: () => void;
|
|
6
|
+
handleCommentSent?: () => void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const noop = () => null;
|
|
10
|
+
|
|
11
|
+
export const useCusidsEvent = (handlers: cusdisEventHandlers) => {
|
|
12
|
+
const { handleCommentLoaded = noop, handleCommentSent = noop } = handlers;
|
|
13
|
+
const onMessage = (e: MessageEvent) => {
|
|
14
|
+
try {
|
|
15
|
+
const msg = JSON.parse(e.data);
|
|
16
|
+
if (msg.from === "cusdis") {
|
|
17
|
+
switch (msg.event) {
|
|
18
|
+
case cusdisEvent.COMMENTS_LOADED:
|
|
19
|
+
handleCommentLoaded();
|
|
20
|
+
break;
|
|
21
|
+
case cusdisEvent.COMMENT_SENT:
|
|
22
|
+
handleCommentSent();
|
|
23
|
+
break;
|
|
24
|
+
default:
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
} catch (e) {}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
window.addEventListener("message", onMessage);
|
|
33
|
+
return () => {
|
|
34
|
+
window.removeEventListener("message", onMessage);
|
|
35
|
+
};
|
|
36
|
+
}, []);
|
|
37
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React, { EventHandler, useEffect } from "react";
|
|
2
|
+
import { PopConfirm } from "@bbki.ng/components";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
|
|
5
|
+
type cornerPromptBoxProps = {
|
|
6
|
+
okLabel?: string;
|
|
7
|
+
onOk?: (() => void) | null;
|
|
8
|
+
onCancel?: (() => void) | null;
|
|
9
|
+
cancelLabel?: string;
|
|
10
|
+
autoCancelAfter?: number;
|
|
11
|
+
content: string;
|
|
12
|
+
showBox: boolean;
|
|
13
|
+
className?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const CornerPromptBox = (props: cornerPromptBoxProps) => {
|
|
17
|
+
const { onOk, onCancel, content, showBox, autoCancelAfter, className } =
|
|
18
|
+
props;
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!autoCancelAfter || !onCancel || !showBox) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const timerId = setTimeout(() => {
|
|
25
|
+
onCancel();
|
|
26
|
+
}, autoCancelAfter);
|
|
27
|
+
|
|
28
|
+
return () => {
|
|
29
|
+
clearTimeout(timerId);
|
|
30
|
+
};
|
|
31
|
+
}, [showBox]);
|
|
32
|
+
|
|
33
|
+
const handleOk = async () => {
|
|
34
|
+
if (!onOk) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
return onOk();
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (!showBox) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="p-0 m-0 w-0 h-0">
|
|
46
|
+
<div
|
|
47
|
+
className={classNames(
|
|
48
|
+
"left-0 bottom-0 m-32 z-10 bg-white fixed",
|
|
49
|
+
className
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
<PopConfirm
|
|
53
|
+
onOk={handleOk}
|
|
54
|
+
onCancel={
|
|
55
|
+
onCancel as EventHandler<React.MouseEvent<HTMLButtonElement>>
|
|
56
|
+
}
|
|
57
|
+
>
|
|
58
|
+
{content}
|
|
59
|
+
</PopConfirm>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import classnames from "classnames";
|
|
3
|
+
|
|
4
|
+
type disabledTextProps = {
|
|
5
|
+
children: any;
|
|
6
|
+
className?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const DisabledText = (props: disabledTextProps) => {
|
|
10
|
+
return (
|
|
11
|
+
<span className={classnames("text-gray-400", props.className)}>
|
|
12
|
+
{props.children}
|
|
13
|
+
</span>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const SmallDisabledText = (props: disabledTextProps) => {
|
|
18
|
+
return (
|
|
19
|
+
<small>
|
|
20
|
+
<DisabledText>{props.children}</DisabledText>
|
|
21
|
+
</small>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React, { FunctionComponent, useEffect, useState } from "react";
|
|
2
|
+
import classnames from "classnames";
|
|
3
|
+
import { DEFAULT_DELAY } from "@/constants";
|
|
4
|
+
|
|
5
|
+
type FadeOutCoverProps = {
|
|
6
|
+
duration: number; // ms
|
|
7
|
+
coverColor: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const FadeOutCover: FunctionComponent<FadeOutCoverProps> = (props) => {
|
|
11
|
+
const [hidden, setHidden] = useState(false);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const id = setTimeout(() => {
|
|
14
|
+
setHidden(true);
|
|
15
|
+
}, props.duration || DEFAULT_DELAY);
|
|
16
|
+
return () => {
|
|
17
|
+
clearTimeout(id);
|
|
18
|
+
};
|
|
19
|
+
}, []);
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
style={{
|
|
23
|
+
backgroundColor: props.coverColor || "#fff",
|
|
24
|
+
}}
|
|
25
|
+
className={classnames(
|
|
26
|
+
"transition-opacity",
|
|
27
|
+
"h-full",
|
|
28
|
+
"w-full",
|
|
29
|
+
"absolute",
|
|
30
|
+
"opacity-100",
|
|
31
|
+
{
|
|
32
|
+
"opacity-0": hidden,
|
|
33
|
+
}
|
|
34
|
+
)}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { pathObj } from "@/types/path";
|
|
2
|
+
import { GITHUB_REPO_ADDRESS, ROUTES } from "@/constants";
|
|
3
|
+
|
|
4
|
+
export const FooterLinks: pathObj[] = [
|
|
5
|
+
{
|
|
6
|
+
name: "blog",
|
|
7
|
+
path: ROUTES.BLOG,
|
|
8
|
+
},
|
|
9
|
+
// {
|
|
10
|
+
// text: "contributing",
|
|
11
|
+
// link: "https://github.com/bbbottle/bbki.ng/blob/main/CONTRIBUTING.md",
|
|
12
|
+
// },
|
|
13
|
+
];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { pathObj } from "@/types/path";
|
|
3
|
+
import { TextColors } from "@/types/color";
|
|
4
|
+
// import { List } from "../list";
|
|
5
|
+
import { FooterLinks } from "./footer_links";
|
|
6
|
+
import { Link, LinkColor } from "@bbki.ng/components";
|
|
7
|
+
|
|
8
|
+
export const Footer = () => {
|
|
9
|
+
const renderFooterLink = (l: pathObj) => {
|
|
10
|
+
return (
|
|
11
|
+
<Link to={l.path as string} color={LinkColor.GRAY}>
|
|
12
|
+
{l.name}
|
|
13
|
+
</Link>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
return renderFooterLink(FooterLinks[0]);
|
|
18
|
+
// return (
|
|
19
|
+
// <List items={FooterLinks} itemRenderer={renderFooterLink} horizontal />
|
|
20
|
+
// );
|
|
21
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useNavigate } from "react-router-dom";
|
|
3
|
+
import { useHotkeys } from "react-hotkeys-hook";
|
|
4
|
+
import { GITHUB_REPO_ADDRESS, ROUTES } from "@/constants";
|
|
5
|
+
|
|
6
|
+
enum HotKeys {
|
|
7
|
+
i = "i",
|
|
8
|
+
e = "e",
|
|
9
|
+
c = "c",
|
|
10
|
+
t = "t",
|
|
11
|
+
p = "p",
|
|
12
|
+
a = "a",
|
|
13
|
+
f = "f",
|
|
14
|
+
b = "b",
|
|
15
|
+
h = "h",
|
|
16
|
+
s = "s",
|
|
17
|
+
T = "shift+t",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const KEY_ROUTES = [
|
|
21
|
+
{ key: HotKeys.i, route: ROUTES.INDEX },
|
|
22
|
+
{ key: HotKeys.c, route: ROUTES.CONTENT },
|
|
23
|
+
{ key: HotKeys.h, route: ROUTES.HELP },
|
|
24
|
+
{ key: HotKeys.T, route: ROUTES.TAGS },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export const HotKeyNav = (props: any) => {
|
|
28
|
+
const nav = useNavigate();
|
|
29
|
+
const goto = (path: string) => {
|
|
30
|
+
nav(path);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
useHotkeys(HotKeys.b, () => {
|
|
34
|
+
nav(-1);
|
|
35
|
+
});
|
|
36
|
+
useHotkeys(HotKeys.f, () => {
|
|
37
|
+
nav(1);
|
|
38
|
+
});
|
|
39
|
+
useHotkeys(HotKeys.s, () => {
|
|
40
|
+
window.open(GITHUB_REPO_ADDRESS);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
KEY_ROUTES.map(({ key, route }) => {
|
|
44
|
+
useHotkeys(key, () => {
|
|
45
|
+
goto(route);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return props.children;
|
|
50
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React, { FunctionComponent, ReactElement, ReactNode } from "react";
|
|
2
|
+
import cls from "classnames";
|
|
3
|
+
import { Photo } from "@/types/photo";
|
|
4
|
+
import { Article, Img } from "@bbki.ng/components";
|
|
5
|
+
|
|
6
|
+
interface imgListProps {
|
|
7
|
+
className: string;
|
|
8
|
+
imgList: Photo[];
|
|
9
|
+
description?: any;
|
|
10
|
+
beforeListRenderer?: () => ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const BaseImgList: FunctionComponent<imgListProps> = (props: imgListProps) => {
|
|
14
|
+
const { imgList, className, beforeListRenderer } = props;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className={cls("max-h-full no-scrollbar overflow-auto", className)}>
|
|
18
|
+
{beforeListRenderer && <>{beforeListRenderer()}</>}
|
|
19
|
+
{imgList.map((img, index) => {
|
|
20
|
+
const isLast = index === imgList.length - 1;
|
|
21
|
+
return (
|
|
22
|
+
<div key={img.src}>
|
|
23
|
+
<Img {...img} className={cls({ "mb-256": !isLast })} />
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
})}
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
interface TitledImageListProps extends imgListProps {
|
|
32
|
+
title: string | ReactElement;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const ImgList = (props: TitledImageListProps) => {
|
|
36
|
+
const { title, description, ...rest } = props;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Article title={title} description={description}>
|
|
40
|
+
<BaseImgList {...rest} />
|
|
41
|
+
</Article>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export { Footer } from "./footer";
|
|
2
|
+
|
|
3
|
+
export { DisabledText, SmallDisabledText } from "./disabled_text";
|
|
4
|
+
|
|
5
|
+
export { ImgList } from "./img_list";
|
|
6
|
+
|
|
7
|
+
export { withArticleWrapper } from "./with_wrapper";
|
|
8
|
+
|
|
9
|
+
export { HotKeyNav } from "./hotkey_nav";
|
|
10
|
+
|
|
11
|
+
export { VideoPlayer } from "./video_player";
|
|
12
|
+
|
|
13
|
+
export { ProgressBar } from "./progress_bar";
|
|
14
|
+
|
|
15
|
+
export { BlurCover } from "./blur_cover";
|
|
16
|
+
|
|
17
|
+
export { ReloadPrompt } from "./reload_prompt";
|
|
18
|
+
|
|
19
|
+
export { Stickers } from "./stickers";
|
|
20
|
+
|
|
21
|
+
export { CornerPromptBox } from "./corner_prompt_box";
|
|
22
|
+
|
|
23
|
+
export { Tags } from "./tags";
|
|
24
|
+
|
|
25
|
+
export { Comment } from "./comment";
|
|
26
|
+
|
|
27
|
+
export { MySuspense } from "./my_suspense";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Table, Link, Error } from "@bbki.ng/components";
|
|
3
|
+
import { TableSkeleton } from "@/components/table_skeleton";
|
|
4
|
+
import { useMovies } from "@/hooks";
|
|
5
|
+
|
|
6
|
+
const CELL_STYLE = {
|
|
7
|
+
width: 100,
|
|
8
|
+
maxWidth: 100,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const MovieList = () => {
|
|
12
|
+
const { movies, isLoading, isError } = useMovies();
|
|
13
|
+
if (isError) {
|
|
14
|
+
return <Error error={isError} />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (isLoading) {
|
|
18
|
+
return <TableSkeleton />;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const renderHeader = () => {
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<Table.HCell style={CELL_STYLE}>名字</Table.HCell>
|
|
25
|
+
<Table.HCell style={CELL_STYLE}>状态</Table.HCell>
|
|
26
|
+
</>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const renderRow = (index: number) => {
|
|
31
|
+
const { name, link, status } = movies[index];
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<Table.Cell style={CELL_STYLE}>
|
|
35
|
+
<Link to={link} external>
|
|
36
|
+
{name}
|
|
37
|
+
</Link>
|
|
38
|
+
</Table.Cell>
|
|
39
|
+
<Table.Cell style={CELL_STYLE}>{status}</Table.Cell>
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
return (
|
|
44
|
+
<Table
|
|
45
|
+
rowCount={movies.length}
|
|
46
|
+
rowRenderer={renderRow}
|
|
47
|
+
headerRenderer={renderHeader}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React, { ReactElement, ReactNode, Suspense } from "react";
|
|
2
|
+
import { Spinner } from "./Spinner";
|
|
3
|
+
import { ErrorBoundary } from "@bbki.ng/components";
|
|
4
|
+
|
|
5
|
+
export const MySuspense = (props: {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
fallback?: ReactElement;
|
|
8
|
+
}) => {
|
|
9
|
+
return (
|
|
10
|
+
<ErrorBoundary>
|
|
11
|
+
<Suspense fallback={<Spinner />}>{props.children}</Suspense>
|
|
12
|
+
</ErrorBoundary>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import classnames from "classnames";
|
|
3
|
+
import { BgColors } from "@/types/color";
|
|
4
|
+
import { floatNumberToPercentageString } from "@/utils";
|
|
5
|
+
|
|
6
|
+
type progressBarProps = {
|
|
7
|
+
bgColor?: BgColors;
|
|
8
|
+
fgColor?: BgColors;
|
|
9
|
+
height?: number;
|
|
10
|
+
progress: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const ProgressBar = (props: progressBarProps) => {
|
|
14
|
+
const {
|
|
15
|
+
height = 2,
|
|
16
|
+
fgColor = BgColors.BLUE,
|
|
17
|
+
bgColor = BgColors.GRAY,
|
|
18
|
+
progress,
|
|
19
|
+
} = props;
|
|
20
|
+
|
|
21
|
+
const wrapperCls = classnames("w-full", "text-left", bgColor);
|
|
22
|
+
const innerBarCls = classnames("h-full", fgColor);
|
|
23
|
+
return (
|
|
24
|
+
<div className={wrapperCls} style={{ height }}>
|
|
25
|
+
<div
|
|
26
|
+
className={innerBarCls}
|
|
27
|
+
style={{ width: floatNumberToPercentageString(progress) }}
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import { useRegisterSW } from "virtual:pwa-register/react";
|
|
5
|
+
import { CornerPromptBox } from "@/components";
|
|
6
|
+
|
|
7
|
+
export const ReloadPrompt = () => {
|
|
8
|
+
const {
|
|
9
|
+
needRefresh: [needRefresh, setNeedRefresh],
|
|
10
|
+
updateServiceWorker,
|
|
11
|
+
} = useRegisterSW({
|
|
12
|
+
onRegisterError(error: any) {
|
|
13
|
+
console.log("SW registration error", error);
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const close = () => {
|
|
18
|
+
// setOfflineReady(false);
|
|
19
|
+
setNeedRefresh(false);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
const appVer = GLOBAL_BBKING_VERSION;
|
|
24
|
+
console.log("appVer: ", appVer);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<CornerPromptBox
|
|
28
|
+
className="z-[1010]"
|
|
29
|
+
content={`🚀 发现新版本(当前 v${appVer})。获取更新?`}
|
|
30
|
+
showBox={needRefresh}
|
|
31
|
+
onCancel={close}
|
|
32
|
+
cancelLabel="关闭"
|
|
33
|
+
onOk={needRefresh ? () => updateServiceWorker(true) : close}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Img } from "@bbki.ng/components";
|
|
3
|
+
import { NavLink, useLocation } from "react-router-dom";
|
|
4
|
+
|
|
5
|
+
export const Stickers = () => {
|
|
6
|
+
const { pathname } = useLocation();
|
|
7
|
+
|
|
8
|
+
if (pathname !== "/") {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="fixed bottom-128 md:bottom-256 right-16 z-10 md:right-64">
|
|
14
|
+
<NavLink to="projects">
|
|
15
|
+
<Img
|
|
16
|
+
size="large"
|
|
17
|
+
src="https://zjh-im-res.oss-cn-shenzhen.aliyuncs.com/image/stickers/sticker-water-delivery.jpg"
|
|
18
|
+
className="-rotate-[32.95deg] relative top-0 md:-top-64 left-64 md:left-[unset]"
|
|
19
|
+
width={126}
|
|
20
|
+
height={59}
|
|
21
|
+
renderedWidth={126}
|
|
22
|
+
/>
|
|
23
|
+
</NavLink>
|
|
24
|
+
<NavLink to="404">
|
|
25
|
+
<Img
|
|
26
|
+
size="large"
|
|
27
|
+
src="https://zjh-im-res.oss-cn-shenzhen.aliyuncs.com/image/stickers/sticker-lock.jpg"
|
|
28
|
+
className="rotate-[10.77deg] relative left-64 md:left-[unset]"
|
|
29
|
+
width={93}
|
|
30
|
+
height={50}
|
|
31
|
+
renderedWidth={93}
|
|
32
|
+
/>
|
|
33
|
+
</NavLink>
|
|
34
|
+
<NavLink to="blog">
|
|
35
|
+
<Img
|
|
36
|
+
size="large"
|
|
37
|
+
src="https://zjh-im-res.oss-cn-shenzhen.aliyuncs.com/image/stickers/bar-code.jpg"
|
|
38
|
+
className="-rotate-[13.37deg] relative top-64 left-64 md:left-[unset] md:right-256 md:top-128"
|
|
39
|
+
width={176}
|
|
40
|
+
height={60}
|
|
41
|
+
renderedWidth={176}
|
|
42
|
+
/>
|
|
43
|
+
</NavLink>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Skeleton, SkeletonColor, Table } from "@bbki.ng/components";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
const CELL_STYLE = {
|
|
5
|
+
width: 100,
|
|
6
|
+
maxWidth: 100,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const TableSkeleton = (props: {
|
|
10
|
+
headers?: string[];
|
|
11
|
+
cellStyle?: { width: number; maxWidth: number };
|
|
12
|
+
}) => {
|
|
13
|
+
const { headers, cellStyle = CELL_STYLE } = props;
|
|
14
|
+
const [name = "名字", status = "状态"] = headers || [];
|
|
15
|
+
const renderHeader = () => {
|
|
16
|
+
return (
|
|
17
|
+
<>
|
|
18
|
+
<Table.HCell style={cellStyle}>{name}</Table.HCell>
|
|
19
|
+
<Table.HCell style={cellStyle}>{status}</Table.HCell>
|
|
20
|
+
</>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const renderRow = () => {
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<Table.Cell style={cellStyle}>
|
|
28
|
+
<Skeleton width={84} height={16} bgColor={SkeletonColor.BLUE} />
|
|
29
|
+
</Table.Cell>
|
|
30
|
+
<Table.Cell style={cellStyle}>
|
|
31
|
+
<Skeleton width={32} height={16} bgColor={SkeletonColor.GRAY} />
|
|
32
|
+
</Table.Cell>
|
|
33
|
+
</>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Table rowCount={3} rowRenderer={renderRow} headerRenderer={renderHeader} />
|
|
39
|
+
);
|
|
40
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import classnames from "classnames";
|
|
3
|
+
import { List, Tag, Link, TitledList } from "@bbki.ng/components";
|
|
4
|
+
import { ROUTES } from "@/constants";
|
|
5
|
+
|
|
6
|
+
type MyTag =
|
|
7
|
+
| {
|
|
8
|
+
path: string;
|
|
9
|
+
name: string;
|
|
10
|
+
}
|
|
11
|
+
| string;
|
|
12
|
+
|
|
13
|
+
type Tags = {
|
|
14
|
+
tags: MyTag[];
|
|
15
|
+
className?: string;
|
|
16
|
+
inline?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const Tags = (props: Tags) => {
|
|
20
|
+
const { inline, className, tags } = props;
|
|
21
|
+
|
|
22
|
+
const renderTag = (tag: MyTag) => {
|
|
23
|
+
const TagComp = inline ? Tag : Link;
|
|
24
|
+
if (typeof tag === "string") {
|
|
25
|
+
return <TagComp to={`${ROUTES.TAGS}/${tag}`}>{tag}</TagComp>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return <TagComp to={tag.path}>{tag.name}</TagComp>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (inline) {
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<List
|
|
35
|
+
items={tags}
|
|
36
|
+
itemRenderer={renderTag}
|
|
37
|
+
horizontal={inline}
|
|
38
|
+
className={classnames(className, "inline-flex", "flex-wrap")}
|
|
39
|
+
/>
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return (
|
|
44
|
+
<TitledList
|
|
45
|
+
title={"标签"}
|
|
46
|
+
items={tags}
|
|
47
|
+
itemRenderer={renderTag}
|
|
48
|
+
horizontal={inline}
|
|
49
|
+
className={className}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
};
|