A few years ago I came across the Johnny.Decimal system to organise your life and I find it really useful to organise stuff at work. An important part of the concept is the index or JDex. Johnny recommends storing it in a notes app and has examples for Bear and Obsidian. I prefer Emacs and Orgmode. Of course Emacs makes it easy to streamline the workflow.
This post describes my configuration and workflow.
The index files
Each index entry is a separate org file. These files live in the directory
00-09_System/00_Meta/00.00_Index and I set this as my org-directory. I only use a
small subset in my org-agenda though to keep the agenda fast.
Entry content
Each file includes the following metadata:
- the title of the entry as in-buffer setting TITLE
- the date of the last change as in-buffer setting DATE
- a Properties drawer that contains
- the area and category of the entry
- storage information. It might be
- just notes in this org file. This is not only useful to find notes on a topic but I also use org-babel to store snippets of code there for example Azure cli commands and its output. For files stored in Sharepoint or other online storage I also document the relevant links in the file.
- files in the file system. This may be either my local laptop or files in customer systems. This is crucial to keep an overview of what is where.
The Emacs Time Stamps functionality updates the date of last change automatically upon each save.
I use a .dir-locals.el file to set the time-stamp pattern to match the orgmode DATE:
((org-mode
(time-stamp-active . t)
(time-stamp-start . "#\\+DATE: \\[")
(time-stamp-end . "\\]$")
(time-stamp-format . "%Y-%m-%d %a %H:%M")))Accessing the index
The simplest way to access the JDex is simply dired. I prefer Deft for Emacs due to its
nice search options.
I want readable filenames deft-use-filename-as-title is nil but
deft-use-filter-string-for-filename is non-nil: In this case the title shown is parsed
from the first line of the file while also generating readable filenames for new files
based on the filter string. That also gets added as a title into the file.
deft-file-naming-rules convert slashes and spaces in the filename to “_”.
deft provides a summary and I want to see the meta data but I don’t want to see the
strings “:PROPERTIES:” and “END”. The regexp in deft-strip-summary-regexp achieves that.
In the summary I don’t want to see org links only their titles using code from SqrtMinusOne Emacs config.
The relevant config looks like this:
(defconst deft-directory "~Documents/00-09_System/00_Meta/00.00_Index/")
(use-package deft
:config
(setq deft-default-extension "org"
deft-extensions '("org")
deft-use-filename-as-title nil
deft-use-filter-string-for-filename t
deft-current-sort-method 'title
deft-auto-save-interval 0
deft-org-mode-title-prefix t
deft-strip-summary-regexp
(rx (or
(: ":PROPERTIES:")
(: ":END:")
(: "#+" (+ alnum) ":" (* nonl))
(regexp "[\n\t]")))
deft-file-naming-rules
'((noslash . "_")
(nospace . "_")
(case-fn . capitalize))))
(defun my-deft-parse-summary-around (fun contents title)
(funcall fun (org-link-display-format contents) title))
(with-eval-after-load 'deft
(advice-add #'deft-parse-summary :around #'my-deft-parse-summary-around))Creating new entries
Initially I added are and category by hand or via a snippet but then decided to populate this automatically. The code only works for adding new ids in an existing category which is fine for me. I add new category rarely and then create the standard zeros with a separate script.
I use two main ways to create new entries
From deft
This happens if I look at the index and realise that I need a new id.
In this case I enter the new id and title as a search term and have deft create the
new org file. I have advised deft-complete to add the metadata automatically.
With a custom function that prepares everything
This is the approach if I start working on something new and already know I need a new id. I enter the category and the function determines the next id and populates the org file accordingly.
The code
I want the metadata added automated if I create the new entry. This function evaluates existing keys. I use it to derive the area and category .
(defun my-get-jd-value-from-file (jdid key)
"From JDID construct the corresponding index file and return the value
associated with KEY in the properties drawer."
(message "%s" jdid)
(message "%s" (car (split-string jdid "\\.")))
(message "%s" key)
(let ((jd (car (split-string jdid "\\.")))
(filepath (concat (car (split-string jdid "\\.")) ".00_Index.org")))
(if (file-exists-p filepath)
(with-current-buffer (find-file-noselect filepath)
(org-with-wide-buffer
(let (found-value)
(org-element-map (org-element-parse-buffer) 'drawer
(lambda (drawer)
(when (string-equal (org-element-property :drawer-name drawer)
"PROPERTIES")
(let ((contents (buffer-substring-no-properties
(org-element-property :contents-begin drawer)
(org-element-property :contents-end drawer))))
(when (string-match (format "^[ \t]*:?%s:[ \t]*\\(.*\\)$"
(regexp-quote key))
contents)
(setq found-value (string-trim (match-string 1 contents)))))))
nil t) ;; Stop at the first match
found-value)))
(message "File not found: %s" filepath))))If I create a new entry via deft I use an advice to add the properties drawer. The
function makes use of my-get-jd-value-from-file to get the area and category names.
(defun deft-add-jd ()
"Insert the JD metadata when creating a new entry in deft."
(goto-line 2)
(insert (concat "#+DATE: " (format-time-string "<%Y-%m-%d %a %H:%M>") "\n"))
(insert ":PROPERTIES:\n")
(insert (concat ":Area: " (my-get-jd-value-from-file (car (split-string (file-name-base buffer-file-name) "_")) "Area") "\n"))
(insert (concat ":Category: " (my-get-jd-value-from-file (car (split-string (file-name-base buffer-file-name) "_")) "Category") "\n"))
(insert (concat ":STORE: notes\n"))
(insert ":END:\n"))
(advice-add 'deft-complete :after #'deft-add-jd)This function calculates the next “free” JDID and creates an entry file. I use C-c j as a
keybinding for it.
(defun my-get-next-jdex-entry (ac title)
"Return the next idex entry for category AC with TITLE from the jdex in deft-directory.
Blanks in TITLE are replaced with _. Standard zeros are ignored the minimum id is 10."
(interactive "sAC: \nstitle: ")
(let* ((dir deft-directory)
(pattern (concat ac "\\.[1-9][1-9]*"))
(jid (+ 10 (length (directory-files dir nil pattern t))))
(filetitle (concat dir ac "."(number-to-string jid) "_" (string-replace " " "_" title) ".org"))
(buf (get-buffer-create filetitle)))
(with-current-buffer buf
(insert (concat "#+TITLE: " ac "." (number-to-string jid) " " title "\n"))
(write-file filetitle)
(deft-add-jd)
(switch-to-buffer buf)
(goto-char (point-max)))))
(global-set-key (kbd "C-c j") 'my-get-next-jdex-entry)
Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.