用 Dataview 批量编辑笔记

引子

Obsidian 好像没有批量查找替换吧,没有吧,没有吧?我懒得深入研究……(真懒

有需求的时候就打开 VS Code,批量查找替换支持正则,很爽啊!

但是这样并不能满足所有需求,毕竟总会有更复杂的情况,比如给某些具有特定标记、特征的笔记进行一些编辑、修改、替换什么的。是的,我可以写个脚本,但是遍历所有文件,分析内容结构啥的,不说难吧,有点麻烦。

而 Dataview 是 Obsidian 下非常强大的查询插件,如果它能在查询之后进行编辑,那就完美了。

原理

Dataview 本身没有提供对笔记的编辑功能,甚至对于笔记内容的读取功能都没有。它能查询笔记中的各种特征,但不是笔记中的所有内容。这是基于资源开销的考虑,现在这样的设计只需要查询 Obsidian 提供的缓存就可以了。

所以这里需要用到几个 Obsidian 自身的 api:

  • app.vault.readRaw(笔记路径):这是读取笔记的原始内容,很多用户脚本中会使用,但是官方的 API 文档中并没推荐。异步函数,需要 await
  • 官方推荐的方法有两种: app.vault.read(文件对象)app.vault.readCache(文件对象)。以下摘抄自官方文档:
    • If you only want to display the content to the user, then use cachedRead() to avoid reading the file from disk multiple times.
      如果只想向用户显示内容,则使用 cachedRead() 以避免多次从磁盘读取文件。
    • If you want to read the content, change it, and then write it back to disk, then use read() to avoid potentially overwriting the file with a stale copy.
      如果您想要读取内容、更改内容,然后将其写回磁盘,请使用 read() 以避免可能用过时的副本覆盖文件。
  • 如何获取参数中的文件对象,方法比较多,最容易发现的是 app.valut.getFiles() 方法,类似的还有 app.valut.getFilesCache() 方法,但这些是一次读取所有文件,所以很多场景下并不适用。毕竟我们已经用 Dataview 去完成了查询和过滤的工作,所以这里没必要再一次读取所有文件并过滤。
  • app.vault.getAbstractFileByPath(文件路径) 可以获取文件对象,在读写 api 中都会用到
  • app.vault.modify(文件对象, 新的内容) 方法对笔记进行修改,必须用完整笔记内容去替换掉旧的笔记。

实践

/* 首先用 Dataivew 查询所有你需要的页面 */
dv.pages()
/* 根据一些条件对页面进行筛选 */
.filter(p=>p.file.frontmatter.published && p.file.frontmatter.title)
/* 遍历并修改选出的页面 */
.forEach(async p=>{
  /* 获取当前遍历到的文件对象 */
  const noteTFile = app.vault.getAbstractFileByPath(p.file.path)
  /* 读取笔记内容 */
  const noteContent = await app.vault.read(noteTFile)
  /* 根据需求对内容进行加工,获得新的内容 */
  const uuidLine = `id: ${crypto.randomUUID()}\n`
  const newContent = noteContent.replace(/(^-{3,}\n)([\s\S]*?-{3,}\n)/g, (m, p1, p2)=>p1+uuidLine+p2)
  /* 将新内容写回 */
  await app.vault.modify(noteTFile, newContent)
})

优化

Dataview 的代码会被自动执行,这导致每次显示到该代码块,这套自动编辑的动作都会被做一遍,这很可能产生意外。所以我会把上面的内容定义为一个函数,在代码块中插入一个按钮来触发这个动作,并且在执行时输出一些日志,以便观察执行情况。

这部分我在自己笔记中的 Dataview\Tools\UUID 视图中有完整的应用。

©2022~2023 稻米鼠. Last build at 2023/12/5 00:00:21