branch: elpa/gnosis
commit f20be60895b0968d60747ffecc9be824eb1546ac
Author: Thanos Apollo <[email protected]>
Commit: Thanos Apollo <[email protected]>
[Feature] Review topic with configurable link depth.
---
gnosis-dashboard.el | 49 ++++++++++++++++++++++++++----------------
gnosis.el | 62 ++++++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 85 insertions(+), 26 deletions(-)
diff --git a/gnosis-dashboard.el b/gnosis-dashboard.el
index 3defcbb2c6..c17c329a2f 100644
--- a/gnosis-dashboard.el
+++ b/gnosis-dashboard.el
@@ -306,23 +306,19 @@ If IDS is not provided, use current themata being
displayed."
:join review-log :on (= themata:id review-log:id)
:where (in themata:id ,(vconcat thema-ids))])))
(cl-loop for sublist in entries
- collect
- (let ((vec (vconcat
- (cl-loop for item in (cdr sublist)
- if (listp item)
- collect (mapconcat (lambda (x) (format "%s"
x)) item ",")
- else
- collect
- (let ((formatted (replace-regexp-in-string
"\n" " " (format "%s" item))))
- ;; Strip org-link markup, keeping only
descriptions
- (replace-regexp-in-string
- "\\[\\[id:[^]]+\\]\\[\\(.*?\\)\\]\\]"
- "\\1"
- formatted))))))
- ;; Format suspend column (last) as Yes/No
- (aset vec (1- (length vec))
- (if (equal (aref vec (1- (length vec))) "1") "Yes" "No"))
- (list (car sublist) vec)))))
+ for fields = (cl-loop for item in (cdr sublist)
+ if (listp item)
+ collect (mapconcat (lambda (x) (format "%s"
x)) item ",")
+ else collect
+ (let ((formatted (replace-regexp-in-string
"\n" " " (format "%s" item))))
+ (replace-regexp-in-string
+ "\\[\\[id:[^]]+\\]\\[\\(.*?\\)\\]\\]"
+ "\\1" formatted)))
+ ;; Last field is suspend (0/1) — format as Yes/No
+ collect (list (car sublist)
+ (vconcat (append (butlast fields)
+ (list (if (equal (car (last
fields)) "1")
+ "Yes" "No"))))))))
(defun gnosis-dashboard--update-entries (ids)
"Re-fetch and update tabulated-list entries for IDS."
@@ -1199,6 +1195,18 @@ Moves cursor to the beginning of the buffer after
sorting."
("t" "By tag" gnosis-dashboard-nodes-filter-by-tag)
("q" "Cancel" transient-quit-one)]])
+(defun gnosis-dashboard-nodes-review ()
+ "Review themata for node at point."
+ (interactive)
+ (gnosis-review-topic (tabulated-list-get-id)))
+
+(defun gnosis-dashboard-nodes-review-with-depth ()
+ "Review themata for node at point, prompting for link depths."
+ (interactive)
+ (gnosis-review-topic (tabulated-list-get-id)
+ (read-number "Forward link depth: " 1)
+ (read-number "Backlink depth: " 0)))
+
(transient-define-prefix gnosis-dashboard-nodes-mode-menu ()
"Transient menu for nodes dashboard mode."
[["Navigate"
@@ -1214,7 +1222,10 @@ Moves cursor to the beginning of the buffer after
sorting."
("b" "Show backlinks" gnosis-dashboard-nodes-show-backlinks)
("t" "Show themata links" gnosis-dashboard-nodes-show-themata-links)
("i" "Show isolated" gnosis-dashboard-nodes-show-isolated)
- ("d" "Show due" gnosis-dashboard-nodes-show-due)]])
+ ("d" "Show due" gnosis-dashboard-nodes-show-due)]
+ ["Review"
+ ("r" "Review topic" gnosis-dashboard-nodes-review)
+ ("R" "Review with depth" gnosis-dashboard-nodes-review-with-depth)]])
(defvar-keymap gnosis-dashboard-nodes-mode-map
:doc "Keymap for nodes dashboard."
@@ -1226,6 +1237,8 @@ Moves cursor to the beginning of the buffer after
sorting."
"t" #'gnosis-dashboard-nodes-show-themata-links
"i" #'gnosis-dashboard-nodes-show-isolated
"d" #'gnosis-dashboard-nodes-show-due
+ "r" #'gnosis-dashboard-nodes-review
+ "R" #'gnosis-dashboard-nodes-review-with-depth
"s" #'gnosis-dashboard-nodes-sort-menu
"SPC" #'gnosis-dashboard-nodes-search-menu
"l" #'gnosis-dashboard-nodes-filter-menu
diff --git a/gnosis.el b/gnosis.el
index bdf5972290..23b7cf3120 100644
--- a/gnosis.el
+++ b/gnosis.el
@@ -1500,17 +1500,63 @@ FN: Review function, defaults to
`gnosis-review-session'"
(topic-id (caar (org-gnosis-select 'id 'nodes `(= title
,topic-title)))))
topic-id))
+(defun gnosis-collect-nodes-at-depth (node-id &optional fwd-depth back-depth)
+ "Collect node IDs reachable from NODE-ID within depth limits.
+FWD-DEPTH is max hops for forward links (default 0).
+BACK-DEPTH is max hops for backlinks (default 0).
+Returns a deduplicated list including NODE-ID itself."
+ (let ((fwd-depth (or fwd-depth 0))
+ (back-depth (or back-depth 0))
+ (max-depth (max fwd-depth back-depth))
+ (visited (make-hash-table :test 'equal))
+ (queue (list node-id)))
+ (puthash node-id t visited)
+ (dotimes (level max-depth)
+ (when queue
+ (let* ((qvec (vconcat queue))
+ (neighbors (append
+ (when (< level fwd-depth)
+ (org-gnosis-select 'dest 'links
+ `(in source ,qvec) t))
+ (when (< level back-depth)
+ (org-gnosis-select 'source 'links
+ `(in dest ,qvec) t))))
+ (next-queue nil))
+ (dolist (neighbor neighbors)
+ (unless (gethash neighbor visited)
+ (puthash neighbor t visited)
+ (push neighbor next-queue)))
+ (setq queue next-queue))))
+ (hash-table-keys visited)))
+
;;;###autoload
-(defun gnosis-review-topic (&optional node-id)
- "Review gnosis for topic with NODE-ID."
- (interactive)
+(defun gnosis-review-topic (&optional node-id fwd-depth back-depth)
+ "Review themata linked to topic NODE-ID.
+FWD-DEPTH and BACK-DEPTH control forward/backlink traversal depth.
+With prefix arg, prompt for depths."
+ (interactive
+ (list nil
+ (when current-prefix-arg (read-number "Forward link depth: " 1))
+ (when current-prefix-arg (read-number "Backlink depth: " 0))))
(let* ((node-id (or node-id (gnosis-review--select-topic)))
- (node-title (car (org-gnosis-select 'title 'nodes `(= id ,node-id) t)))
- (gnosis-questions (gnosis-select 'source 'links `(= dest ,node-id) t)))
+ (fwd-depth (or fwd-depth 0))
+ (back-depth (or back-depth 0))
+ (node-title (car (org-gnosis-select 'title 'nodes
+ `(= id ,node-id) t)))
+ (node-ids (if (or (> fwd-depth 0) (> back-depth 0))
+ (gnosis-collect-nodes-at-depth
+ node-id fwd-depth back-depth)
+ (list node-id)))
+ (gnosis-questions (gnosis-select 'source 'links
+ `(in dest ,(vconcat node-ids)) t)))
(if (and gnosis-questions
- (y-or-n-p (format "Review %s thema(s) for '%s'?"
- (length gnosis-questions)
- node-title)))
+ (y-or-n-p
+ (format "Review %s thema(s) for '%s'%s?"
+ (length gnosis-questions) node-title
+ (if (> (length node-ids) 1)
+ (format " (%d nodes, fwd:%d back:%d)"
+ (length node-ids) fwd-depth back-depth)
+ ""))))
(gnosis-review-session gnosis-questions)
(message "No thema found for %s (id:%s)" node-title node-id))))