initial commit

This commit is contained in:
PrinOrange
2023-12-25 17:21:39 +08:00
commit 0bd1089d74
94 changed files with 18648 additions and 0 deletions

31
pages/404.tsx Normal file
View File

@@ -0,0 +1,31 @@
import { ContentContainer, Page } from "@/components/layouts/layouts";
import { Footer } from "@/components/utils/Footer";
import { NavBar } from "@/components/utils/NavBar";
import { fontSypxzs } from "@/styles/font";
import { TfiFaceSad } from "react-icons/tfi";
export default function NotFoundPage() {
const goBack = () => {
window.history.back();
};
return (
<Page>
<NavBar />
<ContentContainer>
<div className="flex flex-col justify-center">
<TfiFaceSad className="mx-auto my-4" size={"6em"} />
<p className="mx-auto my-3 text-center text-2xl font-bold">{"404 NOT FOUND"}</p>
<p className={`${fontSypxzs.className} mx-auto my-3 text-center text-xl`}>
{"This page does not exist for it might be removed or closed."}
</p>
<div className="my-5 flex justify-center">
<button onClick={goBack} className="link text-xl font-bold">
{"GO BACK"}
</button>
</div>
</div>
</ContentContainer>
<Footer />
</Page>
);
}

16
pages/_app.tsx Normal file
View File

@@ -0,0 +1,16 @@
import "@/styles/globals.css";
import { Analytics } from "@vercel/analytics/react";
import { SpeedInsights } from "@vercel/speed-insights/next";
import "katex/dist/katex.min.css";
import { ThemeProvider } from "next-themes";
import type { AppProps } from "next/app";
export default function App({ Component, pageProps }: AppProps) {
return (
<ThemeProvider attribute="class" enableColorScheme enableSystem={false}>
<Analytics />
<SpeedInsights />
<Component {...pageProps} />
</ThemeProvider>
);
}

13
pages/_document.tsx Normal file
View File

@@ -0,0 +1,13 @@
import { Head, Html, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}

75
pages/about.tsx Normal file
View File

@@ -0,0 +1,75 @@
import { ContentContainer, Page } from "@/components/layouts/layouts";
import { Footer } from "@/components/utils/Footer";
import { NavBar } from "@/components/utils/NavBar";
import { SEO } from "@/components/utils/SEO";
import { SocialIcons } from "@/components/utils/SocialIcons";
import { Config } from "@/data/config";
import { fontFzxbs, fontSypxzs } from "@/styles/font";
import Link from "next/link";
export default function AboutPage() {
return (
<Page>
<SEO
title={`About Me - ${Config.AuthorName}`}
description={"Type your brief self-introduction in a sentence here make SEO recognize it easily."}
coverURL={Config.PageCovers.websiteCoverURL}
/>
<NavBar />
<ContentContainer>
<h2 className={`my-5 flex justify-around text-2xl font-bold ${fontFzxbs.className}`}>{"ABOUT ME"}</h2>
<hr />
<div className={`${fontSypxzs.className} my-5 justify-center md:flex md:space-x-10`}>
<div className="my-auto flex md:w-1/3">
<img alt="my-profile" className="mx-auto my-auto max-h-[23rem] rounded-lg" src="/images/profile.webp" />
</div>
<div className="my-auto md:w-1/3">
<div className="mt-5 mb-3 text-3xl font-bold">Hi, there👋</div>
I am a student / entrepreneur / engineer (Your profession) majoring in (Your Research Field) born in XXXX
(Your birth year)
<br />
<br />
My main research interests includes XXXX
<br />
<br />
Additionally, I am also interested in XXXX.
</div>
</div>
<hr />
<SocialIcons />
<hr />
<ul className="mx-auto my-10 md:w-2/3 list-disc">
{Config.SocialLinks.github && (
<li className="my-2">
{"📕 Check out my github profile at "}
<Link target="_blank" className="underline" href={`https://github.com/${Config.SocialLinks.github}`}>
Github
</Link>
</li>
)}
<li className="my-2">🖥 Programming stack: XXXX</li>
<li className="my-2">🤝 I am looking to XXXXX</li>
{Config.SocialLinks.twitter && (
<li className="my-2">
{"📫 How to reach me on Twitter: "}
<Link target="_blank" className="link" href={`https://x.com/${Config.SocialLinks.twitter}`}>
{Config.SocialLinks.twitter}
</Link>
</li>
)}
<li className="my-2">Language : 汉语普通话First Language / English / / </li>
<li className="my-2">Gender Identity : Male / Female / MTF / FTM / And Others </li>
<li className="my-2">From : Your Country, State / Province</li>
</ul>
<div className="mx-auto my-10 md:w-2/3 font-bold">
{
"** In addition to the above content, you can also add other customized components, content, etc. to this page. **"
}
</div>
</ContentContainer>
<Footer />
</Page>
);
}

