org-mode 系列——org-roam 与 zettelkasten 笔记法

1 什么是 Zettelkasten 笔记法

Zettelkasten 笔记法出自德国社会学家尼克拉斯·卢曼(Niklas Luhmann)之手。Luhmann 的滑动盒(Zettelkasten)就是一盒卡片。这些卡片很小,通常只够容纳一个概念。这种尺寸限制促使人们将想法分解成单个概念。这些概念被明确地联系在一起。对概念的细分鼓励了对概念的切入探索,增加了思考的空间。在笔记之间建立明确的联系还能鼓励人们思考概念之间的联系。

在每个笔记的角落,卢曼都给标注了一个有序的 ID,这样他就可以在笔记之间进行链接和跳转。在 Org-roam 中,我们只需使用超链接。

这种方法的核心是:理解、积累、联结,不要陷入酷炫的软件和技术中,忘记了初心。联结是重要的,但使用最基础的链接就可以解决,然后能够轻松找到就足够了。做笔记的时候一定要有链接,否则笔记一多,很快就找不到了。而理解和积累是考验长期功力的。

org-roam 是基于 Emacs 中 org-mode 实现的 Zettelkasten 笔记法 cite:&Ahrens2017.HowTakeSmart(滑箱卡片)1的包。在 org-roam 中每张卡片都是一个纯文本的 Org 模式文件。笔记之间的链接跳转使用超链接实现。通过强大的模板系统预填模板内容,可以轻松创建新的便笺箱。关于网状笔记,可以参考知乎上的这个帖子:How to Take Smart Notes-《如何记网状笔记》 ,或者看这个:《会记笔记就会写作》完全版

一般来说,滑箱卡片都会被分为三类:临时笔记、文献笔记和永久笔记。临时笔记是用来快速记录想法的,它要求快速记录,不打断正在进行的活动。临时笔记必须尽快整理(一两天内),因为它是想法片断,时间长了就忘记跟这条笔记相关的上下文了。临时笔记整理出来就形成了永久笔记。文献笔记是在阅读文献时形成的笔记。

笔记内容应该都是自己经过思考总结之后写的,不能只是复制粘贴他人的文字(临时笔记不需要)。没有经过思考,我们往往以为自己懂了,但其实并没有,所以抄别人的文字作用并不大。

除临时笔记外,每条笔记都有明确的主题,通常使用多个 tags 来描述其主题。这样当笔记达到一定数量时,自然会形成一个知识网络,而不是传统笔记的层级结构,而且查找相关的笔记也更方便。当然,如果笔记中有到其他笔记的链接,也会增加知识之间的联系,使知识网络更加完善。如果笔记词条有别名,则使用 aliases 来描述其别名,便于查找。

使用 org-roam 需要安装 org-roam 包,它提供了基本的功能。如果需要学术笔记,org-roam-bibtex 包实现了 org-ref、helm-bibtex 与 org-roam 之间的紧密集成,有助于在 org-roam 环境下同时完成学术阅读和笔记撰写。org-roam-ui 则为笔记提供了一个图形化、交互式的知识图谱环境。

删除笔记用 delete-file 函数,或者在 dired 模式下删除文件。

1.1 转瞬即逝的笔记(临时笔记)

便笺箱需要一种快速记录想法的方法。这些笔记被称为稍纵即逝的笔记:它们是对需要稍后处理或销毁的信息或想法的简单提醒。这通常使用 org-capture(见 (org)Capture) 或 Org-roam 的每日笔记功能(见 org-roam-dailies)来实现。这为收集想法提供了一个中央收件箱,以便日后将其处理为永久笔记。

当把临时笔记整理成为永久笔记的时候,要在临时笔记中插入永久笔记的链接。通过这些链接,就能实现临时笔记与永久笔记的双向链接2。通过这种方式,我们就拥有了一个基于时间的日志,同时还构建了一个基于主题的、互联的永久知识库。这个知识库作为生成式大模型的知识库,就能实现一个自我定制的独特大模型。

