import type { Metadata } from "next";
import { notFound } from "next/navigation";
import BlogPostActions from "@/components/BlogPostActions";
import BlogPostInteractions from "@/components/BlogPostInteractions";
import BlogThumbnail from "@/components/BlogThumbnail";
import Markdown from "@/components/Markdown";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Link } from "@/lib/router";
import { blogPostPath, blogPostUrl, normalizeBlogSlugParam } from "@/lib/blog-routes";
import { extractMarkdownHeadings, type MarkdownHeading } from "@/lib/markdown-headings";
import { PublicApiError, getPublicPost, getRecommendedPosts } from "@/lib/public-api";
import { apiBaseUrl, siteUrl, toAbsoluteUrl } from "@/lib/site";
import type * as Types from "@/lib/types";
import { formatJalaliDate, getBlogCardImageUrl, getBlogHeroImageUrl } from "@/lib/utils";
type Params = Promise<{ slug: string }>;
export const dynamic = "force-dynamic";
function cleanText(value?: string | null) {
if (!value) return "";
return value.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
}
function authorName(post: Types.PostListSchema) {
return [post.author.first_name, post.author.last_name].filter(Boolean).join(" ") || post.author.username;
}
async function loadPost(slug: string) {
try {
return await getPublicPost(slug);
} catch (error) {
if (error instanceof PublicApiError && error.status === 404) {
notFound();
}
throw error;
}
}
async function loadRecommended(slug: string) {
try {
return await getRecommendedPosts(slug, 3);
} catch {
return [];
}
}
function TableOfContents({ headings }: { headings: MarkdownHeading[] }) {
if (!headings.length) {
return
برای این نوشته فهرست تیترها ثبت نشده است.
;
}
return (
);
}
function HashTags({ tags }: { tags: Types.PostListSchema["tags"] }) {
if (!tags.length) {
return هشتگی برای این نوشته ثبت نشده است.
;
}
return (
{tags.map((tag) => (
#{tag.name}
))}
);
}
function RecommendedPosts({ posts }: { posts: Types.PostListSchema[] }) {
if (!posts.length) return null;
return (
ادامه مطالعه
نوشتههای پیشنهادی
{posts.map((post) => (
{post.title}
))}
);
}
export async function generateMetadata({
params,
}: {
params: Params;
}): Promise {
const { slug } = await params;
const post = await loadPost(normalizeBlogSlugParam(slug));
const description = cleanText(post.excerpt || post.content).slice(0, 160);
const metaTitle = post.seo_title || post.og_title || post.title;
const metaDescription = post.seo_description || post.og_description || description;
const canonical = post.canonical_url || blogPostPath(post.slug);
const image = toAbsoluteUrl(
post.og_image_url || getBlogHeroImageUrl(post),
apiBaseUrl,
) ?? `${siteUrl}/favicon.ico`;
return {
title: metaTitle,
description: metaDescription,
alternates: { canonical },
robots: post.noindex ? { index: false, follow: true } : undefined,
openGraph: {
title: post.og_title || metaTitle,
description: post.og_description || metaDescription,
url: blogPostUrl(siteUrl, post.slug),
siteName: "انجمن علمی مهندسی کامپیوتر شرق گیلان",
type: "article",
images: [image],
locale: "fa_IR",
publishedTime: post.published_at || post.created_at,
modifiedTime: post.updated_at,
},
twitter: {
card: "summary_large_image",
title: post.og_title || metaTitle,
description: post.og_description || metaDescription,
images: [image],
},
};
}
export default async function BlogDetailPage({
params,
}: {
params: Params;
}) {
const { slug } = await params;
const post = await loadPost(normalizeBlogSlugParam(slug));
const recommendedPosts = await loadRecommended(post.slug);
const headings = extractMarkdownHeadings(post.content);
const description = cleanText(post.excerpt || post.content).slice(0, 160);
const metaDescription = post.seo_description || post.og_description || description;
const coverImage = toAbsoluteUrl(getBlogHeroImageUrl(post), apiBaseUrl);
const seoImage = toAbsoluteUrl(post.og_image_url || getBlogHeroImageUrl(post), apiBaseUrl) ?? `${siteUrl}/favicon.ico`;
const structuredData = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: post.title,
description: metaDescription,
image: [seoImage],
datePublished: post.published_at || post.created_at,
dateModified: post.updated_at,
url: blogPostUrl(siteUrl, post.slug),
author: {
"@type": "Person",
name: authorName(post),
},
publisher: {
"@type": "Organization",
name: "انجمن علمی مهندسی کامپیوتر شرق گیلان",
logo: {
"@type": "ImageObject",
url: `${siteUrl}/favicon.ico`,
},
},
keywords: post.tags.map((tag) => tag.name).join(", "),
};
return (
);
}