I found myself wanting to automatically keep a log of every org-roam entry I work on. After struggling with the capture system for a while, I managed to make something work.

(setf (alist-get "u" org-roam-dailies-capture-templates nil nil #'equal)
      '("updated" plain nil
        :target (file+head+olp "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>\n" ("Updated"))
        :unnarrowed t :prepend t))

(defmacro +save-current-buffer (&rest body)
  "Save current buffer; execute BODY; restore original buffer.
I have found that this macro works under some circumstances in
which ‘save-current-buffer’ does not."
  (declare (indent 0))
  `(let ((original-buffer (current-buffer)))
     (unwind-protect (progn ,@body)
       (when (buffer-name original-buffer)
         (switch-to-buffer original-buffer)))))

(defmacro +edit-to-string (&rest body)
  "Eval BODY in a temporary buffer and extract contents to a string."
  (declare (indent 0))
  `(with-temp-buffer
     ,@body
     (buffer-string)))

(defun +org-element-contents (element)
  "Extract contents of ELEMENT from current buffer."
  (buffer-substring (org-element-property :contents-begin element)
                    (org-element-property :contents-end element)))

(defun +org-roam-updated-on-save ()
  "Log current Org-roam buffer as modified."
  (when (org-roam-buffer-p)
    (let* ((original-buffer (current-buffer))
           (id (org-id-get (point-min)))
           (title (org-get-title))
           (link-text (format "%s\n" (+edit-to-string
                                       (org-insert-link nil id title)))))
      (save-excursion  ;; in case we are currently editing the daily note
        (+save-current-buffer
         (org-roam-dailies-goto-today "u")
         (unless (eq original-buffer (current-buffer))
           (let ((elt (org-element-context)))
             (if (not (eq 'plain-list (org-element-type elt)))
                 (insert "\n- " link-text)
               (org-down-element)
               (cl-loop
                for elt = (org-element-context)
                while (eq 'item (org-element-type elt))
                if (equal link-text (+org-element-contents elt)) return nil
                do (org-forward-element)
                finally (insert "- " link-text))))))))))

(add-hook 'after-save-hook #'+org-roam-updated-on-save)

I originally tried to do this using just capture templates (i.e., just an interpolated Lisp expression, no wrapper code), but I’m not sure that that’s possible due to some flexibility problems with the Org-roam capture system.

One thing I’m not happy about is having to use my weird homespun +save-current-buffer macro, but the original save-current-buffer macro doesn’t switch back for some reason.

I am kind of? happy about having gotten some experience with the loop macro. I’m still not sure I like it, but I understand better why it exists in a language that doesn’t have other, more structure looping facilities.

  • MoistFew@alien.topB
    link
    fedilink
    English
    arrow-up
    1
    ·
    10 months ago

    Damn this is neat I didn’t realize I wanted this until now thank you!

    I use a datetree format for my dailies so I managed to make this work via a few modifications to +org-roam-updated-on-save. Maybe this will come in handy for someone else

    With my current daily capture templates as:

    (setq org-roam-dailies-capture-templates
    '(("d" "default" entry "* %?" :target (file+datetree "dailies.org" week))))
    

    some tiny changes to the key for org-roam-dailies-goto-today, the link text, and making it go to end of subtree before inserting the link

    (defun +org-roam-updated-on-save ()
        ...
        (link-text (format "[[id:%s][%s]]\n" id title)))
        ...
        (org-roam-dailies-goto-today "d")
        ...
        (if (not (eq 'plain-list (org-element-type elt)))
          (progn
            (org-end-of-subtree)          
            (insert "\n- " link-text))
    

    Edit: this might not fully work with regards to not putting the same node over and over