feat: concise view sorts all books by LAST_MODIFIED, add property to sample data

This commit is contained in:
2026-06-21 22:02:12 -04:00
parent fa6be9ff03
commit 653ee3442d
3 changed files with 79 additions and 47 deletions
+73 -46
View File
@@ -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
"<!DOCTYPE html>
<html lang=\"en\">
<head><meta charset=\"utf-8\"/><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/>
<meta name=\"color-scheme\" content=\"light dark\"/>
<title>Spine — note</title>
<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@picocss/pico@2.1.1/css/pico.min.css\"/>
</head>
<body>
<main class=\"container\">
<article style=\"max-width:560px;margin-inline:auto;\">
<header><strong>spine · add note</strong></header>
<p><em>%s</em></p>
<form method=\"post\" action=\"/edit\">
<input type=\"hidden\" name=\"id\" value=\"%s\"/>
<input type=\"hidden\" name=\"action\" value=\"note\"/>
<label>Date <input type=\"date\" name=\"date\" value=\"%s\"/></label>
<label>Note <textarea name=\"text\" rows=\"3\" required autofocus></textarea></label>
<button type=\"submit\">Add note</button>
</form>
</article>
</main>
</body>
</html>"
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))