;; org-roam-link-utils.el (defun org-roam-link-find () "Find \"roam:\" links in Org-Roam" (interactive) (let* ((link-type "roam") (query "select files.title, links.source, links.pos, links.dest, links.properties from links inner join nodes on links.source = nodes.id inner join files on nodes.file = files.file where links.type like $s1") (links (org-roam-db-query query (concat "%" link-type "%"))) (choices (mapcar (lambda (link) (let* ((file-title (nth 0 link)) (pos (nth 2 link)) (dest (nth 3 link)) (outline (mapconcat #'identity (plist-get (nth 4 link) :outline) " > ")) (description (format "%s: %s [%d] %s" dest file-title pos (if (not (string= "" outline)) (concat "> " outline) "")))) (cons description (list pos (nth 1 link))))) links)) (selection (completing-read "Select a roam link: " (mapcar #'car choices))) (selected-data (cdr (assoc selection choices))) (pos (nth 0 selected-data)) (id (nth 1 selected-data))) (org-roam-id-open id) (goto-char pos))) (defun org-roam-link-replace-all-backlinks () "For all \"roam:\" links referencing current node, Convert to an id link &, Convert every raw id link - if any - [[id:uuid]] to [[id:uuid][description]] where description is node-title." (interactive) (when-let* ((original-buffer (current-buffer)) (node (org-roam-node-at-point)) (title (org-roam-node-title node)) (id (org-roam-node-id node)) (query (format "select links.source from links inner join nodes on links.source = nodes.id where links.dest = '\"%s\"' or links.dest = '\"%s\"' group by nodes.file;" title id)) (links (org-roam-db-query query))) (mapc (lambda (link) (+org-roam-id-goto (car link)) (org-with-point-at 1 (while (re-search-forward org-link-bracket-re nil t) (cond ((and (string-match-p (concat "^roam:" title "$") (match-string 1)) (y-or-n-p (format "Process link %s?" (match-string 1)))) (org-roam-link-replace-at-point)) ((and (string-match-p (concat "^id:" id "$") (match-string 1)) (not (match-string 2)) (y-or-n-p (format "Process link %s?" (match-string 1)))) (goto-char (match-end 1)) (forward-char 1) (insert (format "[%s]" title)))))) (write-file (buffer-file-name))) links) ;; switch to our orignal-buffer (switch-to-buffer original-buffer))) (defun org-roam-link-rename-all-backlinks () "Rename the current node title and propagate changes to links referencing current node. 1. Propagates changes to \"roam:\" links by updating the destination, and 2. For \"id:\" links - If it is a raw ID link `[[id:uuid]]' add a description with the new node title [[id:uuid][new-title]], whereas for any standard ID link `[[id:uuid][old-title]]', update to `[[id:uuid][new-title]],' does not affect the description of non-standard IDs, `[[id:uuid][custom-description]]'." (interactive) (when-let* ((original-buffer (current-buffer)) (node (org-roam-node-at-point)) (title (org-roam-node-title node)) (new-title title) (id (org-roam-node-id node)) (level (org-roam-node-level node)) (pos (org-roam-node-point node)) (query (format "select links.source from links inner join nodes on links.source = nodes.id where links.dest = '\"%s\"' or links.dest = '\"%s\"' group by nodes.file;" title id)) (links (org-roam-db-query query))) (setq new-title (read-from-minibuffer (format "Enter new node-title \n [Currently \"%s\"]: " title))) (when (string= "" new-title) (user-error "Warning! You have decided to delete current node-title and propagate changes. Aborting! This is not allowed.")) (if (= level 0) (org-roam-set-keyword "TITLE" new-title) (org-with-point-at pos (org-edit-headline new-title))) (write-file (buffer-file-name)) (mapc (lambda (link) (+org-roam-id-goto (car link)) (org-with-point-at 1 (while (re-search-forward org-link-bracket-re nil t) (cond ((and (string-match-p (concat "^roam:" title "$") (match-string 1)) (y-or-n-p (format "Rename link %s?" (match-string 1)))) (goto-char (match-beginning 1)) (delete-region (match-beginning 1) (match-end 1)) (insert (format "roam:%s" new-title))) ((and (string-match-p (concat "^id:" id "$") (match-string 1)) (y-or-n-p (format "Rename link %s?" (match-string 1)))) (unless (match-string 2) (goto-char (match-end 1)) (forward-char 1) (insert (format "[%s]" new-title)) (goto-char (match-beginning 0)) (re-search-forward org-link-bracket-re nil t)) (when (and (match-string 2) (string-match-p (concat "^" title "$") (match-string 2))) (goto-char (match-beginning 2)) (delete-region (match-beginning 2) (match-end 2)) (insert new-title)))))) (write-file (buffer-file-name))) links) ;; switch to our orignal-buffer (switch-to-buffer original-buffer))) (defun +org-roam-id-goto (id) "Switch to the buffer containing the entry with id ID. Move the cursor to that entry in that buffer. Like `org-id-goto', but additionally uses the Org-roam database" (interactive "sID: ") (let ((m (org-roam-id-find id 'marker))) (unless m (error "Cannot find entry with ID \"%s\"" id)) (pop-to-buffer-same-window (marker-buffer m)) (goto-char m) (move-marker m nil) (org-fold-show-context))) ;; end (provide 'org-roam-link-utils)