refactor: redesign book detail page to match journal mockup styling
- Replace Pico CSS with index-style custom CSS variables - Redesign layout: back breadcrumb, info table, shelf/status selects - Update spine-book-model: add shelf_name, format_icon/label, tags_display, date display fields, rating display, status/shelf options - Remove unused fields: meta, progress_label, formats array - Update spine-book-model-test.el: 23 tests cover all new fields - Add --text-danger CSS variable for remove button
This commit is contained in:
@@ -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 "<span class=\"tag\">%s</span>" 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)))))
|
||||
|
||||
Reference in New Issue
Block a user