1.2 永久性笔记

永久笔记又分为两类:文献笔记和概念笔记。文献笔记可以是对特定来源(如书籍、网站或论文)的简短笔记,您可以在以后查阅。概念笔记在撰写时需要更加谨慎:它们需要具有自明性和详细性。Org-roam 的模板系统支持添加不同的模板,以方便创建这些笔记。

1.3 org-roam 设计理念

Org-roam 的核心是提供一个数据库抽象层,对纯文本中已有的内容进行双重表示。这样,我们(人类)就可以继续使用纯文本,而程序则可以利用数据库层执行复杂的查询。这些功能包括但不限于

  • 链接图遍历和可视化
  • 对标题进行类似 SQL 的即时查询:我的 TODO 是什么?计划 X 还是 Y?
  • 访问节点的属性,如标签、参考文献、TODO 状态或优先级

所有这些功能都由数据库抽象层提供支持。因此,Org-roam 的核心主要目标是提供一种有弹性的双重表示法,这种表示法维护成本低、易于理解,而且尽可能是最新的。此外,Org-roam 还为希望对其 Org 文件执行编程查询的用户提供了一个指向该数据库抽象层的应用程序接口。

2 org-roam 配置

Org-roam 的强大功能源于其高效的缓存机制:它使用 sqlite3 数据库存储相关信息,它会遍历 org-roam-directory 目录下的所有文件,并维护所有链接与节点的缓存。

要开始使用 Org-roam,请选择存储 Org-roam 文件的位置。笔记所在的目录由变量 org-roam-directory 指定。Org-roam 会递归搜索该目录下的笔记文件。在调用任何 Org-roam 函数前,必须先设置此变量。

org-roam 不解析符号链接,所以在设置目录和文件时时一定要注意使用 file-truename 函数确保将所有符号链接解析为真实目录和文件。

org-roam 也是采用 capture 的方式来记录笔记,所以需要设置 org-roam-capture-templates 变量。这里我们建立了两个模板,一个用于正常笔记,另一个用于保存觉得重要的整篇文章。

(use-package org-roam
  :ensure t
  :hook ((after-init . (lambda ()
                         (run-with-idle-timer
                          1 nil
                          (lambda () (org-roam-db-autosync-mode 1))))))
  :bind
  (("C-c n f" . org-roam-node-find)
   ("C-c n c" . org-roam-dailies-capture-date)
   ("C-c n j" . org-roam-dailies-goto-date)
   (:map org-mode-map
         (("C-c n i" . org-roam-node-insert)
          ("C-c n o" . org-id-get-create)
          ("C-c n t" . org-roam-tag-add)
          ("C-c n a" . org-roam-alias-add)
          ("C-c n l" . org-roam-buffer-toggle)
          ("C-c n r" . org-roam-ref-add)
          )))
  :custom
  (find-file-visit-truename t)        ;org-roam不解析符号链接
  (org-roam-node-display-template (concat "${directories}${title} " (propertize "${tags}" 'face 'org-tag))) ;在选择界面节点的显示模板
  (org-roam-capture-templates     ;捕获模板
   '(("d" "default" plain "%?" :target
      (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+TITLE: ${title}\n#+DATE: \n")
      :immediate-finish t     ;建立结点后立即结束编辑
      :unnarrowed t)
     ("a" "article" plain "%?" :target
      (file+head "articles/%<%Y%m%d%H%M%S>-${slug}.org" "#+TITLE: ${title}\n")
      :immediate-finish t
      :unnarrowed t)
     ("c" "Cornell Course Note" plain
      (file "~/org/.emacs.d/org-templates/cornell-course.orgcaptmpl")
      :target (file+head "cornell/%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+TITLE: ${title}\n#+FILETAGS: :cornell:\n")
      :unnarrowed t)
     ("r" "Cornell Reading Note" plain
      (file "~/org/.emacs.d/org-templates/cornell-reading.orgcaptmpl")
      :target (file+head "cornell/%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+TITLE: ${title}\n#+FILETAGS: :cornell:\n")
      :unnarrowed t)
     ))
  (org-roam-dailies-directory "daily/")
  (org-roam-dailies-capture-templates
   '(("d" "default" entry "* %?"
      :target (file+head "%<%Y-%m-%d>.org" ":PROPERTIES:\n:ID: %(org-id-uuid)\n:END:\n#+TITLE: %<%Y-%m-%d>\n")
      :unnarrowed nil)))
  :config
  (cl-defmethod org-roam-node-directories ((node org-roam-node))
    (if-let ((dirs (file-name-directory (file-relative-name (org-roam-node-file node) org-roam-directory))))
        (format "(%s)" (car (split-string dirs "/")))
      ""))
  :preface
  (setopt org-roam-directory (file-truename (concat org-directory "/roam"))) ;定义文件存放位置
  :init
  (if (not (file-directory-p org-roam-directory))
      (make-directory org-roam-directory)) ;如果目录 org-roam-directory 不存在就创建一个
  )

