summaryrefslogtreecommitdiff
path: root/modules/org-roam-config.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-10-18 02:00:53 -0500
committerCraig Jennings <c@cjennings.net>2025-10-18 02:00:53 -0500
commita30f04704b54a45b712c0a8ed4688b3832e0e6d7 (patch)
tree587746234dc3ea95c0f7498025fc49dfb0e975e6 /modules/org-roam-config.el
parente4c651729737ea50eba819adf4d980cb54ffae50 (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.el102
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.