From 653ee3442dec7698d7370d46f7447a714d2fe0f3 Mon Sep 17 00:00:00 2001 From: James Brechtel Date: Sun, 21 Jun 2026 22:02:12 -0400 Subject: [PATCH] feat: concise view sorts all books by LAST_MODIFIED, add property to sample data --- sample-books.org | 5 ++ spine.el | 119 ++++++++++++++++++++------------- test/spine-index-model-test.el | 2 +- 3 files changed, 79 insertions(+), 47 deletions(-) diff --git a/sample-books.org b/sample-books.org index fd27ca5..40c1f95 100644 --- a/sample-books.org +++ b/sample-books.org @@ -10,6 +10,7 @@ :REC_NOTE: Le Guin at her most human :RATING: :ADDED: [2026-04-01] +:LAST_MODIFIED: [2026-04-01] :ID: 9a2b-lhd :END: @@ -23,6 +24,7 @@ :REC_NOTE: :RATING: :ADDED: [2026-05-10] +:LAST_MODIFIED: [2026-05-10] :ID: 7f3c-dun :END: @@ -36,6 +38,7 @@ :REC_NOTE: :RATING: :ADDED: [2026-06-01] +:LAST_MODIFIED: [2026-06-01] :ID: 5e8d-pir :END: @@ -49,6 +52,7 @@ :REC_NOTE: If you liked Player of Games, this one will wreck you :RATING: :ADDED: [2026-02-20] +:LAST_MODIFIED: [2026-03-09] :ID: 8c1e-uow :END: :LOGBOOK: @@ -68,6 +72,7 @@ :REC_NOTE: :RATING: 5 :ADDED: [2025-11-15] +:LAST_MODIFIED: [2025-11-15] :ID: 3a1b-aj :END: :LOGBOOK: diff --git a/spine.el b/spine.el index 6819e43..b6aa140 100644 --- a/spine.el +++ b/spine.el @@ -105,6 +105,20 @@ SELECTED-ID expands the matching book's detail section." (push book (gethash status grouped)))) (let ((groups nil) (summary-groups nil)) + ;; Concise view: sort all books by :LAST_MODIFIED:, take top 5 + (when (null effective-filter) + (clrhash grouped) + (dolist (b (cl-subseq (sort (copy-sequence books) + (lambda (a b) + (let ((a-date (plist-get a :last_modified)) + (b-date (plist-get b :last_modified))) + (cond + ((null a-date) nil) + ((null b-date) t) + (t (string> a-date b-date)))))) + 0 (min (length books) 5))) + (let ((status (or (plist-get b :status) "WANT"))) + (push b (gethash status grouped))))) (dolist (status order) (let ((group-books (nreverse (gethash status grouped)))) (when group-books @@ -118,18 +132,6 @@ SELECTED-ID expands the matching book's detail section." (when (or (null effective-filter) (string= (downcase status) effective-filter) (string= effective-filter "all")) - (when (and (null effective-filter) - (string= status "WANT")) - (setq group-books - (cl-subseq (sort group-books - (lambda (a b) - (let ((a-date (plist-get a :added)) - (b-date (plist-get b :added))) - (cond - ((null a-date) nil) - ((null b-date) t) - (t (string> a-date b-date)))))) - 0 (min (length group-books) 5)))) (let ((book-models nil)) (dolist (book group-books) (let* ((id (plist-get book :id)) @@ -174,38 +176,6 @@ SELECTED-ID expands the matching book's detail section." collect (ht ("date" (format "[%s]" date)) ("text" text)))))))) - ("minibuffer" - (when selected - (let ((status (plist-get book :status))) - (ht ("actions" - (append - (cond - ((equal status "WANT") - (list - (ht ("command" "status") ("label" "Mark reading") - ("href" (format "/edit?id=%s&action=status&value=READING" id))) - (ht ("command" "note") ("label" "Add note") - ("href" (format "/edit?id=%s&action=note" id))))) - ((equal status "READING") - (list - (ht ("command" "status") ("label" "Mark read") - ("href" (format "/edit?id=%s&action=status&value=READ" id))) - (ht ("command" "status") ("label" "Abandon") - ("href" (format "/edit?id=%s&action=status&value=ABANDONED" id))) - (ht ("command" "note") ("label" "Add note") - ("href" (format "/edit?id=%s&action=note" id))))) - ((equal status "READ") - (list - (ht ("command" "status") ("label" "Read again") - ("href" (format "/edit?id=%s&action=status&value=READING" id))) - (ht ("command" "note") ("label" "Add note") - ("href" (format "/edit?id=%s&action=note" id))))) - ((equal status "ABANDONED") - (list - (ht ("command" "status") ("label" "Try again") - ("href" (format "/edit?id=%s&action=status&value=READING" id))))) - (t nil)) - nil)))))) ))) (push model book-models))) (push (ht ("label" (cdr (assoc status status-labels))) @@ -254,8 +224,8 @@ SELECTED-ID expands the matching book's detail section." (ht ("command" "status") ("label" "Try again") ("href" (format "/edit?id=%s&action=status&value=READING" selected-id))))) (t nil)) - nil))))))))) - )))) + nil)))))))) + ))))) (defun spine-add-book (&rest args) "Add a new WANT book to `spine-org-file'. Keyword arguments: :title :author :category :format :isbn :cover @@ -517,6 +487,63 @@ Set by test harnesses that only need the model functions.") (ht ("app_title" "spine"))))) (insert (spine-render "add.mustache" model))))))) +(defun httpd/edit (proc uri-path query request) + "Handle /edit: GET shows prompt page, POST processes actions. +Query params: + id - book ID + action - status | note | rating + value - target status / rating value (for status/rating actions) + text - note text (for note action) + date - optional date string (for note/read actions)" + (let ((method (caar request)) + (id (cadr (assoc "id" query))) + (action (cadr (assoc "action" query))) + (value (cadr (assoc "value" query))) + (text (cadr (assoc "text" query))) + (date (cadr (assoc "date" query)))) + (if (equal method "POST") + (progn + (cond + ((equal action "note") + (spine-add-note id text date)) + ((equal action "status") + (spine-set-status id value)) + ((equal action "rating") + (spine-set-rating id value))) + (httpd-redirect proc (format "/index?id=%s" id) 303)) + ;; GET: show prompt page for actions needing input + (if (and id (equal action "note")) + (let* ((books (spine-books)) + (book (cl-find id books :key (lambda (b) (plist-get b :id)) :test #'equal)) + (title (if book (plist-get book :title) "Unknown"))) + (httpd-with-buffer proc "text/html" + (insert (format +" + + + +Spine — note + + + +
+
+
spine · add note
+

%s

+
+ + + + + +
+
+
+ +" + title id (format-time-string "%Y-%m-%d")))))) + ;; Unknown action or missing id: redirect to index + (httpd-redirect proc "/index" 302)))) (defun httpd/ (proc uri-path query request) "Redirect root to /index." (httpd-redirect proc "/index" 302)) diff --git a/test/spine-index-model-test.el b/test/spine-index-model-test.el index 3718a86..15992d9 100644 --- a/test/spine-index-model-test.el +++ b/test/spine-index-model-test.el @@ -41,7 +41,7 @@ "Use of Weapons")))) (ert-deftest spine-index-model-concise-want-limited-to-five () - "Concise view limits WANT group to 5 books, sorted by :ADDED: desc." + "Concise view limits WANT group to 5 books, sorted by :LAST_MODIFIED: desc." (let* ((model (spine-index-model spine-index-model-test--books)) (groups (ht-get model "groups")) (want-group (cl-find "on deck" groups