feat: 公文三栏单页布局,并修复时间线目录跳转与样式
公告/单页对齐帖子详情三栏壳;时间线按日锚点可跳转,条目改为日志行并用暖金日期区分 feat 色。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -29,7 +29,7 @@ import { getMeCached, getPublicSettingsCached } from "@/lib/serverData";
|
||||
import CommentSection from "@/components/CommentSection";
|
||||
import PostActions from "@/components/PostActions";
|
||||
import LikeButton from "@/components/LikeButton";
|
||||
import PostToc, { type TocItem } from "@/components/PostToc";
|
||||
import PostToc from "@/components/PostToc";
|
||||
import MobilePostBar from "@/components/MobilePostBar";
|
||||
import Avatar from "@/components/Avatar";
|
||||
import RoleBadge from "@/components/RoleBadge";
|
||||
@@ -41,6 +41,7 @@ import AcceptedAnswerCard from "@/components/AcceptedAnswerCard";
|
||||
import { canModerateBoard } from "@/lib/roles";
|
||||
import { IconComment, IconEye } from "@/components/Icons";
|
||||
import { normalizePostType, postTypeLabel, contentAccessLabel, typeStatusLabel, typeStatusClass } from "@/lib/postMeta";
|
||||
import { extractToc, estimateReadingMinutes, markdownHeadingToc } from "@/lib/toc";
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{ id: string }>;
|
||||
@@ -78,26 +79,6 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Markdown 提取二三级标题生成目录项,id 按出现顺序编号;
|
||||
* 代码围栏内的 # 不计入
|
||||
*/
|
||||
function extractToc(content: string): TocItem[] {
|
||||
const items: TocItem[] = [];
|
||||
let inCode = false;
|
||||
for (const raw of content.split(/\r?\n/)) {
|
||||
const line = raw.trim();
|
||||
if (line.startsWith("```")) {
|
||||
inCode = !inCode;
|
||||
continue;
|
||||
}
|
||||
if (inCode) continue;
|
||||
const m = /^(#{2,3})\s+(.+?)\s*#*\s*$/.exec(line);
|
||||
if (m) items.push({ level: m[1].length as 2 | 3, text: m[2], id: `h-${items.length}` });
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
function formatYMD(dateStr: string) {
|
||||
const d = new Date(dateStr);
|
||||
return `${d.getFullYear()}.${String(d.getMonth() + 1).padStart(2, "0")}.${String(
|
||||
@@ -484,10 +465,8 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
|
||||
const publicBody = publicMarkdownForSeo(post.content || "");
|
||||
const toc = extractToc(publicBody);
|
||||
const readingMinutes = Math.max(
|
||||
1,
|
||||
Math.ceil((publicBody.length > 0 ? publicBody.length : post.title.length) / 400)
|
||||
);
|
||||
const headingToc = markdownHeadingToc(toc);
|
||||
const readingMinutes = estimateReadingMinutes(publicBody, post.title.length);
|
||||
const edited =
|
||||
new Date(post.updated_at).getTime() - new Date(post.created_at).getTime() > 60_000;
|
||||
|
||||
@@ -559,11 +538,11 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
}),
|
||||
};
|
||||
|
||||
// 目录项与 Markdown 标题按渲染顺序一一对应(extractToc 顺序 == 文档顺序)
|
||||
// 仅正文 Markdown 标题与 headingRenderer 按序对应(时间线日锚点另有 tl-* id)
|
||||
let headingIndex = 0;
|
||||
const headingRenderer = (Tag: "h2" | "h3") =>
|
||||
function Heading({ children }: { children?: React.ReactNode }) {
|
||||
const item = toc[headingIndex++];
|
||||
const item = headingToc[headingIndex++];
|
||||
return (
|
||||
<Tag id={item?.id} style={{ scrollMarginTop: 90 }}>
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user