214
pages/blog/[id].tsx Normal file
View File

@@ -0,0 +1,214 @@
import { ContentContainer, Page } from "@/components/layouts/layouts";
import { MDXComponentsSet } from "@/components/mdx";
import { PostComments } from "@/components/readerpage/PostComments";
import { PostCover } from "@/components/readerpage/PostCover";
import { ShareButtons } from "@/components/readerpage/ShareButtons";
import { SideTOC } from "@/components/readerpage/SideTOC";
import { TOC } from "@/components/readerpage/TOC";
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";
import { getTOCTree } from "@/lib/toc";
import { fontFzxbs, fontSypxzs } from "@/styles/font";
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";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypeKatex from "rehype-katex";
import rehypePresetMinify from "rehype-preset-minify";
import rehypeSlug from "rehype-slug";
import externalLinks from "remark-external-links";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import remarkPrism from "remark-prism";
type ReaderPageProps = {
source: MDXRemoteSerializeResult;
tocList: TTOCItem[];
frontMatter: TFrontmatter;
postId: string;
nextPostListItem: TPostListItem | null;
prevPostListItem: TPostListItem | null;
};
const ReaderPage = (props: ReaderPageProps) => {
const source = props.source;
return (
<Page>
<SEO
title={`${props.frontMatter.title} - ${Config.SiteTitle}`}
description={props.frontMatter.summary}
coverURL={props.frontMatter.coverURL ?? Config.AvatarURL}
/>
<NavBar />
<Toaster />
<ContentContainer>
<div className="my-5 justify-center md:flex">
<div className="md:w-2/3">
<div className="py-1">
{props.frontMatter.coverURL && <PostCover coverURL={props.frontMatter.coverURL} />}
<h2
className={`${fontFzxbs.className} flex justify-center whitespace-normal break-words text-3xl font-bold capitalize`}
>
{props.frontMatter?.title}
</h2>
{props.frontMatter?.subtitle && (
<div className={`${fontFzxbs.className} my-1 flex justify-center text-xl font-bold capitalize`}>
{props.frontMatter.subtitle}
</div>
)}
<div className="my-2 flex justify-center text-sm italic">{normalizeDate(props.frontMatter?.time)}</div>
{props.frontMatter?.summary && (
<p className={`${fontSypxzs.className} my-4 indent-8 text-gray-800 dark:text-gray-300`}>
{props.frontMatter?.summary}
</p>
)}
{props.frontMatter.tags && (
<div className={`py-3 flex flex-wrap justify-start border-t border-b`}>
<div className="font-bold mr-2 my-1">{"TAGS : "}</div>
{props.frontMatter.tags.map((tagName) => (
<Link
href={`/tags/${tagName}`}
className={`tag-link m-1 text-sm ${fontSypxzs.className}`}
key={`tags-${nanoid()}`}
>
{tagName}
</Link>
))}
</div>
)}
</div>
<div
className={`typesetting ${fontSypxzs.className} flat-scrollbar-thin my-0 ${
!props.frontMatter.allowShare && "select-none"
}`}
>
{source && (
<MDXRemote
compiledSource={source.compiledSource}
frontmatter={source.frontmatter}
scope={source.scope}
//@ts-ignore
components={MDXComponentsSet}
/>
)}
</div>
<hr />
<ShareButtons
subtitle={props.frontMatter.subtitle}
title={props.frontMatter.title}
quote={props.frontMatter.summary}
postId={props.postId}
allowShare={props.frontMatter.allowShare}
/>
<hr />
<ul className="my-2 px-5 flex flex-col justify-center list-disc">
{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>
<PostComments postId={props.postId} />
</div>
{props.tocList.length > 2 && (
<div className="hidden md:block md:w-1/3">
<TOC data={props.tocList} />
</div>
)}
</div>
{props.tocList.length > 2 && (
<div className="md:hidden">
<SideTOC data={props.tocList} />
</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: {
remarkPlugins: [remarkPrism, externalLinks, remarkMath, remarkGfm],
rehypePlugins: [rehypeKatex as any, rehypeAutolinkHeadings, rehypeSlug, rehypePresetMinify],
format: "md",
},
});
const tocList = getTOCTree(renderToString(<MDXRemote {...mdxSource} />));
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: {
source: mdxSource,
tocList: tocList,
frontMatter: frontMatter,
postId: postId,
nextPostListItem: nextPostListItem,
prevPostListItem: prevPostListItem,
},
};
};
export default ReaderPage;

39
pages/friends.tsx Normal file
View File

@@ -0,0 +1,39 @@
import { ContentContainer, Page } from "@/components/layouts/layouts";
import { Footer } from "@/components/utils/Footer";
import { NavBar } from "@/components/utils/NavBar";
import { SEO } from "@/components/utils/SEO";
import { Config } from "@/data/config";
import { FriendsList } from "@/data/friends";
import { fontFzxbs, fontSypxzs } from "@/styles/font";
import { nanoid } from "nanoid";
import Link from "next/link";
export default function FriendsPage() {
return (
<Page>
<SEO title={`${Config.SiteTitle} - Friends`} description={"My Friend Links"} />
<NavBar />
<ContentContainer>
<h2 className={`my-5 flex justify-center text-2xl font-bold ${fontFzxbs.className}`}>{"FRIENDS"}</h2>
<hr />
<div className={`my-5 flex flex-wrap justify-center text-2xl ${fontSypxzs.className}`}>
{FriendsList.map((item) => (
<Link className="mx-3 p-2 underline" href={item.url} key={nanoid()}>
{item.title}
</Link>
))}
</div>
<hr />
<div className="my-2 text-base flex-col flex justify-start">
<div className="mx-auto">
{"Welcome to exchange our friend links and every high-quality blog websites are welcomed. "}
<Link className="underline" href={`mailto:${Config.SocialLinks.email}`}>
{"Email me please"}
</Link>
</div>
</div>
</ContentContainer>
<Footer />
</Page>
);
}

87
pages/index.tsx Normal file
View File

@@ -0,0 +1,87 @@
import { HomeCover } from "@/components/homepage/HomeCover";
import { ContentContainer, Page } from "@/components/layouts/layouts";
import { Footer } from "@/components/utils/Footer";
import { NavBar } from "@/components/utils/NavBar";
import { PostList } from "@/components/utils/PostList";
import { SEO } from "@/components/utils/SEO";
import { LatestPostCountInHomePage } from "@/consts/consts";
import { Config } from "@/data/config";
import { sortedPosts } from "@/lib/post-process";
import { generateRSSFeed } from "@/lib/rss";
import { fontFzxbs } from "@/styles/font";
import { TPostListItem } from "@/types/post-list";
import { GetStaticProps } from "next";
import Link from "next/link";
import { LuPenTool } from "react-icons/lu";
import { RiStarFill } from "react-icons/ri";
type HomePageProps = {
pinnedPostList: TPostListItem[];
latestPostList: TPostListItem[];
};
export default function Home(props: HomePageProps) {
return (
<Page>
<SEO
title={`${Config.SiteTitle} - The personal blog for ${Config.Nickname}`}
description={`Welcome to the ${Config.Nickname}'s blog website. It's the website for recording thoughts for technology, life experience and so on.`}
coverURL={Config.PageCovers.websiteCoverURL}
/>
<NavBar />
<ContentContainer>
<HomeCover />
{props.pinnedPostList.length !== 0 && (
<div>
<hr />
<h2 className={`my-5 flex justify-center text-2xl font-bold ${fontFzxbs.className}`}>
<RiStarFill className="mx-2 my-auto" />
{"PINNED POSTS"}
</h2>
<hr />
<PostList data={props.pinnedPostList} />
</div>
)}
<hr />
{props.latestPostList.length !== 0 && (
<div>
<h2 className={`my-5 flex justify-center text-2xl font-bold ${fontFzxbs.className}`}>
<LuPenTool className="mx-2 my-auto" />
{"LATEST POSTS"}
</h2>
<hr />
<PostList data={props.latestPostList} />
<div className="my-2 flex justify-end">
<Link href="/posts" className="link-button font-bold text-base">
{"MORE POSTS >"}
</Link>
</div>
</div>
)}
</ContentContainer>
<Footer />
</Page>
);
}
export const getStaticProps: GetStaticProps<HomePageProps> = async () => {
const pinnedPostList = sortedPosts.pinnedPostList;
const latestPostList = [];
for (let i = 0, j = 0; j < LatestPostCountInHomePage && i < sortedPosts.allPostList.length; i++) {
const postListItem = sortedPosts.allPostList[i];
if (!postListItem.frontMatter.noPrompt) {
latestPostList.push(postListItem);
j++;
}
}
await generateRSSFeed();
return {
props: {
pinnedPostList: pinnedPostList,
latestPostList: latestPostList,
},
};
};

116
pages/posts/[[...slug]].tsx Normal file
View File

@@ -0,0 +1,116 @@
import { ContentContainer, Page } from "@/components/layouts/layouts";
import { Input } from "@/components/ui/input";
import { Footer } from "@/components/utils/Footer";
import { NavBar } from "@/components/utils/NavBar";
import { PostList } from "@/components/utils/PostList";
import { SEO } from "@/components/utils/SEO";
import { PostCountPerPagination } from "@/consts/consts";
import { Config } from "@/data/config";
import { sortedPosts } from "@/lib/post-process";
import { paginateArray } from "@/lib/utils";
import { fontFzxbs } from "@/styles/font";
import { TPostListItem } from "@/types/post-list";
import { GetStaticPaths, GetStaticProps } from "next";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { ChangeEvent, KeyboardEvent, useEffect, useState } from "react";
import { LuPenTool } from "react-icons/lu";
type PostsPageProps = {
pageAmount: number;
pageNumber: number;
postList: TPostListItem[];
};
export default function PostsPage(props: PostsPageProps) {
const router = useRouter();
const [pageNumber, setPageNumber] = useState<string>(props.pageNumber.toString());
const handleEnterKeyJump = (event: KeyboardEvent<HTMLInputElement>) => {
setPageNumber(pageNumber.replace(/[^\d]/g, ""));
if (parseInt(pageNumber) > 0 && parseInt(pageNumber) < props.pageAmount + 1) {
(event.key === "Go" || event.key === "Enter") && router.push(`/posts/${pageNumber}`);
return;
}
};
const handleChangePageNumber = (event: ChangeEvent<HTMLInputElement>) => {
setPageNumber(event.target.value);
};
useEffect(() => {
setPageNumber(props.pageNumber.toString().replace(/[^\d]/g, ""));
}, [props.pageNumber]);
return (
<Page>
<SEO
title={`${Config.SiteTitle} - All published posts`}
description={"Here is the list page for all published posts. Click here for more details."}
coverURL={Config.PageCovers.websiteCoverURL}
/>
<NavBar />
<ContentContainer>
<h2 className={`my-5 flex justify-center text-2xl ${fontFzxbs.className} font-bold`}>
<LuPenTool className="mx-2 my-auto" />
{"ALL POSTS"}
</h2>
<hr />
<PostList data={props.postList} />
<div className="my-5 flex justify-between text-base font-bold">
{props.pageNumber !== 1 && (
<Link href={`/posts/${props.pageNumber - 1}`} className="link-button my-auto">
{"< PREV"}
</Link>
)}
<div className="my-auto font-bold flex justify-center">
<Input
onKeyDown={handleEnterKeyJump}
onChange={handleChangePageNumber}
className="my-auto mx-2 w-11 h-6"
value={pageNumber}
/>
<div className="my-auto">{` / ${props.pageAmount}`}</div>
</div>
{props.pageNumber !== props.pageAmount && (
<Link href={`/posts/${props.pageNumber + 1}`} className="link-button my-auto">
{"NEXT >"}
</Link>
)}
</div>
</ContentContainer>
<Footer />
</Page>
);
}
export const getStaticPaths: GetStaticPaths = () => {
const allPaths: { params: { slug?: string[] } }[] = [{ params: { slug: [] } }];
const pageAmount = Math.ceil(sortedPosts.allPostList.length / PostCountPerPagination);
for (let i = 0; i < pageAmount; i++) {
allPaths.push({ params: { slug: [(i + 1).toString()] } });
}
return { paths: allPaths, fallback: false };
};
export const getStaticProps: GetStaticProps<PostsPageProps> = async (context) => {
const params = (context.params?.slug as string[]) ?? [];
const pageNumber = params[0] ? parseInt(params[0]) : 1;
let postList: TPostListItem[] = [];
postList = paginateArray(sortedPosts.allPostList, PostCountPerPagination, pageNumber);
const pageAmount = Math.ceil(sortedPosts.allPostList.length / PostCountPerPagination);
return {
props: {
pageAmount: pageAmount,
pageNumber: pageNumber,
postList: postList,
},
};
};

97
pages/sponsor.tsx Normal file
View File

@@ -0,0 +1,97 @@
import { ContentContainer, Page } from "@/components/layouts/layouts";
import { Footer } from "@/components/utils/Footer";
import { NavBar } from "@/components/utils/NavBar";
import { SEO } from "@/components/utils/SEO";
import { Config } from "@/data/config";
import { isEmptyString } from "@/lib/utils";
import { fontFzxbs, fontSypxzs } from "@/styles/font";
import Link from "next/link";
import { QRCodeSVG } from "qrcode.react";
import { FaCcPaypal } from "react-icons/fa";
import { GoHeartFill } from "react-icons/go";
import { SiAlipay, SiWechat } from "react-icons/si";
export default function AboutPage() {
return (
<Page>
<SEO
title={`${Config.SiteTitle} - Sponsor Me`}
description={
"If you like my works, I would deeply appreciate your support as a patron. Your contribution not only fuels my creative journey but also allows me to delve deeper into my passion."
}
/>
<NavBar />
<ContentContainer>
<div className="md:flex">
<div className="flex flex-col justify-center md:w-1/2">
<h2 className={`my-5 flex justify-center text-2xl font-bold text-red-500 ${fontFzxbs.className}`}>
<GoHeartFill className="mx-2 my-auto" />
{"SPONSOR"}
</h2>
<p className={`${fontSypxzs.className} break-words text-lg`}>
{
"If you like my works, I would deeply appreciate your support as a patron. Your contribution not only fuels my creative journey but also allows me to delve deeper into my passion. Your support plays a vital role in making this vision a reality. Thank you for considering becoming a patron and being an integral part of this work endeavor."
}
<br />
<br />
{"Here are the ways you can become a patron. Thank you for your support!"}
<br />
<br />
{`Yours, ${Config.AuthorName}`}
</p>
</div>
<div className="md:px-15 md:w-1/2">
<div className="mx-2 my-10 flex flex-col justify-around font-bold">
{!isEmptyString(Config.SponsorLink?.wechatPay) && (
<div className="my-3 flex justify-between">
<div className="my-auto flex">
<SiWechat className="mx-3 my-auto text-5xl text-green-500" />
<div className="my-auto">
<h3 className="mx-auto text-sm">{"WECHAT-PAY"}</h3>
</div>
</div>
<div className="my-2 bg-white p-1">
<QRCodeSVG width={120} value={Config.SponsorLink?.wechatPay!} />
</div>
</div>
)}
<hr />
{!isEmptyString(Config.SponsorLink?.alipay) && (
<div className="my-6 flex justify-between">
<div className="my-auto flex">
<SiAlipay className="mx-3 my-auto text-5xl text-blue-500" />
<div className="my-auto">
<h3 className="mx-auto text-sm">{"ALIPAY"}</h3>
</div>
</div>
<div className="my-2">
<Link className="link-button my-auto text-2xl" target="_blank" href={Config.SponsorLink?.alipay!}>
{"DONATE"}
</Link>
</div>
</div>
)}
<hr />
{!isEmptyString(Config.SponsorLink?.paypal) && (
<div className="my-6 flex justify-between">
<div className="my-auto flex">
<FaCcPaypal className="mx-3 my-auto text-5xl text-blue-600" />
<div className="my-auto">
<h3 className="mx-auto text-sm">{"PAYPAL"}</h3>
</div>
</div>
<div className="my-2">
<Link className="link-button my-auto text-2xl" target="_blank" href={Config.SponsorLink?.paypal!}>
{"DONATE"}
</Link>
</div>
</div>
)}
</div>
</div>
</div>
</ContentContainer>
<Footer />
</Page>
);
}

123
pages/tags/[...slug].tsx Normal file
View File

@@ -0,0 +1,123 @@
import { ContentContainer, Page } from "@/components/layouts/layouts";
import { Input } from "@/components/ui/input";
import { Footer } from "@/components/utils/Footer";
import { NavBar } from "@/components/utils/NavBar";
import { PostList } from "@/components/utils/PostList";
import { SEO } from "@/components/utils/SEO";
import { PostCountPerPagination } from "@/consts/consts";
import { Config } from "@/data/config";
import { sortedPosts } from "@/lib/post-process";
import { paginateArray } from "@/lib/utils";
import { fontFzxbs } from "@/styles/font";
import { TPostListItem } from "@/types/post-list";
import { GetStaticPaths, GetStaticProps } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
import { ChangeEvent, KeyboardEvent, useEffect, useState } from "react";
type TagsContentPageProps = {
tagName: string | null;
postList: TPostListItem[];
pageAmount: number;
pageNumber: number;
};
export default function TagsContentPage(props: TagsContentPageProps) {
const router = useRouter();
const [pageNumber, setPageNumber] = useState<string>(props.pageNumber.toString());
const handleEnterKeyJump = (event: KeyboardEvent<HTMLInputElement>) => {
setPageNumber(pageNumber.replace(/[^\d]/g, ""));
if (parseInt(pageNumber) > 0 && parseInt(pageNumber) < props.pageAmount + 1) {
(event.key === "Go" || event.key === "Enter") && router.push(`/tags/${props.tagName}/${pageNumber}`);
return;
}
};
const handleChangePageNumber = (event: ChangeEvent<HTMLInputElement>) => {
setPageNumber(event.target.value);
};
useEffect(() => {
setPageNumber(props.pageNumber.toString());
}, [props.pageNumber]);
return (
<Page>
<SEO
title={`Tag - ${props.tagName}`}
description={`Here are posts under the tag ${props.tagName}.`}
coverURL={Config.PageCovers.websiteCoverURL}
/>
<NavBar />
<ContentContainer>
<h2 className={`my-5 flex flex-col justify-center text-center text-3xl font-bold ${fontFzxbs.className}`}>
{`Posts of ${props.tagName}`}
</h2>
<hr />
<PostList data={props.postList} />
<div className="my-5 flex justify-between text-base font-bold">
{props.pageNumber !== 1 && (
<Link href={`/tags/${props.tagName}/${props.pageNumber - 1}/`} className="link-button my-auto">
{"< PREV"}
</Link>
)}
<div className="my-auto font-bold flex justify-center">
<Input
onKeyDown={handleEnterKeyJump}
onChange={handleChangePageNumber}
className="my-auto mx-2 w-11 h-6"
value={pageNumber}
/>
<div className="my-auto">{` / ${props.pageAmount}`}</div>
</div>
{props.pageNumber !== props.pageAmount && (
<Link href={`/tags/${props.tagName}/${props.pageNumber + 1}/`} className="link-button my-auto">
{"NEXT >"}
</Link>
)}
</div>
</ContentContainer>
<Footer />
</Page>
);
}
export const getStaticPaths: GetStaticPaths = () => {
const allPaths: { params: { slug: string[] } }[] = [];
const allTags = Object.keys(sortedPosts.tagSubPostSet).map((tagName) => ({
name: tagName,
count: sortedPosts.tagSubPostSet[tagName].length,
}));
for (let i = 0; i < allTags.length; i++) {
allPaths.push({ params: { slug: [allTags[i].name] } });
for (let j = 0; j < allTags[i].count; j++) {
allPaths.push({ params: { slug: [allTags[i].name, (j + 1).toString()] } });
}
}
return { paths: allPaths, fallback: false };
};
export const getStaticProps: GetStaticProps<TagsContentPageProps> = async (context) => {
const params = (context.params?.slug as string[]) ?? [];
const tagName = params[0] ?? null;
const pageNumber = params[1] ? parseInt(params[1]) : 1;
let postList: TPostListItem[] = [];
postList = paginateArray(sortedPosts.tagSubPostSet[tagName], PostCountPerPagination, pageNumber);
const pageAmount = Math.ceil(sortedPosts.tagSubPostSet[tagName].length / PostCountPerPagination);
return {
props: {
tagName: tagName,
pageAmount: pageAmount,
pageNumber: pageNumber,
postList: postList,
},
};
};

58
pages/tags/index.tsx Normal file
View File

@@ -0,0 +1,58 @@
import { ContentContainer, Page } from "@/components/layouts/layouts";
import { Footer } from "@/components/utils/Footer";
import { NavBar } from "@/components/utils/NavBar";
import { SEO } from "@/components/utils/SEO";
import { Config } from "@/data/config";
import { sortedPosts } from "@/lib/post-process";
import { fontFzxbs, fontSypxzs } from "@/styles/font";
import { nanoid } from "nanoid";
import { GetStaticProps } from "next";
import Link from "next/link";
import { AiOutlineTags } from "react-icons/ai";
type TagsIndexPageProps = {
tagList: { name: string; count: number }[];
};
export default function TagsIndexPage(props: TagsIndexPageProps) {
return (
<Page>
<SEO
title={`${Config.SiteTitle} - All tags`}
description={"Here is the list page for all tags which sorts all posts to every catagories."}
coverURL={Config.PageCovers.websiteCoverURL}
/>
<NavBar />
<ContentContainer>
<h2 className={`my-5 flex justify-center text-2xl font-bold ${fontFzxbs.className}`}>
<AiOutlineTags className="mx-2 my-auto" />
{"ALL TAGS"}
</h2>
<div className={`my-5 flex flex-wrap justify-center px-2 ${fontSypxzs.className}`}>
{props.tagList.map((item) => (
<Link key={`tag-link-${nanoid()}`} href={`/tags/${item.name}`} className="tag-link m-2 text-base">
{`${item.name} (${item.count})`}
</Link>
))}
</div>
</ContentContainer>
<Footer />
</Page>
);
}
export const getStaticProps: GetStaticProps<TagsIndexPageProps> = async (context) => {
const tagList: {
name: string;
count: number;
}[] = Object.keys(sortedPosts.tagSubPostSet).map((tagName) => ({
name: tagName,
count: sortedPosts.tagSubPostSet[tagName].length,
}));
return {
props: {
tagList: tagList,
},
};
};