Source Notes
Source Note 是 ZotFlow 的核心机制:每个 Zotero 条目自动生成一份结构化 Markdown,作为知识图谱中稳定、可寻址的"来源事实层"。
快速理解
- Source Note 是条目的汇总页——元数据、annotation 摘录、子笔记由模板驱动,随条目变化自动刷新
- 页面内的内容有三种所有者:模板在每次更新时重生成骨架;Zotero 同步区域(Item Note、注释评论)可编辑并随同步回流;persist region 与自加 frontmatter 字段完全属于你——仅存本地、原样保留。完整图景见所有权模型
- 页面以锁定状态打开(
zotflow-locked: true),防止误改模板所有的内容——凡属于你的始终可编辑,且不会因重渲染丢失
渲染流程
Library Source Note(Zotero 条目)
创建或更新 Source Note 时的完整 pipeline:
- 路径模板渲染,决定文件落点
- ZotFlow 读取你的内容模板(或使用 built-in 默认模板)
- 从本地 IndexedDB 收集条目的元数据、子笔记、附件、annotation
- LiquidJS 渲染模板,产出 Markdown 正文
- Frontmatter 合并:如果目标文件已存在,执行注记合并策略:用户直接在 note 中添加的字段不受影响,template 中的
??前缀字段仅在 note 中无此字段时填充,template 中无??前缀的字段始终覆盖 - 注入强制字段(这些总是覆盖模板):
zotflow-locked: truelibrary-id— Zotero 库标识zotero-key— 链接到 Zotero 条目item-version— 用于更新检测,仅在版本变化时触发重渲染
- 文件写入磁盘
Local Source Note(vault 本地文件)
相同 pipeline,但 context 变量和强制字段有所不同:
zotflow-locked: truezotflow-local-attachment: [[path/to/file.pdf]]
本地文件的 annotation 数据存储在 co-located .zf.json sidecar 中(如 Papers/paper.pdf → Papers/paper.zf.json),不在 Source Note 内部。
本地 Source Note 同样支持可编辑区:annotation 评论区(编辑回写到 .zf.json sidecar,已打开的本地 reader 会自动刷新)和 persist region(仅存本地、每次重渲染都存活,见下文)。Zotero note region 不适用——本地文件没有 Zotero 子笔记。
用户可编辑的范围
模板拥有页面的骨架——除此之外的内容都归你编辑:frontmatter(始终自由)、Zotero 同步区域(Item Note 与注释评论)、以及仅存本地的 persist region。
Frontmatter(始终可编辑)
Frontmatter 有两种编辑来源:
- Template 中定义的字段:在模板的
---块中声明 - 你在 note 里直接添加的字段:打开
.md文件后手动写入 frontmatter
你在 note 里直接添加的字段
ZotFlow 永不修改。重渲染时原样保留,不参与任何合并逻辑。
Template 中定义的字段
重渲染时按前缀规则合并:
| 前缀 | 行为 |
|---|---|
?? 前缀(如 ??rating) | note 中不存在 → 用 template 的值填充;note 中已存在 → 保留 note 中的现有值 |
无 ?? 前缀 | 总是用 template 内容覆盖 note |
强制字段(zotflow-locked、library-id、zotero-key、item-version、local note 的 zotflow-local-attachment)始终重新注入,不受上述规则影响。
典型用法
- 在 template 中写
??rating: 0→ 首次生成时 note 获得rating: 0,之后你在 note 中改为 5 → 重渲染保留你的 5 - 在 template 中写
tags:→ 每次重渲染都覆盖,确保 tags 与 Zotero 同步 - 在 note 里直接写
myNotes: "..."→ ZotFlow 永远不碰
可编辑区:三种类型
正文中,三种 region 被隐藏 HTML comment marker 包裹,视为可编辑区:
| Region 类型 | Marker | 默认承载内容 |
|---|---|---|
| Zotero child note | <!-- ZF_NOTE_BEG_<key> --> … <!-- ZF_NOTE_END_<key> --> | 一个 Zotero note item 的 Markdown 渲染 |
| Annotation comment | <!-- ZF_ANNO_BEG_<key> --> … <!-- ZF_ANNO_END_<key> --> | 你附加在某条 annotation 上的评论文本 |
| Persist region | <!-- ZF_PERSIST_BEG_<id> --> … <!-- ZF_PERSIST_END_<id> --> | 模板给的默认内容——之后完全归你,仅存本地 |
在 Source / Live Preview 模式下,每个 region 在 BEG marker 行首显示 🔒 锁图标。点击解锁后,region 内的内容变为可编辑。
保存时(debounce ~2s):
- Note region → Markdown 转回 Zotero HTML,更新 IndexedDB 中对应的 note 记录。如果 region 包含
<!-- ZF_NOTE_META … -->行,wrapper 属性会在回写时重建 - Annotation comment region → 去除 leading
>前缀,Markdown 转 Zotero 注释 HTML(仅支持<b>、<i>、<sub>、<sup>),更新 IndexedDB 中对应的 annotation comment
下一次 bidirectional sync 将修改推到 Zotero。
Persist Region(仅存本地的内容)
Persist region 属于你而不属于 Zotero:写在里面的内容在每次 source note 更新中都会存活,且永远不会同步到 Zotero。典型用法是不创建 item note、直接在 source note 里写个人总结或阅读笔记。
在模板中用一个你自选的稳定 id 声明——原生注释对或 wrap_editable: "PERSIST", "your-id" 均可(语法与 id 规则详见模板 Filter 参考):
## 我的总结
<!-- ZF_PERSIST_BEG_summary -->
<!-- ZF_PERSIST_END_summary -->
每次 note 更新时,ZotFlow 会在重渲染前提取 persist 内容,渲染后按 id 拼回原位。Marker 有问题(缺 id、id 重复、未闭合、嵌套)时该 note 会拒绝更新并报错指向出错行——解析失败时绝不覆写任何内容。
孤儿内容: 如果模板中某个 region id 后来被删除或改名,其内容不会被删除——它会被移动到 note 底部一段有明确边界的 "Orphaned persist regions" 区域(<!-- ZF_PERSIST_ORPHAN_BEG --> … <!-- ZF_PERSIST_ORPHAN_END -->),并弹出一次性警告通知。清理由你自己完成:
- 删除单个 region 的内容 — 解锁后清空文字;空 region 会在下次更新时自动消失。
- 编辑或移除孤儿内容 — 在 frontmatter 中把
zotflow-locked改为false(完成后改回),或使用任何外部工具:哨兵 marker 让这段区域很容易被脚本定位,而编辑器锁定在 Obsidian 之外不存在。
编辑器中,persist region 以低饱和橙色边框显示,与主题色边框的同步类 region 相区分。
⚠️ Persist 内容存在 note 文件里。如果启用了自动清理已回收 source note,Zotero item 被移入回收站时整个文件(连同 persist 内容)会一起进入系统回收站。
⚠️ Marker 之外的结构、annotation excerpt、标题、生成的骨架——仍然是 locked 的。只有 Marker 内部(和 frontmatter)属于你可编辑的范围。 Note 和 annotation region 由模板中的
wrap_editablefilter 生成(详见模板 Filter 参考);persist region 也可以直接手写注释对。
Editable Region 相关设置
- Default Editable Region Locked(Settings → ZotFlow → General) — 新 region 初始是否锁定。单 region 的 toggle 会覆盖此默认值(当前 session 内有效)
- Hide Editable Region Markers — 隐藏
ZF_*_BEG/ZF_*_ENDmarker 行 - Read Only 库 → note 和 annotation region 的解锁图标不可用;persist region 仍可编辑(其内容从不离开你的 vault)
- Editable region 仅在 Source 和 Live Preview 模式下可用。Reading View 下整页只读
自动更新行为
Library Source Note
同步触发更新
- 同步从 Zotero 拉取变更条目
- 对每个已有 Source Note 且发生变更的条目,调度一次 debounced(~2s)重渲染
- 更新是版本感知的:如果文件的
item-versionfrontmatter 与当前条目版本一致,不触发重渲染
Annotation 变更触发更新
当你在 reader 中添加、编辑或删除 annotation,Source Note 自动更新——同样是 ~2s debounce。此类更新强制触发,不受 version 检查约束。仅修改标签(通过标签编辑弹窗)同样触发重渲染,因为 annotation 变更检测现在会比较标签签名。
手动触发更新
除了自动更新,也可以随时手动强制重渲染:
- Tree View:右键条目 → Open source note(会强制更新该条目的 Source Note)
- 命令面板:
ZotFlow: Sync Source Notes批量同步所有 Source Note
Local Source Note
本地文件的 Source Note 随 reader annotation 的增删改自动更新,debounce ~2s。
推荐用法
写在哪,问两个问题——是不是关于这一篇? 和 要不要让 Zotero 看到?
| 要同步到 Zotero | 只留在 vault | |
|---|---|---|
| 关于这一篇 | Item Note——注释延伸、复述、想在 Zotero 所达设备上都看到的总结 | Persist region——私人阅读笔记、评价、工作草稿,就写在来源页面里 |
| 跨多篇 | — | 独立 Obsidian 笔记——综述、比较、论证;wikilink 连回各 Source Note |
配套习惯:
- 让模板所有的部分承载来源事实——题录、摘要、注释摘录。别在那里写正文:重渲染会重新生成。
- 结构化的单条目元数据(评分、阅读状态)用自定义 frontmatter 字段——直接在 note 里添加(永不被碰),或用
??前缀在模板中预设可覆盖的默认值(如??rating: 0、??status: unread)。 - 在模板里为你常用的个人段落声明 persist region(比如一个
## 我的总结区块)——改一次模板,每张 source note 都有了你的文字的持久归处。
常见问题
我改过的正文又变回去了
你写在了模板所有的区域——那里每次重渲染都会重新生成。为你的内容设计了三个持久归处:
- Persist region——模板中声明的仅本地区块,最适合在来源页面里写自由笔记(详情)
- 同步区域——Item Note / 注释评论的编辑写入 IndexedDB 并回流 Zotero
- 自定义 frontmatter 字段——ZotFlow 永不修改
完整决策指南见所有权模型。
锁图标不可点
多见于 Read Only 库或 API Key 权限不足。
没有自动更新
- 确认同步是否成功执行
- 确认条目的
item-version是否真的变了(annotation 更新不受此限制,始终强制触发) - 检查 Source Note 文件是否被外部修改导致 frontmatter 异常