1、新建一個 tocify.tsx
import React from 'react';
import { Anchor } from 'antd';
import { last } from 'lodash';
const { Link } = Anchor;
export interface TocItem {
anchor: string;
level: number;
text: string;
children?: TocItem[];
}
export type TocItems = TocItem[]; // TOC目錄樹結構
export default class Tocify {
tocItems: TocItems = [];
index: number = 0;
constructor() {
this.tocItems = [];
this.index = 0;
}
add(text: string, level: number) {
const anchor = `toc${level}${++this.index}`;
const item = { anchor, level, text };
const items = this.tocItems;
if (items.length === 0) { // 第一個 item 直接 push
items.push(item);
} else {
let lastItem = last(items) as TocItem; // 最後一個 item
if (item.level > lastItem.level) { // item 是 lastItem 的 children
for (let i = lastItem.level + 1; i <= 6; i++) {
const { children } = lastItem;
if (!children) { // 如果 children 不存在
lastItem.children = [item];
break;
}
lastItem = last(children) as TocItem; // 重置 lastItem 為 children 的最後一個 item
if (item.level <= lastItem.level) { // item level 小於或等於 lastItem level 都視為與 children 同級
children.push(item);
break;
}
}
} else { // 置於最頂級
items.push(item);
}
}
return anchor;
}
reset = () => {
this.tocItems = [];
this.index = 0;
};
renderToc(items: TocItem[]) { // 遞迴 render
return items.map(item => (
<Link key={item.anchor} href={`#${item.anchor}`} title={item.text}>
{item.children && this.renderToc(item.children)}
</Link>
));
}
render() {
return (
<Anchor style={{ padding: 24 }} affix showInkInFixed>
{this.renderToc(this.tocItems)}
</Anchor>
);
}
}
2、重寫 renderer.heading
import marked from 'marked';
import Tocify from './tocify';
const tocify = new Tocify();
const renderer = new marked.Renderer();
renderer.heading = function(text, level, raw) {
const anchor = tocify.add(text, level);
return `<a id="${anchor}" href="#${anchor}" class="anchor-fix"><h${level}>${text}</h${level}></a>\n`;
};
marked.setOptions({ renderer });
3、最後程式碼實現
(props) => (
<div>
<div
className="content"
dangerouslySetInnerHTML={{ __html: marked(props.content) }}
/>
<div className="toc">{tocify && tocify.render()}</div>
</div>
)
markdown 解析的時候會通過 rendeer.heading 解析標題,然後我們在 rendeer.heading 使用 tocify.add 來生成目錄樹(根據 level)並返回一個錨點,rendeer.heading 再根據這個錨點生成一個a連結,最後我們呼叫 tocify.render() 渲染就可以了