跳到主要内容

内容管线:从 Feed 原文到可读、可搜、可增强

一、Entry 不是一个字符串

一个 Entry 可能同时包含:标题、摘要、正文、作者、作者头像、媒体、附件、分类、来源、语言、阅读状态、Readability 正文、翻译、摘要和收藏视图。内容展示必须先判断“有哪些事实”,再决定“用哪种模板”。

二、Readability 的位置

packages/readability 独立成包,依赖 Mozilla Readability、JSDOM/Linkedom、DOMPurify 和字符检测。桌面 Main 也依赖它,说明正文抽取可能在不同运行时按需要执行。

Readability 的输出写进 Entry 的派生字段,而不是把 Feed 原文替换掉。原因很现实:

  • 原站 HTML 可能已经被截断;
  • 抽取规则可能随库升级改变;
  • 用户可能需要回看原始描述;
  • 安全清洗必须与内容展示责任分离。

三、内容模板为什么拆开

桌面和移动端都能看到 Article、Social、Video、Picture、Normal 等内容形态。拆成模板有三个收益:

  1. 图片/视频的加载和占位策略不污染文章阅读;
  2. 社交内容的作者与媒体结构可以独立演进;
  3. 移动端列表性能优化可以针对最常见模板做虚拟化和 memo。

模板选择依赖 Entry 数据和用户 view 设置,因此“视图类型”不仅是 CSS 样式,它是领域配置与渲染策略之间的协议。

四、AI Summary 与 Translation 的缓存语义

摘要和翻译表都按 entryId + language 建联合唯一索引。这给 UI 一个明确的缓存模型:请求前检查本地是否有同语言结果,有就直接显示;没有则发起任务,结果落表并更新 Store;请求失败不影响原始 Entry。

语言不是全局单值:用户可能在中文界面阅读英文源,也可能为同一 Entry 生成多种语言结果。把语言放进唯一键,是比“Entry 上只有 summary 字段”更可持续的设计。

五、HTML 安全边界

内容来自外部源,渲染链必须假设 HTML 不可信。Folo 的依赖和工具层可见 dompurifyrehype-sanitizexss 等清洗手段。它们应当分布在不同边界:

  • 服务端 Meta/HTML 注入:对动态文本做 XSS 保护;
  • 阅读器 HTML:清洗标签、属性和 URL;
  • 外部图片/视频:通过代理、协议和 CSP 限制风险;
  • Markdown/富文本编辑:使用统一解析与 schema,而不是信任用户输入。

安全不是“入口过滤一次”就结束,因为同一内容会在 API、SQLite、SSR、WebView 和原生文本组件之间流动。

六、搜索与内容索引

桌面 Renderer 有独立 search store/types,搜索结果区分 feeds、entries、subscriptions,并保留与 Entry/Feed 的关联信息。搜索不是在每个页面里写一套过滤,而是把“跨领域检索”提升到应用级能力。

这带来的设计问题是索引来源:

  • 本地搜索适合已 hydrate 的 Feed/Entry;
  • 服务端搜索适合发现、推荐和未在本地的内容;
  • 分享页搜索/Meta 更关心 URL 对应的公开资源。

读代码时要把“查询数据”和“内容展示”分开,避免因为一个搜索框就让所有领域模块互相依赖。

七、内容管线的可迁移经验

  • 永远保留原始事实,派生结果单独存储;
  • 让派生结果带有语言、版本或时间信息;
  • 让每种内容形态拥有自己的展示模板;
  • 把清洗、安全和代理放在内容边界;
  • 让 UI 能在增强结果缺失时优雅降级。