Backlinking and connected thinking tools have seen a hype in 2020. But all tools come with caveats and design decisions from other people. Emacs seems to be the wisest choice for a platform to build a malleable tool. With org-mode, a near perfect text-based local-first note-taking solution already exists.
The automatic back-linking offered by programs such as RoamResearch, RemNote, and Obsidian is a very enticing feature which can be replicated in principle by a recursive grep. org-roam replicates much of RoamResearch based on support by a sqlite database. But its implementation is not stable, yet, and design decisions such as one note per file and the backlinking sidebar may not be to everybody’s liking.
The proposed workflow of pk-backlinking
is to pick-up the custom-id
of the current headline and search references to it (recursively) in
the working directory. The references are listed under a new
sub-headline called ‘Backlinks’. The mindset behind this workflow is
that of telling oneself “Please show me all headlines referenceing back
to the current headline.”
Add all backlinks to a headline under a new sub-headline called ‘Backlinks’.
(use-package pk-bl
:load-path "path/to/package/pk-backlinking")
Ensure that the following has been loaded during initialization:
(defun eos/org-custom-id-get (&optional pom create prefix)
"Get the CUSTOM_ID property of the entry at point-or-marker POM.
If POM is nil, refer to the entry at point. If the entry does
not have an CUSTOM_ID, the function returns nil. However, when
CREATE is non nil, create a CUSTOM_ID if none is present
already. PREFIX will be passed through to `org-id-new'. In any
case, the CUSTOM_ID of the entry is returned."
(org-with-point-at pom
(let ((id (org-entry-get nil "CUSTOM_ID")))
(cond
((and id (stringp id) (string-match "\\S-" id))
id)
(create
(setq id (org-id-new))
(org-entry-put pom "CUSTOM_ID" id)
(org-id-add-location id (buffer-file-name (buffer-base-buffer)))
id)))))
(advice-add 'org-store-link
:before (lambda (arg1 arg2)
(eos/org-custom-id-get (point) 'create)))
Under any org heading invoke
M-x pk-bl
The heading in ./path/to/file1.org
carries a custom-id.
* Heading A in file1 :PROPERTIES: :CUSTOM_ID: 8d67362e-aef3-4a0a-9f9c-b74170d3be11 :END: <Use M-x pk-bl here> * Heading B in file1 * Heading C in file1
In ./path/to/assets/file2.org
, the custom-id is referenced (org-store-link
and org-insert-link
are used for that).
* Heading A in file2 ** Sub-Heading *** Leaf [[file:path/to/file1.org::#8d67362e-aef3-4a0a-9f9c-b74170d3be11][Heading A in file1]] * Heading B in file2 * Heading C in file2
The modified ./path/to/file1.org
after calling M-x pk-bl
has inserted the backlinks:
* Heading A in file1 :PROPERTIES: :CUSTOM_ID: 8d67362e-aef3-4a0a-9f9c-b74170d3be11 :END: ** Backlinks - Heading A in file2 - Sub-Heading - [[path/to/assets/file2.org::#4eb1e621-4b6e-44e9-a4f9-5a5b4c3416b9][Leaf]] * Heading B in file1 * Heading C in file1
The Leaf-heading in ./path/to/assets/file2.org
will carry a newly generated custom-id. The modified ./path/to/assets/file2.org
becomes
* Heading A in file2 ** Sub-Heading *** Leaf :PROPERTIES: :CUSTOM_ID: 4eb1e621-4b6e-44e9-a4f9-5a5b4c3416b9 :END: [[file:path/to/file1.org::#8d67362e-aef3-4a0a-9f9c-b74170d3be11][Heading A in file1]] * Heading B in file2 * Heading C in file2
- Tested and developed under Windows 10
- A
.projectile
file MUST be present in the root directory, since it is the starting point for recursively searching for org-files. - Linking is based on the CUSTOM_ID property.
- Uses
org-ql-select
to find backlinks.