经过之前基础篇的铺垫,接下来的文章将介绍 Trilium 的精华所在,也是我认为 Trilium 独异于其他同类产品的地方。

然而,在写作过程中,我发现内容比最初拟定的多了不少,所以本篇也不得不拆成两半,原来计划写的一篇简单介绍文章,变成了一个系列。本篇所关注的内容,是 Trilium 独有的笔记关系和属性系统。

笔记关系

对于一个专注于建立个人知识库的笔记系统来说,笔记之间的层级和联系(亦即知识之间的联系)是非常重要的。前一篇提到,Trilium 不仅有一棵功能完整的文档树,还建立了横向的联系,一个页面可以同时存在于文档树里不同的位置,便于分类和参考。事实上,Trilium 的复杂度远不止此,我认为 Trilium 几乎把笔记关系这块功能做到了极致。

首先,笔记之间可以轻松地彼此引用——只要输入 @ 并在后面打字,就能搜索并插入通往其他页面的链接,鼠标移到上面还会有预览:

请输入图片标题

而在右侧边栏 Link Map 组件中,可以实时看到这则笔记和其他笔记的链接图

图中可以看到我在哪几天的子弹日记里关联了这篇文章……

当我发现这么方便的内部引用功能时,我知道,我的意志已经被迁移的诱惑动摇。

无论是印象笔记还是 Bear 还是其他类似的笔记软件,要做到内部引用,都有一个必经步骤:复制笔记链接。而这就意味着切出键盘工作区,拿起鼠标,右键,复制,再回到正在编辑的笔记,粘贴……而且(根据我的印象)它们当中没有一个能显示当前笔记被引用的情况。

而在 Trilium 里,一切都如此简单:你输入 @ ,选择页面,事情就成了。

哦,Notion 似乎也可以做到,但 Notion 可没有引用链接图。我想这是因为 Notion 的主要定位并不是个人知识库,更接近协作工具,而建立知识之间的联系是维基类笔记应用的传统艺能。

最后,Trilium 还提供了一种叫做 Relation Map(关系图) 的笔记形式。它本质上是一个简单的有向图:

图里每个节点对应一则子笔记,点进去就能编辑对应内容,还能给关系加上标注……不仅能画传统的思维导图,还解决了我多年来的痛点——阅读历史书籍或者写小说时,对人物关系可视化的需求。

当然,也很适合用来做全局大纲。我觉得这个功能甚至可以对标 Scrivener 的 corkboard,如果对卡片表面显示的文字长度没什么要求的话。

综上,我认为 Trilium 在建立笔记联系这方面(暂时)堪称一枝独秀。不过眼尖的老司机也许会发现:说了那么多,怎么都没有提标签系统呢?堂堂笔记软件没有标签的嘛?不要着急,下一部分就要介绍 Trilium 特色的 attributes(属性)系统。之所以单独拉出来讨论,是因为 attributes 的应用远远不止于给笔记打标签,更是很多自定义功能的调度中枢。

Attributes 系统

所谓 attributes(属性),可以认为是一则笔记的 metadata(元数据),是「关于笔记的信息」。在 Trilium 中,属性分为四类: label(标签)和 relation(关系),以及对前两者的定义。

先从标签和标签定义开始谈吧。最普通的标签,也就是用于分类的标签,可以在属性面板里自行定义,然后使用。

为什么要先定义呢?因为标签有很多类型:文本、数字、布尔值、日期和链接;又分为单个值和列表值。

用过 Notion 的朋友们可能已经发现了,这不就是给 Notion 的数据库设置数据列吗?没错,Trilium 的标签也是这么用的。不过,Trilium 并没有相应的表单视图,因为每一则笔记的标签定义可以彼此独立。

比起数据库,我想它还是更接近「元数据」的概念——提供当前笔记的一些信息。这些信息可以通过 Ctrl/Cmd+S 唤起搜索栏搜索,例如 @year=1999, @genre=sci-fi, @author=Neal Stephenson……直接搜索标签本身也可以。

那么,有没有办法快速浏览笔记的标签信息,而不是每次都到右侧边栏的属性模块里翻找呢?这就不得不提到标签定义里那个叫「Promoted」的勾选方框。只要勾上这一项,该标签就会显示在笔记的最前面:

这张图的 todo 笔记有五个 promoted 标签,它们被固定在笔记前面,可以直观看到该项任务在何时何地发生,又在哪一天完成。

「可是,给每一则笔记都定义标签也太麻烦了吧!如果我想给一组笔记打上同样的标签呢?」

Trilium 也支持标签继承——子笔记可以继承上一级的标签,只要在最上层勾上「inheritable」就行。而且,继承了标签的笔记也可以拥有其他自己的标签,不影响它的子笔记和兄弟姐妹。

我阅读到相关文档时陷入了沉思,因为这一切都体现出了「面向对象」的思想——建立父笔记,在里面定义标签也就是 field,这不是等同于创建父类吗?而继承了它的标签的子笔记,正如继承父类的子类,也可以扩展出自己专属的标签……

