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

@@ -0,0 +1,54 @@
"use client";
import { useEffect, useState } from "react";
import { Loader2 } from "lucide-react";
import { apiAdminListAnnouncements, type Announcement } from "@/lib/api";
import AnnouncementComposeClient from "../../AnnouncementComposeClient";
/** 无详情 API:从管理列表按 id 拉取后进入编辑器 */
export default function AnnouncementEditLoader({ id }: { id: number }) {
const [item, setItem] = useState<Announcement | null>(null);
const [error, setError] = useState("");
const [loading, setLoading] = useState(true);
useEffect(() => {
let alive = true;
setLoading(true);
apiAdminListAnnouncements()
.then((res) => {
if (!alive) return;
const found = res.announcements.find((a) => a.id === id) ?? null;
if (!found) {
setError("公告不存在或已删除");
setItem(null);
} else {
setItem(found);
setError("");
}
})
.catch((e) => {
if (!alive) return;
setError(e instanceof Error ? e.message : "加载失败");
})
.finally(() => {
if (alive) setLoading(false);
});
return () => {
alive = false;
};
}, [id]);
if (loading) {
return (
<div className="panel p-10 flex items-center justify-center gap-2 meta">
<Loader2 size={18} className="animate-spin" /> 加载公告…
</div>
);
}
if (error || !item) {
return <p className="alert-error">{error || "公告不存在"}</p>;
}
return <AnnouncementComposeClient mode="edit" initial={item} />;
}

View File

@@ -0,0 +1,31 @@
import type { Metadata } from "next";
import { cookies } from "next/headers";
import { authCookieHeader } from "@/lib/cookies";
import { getMeCached } from "@/lib/serverData";
import { isAdminOrAbove } from "@/lib/roles";
import Forbidden from "../../../Forbidden";
import AnnouncementEditLoader from "./AnnouncementEditLoader";
export const metadata: Metadata = {
title: "编辑公告",
};
interface PageProps {
params: Promise<{ id: string }>;
}
export default async function AdminAnnouncementEditPage({ params }: PageProps) {
const { id } = await params;
const cookie = authCookieHeader(await cookies());
const me = await getMeCached(cookie || undefined);
if (!me.user || !isAdminOrAbove(me.user.role)) {
return <Forbidden text="公告管理仅管理员及以上角色可操作。" />;
}
const numId = parseInt(id, 10);
if (!Number.isFinite(numId) || numId <= 0) {
return <Forbidden text="无效的公告 ID。" />;
}
return <AnnouncementEditLoader id={numId} />;
}