import { Anchor } from "antd";
import { useEffect, useState, useRef } from "react";
import { convertToPlain } from "../../utils";
const { Link } = Anchor;
interface HeadingNode {
key: string;
label: string;
level: number;
children?: HeadingNode[];
}
interface Props {
open?: boolean;
containerSelector?: string; // 可指定扫描范围
}
/** 构建树结构 */
function buildTree(list: HeadingNode[]): HeadingNode[] {
const root: HeadingNode = { key: "root", label: "", level: 0, children: [] };
const stack = [root];
for (const node of list) {
while (stack.length && stack[stack.length - 1].level >= node.level) {
stack.pop();
}
const parent = stack[stack.length - 1];
parent.children ??= [];
parent.children.push(node);
stack.push(node);
}
return root.children ?? [];
}
/** 递归渲染 */
function renderLinks(nodes: HeadingNode[]): React.ReactNode {
return nodes.map((node) => (
{node.children && renderLinks(node.children)}
));
}
const AnchorNavWidget = ({ open = false, containerSelector }: Props) => {
const [tree, setTree] = useState([]);
const containerRef = useRef(null);
/** 获取容器 */
useEffect(() => {
containerRef.current = containerSelector
? document.querySelector(containerSelector)
: document.body;
}, [containerSelector]);
/** 扫描 heading */
useEffect(() => {
if (!open || !containerRef.current) return;
const headings = Array.from(
containerRef.current.querySelectorAll("h1,h2,h3,h4,h5,h6")
);
const list: HeadingNode[] = headings
.map((el) => {
if (!el.id) return null;
return {
key: `#${el.id}`,
label: convertToPlain(el.innerHTML).slice(0, 30),
level: Number(el.tagName[1]),
};
})
.filter(Boolean) as HeadingNode[];
setTree(buildTree(list));
}, [open]);
if (!open || tree.length === 0) return null;
return (
);
};
export default AnchorNavWidget;