aboutsummaryrefslogtreecommitdiff
path: root/pearl.el
diff options
context:
space:
mode:
Diffstat (limited to 'pearl.el')
-rw-r--r--pearl.el54
1 files changed, 53 insertions, 1 deletions
diff --git a/pearl.el b/pearl.el
index 3f65617..da7161b 100644
--- a/pearl.el
+++ b/pearl.el
@@ -2897,7 +2897,8 @@ page cap was hit. Pure function, no side effects."
(name (pearl--source-name src))
(filter (plist-get src :filter)))
(with-temp-buffer
- (insert (format "#+title: Linear — %s\n" name))
+ (insert (format "#+title: Linear — %s\n"
+ (if pearl-title-case-headings (pearl--title-case name) name)))
(insert "#+STARTUP: show3levels\n")
(insert (format "#+TODO: %s\n"
(if (bound-and-true-p org-todo-keywords)
@@ -3228,6 +3229,57 @@ track correctly across the in-place inserts and deletes the merge performs."
(push (cons id (copy-marker (point) t)) markers))))))
(nreverse markers)))
+;;; Dirty detection (the save model's local scanners)
+
+(defun pearl--changed-comment-candidates ()
+ "Return comments under the issue at point whose body changed since fetch.
+Each is a plist (:comment-id :author-id :marker). Local only: compares
+`secure-hash' of `pearl--org-to-md' of each comment body to its stored
+`LINEAR-COMMENT-SHA256' (taken over the markdown Linear stored, so the
+comparison must round-trip through markdown, as `pearl-edit-current-comment'
+does). A candidate only means the body differs; ownership is decided later."
+ (let (candidates)
+ (save-excursion
+ (pearl--goto-heading-or-error)
+ (let ((issue-end (save-excursion (org-end-of-subtree t t) (point))))
+ (while (and (outline-next-heading) (< (point) issue-end))
+ (let ((cid (org-entry-get nil "LINEAR-COMMENT-ID")))
+ (when (and cid (not (string-empty-p cid)))
+ (let ((stored (or (org-entry-get nil "LINEAR-COMMENT-SHA256") ""))
+ (local (secure-hash 'sha256
+ (pearl--org-to-md (pearl--issue-body-at-point)))))
+ (unless (string= local stored)
+ (push (list :comment-id cid
+ :author-id (org-entry-get nil "LINEAR-COMMENT-AUTHOR-ID")
+ :marker (point-marker))
+ candidates))))))))
+ (nreverse candidates)))
+
+(defun pearl--issue-dirty-fields ()
+ "Return the locally-changed fields of the issue subtree at point, no network.
+A plist: `:title' and `:description' are booleans; `:comment-candidates' is the
+list from `pearl--changed-comment-candidates'. This is Phase A of the two-phase
+save scan -- comment ownership (own vs read-only) is classified separately once
+the viewer id is known (see `pearl--classify-comment-candidates')."
+ (save-excursion
+ (pearl--goto-heading-or-error)
+ (list :title (not (string= (secure-hash 'sha256 (pearl--issue-title-at-point))
+ (or (org-entry-get nil "LINEAR-TITLE-SHA256") "")))
+ :description (pearl--subtree-dirty-p)
+ :comment-candidates (pearl--changed-comment-candidates))))
+
+(defun pearl--classify-comment-candidates (candidates viewer-id)
+ "Split CANDIDATES into own dirty comments and read-only ones, by VIEWER-ID.
+Returns a plist (:own (...) :read-only (...)). A candidate is `:own' when its
+`:author-id' equals VIEWER-ID (see `pearl--comment-editable-p'); a non-own /
+bot / external comment is `:read-only' -- edited locally but not pushable."
+ (let (own read-only)
+ (dolist (c candidates)
+ (if (pearl--comment-editable-p (plist-get c :author-id) viewer-id)
+ (push c own)
+ (push c read-only)))
+ (list :own (nreverse own) :read-only (nreverse read-only))))
+
(defun pearl--merge-issues-into-buffer (issues)
"Merge normalized ISSUES into the current buffer by `LINEAR-ID'.
Same-source refresh semantics: an existing issue still in ISSUES is re-rendered