# 模板衍生 · Template Derive **一句话**:写一条规则说清楚「哪个文件夹里笔记的哪个段落,要变成哪个文件」,插件就替你看着——段落里写了东西就生成 / 更新产物,没写就不生成。 一个源文件对一个产物文件。已生成的内容跟着源笔记走,你在标记外面写的东西永远不会被覆盖。 > **English summary**: Template Derive watches a folder of notes. For each rule you define — source folder + source section + target path + output template — it renders that section into a separate file. Nothing is created unless you actually wrote something in that section. Files it manages are wrapped in `` / `end` markers; anything you write outside those markers is never touched. --- ## 它解决什么问题 Obsidian 里有一类很别扭的需求:**一份笔记里塞着两种东西,一种是一天一天的流水,另一种是需要连续积累的记录。** 比如日记。今天学了什么、心情如何,这是流水;但「我卡在哪儿、下一步改什么」这种反思,攒成一串才有用,混在日记里会被冲掉。手动复制到另一个文件夹?坚持三天就放弃了。 这个插件把这件事变成自动的:你在日记里写的那四行,会**同时**出现在 `Personal/Review/2026-10-07.md` 里,按日期排成一条可检索的序列。 但它的内核是通用的:**源段落 → 判定 → 渲染 → 落盘**,四步全是配置。日记只是出厂自带的一组示例。 --- ## 安装 ### 手动(目前唯一方式) 1. 从 [Releases](../../releases) 下载 `main.js` 和 `manifest.json` 2. 放进 `<你的库>/.obsidian/plugins/template-derive/` 3. Obsidian → 设置 → 第三方插件 → 关闭「安全模式」→ 启用 **Template Derive** 插件只支持**桌面版**:写入走的是「临时文件 + rename」的原子写入,`fs` 在移动端不可用(见下面「为什么必须原子写入」)。 --- ## 快速上手 装上就有一组预设规则,对应下面这套日记体系: | 规则 | 源 | 触发 | 产物 | |---|---|---|---| | 计划结转 | `Personal/Diary` | 「今日计划」段里没做完的复选框 | 搬进今天日记的同一个段落(带 `↺` 标记) | | 复盘 | `Personal/Diary` | 「今日复盘」段写了东西 | `Personal/Review/YYYY-MM-DD.md` | | 成长轨迹 | `Personal/Diary` | 「成长轨迹」段打了分或写了字 | `Personal/Growth Track/YYYY-MM-DD.md` | | 阅读记录 | `Personal/Diary` | 「今日阅读」段写了东西 | `Personal/Reading/YYYY-MM-DD.md` | 预设里的「今日计划」段约定**每条占两行** —— 上面一行是目标,下面缩进两格的那行写结果: ```markdown - [ ] 背 30 词(早饭后) 完成:对了 9 道 ``` 勾上、以及「完成行写了东西」,都算做完的信号;**结转只搬「没勾上、并且完成行还空着」的那些**,搬过去时会把空的完成行一起带上,方便回头填。 不需要这套?在设置界面里删掉就行,或者改成你自己的。 ### 加一条自己的规则 设置 → Template Derive → 新增规则,填四个东西: **① 源** — 哪个文件夹、文件名长什么样 - `源文件夹`:`Subjects/数据结构与算法` - `文件名日期格式`:填 `*` 表示**文件名任意**,不认日期(这时用 `{{name}}` 取文件名);填 `YYYY-MM-DD` 表示文件名就是日期 **② 触发** — 哪个段落、什么时候算「写了」 - `触发段落`:`自测`(不含 `##`) - `写了才创建`:开着,就是你想要的行为 **③ 产物** — 放哪、叫什么 - `产物路径`:`Reviews/{{name}} 复习卡.md` **④ 渲染** — 内容长什么样 - `输出模板文件`:库里的一份 Markdown,比如 `Templates/衍生/复习卡.md`。留空就原样搬段落。 - `文件头` / `附录`:只在创建时写一次。适合放标题和「为什么这么设计」的依据。 --- ## 模板变量 写在 `产物路径` / `输出模板文件` / `文件头` / `附录` 里,渲染时替换。 | 变量 | 含义 | |---|---| | `{{date}}` | 源文件日期(按规则的日期格式)。文件名任意模式下是空的 | | `{{date:YYYY-MM-DD}}` `{{date:dddd}}` | 指定格式。只支持 `YYYY YY MM M DD D dddd ddd` | | `{{name}}` | 源文件名(不含 `.md`) | | `{{key}}` | 源文件名的「身份」:日期模式下是日期,任意模式下就是文件名 | | `{{file}}` | 源文件完整路径 | | `{{link}}` | `[[源文件路径]]` | | `{{rule}}` `{{ruleid}}` | 规则名 / 规则 id | | `{{section}}` | **触发段落**的正文 | | `{{section:段落名}}` | 指定段落的正文(可跨段) | | `{{field:关键词}}` | 触发段落里取 `- 标签:值` 这一行的值 | | `{{field:段落名 > 关键词}}` | 从指定段落里取 | | `{{field:候选1,候选2}}` | 关键词可以写多个候选,命中哪个都行 | | `{{field:2}}` | 纯数字 = 按出现顺序取第 2 行 | | `{{lines:关键词}}` | 取值同 `{{field:}}`,但把值按行拆成 `- ` 列表 —— 一个标签下写了好几行时用它分点显示 | | `{{checks:段落名}}` | 取复选框项,保留勾选状态;**完成结果(写在目标下面缩进的那行)另起一行输出** | | `{{checks:段落名/子小节}}` | 只取某个 `### 子小节` 里的 | | `{{score:精力}}` | 取 `精力:4 / 5` 里的分数,输出 `4 / 5` | | `{{fm:course}}` | 源文件 frontmatter 里的字段 | **每个变量都能带默认值**:`{{field:去哪|(今天没写)}}` —— 取不到时用竖线后面那段。 **不认识的变量原样保留**,所以你在同一个文件里用核心 Templates 的 `{{title}}` 之类不会被吃掉。 段落正文里的**折叠 callout(`> [!note]-`)会被自动过滤**——那是模板自带的说明,不属于你写的内容。普通的 `>` 引用不受影响。 --- ## 三条安全约定 这三条都是踩过坑之后定下来的,用的时候可以放心: **① 原子写入。** 写入走「写临时文件 → rename 覆盖」,绝不直接覆盖。原因是:直接覆盖时 Obsidian 的文件监听器可能读到「写了一半」的中间态,然后把它当成新内容回写,结果是文件里两份内容交错拼贴。这个真事故发生过一次,修了两小时。 **② auto 标记区。** 插件只维护这对标记之间的内容: ``` (插件写这里) ``` 标记外面你写的任何东西**永不覆盖**。如果你自己建了一个同名文件、里面没有这对标记,插件判定是你手建的,**直接跳过不动**。 (第一版用的是 `wb:auto:start` 标记,插件认得,不会迁移你的文件,继续原地维护。) **③ 只在内容真的变了才写盘。** 不然会「写入 → 触发保存事件 → 再写入」无限循环。 --- ## 输入体验:键盘怎么按 预设的日记格式里,一条计划是**两行**: ``` - [ ] 背 30 词(早饭后) 完成:对了 9 道 ``` 手打这个结构很烦,麻烦的根源是 Obsidian 的原生行为 —— **不是插件坏了**: - `- [ ]` 是它内置的任务语法,可它**不认识「完成:」那一行**。在写满的任务行末尾按回车,它只续出 `- [ ] `,下面那行「完成:」得你自己敲。 - 更坑的是**空的任务行**:CommonMark 规定空列表项按回车要结束列表,Obsidian 老老实实照做,方框当场消失。 所以插件接管了这几个键。**只在上面那些规则管着的文件里生效**,别的笔记一个键都不碰: | 光标所在 | 按回车 → | |---|---| | `- [ ] 目标`,下面已有「完成:」行 | 光标送到「完成:」行,接着写结果 | | `- [ ] 目标`,下面没有 | 补出一行缩进的「完成:」 | | ` 完成:结果`,下面还有空占位 | 光标送到下一个方框 | | ` 完成:结果`,这一节写到最后一格了 | 补出一整组「`- [ ] ` + ` 完成:`」 | | `### 小标题`(光标在行尾) | 在这一节下面补出一组「`- [ ] ` + ` 完成:`」 | 也就是说,**写完目标按一下回车、写完结果再按一下回车**,就能一条接一条写下去,中间不用碰鼠标。 三点说明: - **空占位行直接打字就行**(占位就是让你填的)。在一个空方框上按回车走的是 Obsidian 原生规矩。 - 想在条目里单纯换行,用 **Alt + 回车**。 - 不需要就在设置里「输入增强」关掉。 ### Shift + 回车:再开一个小节 一门课记一个 `###` 小节。手动补这一整节最容易错的是完成行的缩进 —— 少敲两个空格,插件就认不出「这两行是一对」,结转和输出的时候整条会静默丢掉。所以给它也配了按键:光标放在「今日计划」里**任意一行**(不要求停在行尾)按 Shift + 回车: ``` ### Precision Design and Dimensional Inspection - [ ]  对几何公差有直观认识 完成: ### ▏ ← 光标停这,直接打下一门课的名字 - [ ] 完成: ### 阅读 ← 原来的小节没被劈开,整体往后挪 - [ ]   完成: ``` 四条边界: - **插在光标所在小节的末尾**,不是插在光标下面 —— 否则会把正在写的那一节劈成两半。 - **只在「今日计划」这一段生效。** 往上找最近的 `##` 标题,不在名单里就一个字都不动,所以今日复盘、今日阅读按了没反应。名单在设置里可改(逗号分隔)。 - **不要求光标停在行尾**,随便点在这一节的任何一行都能按。 - 不想用了就在设置里「输入增强」关掉(叫 `新增小节`,独立于上面那个回车开关)。 ### 明天的日记:计划本来就该前一天定 核心 Daily Notes 只有一个「打开今天的日记」,它不理解「明天」这个概念。想今晚就把明天的计划写好,两条路:**命令面板 → 新建明天的日记**(也可以给它绑个顺手的键),或者用 **打开日历视图** 之后直接点日历上那个日期。 它照抄**核心 Daily Notes 自己的配置**(直接读 `.obsidian/daily-notes.json`):日记文件夹、文件名格式、用哪份模板 —— 所以生成出来的 `Personal/Diary/2026-10-08.md` 和核心自己建的那份看不出区别。模板里的 `{{date:YYYY-MM-DD}}` `{{date:dddd}}` 会换成那一天的日期和星期。**已经存在就只打开,一个字都不改。** ★ 提前写要知道的一件事:**结转在建立文件的那一刻发生,之后不回头。** 也就是说,今天没做完的条目,在你建明天日记的那一刻就已经搬过去了;之后你再回头改今天这篇,明天那篇不会跟着变 —— 结转对同一天只跑一次,不然会把搬过的条目再追加一遍。真要补,用命令「把上一天没做完的条目结转到今天(忽略去重)」再搬一次。 --- ## 命令 `Ctrl/Cmd + P`: - **为当前笔记生成 / 更新衍生文件** — 手动跑一遍,自动触发失灵时的兜底 - **处理今天的所有源文件** — 按规则的日期格式推出今天的源文件,逐个处理 - **把上一天没做完的条目结转到今天(忽略去重)** — 结转规则一般一天只跑一次,这个强制再跑 - **在计划里新增一个小节(### + 待办)** — 和 Shift + 回车做一模一样的事。**快捷键失灵时的判断依据**:这条命令能跑就说明插件是新的,只是按键被挡了。也可以在「设置 → 快捷方式」里给它绑任何顺手的键,那条路走 Obsidian 自己的命令系统,比插件监听键盘稳。 - **新建明天的日记** — 在核心 Daily Notes 的配置下建明天那份,模板里的日期变量自动换成明天(详见上面那一节)。 - **打开日历视图** — 把 Calendar 插件的日历面板叫出来(已经开着就把它推到前台)。之所以自己留一条:Calendar 自己的那条命令叫 `Calendar: Open view`,是英文的,而且**只在日历没打开的时候才出现**,开着的时候搜不到它。这条不限状态。(Calendar 没启用时这条会自己藏起来。) - **为光标所在学科建今天的笔记** — 从光标往上找最近的学科标题,在 `Subjects/<学科>/笔记/` 下按模板建今天的文件并跳过去;已经存在就只打开,绝不覆盖。默认没绑键。 --- ## 已知限制 - **只能桌面版**(原子写入依赖 `fs`)。 - **模板里的日期格式只支持 `YYYY YY MM M DD D`**,不支持 `{{date+1d}}` 这种偏移写法。想在前一天先把明天的日记写出来,用命令「新建明天的日记」,或者用 Calendar 插件直接点日期(两条路建出来的文件一样)。 - **产物文件不会自动删除。** 你把源段落清空了,产物里会换成你配的「空提示」,文件本身保留——毕竟你可能在标记外面写过东西。 - **一个源文件对一个产物文件。** 不支持「多条记录追加进同一个文件」。 - 结转依赖源文件按日期命名。文件名任意(`*`)的规则不参与结转。 - **回车增强只在规则命中的文件里生效**,而且只认「`- [ ] 目标` + 缩进的 `完成:`」这一种结构;旧的内联写法(`- [ ] 目标 完成:结果`)不碰。 - **Shift + 回车开新小节只在设置里的「段落名单」命中时才生效**(默认就 `今日计划` 一个),而且新小节永远插在当前小节的末尾。 --- ## 为什么是这些默认值 预设那三条规则不是随手写的,每一段的形态都对应一条有据可查的结论。完整出处写在产物文件的附录里(可折叠),这里列个索引: | 设计 | 依据 | |---|---| | 复盘分 Feed up / Feed back / Feed forward | Hattie & Timperley (2007)《The Power of Feedback》 | | 归因只写内部原因 | Oettingen 的 WOOP / MCII:纯积极幻想反而降动机 | | 计划写「什么时候做」 | Gollwitzer 实现意图,94 项研究元分析 d = 0.65 | | 勾选必须接复盘 | Michie et al. (2009):自监控单打 0.26,配其他技术 0.42 | | 「完成:」写回忆出多少 | Roediger & Karpicke (2006):提取 61% vs 重读 40% | | 目标与完成**拆成两行** | Michie et al. (2009):勾选只是自监控,写了结果才是反馈 | | 阅读记录第二步要「合上书」 | Karpicke & Blunt (2011, *Science*):自由回忆多记住 145% | | 三格打分:自主 / 胜任 / 联结 | Deci & Ryan 自我决定理论 | | 「掌握性证据」独立成节 | Bandura (1977):自我效能四来源里掌握性经验最强 | | 「今日小胜」独立成节 | Amabile & Kramer《The Progress Principle》:11,872 篇工作日日记 | | 「我在成为什么样的人」 | Mueller & Dweck (1998):过程表扬 vs 个人表扬 | --- ## 开发 ```bash node --check main.js # 语法 node test/run.cjs # 单元测试(纯 Node,不需要 Obsidian),当前 260 项 ``` `main.js` 是**单文件**,没有构建步骤:纯函数核心和 Obsidian 插件壳住在同一个文件里,靠 `require('obsidian')` 是否成功来分流。所以同一份代码既能被 Obsidian 加载,也能被 Node 直接 `require` 跑测试。 「回车续行」的判断也全在纯函数 `taskEnterAction(getLine, lineNo, opts)` 里 —— 它只回答「光标在这一行按回车该干什么」,不知道 Obsidian 的存在,所以那套边界全都能单测。「Shift + 回车开新小节」同理,在 `sectionEnterAction(getLine, lineNo, opts)` 里:输入是几行文本和光标行号,输出是「插什么、光标落在哪」。插件壳里只有一个 keydown 监听负责接键和落光标。 这两个函数的返回值可以直接断言,所以改键盘行为时**先写用例再改代码**,比在 Obsidian 里反复试快得多。 改了 `main.js` 之后必须让 Obsidian 重新加载插件代码(设置里关掉再打开;不生效就重启 Obsidian)。 ## 许可 MIT