One of my goals for 2020 was to try zettelkasten for managing my notes. I've experimented with various approaches in the past: paper notes in folders (and all over my desk), self-hosted wikis, and custom software like OneNote or Notion.

A few years ago I started using LionWiki to manage a small personal knowledge base. It was somewhat successful, but it didn't quite fit in with my daily workflow. It also required a network connection and browser to be open, and re-organizing notes wasn't quite as quick as I would like. I really like it as a lightweight wiki solution, but for personal notes I wanted something more.

Eventually I switched to deft, an Emacs-based tool for organizing notes that is based on Notational Velocity. This summer I evaluated a number of other zettelkasten tools, but I settled on Zetteldeft as it integrated well with my existing set of notes.

zetteldeft works alongside deft and adds support for special linking syntax, as well as a slightly different naming scheme for notes (timestamp followed by the title).

Zetteldeft open and ready to search
Figure 1: Click to see deft's search in action

There are a few things I really like about deft/zetteldeft's approach:

My zetteldeft configuration

The following code lives in my Emacs init.el. It sets up deft and zetteldeft, along with a bunch of keyboard shortcuts.

One additional change I made is binding C-c z p to sodaware/deft-open-preview. This allows me to open notes in a preview window without the list losing keyboard focus.

I use file-truename when setting my notes path as zetteldeft had some problems locating my notes when using a relative path.

(use-package deft
  :bind
  (("C-c d" . deft))
  :custom
  ;; Set deft path to full path so that zetteldeft works.
  (deft-directory         (file-truename "~/Documents/notes"))
  (deft-extensions        '("md" "org"))
  (deft-default-extension "org")
  (deft-recursive         t))

(use-package zetteldeft
  :bind
  ("C-c z d" . deft)
  ("C-c z R" . deft-refresh)
  ("C-c z D" . zetteldeft-deft-new-search)
  ("C-c z s" . zetteldeft-search-at-point)
  ("C-c z c" . zetteldeft-search-current-id)
  ("C-c z f" . zetteldeft-follow-link)
  ("C-c z F" . zetteldeft-avy-file-search-ace-window)
  ("C-c z l" . zetteldeft-avy-link-search)
  ("C-c z t" . zetteldeft-avy-tag-search)
  ("C-c z T" . zetteldeft-tag-buffer)
  ("C-c z i" . zetteldeft-find-file-id-insert)
  ("C-c z I" . zetteldeft-find-file-full-title-insert)
  ("C-c z o" . zetteldeft-find-file)
  ("C-c z n" . zetteldeft-new-file)
  ("C-c z N" . zetteldeft-new-file-and-link)
  ("C-c z p" . sodaware/deft-open-preview)
  ("C-c z r" . zetteldeft-file-rename)
  ("C-c z x" . zetteldeft-count-words)
  :config
  (defun sodaware/deft-open-preview ()
    (interactive)
    (deft-open-file-other-window))

  (font-lock-add-keywords
   'org-mode
   `((,zetteldeft-id-regex  . font-lock-warning-face)
     (,zetteldeft-tag-regex . font-lock-warning-face))))

Additional changes: the zdlink protocol

This is one of my favourite changes - it adds a new org-mode protocol, zdlink, which allows me to insert an org-style link to any note via C-c C-l. These links can be opened like any other org-mode link, and because they only include the note name (not the full path) they work across machines.

This isn't very useful if you're using markdown, but for pure org it makes linking and navigating much easier.

;; Add custom `zdlink` to handle zettledeft links.
(eval-after-load 'org
  (lambda ()
    (require 'zetteldeft)
    (org-link-set-parameters
     "zdlink"
     :follow
     (lambda (str) (zetteldeft--search-filename
		    (zetteldeft--lift-id str)))
     :complete  #'sodaware/zd-complete-link
     :help-echo "Searches provided ID in Zetteldeft")))

(defun sodaware/zd-complete-link ()
  "Link completion for `zdlink' type links."
  (let* ((file (completing-read "File to link to: "
				(deft-find-all-files-no-prefix)))
	 (link (zetteldeft--lift-id file)))
    (unless link (user-error "No file selected"))
    (concat "zdlink:" link)))

Additional feature: zetteldeft homepage

Note: This behaviour is now part of zetteldeft core. A home note can be assigned via the zetteldeft-home-id variable, and then accessed with M-x zetteldeft-go-home.

This is a small change that allows me to open a specific note file with a keyboard shortcut. This is useful when keeping a single note that acts as a gateway to all others.

I prefer the main search buffer for finding notes, but I keep one note that lists my main projects and links to their notes.

(defun sodaware/zd-homepage ()
  "Open Zetteldeft home file."
  (interactive)
  (zetteldeft-find-file "2020-07-10-0959 Home.org"))
(global-set-key (kbd "C-c z h") #'sodaware/zd-homepage)

Additional feature: .dir-locals.el config

This is a really simple .dir-locals.el file that lives in my main notes directory. It adds 3 features:

  • emojify-mode support – Sometimes I see fancy notion setups and want to spice up my buffers with some emoji. I don't, but with emojify-mode I could.
  • whitespace-mode - This removes trailing whitespace when I save notes.
  • Expands notes on opening – I normally have org-mode headlines collapsed as I use it for organizing my todo lists, but for notes I like to see the entire document when I open it.
((org-mode . ((mode . emojify)
	      (mode . whitespace-cleanup)
	      (eval . (outline-show-all)))))