3 模板系统

org-roam 的模板系统与 org-mode 的模板系统看起来很像,并不兼容。

在 org-roam 中选择结点是通过 completing-read 界面实现的,结点的呈现由 org-roam-node-display-template 变量控制。模板基本构成单元是 ${field-name:length} 这样的结构( length 是可选的),这些模板变量会被扩展(expand)为 org-roam-node-* 型的函数(或者叫做访问器),比如 ${title} 会被扩展为 org-roam-node-title 函数。根据文档,变量 org-roam-node-display-template 也可设置为一个闭包(Closure),该闭包需要返回一个有效的“模板字符串”,即其返回值还是一个包含 ${field-name:length} 这样的结构的字符串。

在设置结点显示模板变量 org-roam-node-display-template 时,我们使用了 ${directories}, ${title}${tags} ,其中 org-roam-node-titleorg-roam-node-tags 都是包里预定义的,但 org-roam-node-directories 并不存在。所以我们使用 cl-defmethod 定义了该函数以供模板调用,此函数的返回值是结点所在的目录(相对于 org-roam-directory ),用以区别不同类型的结点。

4 多媒体文件的处理

笔记只包含文本这个愿望是好的,但是有时候却不得不包含图片、照片、书籍等非文字内容的文件。怎么放置这些文件更便于使用和管理,是一个让人头疼的问题。参考 org-attach 的建议,我把 org-roam 文件目录中涉及到的媒体文件使用 org-attach 进行集中管理,为此在 /path/to/org/roam 目录下创建一个配置文件 .dir-locals.el

((org-mode . ((org-attach-id-dir . "~/org/roam/media/"))))

5 org-roam 的使用

5.1 什么是结点

在 org-roam 的语境中,结点(node)是指带有 ID 属性的标题行或文件,没有 ID 属性的标题行或文件都不是结点。比如这个官方手册上的例子,它是一个 org 文档:

:PROPERTIES:
:ID:       foo
:END:
#+title: Foo

* Bar
:PROPERTIES:
:ID:       bar
:END:

这个 org 文档本身是一个 ID 为 foo 的结点,它里面包含了一个 ID 为 bar 的结点。如果要手动添加 ID 属性,可以使用 org-id-get-create 命令,它会为标题或文件添加一个 UUID 作为标题或文件的 ID 属性值。

org-roam 在节点间使用 Org 的标准 ID 链接(例如 id:foo )。虽然计算节点间链接时仅考虑 ID 链接,但 Org-roam 会缓存文档中所有其他链接以供外部使用。

5.2 创建新结点

使用 org-roam 可以很容易创建结点并把它们链接在一起。主要用到的命令有两个: org-roam-node-insert 插入结点链接,如果结点不存在则创建一个; org-roam-node-find 打开一个结点,如果不存在则创建一个。另外, org-roam-capture 则直接创建一个结点。这几个命令都要使用 org-roam 的模板系统,如果默认模板不适用,可以自行定义模板。比如在配置文件中,我们就定义了“default”、“article”和“Cornell Note”模板用于不同笔记的创建。

