Export backlinks on Org export

I’ve been using this to export backlinks when publishing my notes to html, it works with V2.

I hope it will help.

(defun collect-backlinks-string (backend)
  "Insert backlinks into the end of the org file before parsing it."
  (goto-char (point-max))
  ;; Add a new header for the references
  (insert "\n\n* Referenced in\n")
  (let* ((backlinks (org-roam-backlinks-get (org-roam-node-at-point))))
    (dolist (backlink backlinks)
      (let* ((source-node (org-roam-backlink-source-node backlink))
             (point (org-roam-backlink-point backlink))
             (text (-last-item (org-roam-node-preview (org-roam-node-file source-node)
                                                      point))))
        (insert
         (format "- [[./%s][%s]]\n%s\n"
                 (file-name-nondirectory (org-roam-node-file source-node))
                 (org-roam-node-title source-node)
                 text))))))

(add-hook 'org-export-before-processing-hook 'collect-backlinks-string)

The final result will be something like this:

4 Likes

Thank you very much!
But when i try to export any file without org-roam-node, it fails.
Maybe better add something like

(when (org-roam-node-at-point)
...
 )

?
I’m not sure how to say it in elisp correct, but this works for me :smiley:

1 Like

That is exactly it. Thank you.

Here is the the code:

(defun collect-backlinks-string (backend)
  "Insert backlinks into the end of the org file before parsing it."
  (when (org-roam-node-at-point)
    (goto-char (point-max))
    ;; Add a new header for the references
    (insert "\n\n* Referenced in\n")
    (let* ((backlinks (org-roam-backlinks-get (org-roam-node-at-point))))
      (dolist (backlink backlinks)
        (let* ((source-node (org-roam-backlink-source-node backlink))
               (point (org-roam-backlink-point backlink))
               (text (-last-item (org-roam-node-preview (org-roam-node-file source-node)
                                                        point))))
          (insert
           (format "- [[./%s][%s]]\n%s\n"
                   (file-name-nondirectory (org-roam-node-file source-node))
                   (org-roam-node-title source-node)
                   text)))))))

(add-hook 'org-export-before-processing-hook 'collect-backlinks-string)
1 Like

Maybe file-relative-name is better than file-name-nondirectory. I have subfolders in my org-roam folder. :slight_smile:

Wish it is possible to make backlinks list for every org-roam-node in file…

1 Like

Hey!

I’ve tried to implement a way to take into account files with multiple nodes.

Is it something like this?

An org file:

:PROPERTIES:
:ID:       114b9147-47c4-4008-9cba-fcb8338f2e98
:END:
#+title: Org-Roam test

Testing stuff with org-roam.

It contains both [[id:foo][Foo]] and [[id:bar][Bar]].

* Foo
:PROPERTIES:
:ID:       foo
:END:

Part of [[id:bar][Bar]].

* Bar
:PROPERTIES:
:ID:       bar
:END:

Part of [[id:foo][Foo]]

The output:

The code:

