Skip to content

Commit f529eeb

Browse files
committed
feat(docs): CommunityShare 索引改为从 fumadocs source 自动生成
现状:/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 的痛点诊断)。
1 parent 4c650d8 commit f529eeb

2 files changed

Lines changed: 137 additions & 24 deletions

File tree

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { source } from "@/lib/source";
2+
import Link from "next/link";
3+
4+
/**
5+
* CommunityShare 自动目录索引(Server Component,渲染在 /docs/CommunityShare/index.mdx 内)。
6+
*
7+
* 为什么需要这个组件:
8+
* 原先 index.mdx 里的分类列表是人工维护的 Markdown,每次新增文章 / 新增分类都要顺手改索引,
9+
* 实际上经常漏(main 上 index.mdx 少列了 Amazing-AI-Tools / Language / Leetcode / Life /
10+
* Personal-Study-Notes 五个分类,还留着"身体健康"这种完全空的占位分类)。
11+
* 改成 server 组件从 fumadocs `source.getPages()` 实时读目录树 → 文档增删后索引自动同步,
12+
* 不再需要 #110 那种靠脚本定期重新生成 MDX 的方案(也就不需要引入 glob / gray-matter 依赖)。
13+
*
14+
* 设计:
15+
* - 顶级分类 = CommunityShare 下的直属一级目录
16+
* - 每个分类的标题:优先读该分类 index.mdx 的 frontmatter.title;没 index 就用目录名兜底
17+
* - 分类内的文章按 title 字母排序,排除分类自己的 index.mdx(避免"点击进入自己"的死循环)
18+
* - 翻译版(lang === "en" 或文件名以 .en 结尾)不出现在列表,统一走原文 URL,locale 由 cookie 切
19+
* - 分类条目超过 INLINE_LIMIT (12) 时折叠显示:"共 N 篇 → 进入分类" 单行链接,
20+
* 避免 Leetcode 这种几十上百篇的分类把页面顶爆
21+
* - 完全不指向 "/docs/CommunityShare/<dir>" 硬拼 URL,全部走 page.url(fumadocs 已做 slug 规范化
22+
* 和拼音转换,硬拼会漏掉 lib/source.ts 里 Leetcode 目录的 pinyin slug transform)
23+
*/
24+
25+
type PageLike = ReturnType<typeof source.getPages>[number];
26+
27+
const ROOT = "CommunityShare";
28+
const INLINE_LIMIT = 12;
29+
30+
/** 判定一个页面是不是英文翻译版(不应出现在索引里) */
31+
function isEnglishVariant(page: PageLike): boolean {
32+
const data = page.data as { lang?: string };
33+
if (data.lang === "en") return true;
34+
// 兜底:历史上有未加 lang frontmatter 的 .en.mdx 文件,靠文件名识别
35+
return page.file.name.endsWith(".en");
36+
}
37+
38+
/** 取页面 file.path 相对 ROOT 的第一级目录名,如 "CommunityShare/Geek/foo" → "Geek" */
39+
function firstSegmentUnderRoot(filePath: string): string | null {
40+
const prefix = `${ROOT}/`;
41+
if (!filePath.startsWith(prefix)) return null;
42+
const rest = filePath.slice(prefix.length);
43+
const slashIdx = rest.indexOf("/");
44+
return slashIdx === -1 ? null : rest.slice(0, slashIdx);
45+
}
46+
47+
export function CommunityShareIndex() {
48+
const all = source.getPages();
49+
50+
// 第一步:筛出 CommunityShare 下的全部非英文版页面
51+
const pages = all.filter(
52+
(p) => p.file.path.startsWith(`${ROOT}/`) && !isEnglishVariant(p),
53+
);
54+
55+
// 第二步:按第一级子目录分组(根目录的 index.mdx 本身 category=null,跳过)
56+
const byCategory = new Map<string, PageLike[]>();
57+
for (const page of pages) {
58+
const category = firstSegmentUnderRoot(page.file.path);
59+
if (!category) continue;
60+
const bucket = byCategory.get(category) ?? [];
61+
bucket.push(page);
62+
byCategory.set(category, bucket);
63+
}
64+
65+
// 第三步:构造渲染所需的 view-model,并按分类名排序
66+
const categories = [...byCategory.entries()]
67+
.map(([dirName, catPages]) => {
68+
// 分类自己的 index.mdx(若存在)
69+
const categoryIndex = catPages.find(
70+
(p) =>
71+
p.file.dirname === `${ROOT}/${dirName}` && p.file.name === "index",
72+
);
73+
const displayTitle = categoryIndex?.data.title ?? dirName;
74+
const categoryUrl = categoryIndex?.url ?? `/docs/${ROOT}/${dirName}`;
75+
76+
// 内容条目 = 排除分类 index 本身
77+
const entries = catPages
78+
.filter((p) => p !== categoryIndex)
79+
.sort((a, b) => a.data.title.localeCompare(b.data.title, "zh-Hans-CN"));
80+
81+
return {
82+
dirName,
83+
displayTitle,
84+
categoryUrl,
85+
entries,
86+
};
87+
})
88+
.sort((a, b) => a.displayTitle.localeCompare(b.displayTitle, "zh-Hans-CN"));
89+
90+
if (categories.length === 0) {
91+
// 兜底:理论上不会走到,但避免开发期目录清空时整个页面报错
92+
return (
93+
<p className="text-sm text-neutral-500">暂无分享内容,期待你的投稿!</p>
94+
);
95+
}
96+
97+
return (
98+
<div className="flex flex-col gap-8">
99+
{categories.map((cat) => (
100+
<section key={cat.dirName}>
101+
<h2 className="text-xl font-bold mb-3">
102+
<Link href={cat.categoryUrl} className="hover:underline">
103+
{cat.displayTitle}
104+
</Link>
105+
<span className="ml-2 text-xs font-normal text-neutral-500">
106+
({cat.entries.length} 篇)
107+
</span>
108+
</h2>
109+
{cat.entries.length > INLINE_LIMIT ? (
110+
// 超过阈值:折叠显示,避免 Leetcode 这种分类把页面顶爆
111+
<p className="text-sm text-neutral-600 dark:text-neutral-400">
112+
<Link
113+
href={cat.categoryUrl}
114+
className="text-[var(--color-fd-primary)] hover:underline"
115+
>
116+
查看全部 {cat.entries.length} 篇 →
117+
</Link>
118+
</p>
119+
) : (
120+
<ul className="list-disc pl-6 space-y-1">
121+
{cat.entries.map((p) => (
122+
<li key={p.url}>
123+
<Link href={p.url} className="hover:underline">
124+
{p.data.title}
125+
</Link>
126+
</li>
127+
))}
128+
</ul>
129+
)}
130+
</section>
131+
))}
132+
</div>
133+
);
134+
}

