Skip to content

Commit f4f11e7

Browse files
authored
Merge pull request #183 from InvolutionHell/feat/longsizhuo/giscus2docId
fix: 放弃拼接,只采用docId作为唯一键
2 parents 20f57f4 + 0541b8c commit f4f11e7

File tree

3 files changed

+159
-53
lines changed

3 files changed

+159
-53
lines changed

app/components/GiscusComments.tsx

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,11 @@ import Giscus from "@giscus/react";
55
interface GiscusCommentsProps {
66
className?: string;
77
docId?: string | null;
8-
title?: string | null;
98
}
109

11-
export function GiscusComments({
12-
className,
13-
docId,
14-
title,
15-
}: GiscusCommentsProps) {
10+
export function GiscusComments({ className, docId }: GiscusCommentsProps) {
1611
const normalizedDocId = typeof docId === "string" ? docId.trim() : "";
17-
const normalizedTitle = typeof title === "string" ? title.trim() : "";
18-
1912
const useSpecificMapping = normalizedDocId.length > 0;
20-
const termValue = useSpecificMapping
21-
? `${normalizedTitle || "Untitled"} | ${normalizedDocId}`
22-
: undefined;
2313

2414
return (
2515
<div className={className}>
@@ -29,7 +19,7 @@ export function GiscusComments({
2919
category="Comments"
3020
categoryId="DIC_kwDOPuD_8M4Cvip8"
3121
mapping={useSpecificMapping ? "specific" : "pathname"}
32-
term={termValue}
22+
term={useSpecificMapping ? normalizedDocId : undefined}
3323
strict="0"
3424
reactionsEnabled="1"
3525
emitMetadata="0"

app/docs/[...slug]/page.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ export default async function DocPage({ params }: Param) {
5757
const contributorsEntry =
5858
getDocContributorsByPath(page.file.path) ||
5959
getDocContributorsByDocId(docIdFromPage);
60-
const discussionTitle = page.data.title ?? docIdFromPage ?? page.path;
6160
const Mdx = page.data.body;
6261

6362
// Prepare page content for AI assistant
@@ -87,10 +86,7 @@ export default async function DocPage({ params }: Param) {
8786
<Mdx components={getMDXComponents()} />
8887
<Contributors entry={contributorsEntry} />
8988
<section className="mt-16">
90-
<GiscusComments
91-
docId={docIdFromPage ?? null}
92-
title={discussionTitle}
93-
/>
89+
<GiscusComments docId={docIdFromPage ?? null} />
9490
</section>
9591
</DocsBody>
9692
</DocsPage>

scripts/test.mjs

Lines changed: 156 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env node
22
/**
3-
* 将 GitHub Discussions 标题补上 [docId: <id>],用于从 pathname->docId 的 Giscus 迁移。
3+
* 将 GitHub Discussions 标题统一重写为 docId,用于从 pathname->docId 的 Giscus 迁移。
44
*
55
* 两种输入来源:
66
* A) DB 模式(推荐):读取 Postgres(docs/path_current + doc_paths)获得每个 docId 的所有历史路径
@@ -21,6 +21,12 @@
2121
* # 用映射文件(不连 DB)
2222
* node scripts/migrate-giscus-add-docid.mjs --map=tmp/discussion-map.json --apply=true
2323
*
24+
* # 仅处理部分 doc,支持多次传参或逗号/换行分隔
25+
* node scripts/migrate-giscus-add-docid.mjs --doc=abcd123 --doc=efg456 --apply=true
26+
* node scripts/migrate-giscus-add-docid.mjs --doc-path=app/docs/foo/bar.mdx --doc-path=docs/foo/bar --apply=true
27+
* node scripts/migrate-giscus-add-docid.mjs --doc-paths="app/docs/foo/bar.mdx,docs/foo/bar" --apply=true
28+
* GISCUS_DOC_PATHS="app/docs/foo/bar.mdx\napp/docs/baz.mdx" node scripts/migrate-giscus-add-docid.mjs --apply=true
29+
*
2430
* 映射文件格式(示例):
2531
* {
2632
* "i0xmpsk...xls": ["app/docs/foo/bar.mdx", "/docs/foo/bar"],
@@ -52,6 +58,17 @@ const REPO =
5258
const MAP = getArg("map") || process.env.GISCUS_DISCUSSION_MAP || ""; // JSON 文件(映射文件模式)
5359
const APPLY = (getArg("apply") || "false").toLowerCase() === "true"; // 是否真的更新标题
5460

61+
const DOC_FILTERS = getArgList("doc");
62+
const DOC_PATH_FILTERS = [
63+
...getArgList("doc-path"),
64+
...getArgList("doc-paths"),
65+
...(process.env.GISCUS_DOC_PATHS
66+
? process.env.GISCUS_DOC_PATHS.split(/[,\n]/)
67+
.map((v) => v.trim())
68+
.filter(Boolean)
69+
: []),
70+
];
71+
5572
if (!GH_TOKEN) {
5673
console.error("[migrate-giscus] Missing GH_TOKEN/GITHUB_TOKEN.");
5774
process.exit(1);
@@ -62,6 +79,21 @@ function getArg(k) {
6279
return arg ? arg.split("=")[1] : null;
6380
}
6481

82+
function getArgList(k) {
83+
const matches = process.argv
84+
.slice(2)
85+
.filter((s) => s.startsWith(`--${k}=`))
86+
.map((s) => s.split("=")[1]);
87+
if (matches.length === 0) {
88+
const single = getArg(k);
89+
if (single) matches.push(single);
90+
}
91+
return matches
92+
.flatMap((value) => (value ?? "").split(/[,\n]/))
93+
.map((value) => value.trim())
94+
.filter(Boolean);
95+
}
96+
6597
const GQL = "https://api.github.com/graphql";
6698
const ghHeaders = {
6799
"Content-Type": "application/json",
@@ -126,21 +158,21 @@ async function loadDocIdTerms() {
126158
select: {
127159
id: true,
128160
path_current: true,
161+
title: true,
129162
doc_paths: { select: { path: true } },
130163
},
131164
});
132-
const map = new Map(); // docId -> Set<term>
165+
const map = new Map(); // docId -> { title: string|null, terms: Set }
133166
for (const d of docs) {
134-
const set = map.get(d.id) ?? new Set();
135-
if (d.path_current) set.add(d.path_current);
136-
for (const p of d.doc_paths) if (p?.path) set.add(p.path);
137-
// 兼容站点实际的 pathname(可选添加去掉扩展名、加前缀)
138-
for (const p of Array.from(set)) {
139-
const noExt = p.replace(/\.(md|mdx|markdown)$/i, "");
140-
set.add(noExt);
141-
set.add(`/${noExt}`); // 常见 pathname 形态
142-
}
143-
map.set(d.id, set);
167+
const entry = map.get(d.id) ?? {
168+
title: d.title ?? null,
169+
terms: new Set(),
170+
};
171+
if (!entry.title && d.title) entry.title = d.title;
172+
if (d.path_current) registerPathVariants(entry.terms, d.path_current);
173+
for (const p of d.doc_paths)
174+
if (p?.path) registerPathVariants(entry.terms, p.path);
175+
map.set(d.id, entry);
144176
}
145177
return map;
146178
}
@@ -151,17 +183,26 @@ async function loadDocIdTerms() {
151183
const raw = await fs.readFile(abs, "utf8");
152184
const obj = JSON.parse(raw);
153185
const map = new Map();
154-
for (const [docId, arr] of Object.entries(obj)) {
155-
const set = new Set();
156-
(arr || []).forEach((t) => {
157-
if (typeof t === "string" && t.trim()) {
158-
set.add(t.trim());
159-
const noExt = t.replace(/\.(md|mdx|markdown)$/i, "");
160-
set.add(noExt);
161-
set.add(`/${noExt}`);
186+
for (const [docId, rawValue] of Object.entries(obj)) {
187+
const entry = { title: null, terms: new Set() };
188+
189+
if (Array.isArray(rawValue)) {
190+
rawValue.forEach((t) => registerPathVariants(entry.terms, t));
191+
} else if (rawValue && typeof rawValue === "object") {
192+
if (typeof rawValue.title === "string" && rawValue.title.trim()) {
193+
entry.title = rawValue.title.trim();
194+
}
195+
const termsSource = Array.isArray(rawValue.terms)
196+
? rawValue.terms
197+
: rawValue.paths;
198+
if (Array.isArray(termsSource)) {
199+
termsSource.forEach((t) => registerPathVariants(entry.terms, t));
162200
}
163-
});
164-
map.set(docId, set);
201+
} else if (typeof rawValue === "string") {
202+
registerPathVariants(entry.terms, rawValue);
203+
}
204+
205+
map.set(docId, entry);
165206
}
166207
return map;
167208
}
@@ -183,18 +224,90 @@ async function searchDiscussionByTerm(term) {
183224
);
184225
}
185226

186-
// 如果标题中已经包含 [docId: xxx],就跳过
187-
function alreadyHasDocIdTag(title, docId) {
188-
const tag = `[docId:${docId}]`;
189-
return title.includes(tag);
227+
function titleAlreadyNormalized(title, docId) {
228+
const normalized = docId.trim();
229+
if (!normalized) return false;
230+
return title.trim() === normalized;
190231
}
191232

192-
// 生成新标题(在末尾追加,如已含则不变)
193-
function appendDocIdTag(title, docId) {
194-
const tag = `[docId:${docId}]`;
195-
if (title.includes(tag)) return title;
196-
// 避免标题太挤,加个空格
197-
return `${title.trim()} ${tag}`;
233+
function normalizeTitleToDocId(currentTitle, docId) {
234+
const normalized = docId.trim();
235+
if (!normalized) return currentTitle.trim();
236+
return normalized;
237+
}
238+
239+
function registerPathVariants(targetSet, rawPath) {
240+
if (typeof rawPath !== "string") return;
241+
const trimmed = rawPath.trim();
242+
if (!trimmed) return;
243+
244+
const variants = new Set();
245+
const candidates = [trimmed];
246+
247+
const withoutExt = trimmed.replace(/\.(md|mdx|markdown)$/i, "");
248+
candidates.push(withoutExt);
249+
250+
const leadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
251+
candidates.push(leadingSlash);
252+
253+
const withoutExtLeadingSlash = withoutExt.startsWith("/")
254+
? withoutExt
255+
: `/${withoutExt}`;
256+
candidates.push(withoutExtLeadingSlash);
257+
258+
const withoutApp = trimmed.replace(/^app\//i, "");
259+
if (withoutApp && withoutApp !== trimmed) {
260+
candidates.push(withoutApp);
261+
const withoutAppNoExt = withoutApp.replace(/\.(md|mdx|markdown)$/i, "");
262+
candidates.push(withoutAppNoExt);
263+
candidates.push(withoutApp.startsWith("/") ? withoutApp : `/${withoutApp}`);
264+
candidates.push(
265+
withoutAppNoExt.startsWith("/") ? withoutAppNoExt : `/${withoutAppNoExt}`,
266+
);
267+
}
268+
269+
for (const candidate of candidates) {
270+
const value = typeof candidate === "string" ? candidate.trim() : "";
271+
if (value) variants.add(value);
272+
}
273+
274+
for (const value of variants) targetSet.add(value);
275+
}
276+
277+
function applyFilters(docIdMap) {
278+
const docIdFilterSet = new Set(DOC_FILTERS);
279+
const hasDocIdFilter = docIdFilterSet.size > 0;
280+
281+
const pathFilterVariants = new Set();
282+
for (const path of DOC_PATH_FILTERS) {
283+
registerPathVariants(pathFilterVariants, path);
284+
}
285+
const hasPathFilter = pathFilterVariants.size > 0;
286+
287+
if (!hasDocIdFilter && !hasPathFilter) {
288+
return;
289+
}
290+
291+
for (const [docId, info] of Array.from(docIdMap.entries())) {
292+
let keep = true;
293+
294+
if (keep && hasDocIdFilter) {
295+
keep = docIdFilterSet.has(docId);
296+
}
297+
298+
if (keep && hasPathFilter) {
299+
const terms = Array.from(info?.terms ?? []);
300+
keep = terms.some((term) => pathFilterVariants.has(term));
301+
}
302+
303+
if (!keep) {
304+
docIdMap.delete(docId);
305+
}
306+
}
307+
308+
if (docIdMap.size === 0) {
309+
log("⚠️ 未找到符合过滤条件的 docId,本次执行不会更新任何讨论。");
310+
}
198311
}
199312

200313
async function main() {
@@ -203,13 +316,20 @@ async function main() {
203316
);
204317
const docIdToTerms = await loadDocIdTerms();
205318

319+
applyFilters(docIdToTerms);
320+
321+
if (docIdToTerms.size === 0) {
322+
if (prisma) await prisma.$disconnect();
323+
return;
324+
}
325+
206326
let updated = 0,
207327
skipped = 0,
208328
notFound = 0,
209329
examined = 0;
210330

211-
for (const [docId, termsSet] of docIdToTerms) {
212-
const terms = Array.from(termsSet);
331+
for (const [docId, info] of docIdToTerms) {
332+
const terms = Array.from(info?.terms ?? []);
213333
let matched = null;
214334

215335
// 尝试每个 term,直到命中一个讨论
@@ -235,13 +355,13 @@ async function main() {
235355
examined += 1;
236356

237357
const oldTitle = matched.title;
238-
if (alreadyHasDocIdTag(oldTitle, docId)) {
358+
if (titleAlreadyNormalized(oldTitle, docId)) {
239359
skipped += 1;
240360
log(`⏭ #${matched.number} 已包含 docId:${matched.url}`);
241361
continue;
242362
}
243363

244-
const newTitle = appendDocIdTag(oldTitle, docId);
364+
const newTitle = normalizeTitleToDocId(oldTitle, docId);
245365
log(
246366
`${APPLY ? "✏️ 更新" : "👀 预览"} #${matched.number} "${oldTitle}" → "${newTitle}"`,
247367
);

0 commit comments

Comments
 (0)