Files
NewBlog/src/plugins/satteri/reading-time.mjs
T
TimeCrackbot 5e808a9cd4 feat: 接入 KaTeX 渲染,完成 Sätteri 迁移(Phase 3)
方案:用现成的 satteri-katex(MDAST 插件,40 行,是 rehype-katex 的移植),
而不是自己写。它跑在 MDAST 阶段,天然绕开下面这个坑。

为什么不能用 HAST 阶段做
Sätteri 把块级公式输出成 <pre><code class="language-math math-display">,
而 expressive-code 过滤所有 <pre>,会把它当成未知语言渲染成带行号的
plaintext 代码块 —— 实测 10 个块级公式全部被吃掉(math-display: 0)。
unified 时期没这问题,是因为 rehype-katex 排在 rehypePlugins 第一位,
先把 <pre><code> 换掉了。satteri-katex 的源码注释里记录的是同一个坑。

插件顺序
satteriKatex() 放在 mdastPlugins 最后:它把 math 节点换成 KaTeX 的 html
节点,若排在 reading-time / excerpt 之前,字数统计会把 KaTeX 标记也算进去。

顺带修掉一个之前漏掉的回归
reading-time / excerpt 原本用 ctx.textContent(),但它只拼接纯 text 节点,
会漏掉代码块、行内代码、公式、图片 alt。实测 LaTeX 那篇字数 830 → 488。
改用 mdast-util-to-string 的 toString()(与迁移前的实现一致),字数完全复原。
之前没发现是因为我的对比脚本只截取了 <article> 区域,而字数在它外面的 header 里。
现改为整页对比。

清理依赖(10 个,已确认无引用)
remark-math / rehype-katex / remark-directive /
remark-github-admonitions-to-directives / remark-sectionize / rehype-slug /
rehype-components / rehype-autolink-headings / @astrojs/markdown-remark /
hastscript / unist-util-visit

验证(与 unified() 基线整页对比,41 页)
- 39 页语义完全一致;2 页只剩智能标点在中文/emoji 旁的引号方向差异
  (Sätteri 的结果更正确:把开引号排成 “ 而不是 ”)。
- LaTeX 页:class="katex" 76、katex-display 10,与基线完全一致;
  块级公式不再被 expressive-code 吞掉。
- 字数/阅读时长与基线逐篇一致(830/4、1042/5、416/2)。
- pnpm build 41 页 + pagefind 通过;astro check 仍是 1 error/0 warnings
  (原有 footer.astro 报错,与本次无关)。
2026-09-17 17:52:56 +08:00

24 lines
929 B
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { defineMdastPlugin } from "satteri"
import { toString } from "mdast-util-to-string"
import getReadingTime from "reading-time"
/**
* 把阅读时长写进 frontmatter(minutes / words)。
*
* 页面侧通过 render(entry).remarkPluginFrontmatter 读这两个值,
* 与 unified 时期完全一致,所以 posts/[...slug].astro 不用改。
*/
export const readingTime = defineMdastPlugin({
name: "reading-time",
after(root, ctx) {
// 用 mdast-util-to-string 而不是 ctx.textContent:
// 后者只拼接纯 text 节点,会漏掉代码块、行内代码、公式、图片 alt,
// 导致字数比迁移前少一截(实测 LaTeX 那篇 830 → 488 字)。
const text = toString(root)
const stats = getReadingTime(text)
const frontmatter = ctx.data.astro.frontmatter
frontmatter.minutes = Math.max(1, Math.round(stats.minutes))
frontmatter.words = stats.words
},
})