Add blog post pagination

This commit is contained in:
DefectingCat
2022-08-23 15:04:51 +08:00
parent 2f42e3fb90
commit a62f420f4d
8 changed files with 220 additions and 56 deletions

View File

@ -1,5 +1,6 @@
import dynamic from 'next/dynamic';
import Link from 'next/link';
import { DetailedHTMLProps, HTMLAttributes } from 'react';
const Button = dynamic(() => import('components/RUA/Button'));
@ -10,7 +11,7 @@ type Props = {
nextLink: string;
current?: number;
total?: number;
};
} & DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
const RUAPagination = ({
hasPrev,
@ -19,10 +20,11 @@ const RUAPagination = ({
nextLink,
current,
total,
...rest
}: Props) => {
return (
<>
<nav>
<nav {...rest}>
<ul className="flex items-center justify-between -space-x-px">
<li>
{hasPrev ? (

24
layouts/BlogList.tsx Normal file
View File

@ -0,0 +1,24 @@
import cn from 'classnames';
type Props = {
children: React.ReactElement | React.ReactElement[];
};
const BlogList = ({ children }: Props) => {
return (
<>
<h1
className={cn(
'text-5xl font-bold text-center font-Barlow',
'mt-8 mb-20 text-gray-800 dark:text-gray-200'
)}
>
Blog posts
</h1>
<div className="px-4 lg:px-0">{children}</div>
</>
);
};
export default BlogList;

View File

@ -33,6 +33,25 @@ export const postLists = async (): Promise<Post[]> => {
return (await Promise.all(files.map(readFileMeta))).sort(sortByDate);
};
/**
* Get posts list page.
*/
export const PostPerPage = 5;
export type PostPath = { params: { page: string } };
const postPathCallback = (prev: PostPath[], _: unknown, i: number) =>
i + 1 > 2
? prev.concat({
params: { page: (i + 1).toString() },
})
: prev;
export const getPostListPath = async () => {
const length = (await fs.readdir(path.join(dataPath))).length;
const pages = Math.ceil(length / PostPerPage);
return Array.from({ length: pages }).reduce<PostPath[]>(postPathCallback, [
{ params: { page: '2' } },
]);
};
/**
* Replace file name.
* @param filename

View File

@ -1,48 +0,0 @@
import cn from 'classnames';
import PostCardLoading from 'components/RUA/loading/PostCardLoading';
import { postLists } from 'lib/posts';
import { InferGetStaticPropsType } from 'next';
import dynamic from 'next/dynamic';
import { ReactElement } from 'react';
const PostCard = dynamic(() => import('components/PostCard'), {
loading: () => <PostCardLoading />,
});
const MainLayout = dynamic(() => import('layouts/MainLayout'));
const Blog = ({ posts }: InferGetStaticPropsType<typeof getStaticProps>) => {
return (
<>
<main className="max-w-4xl mx-auto">
<h1
className={cn(
'text-5xl font-bold text-center font-Barlow',
'mt-8 mb-20 text-gray-800 dark:text-gray-200'
)}
>
Blog posts
</h1>
<div className="px-4 lg:px-0">
{posts.map((post) => (
<PostCard key={post.slug} post={post} />
))}
</div>
</main>
</>
);
};
export const getStaticProps = async () => {
return {
props: {
posts: await postLists(),
},
};
};
Blog.getLayout = function getLayout(page: ReactElement) {
return <MainLayout>{page}</MainLayout>;
};
export default Blog;

79
pages/blog/[page].tsx Normal file
View File

@ -0,0 +1,79 @@
import PostCardLoading from 'components/RUA/loading/PostCardLoading';
import { getPostListPath, postLists, PostPerPage } from 'lib/posts';
import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from 'next';
import dynamic from 'next/dynamic';
import { ReactElement } from 'react';
import { Post } from 'types';
const PostCard = dynamic(() => import('components/PostCard'), {
loading: () => <PostCardLoading />,
});
const MainLayout = dynamic(() => import('layouts/MainLayout'));
const BlogList = dynamic(() => import('layouts/BlogList'));
const Pagination = dynamic(() => import('components/RUA/RUAPagination'));
const BlogPage = ({
posts,
prev,
next,
total,
}: InferGetStaticPropsType<typeof getStaticProps>) => {
return (
<>
<main className="max-w-4xl mx-auto">
<BlogList>
{posts.map((post) => (
<PostCard key={post.slug} post={post} />
))}
</BlogList>
<Pagination
className="py-6 px-7 lg:px-5"
hasPrev={!!prev}
hasNext={next === total}
prevLink={prev === 1 ? '/blog' : `/blog/${prev}`}
nextLink={`/blog/${next}`}
current={next - 1}
total={total}
/>
</main>
</>
);
};
export const getStaticPaths: GetStaticPaths = async () => {
return {
paths: await getPostListPath(),
fallback: false,
};
};
export const getStaticProps: GetStaticProps<{
posts: Post[];
prev: number;
next: number;
total: number;
}> = async ({ params }) => {
const page = Number(params?.page);
if (!page) {
return {
notFound: true,
};
}
const posts = await postLists();
return {
props: {
posts: posts.slice(page, PostPerPage),
prev: page - 1,
next: page + 1,
total: Math.ceil(posts.length / PostPerPage),
},
};
};
BlogPage.getLayout = function getLayout(page: ReactElement) {
return <MainLayout>{page}</MainLayout>;
};
export default BlogPage;

67
pages/blog/index.tsx Normal file
View File

@ -0,0 +1,67 @@
import cn from 'classnames';
import PostCardLoading from 'components/RUA/loading/PostCardLoading';
import { postLists, PostPerPage } from 'lib/posts';
import { GetStaticProps, InferGetStaticPropsType } from 'next';
import dynamic from 'next/dynamic';
import { ReactElement } from 'react';
import { Post } from 'types';
const PostCard = dynamic(() => import('components/PostCard'), {
loading: () => <PostCardLoading />,
});
const MainLayout = dynamic(() => import('layouts/MainLayout'));
const BlogList = dynamic(() => import('layouts/BlogList'));
const Pagination = dynamic(() => import('components/RUA/RUAPagination'));
const Blog = ({
posts,
prev,
next,
total,
}: InferGetStaticPropsType<typeof getStaticProps>) => {
return (
<>
<main className="max-w-4xl mx-auto">
<BlogList>
{posts.map((post) => (
<PostCard key={post.slug} post={post} />
))}
</BlogList>
<Pagination
className="py-6 px-7 lg:px-5"
hasPrev={false}
hasNext={next === total}
prevLink={''}
nextLink={`/blog/${next}`}
current={1}
total={total}
/>
</main>
</>
);
};
export const getStaticProps: GetStaticProps<{
posts: Post[];
prev: number;
next: number;
total: number;
}> = async () => {
const posts = await postLists();
return {
props: {
// Latest posts.
posts: posts.slice(0, PostPerPage),
prev: 0,
next: 2,
total: Math.ceil(posts.length / PostPerPage),
},
};
};
Blog.getLayout = function getLayout(page: ReactElement) {
return <MainLayout>{page}</MainLayout>;
};
export default Blog;

View File

@ -9,13 +9,16 @@ import { ReactElement } from 'react';
const MainLayout = dynamic(() => import('layouts/MainLayout'));
const UserInfo = dynamic(() => import('components/gists/UserInfo'));
const FileContent = dynamic(() => import('components/gists/FileContent'));
const Pagination = dynamic(() => import('components/gists/Pagination'));
const Pagination = dynamic(() => import('components/RUA/RUAPagination'));
dayjs.extend(relativeTime);
const Gists = ({
gists,
user,
prev,
next,
total,
}: InferGetStaticPropsType<typeof getStaticProps>) => {
return (
<>
@ -25,7 +28,14 @@ const Gists = ({
<div className="flex-1 py-4 overflow-hidden md:pl-8">
<FileContent gists={gists.gists} />
<Pagination pageSize={gists.pageSize} />
<Pagination
hasPrev={!!prev}
hasNext={!!next}
prevLink={prev === 1 ? `/gists/` : `/gists/${prev}`}
nextLink={`/gists/${next}`}
current={prev == null ? next - 1 : prev + 1}
total={total}
/>
</div>
</div>
</main>
@ -61,6 +71,9 @@ export const getStaticPaths: GetStaticPaths = async () => {
export const getStaticProps: GetStaticProps<{
gists: GetGists;
user: GetUser;
prev: number;
next: number;
total: number;
}> = async ({ params }) => {
if (typeof params?.p !== 'string')
return {
@ -79,6 +92,9 @@ export const getStaticProps: GetStaticProps<{
props: {
gists: result,
user,
prev: Number(result.pageSize.prev),
next: Number(result.pageSize.next),
total: Number(result.pageSize.last),
},
revalidate: 600,
};

View File

@ -15,11 +15,10 @@ dayjs.extend(relativeTime);
const Gists = ({
gists,
user,
prev,
next,
total,
}: InferGetStaticPropsType<typeof getStaticProps>) => {
const prev = Number(gists.pageSize.prev);
const next = Number(gists.pageSize.next);
const total = Number(gists.pageSize.last);
return (
<>
<main className="max-w-5xl px-4 mx-auto lg:px-0">
@ -46,6 +45,9 @@ const Gists = ({
export const getStaticProps: GetStaticProps<{
gists: GetGists;
user: GetUser;
prev: number;
next: number;
total: number;
}> = async () => {
const result = await getGists();
if (!result)
@ -59,6 +61,9 @@ export const getStaticProps: GetStaticProps<{
props: {
gists: result,
user,
prev: Number(result.pageSize.prev),
next: Number(result.pageSize.next),
total: Number(result.pageSize.last),
},
revalidate: 600,
};