tyx's corner


Archive org tasks to org-roam dailies

The snippets in the following article won’t work with tasks that require taking a note after being set: you’ll be left with a note where the task was.

I recently stumbled upon this article from systemcrafters, and quite liked the idea of being able to store DONE tasks into org-roam dailies. However, I already had a working system using org archives which held metadata about the task and kept hierarchy, and I wanted to avoid scrapping that (I also think it makes more sense to archive DONE tasks than to refile them) so I adapted the snippet from systemcrafters’ article to fit my needs.

First, let’s see how we archive the subtree under the same hierarchy as the original source file. I did not write this, but found it here and here.

(require 'dash)
(require 'org-archive)

(setq org-archive-default-command #'org-archive-subtree-hierarchically)

(defun org-archive-subtree-hierarchically (&optional prefix)
  (interactive "P")
  (let* ((fix-archive-p (and (not prefix)
                             (not (use-region-p))))
         (afile  (car (org-archive--compute-location
                       (or (org-entry-get nil "ARCHIVE" 'inherit) org-archive-location))))
         (buffer (or (find-buffer-visiting afile) (find-file-noselect afile))))
    (org-archive-subtree prefix)
    (when fix-archive-p
      (with-current-buffer buffer
        (goto-char (point-max))
        (while (org-up-heading-safe))
        (let* ((olpath (org-entry-get (point) "ARCHIVE_OLPATH"))
               (path (and olpath (split-string olpath "/")))
               (level 1)
               tree-text)
          (when olpath
            (org-mark-subtree)
            (setq tree-text (buffer-substring (region-beginning) (region-end)))
            ;; we don’t want to see "Cut subtree" messages
            (let (this-command (inhibit-message t)) (org-cut-subtree))
            (goto-char (point-min))
            (save-restriction
              (widen)
              (-each path
                (lambda (heading)
                  (if (re-search-forward
                       (rx-to-string
                        `(: bol (repeat ,level "*") (1+ " ") ,heading)) nil t)
                      (org-narrow-to-subtree)
                    (goto-char (point-max))
                    (unless (looking-at "^")
                      (insert "\n"))
                    (insert (make-string level ?*)
                            " "
                            heading
                            "\n"))
                  (cl-incf level)))
              (widen)
              (org-end-of-subtree t t)
              (org-paste-subtree level tree-text))))))))

So now we can adapt the snippet to our needs (I also archive CANCELLED and READ tasks):

;; Set archive location just before archiving
(defun tyx/archive-to-location (location)
  (interactive)
  (setq org-archive-location location)
  (setq org-archive-subtree-add-inherited-tags t)
  (org-archive-subtree-hierarchically))

(defun tyx/org-roam-archive-todo-to-today ()
  (interactive)
  (let ((org-roam-dailies-capture-templates
          '(("t" "tasks" entry "%?"
             :if-new (file+head+olp
                      "%<%Y-%m-%d>.org.gpg" "#+title: %<%Y-%m-%d>\n"
                      ("Tasks done")))))
        (org-archive-hook #'save-buffer)
        today-file
        pos)
    (save-window-excursion
      (org-roam-dailies--capture (current-time) t)
      (setq today-file (buffer-file-name))
      (setq pos (point)))

    ;; Only archive if the target file is different than the current file
    (unless (equal (file-truename today-file)
                   (file-truename (buffer-file-name)))

      (tyx/archive-to-location (concat today-file "::* Tasks done")))))

(add-to-list 'org-after-todo-state-change-hook
             (lambda ()
               (when (member org-state '("DONE" "CANCELLED" "READ"))
                 (my/org-roam-archive-todo-to-today))))

Previously, I was archiving tasks by week, under the archives subdirectory using the following functions, regularly called manually (inspired from here):

(defun tyx/archive-on-closed-time ()
  (interactive)
  (let ((timestamp (date-to-time (cdr (assoc "CLOSED" (org-entry-properties))))))
    (setq org-archive-location (concat "~/org/archives/" ;
                                 (format-time-string "%Y" timestamp)
                                 "/"
                                 (format-time-string "%m" timestamp)
                                 "/"
                                 (format-time-string "W%V" timestamp)
                                 ".org::"))
    (org-archive-subtree-hierarchically)))

(defun tyx/org-archive-done-tasks ()
  (interactive)
  (org-map-entries 'tyx/archive-on-closed-time "/DONE" 'file))

(defun tyx/org-archive-cancelled-tasks ()
  (interactive)
  (org-map-entries 'tyx/archive-on-closed-time "/CANCELLED" 'file))