(defun collect-backlinks-string (backend)
  (let* ((source-node (org-roam-node-at-point))
         (source-file (org-roam-node-file source-node))
         ;; Sort the nodes by the point to avoid errors when inserting the
         ;; references
         (nodes-in-file (--sort (< (org-roam-node-point it)
                                   (org-roam-node-point other))
                                (-filter (lambda (node)
                                           (s-equals?
                                            (org-roam-node-file node)
                                            source-file))
                                         (org-roam-node-list))))
         ;; Nodes don't store the last position so, get the next node position
         ;; and subtract one character
         (nodes-start-position (-map (lambda (node) (org-roam-node-point node))
                                     nodes-in-file))
         (nodes-end-position (-concat (-map (lambda (next-node-position)
                                              (- next-node-position 1))
                                            (-drop 1 nodes-start-position))
                                      (list (point-max))))
         ;; Keep track of the current-node index
         (current-node 0)
         ;; Keep track of the amount of text added
         (character-count 0))
    (dolist (node nodes-in-file)
      (when (org-roam-backlinks-get node)
        ;; Go to the end of the node and don't forget about previously inserted
        ;; text
        (goto-char (+ (nth current-node nodes-end-position) character-count))
        ;; Add the references as a subtree of the node
        (setq heading (format "\n\n%s References\n\n"
                              (s-repeat (+ (org-roam-node-level node) 1) "*")))
        ;; Also count the new lines (4)
        (setq character-count (+ 4 character-count (string-width heading)))
        (insert heading)
        (dolist (backlink (org-roam-backlinks-get node))
          (let* ((source-node (org-roam-backlink-source-node backlink))
                 (point (org-roam-backlink-point backlink))
                 (text (-last-item (org-roam-node-preview
                                    (org-roam-node-file source-node)
                                    point)))
                 (references (format "- [[./%s][%s]]: %s\n\n"
                                     (file-relative-name (org-roam-node-file source-node))
                                     (org-roam-node-title source-node)
                                     text)))
            ;; Also count the new lines (2)
            (setq character-count (+ 2 character-count (string-width references)))
            (insert references))))
      (setq current-node (+ current-node 1)))))

Thank you!
It’s really something like this!
But (i don’t know why) org-roam-node-preview does not works for me now…

Are you using org-roam V2?

