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 (