Background

My notes database consists primarily of Emacs Org mode files, interspersed with a small number of markdown files, some of them from previous note-taking systems (for example, I went through a Gollum stage early in 2014, according to my notes then), and some of them for easier mobile consumption and production.

I recently discovered nobiot’s md-roam Emacs package which makes it possible for these markdown files to show up (in sheep’s clothes, as it were) amongst all of my usual org-roam nodes.

To demonstrate the unified org / markdown node completion (note that it’s listing org-roam nodes in this markdown file), I’ve selected a choice note from 10 years ago, in which I was already trying to find a better mobile note-taking solution:

However, md-roam requires each markdown file to have a ---\n...\n----delineated yaml section at the start with at a minimum the note’s title and ID.

I thought that it would be much more convenient if md-roam could use the first non-empty line of text as the note title, the filename as ID and as a bonus, extract hashtags from anywhere in the file.

This would also make it much easier to author these files on my mobile device using the 1writer app, and many others like it which follow the same title and hashtag conventions.

General idea

Thanks to the magic of Emacs Lisp, it was not too tricky to override a number of md-roam functions to make exactly this happen.

We use advice to override a number of md-roam-... functions to extract the title and id we want, and more subtly we modify md-roam-get-yaml-front-matter-endpoint in two different ways, first to support generally frontmatter-less operation, and second to extract tags from everywhere.

Once implemented (see the code below), almost any old markdown file in your org-roam directory will be indexed by org-roam, and available for linking and viewing via the usual org-roam functions.

The code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
(defun cpb/get-first-line ()
  "Get first non-empty line from the current buffer"
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (if (re-search-forward "[a-zA-Z]")
        ;; go back 1 char as re-search-point leaves point to the right of first hit
        (buffer-substring-no-properties (1- (point)) (line-end-position)))))

(defun cpb/md-roam-get-id ()
  "Return basename+ext of currently visited file, to be used as org-roam ID."
  (when-let* ((bfn (buffer-file-name)) ;; only if we are visiting a file
              (ext (file-name-extension bfn))) ;; and it has an extension
    ;; and ext is markdown, then we use the basename+ext
    (if (string= ext "md") (file-name-nondirectory(buffer-file-name)))))

;; thanks to nobiot we can include md files with our org-roam database!
;; as per the docs https://github.com/nobiot/md-roam#basic-configuration
;; NB: md-roam-mode should be active before org-roam-db-autosync-mode
(use-package md-roam
  :quelpa (md-roam :fetcher github :repo "nobiot/md-roam")
  :config
  (setq org-roam-file-extensions '("org" "md"))
  (md-roam-mode 1)

  ;; the first line with characters in the file becomes the title
  ;; (a heading will also work)
  (advice-add #'md-roam-get-title :override #'cpb/get-first-line)
  ;; use the basename+ext as the ID -- if you're using ZK-style timestamps, these should be unique
  (advice-add #'md-roam-get-id :override #'cpb/md-roam-get-id)
  ;; many md-roam functions require and assume front-matter and want to do stuff
  ;; right after, in body of file. We don't want front-matter, so we substitute
  ;; with start of buffer which is also start of body in our case!
  (advice-add #'md-roam-get-yaml-front-matter-endpoint :override #'point-min)
  ;; however, only for get-tags, we want to override the front-matter endpoint to be the end of the buffer
  ;; as md-roam usually searches only in the frontmatter, but we want it to search the whole buffer
  ;; #tags everywhere!
  ;; 1. define around function
  (defun get-tags-whole-buffer (orig-get-tags &rest args)
    (cl-letf (((symbol-function 'md-roam-get-yaml-front-matter-endpoint) #'point-max))
      (apply orig-get-tags args)))
  ;; 2. install it around the md-roam-get-tags function
  (advice-add #'md-roam-get-tags :around #'get-tags-whole-buffer))

Remember that you should also set (setq markdown-enable-wiki-links t) if you want wiki-style [[...]] links to support opening, and auto-completion.