feat: enhance blog pagination and related posts functionality
- Added pagination size and related posts size configurations to the website config. - Updated blog components to utilize the new pagination settings. - Introduced a new feature to display related posts on individual blog pages, improving user engagement. - Added new translations for "More Posts" in English and Chinese.
This commit is contained in:
parent
b1a93a6e49
commit
dd12afcce0
@ -178,7 +178,8 @@
|
||||
"readTime": " min read",
|
||||
"all": "All",
|
||||
"noPostsFound": "No posts found",
|
||||
"allPosts": "All Posts"
|
||||
"allPosts": "All Posts",
|
||||
"morePosts": "More Posts"
|
||||
},
|
||||
"DocsPage": {
|
||||
"toc": "Table of Contents",
|
||||
|
||||
@ -174,7 +174,8 @@
|
||||
"readTime": " 分钟阅读",
|
||||
"all": "全部",
|
||||
"noPostsFound": "没有找到文章",
|
||||
"allPosts": "全部文章"
|
||||
"allPosts": "全部文章",
|
||||
"morePosts": "更多文章"
|
||||
},
|
||||
"DocsPage": {
|
||||
"toc": "目录",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import BlogGrid from '@/components/blog/blog-grid';
|
||||
import EmptyGrid from '@/components/shared/empty-grid';
|
||||
import CustomPagination from '@/components/shared/pagination';
|
||||
import { POSTS_PER_PAGE } from '@/constants';
|
||||
import { websiteConfig } from '@/config';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getBaseUrlWithLocale } from '@/lib/urls/get-base-url';
|
||||
import { NextPageProps } from '@/types/next-page-props';
|
||||
@ -48,13 +48,14 @@ export default async function BlogCategoryPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: NextPageProps) {
|
||||
const paginationSize = websiteConfig.blog.paginationSize;
|
||||
const resolvedParams = await params;
|
||||
const { slug, locale } = resolvedParams;
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const { page } = (resolvedSearchParams as { [key: string]: string }) || {};
|
||||
const currentPage = page ? Number(page) : 1;
|
||||
const startIndex = (currentPage - 1) * POSTS_PER_PAGE;
|
||||
const endIndex = startIndex + POSTS_PER_PAGE;
|
||||
const startIndex = (currentPage - 1) * paginationSize;
|
||||
const endIndex = startIndex + paginationSize;
|
||||
|
||||
// Find category with matching slug and locale
|
||||
const category = allCategories.find(
|
||||
@ -81,7 +82,7 @@ export default async function BlogCategoryPage({
|
||||
// Paginate posts
|
||||
const paginatedPosts = sortedPosts.slice(startIndex, endIndex);
|
||||
const totalCount = filteredPosts.length;
|
||||
const totalPages = Math.ceil(totalCount / POSTS_PER_PAGE);
|
||||
const totalPages = Math.ceil(totalCount / paginationSize);
|
||||
|
||||
// console.log("BlogCategoryPage, totalCount", totalCount, ", totalPages", totalPages);
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import BlogGrid from '@/components/blog/blog-grid';
|
||||
import EmptyGrid from '@/components/shared/empty-grid';
|
||||
import CustomPagination from '@/components/shared/pagination';
|
||||
import { POSTS_PER_PAGE } from '@/constants';
|
||||
import { websiteConfig } from '@/config';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getBaseUrlWithLocale } from '@/lib/urls/get-base-url';
|
||||
import { NextPageProps } from '@/types/next-page-props';
|
||||
@ -29,13 +29,14 @@ export default async function BlogPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: NextPageProps) {
|
||||
const paginationSize = websiteConfig.blog.paginationSize;
|
||||
const resolvedParams = await params;
|
||||
const { locale } = resolvedParams;
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const { page } = (resolvedSearchParams as { [key: string]: string }) || {};
|
||||
const currentPage = page ? Number(page) : 1;
|
||||
const startIndex = (currentPage - 1) * POSTS_PER_PAGE;
|
||||
const endIndex = startIndex + POSTS_PER_PAGE;
|
||||
const startIndex = (currentPage - 1) * paginationSize;
|
||||
const endIndex = startIndex + paginationSize;
|
||||
|
||||
// Filter posts by locale
|
||||
const localePosts = allPosts.filter(
|
||||
@ -56,7 +57,7 @@ export default async function BlogPage({
|
||||
// Paginate posts
|
||||
const paginatedPosts = sortedPosts.slice(startIndex, endIndex);
|
||||
const totalCount = filteredPosts.length;
|
||||
const totalPages = Math.ceil(totalCount / POSTS_PER_PAGE);
|
||||
const totalPages = Math.ceil(totalCount / paginationSize);
|
||||
|
||||
// console.log("BlogPage, totalCount", totalCount, ", totalPages", totalPages,);
|
||||
|
||||
|
||||
@ -1,20 +1,22 @@
|
||||
import AllPostsButton from '@/components/blog/all-posts-button';
|
||||
import BlogGrid from '@/components/blog/blog-grid';
|
||||
import { BlogToc } from '@/components/blog/blog-toc';
|
||||
import { NewsletterCard } from '@/components/newsletter/newsletter-card';
|
||||
import { CustomMDXContent } from '@/components/shared/custom-mdx-content';
|
||||
import { websiteConfig } from '@/config';
|
||||
import { LocaleLink } from '@/i18n/navigation';
|
||||
import { getTableOfContents } from '@/lib/blog/toc';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getBaseUrlWithLocale } from '@/lib/urls/get-base-url';
|
||||
import { getLocaleDate } from '@/lib/utils';
|
||||
import type { NextPageProps } from '@/types/next-page-props';
|
||||
import { allPosts } from 'content-collections';
|
||||
import { CalendarIcon, ClockIcon } from 'lucide-react';
|
||||
import { allPosts, Post } from 'content-collections';
|
||||
import { CalendarIcon, ClockIcon, FileTextIcon } from 'lucide-react';
|
||||
import type { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import Image from 'next/image';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { CustomMDXContent } from '@/components/shared/custom-mdx-content';
|
||||
|
||||
import '@/styles/mdx.css';
|
||||
|
||||
@ -24,15 +26,10 @@ import '@/styles/mdx.css';
|
||||
* @returns The blog post
|
||||
*
|
||||
* How it works:
|
||||
* 1. /[locale]/blog/first-post:
|
||||
* /[locale]/blog/first-post:
|
||||
* params.slug = ["first-post"]
|
||||
* slug becomes "first-post" after join('/')
|
||||
* Matches post where slugAsParams === "first-post" AND locale === params.locale
|
||||
*
|
||||
* 2. /[locale]/blog/2023/year-review:
|
||||
* params.slug = ["2023", "year-review"]
|
||||
* slug becomes "2023/year-review" after join('/')
|
||||
* Matches post where slugAsParams === "2023/year-review" AND locale === params.locale
|
||||
*/
|
||||
async function getBlogPostFromParams(props: NextPageProps) {
|
||||
const params = await props.params;
|
||||
@ -65,6 +62,20 @@ async function getBlogPostFromParams(props: NextPageProps) {
|
||||
return post;
|
||||
}
|
||||
|
||||
/**
|
||||
* get related posts, random pick from all posts with same locale, different slug,
|
||||
* max size is websiteConfig.blog.relatedPostsSize
|
||||
*/
|
||||
async function getRelatedPosts(post: Post) {
|
||||
const relatedPosts = allPosts
|
||||
.filter((p) => p.locale === post.locale)
|
||||
.filter((p) => p.slugAsParams !== post.slugAsParams)
|
||||
.sort(() => Math.random() - 0.5)
|
||||
.slice(0, websiteConfig.blog.relatedPostsSize);
|
||||
|
||||
return relatedPosts;
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
@ -103,6 +114,9 @@ export default async function BlogPostPage(props: NextPageProps) {
|
||||
const toc = await getTableOfContents(post.content);
|
||||
const t = await getTranslations('BlogPage');
|
||||
|
||||
// get related posts
|
||||
const relatedPosts = await getRelatedPosts(post);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
{/* content section */}
|
||||
@ -219,8 +233,24 @@ export default async function BlogPostPage(props: NextPageProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer section shows related posts */}
|
||||
{relatedPosts && relatedPosts.length > 0 && (
|
||||
<div className="flex flex-col gap-8 mt-8">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileTextIcon className="size-4 text-muted-foreground" />
|
||||
<h2 className="text-lg tracking-wider font-semibold text-gradient_indigo-purple">
|
||||
{t('morePosts')}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<BlogGrid posts={relatedPosts} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* newsletter */}
|
||||
<NewsletterCard />
|
||||
<div className="flex items-center justify-start my-8">
|
||||
<NewsletterCard />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import BlogCard, { BlogCardSkeleton } from '@/components/blog/blog-card';
|
||||
import { POSTS_PER_PAGE } from '@/constants';
|
||||
import { websiteConfig } from '@/config';
|
||||
import { Post } from 'content-collections';
|
||||
|
||||
interface BlogGridProps {
|
||||
@ -22,7 +22,7 @@ export default function BlogGrid({ posts }: BlogGridProps) {
|
||||
}
|
||||
|
||||
export function BlogGridSkeleton({
|
||||
count = POSTS_PER_PAGE,
|
||||
count = websiteConfig.blog.paginationSize,
|
||||
}: { count?: number }) {
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
|
||||
@ -58,6 +58,10 @@ export const websiteConfig: WebsiteConfig = {
|
||||
from: 'support@mksaas.com',
|
||||
to: 'support@mksaas.com',
|
||||
},
|
||||
blog: {
|
||||
paginationSize: 6,
|
||||
relatedPostsSize: 3,
|
||||
},
|
||||
social: {
|
||||
github: 'https://github.com/MkSaaSHQ',
|
||||
twitter: 'https://x.com/MkSaaSHQ',
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export const POSTS_PER_PAGE = 6;
|
||||
6
src/types/index.d.ts
vendored
6
src/types/index.d.ts
vendored
@ -11,7 +11,11 @@ export type WebsiteConfig = {
|
||||
mail: {
|
||||
from?: string;
|
||||
to?: string;
|
||||
}
|
||||
};
|
||||
blog: {
|
||||
paginationSize: number;
|
||||
relatedPostsSize: number;
|
||||
};
|
||||
social: {
|
||||
twitter?: string;
|
||||
github?: string;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user