新增: - 书库导入导出(library_import/library_export)及测试 - 图片变体生成(image_variants)与响应式图片(responsiveImage) - 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件 - 小组件运行时(widgetRuntime)与静态检查(widgetLint) - 上传缓存中间件(upload_cache)与字体 CSS 提取脚本 其它: - 后端 handlers/services 全量调整 - 前端页面、组件、库函数与配置更新
107 lines
4.0 KiB
TypeScript
107 lines
4.0 KiB
TypeScript
import Link from "next/link";
|
|
import { Pin, Star } from "lucide-react";
|
|
import type { PostListItem } from "@/lib/api";
|
|
import { postPath } from "@/lib/urlStyle";
|
|
import { getPublicSettingsCached } from "@/lib/serverData";
|
|
import Avatar from "./Avatar";
|
|
import { IconComment, IconEye } from "./Icons";
|
|
|
|
function formatTime(dateStr: string) {
|
|
const d = new Date(dateStr);
|
|
return `${d.getFullYear()}.${String(d.getMonth() + 1).padStart(2, "0")}.${String(
|
|
d.getDate()
|
|
).padStart(2, "0")}`;
|
|
}
|
|
|
|
/**
|
|
* 管理员置顶区:独立于普通帖子列表。
|
|
* 陶土色语义(与 badge-pin 一致),紧凑排列,一眼识别为公告性质内容。
|
|
*/
|
|
export default async function PinnedPosts({
|
|
posts,
|
|
showBoard = true,
|
|
}: {
|
|
posts: PostListItem[];
|
|
showBoard?: boolean;
|
|
}) {
|
|
if (posts.length === 0) return null;
|
|
const urlStyle = (await getPublicSettingsCached()).url_style;
|
|
|
|
return (
|
|
<section className="mb-6" aria-label="置顶讨论">
|
|
<div className="flex items-center gap-2 mb-2.5 px-1">
|
|
<span
|
|
className="w-6 h-6 rounded-full flex items-center justify-center"
|
|
style={{ background: "var(--clay-soft)", color: "var(--clay)" }}
|
|
>
|
|
<Pin size={12} fill="currentColor" strokeWidth={2.4} />
|
|
</span>
|
|
<h2 className="text-[13.5px] font-extrabold tracking-wide" style={{ color: "var(--clay)" }}>
|
|
置顶讨论
|
|
</h2>
|
|
<span className="meta text-xs">管理员置顶 · {posts.length}</span>
|
|
</div>
|
|
|
|
<div className="panel overflow-hidden">
|
|
{posts.map((p, i) => (
|
|
<Link
|
|
key={p.id}
|
|
href={postPath(urlStyle, p.id)}
|
|
className="group relative flex items-center gap-3 sm:gap-3.5 px-4 sm:px-5 py-3.5 transition-colors hover:bg-[var(--item-hover-bg)] focus-visible:bg-[var(--item-hover-bg)] j13-fade-up"
|
|
style={{
|
|
...(i > 0 ? { borderTop: "1px solid var(--line)" } : {}),
|
|
animationDelay: `${i * 35}ms`,
|
|
}}
|
|
>
|
|
<Avatar name={p.user.nickname || p.user.username} src={p.user.avatar || undefined} size={36} square />
|
|
<div className="flex-1 min-w-0">
|
|
<h3
|
|
className="text-[14.5px] font-bold leading-snug truncate transition-colors group-hover:text-[var(--accent)]"
|
|
style={{ color: "var(--ink)" }}
|
|
>
|
|
{p.title}
|
|
</h3>
|
|
<div className="meta flex items-center gap-2 mt-1 text-xs min-w-0">
|
|
<span className="truncate">{p.user.nickname}</span>
|
|
<span aria-hidden>·</span>
|
|
<span className="shrink-0">{formatTime(p.created_at)}</span>
|
|
{showBoard && (
|
|
<>
|
|
<span aria-hidden>·</span>
|
|
<span className="truncate">{p.board.name}</span>
|
|
</>
|
|
)}
|
|
{p.recommended && (
|
|
<span
|
|
className="badge-rec shrink-0"
|
|
style={{ width: 16, height: 16 }}
|
|
title="推荐"
|
|
aria-label="推荐"
|
|
>
|
|
<Star size={10} fill="currentColor" strokeWidth={2.2} aria-hidden />
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="hidden sm:flex items-center gap-3 shrink-0 meta text-[12.5px]">
|
|
<span className="inline-flex items-center gap-1.5" aria-label={`${p.comment_count} 条评论`}>
|
|
<IconComment size={14} /> {p.comment_count}
|
|
</span>
|
|
<span className="inline-flex items-center gap-1.5" aria-label={`${p.view_count} 次浏览`}>
|
|
<IconEye size={14} /> {p.view_count}
|
|
</span>
|
|
</div>
|
|
<Pin
|
|
size={13}
|
|
className="shrink-0 hidden sm:block"
|
|
style={{ color: "var(--clay)" }}
|
|
fill="currentColor"
|
|
strokeWidth={2.2}
|
|
/>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|