125 lines
4.8 KiB
TypeScript
125 lines
4.8 KiB
TypeScript
import Link from "next/link";
|
||
import { CodeXml, Lock, Copy, Eye, MessageSquare, GitFork } from "lucide-react";
|
||
import { fetchSnippetById } from "@/lib/api";
|
||
import { postViewerCookieHeader } from "@/lib/cookies";
|
||
import { getPublicSettingsCached } from "@/lib/serverData";
|
||
import { cookies } from "next/headers";
|
||
import { snippetPath } from "@/lib/urlStyle";
|
||
import Avatar from "@/components/Avatar";
|
||
|
||
/**
|
||
* [snippet id=N] 嵌入卡片(SSR)。
|
||
* 私有/已删除/不存在时渲染降级占位,不暴露内容。
|
||
*/
|
||
export default async function MdSnippet({ id }: { id: number }) {
|
||
const cookieHeader = postViewerCookieHeader(await cookies());
|
||
const [data, settings] = await Promise.all([
|
||
fetchSnippetById(id, cookieHeader).catch(() => null),
|
||
getPublicSettingsCached(),
|
||
]);
|
||
const urlStyle = settings.url_style;
|
||
|
||
// 降级占位:不存在 / 私有且无权限 / 已删除
|
||
if (!data) {
|
||
return (
|
||
<div
|
||
className="my-4 rounded-xl px-4 py-4 flex items-center gap-3"
|
||
style={{ background: "var(--panel-2)", border: "1px solid var(--line)" }}
|
||
>
|
||
<span
|
||
className="w-9 h-9 flex items-center justify-center rounded-lg shrink-0"
|
||
style={{ background: "var(--panel)", color: "var(--ink-3)" }}
|
||
>
|
||
<Lock size={16} />
|
||
</span>
|
||
<div className="min-w-0">
|
||
<p className="text-[13px] font-medium" style={{ color: "var(--ink-2)" }}>
|
||
代码片段 #{id}
|
||
</p>
|
||
<p className="meta text-[11.5px]">片段不存在或无权查看</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const s = data.snippet;
|
||
const ownerName = s.user ? s.user.nickname || s.user.username : "";
|
||
const previewLines = s.code.split("\n").slice(0, 5);
|
||
const href = snippetPath(urlStyle, s.id);
|
||
|
||
return (
|
||
<Link href={href} className="group block my-4">
|
||
<div
|
||
className="rounded-xl overflow-hidden transition-[box-shadow,transform] duration-200 group-hover:-translate-y-0.5 group-hover:shadow-[var(--shadow-md)]"
|
||
style={{ border: "1px solid var(--line)", background: "var(--panel)" }}
|
||
>
|
||
{/* 头部:语言 + 标题 + 作者 */}
|
||
<div className="flex items-center gap-2.5 px-4 py-3" style={{ borderBottom: "1px solid var(--line)" }}>
|
||
<span
|
||
className="w-8 h-8 flex items-center justify-center rounded-lg shrink-0"
|
||
style={{ background: "var(--accent-soft)", color: "var(--accent)" }}
|
||
>
|
||
<CodeXml size={15} />
|
||
</span>
|
||
<div className="min-w-0 flex-1">
|
||
<div className="flex items-center gap-2">
|
||
<span
|
||
className="text-[10px] font-semibold px-1.5 py-0.5 rounded uppercase tracking-wide shrink-0"
|
||
style={{
|
||
color: "var(--accent)",
|
||
background: "var(--accent-soft)",
|
||
border: "1px solid color-mix(in srgb, var(--accent) 30%, transparent)",
|
||
}}
|
||
>
|
||
{s.language}
|
||
</span>
|
||
<p className="text-[14px] font-semibold truncate" style={{ color: "var(--ink)" }}>
|
||
{s.title}
|
||
</p>
|
||
</div>
|
||
{s.user && (
|
||
<span className="flex items-center gap-1.5 mt-0.5">
|
||
<Avatar size={14} name={ownerName} src={s.user.avatar} />
|
||
<span className="meta text-[11px] truncate">{ownerName}</span>
|
||
</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 代码预览 */}
|
||
<div className="px-4 py-3" style={{ background: "var(--panel-2)" }}>
|
||
<pre
|
||
className="text-[12px] leading-[1.6] overflow-hidden"
|
||
style={{ color: "var(--ink-2)", fontFamily: "var(--font-mono, ui-monospace, monospace)" }}
|
||
>
|
||
{previewLines.map((line, i) => (
|
||
<div key={i} className="flex gap-2 min-w-0">
|
||
<span className="shrink-0 w-4 text-right select-none" style={{ color: "var(--ink-3)", opacity: 0.5 }}>
|
||
{i + 1}
|
||
</span>
|
||
<span className="truncate">{line || " "}</span>
|
||
</div>
|
||
))}
|
||
</pre>
|
||
</div>
|
||
|
||
{/* 底部统计 */}
|
||
<div className="flex items-center gap-3 px-4 py-2.5 meta text-[11px]">
|
||
<span className="inline-flex items-center gap-1">
|
||
<Copy size={11} /> {s.copy_count}
|
||
</span>
|
||
<span className="inline-flex items-center gap-1">
|
||
<Eye size={11} /> {s.view_count}
|
||
</span>
|
||
<span className="inline-flex items-center gap-1">
|
||
<MessageSquare size={11} /> {s.comment_count}
|
||
</span>
|
||
<span className="inline-flex items-center gap-1">
|
||
<GitFork size={11} /> {s.fork_count}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</Link>
|
||
);
|
||
}
|