feat: add LAST_MODIFIED property tracked on book creation and edits

This commit is contained in:
2026-06-21 21:58:16 -04:00
parent c505a3c239
commit fa6be9ff03
+37 -59
View File
@@ -174,6 +174,38 @@ SELECTED-ID expands the matching book's detail section."
collect (ht collect (ht
("date" (format "[%s]" date)) ("date" (format "[%s]" date))
("text" text)))))))) ("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 model book-models)))
(push (ht ("label" (cdr (assoc status status-labels))) (push (ht ("label" (cdr (assoc status status-labels)))
@@ -222,8 +254,8 @@ SELECTED-ID expands the matching book's detail section."
(ht ("command" "status") ("label" "Try again") (ht ("command" "status") ("label" "Try again")
("href" (format "/edit?id=%s&action=status&value=READING" selected-id))))) ("href" (format "/edit?id=%s&action=status&value=READING" selected-id)))))
(t nil)) (t nil))
nil)))))))) nil)))))))))
))))) ))))
(defun spine-add-book (&rest args) (defun spine-add-book (&rest args)
"Add a new WANT book to `spine-org-file'. "Add a new WANT book to `spine-org-file'.
Keyword arguments: :title :author :category :format :isbn :cover Keyword arguments: :title :author :category :format :isbn :cover
@@ -275,6 +307,8 @@ Signals an error on failure."
(format "%04x-%s" (format "%04x-%s"
(random 65535) (random 65535)
(substring (sha1 (concat title (number-to-string (random)))) 0 4))) (substring (sha1 (concat title (number-to-string (random)))) 0 4)))
;; Set last modified
(org-set-property "LAST_MODIFIED" (format-time-string "[%Y-%m-%d]"))
;; Set tags ;; Set tags
(when (and category (> (length category) 0)) (when (and category (> (length category) 0))
(org-set-tags (list category))) (org-set-tags (list category)))
@@ -416,6 +450,7 @@ Returns nil if the file is missing or cannot be parsed."
:rec_note (spine--prop pos "REC_NOTE") :rec_note (spine--prop pos "REC_NOTE")
:rating (spine--prop pos "RATING") :rating (spine--prop pos "RATING")
:added (spine--prop pos "ADDED") :added (spine--prop pos "ADDED")
:last_modified (spine--prop pos "LAST_MODIFIED")
:tags (org-element-property :tags hl) :tags (org-element-property :tags hl)
:notes (spine--extract-notes :notes (spine--extract-notes
(org-element-property :begin hl) (org-element-property :begin hl)
@@ -482,63 +517,6 @@ Set by test harnesses that only need the model functions.")
(ht ("app_title" "spine"))))) (ht ("app_title" "spine")))))
(insert (spine-render "add.mustache" model))))))) (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) (defun httpd/ (proc uri-path query request)
"Redirect root to /index." "Redirect root to /index."
(httpd-redirect proc "/index" 302)) (httpd-redirect proc "/index" 302))