import { defineHastPlugin } from "satteri" /** * 在标题末尾追加一个 # 锚点链接(替代 rehype-autolink-headings)。 * * 依赖 satteriHeadingIdsPlugin() 先生成 id,所以本插件必须排在它之后。 * * 目标输出(与 unified 时期逐字一致): *

标题#

*/ const HEADING_TAGS = ["h1", "h2", "h3", "h4", "h5", "h6"] /** @param {any} node */ const hasAnchor = (node) => { if (!Array.isArray(node.children)) return false return node.children.some( (child) => child?.type === "element" && child.tagName === "a" && Array.isArray(child.properties?.className) && child.properties.className.includes("anchor"), ) } export const autolinkHeadings = defineHastPlugin({ name: "autolink-headings", element: { filter: HEADING_TAGS, visit(node, ctx) { const id = node.properties?.id if (typeof id !== "string" || id.length === 0) return if (hasAnchor(node)) return // 脚注区那个视觉隐藏的 "脚注" 标题不需要 # 锚点 let parent = ctx.parent(node) while (parent) { if (parent.type === "element" && parent.properties?.["data-footnotes"] !== undefined) { return } parent = ctx.parent(parent) } ctx.appendChild(node, { type: "element", tagName: "a", properties: { className: ["anchor"], href: `#${id}` }, children: [ { type: "element", tagName: "span", properties: { className: ["anchor-icon"], "data-pagefind-ignore": "" }, children: [{ type: "text", value: "#" }], }, ], }) }, }, })