2024-01-15 11:06:49 +08:00
|
|
|
import { ContentContainer, Page } from "@/components/layouts";
|
2023-12-25 17:21:39 +08:00
|
|
|
import { MDXComponentsSet } from "@/components/mdx";
|
2024-04-03 22:08:27 +08:00
|
|
|
import { BottomCard } from "@/components/readerpage/BottomCard";
|
2024-01-04 11:34:27 +08:00
|
|
|
import { DrawerTOC } from "@/components/readerpage/DrawerTOC";
|
2023-12-25 17:21:39 +08:00
|
|
|
import { PostComments } from "@/components/readerpage/PostComments";
|
|
|
|
|
import { PostCover } from "@/components/readerpage/PostCover";
|
|
|
|
|
import { ShareButtons } from "@/components/readerpage/ShareButtons";
|
|
|
|
|
import { TOC } from "@/components/readerpage/TOC";
|
2024-01-15 11:44:48 +08:00
|
|
|
import { Separator } from "@/components/ui/separator";
|
2023-12-25 17:21:39 +08:00
|
|
|
import { Toaster } from "@/components/ui/toaster";
|
|
|
|
|
import { Footer } from "@/components/utils/Footer";
|
|
|
|
|
import { NavBar } from "@/components/utils/NavBar";
|
|
|
|
|
import { SEO } from "@/components/utils/SEO";
|
|
|
|
|
import { Config } from "@/data/config";
|
|
|
|
|
import { normalizeDate } from "@/lib/date";
|
|
|
|
|
import { getPostFileContent, sortedPosts } from "@/lib/post-process";
|
2024-04-03 22:08:27 +08:00
|
|
|
import { makeTOCTree } from "@/lib/toc";
|
2024-01-04 12:30:29 +08:00
|
|
|
import useDrawerTOCState from "@/stores/useDrawerTOCState";
|
2023-12-25 17:21:39 +08:00
|
|
|
import { TFrontmatter } from "@/types/frontmatter.type";
|
|
|
|
|
import { TPostListItem } from "@/types/post-list";
|
|
|
|
|
import { TTOCItem } from "@/types/toc.type";
|
|
|
|
|
import { nanoid } from "nanoid";
|
|
|
|
|
import { GetStaticPaths, GetStaticProps } from "next";
|
|
|
|
|
import { MDXRemote, MDXRemoteSerializeResult } from "next-mdx-remote";
|
|
|
|
|
import { serialize } from "next-mdx-remote/serialize";
|
|
|
|
|
import Link from "next/link";
|
|
|
|
|
import { renderToString } from "react-dom/server";
|
2024-01-04 12:30:29 +08:00
|
|
|
import { useSwipeable } from "react-swipeable";
|
2023-12-25 17:21:39 +08:00
|
|
|
import rehypeAutolinkHeadings from "rehype-autolink-headings";
|
2024-04-03 22:08:27 +08:00
|
|
|
import rehypeHighlight from "rehype-highlight";
|
2023-12-25 17:21:39 +08:00
|
|
|
import rehypeKatex from "rehype-katex";
|
|
|
|
|
import rehypePresetMinify from "rehype-preset-minify";
|
2023-12-30 14:45:55 +08:00
|
|
|
import rehypeRaw from "rehype-raw";
|
2023-12-25 17:21:39 +08:00
|
|
|
import rehypeSlug from "rehype-slug";
|
|
|
|
|
import externalLinks from "remark-external-links";
|
|
|
|
|
import remarkGfm from "remark-gfm";
|
|
|
|
|
import remarkMath from "remark-math";
|
|
|
|
|
|
|
|
|
|
type ReaderPageProps = {
|
2024-01-04 13:28:16 +08:00
|
|
|
compiledSource: MDXRemoteSerializeResult;
|
2023-12-25 17:21:39 +08:00
|
|
|
tocList: TTOCItem[];
|
|
|
|
|
frontMatter: TFrontmatter;
|
|
|
|
|
postId: string;
|
|
|
|
|
nextPostListItem: TPostListItem | null;
|
|
|
|
|
prevPostListItem: TPostListItem | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const ReaderPage = (props: ReaderPageProps) => {
|
2024-01-04 13:28:16 +08:00
|
|
|
const compiledSource = props.compiledSource;
|
2024-01-04 12:30:29 +08:00
|
|
|
const setIsTOCOpen = useDrawerTOCState((state) => state.changeDrawerTOCOpen);
|
2024-01-04 13:28:16 +08:00
|
|
|
|
|
|
|
|
// Only the TOC length reaches 3 can be displayed.
|
|
|
|
|
// In order to avoid large blank spaces that ruin the visual perception
|
2024-04-03 22:08:27 +08:00
|
|
|
const isTOCLongEnough = props.tocList.length > 2;
|
2024-01-04 12:30:29 +08:00
|
|
|
const handleRightSwipe = useSwipeable({
|
|
|
|
|
onSwipedRight: () => {
|
2024-01-04 13:28:16 +08:00
|
|
|
isTOCLongEnough && setIsTOCOpen(true);
|
2024-01-04 12:30:29 +08:00
|
|
|
},
|
2024-01-04 13:28:16 +08:00
|
|
|
delta: 150,
|
2024-01-04 12:30:29 +08:00
|
|
|
});
|
2024-01-04 13:28:16 +08:00
|
|
|
|
2023-12-25 17:21:39 +08:00
|
|
|
return (
|
|
|
|
|
<Page>
|
|
|
|
|
<SEO
|
|
|
|
|
coverURL={props.frontMatter.coverURL ?? Config.AvatarURL}
|
2024-04-03 22:08:27 +08:00
|
|
|
description={props.frontMatter.summary}
|
|
|
|
|
title={`${props.frontMatter.title} - ${Config.SiteTitle}`}
|
2023-12-25 17:21:39 +08:00
|
|
|
/>
|
|
|
|
|
<Toaster />
|
2024-01-09 16:48:48 +08:00
|
|
|
<NavBar />
|
2023-12-25 17:21:39 +08:00
|
|
|
<ContentContainer>
|
2024-04-03 22:08:27 +08:00
|
|
|
<div
|
|
|
|
|
className={`py-1 ${isTOCLongEnough ? "justify-between" : "justify-center"} lg:flex space-x-5`}
|
|
|
|
|
style={{ borderRadius: "5px" }}
|
|
|
|
|
>
|
2024-01-15 11:44:48 +08:00
|
|
|
<div className={`${isTOCLongEnough ? "lg:w-2/3" : "lg:w-5/6"} py-5`}>
|
2024-01-09 16:48:48 +08:00
|
|
|
<div className="typesetting">
|
2023-12-25 17:21:39 +08:00
|
|
|
{props.frontMatter.coverURL && <PostCover coverURL={props.frontMatter.coverURL} />}
|
2024-04-03 22:08:27 +08:00
|
|
|
<div className="pb-1 border-b-2 border-black dark:border-gray-300">
|
2023-12-29 20:28:13 +08:00
|
|
|
<div
|
2024-08-12 13:39:19 +08:00
|
|
|
className={`caption-font my-2 text-black dark:text-white flex justify-center whitespace-normal break-words text-3xl font-bold capitalize`}
|
2023-12-29 20:28:13 +08:00
|
|
|
>
|
2024-04-03 22:08:27 +08:00
|
|
|
{props.frontMatter?.title}
|
2023-12-25 17:21:39 +08:00
|
|
|
</div>
|
2024-04-03 22:08:27 +08:00
|
|
|
{props.frontMatter?.subtitle && (
|
2024-08-12 13:39:19 +08:00
|
|
|
<div className={`caption-font my-1 flex justify-center text-xl font-bold capitalize`}>
|
2024-04-03 22:08:27 +08:00
|
|
|
{props.frontMatter.subtitle}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<div className="my-1 flex justify-center text-sm italic">{normalizeDate(props.frontMatter?.time)}</div>
|
|
|
|
|
{props.frontMatter?.summary && (
|
2024-08-12 13:39:19 +08:00
|
|
|
<p className={"content-font my-4 indent-8 text-gray-800 dark:text-gray-300"}>
|
2024-04-03 22:08:27 +08:00
|
|
|
{props.frontMatter?.summary}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
{props.frontMatter.tags && (
|
|
|
|
|
<div className={"pt-1 flex flex-wrap border-t-2 border-black dark:border-gray-300"}>
|
|
|
|
|
{props.frontMatter.tags.map((tagName) => (
|
|
|
|
|
<Link
|
|
|
|
|
className="not-prose mr-2 px-2 py-1 font-bold border-2 border-black dark:border-gray-300 my-1 text-gray-700 hover:text-black text-xs dark:text-gray-300 dark:hover:text-gray-200"
|
|
|
|
|
href={`/tags/${tagName}`}
|
|
|
|
|
key={`tags-${nanoid()}`}
|
|
|
|
|
>
|
|
|
|
|
{tagName}
|
|
|
|
|
</Link>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2024-01-09 16:48:48 +08:00
|
|
|
<div
|
2024-08-12 13:39:19 +08:00
|
|
|
className={`text-wrap border-gray-500 content-font ${
|
2024-01-09 16:48:48 +08:00
|
|
|
!props.frontMatter.allowShare ? "select-none" : ""
|
|
|
|
|
}`}
|
|
|
|
|
{...handleRightSwipe}
|
|
|
|
|
>
|
|
|
|
|
{compiledSource && (
|
|
|
|
|
<MDXRemote
|
|
|
|
|
compiledSource={compiledSource.compiledSource}
|
2024-04-03 22:08:27 +08:00
|
|
|
// @ts-ignore
|
|
|
|
|
components={MDXComponentsSet}
|
2024-01-09 16:48:48 +08:00
|
|
|
frontmatter={compiledSource.frontmatter}
|
|
|
|
|
scope={compiledSource.scope}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2023-12-25 17:21:39 +08:00
|
|
|
</div>
|
2024-01-15 11:44:48 +08:00
|
|
|
<Separator />
|
2024-04-03 22:08:27 +08:00
|
|
|
<BottomCard />
|
|
|
|
|
<Separator />
|
2023-12-25 17:21:39 +08:00
|
|
|
<ShareButtons
|
2024-04-03 22:08:27 +08:00
|
|
|
allowShare={props.frontMatter.allowShare}
|
|
|
|
|
postId={props.postId}
|
|
|
|
|
quote={props.frontMatter.summary}
|
2023-12-25 17:21:39 +08:00
|
|
|
subtitle={props.frontMatter.subtitle}
|
|
|
|
|
title={props.frontMatter.title}
|
|
|
|
|
/>
|
2024-01-15 11:44:48 +08:00
|
|
|
<Separator />
|
|
|
|
|
<ul className="my-5 px-5 flex flex-col justify-center list-disc">
|
2023-12-25 17:21:39 +08:00
|
|
|
{props.prevPostListItem && (
|
|
|
|
|
<li className="my-1">
|
|
|
|
|
<Link
|
|
|
|
|
className=" hover:text-sky-600 dark:hover:text-sky-500"
|
|
|
|
|
href={`/blog/${props.prevPostListItem?.id}`}
|
|
|
|
|
>
|
|
|
|
|
{props.prevPostListItem?.frontMatter.title}
|
|
|
|
|
</Link>
|
|
|
|
|
</li>
|
|
|
|
|
)}
|
|
|
|
|
{props.nextPostListItem && (
|
|
|
|
|
<li className="my-1">
|
|
|
|
|
<Link
|
|
|
|
|
className=" hover:text-sky-600 dark:hover:text-sky-500"
|
|
|
|
|
href={`/blog/${props.nextPostListItem?.id}`}
|
|
|
|
|
>
|
|
|
|
|
{props.nextPostListItem?.frontMatter.title}
|
|
|
|
|
</Link>
|
|
|
|
|
</li>
|
|
|
|
|
)}
|
|
|
|
|
</ul>
|
2024-05-03 22:09:58 +08:00
|
|
|
{Config.Giscus?.enabled && <PostComments postId={props.postId} />}
|
2023-12-25 17:21:39 +08:00
|
|
|
</div>
|
2024-01-04 13:28:16 +08:00
|
|
|
{isTOCLongEnough && (
|
2024-01-15 11:44:48 +08:00
|
|
|
<div className="hidden lg:block md:w-1/3 py-5">
|
2024-04-03 22:08:27 +08:00
|
|
|
<div className="sticky top-[5em]">
|
|
|
|
|
<TOC data={props.tocList} />
|
|
|
|
|
</div>
|
2023-12-25 17:21:39 +08:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2024-01-04 13:28:16 +08:00
|
|
|
{isTOCLongEnough && (
|
2024-01-09 16:48:48 +08:00
|
|
|
<div className="lg:hidden">
|
2024-01-04 11:34:27 +08:00
|
|
|
<DrawerTOC data={props.tocList} />
|
2023-12-25 17:21:39 +08:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</ContentContainer>
|
|
|
|
|
<Footer />
|
|
|
|
|
</Page>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getStaticPaths: GetStaticPaths<{ id: string }> = async () => {
|
|
|
|
|
const allPaths = sortedPosts.allPostList.map((item) => ({
|
|
|
|
|
params: { id: item.id },
|
|
|
|
|
}));
|
|
|
|
|
return {
|
|
|
|
|
paths: allPaths,
|
|
|
|
|
fallback: false,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getStaticProps: GetStaticProps<ReaderPageProps> = async (context) => {
|
|
|
|
|
const postId = context.params?.id;
|
|
|
|
|
|
|
|
|
|
if (postId == null || Array.isArray(postId)) {
|
|
|
|
|
return { notFound: true };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const source = getPostFileContent(postId);
|
|
|
|
|
|
|
|
|
|
if (source == null) {
|
|
|
|
|
return { notFound: true };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mdxSource = await serialize(source, {
|
|
|
|
|
parseFrontmatter: true,
|
|
|
|
|
mdxOptions: {
|
2024-04-03 22:08:27 +08:00
|
|
|
remarkPlugins: [externalLinks, remarkMath, remarkGfm],
|
|
|
|
|
rehypePlugins: [
|
|
|
|
|
rehypeRaw,
|
|
|
|
|
rehypeKatex as any,
|
|
|
|
|
rehypeAutolinkHeadings,
|
|
|
|
|
rehypeSlug,
|
|
|
|
|
rehypePresetMinify.plugins,
|
|
|
|
|
() => rehypeHighlight({ detect: true }),
|
|
|
|
|
],
|
2023-12-25 17:21:39 +08:00
|
|
|
format: "md",
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2024-04-03 22:08:27 +08:00
|
|
|
const tocList = makeTOCTree(renderToString(<MDXRemote {...mdxSource} />));
|
2023-12-25 17:21:39 +08:00
|
|
|
|
|
|
|
|
const postIndexInAllPosts = sortedPosts.allPostList.findIndex((item) => item.id === postId);
|
|
|
|
|
|
|
|
|
|
const frontMatter: TFrontmatter = sortedPosts.allPostList[postIndexInAllPosts].frontMatter;
|
|
|
|
|
|
|
|
|
|
const nextPostListItem =
|
|
|
|
|
postIndexInAllPosts !== sortedPosts.allPostList.length - 1
|
|
|
|
|
? sortedPosts.allPostList[postIndexInAllPosts + 1]
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
const prevPostListItem = postIndexInAllPosts !== 0 ? sortedPosts.allPostList[postIndexInAllPosts - 1] : null;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
props: {
|
2024-01-04 13:28:16 +08:00
|
|
|
compiledSource: mdxSource,
|
2023-12-25 17:21:39 +08:00
|
|
|
tocList: tocList,
|
|
|
|
|
frontMatter: frontMatter,
|
|
|
|
|
postId: postId,
|
|
|
|
|
nextPostListItem: nextPostListItem,
|
|
|
|
|
prevPostListItem: prevPostListItem,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default ReaderPage;
|