Journaling with org-mode

Regardless of whether you are into capturing personal data as part of the Quantified Self movement, or simply like to reflect on your day, I thought Emacs and org-mode would be a good approach to journaling.

This essay is almost a historical account of what I did and how I adapted over the few years of org-mode-journaling. While I’m not using every technique described, perhaps you might find the parts that you like?

Single File

I began my journal as a single file, ~/journal.org. Each day, I would add one or more events by using a capture template:1

    (setq org-capture-templates
      '(    ;; ... other templates

        ("j" "Journal Entry"
             entry (file+datetree "~/journal.org")
             "* %?"
             :empty-lines 1)

            ;; ... other templates
        ))

Depending on how you bind it (I have mine set to C-c c), I see a menu of locations where I could shoot quick notes (I’ll explain that code later).

  Select a capture template
  =========================

  [n]    General Sprint Note
  [m]    Meeting Note
  [j]    Journal Entry
  [s]    Scrum Status
  [t]    Start Sub-task w/Comments
  [c]    Close Sub-task
  -------------------------------------------
  [C]    Customize org-capture-templates
  [q]    Abort

Selecting j brings up a narrowed perspective of the my journal file, only showing where I would enter a 4th level heading:

  **** ▯

After entering the contents of the heading and the journal entry, I hit C-c C-c to return to my previous buffer.

Assuming I was beginning with an empty file, the entire journal buffer would have a first-level heading for the year, followed by a subheading for month, etc. as in:

    * 2014
    ** 2014-10 October
    *** 2014-10-18 Wednesday
    **** Pumpkin Patch Trip

         Traveled to Sauvie Island to acquire the required
         holiday decorations, but with constipated traffic, we...

Interconnections

What is a capture template? Clearly the first two strings (the j and the Journal Entry) labels the menu. The list:

  (file+datetree "~/journal.org")

Contains the file to load, and specifies the header tree I describe above (if interested, the org-mode documentation lists others). The next string, * %?, is the template, and this is probably the simplest option, as it inserts only an empty header).

An org-mode journal became a way for me to stitch my notes, blogs and other thoughts together; especially useful since my notes spanned work, personal and technical categories. So I soon appended my template with some template expansion goodies:

    (setq org-capture-templates '(
        ("j" "Journal Entry"
             entry (file+datetree "~/journal.org")
             "* Event: %?\n\n  %i\n\n  From: %a"
             :empty-lines 1)
    ))

Assuming I selected the part of my previous paragraph when I opted for a new journal entry, my journal entry would begin with:

  * Event: ▯

    An =org-mode= journal became a way for me to stitch my notes,
    blogs and other thoughts together; especially useful since my
    notes spanned work, personal and technical categories.

    From: I̲n̲t̲e̲r̲c̲o̲n̲n̲e̲c̲t̲i̲o̲n̲s̲

Where the selected text is copied into the journal (the %i marker), and a link is created to the file active when I started the entry (because of the %a). And the line, Interconnections is a clickable link to the header within that file.

One Entry Per File

While useful, one large journal file became unwieldy, so I split my monolith into separate files, one per day. I created a directory, ~/journal/, and each filename would have the day’s date.

To create a new journal file, I first created a function to create the file’s name:

  (defun get-journal-file-today ()
    "Return filename for today's journal entry."
    (let ((daily-name (format-time-string "%Y%m%d")))
      (expand-file-name (concat org-journal-dir daily-name))))

Then a simple function to load that file:

  (defun journal-file-today ()
    "Create and load a journal file based on today's date."
    (interactive)
    (find-file (get-journal-file-today)))

  (global-set-key (kbd "C-c f j") 'journal-file-today)

Since the time stamp for the file’s name was YYYYMMDD, I move back and forth between entries with a couple simple functions2, and the org-journal project lets me create the entry by selecting the day in Emacs’ calendar.

These files do not have a .org extension, but we’re talking about Emacs, so I use this code to have org-mode be the default mode for filenames consisting of nothing but numbers:

    (add-to-list 'auto-mode-alist '(".*/[0-9]*$" . org-mode))

Snippet

In case I decide to export my journal, I wanted each file to have a title with the date. Easy enough using Yasnippet:

  # -*- mode: snippet -*-
  # name: Journal Template
  # key: journal
  # --
  #+TITLE: Journal Entry- `(format-time-string "%Y-%m-%d (%A)")`

  $0

However, this inserts today’s date, and I really wanted the date associated with the file’s name:

    (defun journal-file-insert ()
      "Insert's the journal heading based on the file's name."
      (interactive)
      (when (string-match "\\(20[0-9][0-9]\\)\\([0-9][0-9]\\)\\([0-9][0-9]\\)"
                          (buffer-name))
        (let ((year  (string-to-number (match-string 1 (buffer-name))))
              (month (string-to-number (match-string 2 (buffer-name))))
              (day   (string-to-number (match-string 3 (buffer-name))))
              (datim nil))
          (setq datim (encode-time 0 0 0 day month year))
          (insert (format-time-string
                     "#+TITLE: Journal Entry- %Y-%b-%d (%A)\n\n" datim)))))

Also, I really wanted to have this information inserted automatically without having to trigger the snippet…yeah, we’re talking auto-insert.

    (add-hook 'find-file-hook 'auto-insert)
    (add-to-list 'auto-insert-alist '(".*/[0-9]*$" . journal-file-insert))

Any way I start a new journal entry, I now have a default header template.

Interconnecting the Multi-File Journal

While pulling up Emacs’ calendar or calling my journal-file-today function is good when I’m writing the fun I had on the weekend, I still want to interconnect my other org-mode files with the org-capture-template, by calling my get-journal-file-today function I already wrote:

    (setq org-capture-templates '(
        ;; ...
        ("j" "Journal Note"
             entry (file (get-journal-file-today))
             "* Event: %?\n\n  %i\n\n  From: %a"
             :empty-lines 1)
        ;; ..
    ))

Now when I am making notes in a blog entry, I can easily write about it in my journal.

Footnotes:

1

A capture template allows you to quickly insert a “section” to an org-mode file. This requires at least a new header to be inserted, so you need a string that begins with an asterisks and a space: “* ”

The rest is gravy.

2

The find-file-increment (bound to C-c f +) will load a file that is one more than the value in the current file, and since that is a journal entry of the date, it would load the next day’s journal entry.

I also have the find-file-decrement (bound to C-c f -) to go back a day.

Date: 2014 Oct 15

Created: 2020-12-23 Wed 10:09