5.3 结点属性

除了 org-mode 支持的属性,org-roam 还支持几个独特的属性:

5.3.1 ROAMALIAS(别名)

节点可以拥有多个别名方便搜索。例如,名为“人工智能”的节点可以分配一个广为人知的缩写(AI),还可以再增加一个 "Artificial Intelligence" 别名,注意中间有空格的要用英文引号引起来。添加别名使用 org-roam-alias-add ,删除别名用 org-roam-alias-remove ,或者也可以直接修改属性内容。

5.3.2 Tag(标签)

文件结点的标签使用 org-file-tags 文件关键字,标题结点的标签与普通的 org 文档标签相同。请注意,#+filetags 关键字会导致文件内所有标题节点继承该文件的标签,这使得选择性标签继承无法实现。

5.3.3 Refs(引用)

引用是节点的唯一标识符,这些键值可使引用在Org-roam缓冲区中显示3。例如,网站节点可使用 URL 作为引用,而论文节点则可使用 Org-ref 引文键值。

可以为单个节点指定多个引用,例如一个系列中的多篇论文共享同一注释,或某篇文章同时拥有引文键值和 URL 时,就需要多个引用。添加引用使用 org-roam-ref-add ,移除引用使用 org-roam-ref-remove

5.4 引文

在学术论文中使用参考文献笔记是常见做法。将某个节点指定为该学术论文的规范节点,我们可以使用其唯一的引用键,插入引用键可以使用 org-cite-insertorg-ref-insert-link 。它们的格式并不完全相同:

  • org-cite@thrun2005probabilistic[cite:@thrun2005probabilistic]
  • org-refcite:thrun2005probabilistic[[cite:&thrun2005probabilistic]]

当其他节点引用该键值时,我们可通过 Org-roam 缓冲区的“引用链接”部分查看。

5.5 查看关联结点

Org-roam 提供 Org-roam 缓冲区,用于查看与其他笔记之间关联关系(包括反向链接、引用链接、未链接的引用等),主要包含两个常用命令。 org-roam-buffer-toggle 启动一个 Org-roam 缓冲区,用于跟踪当前光标所在的节点,当光标移动时,缓冲区的内容会相应变化(如有必要)。 org-roam-buffer-display-dedicated 为特定节点启动 Org-roam 缓冲区,无需访问其文件,与 org-roam-buffer-toggle 不同,它可以打开多个此类缓冲区,其内容不会随光标移动而变化。

在 org-roam buffer 里,可以按 nmagit-section-forwardmagit-section-forward )移动光标到下一个结点,按 RETrg-roam-buffer-visit-thing )可以访问该结点(或者按 C-u RET 在其它窗口打开)。

5.6 设置 org-roam 处理哪些 headline

org-roam 将结点信息缓存在 SQlite 数据库里。如果不想让 org-roam 缓存某个带有 ID 属性的 headline,可以在属性里添加 :ROAM_EXCLUDE: t ,或者设置 org-roam-db-node-include-function (查看文档里的例子:What to cache):

(setq org-roam-db-node-include-function
      (lambda ()
        (not (member "ATTACH" (org-get-tags)))))

Org-roam 依赖获取的 Org AST 来解析缓冲区中的链接,某些位置出现的链接(例如属性抽屉内部)不会被 Org AST 识别为链接。如果要处理这些额外链接,可通过 org-roam-db-extra-links-elements 指定需要额外考虑的 Org AST 元素类型。另外,还可以使用 org-roam-db-extra-links-exclude-keys 排除某些键(如 ROAM_REFS ,它是指向自身的)。

5.7 导出

Org-roam 文件本质是 Org 文件,可通过 org-export 轻松导出为多种格式,包括 html 和 pdf。但 Org-roam 高度依赖 ID 链接,而 Org 的 html 导出功能对此支持不佳。为解决此问题,Org-roam 提供了一系列覆盖方案以增强导出支持,导出时只需运行以下代码即可:

