import type { Metadata } from "next"; import { notFound } from "next/navigation"; import { CalendarDays, Clock3, Hash, ListTree } from "lucide-react"; import BlogPostActions from "@/components/BlogPostActions"; import BlogPostInteractions from "@/components/BlogPostInteractions"; import BlogTableOfContents from "@/components/BlogTableOfContents"; import BlogThumbnail from "@/components/BlogThumbnail"; import Markdown from "@/components/Markdown"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Link } from "@/lib/router"; import { blogPostPath, blogPostUrl, normalizeBlogSlugParam } from "@/lib/blog-routes"; import { extractMarkdownHeadings } 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, toPersianDigits } from "@/lib/utils"; type Params = Promise<{ slug: string }>; type Writer = NonNullable[number]; export const dynamic = "force-dynamic"; function cleanText(value?: string | null) { if (!value) return ""; return value.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim(); } function personName(person: { first_name: string; last_name: string; username: string }) { return [person.first_name, person.last_name].filter(Boolean).join(" ") || person.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 Topics({ tags }: { tags: Types.PostListSchema["tags"] }) { if (!tags.length) { return null; } return (
{tags.map((tag) => ( {tag.name} ))}
); } function Breadcrumbs({ post }: { post: Types.PostDetailSchema }) { const crumbs = post.category_path || []; return ( ); } function WriterCards({ post }: { post: Types.PostDetailSchema }) { const writers = post.writers?.length ? post.writers : [post.author]; return (
{/*

نویسندگان

*/}

درباره نویسندگان این مقاله

{writers.map((writer: Writer) => { const image = writer.profile_picture_preview_url || writer.profile_picture_thumbnail_url || writer.profile_picture; return ( {personName(writer)[0] || "ن"}

{personName(writer)}

{writer.bio || "توضیحی برای این نویسنده ثبت نشده است."}

); })}
); } 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 writers = post.writers?.length ? post.writers : [post.author]; 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: writers.map((writer) => ({ "@type": "Person", name: personName(writer), })), publisher: { "@type": "Organization", name: "انجمن علمی مهندسی کامپیوتر شرق گیلان", logo: { "@type": "ImageObject", url: `${siteUrl}/favicon.ico`, }, }, keywords: post.tags.map((tag) => tag.name).join(", "), }; return (