diff options
author | Thomas F. K. Jorna <[email protected]> | 2021-11-02 12:35:41 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2021-11-02 12:35:41 +0100 |
commit | b123e90b39236e7fbd30a3e277d1fb8978adc479 (patch) | |
tree | 31771ba01d5bf47b1a871cb05c904d40bb88b021 /org-roam-ui.el | |
parent | 952be7230115d30eb3d7a35212cf60d0c5ff903f (diff) | |
parent | b8096ad7623fae14d4c509576071128389ea6785 (diff) |
feat(melpa): melpa requirements
MELPA requirements
Diffstat (limited to 'org-roam-ui.el')
-rw-r--r-- | org-roam-ui.el | 328 |
1 files changed, 230 insertions, 98 deletions
diff --git a/org-roam-ui.el b/org-roam-ui.el index a14b4be..2c7f9cb 100644 --- a/org-roam-ui.el +++ b/org-roam-ui.el @@ -5,7 +5,7 @@ ;; author: Kirill Rogovoy, Thomas Jorna ;; URL: https://github.com/org-roam/org-roam-ui ;; Keywords: files outlines -;; Version: 0 +;; Version: 0.1 ;; Package-Requires: ((emacs "27.1") (org-roam "2.0.0") (simple-httpd "20191103.1446") (websocket "20210110.17") (json "1.2")) ;; This file is NOT part of GNU Emacs. @@ -52,7 +52,8 @@ ".") "Root directory of the org-roam-ui project.") -(defvar org-roam-ui-app-build-dir (expand-file-name "./out/" org-roam-ui-root-dir) +(defvar org-roam-ui-app-build-dir + (expand-file-name "./out/" org-roam-ui-root-dir) "Directory containing org-roam-ui's web build.") ;; TODO: make into defcustom @@ -114,7 +115,8 @@ This can lead to some jank." :group 'org-roam-ui :type 'boolean) -(defcustom org-roam-ui-ref-title-template "%^{author-abbrev} (%^{year}) %^{title}" +(defcustom org-roam-ui-ref-title-template + "%^{author-abbrev} (%^{year}) %^{title}" "A template for title creation, used for references without associated nodes. This uses `orb--pre-expand-template' under the hood and therefore only org-style @@ -188,7 +190,8 @@ This serves the web-build and API over HTTP." (defun org-roam-ui--ws-on-message (_ws frame) "Functions to run when the org-roam-ui server receives a message. Takes _WS and FRAME as arguments." - (let* ((msg (json-parse-string (websocket-frame-text frame) :object-type 'alist)) + (let* ((msg (json-parse-string + (websocket-frame-text frame) :object-type 'alist)) (command (alist-get 'command msg)) (data (alist-get 'data msg))) (cond ((string= command "open") @@ -197,7 +200,9 @@ Takes _WS and FRAME as arguments." (org-roam-ui--on-msg-delete-node data)) ((string= command "create") (org-roam-ui--on-msg-create-node data)) - (t (message "Something went wrong when receiving a message from org-roam-ui"))))) + (t + (message + "Something went wrong when receiving a message from org-roam-ui"))))) (defun org-roam-ui--on-msg-open-node (data) "Open a node when receiving DATA from the websocket." @@ -206,8 +211,13 @@ Takes _WS and FRAME as arguments." (buf (org-roam-node-find-noselect node))) (unless (window-live-p org-roam-ui--window) (if-let ((windows (window-list)) - (or-windows (seq-filter (lambda (window) (org-roam-buffer-p (window-buffer window))) windows)) - (newest-window (car (seq-sort-by #'window-use-time #'> or-windows)))) + (or-windows (seq-filter + (lambda (window) + (org-roam-buffer-p + (window-buffer window))) windows)) + (newest-window (car + (seq-sort-by + #'window-use-time #'> or-windows)))) (setq org-roam-ui--window newest-window) (split-window-horizontally) (setq org-roam-ui--window (frame-selected-window)))) @@ -249,10 +259,13 @@ TODO: Be able to delete individual nodes." (text)) (org-roam-with-temp-buffer file - (setq text (buffer-substring-no-properties (buffer-end -1) (buffer-end 1))) + (setq text + (buffer-substring-no-properties (buffer-end -1) (buffer-end 1))) text) (websocket-send-text ws - (json-encode `((type . "orgText") (data . ,text)))))) + (json-encode + `((type . "orgText") + (data . ,text)))))) (defservlet* file/:file text/plain () "Servlet for accessing file contents of org-roam files. @@ -281,7 +294,8 @@ TODO: Make this only send the changes to the graph data, not the complete graph. (when (and org-roam-ui-retitle-ref-nodes (boundp 'orb-preformat-keywords)) (dolist (keyword '("author-abbrev" "year" "title")) (unless (seq-contains-p orb-preformat-keywords keyword) - (setq orb-preformat-keywords (append orb-preformat-keywords (list keyword))))))) + (setq orb-preformat-keywords + (append orb-preformat-keywords (list keyword))))))) (defun org-roam-ui--find-ref-title (ref) "Find the title of the bibtex entry keyed by `REF'. @@ -293,11 +307,14 @@ loaded. Returns `ref' if an entry could not be found." (fboundp 'orb--pre-expand-template) (boundp 'orb-preformat-keywords)) (if-let ((entry (bibtex-completion-get-entry ref)) - (orb-preformat-keywords (append orb-preformat-keywords '("author-abbrev" "year" "title")))) + (orb-preformat-keywords + (append orb-preformat-keywords + '("author-abbrev" "year" "title")))) ;; Create a fake capture template list, only the actual capture at 3 ;; matters. Interpolate the bibtex entries, and extract the filled ;; template from the return value. - (nth 3 (orb--pre-expand-template `("" "" plain ,org-roam-ui-ref-title-template) entry)) + (nth 3 (orb--pre-expand-template + `("" "" plain ,org-roam-ui-ref-title-template) entry)) ref) ref)) @@ -344,76 +361,166 @@ unchanged." (defun org-roam-ui--create-fake-node (ref) "Create a fake node for REF without a source note." - (list ref ref (org-roam-ui--find-ref-title ref) 0 0 'nil `(("ROAM_REFS" . ,(format "cite:%s" ref)) ("FILELESS" . t)) 'nil)) + (list + ref + ref + (org-roam-ui--find-ref-title ref) + 0 + 0 + 'nil + `(("ROAM_REFS" . ,(format "cite:%s" ref)) + ("FILELESS" . t)) + 'nil)) (defun org-roam-ui--send-graphdata () - "Get roam data, make JSON, send through websocket to org-roam-ui. - -TODO: Split this up." - (let* ((nodes-columns [id file title level pos olp properties ,(funcall group-concat tag (emacsql-escape-raw \, ))]) - (nodes-names [id file title level pos olp properties tags]) - (links-columns [links:source links:dest links:type]) - (cites-columns [citations:node-id citations:cite-key refs:node-id]) - (nodes-db-rows (org-roam-db-query `[:select ,nodes-columns :as tags - :from nodes - :left-join tags - :on (= id node_id) - :group :by id])) - links-db-rows - cites-db-rows - links-with-empty-refs) - ;; Put this check in until Doom upgrades to the latest org-roam - (if (fboundp 'org-roam-db-map-citations) - (setq links-db-rows (org-roam-db-query `[:select ,links-columns - :from links - :where (= links:type "id")]) - ;; Left outer join on refs means any id link (or cite link without a - ;; corresponding node) will have 'nil for the `refs:node-id' value. Any - ;; cite link where a node has that `:ROAM_REFS:' will have a value. - cites-db-rows (org-roam-db-query `[:select ,cites-columns - :from citations - :left :outer :join refs :on (= citations:cite-key refs:ref)]) - ;; Convert any cite links that have nodes with associated refs to an - ;; id based link of type `ref' while removing the 'nil `refs:node-id' - ;; from all other links - cites-db-rows (seq-map (lambda (l) - (pcase-let ((`(,source ,dest ,node-id) l)) - (if node-id - (list source node-id "ref") - (list source dest "cite")))) cites-db-rows) - links-db-rows (append links-db-rows cites-db-rows) - links-with-empty-refs (seq-filter (lambda (link) (string-match-p "cite" (nth 2 link))) cites-db-rows)) - (setq links-db-rows (org-roam-db-query `[:select [links:source links:dest links:type refs:node-id] - :from links - :left :outer :join refs :on (= links:dest refs:ref) - :where (or (= links:type "id") (like links:type "%cite%"))]) - links-db-rows (seq-map (lambda (l) - (pcase-let ((`(,source ,dest ,type ,node-id) l)) - (if node-id - (list source node-id "ref") - (list source dest type)))) links-db-rows) - links-with-empty-refs (seq-filter (lambda (link) (string-match-p "cite" (nth 2 link))) links-db-rows))) - (let* ((empty-refs (delete-dups (seq-map (lambda (link) (nth 1 link)) links-with-empty-refs))) - (fake-nodes (seq-map 'org-roam-ui--create-fake-node empty-refs)) - ;; Try to update real nodes that are reference with a title build from - ;; their bibliography entry. Check configuration here for avoid unneeded - ;; iteration though nodes. - (nodes-db-rows (if org-roam-ui-retitle-ref-nodes (seq-map 'org-roam-ui--retitle-node nodes-db-rows) nodes-db-rows)) - (nodes-db-rows (append nodes-db-rows fake-nodes)) - (response `((nodes . ,(mapcar (apply-partially #'org-roam-ui-sql-to-alist (append nodes-names nil)) nodes-db-rows)) - (links . ,(mapcar (apply-partially #'org-roam-ui-sql-to-alist '(source target type)) links-db-rows)) - (tags . ,(seq-mapcat #'seq-reverse (org-roam-db-query [:select :distinct tag :from tags])))))) - (websocket-send-text org-roam-ui-ws-socket (json-encode `((type . "graphdata") (data . ,response))))))) - - + "Get roam data, make JSON, send through websocket to org-roam-ui." + (let* ((nodes-names + [id + file + title + level + pos + olp + properties + tags]) + (old (not (fboundp 'org-roam-db-map-citations))) + (links-db-rows (if old + (org-roam-ui--separate-ref-links + (org-roam-ui--get-links old)) + (seq-concatenate + 'list + (org-roam-ui--separate-ref-links + (org-roam-ui--get-cites)) + (org-roam-ui--get-links)))) + (links-with-empty-refs (org-roam-ui--filter-citations links-db-rows)) + (empty-refs (delete-dups (seq-map + (lambda (link) + (nth 1 link)) + links-with-empty-refs))) + (nodes-db-rows (org-roam-ui--get-nodes)) + (fake-nodes (seq-map 'org-roam-ui--create-fake-node empty-refs)) + ;; Try to update real nodes that are reference with a title build + ;; from their bibliography entry. Check configuration here for avoid + ;; unneeded iteration though nodes. + (retitled-nodes-db-rows (if org-roam-ui-retitle-ref-nodes + (seq-map 'org-roam-ui--retitle-node + nodes-db-rows) + nodes-db-rows)) + (complete-nodes-db-rows (append retitled-nodes-db-rows fake-nodes)) + (response `((nodes . ,(mapcar + (apply-partially + #'org-roam-ui-sql-to-alist + (append nodes-names nil)) + complete-nodes-db-rows)) + (links . ,(mapcar + (apply-partially + #'org-roam-ui-sql-to-alist + '(source target type)) + links-db-rows)) + (tags . ,(seq-mapcat + #'seq-reverse + (org-roam-db-query + [:select :distinct tag :from tags])))))) + (when old + (message "[org-roam-ui] You are not using the latest version of org-roam. +This database model won't be supported in the future, please consider upgrading.")) + (websocket-send-text org-roam-ui-ws-socket (json-encode + `((type . "graphdata") + (data . ,response)))))) + + +(defun org-roam-ui--filter-citations (links) + "Filter out the citations from LINKS." + (seq-filter + (lambda (link) + (string-match-p "cite" (nth 2 link))) + links)) + +(defun org-roam-ui--get-nodes () + "." + (org-roam-db-query [:select [id + file + title + level + pos + olp + properties + (funcall group-concat tag + (emacsql-escape-raw \, ))] + :as tags + :from nodes + :left-join tags + :on (= id node_id) + :group :by id])) + +(defun org-roam-ui--get-links (&optional old) + "Get the cites and links tables as rows from the org-roam-db. +Optionally set OLD to t to use the old db model (where the cites +were in the same table as the links)." +(if (not old) + (org-roam-db-query + `[:select [links:source + links:dest + links:type] + :from links + :where (= links:type "id")]) + ;; Left outer join on refs means any id link (or cite link without a + ;; corresponding node) will have 'nil for the `refs:node-id' value. Any + ;; cite link where a node has that `:ROAM_REFS:' will have a value. + (org-roam-db-query + `[:select [links:source + links:dest + links:type + refs:node-id] + :from links + :left :outer :join refs :on (= links:dest refs:ref) + :where (or + (= links:type "id") + (like links:type "%cite%"))]))) + +(defun org-roam-ui--get-cites () + "Get the citations when using the new db-model." + (org-roam-db-query + `[:select [citations:node-id citations:cite-key refs:node-id] + :from citations + :left :outer :join refs :on (= citations:cite-key refs:ref)])) + +(defun org-roam-ui--separate-ref-links (links &optional old) + "Create separate entries for LINKS with existing reference nodes. +Optionally set OLD to t to support old citations db-model. + +Convert any cite links that have nodes with associated refs to an +id based link of type `ref' while removing the 'nil `refs:node-id' +from all other links." + + (if (not old) + (seq-map + (lambda (link) + (pcase-let ((`(,source ,dest ,node-id) link)) + (if node-id + (list source node-id "ref") + (list source dest "cite")))) + links) + (seq-map + (lambda (link) + (pcase-let ((`(,source ,dest ,type ,node-id) link)) + (if node-id + (list source node-id "ref") + (list source dest type)))) + links))) (defun org-roam-ui--update-current-node () "Send the current node data to the web-socket." - (when (and (websocket-openp org-roam-ui-ws-socket) (org-roam-buffer-p) (buffer-file-name (buffer-base-buffer))) + (when (and (websocket-openp org-roam-ui-ws-socket) + (org-roam-buffer-p) + (buffer-file-name (buffer-base-buffer))) (let* ((node (org-roam-id-at-point))) (unless (string= org-roam-ui--ws-current-node node) (setq org-roam-ui--ws-current-node node) - (websocket-send-text org-roam-ui-ws-socket (json-encode `((type . "command") (data . ((commandName . "follow") (id . ,node)))))))))) + (websocket-send-text org-roam-ui-ws-socket + (json-encode `((type . "command") + (data . ((commandName . "follow") + (id . ,node)))))))))) (defun org-roam-ui--update-theme () @@ -422,10 +529,14 @@ TODO: Split this up." (if org-roam-ui-sync-theme (if (boundp 'doom-themes--colors) (let* - ((colors (butlast doom-themes--colors (- (length doom-themes--colors) 25))) + ((colors (butlast doom-themes--colors + (- (length doom-themes--colors) 25))) doom-theme) (progn - (dolist (color colors) (push (cons (car color) (car (cdr color))) doom-theme))) + (dolist (color colors) + (push + (cons (car color) (car (cdr color))) + doom-theme))) (setq ui-theme doom-theme)) (setq ui-theme (org-roam-ui-get-theme))) (when org-roam-ui-custom-theme @@ -438,19 +549,23 @@ TODO: Split this up." (when (boundp 'org-roam-dailies-directory) (let ((daily-dir (if (file-name-absolute-p org-roam-dailies-directory) (expand-file-name org-roam-dailies-directory) - (concat org-roam-directory org-roam-dailies-directory)))) - (websocket-send-text ws (json-encode `((type . "variables") - (data . - (("dailyDir" . - ,daily-dir) - ("roamDir" . ,org-roam-directory))))))))) + (expand-file-name + (file-name-concat org-roam-directory + org-roam-dailies-directory))))) + (websocket-send-text ws + (json-encode + `((type . "variables") + (data . + (("dailyDir" . + ,daily-dir) + ("roamDir" . ,org-roam-directory))))))))) (defun org-roam-ui-sql-to-alist (column-names rows) "Convert sql result to alist for json encoding. ROWS is the sql result, while COLUMN-NAMES is the columns to use." (let (res) (while rows - ;; emacsql does not want to give us the tags as a list, so we post process it + ;; I don't know how to get the tags as a simple list, so we post process it (if (not (string= (car column-names) "tags")) (push (cons (pop column-names) (pop rows)) res) (push (cons (pop column-names) @@ -480,14 +595,15 @@ ROWS is the sql result, while COLUMN-NAMES is the columns to use." ;;;; interactive commands ;;;###autoload -(defun orui-open () +(defun org-roam-ui-open () "Ensure `org-roam-ui' is running, then open the `org-roam-ui' webpage." (interactive) - (or org-roam-ui-mode (org-roam-ui-mode)) - (funcall org-roam-ui-browser-function (format "http://localhost:%d" org-roam-ui-port))) + (unless org-roam-ui-mode (org-roam-ui-mode)) + (funcall org-roam-ui-browser-function + (format "http://localhost:%d" org-roam-ui-port))) ;;;###autoload -(defun orui-node-zoom (&optional id speed padding) +(defun org-roam-ui-node-zoom (&optional id speed padding) "Move the view of the graph to current node. or optionally a node of your choosing. Optionally takes three arguments: @@ -496,26 +612,42 @@ The SPEED in ms it takes to make the transition. The PADDING around the nodes in the viewport." (interactive) (if-let ((node (or id (org-roam-id-at-point)))) - (websocket-send-text org-roam-ui-ws-socket (json-encode `((type . "command") (data . - ((commandName . "zoom") (id . ,node) (speed . ,speed) (padding . ,padding))))))) - (message "No node found.")) + (websocket-send-text org-roam-ui-ws-socket + (json-encode `((type . "command") + (data . ((commandName . "zoom") + (id . ,node) + (speed . ,speed) + (padding . ,padding)))))) + (message "No node found."))) ;;;###autoload -(defun orui-node-local (&optional id speed padding) +(defun org-roam-ui-node-local (&optional id speed padding) "Open the local graph view of the current node. Optionally with ID (string), SPEED (number, ms) and PADDING (number, px)." (interactive) (if-let ((node (or id (org-roam-id-at-point)))) - (websocket-send-text org-roam-ui-ws-socket (json-encode `((type . "command") (data . - ((commandName . "local") (id . ,node) (speed . ,speed) (padding . ,padding))))))) - (message "No node found.")) + (websocket-send-text org-roam-ui-ws-socket + (json-encode `((type . "command") + (data . ((commandName . "local") + (id . ,node) + (speed . ,speed) + (padding . ,padding)))))) + (message "No node found."))) ;;;###autoload -(defun orui-sync-theme () +(defun org-roam-ui-sync-theme () "Sync your current Emacs theme with org-roam-ui." (interactive) - (websocket-send-text org-roam-ui-ws-socket (json-encode `((type . "theme") (data . ,(org-roam-ui--update-theme)))))) + (websocket-send-text org-roam-ui-ws-socket + (json-encode `((type . "theme") + (data . ,(org-roam-ui--update-theme)))))) + +;;; Obsolete commands +(define-obsolete-function-alias 'orui-open 'org-roam-ui-open "0.1") +(define-obsolete-function-alias 'orui-node-local 'org-roam-ui-node-local "0.1") +(define-obsolete-function-alias 'orui-node-zoom 'org-roam-ui-node-zoom "0.1") +(define-obsolete-function-alias 'orui-sync-theme 'org-roam-ui-sync-theme "0.1") ;;;###autoload (define-minor-mode org-roam-ui-follow-mode @@ -527,9 +659,9 @@ Optionally with ID (string), SPEED (number, ms) and PADDING (number, px)." (if org-roam-ui-follow-mode (progn (add-hook 'post-command-hook #'org-roam-ui--update-current-node) - (message "Org-Roam-UI will now follow you around.")) + (message "org-roam-ui will now follow you around.")) (remove-hook 'post-command-hook #'org-roam-ui--update-current-node) - (message "Org-Roam-UI will now leave you alone."))) + (message "org-roam-ui will now leave you alone."))) (provide 'org-roam-ui) ;;; org-roam-ui.el ends here |