From 010a596599822cbc4bfc594e8f4197e414428f8c Mon Sep 17 00:00:00 2001 From: longsizhuo Date: Thu, 16 Apr 2026 19:00:11 +0000 Subject: [PATCH] =?UTF-8?q?feat(docs):=20CommunityShare=20=E7=B4=A2?= =?UTF-8?q?=E5=BC=95=E6=94=B9=E4=B8=BA=E4=BB=8E=20fumadocs=20source=20?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现状:/docs/CommunityShare/index.mdx 里的分类列表长期人工维护,已经过期: - 缺列了 Amazing-AI-Tools / Language / Leetcode / Life / Personal-Study-Notes 五个分类 - 还留着"身体健康"这种没对应目录、只有标题没有文章的占位分类 - 新增文章后还得记得回来改 index,实际做不到 改法:新增 server component `CommunityShareIndex`,直接读 fumadocs `source.getPages()` 按第一级子目录分组渲染,分类标题优先读子目录 index.mdx 的 frontmatter.title, 没 index 时降级用目录名。翻译版(lang === "en" / 文件名 .en)统一不进列表。 条目数超过 12 的分类折叠成"查看全部 N 篇 →"单行链接,避免 Leetcode 顶爆页面。 替代 #110:#110 是脚本生成静态 MDX 的路线,需要引入 glob / gray-matter 两个新依赖 + 一个 CI 步骤 + 一套目录名硬编码映射表。用 fumadocs source 之后这些 全部不需要——fumadocs 已经在跑目录扫描和 slug 规范化(包括 lib/source.ts 里 Leetcode 的拼音 slug transform,硬拼 URL 会漏掉),直接复用更干净。 感谢 @LynPtl 最早指出了手维护索引会过期的问题(#110 的痛点诊断)。 Co-authored-by: LynPtl <194795025+LynPtl@users.noreply.github.com> --- app/components/CommunityShareIndex.tsx | 134 +++++++++++++++++++++++++ app/docs/CommunityShare/index.mdx | 27 +---- 2 files changed, 137 insertions(+), 24 deletions(-) create mode 100644 app/components/CommunityShareIndex.tsx diff --git a/app/components/CommunityShareIndex.tsx b/app/components/CommunityShareIndex.tsx new file mode 100644 index 00000000..f448ea07 --- /dev/null +++ b/app/components/CommunityShareIndex.tsx @@ -0,0 +1,134 @@ +import { source } from "@/lib/source"; +import Link from "next/link"; + +/** + * CommunityShare 自动目录索引(Server Component,渲染在 /docs/CommunityShare/index.mdx 内)。 + * + * 为什么需要这个组件: + * 原先 index.mdx 里的分类列表是人工维护的 Markdown,每次新增文章 / 新增分类都要顺手改索引, + * 实际上经常漏(main 上 index.mdx 少列了 Amazing-AI-Tools / Language / Leetcode / Life / + * Personal-Study-Notes 五个分类,还留着"身体健康"这种完全空的占位分类)。 + * 改成 server 组件从 fumadocs `source.getPages()` 实时读目录树 → 文档增删后索引自动同步, + * 不再需要 #110 那种靠脚本定期重新生成 MDX 的方案(也就不需要引入 glob / gray-matter 依赖)。 + * + * 设计: + * - 顶级分类 = CommunityShare 下的直属一级目录 + * - 每个分类的标题:优先读该分类 index.mdx 的 frontmatter.title;没 index 就用目录名兜底 + * - 分类内的文章按 title 字母排序,排除分类自己的 index.mdx(避免"点击进入自己"的死循环) + * - 翻译版(lang === "en" 或文件名以 .en 结尾)不出现在列表,统一走原文 URL,locale 由 cookie 切 + * - 分类条目超过 INLINE_LIMIT (12) 时折叠显示:"共 N 篇 → 进入分类" 单行链接, + * 避免 Leetcode 这种几十上百篇的分类把页面顶爆 + * - 完全不指向 "/docs/CommunityShare/" 硬拼 URL,全部走 page.url(fumadocs 已做 slug 规范化 + * 和拼音转换,硬拼会漏掉 lib/source.ts 里 Leetcode 目录的 pinyin slug transform) + */ + +type PageLike = ReturnType[number]; + +const ROOT = "CommunityShare"; +const INLINE_LIMIT = 12; + +/** 判定一个页面是不是英文翻译版(不应出现在索引里) */ +function isEnglishVariant(page: PageLike): boolean { + const data = page.data as { lang?: string }; + if (data.lang === "en") return true; + // 兜底:历史上有未加 lang frontmatter 的 .en.mdx 文件,靠文件名识别 + return page.file.name.endsWith(".en"); +} + +/** 取页面 file.path 相对 ROOT 的第一级目录名,如 "CommunityShare/Geek/foo" → "Geek" */ +function firstSegmentUnderRoot(filePath: string): string | null { + const prefix = `${ROOT}/`; + if (!filePath.startsWith(prefix)) return null; + const rest = filePath.slice(prefix.length); + const slashIdx = rest.indexOf("/"); + return slashIdx === -1 ? null : rest.slice(0, slashIdx); +} + +export function CommunityShareIndex() { + const all = source.getPages(); + + // 第一步:筛出 CommunityShare 下的全部非英文版页面 + const pages = all.filter( + (p) => p.file.path.startsWith(`${ROOT}/`) && !isEnglishVariant(p), + ); + + // 第二步:按第一级子目录分组(根目录的 index.mdx 本身 category=null,跳过) + const byCategory = new Map(); + for (const page of pages) { + const category = firstSegmentUnderRoot(page.file.path); + if (!category) continue; + const bucket = byCategory.get(category) ?? []; + bucket.push(page); + byCategory.set(category, bucket); + } + + // 第三步:构造渲染所需的 view-model,并按分类名排序 + const categories = [...byCategory.entries()] + .map(([dirName, catPages]) => { + // 分类自己的 index.mdx(若存在) + const categoryIndex = catPages.find( + (p) => + p.file.dirname === `${ROOT}/${dirName}` && p.file.name === "index", + ); + const displayTitle = categoryIndex?.data.title ?? dirName; + const categoryUrl = categoryIndex?.url ?? `/docs/${ROOT}/${dirName}`; + + // 内容条目 = 排除分类 index 本身 + const entries = catPages + .filter((p) => p !== categoryIndex) + .sort((a, b) => a.data.title.localeCompare(b.data.title, "zh-Hans-CN")); + + return { + dirName, + displayTitle, + categoryUrl, + entries, + }; + }) + .sort((a, b) => a.displayTitle.localeCompare(b.displayTitle, "zh-Hans-CN")); + + if (categories.length === 0) { + // 兜底:理论上不会走到,但避免开发期目录清空时整个页面报错 + return ( +

