Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
org-roam configuration
;; See
;; for details on this configuration.
;; See
;; for a walk through of the implementation.
;; A Property List of my `org-roam' capture templates.
(setq jnf/org-roam-capture-templates-plist
'("h" "Hesburgh Libraries" plain "%?"
"#+title: ${title}\n#+FILETAGS: :hesburgh: %^G\n\n")
:unnarrowed t)
'("j" "JF Consulting" plain "%?"
"#+title: ${title}\n#+FILETAGS: :personal:jeremy-friesen-consulting: %^G\n\n")
:unnarrowed t)
'("p" "Personal" plain "%?"
"#+title: ${title}\n#+FILETAGS: :personal: %^G\n\n")
:unnarrowed t)
'("P" "Personal (Encrypted)" plain "%?"
"#+title: ${title}\n#+FILETAGS: :personal:encrypted: %^G\n\n")
:unnarrowed t)
'("u" "Public" plain "%?"
"#+title: ${title}\n#+FILETAGS: :public: %^G\n\n")
:unnarrowed t)
'("t" "Thel Sector" plain "%?"
"#+title: ${title}\n#+FILETAGS: :thel-sector: %^G\n\n")
:unnarrowed t)
;; A plist that contains the various org-roam subject. Each subject
;; has a plist of :templates, :title, :name, :path-to-todo, :prefix,
;; and :group.
;; The :templates defines the ;; named templates available for this subject. See
;; `jnf/org-roam-capture-templates-plist' for list of valid templates.
;; The :name is the string version of the subject, suitable for
;; creating function names.
;; The :title is the human readable "title-case" form of the subject.
;; The :group is for `pretty-hydra-define+'
;; The :prefix helps with menu key build for `pretty-hydra-define+'
;; The :path-to-todo is the path to the todo file for this subject.
(setq jnf/org-roam-capture-subjects-plist
;; The :all subject is different from the other items.
:all (list
;; Iterate through all registered capture templates and
;; generate a list
:templates (-non-nil (seq-map-indexed (lambda (template index)
(when (evenp index) template))
:name "all"
:title "All"
:group "All"
:prefix "a"
:path-to-todo "~/git/org/")
:jf-consulting (list
:templates (list :jf-consulting)
:name "jf-consulting"
:title "JF Consulting"
:group "Projects"
:prefix "j"
:path-to-todo "~/git/org/jeremy-friesen-consulting/")
:hesburgh-libraries (list
:templates (list :hesburgh-libraries)
:name "hesburgh-libraries"
:title "Hesburgh Libraries"
:group "Projects"
:prefix "h"
:path-to-todo "~/git/org/hesburgh-libraries/")
:personal (list
:templates (list :personal :personal-encrypted)
:name "personal"
:title "Personal"
:group "Life"
:prefix "p"
:path-to-todo "~/git/org/personal/")
:public (list
:templates (list :public)
:name "public"
:title "Public"
:group "Life"
:prefix "u"
:path-to-todo "~/git/org/public/")
:thel-sector (list
:templates (list :thel-sector)
:name "thel-sector"
:title "Thel Sector"
:group "Projects"
:prefix "t"
:path-to-todo "~/git/org/personal/thel-sector/")
(cl-defun jnf/org-roam-templates-for-subject (subject
(subjects-plist jnf/org-roam-capture-subjects-plist)
(template-definitions-plist jnf/org-roam-capture-templates-plist))
"Return a list of `org-roam' templates for the given SUBJECT.
Use the given (or default) SUBJECTS-PLIST to fetch from the
(let ((templates (plist-get (plist-get subjects-plist subject) :templates)))
(-map (lambda (template) (plist-get template-definitions-plist template))
;; A menu of common tasks for `org-roam'. This menu is for all subjects.
;; Note the convention:
;; * @ - for todo
;; * + - for capture
;; * ! - for insert
;; * ? - for find
;; The `create-org-roam-subject-fns-for' presupposes those keys for
;; narrowed subjects.
(defvar jnf/org-subject-menu--title (with-faicon "book" "Org Subject Menu" 1 -0.05))
(pretty-hydra-define jnf/org-subject-menu--all (:foreign-keys warn :title jnf/org-subject-menu--title :quit-key "q" :exit t)
;; Note: This matches at least one of the :groups in `jnf/org-roam-capture-subjects-plist'
;; Note: This matches at least one of the :groups in `jnf/org-roam-capture-subjects-plist'
"Org Mode"
(("@" (lambda ()
(find-file (file-truename (plist-get (plist-get jnf/org-roam-capture-subjects-plist :all) :path-to-todo))))
("+" jnf/org-roam--all--capture "Capture…")
("!" jnf/org-roam--all--node-insert " ├─ Insert…")
("?" jnf/org-roam--all--node-find " └─ Find…")
("/" org-roam-buffer-toggle "Toggle Buffer")
("#" jnf/toggle-roam-subject-filter "Toggle Default Filter")
(cl-defmacro create-org-roam-subject-fns-for (subject
(subjects-plist jnf/org-roam-capture-subjects-plist))
"Define the org roam SUBJECT functions and create & update hydra menus.
The functions are wrappers for `org-roam-capture',
`org-roam-node-find', `org-roam-node-insert', and `find-file'.
Create a subject specific `pretty-define-hydra' and append to the
`jnf/org-subject-menu--all' hydra via the `pretty-define-hydra+'
Fetch the given SUBJECT from the given SUBJECTS-PLIST."
(let* ((subject-plist (plist-get subjects-plist subject))
(subject-as-symbol subject)
(subject-title (plist-get subject-plist :title))
(subject-name (plist-get subject-plist :name))
;; For todo related antics
(todo-fn-name (intern (concat "jnf/find-file--" subject-name "--todo")))
(path-to-todo (plist-get subject-plist :path-to-todo))
(todo-docstring (concat "Find the todo file for " subject-name " subject."))
;; For hydra menu related antics
(hydra-fn-name (intern (concat "jnf/org-subject-menu--" subject-name)))
(hydra-menu-title (concat subject-title " Subject Menu"))
(hydra-todo-title (concat subject-title " Todo…"))
(hydra-group (plist-get subject-plist :group))
(hydra-prefix (plist-get subject-plist :prefix))
(hydra-kbd-prefix-todo (concat hydra-prefix " @"))
(hydra-kbd-prefix-capture (concat hydra-prefix " +"))
(hydra-kbd-prefix-insert (concat hydra-prefix " !"))
(hydra-kbd-prefix-find (concat hydra-prefix " ?"))
;; For `org-roam-capture' related antics
(capture-fn-name (intern (concat "jnf/org-roam--" subject-name "--capture")))
(capture-docstring (concat "As `org-roam-capture' but scoped to " subject-name
".\n\nArguments GOTO and KEYS see `org-capture'."))
;; For `org-roam-insert-node' related antics
(insert-fn-name (intern (concat "jnf/org-roam--" subject-name "--node-insert")))
(insert-docstring (concat "As `org-roam-insert-node' but scoped to " subject-name " subject."))
;; For `org-roam-find-node' related antics
(find-fn-name (intern (concat "jnf/org-roam--" subject-name "--node-find")))
(find-docstring (concat "As `org-roam-find-node' but scoped to "
subject-name " subject."
"\n\nArguments INITIAL-INPUT and OTHER-WINDOW are from `org-roam-find-mode'."))
(defun ,todo-fn-name ()
(find-file (file-truename ,path-to-todo)))
(defun ,capture-fn-name (&optional goto keys)
(interactive "P")
(org-roam-capture goto
:filter-fn (lambda (node) (-contains-p (org-roam-node-tags node) ,subject-name))
:templates (jnf/org-roam-templates-for-subject ,subject-as-symbol)))
(defun ,insert-fn-name ()
(org-roam-node-insert (lambda (node) (-contains-p (org-roam-node-tags node) ,subject-name))
:templates (jnf/org-roam-templates-for-subject ,subject-as-symbol)))
(defun ,find-fn-name (&optional other-window initial-input)
(interactive current-prefix-arg)
(org-roam-node-find other-window
(lambda (node) (-contains-p (org-roam-node-tags node) ,subject-name))
:templates (jnf/org-roam-templates-for-subject ,subject-as-symbol)))
;; Create a hydra menu for the given subject
(pretty-hydra-define ,hydra-fn-name (:foreign-keys warn :title jnf/org-subject-menu--title :quit-key "q" :exit t)
("@" ,todo-fn-name ,hydra-todo-title)
("+" ,capture-fn-name " ├─ Capture…")
("!" ,insert-fn-name " ├─ Insert…")
("?" ,find-fn-name " └─ Find…")
("/" org-roam-buffer-toggle "Toggle Buffer")
("#" jnf/toggle-roam-subject-filter "Toggle Filter…")
;; Append the following menu items to the `jnf/org-subject-menu--all'
(pretty-hydra-define+ jnf/org-subject-menu--all()
(,hydra-kbd-prefix-todo ,todo-fn-name ,hydra-todo-title)
(,hydra-kbd-prefix-capture ,capture-fn-name " ├─ Capture…")
(,hydra-kbd-prefix-insert ,insert-fn-name " ├─ Insert…")
(,hydra-kbd-prefix-find ,find-fn-name " └─ Find…")
;; I tried using a dolist to call each of the macros, but that didn't
;; work. I'd love some additional help refactoring this. But for
;; now, what I have is quite adequate. It would be nice to
;; more programatically generate the hydra menus (see below).
(create-org-roam-subject-fns-for :personal)
(create-org-roam-subject-fns-for :public)
(create-org-roam-subject-fns-for :hesburgh-libraries)
(create-org-roam-subject-fns-for :jf-consulting)
(create-org-roam-subject-fns-for :thel-sector)
;; Including the aliases to reduce switching necessary for re-mapping
;; keys via `jnf/toggle-roam-subject-filter'.
(defalias 'jnf/org-roam--all--node-insert 'org-roam-node-insert)
(defalias 'jnf/org-roam--all--node-find 'org-roam-node-find)
(defalias 'jnf/org-roam--all--capture 'org-roam-capture)
(cl-defun jnf/subject-list-for-completing-read (&key
"Create a list from the SUBJECTS-PLIST for completing read.
The form should be '((\"all\" 1) (\"hesburgh-libraries\" 2))."
;; Skipping the even entries as those are the "keys" for the plist,
;; the odds are the values.
(-non-nil (seq-map-indexed (lambda (subject index)
(when (oddp index) (list (plist-get subject :name) index)))
(defun jnf/toggle-roam-subject-filter (subject)
"Prompt for a SUBJECT, then toggle the 's-i' kbd to filter for that subject."
(interactive (list
"Project: " (jnf/subject-list-for-completing-read))))
(kbd "C-s-1")
(intern (concat "jnf/org-roam--" subject "--node-insert")))
(kbd "C-s-=")
(intern (concat "jnf/org-roam--" subject "--capture")))
(kbd "C-s-/")
(intern (concat "jnf/org-roam--" subject "--node-find")))
(kbd "s-i")
(intern (concat "jnf/org-roam--" subject "--node-insert")))
(kbd "C-c i")
(intern (concat "jnf/org-subject-menu--" subject "/body"))))
;; With the latest update of org-roam, things again behavior
;; correctly. Now I can just load org-roam as part of my day to day
(use-package org-roam
:straight t
(org-roam-directory (file-truename "~/git/org"))
;; Set more spaces for tags; As much as I prefer the old format,
;; this is the new path forward.
(org-roam-node-display-template "${title:*} ${tags:40}")
(org-roam-capture-templates (jnf/org-roam-templates-for-subject :all))
;; Help keep the `org-roam-buffer', toggled via `org-roam-buffer-toggle', sticky.
(add-to-list 'display-buffer-alist
(side . right)
(slot . 0)
(window-width . 0.33)
(window-parameters . ((no-other-window . t)
(no-delete-other-windows . t)))))
(setq org-roam-completion-everywhere t)
(setq org-roam-v2-ack t)
;; Configure the "all" subject key map
(jnf/toggle-roam-subject-filter "all"))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment