Files
jiang13-bbs/frontend/components/MarkdownBodyClient.tsx
freefire 4f3f3a265c feat: 友链/书库/移动端导航/排行榜等多模块功能
新增模块:
- 友链(friendlink)后端处理 + 前端管理页与友链板
- 书库(library):导入、章节、封面、阅读页、横竖版自适应 AdaptiveCoverSlot
- 顶栏导航(header_nav)配置与 MobileTabBar/MobileRailDrawers 移动端抽屉
- 排行榜 service 测试、站点页面测试、时间线发布(timeline_release)

其它改动:
- 后端 handlers/services 全量小幅调整
- 前端组件、库函数、URL/品牌/站点 URL 工具更新
- Lightbox 图片、MdEntries 条目卡、coverColor 派生色相等前端能力
2026-09-30 16:30:07 +08:00

113 lines
3.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import ReactMarkdown, { type Components } from "react-markdown";
import remarkGfm from "remark-gfm";
import { mergeMarkdownComponents } from "@/components/markdown/mergeComponents";
import { splitContentSegments } from "@/lib/directiveBlocks";
import { expandDyEmojiShortcodes } from "@/lib/dyEmoji";
import type { HideKind } from "@/lib/hideBlocks";
import MdHideBlock from "@/components/markdown/MdHideBlock";
import MdTimeline from "@/components/markdown/MdTimeline";
import { MdEntriesView } from "@/components/markdown/MdEntries";
type MarkdownBodyClientProps = {
content: string;
className?: string;
components?: Components;
postId?: number;
loginHref?: string;
loggedIn?: boolean;
/** 书级「全章节条目卡模式」:章节内容免写 [entries] 壳 */
entriesAuto?: boolean;
/** 按分段索引的条目标题锚点 id(与章内目录共享;阅读页传入,其他场景缺省) */
entriesTitleIds?: (string[] | undefined)[];
};
/**
* 客户端 Markdown(发帖预览 / 评论):GFM + 隐藏块 / 时间线 / 条目卡预览。
*/
export default function MarkdownBodyClient({
content,
className = "prose-content",
components,
postId,
loginHref,
loggedIn = false,
entriesAuto = false,
entriesTitleIds,
}: MarkdownBodyClientProps) {
const segs = splitContentSegments(expandDyEmojiShortcodes(content || ""), {
entriesAuto,
});
const mdComponents = mergeMarkdownComponents(components);
// 时间线/隐藏块正文不套外层 heading 覆盖,避免预览与正文目录 id 错位
const nestedMd = mergeMarkdownComponents();
return (
<div className={className}>
{segs.map((seg, i) => {
if (seg.type === "md") {
if (!seg.text.trim()) return null;
return (
<ReactMarkdown key={`md-${i}`} remarkPlugins={[remarkGfm]} components={mdComponents}>
{seg.text}
</ReactMarkdown>
);
}
if (seg.type === "timeline") {
return (
<MdTimeline
key={`tl-${i}`}
items={seg.items}
renderBody={(it) =>
it.body.trim() ? (
<ReactMarkdown remarkPlugins={[remarkGfm]} components={nestedMd}>
{expandDyEmojiShortcodes(it.body)}
</ReactMarkdown>
) : null
}
/>
);
}
if (seg.type === "entries") {
return (
<MdEntriesView
key={`entries-${i}`}
items={seg.items}
titleIds={entriesTitleIds?.[i]}
renderMd={(t) => (
<ReactMarkdown remarkPlugins={[remarkGfm]} components={nestedMd}>
{expandDyEmojiShortcodes(t)}
</ReactMarkdown>
)}
/>
);
}
const kind = seg.kind as HideKind;
if (seg.locked) {
return (
<MdHideBlock
key={`hide-${i}`}
kind={kind}
points={seg.points}
locked
postId={postId}
loginHref={loginHref}
loggedIn={loggedIn}
/>
);
}
return (
<MdHideBlock key={`hide-${i}`} kind={kind} points={seg.points}>
{seg.body.trim() ? (
<ReactMarkdown remarkPlugins={[remarkGfm]} components={nestedMd}>
{expandDyEmojiShortcodes(seg.body)}
</ReactMarkdown>
) : null}
</MdHideBlock>
);
})}
</div>
);
}