Org-roam

A Knowledge Management System for Org-mode

about | blog | config | notes | github

org_roam_logo.png

Org-roam is a really cool piece of software thats built to emulate the feature of Roam Research using the system provided by Org-mode. The project aims to implement Zettelkasten Method through software.

1. Useful Resources

2. Configuration

2.1. Setup Dependencies

Before loading up any org-roam related things, let's import Org-mode first.

(require 'init-org)

We also import All The Icons here because we use them to create icons in our search buffer.

(require 'init-all-the-icons)

2.2. Package Install

Let's setup org-roam now.

(use-package org-roam
  :bind
  (("C-c n l" . org-roam-buffer-toggle)
   ("C-c n /" . org-roam-node-find)
   ("C-c n ?" . org-roam-ref-find)
   ("C-c n i" . org-roam-node-insert)
   ("C-c n t" . org-roam-tag-add)
   ("C-c n T" . org-roam-tag-remove)
   ("C-c n a" . org-roam-alias-add)
   ("C-c n A" . org-roam-alias-remove)
   ("C-c n r" . org-roam-ref-add)
   ("C-c n R" . org-roam-ref-remove)
   ("C-c n b" . org-roam-db-sync)
   :map org-mode-map
   ("M-i"   . completion-at-point)))

2.3. Customize Parameter

Before we use org-roam, we need to acknowledge that we are ready to use v2.

(setq org-roam-v2-ack t)

Configure various paths for org-roam.

(setq org-roam-directory "~/org/")
(setq org-roam-dailies-directory "private/journal/")
(setq org-roam-file-exclude-regexp "README.org")

We always want the database to be updated the moment any file is udpated.

(setq org-roam-db-update-method 'immediate)

Provides completion without the use of [[.

(setq org-roam-completion-everywhere t)

2.4. Start Org-roam

Let's manually start up org-roam

(org-roam-db-autosync-mode)

2.5. Properties Drawer Visibility

We can control the visibility of the propertie drawer for the org-roam nodes so they don't stick out like a sore thumb.

(defun zamlz/org-hide-properties ()
  "Hide all org-mode headline property drawers in buffer. Could be slow if it has a lot of overlays."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward
            "^ *:properties:\n\\( *:.+?:.*\n\\)+ *:end:\n" nil t)
      (let ((ov_this (make-overlay (match-beginning 0) (match-end 0))))
        (overlay-put ov_this 'display "")
        (overlay-put ov_this 'hidden-prop-drawer t))))
  (put 'org-toggle-properties-hide-state 'state 'hidden))
(defun zamlz/org-show-properties ()
  "Show all org-mode property drawers hidden by org-hide-properties."
  (interactive)
  (remove-overlays (point-min) (point-max) 'hidden-prop-drawer t)
  (put 'org-toggle-properties-hide-state 'state 'shown))
(defun zamlz/org-toggle-properties ()
  "Toggle visibility of property drawers."
  (interactive)
  (if (eq (get 'org-toggle-properties-hide-state 'state) 'hidden)
      (zamlz/org-show-properties)
    (zamlz/org-hide-properties)))

2.6. Display Template

We can update the look of the default display template used to show the results of our search through the org-roam database. The default looks pretty bad and doesn't have useful information for us to use. We'll need to define a couple of special functions using the cl-defmethod function.

Here we get the file title:

(cl-defmethod org-roam-node-filetitle ((node org-roam-node))
  "Return the file TITLE for the node."
  (org-roam-get-keyword "TITLE" (org-roam-node-file node)))

We also want the heirachy of the node too

(cl-defmethod org-roam-node-hierarchy ((node org-roam-node))
  "Return the hierarchy for the node."
  (let ((title (org-roam-node-title node))
        (olp (org-roam-node-olp node))
        (level (org-roam-node-level node))
        (filetitle (org-roam-node-filetitle node))
        (sepicon (propertize " > " 'face 'org-roam-dim)))
    (concat
     (let ((tags (seq-filter
                  (lambda (tag) (not (string= tag "ATTACH")))
                  (org-roam-node-tags node))))
       (cond ((member "CONFIG" tags) (all-the-icons-material "settings" :face 'org-roam-dim))
             ((member "CONTACTS" tags) (all-the-icons-material "person" :face 'org-roam-dim))
             (t (all-the-icons-material "list" :face 'org-roam-dim))))
     " "
     (if (> level 0) (concat filetitle sepicon))
     (if (> level 1) (concat (string-join olp sepicon) sepicon))
     title)))

The following provides which directory the node file belongs to:

(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))))
      (concat (all-the-icons-material "folder"
                                      :face 'all-the-icons-dblue
                                      :height 0.9)
              (format " %s/" (car (f-split dirs))))
    ""))

While this one will give us the backlink count.

(cl-defmethod org-roam-node-backlinkscount ((node org-roam-node))
   (let* ((count (caar (org-roam-db-query
                        [:select (funcall count source)
                                 :from links
                                 :where (= dest $s1)
                                 :and (= type "id")]
                        (org-roam-node-id node))))
          )
     (if (> count 0)
         (concat (all-the-icons-material "link"
                                         :face 'all-the-icons-red
                                         :height 0.9)
                 (format " %-3d" count))
       (all-the-icons-material "link"
                               :face 'org-roam-dim
                               :height 0.9))))

We also need to have a function that displays the tags for the file.

(cl-defmethod org-roam-node-taglist ((node org-roam-node))
  "Return the tags of the node"
  (let* ((tags (seq-filter
                (lambda (tag) (not (string= tag "ATTACH")))
                (org-roam-node-tags node))))
    (propertize (string-join tags ", ") 'face 'all-the-icons-dgreen)))

One last method to create the identifier for references

(cl-defmethod org-roam-node-refindicator((node org-roam-node))
  (let ((refs (org-roam-node-refs node)))
    (if (eq nil refs)
        (all-the-icons-material "bookmark_border" :face 'org-roam-dim)
      (all-the-icons-material "bookmark" :face 'org-roam-dim))))

Finally, update the display template.

(setq org-roam-node-display-template
      (concat
       "${directories:14}"
       "${backlinkscount:7}"
       "${hierarchy:80}"
       "${refindicator:3}"
       "${taglist:50}"
))

2.7. Capture Templates

Capture templates, not much to explain here.

(setq org-roam-capture-templates
      `(("d" "default" plain "\n%?"
         :unnarrowed t
         :if-new (file+head "notes/${slug}.org"
                            ,(concat "#+TITLE: ${title}\n"
                                     "#+SUBTITLE:\n"
                                     "#+AUTHOR: %n (%(user-login-name))\n"
                                     "#+CREATED: %U\n"
                                     "#+LAST_MODIFIED: %U\n"
                                     "#+FILETAGS:\n")))
        ("c" "config" plain "\n%?"
         :unnarrowed t
         :if-new (file+head "config/${slug}.org"
                            ,(concat "#+TITLE: ${title}\n"
                                     "#+SUBTITLE:\n"
                                     "#+AUTHOR: %n (%(user-login-name))\n"
                                     "#+CREATED: %U\n"
                                     "#+LAST_MODIFIED: %U\n"
                                     "#+FILETAGS: :config:\n")))
        ("b" "blog" plain "\n%?"
         :unnarrowed t
         :if-new (file+head "blog/${slug}.org"
                            ,(concat "#+TITLE: ${title}\n"
                                     "#+SUBTITLE:\n"
                                     "#+AUTHOR: %n (%(user-login-name))\n"
                                     "#+CREATED: %U\n"
                                     "#+LAST_MODIFIED: %U\n"
                                     "#+FILETAGS: :blog:\n")))
        ("p" "private")
        ("pp" "notes" plain "\n%?"
         :unnarrowed t
         :if-new (file+head "private/${slug}.org"
                            ,(concat "#+TITLE: ${title}\n"
                                     "#+SUBTITLE:\n"
                                     "#+AUTHOR: %n (%(user-login-name))\n"
                                     "#+CREATED: %U\n"
                                     "#+LAST_MODIFIED: %U\n"
                                     "#+FILETAGS:\n")))
        ("pd" "pathfinder" plain "\n%?"
         :unnarrowed t
         :if-new (file+head "private/pathfinder/${slug}.org"
                            ,(concat "#+TITLE: ${title}\n"
                                     "#+SUBTITLE:\n"
                                     "#+AUTHOR: %n (%(user-login-name))\n"
                                     "#+CREATED: %U\n"
                                     "#+LAST_MODIFIED: %U\n"
                                     "#+FILETAGS: :pathfinder:\n")))
        ))

2.8. Feature Provide

(provide 'init-org-roam)

Created: 2021-11-13

Emacs 26.1 (Org mode 9.5)