feat: 站点单页、时间线导入与行级 BBCode 短代码

首页右栏接入单页入口与公告置顶/全部页;Markdown 编辑器支持可视化时间线与 Git 导入;隐藏块改为 [hide]/[timeline] 命名闭合,并修复单页/公告 GORM 更新只改 updated_at 的问题。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-17 19:52:06 +08:00
parent 2aacf99a49
commit a83276060f
52 changed files with 5073 additions and 648 deletions

View File

@@ -11,6 +11,8 @@ import {
PenLine,
Activity,
TrendingUp,
Info,
Pin,
} from "lucide-react";
import type {
PostListItem,
@@ -19,6 +21,7 @@ import type {
AnnouncementSummary,
NewUser,
CheckinStatus,
SidebarPageItem,
} from "@/lib/api";
import Avatar from "@/components/Avatar";
import { PulseCheckin } from "./CheckinCard";
@@ -117,14 +120,82 @@ function PulseCard({
);
}
/* ---------- 关于本站(侧栏单页) ---------- */
function AboutSite({ pages }: { pages: SidebarPageItem[] }) {
if (pages.length === 0) return null;
if (pages.length === 1) {
const p = pages[0];
return (
<section className="j13-module" aria-label="关于本站">
<p className="j13-module-title">
<Info size={15} style={{ color: "var(--accent)" }} /> 关于本站
</p>
<Link href={`/p/${p.slug}`} className="block mt-2 rounded-lg px-0.5 py-1 transition-colors hover:bg-[var(--accent-soft)]">
<p className="text-[14px] font-semibold leading-snug" style={{ color: "var(--ink)" }}>
{p.title}
</p>
{p.excerpt ? (
<p className="meta text-[12.5px] mt-1.5 leading-relaxed line-clamp-3">{p.excerpt}</p>
) : null}
</Link>
</section>
);
}
return (
<section className="j13-module" aria-label="关于本站">
<p className="j13-module-title mb-1.5">
<Info size={15} style={{ color: "var(--accent)" }} /> 关于本站
</p>
<ul className="space-y-0.5">
{pages.map((p) => (
<li key={p.id}>
<Link href={`/p/${p.slug}`} className="j13-raillink" data-tip={p.title}>
<span className="flex-1 min-w-0 truncate text-[13px] font-medium" style={{ color: "var(--ink)" }}>
{p.title}
</span>
</Link>
</li>
))}
</ul>
</section>
);
}
/* ---------- 站点公告 ---------- */
function Announcements({ items }: { items: AnnouncementSummary[] }) {
function Announcements({
items,
canManageAnnouncements,
}: {
items: AnnouncementSummary[];
canManageAnnouncements?: boolean;
}) {
return (
<section className="j13-module" aria-label="站点公告">
<p className="j13-module-title">
<Megaphone size={15} style={{ color: "var(--accent)" }} /> 站点公告
</p>
<div className="flex items-center justify-between gap-2">
<p className="j13-module-title !mb-0">
<Megaphone size={15} style={{ color: "var(--accent)" }} /> 站点公告
</p>
<span className="inline-flex items-center gap-2 shrink-0">
<Link
href="/announcements"
className="meta text-[12px] font-normal transition-colors hover:text-[var(--accent)]"
>
全部
</Link>
{canManageAnnouncements ? (
<Link
href="/admin/announcements"
className="meta text-[12px] font-normal transition-colors hover:text-[var(--accent)]"
>
管理
</Link>
) : null}
</span>
</div>
{items.length === 0 ? (
<p className="meta mt-2 px-0.5">暂无公告</p>
) : (
@@ -137,7 +208,17 @@ function Announcements({ items }: { items: AnnouncementSummary[] }) {
data-tip={a.title}
>
<span className="j13-anno-item-body">
<span className="j13-anno-item-title truncate">{a.title}</span>
<span className="j13-anno-item-title truncate inline-flex items-center gap-1 min-w-0">
{a.pinned ? (
<Pin
size={11}
className="shrink-0"
style={{ color: "var(--accent)" }}
aria-label="置顶"
/>
) : null}
<span className="truncate">{a.title}</span>
</span>
<span className="j13-anno-item-meta">
{a.tag ? (
<span className={`j13-anno-tag is-${a.tag_color}`}>{a.tag}</span>
@@ -338,6 +419,8 @@ type PanelsProps = {
newUsers: NewUser[];
checkin?: CheckinStatus | null;
allowComments: boolean;
canManageAnnouncements?: boolean;
sidebarPages?: SidebarPageItem[];
};
function CommunityPanelsBody({
@@ -348,11 +431,14 @@ function CommunityPanelsBody({
newUsers,
checkin,
allowComments,
canManageAnnouncements,
sidebarPages = [],
}: PanelsProps) {
return (
<>
<PulseCard stats={stats} checkin={checkin} allowComments={allowComments} />
<Announcements items={announcements} />
<AboutSite pages={sidebarPages} />
<Announcements items={announcements} canManageAnnouncements={canManageAnnouncements} />
<HotRanking posts={hot} allowComments={allowComments} />
<NewUsers users={newUsers} />
<ActiveUsers users={activeUsers} allowComments={allowComments} />