……停一停,你是在介绍一款笔记应用。

咳咳,让我们回到原来的话题。除了自定义的标签,Trilium 里有一些内置的标准标签,可以对笔记进行标注和魔改。由于功能复杂,具体请参见使用文档中的说明,在此只介绍几个:

  • archived 带有此标签的笔记将被归档,不出现在搜索结果中
  • readOnly 只读标签,这篇笔记的内容将不能被修改(当然标签仍然可以)
  • cssClass 用于 css 代码笔记,一旦标注后,该笔记将成为 Trilium 的主题源文件,可以在设置里调用(事实上,Trilium 有一个自带主题就存在默认文档树里,是通过这种方式调用的,神不神奇?而且就算换客户端,主题文件也不用重装一遍,因为它们也是笔记)
  • iconClass 更换笔记的标签

说完了标签和继承,下面就该谈谈「关系」为何物了。其实,之前提到的关系图,就会自动在它的子笔记之间创建关系。可以想象,关系一定是发生在多则笔记之间的,因此关系定义只有两个要素:关系的名称,以及要关联的笔记或笔记列表。

和标签一样,最简单的关系仅用于标注出……呃,笔记间的关系。如果你 promote 一个关系,就能在笔记的最上面看到它。

而内置的标准关系,可以说是 Trilium 的精髓之一——因为稍后介绍的静态页面和插件系统就是靠它调度的。这部分暂时跳过不表,等写到插件系统再折回来介绍。

我们先看看另一个比较简单的应用场景:「怎么通过模板建立一堆格式相同的笔记?」

这就要用到一组名为 template , child:template , child:child:template … 的标准关系了。它的作用就是:指定当前笔记或者子笔记、孙笔记、……的模板。它的值指向作为模板的笔记,继承模板的所有属性;如果设置了子笔记模板,那么创建子笔记时,除了属性之外,还会拷贝一份模板的正文;这期间,如果模板的属性变动了,变动会实时反映在所有使用该模板的笔记中。

我想,这个功能除了不能整理成表格视图之外,已经差不多涵盖了 Notion 数据库能做到的事情吧。

还是通过一个官方例子来说明它的作用:

请输入图片标题
请输入图片标题

以上是我根据官方模板写的读书笔记的一部分。可以看到 Books 这则笔记的子笔记模板指向了其下的 Book template,因此在下面新建的子笔记有了 author, readingStart 等模板所带的属性,而孙笔记(章节摘抄笔记)却没有。

通过模板和关系图,更是可以组合出奇妙的用法,比如另一个官方范例——英国皇室家谱图就用到了人物模板:

日记

在本篇的最后,简单介绍下 Trilium 的原生插件之一——Day Notes,可以理解为日记。这也是我现在采用的电子子弹日记方案的一部分。所有日记都存放在一个带 calendarRoot 标签的笔记下,文档树里只允许出现一则这样的笔记;日记的侧边栏会有一个日历:

请输入图片标题

点击日历中的数字,就能创建或跳转到那一天的日记。

日记当然是从模板创建的,所以我对模板进行了一定魔改,把自己每日循环的任务做成了 checklist,这样,点击日期可以自动生成那一天的任务列表。

除了这样基本的应用,日记最厉害的地方在于右侧的「Edited notes on this day」组件——这个组件会显示当日编辑过的所有笔记列表!

也就是说,当日日记页面完全可以当做一个「工作台」来使用:编辑过的笔记,即今日工作的全部记录,都可以轻松在日记里找到。而需要特别关注的任务,也可以通过内部引用的方式链接到日记,既可以从日记直接跳转,日后查看那一份笔记,也可以清晰地看到哪几天的日记提到了它。

于是,我们发现,日记功能用上了之前提到的大部分「笔记关系」,把管理时间的日历和工作用的文档树联系了起来,这一切都是自动完成的。而在实践中,日记给我带来的效率提升简直令人晕眩。再也不用费心管理日程和笔记之间的关系了。上一个我用过的有类似效果的笔记应用可能是 Pendo 吧,但毕竟只支持 iOS,主打的也是线性文档流。Agenda 好像也有日程+笔记的设计,虽然给人的体验并不一样,又是苹果三件套独享,而我工作必须用 windows 电脑,所以直接否决了。

总之,在我用过的笔记软件中,日记和文档管理往往是鱼与熊掌不可兼得,但 Trilium 奇迹般地将两者统一起来,而且用的是一套优雅的解决方案——它独特的笔记关系系统。

顺带一提,Trilium 还有一个待办事项的原生插件,但我用下来感觉有点累赘,每个待办事项都会生成笔记,让数据库急速膨胀。再加上没发现明显的功能优势,我就没再继续使用了,在此也略过介绍。有兴趣的朋友可以试一下。

接下来……

看了下大纲,剩下的内容偏技术向:

  • 静态页面部署即 Render Note
  • 自定义插件
  • 自建服务器远程同步
  • 自定义 API
  • 遇到的一些坑和解决办法