自研小插件:给 Markdown 加目录、锚点与代码复制
写作时不该为了目录而手写 HTML。思路是:Markdown 照常写,渲染之后统一做后处理。
第一步:渲染 HTML#
import { marked } from 'marked';
const html = marked.parse(markdown, { async: false }) as string;第二步:给 h2/h3 注入锚点并顺手建目录#
const toc: TocItem[] = [];
const counter = new Map<string, number>();
const withIds = html.replace(/<h([23])(\s[^>]*)?>([\s\S]*?)<\/h\1>/g, (m, level, attrs, inner) => {
const plain = inner.replace(/<[^>]+>/g, '').trim();
const base = headingId(plain);
const used = counter.get(base) ?? 0;
counter.set(base, used + 1);
const id = used === 0 ? base : `${base}-${used + 1}`;
toc.push({ id, text: plain, level: Number(level) });
return `<h${level} id="${id}">${inner}<a class="heading-anchor" href="#${id}">#</a></h${level}>`;
});中文标题直接进 id 也没问题,浏览器会自动做百分号编码。重复标题必须去重,否则第二个同名小节的锚点会跳错位置。
第三步:代码高亮 + 复制按钮#
const highlighted = hljs.getLanguage(lang)
? hljs.highlight(raw, { language: lang }).value
: hljs.highlightAuto(raw).value;
return `<figure class="code-block" data-lang="${lang}">
<figcaption><span>${lang}</span><button data-copy>复制</button></figcaption>
<pre><code class="hljs">${highlighted}</code></pre>
</figure>`;复制按钮不用在渲染时绑定事件,交给事件委托即可:
root.addEventListener('click', (event) => {
const btn = (event.target as HTMLElement).closest('[data-copy]');
if (!btn) return;
const code = btn.closest('.code-block')?.querySelector('code');
navigator.clipboard.writeText(code?.textContent ?? '');
});这样服务端渲染的 HTML 不需要任何改造,客户端只加一层增强。
效果与边界#
- ✅ 目录、锚点、高亮、复制、灯箱,全部零写作成本;
- ✅ 与静态渲染兼容,不依赖客户端 JS 也能读到正文;
- ⚠️ 后处理用的是正则,别在标题里写嵌套的复杂 HTML,否则锚点文本会带上标签残留。
更严格的做法是自定义 marked 渲染器,但正则后处理的好处是:升级 marked 大版本时基本不受影响。
付费下载 · ¥39.00
付款后立即开通下载链接(30 天有效、可下载 5 次)。在微信中打开本页可直接支付,在电脑上则显示扫码支付。
微信扫码加客服
评论
正在加载评论…