(require 'org-roam-export)

5.8 全文搜索

大量的笔记,靠人脑是不可能记住某个内容是放在哪里的,所以笔记的搜索能力是非常重要的。org-roam 本身不具备全文搜索能力,它只能在 org-roam-node-find 界面进行搜索,如果使用 helm,其搜索能力来自于 helm。

如果要进行全文搜索,可以使用 grep 进行正则搜索4。也可以使用 deptnotdeftdeft 处理大量文件力不从心,而 notdeft 则使用外部工具来增强搜索能力。

5.8.1 notdeft 配置

(use-package notdeft
    :ensure t
    :quelpa (notdeft :fetcher github :repo "hasu/notdeft" :upgrade nil)
    :hook ((notdeft-load . notdeft-xapian-make-program-when-uncurrent))
    :custom
    (notdeft-directories '("~/org/roam/"))  ; 设置笔记目录
    (notdeft-extension "org")      ; 默认笔记扩展名
    (notdeft-xapian-max-results 200)        ;限制数量为200
    ;; (notdeft-xapian-program (expand-file-name
    ;;                          "xapian/notdeft-xapian"
    ;;                          (package-desc-dir
    ;;                           (cadr (assq 'notdeft package-alist)))))
    :init
    (require 'notdeft-global)
    (global-set-key [f6] 'notdeft-global-map)
    (setq notdeft-open-query-function 'notdeft-mode-open-query)
    (setenv "XAPIAN_CJK_NGRAM" "1"))

(with-eval-after-load 'org
    (require 'notdeft-org9)
    (add-hook 'org-mode-hook 'notdeft-note-mode-enable))

配置完成后,输入 M-x notdeft 或者 <f6>-e ,即可进行 notdeft 搜索界面。

5.8.2 notdeft 的两阶段搜索

运行 notdeft 命令会进入一个名为 *notdeft* 的特殊缓冲区,notdeft的查询很有特色,它分为两个步骤,先在缓冲区按 TABC-c C-o 输入过滤字符串,然后再输入查询词缩小查询范围。我们可以更改查询词重新缩小查询范围,也可以按 TAB 键更改过滤字符串,查询词则会保留。

5.8.3 notdeft 的搜索前缀

notdeft 支持 5 个查询前缀, file: (文件名,非目录非后缀), ext: (后缀), path: (路径中的非目录部分), title: (标题), tag: (标签)。同时,还支持 4 个 修改符, !time (按时间降序), !rank (按相关度降序), !file (按文件名降序), !all (显示所有匹配结果)。举例:

title:Emacs OR <file:Emacs> !all title:(Ayn AND Rand)

6 org-roam-ui

org-roam-ui 是一个为 org-roam 知识库提供图形化、交互式网络图的强大插件,它使用浏览器把整个 org-roam 构建的知识库用知识图谱的方式进行展现。虽然这并不是 org-roam 所必须的,但可以使用它来浏览发现结点之间的关联,更好理解知识结构。

(use-package org-roam-ui      ;可视化
  :defer t
  :ensure t)

注意这个包会被自动安装,但永远不会被自动加载,只能使用命令 org-roam-ui-mode 启动。

7 org-roam-bibtex

(use-package org-roam-bibtex
  :ensure t
  :after org-roam)

8 最终生成配置

;;; -*- lexical-binding: t -*-
;;; init-org-roam.el --- init for org-roam mode.
;;; Commentary:
;;; Code:

(eval-when-compile (require 'use-package))

(use-package org-roam
  :ensure t
  :hook ((after-init . (lambda ()
                         (run-with-idle-timer
                          1 nil
                          (lambda () (org-roam-db-autosync-mode 1))))))
  :bind
  (("C-c n f" . org-roam-node-find)
   ("C-c n c" . org-roam-dailies-capture-date)
   ("C-c n j" . org-roam-dailies-goto-date)
   (:map org-mode-map
         (("C-c n i" . org-roam-node-insert)
          ("C-c n o" . org-id-get-create)
          ("C-c n t" . org-roam-tag-add)
          ("C-c n a" . org-roam-alias-add)
          ("C-c n l" . org-roam-buffer-toggle)
          ("C-c n r" . org-roam-ref-add)
          )))
  :custom
  (find-file-visit-truename t)        ;org-roam不解析符号链接
  (org-roam-node-display-template (concat "${directories}${title} " (propertize "${tags}" 'face 'org-tag))) ;在选择界面节点的显示模板
  (org-roam-capture-templates     ;捕获模板
   '(("d" "default" plain "%?" :target
      (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+TITLE: ${title}\n#+DATE: \n")
      :immediate-finish t     ;建立结点后立即结束编辑
      :unnarrowed t)
     ("a" "article" plain "%?" :target
      (file+head "articles/%<%Y%m%d%H%M%S>-${slug}.org" "#+TITLE: ${title}\n")
      :immediate-finish t
      :unnarrowed t)
     ("c" "Cornell Course Note" plain
      (file "~/org/.emacs.d/org-templates/cornell-course.orgcaptmpl")
      :target (file+head "cornell/%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+TITLE: ${title}\n#+FILETAGS: :cornell:\n")
      :unnarrowed t)
     ("r" "Cornell Reading Note" plain
      (file "~/org/.emacs.d/org-templates/cornell-reading.orgcaptmpl")
      :target (file+head "cornell/%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+TITLE: ${title}\n#+FILETAGS: :cornell:\n")
      :unnarrowed t)
     ))
  (org-roam-dailies-directory "daily/")
  (org-roam-dailies-capture-templates
   '(("d" "default" entry "* %?"
      :target (file+head "%<%Y-%m-%d>.org" ":PROPERTIES:\n:ID: %(org-id-uuid)\n:END:\n#+TITLE: %<%Y-%m-%d>\n")
      :unnarrowed nil)))
  :config
  (cl-defmethod org-roam-node-directories ((node org-roam-node))
    (if-let ((dirs (file-name-directory (file-relative-name (org-roam-node-file node) org-roam-directory))))
        (format "(%s)" (car (split-string dirs "/")))
      ""))
  :preface
  (setopt org-roam-directory (file-truename (concat org-directory "/roam"))) ;定义文件存放位置
  :init
  (if (not (file-directory-p org-roam-directory))
      (make-directory org-roam-directory)) ;如果目录 org-roam-directory 不存在就创建一个
  )

(use-package notdeft
    :ensure t
    :quelpa (notdeft :fetcher github :repo "hasu/notdeft" :upgrade nil)
    :hook ((notdeft-load . notdeft-xapian-make-program-when-uncurrent))
    :custom
    (notdeft-directories '("~/org/roam/"))  ; 设置笔记目录
    (notdeft-extension "org")      ; 默认笔记扩展名
    (notdeft-xapian-max-results 200)        ;限制数量为200
    ;; (notdeft-xapian-program (expand-file-name
    ;;                          "xapian/notdeft-xapian"
    ;;                          (package-desc-dir
    ;;                           (cadr (assq 'notdeft package-alist)))))
    :init
    (require 'notdeft-global)
    (global-set-key [f6] 'notdeft-global-map)
    (setq notdeft-open-query-function 'notdeft-mode-open-query)
    (setenv "XAPIAN_CJK_NGRAM" "1"))

(with-eval-after-load 'org
    (require 'notdeft-org9)
    (add-hook 'org-mode-hook 'notdeft-note-mode-enable))

(use-package org-roam-ui      ;可视化
  :defer t
  :ensure t)

(use-package org-roam-bibtex
  :ensure t
  :after org-roam)

(provide 'init-org-roam)
;;; init-org-roam ends here

  1. 如何聪明地做笔记,Sonke Ahrens, 2017 ↩︎

  2. 从永久笔记到临时笔记的反向链接由 org-roam 自动生成 ↩︎

  3. 这句话来自于文档,含义不详 ↩︎

  4. 但在 windows 下面返回的文件名始终不对,没能解决 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值