diff options
| author | Craig Jennings <c@cjennings.net> | 2025-10-18 02:00:53 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-10-18 02:00:53 -0500 |
| commit | a30f04704b54a45b712c0a8ed4688b3832e0e6d7 (patch) | |
| tree | 587746234dc3ea95c0f7498025fc49dfb0e975e6 /modules/org-roam-config.el | |
| parent | e4c651729737ea50eba819adf4d980cb54ffae50 (diff) | |
maint: org-roam: moved org-branch-to-org-roam-node from webclipper
Diffstat (limited to 'modules/org-roam-config.el')
| -rw-r--r-- | modules/org-roam-config.el | 102 |
1 files changed, 95 insertions, 7 deletions
diff --git a/modules/org-roam-config.el b/modules/org-roam-config.el index 741e42cf..feecebd2 100644 --- a/modules/org-roam-config.el +++ b/modules/org-roam-config.el @@ -106,6 +106,7 @@ the arguments that org-roam-node-insert expects." ;; -------------------------- Org Roam Find Functions -------------------------- +;;;###autoload (defun cj/org-roam-find-node (tag template-key template-file &optional subdir) "List all nodes of type TAG in completing read for selection or creation. Interactively find or create an Org-roam node with a given TAG. Newly @@ -121,21 +122,17 @@ TEMPLATE-KEY and TEMPLATE-FILE." :if-new (file+head ,(concat (or subdir "") "%<%Y%m%d%H%M%S>-${slug}.org") "") :unnarrowed t)))) +;;;###autoload (defun cj/org-roam-find-node-topic () "List nodes of type \=`topic\=` in completing read for selection or creation." (interactive) (cj/org-roam-find-node "Topic" "t" (concat roam-dir "templates/topic.org"))) +;;;###autoload (defun cj/org-roam-find-node-recipe () (interactive) (cj/org-roam-find-node "Recipe" "r" (concat roam-dir "templates/recipe.org") "recipes/")) -(defun cj/org-roam-find-node-project () - "List nodes of type \='project\=' in completing read for selection or creation." - - (interactive) - (cj/org-roam-find-node "Project" "p" (concat roam-dir "templates/project.org"))) - ;; ---------------------- Org Capture After Finalize Hook ---------------------- (defun cj/org-roam-add-node-to-agenda-files-finalize-hook () @@ -172,7 +169,98 @@ TEMPLATE-KEY and TEMPLATE-FILE." ;; Only refile if the target file is different than the current file (unless (equal (file-truename today-file) (file-truename (buffer-file-name))) - (org-refile nil nil (list "Completed Tasks" today-file nil pos))))) + (org-refile nil nil (list "Completed Tasks" today-file nil pos))))) + +;; ------------------------ Org-Branch To Org-Roam-Node ------------------------ + +(defun cj/org-link-get-description (text) + "Extract the description from an org link, or return the text unchanged. +If TEXT contains an org link like [[url][description]], return description. +If TEXT contains multiple links, only process the first one. +Otherwise return TEXT unchanged." + (if (string-match "\\[\\[\\([^]]+\\)\\]\\(?:\\[\\([^]]+\\)\\]\\)?\\]" text) + (let ((description (match-string 2 text)) + (url (match-string 1 text))) + ;; If there's a description, use it; otherwise use the URL + (or description url)) + text)) + +;;;###autoload +(defun cj/move-org-branch-to-roam () + "Move the org subtree at point to a new org-roam node. +The node filename will be timestamp-based with the heading name. +The heading becomes the node title, and the entire subtree is demoted to level 1. +If the heading contains a link, extract the description for the title." + (interactive) + ;; Lazy load org and org-roam when needed + (require 'org) + (require 'org-id) + (require 'org-roam) + + (unless (org-at-heading-p) + (user-error "Not at an org heading")) + + (let* ((heading-components (org-heading-components)) + (current-level (nth 0 heading-components)) + (raw-title (nth 4 heading-components)) + ;; Extract clean title from potential link + (title (cj/org-link-get-description raw-title)) + (timestamp (format-time-string "%Y%m%d%H%M%S")) + ;; Convert title to filename-safe format + (title-slug (replace-regexp-in-string + "[^a-zA-Z0-9]+" "-" + (downcase title))) + ;; Remove leading/trailing hyphens + (title-slug (replace-regexp-in-string + "^-\\|-$" "" title-slug)) + (filename (format "%s-%s.org" timestamp title-slug)) + (filepath (expand-file-name filename org-roam-directory)) + ;; Generate a unique ID for the node + (node-id (org-id-new)) + ;; Store the subtree in a temporary buffer + subtree-content) + + ;; Copy the subtree content + (org-copy-subtree) + (setq subtree-content (current-kill 0)) + + ;; Now cut it to remove from original buffer + (org-cut-subtree) + + ;; Process the subtree to demote it to level 1 + (with-temp-buffer + (org-mode) + (insert subtree-content) + ;; Demote the entire tree so the top level becomes level 1 + (goto-char (point-min)) + (when (> current-level 1) + (let ((demote-count (- current-level 1))) + (while (re-search-forward "^\\*+ " nil t) + (beginning-of-line) + (dotimes (_ demote-count) + (when (looking-at "^\\*\\*") + (delete-char 1))) + (forward-line)))) + (setq subtree-content (buffer-string))) + + ;; Create the new org-roam file + (with-temp-file filepath + ;; Insert the org-roam template with ID at file level + (insert ":PROPERTIES:\n") + (insert ":ID: " node-id "\n") + (insert ":END:\n") + (insert "#+TITLE: " title "\n") + (insert "#+CATEGORY: " title "\n") + (insert "#+FILETAGS: Topic\n\n") + + ;; Insert the demoted subtree content + (insert subtree-content)) + + ;; Sync the org-roam database + (org-roam-db-sync) + + ;; Message to user + (message "'%s' added as an org-roam node." title))) (provide 'org-roam-config) ;;; org-roam-config.el ends here. |
