diff --git a/spine.el b/spine.el
index 82ff5b0..e434f07 100644
--- a/spine.el
+++ b/spine.el
@@ -69,12 +69,17 @@ Returns the rendered string."
("audiobook" "ti-headphones" "Audiobook"))
"Alist of (format-key icon label) for book format display.")
-(defun spine--format-date (org-date-str)
- "Format Org date \"[2026-02-20]\" to \"20 Feb\"."
+(defconst spine--status-defs
+ '(("want" "Want") ("reading" "Reading") ("read" "Read"))
+ "Alist of (key label) for reading status options.")
+
+(defun spine--format-date (org-date-str &optional include-year)
+ "Format Org date \"[2026-02-20]\" to \"20 Feb\".
+With optional INCLUDE-YEAR, returns \"20 Feb 2026\"."
(when (and org-date-str (> (length org-date-str) 0))
(let ((clean (replace-regexp-in-string "\\[\\|\\]" "" org-date-str)))
(condition-case nil
- (format-time-string "%e %b" (date-to-time clean))
+ (format-time-string (if include-year "%e %b %Y" "%e %b") (date-to-time clean))
(error org-date-str)))))
(defun spine--parse-title-status (title)
@@ -92,33 +97,35 @@ TITLE may have a leading TODO prefix like \"WANT \", \"READING \", \"READ \"."
(defun spine-book-model (book)
"Build view model `ht` for a single BOOK plist, for templates/book.mustache."
(let* ((id (plist-get book :id))
- (title (or (plist-get book :title) ""))
+ (raw-title (or (plist-get book :title) ""))
(author (or (plist-get book :author) ""))
(fmt (plist-get book :format))
(added (plist-get book :added))
(notes (plist-get book :notes))
(rec-by (plist-get book :rec_by))
(rec-note (plist-get book :rec_note))
- (title-info (spine--parse-title-status title))
+ (tags (plist-get book :tags))
+ (rating (plist-get book :rating))
+ (shelf-name (plist-get book :shelf))
+ (title-info (spine--parse-title-status raw-title))
(clean-title (nth 0 title-info))
(status-class (nth 1 title-info))
(status-label (nth 2 title-info))
- (meta (cond
- ((and added fmt)
- (format "started %s \302\267 %s" (spine--format-date added) fmt))
- (added (format "started %s" (spine--format-date added)))
- (fmt (format "format: %s" fmt))
- (t "")))
- (formats (mapcar
- (lambda (fd)
- (ht ("icon" (nth 1 fd))
- ("label" (nth 2 fd))
- ("active" (and fmt (equal (car fd) fmt)))))
- spine--format-defs))
+ (format-def (and fmt (assoc fmt spine--format-defs)))
+ (format-icon (if format-def (nth 1 format-def) "ti-book"))
+ (format-label (if format-def (nth 2 format-def) ""))
+ (rating-display
+ (cond
+ ((and rating (> (length rating) 0))
+ (make-string (string-to-number rating) ?\u2605))
+ ((equal status-class "read") "rated —")
+ (t "rate when finished")))
+ (has-rating (and rating (> (length rating) 0)))
(notes-model
(mapcar (lambda (n)
(ht ("date" (car n))
- ("text" (cadr n))))
+ ("text" (cadr n))
+ ("edit_url" (format "/edit?id=%s&action=note&id=%s" id (car n)))))
notes))
(recommendation
(when (and rec-by (> (length rec-by) 0))
@@ -130,16 +137,36 @@ TITLE may have a leading TODO prefix like \"WANT \", \"READING \", \"READ \"."
(split-string rec-by " +" t) "")))
(ht ("initials" initials)
("by" rec-by)
- ("note" (or rec-note "")))))))
+ ("note" (or rec-note ""))))))
+ (status-options
+ (mapcar (lambda (sd)
+ (ht ("name" (cadr sd))
+ ("selected" (equal (car sd) status-class))))
+ spine--status-defs))
+ (shelf-options
+ (let ((shelves (or (spine-shelves) '())))
+ (mapcar (lambda (s)
+ (ht ("name" s)
+ ("selected" (and shelf-name (equal s shelf-name)))))
+ shelves))))
(ht ("cover_icon" "ti-book")
("id" id)
("title" clean-title)
("author" author)
("status_class" status-class)
("status_label" status-label)
- ("meta" meta)
- ("progress_label" "")
- ("formats" formats)
+ ("shelf_name" (or shelf-name ""))
+ ("format_icon" format-icon)
+ ("format_label" format-label)
+ ("tags_display"
+ (if tags (mapconcat (lambda (tag) (format "%s" tag)) tags " ") ""))
+ ("added_display" (if added (spine--format-date added t) ""))
+ ("started_display" (if added (spine--format-date added t) ""))
+ ("finished_display" "—")
+ ("rating_display" rating-display)
+ ("has_rating" has-rating)
+ ("status_options" status-options)
+ ("shelf_options" shelf-options)
("notes" notes-model)
("recommendation" recommendation)
("back_url" (format "/index?id=%s" id)))))
diff --git a/templates/book.mustache b/templates/book.mustache
index 80a4933..bd9d5ba 100644
--- a/templates/book.mustache
+++ b/templates/book.mustache
@@ -5,100 +5,157 @@
Spine — {{title}}
-
-
-
- ← Back to shelf
+
+
+
-
-
-
-
- {{title}}
- {{author}}
-
- {{#status_class}}
-
{{status_label}}
- {{/status_class}}
- {{#meta}}
-
{{meta}}
- {{/meta}}
+
-
-
- {{#formats}}
-
- {{/formats}}
-
+
+
+
+
{{title}}
+
{{author}}
+ {{#status_class}}
+
{{status_label}}
+ {{/status_class}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{#format_label}}
+ | Format |
+ {{format_label}} |
+ {{/format_label}}
+ {{#tags_display}}
+ | Tags |
+ {{{tags_display}}} |
+ {{/tags_display}}
+ {{#added_display}}
+ | Added |
+ {{added_display}} |
+ {{/added_display}}
+ {{#started_display}}
+ | Started |
+ {{started_display}} |
+ {{/started_display}}
+ | Finished |
+ {{finished_display}} |
+ | Rating |
+ {{rating_display}} |
+
+
- {{#notes}}
-
Reading log
-
{{#notes}}
-
-
{{date}}
-
{{text}}
+
+
Reading log
+ {{#notes}}
+
+ {{date}}
+ {{text}}
+
+ {{/notes}}
+
+
+
+
{{/notes}}
-
- {{/notes}}
-
-
-
-
+ {{#recommendation}}
+
+
On your radar
+
+
{{initials}}
+
{{by}}
"{{note}}"
+
+
+ {{/recommendation}}
-
+
+
+
+
- {{#recommendation}}
-
On your radar
-
-
{{initials}}
-
{{by}}
"{{note}}"
- {{/recommendation}}
-
-
-
-
-
-
-
-
+
+