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: "#" }],
},
],
})
},
},
})