import type { Metadata } from "next"; import { notFound } from "next/navigation"; import Link from "next/link"; import { cookies } from "next/headers"; import { GitFork, Copy, Eye, MessageSquare } from "lucide-react"; import { fetchSnippetById, fetchSnippetComments, fetchSnippetForks, } from "@/lib/api"; import { getPublicSettingsCached } from "@/lib/serverData"; import { requestCanonical } from "@/lib/canonicalMeta"; import { snippetPath, userPath, snippetsPath } from "@/lib/urlStyle"; import { postViewerCookieHeader } from "@/lib/cookies"; import { formatRelative } from "@/lib/format"; import MarkdownBody from "@/components/MarkdownBody"; import Avatar from "@/components/Avatar"; import SnippetCodeView from "@/components/SnippetCodeView"; import SnippetCommentSection from "@/components/SnippetCommentSection"; import SnippetCommentCountChip from "@/components/SnippetCommentCountChip"; import SnippetActions from "@/components/SnippetActions"; interface PageProps { params: Promise<{ id: string }>; } export async function generateMetadata({ params }: PageProps): Promise { const { id } = await params; const numId = Number(id); if (!Number.isInteger(numId) || numId <= 0) return { title: "片段不存在" }; const cookieHeader = postViewerCookieHeader(await cookies()); const data = await fetchSnippetById(numId, cookieHeader).catch(() => null); if (!data) return { title: "片段不存在" }; const s = data.snippet; return { title: s.title, description: s.description ? s.description.slice(0, 150) : `${s.language} 代码片段`, ...(await requestCanonical(snippetPath((await getPublicSettingsCached()).url_style, s.id))), }; } export default async function SnippetDetailPage({ params }: PageProps) { const { id } = await params; const numId = Number(id); if (!Number.isInteger(numId) || numId <= 0) notFound(); const cookieHeader = postViewerCookieHeader(await cookies()); const [detailRes, commentsRes, forksRes, settings] = await Promise.all([ fetchSnippetById(numId, cookieHeader).catch(() => null), fetchSnippetComments(numId, 1, 20, cookieHeader).catch(() => ({ items: [], total: 0 })), fetchSnippetForks(numId, 1, 6).catch(() => ({ items: [], total: 0 })), getPublicSettingsCached(), ]); if (!detailRes) notFound(); const snippet = detailRes.snippet; const urlStyle = settings.url_style; const isOwner = false; // 客户端组件中通过 apiMe 校正 const isStaff = false; return (
← 片段广场 {snippet.forked_from && ( <> · Fork 自:{snippet.forked_from.title} )}
{/* 头部 */}
{snippet.language} {snippet.visibility === "private" && ( 私有 )}

{snippet.title}

{/* 作者 + 统计 */}
{snippet.user && ( {snippet.user.nickname || snippet.user.username} @{snippet.user.username} )} {formatRelative(snippet.created_at)} {snippet.updated_at !== snippet.created_at && ( (编辑于 {formatRelative(snippet.updated_at)}) )} } value={snippet.copy_count} label="复制" /> } value={snippet.view_count} label="查看" /> } label="评论" /> 0 ? "#snippet-forks" : undefined} className="inline-flex rounded-full" style={{ cursor: snippet.fork_count > 0 ? "pointer" : "default" }} title={snippet.fork_count > 0 ? "查看 Fork 演化" : "还没有人 Fork"} > } value={snippet.fork_count} label="Fork" />
{/* 操作 */}
{/* 代码窗格 */}
{snippet.description && (

说明

)} {forksRes.total > 0 && (

Fork({forksRes.total})

{forksRes.items.slice(0, 5).map((f) => ( {f.title} ))}
)} {/* 评论 */}
); } function StatChip({ icon, value, label }: { icon: React.ReactNode; value: number; label: string }) { return ( {icon} {value} {label} ); }