Edit: I forgot to add (when (org-roam-node-at-point) so here it is the code again:
Edit 2: You need both dash.el and s.el packages.

(defun collect-backlinks-string (backend)
  (when (org-roam-node-at-point)
    (let* ((source-node (org-roam-node-at-point))
           (source-file (org-roam-node-file source-node))
           ;; Sort the nodes by the point to avoid errors when inserting the
           ;; references
           (nodes-in-file (--sort (< (org-roam-node-point it)
                                     (org-roam-node-point other))
                                  (-filter (lambda (node)
                                             (s-equals?
                                              (org-roam-node-file node)
                                              source-file))
                                           (org-roam-node-list))))
           ;; Nodes don't store the last position so, get the next node position
           ;; and subtract one character
           (nodes-start-position (-map (lambda (node) (org-roam-node-point node))
                                       nodes-in-file))
           (nodes-end-position (-concat (-map (lambda (next-node-position)
                                                (- next-node-position 1))
                                              (-drop 1 nodes-start-position))
                                        (list (point-max))))
           ;; Keep track of the current-node index
           (current-node 0)
           ;; Keep track of the amount of text added
           (character-count 0))
      (dolist (node nodes-in-file)
        (when (org-roam-backlinks-get node)
          ;; Go to the end of the node and don't forget about previously inserted
          ;; text
          (goto-char (+ (nth current-node nodes-end-position) character-count))
          ;; Add the references as a subtree of the node
          (setq heading (format "\n\n%s References\n\n"
                                (s-repeat (+ (org-roam-node-level node) 1) "*")))
          ;; Also count the new lines (4)
          (setq character-count (+ 4 character-count (string-width heading)))
          (insert heading)
          (dolist (backlink (org-roam-backlinks-get node))
            (let* ((source-node (org-roam-backlink-source-node backlink))
                   (point (org-roam-backlink-point backlink))
                   (text (-last-item (org-roam-node-preview
                                      (org-roam-node-file source-node)
                                      point)))
                   (references (format "- [[./%s][%s]]: %s\n\n"
                                       (file-relative-name (org-roam-node-file source-node))
                                       (org-roam-node-title source-node)
                                       text)))
              ;; Also count the new lines (2)
              (setq character-count (+ 2 character-count (string-width references)))
              (insert references))))
        (setq current-node (+ current-node 1))))))

Where is org-roam-node-preview defined? I get an error when running this code, and indeed I can’t find org-roam-node-preview in the source.

By describing the function it says:

org-roam-node-preview is a Lisp closure in ‘org-roam.el’.

(org-roam-node-preview FILE POINT)

Get preview content for FILE at POINT.

I’ve updated my org-roam package and now it doesn’t work. :smiley: I will try to fix it.

1 Like

I think the name looks to have changed to org-roam-get-preview in a recent commit: (feat)preview: improve org-roam link preview (#1655) · org-roam/org-roam@9c10a3c · GitHub

Yes, the name changed and now it doesn’t return a list, just text. I guess it is fixed now:

(defun collect-backlinks-string (backend)
  (when (org-roam-node-at-point)
    (let* ((source-node (org-roam-node-at-point))
           (source-file (org-roam-node-file source-node))
           ;; Sort the nodes by the point to avoid errors when inserting the
           ;; references
           (nodes-in-file (--sort (< (org-roam-node-point it)
                                     (org-roam-node-point other))
                                  (-filter (lambda (node)
                                             (s-equals?
                                              (org-roam-node-file node)
                                              source-file))
                                           (org-roam-node-list))))
           ;; Nodes don't store the last position so, get the next node position
           ;; and subtract one character
           (nodes-start-position (-map (lambda (node) (org-roam-node-point node))
                                       nodes-in-file))
           (nodes-end-position (-concat (-map (lambda (next-node-position)
                                                (- next-node-position 1))
                                              (-drop 1 nodes-start-position))
                                        (list (point-max))))
           ;; Keep track of the current-node index
           (current-node 0)
           ;; Keep track of the amount of text added
           (character-count 0))
      (dolist (node nodes-in-file)
        (when (org-roam-backlinks-get node)
          ;; Go to the end of the node and don't forget about previously inserted
          ;; text
          (goto-char (+ (nth current-node nodes-end-position) character-count))
          ;; Add the references as a subtree of the node
          (setq heading (format "\n\n%s References\n\n"
                                (s-repeat (+ (org-roam-node-level node) 1) "*")))
          ;; Also count the new lines (4)
          (setq character-count (+ 4 character-count (string-width heading)))
          (insert heading)
          (dolist (backlink (org-roam-backlinks-get node))
            (let* ((source-node (org-roam-backlink-source-node backlink))
                   (point (org-roam-backlink-point backlink))
                   (text (org-roam-get-preview
                          (org-roam-node-file source-node)
                          point))
                   (references (format "- [[./%s][%s]]: %s\n\n"
                                       (file-relative-name (org-roam-node-file source-node))
                                       (org-roam-node-title source-node)
                                       text)))
              ;; Also count the new lines (2)
              (setq character-count (+ 2 character-count (string-width references)))
              (insert references))))
        (setq current-node (+ current-node 1))))))


2 Likes

I’m trying to figure out how to insert a properties drawer on the backlinks node so I can style it with CSS.

when I try:

        (insert heading)
        (insert ":PROPERTIES:\n:HTML_CONTAINER_CLASS: references\n:END:\n")

it does not insert the text correctly, instead it inserts it as formatted text within the node. Any suggestions?

You also need to increase the character count, make it like this bellow the (insert heading):

;; Insert properties drawer
(setq properties-drawer ":PROPERTIES:\n:HTML_CONTAINER_CLASS: references\n:END:\n")
;; Count the characters and count the new lines (3)
(setq character-count (+ 3 character-count (string-width properties-drawer)))
(insert properties-drawer)

Hm, this is resulting in the same output.

This is what I used. I made 2 changes:

  • Removed one \n from the heading
  • Changed (setq character-count (+ 4 to (setq character-count (+ 3 because now it only has 3 new lines.
(defun collect-backlinks-string (backend)
  (when (org-roam-node-at-point)
    (let* ((source-node (org-roam-node-at-point))
           (source-file (org-roam-node-file source-node))
           ;; Sort the nodes by the point to avoid errors when inserting the
           ;; references
           (nodes-in-file (--sort (< (org-roam-node-point it)
                                     (org-roam-node-point other))
                                  (-filter (lambda (node)
                                             (s-equals?
                                              (org-roam-node-file node)
                                              source-file))
                                           (org-roam-node-list))))
           ;; Nodes don't store the last position so, get the next node position
           ;; and subtract one character
           (nodes-start-position (-map (lambda (node) (org-roam-node-point node))
                                       nodes-in-file))
           (nodes-end-position (-concat (-map (lambda (next-node-position)
                                                (- next-node-position 1))
                                              (-drop 1 nodes-start-position))
                                        (list (point-max))))
           ;; Keep track of the current-node index
           (current-node 0)
           ;; Keep track of the amount of text added
           (character-count 0))
      (dolist (node nodes-in-file)
        (when (org-roam-backlinks-get node)
          ;; Go to the end of the node and don't forget about previously inserted
          ;; text
          (goto-char (+ (nth current-node nodes-end-position) character-count))
          ;; Add the references as a subtree of the node
          (setq heading (format "\n\n%s References\n"
                                (s-repeat (+ (org-roam-node-level node) 1) "*")))
          ;; Count the characters and count the new lines (4)
          (setq character-count (+ 3 character-count (string-width heading)))
          (insert heading)
          ;; Insert properties drawer
          (setq properties-drawer ":PROPERTIES:\n:HTML_CONTAINER_CLASS: references\n:END:\n")
          ;; Count the characters and count the new lines (3)
          (setq character-count (+ 3 character-count (string-width properties-drawer)))
          (insert properties-drawer)
          (dolist (backlink (org-roam-backlinks-get node))
            (let* ((source-node (org-roam-backlink-source-node backlink))
                   (point (org-roam-backlink-point backlink))
                   (text (org-roam-get-preview
                          (org-roam-node-file source-node)
                          point))
                   (references (format "- [[./%s][%s]]: %s\n\n"
                                       (file-relative-name (org-roam-node-file source-node))
                                       (org-roam-node-title source-node)
                                       text)))
              ;; Also count the new lines (2)
              (setq character-count (+ 2 character-count (string-width references)))
              (insert references))))
        (setq current-node (+ current-node 1))))))

Thank you so much! It works.

1 Like

Since today I receive this error:

Symbol's function definition is void: org-roam-get-preview

Removing the call to org-roam-get-preview from collect-backlinks-string gets things working again.

Not sure what happened to org-roam-get-preview - maybe related to this PR? - (feat): globally restructure and refactor the codebase by Wetlize · Pull Request #1724 · org-roam/org-roam · GitHub - which does seem to rework a lot of the code.

The function changed. Now it’s called: org-roam-preview-get-contents

I’ve changed the code accordingly and removed new lines from preview, to avoid creating new paragraphs.

(defun collect-backlinks-string (backend)
  (when (org-roam-node-at-point)
    (let* ((source-node (org-roam-node-at-point))
           (source-file (org-roam-node-file source-node))
           ;; Sort the nodes by the point to avoid errors when inserting the
           ;; references
           (nodes-in-file (--sort (< (org-roam-node-point it)
                                     (org-roam-node-point other))
                                  (-filter (lambda (node)
                                             (s-equals?
                                              (org-roam-node-file node)
                                              source-file))
                                           (org-roam-node-list))))
           ;; Nodes don't store the last position so, get the next node position
           ;; and subtract one character
           (nodes-start-position (-map (lambda (node) (org-roam-node-point node))
                                       nodes-in-file))
           (nodes-end-position (-concat (-map (lambda (next-node-position)
                                                (- next-node-position 1))
                                              (-drop 1 nodes-start-position))
                                        (list (point-max))))
           ;; Keep track of the current-node index
           (current-node 0)
           ;; Keep track of the amount of text added
           (character-count 0))
      (dolist (node nodes-in-file)
        (when (org-roam-backlinks-get node)
          ;; Go to the end of the node and don't forget about previously inserted
          ;; text
          (goto-char (+ (nth current-node nodes-end-position) character-count))
          ;; Add the references as a subtree of the node
          (setq heading (format "\n\n%s References\n"
                                (s-repeat (+ (org-roam-node-level node) 1) "*")))
          ;; Count the characters and count the new lines (4)
          (setq character-count (+ 3 character-count (string-width heading)))
          (insert heading)
          ;; Insert properties drawer
          (setq properties-drawer ":PROPERTIES:\n:HTML_CONTAINER_CLASS: references\n:END:\n")
          ;; Count the characters and count the new lines (3)
          (setq character-count (+ 3 character-count (string-width properties-drawer)))
          (insert properties-drawer)
          (dolist (backlink (org-roam-backlinks-get node))
            (let* ((source-node (org-roam-backlink-source-node backlink))
                   (point (org-roam-backlink-point backlink))
                   (text (s-replace "\n" " " (org-roam-preview-get-contents
                                           (org-roam-node-file source-node)
                                           point)))
                   (references (format "- [[./%s][%s]]: %s\n\n"
                                       (file-relative-name (org-roam-node-file source-node))
                                       (org-roam-node-title source-node)
                                       text)))
              ;; Also count the new lines (2)
              (setq character-count (+ 2 character-count (string-width references)))
              (insert references))))
        (setq current-node (+ current-node 1))))))
3 Likes

Hey, this function is nice, thanks! I’m using it on my website.

I’ve fixed a few bugs in the way it works below. Namely, a node’s end position isn’t necessarily the next node, but rather the next same-level headline (if there is one.) Also, I changed it to sort in decreasing order of the node’s end position (which removes the need for manually counting characters entirely,) and then insert backlinks in that order.

A couple other tweaks I added:

  • the outline path, as is displayed in the org-roam-buffer. You can take this out if desired.
  • backlinks link directly to the node ID, rather than to the source file.
(defun collect-backlinks-string (backend)
  (when (org-roam-node-at-point)
    (let* ((source-node (org-roam-node-at-point))
           (source-file (org-roam-node-file source-node))
           (nodes-in-file (--filter (s-equals? (org-roam-node-file it) source-file)
                                    (org-roam-node-list)))
           (nodes-start-position (-map 'org-roam-node-point nodes-in-file))
           ;; Nodes don't store the last position, so get the next headline position
           ;; and subtract one character (or, if no next headline, get point-max)
           (nodes-end-position (-map (lambda (nodes-start-position)
                                       (goto-char nodes-start-position)
                                       (if (org-before-first-heading-p) ;; file node
                                           (point-max)
                                         (call-interactively
                                          'org-forward-heading-same-level)
                                         (if (> (point) nodes-start-position)
                                             (- (point) 1) ;; successfully found next
                                           (point-max)))) ;; there was no next
                                     nodes-start-position))
           ;; sort in order of decreasing end position
           (nodes-in-file-sorted (->> (-zip nodes-in-file nodes-end-position)
                                      (--sort (> (cdr it) (cdr other))))))
      (dolist (node-and-end nodes-in-file-sorted)
        (-let (((node . end-position) node-and-end))
          (when (org-roam-backlinks-get node)
            (goto-char end-position)
            ;; Add the references as a subtree of the node
            (setq heading (format "\n\n%s References\n"
                                  (s-repeat (+ (org-roam-node-level node) 1) "*")))
            (insert heading)
            (setq properties-drawer ":PROPERTIES:\n:HTML_CONTAINER_CLASS: references\n:END:\n")
            (insert properties-drawer)
            (dolist (backlink (org-roam-backlinks-get node))
              (let* ((source-node (org-roam-backlink-source-node backlink))
                     (properties (org-roam-backlink-properties backlink))
                     (outline (when-let ((outline (plist-get properties :outline)))
                                  (mapconcat #'org-link-display-format outline " > ")))
                     (point (org-roam-backlink-point backlink))
                     (text (s-replace "\n" " " (org-roam-preview-get-contents
                                                (org-roam-node-file source-node)
                                                point)))
                     (reference (format "%s [[id:%s][%s]]\n%s\n%s\n\n"
                                        (s-repeat (+ (org-roam-node-level node) 2) "*")
                                        (org-roam-node-id source-node)
                                        (org-roam-node-title source-node)
                                        (if outline (format "%s (/%s/)"
                                        (s-repeat (+ (org-roam-node-level node) 3) "*") outline) "")
                                        text)))
                (insert reference)))))))))
2 Likes