Journaling with org-mode
I began journaling with org-mode after the Day One app was acquired by Automattic. I started with the org-journal package, then migrated to org-roam dailies and later switched to my own code. Here’s how and why.
My journaling workflow begins with one file/journal per day. I open the journal buffer throughout the day and log the current activity with a timestamp. I end the day logging in my closing thoughts. Every day, I also like to look at entries for the month and day from past years. This is the format and structure of a typical Journal file I use:
#+title: 2021-01-31
#+date: 2021-01-31
* Log
- 06:00 ....
- 11:42 ....
- 17:30 ....
- 23:42 ....
* Reflections
The quick brown fox jumps over the lazy dog.
org-journal and org-capture fit into my workflow perfectly. But searching using org-journal was painfully slow (I blame ripgrep for setting the bar up so high).
When org-roam came bundled with the dailies feature, I immediately switched to that. Seamlessly integrating knowledge management with journaling sounded too good. Fast-forward a few months, I realized it was not my cup of tea. There was a lot of noise in the graph from dailies, and the value added was minimal.
My journaling needs are far more straightforward than what org-journal and org-roam offered. So I hacked up what I wanted in a few lines of Emacs lisp.
(defvar maze-notes-templates-directory "/Volumes/Encrypted/templates/default-daily.org")
(defvar maze-notes-journal-directory "/Volumes/Encrypted/journal")
(defun maze-notes-template-content (template)
(f-read-text (expand-file-name template maze-notes-templates-directory)))
(defun maze-journal-new-journal-template ()
(maze-notes-template-content "default-daily.org"))
(defun maze-journal-filename (offset)
"RETURN file name for journal OFFSET days from today. OFFSET needs to be an integer."
(if (integerp offset)
(format-time-string "%Y-%m-%d.org" (+ (float-time) (* 86400 offset)))
(error "Invalid offset value '%s'. Expecting an integer" offset)))
(defun maze-journal-create-or-open (journal)
"Visits JOURNAL if file exists or creates a new buffer with the result of `maze-journal-new-journal-template'."
(find-file (expand-file-name journal maze-notes-journal-directory))
(when (equal 1 (point-max))
(insert (format-time-string (maze-journal-new-journal-template)))
(goto-char (point-max))))
(defun maze/journal-goto-today ()
"Open today's journal."
(interactive)
(maze-journal-create-or-open (maze-journal-filename 0)))
(defun maze/journal-goto-yesterday ()
"Open yesterday's journal."
(interactive)
(maze-journal-create-or-open (maze-journal-filename -1)))
(defun maze/journal-goto-tomorrow ()
"Open tomorrow's journal."
(interactive)
(maze-journal-create-or-open (maze-journal-filename 1)))
(defun maze/journal-goto-date ()
"Read a date and open journal for that particular date."
(interactive)
(maze-journal-create-or-open (format "%s.org" (org-read-date))))
(defun maze-open-retrospective-for-glob (pattern)
"Open a dired buffer filtering journals on PATTERN"
(find-name-dired maze-notes-journal-directory pattern))
(defun maze/journal-retrospect-today ()
"Open a dired buffer to revisit journals written on this month and day in years past."
(interactive)
(maze-open-retrospective-for-glob (format-time-string "*-%m-%d.org")))
(defun maze/journal-retrospect-date ()
"Open a dired buffer to revisit journals written on a month,day."
(interactive)
(let* ((date (parse-time-string (org-read-date)))
(month (nth 4 date))
(day (nth 3 date)))
(maze-open-retrospective-for-glob (format "*-%d-%d.org" month day))))
All the journals are in maze-notes-journal-directory
. maze/journal-goto-*
interactive functions open the respective journal entries. maze/journal-retrospect-*
class of functions open a Dired buffer with all the journals written on this day.
The org-capture template below adds the functionality to add entries under the ‘Log’ outline path.
(defun maze-journal-append-to-todays-log-olp()
"Opens today's journal and places `point' at the content-end of 'Log' outline path."
(maze-journal-create-or-open (maze-journal-filename 0))
(goto-char (point-min))
(save-match-data
(when (re-search-forward (format org-complex-heading-regexp-format (regexp-quote "Log")) nil t)
(let ((point (org-element-property :contents-end (org-element-at-point))))
(if point
(goto-char point)
(insert "\n"))))))
(setq org-capture-templates
'(("l" "Log" plain (function maze-journal-append-to-todays-log-olp)
"- %<%H:%M> %?")))
I have been using this bare-bones solution for the past month and like how it fits into my Emacs setup. Searches are blazing fast using ripgrep, and I have the exact functionality I need.
A note on encryption: Org-mode transparently handles encrypting and decrypting files ending in org.gpg
extension. Having faced several issues over the years with pinentry and caching on Mac OS, I chose not to encrypt each journal. Instead, I use Cryptomator to mount an encrypted volume and store all the files in plain text on that encrypted volume.