ContentIndex 插件 · cleanHtml 定制清洗 · follow.it 消费
站点需要 RSS 来支撑两种消费场景:用户通过 RSS 阅读器订阅更新,以及 follow.it 定时抓取 RSS 触发邮件群发。Quartz v4 内置了 RSS 生成能力(ContentIndex 插件),但它的输出是「原样照搬页面 HTML」,不处理页内导航元素——这些元素在邮件客户端中毫无意义且干扰阅读。
因此需要一条定制的 RSS 管线:在 Quartz 内置流程中插入一步清洗,去除页面专属的锚点标记,输出干净的、适合邮件和 RSS 阅读器消费的全文内容。
管线绝大部分是 Quartz 原生能力,定制的只有一步清洗。
| 组件 | 来源 | 做了什么 |
|---|---|---|
ContentIndex 插件 |
Quartz 内置 | 按配置生成 RSS XML + Sitemap。RSS 输出「原样照搬页面 HTML」——包括 rehype-autolink-headings 生成的 heading 锚点 SVG |
generateRSSFeed() |
Quartz 内置 | 生成 RSS 2.0 XML 结构(title、link、pubDate、description) |
rssFullHtml / rssLimit |
Quartz 内置 | 配置项:开启全文输出(true 时 description 含完整 HTML)、控制条数 |
escapeHTML() |
Quartz 内置 | HTML 实体编码,确保 XML 安全(< → <) |
cleanHtml() |
★ 定制 | 正则剥除 <a role="anchor">...</a> 锚点 SVG。Quartz 不做任何内容清洗,邮件渠道的清洗完全是我们加的 |
唯一的一步定制:在 toHtml() 之后、escapeHTML() 之前,插入正则清洗。
// quartz/plugins/emitters/contentIndex.tsx
const cleanHtml = (html: string): string => {
return html.replace(/<a\s+role="anchor"[^>]*>.*?<\/a>/g, "")
}
// emit() 中构建 RSS description
richContent: opts?.rssFullHtml
? escapeHTML(cleanHtml(toHtml(tree, { allowDangerousHtml: true })))
: undefined,
// ^^^^^^^^
// 先清洗原始 HTML,再编码。清洗必须在编码前——编码后的 <a 和原始 <a> 是不同的字符串。
配置层面,在 quartz.config.ts 中开启全文 RSS:
Plugin.ContentIndex({
enableSiteMap: true,
enableRSS: true,
rssLimit: 50,
rssFullHtml: true, // 输出完整 HTML 正文
})
| 决策 | 选择 | 原因 |
|---|---|---|
| 清洗方式 | 正则剥除锚点 SVG | 最小改动,不修改 Quartz 源码流程,仅在构建管线中插入一步 |
| 清洗时机 | toHtml() 之后、escapeHTML() 之前 | 正则匹配的是原始 HTML 字符序列,编码后的实体无法匹配 |
| RSS 内容 | 全文 HTML(rssFullHtml: true) | 邮件订阅需要完整正文,摘要 RSS 没有订阅价值 |
| RSS 条数 | 50 条 | 覆盖足够长的历史,新订阅者可以回溯 |