108 lines
3.3 KiB
TypeScript
108 lines
3.3 KiB
TypeScript
import type { Metadata } from "next";
|
|
import Link from "next/link";
|
|
import { notFound, redirect } from "next/navigation";
|
|
|
|
import CommentBox from "@/components/CommentBox";
|
|
import SlotImage from "@/components/SlotImage";
|
|
import {
|
|
blogDate,
|
|
blogHref,
|
|
blogImage,
|
|
blogTitleSlug,
|
|
fetchBlogById,
|
|
getLocalBlogPost,
|
|
} from "@/lib/blog";
|
|
import { fetchComments } from "@/lib/comments";
|
|
import { sanitizeHtml } from "@/lib/format";
|
|
import { pageMetadata } from "@/lib/seo";
|
|
|
|
export const revalidate = 60;
|
|
|
|
type Params = { id: string; slug: string };
|
|
|
|
export async function generateMetadata({
|
|
params,
|
|
}: {
|
|
params: Promise<Params>;
|
|
}): Promise<Metadata> {
|
|
const { id } = await params;
|
|
const post = (await fetchBlogById(id)) ?? getLocalBlogPost(id);
|
|
if (!post) {
|
|
return {
|
|
title: "مقاله یافت نشد",
|
|
description: "این مطلب در وبلاگ پنجره فرخ موجود نیست.",
|
|
robots: { index: false },
|
|
};
|
|
}
|
|
return pageMetadata({
|
|
title: post.title,
|
|
description: post.abstract?.trim() || `${post.title} | مقالات پنجره فرخ`,
|
|
path: blogHref(post),
|
|
image: blogImage(post),
|
|
});
|
|
}
|
|
|
|
export default async function BlogDetailPage({ params }: { params: Promise<Params> }) {
|
|
const { id, slug } = await params;
|
|
const apiPost = await fetchBlogById(id);
|
|
const post = apiPost ?? getLocalBlogPost(id);
|
|
if (!post) notFound();
|
|
|
|
const canonical = blogTitleSlug(post.title);
|
|
if (decodeURIComponent(slug) !== canonical) {
|
|
redirect(blogHref(post));
|
|
}
|
|
|
|
const comments = apiPost ? await fetchComments("blog", post.id) : [];
|
|
const html = sanitizeHtml(post.mainTextHtml);
|
|
|
|
return (
|
|
<>
|
|
<section className="page-hero">
|
|
<div className="container page-hero-inner">
|
|
<div className="page-hero-en">
|
|
<span>ARTICLE</span>
|
|
<i />
|
|
</div>
|
|
<div className="page-hero-fa">
|
|
<nav className="crumbs">
|
|
<Link href="/">خانه</Link>
|
|
{" > "}
|
|
<Link href="/blog">مقالات</Link>
|
|
{" > "}
|
|
<span>{post.title}</span>
|
|
</nav>
|
|
<h1>{post.title}</h1>
|
|
{blogDate(post) ? <p className="muted">{blogDate(post)}</p> : null}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="section">
|
|
<div className="container detail-article">
|
|
{blogImage(post) ? (
|
|
<div className="article-cover">
|
|
<SlotImage src={blogImage(post)} alt={post.title} />
|
|
</div>
|
|
) : null}
|
|
{post.abstract ? <p className="detail-lead">{post.abstract}</p> : null}
|
|
{html ? <div className="rich-text" dangerouslySetInnerHTML={{ __html: html }} /> : null}
|
|
|
|
<section className="detail-tabs">
|
|
<h2>نظرات</h2>
|
|
{apiPost ? (
|
|
<CommentBox entityType="blog" entityId={post.id} comments={comments} />
|
|
) : (
|
|
<p className="comment-empty">ثبت نظر برای این مطلب در دسترس نیست.</p>
|
|
)}
|
|
</section>
|
|
|
|
<Link href="/blog" className="text-link">
|
|
بازگشت به مقالات
|
|
</Link>
|
|
</div>
|
|
</section>
|
|
</>
|
|
);
|
|
}
|