Files
org-roam/doc/templating.md
Leo Vivier e05ee1240d (feat) cond display relative subdir in title for selection (#427)
* (feat) cond display relative subdir in title for selection

A title can now be constructed as ‘subdir/file-title’ where ‘subdir’ is the
sub-directory relative from ‘org-roam-directory’.

When ‘org-roam-title-include-subdirs’ is non-nil, ‘org-roam--extract-titles’
now outputs titles in this format.

* (fix) improve docstrings

* (feat) add var for modifying subdir-separator

* (fix) nitpick docstring

* (doc) document subdir creation in templates with :file-name

* (feat) make subdir-format accept a function

* (fix) respect docstring one-line-description

* (fix) address checkdoc errors

* (fix) also add subdirs when db--update-titles

* (feat) improve modularisation

`org-roam--extract-titles' is now reverted to its default definition, i.e. it
doesn’t format the titles.  In its stead,
`org-roam--extract-and-format-titles' is created to both extract the titles
and process all of them with `org-roam--format-title', which is a much better
way to do this.

* Refactor with ->>

* Refactor with let*

Pretty sure I got confused because I thought I needed to use the result of the
db-queryr.

* Remove useless ->>
2020-04-30 13:13:19 +02:00

5.0 KiB

Rather than creating blank files on org-roam-insert and org-roam-find-file, it is may be desirable to prefill the file with content. This may include:

  • Time of creation
  • File it was created from
  • Clipboard content
  • Any other data you may want to input manually

This requires a complex template insertion system, but fortunately, Org ships with a powerful one: org-capture. However, org-capture was not designed for such use. Org-roam abuses org-capture to some extent, extending its syntax. To first understand how org-roam's templating system works, it may be useful to look into org-capture.

Org-roam Templates

The org-roam capture template extends org-capture's template with 2 additional properties:

  1. :file-name: This is the file name template used when a new note is created. Notes can be placed in sub-directories by prepending them to the filename (sub/file-name)
  2. :head: This is the template that is inserted on initial note creation.

Org-roam Template Expansion

Org-roam's template definitions also extend org-capture's template syntax, to allow prefilling of strings. In many scenarios, org-roam--capture is passed a mapping between variables and strings. For example, during org-roam-insert, a title is prompted for. If the title doesn't already exist, we would like to create a new file, without prompting for the title again.

Variables passed are expanded with the ${var} syntax. For example, during org-roam-insert, ${title} is prefilled for expansion. Any variables that do not contain strings, are prompted for values using completing-read.

After doing this expansion, the org-capture's template expansion system is used to fill up the rest of the template. You may read up more on this on org-capture's documentation page.

For example, take the template: "%<%Y%m%d%H%M%S>-${title}", with the title "Foo". The template is first expanded into %<%Y%m%d%H%M%S>-Foo. Then org-capture expands %<%Y%m%d%H%M%S> with timestamp: e.g. 20200213032037-Foo.

All of the flexibility afforded by emacs and org-mode are available. For example, if you want to encode a UTC timestamp in the filename, you can take advantage of org-mode's %(EXP) template expansion to call format-time-string directly to provide its third argument to specify UTC.

("d" "default" plain (function org-roam--capture-get-point)
     "%?"
     :file-name "%(format-time-string \"%Y-%m-%d--%H-%M-%SZ--${slug}\" (current-time) t)"
     :head "#+TITLE: ${title}\n"
     :unnarrowed t)

Similarly, if you want to change how titles are transformed into slugs, you can override org-roam--title-to-slug. For example, to use hyphens instead of underscores:

  (defun org-roam--title-to-slug (title)
    "Convert TITLE to a filename-suitable slug. Uses hyphens rather than underscores."
    (cl-flet* ((nonspacing-mark-p (char)
                                  (eq 'Mn (get-char-code-property char 'general-category)))
               (strip-nonspacing-marks (s)
                                       (apply #'string (seq-remove #'nonspacing-mark-p
                                                                   (ucs-normalize-NFD-string s))))
               (cl-replace (title pair)
                           (replace-regexp-in-string (car pair) (cdr pair) title)))
      (let* ((pairs `(("[^[:alnum:][:digit:]]" . "-")  ;; convert anything not alphanumeric
                      ("--*" . "-")  ;; remove sequential underscores
                      ("^-" . "")  ;; remove starting underscore
                      ("-$" . "")))  ;; remove ending underscore
             (slug (-reduce-from #'cl-replace (strip-nonspacing-marks title) pairs)))
        (s-downcase slug))))

This templating system is used throughout org-roam templates.

Template examples

Here I walkthrough the default template, reproduced below.

("d" "default" plain (function org-roam--capture-get-point)
     "%?"
     :file-name "%<%Y%m%d%H%M%S>-${slug}"
     :head "#+TITLE: ${title}\n"
     :unnarrowed t)
  1. The template has short key "d". If you have only one template, org-roam automatically chooses this template for you.
  2. The template is given a description of "default".
  3. plain text is inserted. Other options include Org headings via entry.
  4. (function org-roam--capture-get-point) should not be changed.
  5. "%?" is the template inserted on each call to org-roam--capture. This template means don't insert any content, but place the cursor here.
  6. :file-name is the file-name template for a new note, if it doesn't yet exist. This creates a file at path that looks like /path/to/org-roam-directory/20200213032037-foo.org.
  7. :head contains the initial template to be inserted (once only), at the beginning of the file. Here, the title global attribute is inserted.
  8. :unnarrowed t tells org-capture to show the contents for the whole file, rather than narrowing to just the entry.

Other options you may want to learn about include :immediate-finish.