fix(frontend): 增强锚点链接模糊匹配,兼容 LLM 生成的不一致目录格式

This commit is contained in:
techotaku39
2026-05-24 03:21:17 +08:00
parent ebdb254fc6
commit 905dbcce47
3 changed files with 56 additions and 4 deletions

View File

@@ -57,7 +57,7 @@
"react-router-dom": "^7.5.1",
"react-syntax-highlighter": "^15.6.1",
"rehype-katex": "^6.0.2",
"rehype-slug": "^6.0.0",
"rehype-slug": "5.1.0",
"remark-gfm": "3.0.1",
"remark-math": "^5.1.1",
"sonner": "^2.0.3",

View File

@@ -150,8 +150,8 @@ importers:
specifier: ^6.0.2
version: 6.0.3
rehype-slug:
specifier: ^6.0.0
version: 6.0.0
specifier: 5.1.0
version: 5.1.0
remark-gfm:
specifier: 3.0.1
version: 3.0.1
@@ -2803,9 +2803,15 @@ packages:
hast-util-from-parse5@8.0.3:
resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==}
hast-util-has-property@2.0.1:
resolution: {integrity: sha512-X2+RwZIMTMKpXUzlotatPzWj8bspCymtXH3cfG3iQKV+wPF53Vgaqxi/eLqGck0wKq1kS9nvoB1wchbCPEL8sg==}
hast-util-has-property@3.0.0:
resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==}
hast-util-heading-rank@2.1.1:
resolution: {integrity: sha512-iAuRp+ESgJoRFJbSyaqsfvJDY6zzmFoEnL1gtz1+U8gKtGGj1p0CVlysuUAUjq95qlZESHINLThwJzNGmgGZxA==}
hast-util-heading-rank@3.0.0:
resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==}
@@ -2842,6 +2848,9 @@ packages:
hast-util-to-parse5@8.0.1:
resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==}
hast-util-to-string@2.0.0:
resolution: {integrity: sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==}
hast-util-to-string@3.0.1:
resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==}
@@ -4411,6 +4420,9 @@ packages:
resolution: {integrity: sha512-L/FO96EOzSA6bzOam4DVu61/PB3AGKcSPXpa53yMIozoxH4qg1+bVZDF8zh1EsuxtSauAhzt5cCnvoplAaSLrw==}
engines: {node: '>=16.0.0'}
rehype-slug@5.1.0:
resolution: {integrity: sha512-Gf91dJoXneiorNEnn+Phx97CO7oRMrpi+6r155tTxzGuLtm+QrI4cTwCa9e1rtePdL4i9tSO58PeSS6HWfgsiw==}
rehype-slug@6.0.0:
resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==}
@@ -7878,10 +7890,16 @@ snapshots:
vfile-location: 5.0.3
web-namespaces: 2.0.1
hast-util-has-property@2.0.1: {}
hast-util-has-property@3.0.0:
dependencies:
'@types/hast': 3.0.4
hast-util-heading-rank@2.1.1:
dependencies:
'@types/hast': 2.3.10
hast-util-heading-rank@3.0.0:
dependencies:
'@types/hast': 3.0.4
@@ -8004,6 +8022,10 @@ snapshots:
web-namespaces: 2.0.1
zwitch: 2.0.4
hast-util-to-string@2.0.0:
dependencies:
'@types/hast': 2.3.10
hast-util-to-string@3.0.1:
dependencies:
'@types/hast': 3.0.4
@@ -10191,6 +10213,16 @@ snapshots:
unified: 11.0.5
unist-util-visit: 5.1.0
rehype-slug@5.1.0:
dependencies:
'@types/hast': 2.3.10
github-slugger: 2.0.0
hast-util-has-property: 2.0.1
hast-util-heading-rank: 2.1.1
hast-util-to-string: 2.0.0
unified: 10.1.2
unist-util-visit: 4.1.2
rehype-slug@6.0.0:
dependencies:
'@types/hast': 3.0.4

View File

@@ -123,7 +123,27 @@ function createMarkdownComponents(baseURL: string) {
const handleAnchorClick = (e: React.MouseEvent) => {
e.preventDefault()
const id = decodeURIComponent(href.slice(1))
const target = document.getElementById(id)
// 1. 优先精确匹配 id
let target = document.getElementById(id)
// 2. 精确失败时按 heading 文本模糊匹配
// LLM 生成的目录锚点可能和 heading 实际文本不完全一致
//(例如 heading 带 *Content-[00:00]* 后缀,目录链接里没有)
if (!target) {
const normalize = (s: string) =>
s.replace(/[-:\s*\[\]]/g, '').toLowerCase()
const search = normalize(id)
const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6')
for (const h of headings) {
const text = h.textContent || ''
if (normalize(text).includes(search) || search.includes(normalize(text))) {
target = h
break
}
}
}
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
} else {