app/docs/CommunityShare/index.mdx

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,12 @@ date: "2025-09-18"
44
docId: sfzt30mtx0jsuv6esnpm3w8y
55
---
66

7+
import { CommunityShareIndex } from "@/app/components/CommunityShareIndex";
8+
79
欢迎来到群友分享板块!无论你是技术极客,还是热爱生活,都欢迎积极投稿!
810

911
一篇微不足道的文章或许可以帮助一个迷茫的陌生人~
1012

1113
> 转载文章请先联系原作者获取授权,谢谢!
1214
13-
## 技术分享
14-
15-
- [常用Markdown语法](/docs/CommunityShare/Geek/CommonUsedMarkdown)
16-
17-
- [Git入门操作指南-程序员必会的git小技巧](/docs/CommunityShare/Geek/git101)
18-
19-
- [用闲置树莓派搭建一个Minecraft服务器](/docs/CommunityShare/Geek/raspberry-guide)
20-
21-
- [常用Katex语法](/docs/CommunityShare/Geek/Katex/index)
22-
23-
## 心理健康
24-
25-
- [程序员 Burnout 自救指南](/docs/CommunityShare/MentalHealth/burnout-guide) - 识别和应对职业倦怠
26-
27-
## RAG
28-
29-
- [RAG toy demo](/docs/CommunityShare/RAG/rag)
30-
31-
## 身体健康
32-
33-
- 久坐办公的解决方案
34-
- 程序员健身指南
35-
- 饮食与营养建议
36-
- 睡眠质量改善
15+
<CommunityShareIndex />

0 commit comments

Comments
 (0)