暂无分享内容,期待你的投稿!

+ ); + } + + return ( +
+ {categories.map((cat) => ( +
+

+ + {cat.displayTitle} + + + ({cat.entries.length} 篇) + +

+ {cat.entries.length > INLINE_LIMIT ? ( + // 超过阈值:折叠显示,避免 Leetcode 这种分类把页面顶爆 +

+ + 查看全部 {cat.entries.length} 篇 → + +

+ ) : ( +
    + {cat.entries.map((p) => ( +
  • + + {p.data.title} + +
  • + ))} +
+ )} +
+ ))} +
+ ); +} diff --git a/app/docs/CommunityShare/index.mdx b/app/docs/CommunityShare/index.mdx index 19ade43f..e29fcb27 100644 --- a/app/docs/CommunityShare/index.mdx +++ b/app/docs/CommunityShare/index.mdx @@ -4,33 +4,12 @@ date: "2025-09-18" docId: sfzt30mtx0jsuv6esnpm3w8y --- +import { CommunityShareIndex } from "@/app/components/CommunityShareIndex"; + 欢迎来到群友分享板块!无论你是技术极客,还是热爱生活,都欢迎积极投稿! 一篇微不足道的文章或许可以帮助一个迷茫的陌生人~ > 转载文章请先联系原作者获取授权,谢谢! -## 技术分享 - -- [常用Markdown语法](/docs/CommunityShare/Geek/CommonUsedMarkdown) - -- [Git入门操作指南-程序员必会的git小技巧](/docs/CommunityShare/Geek/git101) - -- [用闲置树莓派搭建一个Minecraft服务器](/docs/CommunityShare/Geek/raspberry-guide) - -- [常用Katex语法](/docs/CommunityShare/Geek/Katex/index) - -## 心理健康 - -- [程序员 Burnout 自救指南](/docs/CommunityShare/MentalHealth/burnout-guide) - 识别和应对职业倦怠 - -## RAG - -- [RAG toy demo](/docs/CommunityShare/RAG/rag) - -## 身体健康 - -- 久坐办公的解决方案 -- 程序员健身指南 -- 饮食与营养建议 -- 睡眠质量改善 +