feat: rewrite spine-index-model for shelf grouping
This commit is contained in:
@@ -63,119 +63,86 @@ Returns the rendered string."
|
||||
(ht ("app_title" "spine")
|
||||
("shelves" (or (spine-shelves) '()))))
|
||||
|
||||
(defun spine-index-model (books &optional filter selected-id)
|
||||
(defun spine-index-model (books &optional shelf-filter selected-id)
|
||||
"Build the view model ht for templates/index.mustache from BOOKS.
|
||||
FILTER controls which books are shown:
|
||||
nil - concise: READING + 5 most recent WANT, others as summary links
|
||||
all - full listing, no truncation
|
||||
read - only READ books
|
||||
want - only WANT books
|
||||
SHELF-FILTER limits to one shelf by name (string). nil = all shelves.
|
||||
SELECTED-ID expands the matching book's detail section."
|
||||
(let* ((status-labels
|
||||
'(("WANT" . "on deck")
|
||||
("READING" . "reading")
|
||||
("READ" . "read")
|
||||
("ABANDONED" . "abandoned")))
|
||||
(format-icons
|
||||
(let* ((format-icons
|
||||
'(("hardcover" . "ti-book")
|
||||
("ebook" . "ti-device-tablet")
|
||||
("audiobook" . "ti-headphones")))
|
||||
(order '("WANT" "READING" "READ" "ABANDONED"))
|
||||
(valid-filters '("all" "read" "want"))
|
||||
(effective-filter (if (member filter valid-filters) filter nil))
|
||||
(grouped (make-hash-table :test 'equal))
|
||||
(total (length books))
|
||||
(reading-count 0))
|
||||
(all-shelves (or (spine-shelves) '()))
|
||||
(total (length books)))
|
||||
;; Group books by shelf
|
||||
(dolist (book books)
|
||||
(when (equal "READING" (plist-get book :status))
|
||||
(cl-incf reading-count)))
|
||||
(dolist (book books)
|
||||
(let ((status (or (plist-get book :status) "WANT")))
|
||||
(push book (gethash status grouped))))
|
||||
(let ((shelf (or (plist-get book :shelf) "Uncategorized")))
|
||||
(push book (gethash shelf 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
|
||||
(if (and (null effective-filter)
|
||||
(member status '("READ" "ABANDONED")))
|
||||
(push (ht ("label" (cdr (assoc status status-labels)))
|
||||
("count" (length group-books))
|
||||
("href" (format "/index?filter=%s"
|
||||
(downcase status))))
|
||||
summary-groups)
|
||||
(when (or (null effective-filter)
|
||||
(string= (downcase status) effective-filter)
|
||||
(string= effective-filter "all"))
|
||||
(let ((book-models nil))
|
||||
(dolist (book group-books)
|
||||
(let* ((id (plist-get book :id))
|
||||
(selected (equal id selected-id))
|
||||
(fmt (plist-get book :format))
|
||||
(rating (plist-get book :rating))
|
||||
(notes (plist-get book :notes))
|
||||
(status (or (plist-get book :status) "WANT"))
|
||||
(model
|
||||
(shelf-nav nil))
|
||||
;; Build shelf nav from all shelves (including empty)
|
||||
(dolist (shelf all-shelves)
|
||||
(push (ht ("name" shelf)
|
||||
("href" (format "/index?shelf=%s" shelf))
|
||||
("current" (equal shelf shelf-filter)))
|
||||
shelf-nav))
|
||||
(setq shelf-nav (nreverse shelf-nav))
|
||||
;; Build shelf groups
|
||||
(dolist (shelf all-shelves)
|
||||
(let ((shelf-books (nreverse (gethash shelf grouped))))
|
||||
(when (or (null shelf-filter) (equal shelf shelf-filter))
|
||||
(let ((book-models nil))
|
||||
(dolist (book shelf-books)
|
||||
(let* ((id (plist-get book :id))
|
||||
(selected (equal id selected-id))
|
||||
(fmt (plist-get book :format))
|
||||
(rating (plist-get book :rating))
|
||||
(notes (plist-get book :notes))
|
||||
(model
|
||||
(ht
|
||||
("format_icon"
|
||||
(or (cdr (assoc fmt format-icons)) ""))
|
||||
("title" (plist-get book :title))
|
||||
("author" (or (plist-get book :author) ""))
|
||||
("tags_inline"
|
||||
(let ((tags (plist-get book :tags)))
|
||||
(when tags
|
||||
(mapconcat (lambda (tag) (concat ":" tag ":"))
|
||||
tags " "))))
|
||||
("rec_via" (plist-get book :rec_by))
|
||||
("rating_display"
|
||||
(when (and rating (> (length rating) 0))
|
||||
(make-string (string-to-number rating) ?\u2605)))
|
||||
("selected" selected)
|
||||
("detail"
|
||||
(when selected
|
||||
(ht
|
||||
("format_icon"
|
||||
(or (cdr (assoc fmt format-icons)) ""))
|
||||
("status_class" (downcase status))
|
||||
("status_label" (downcase status))
|
||||
("title" (plist-get book :title))
|
||||
("author" (or (plist-get book :author) ""))
|
||||
("tags_inline"
|
||||
(let ((tags (plist-get book :tags)))
|
||||
(when tags
|
||||
(mapconcat (lambda (tag) (concat ":" tag ":"))
|
||||
tags " "))))
|
||||
("rec_via" (plist-get book :rec_by))
|
||||
("rating_display"
|
||||
(when (and rating (> (length rating) 0))
|
||||
(make-string (string-to-number rating) ?\u2605)))
|
||||
("selected" selected)
|
||||
("detail"
|
||||
(when selected
|
||||
(ht
|
||||
("meta"
|
||||
(let ((added (plist-get book :added))
|
||||
(fmt (plist-get book :format)))
|
||||
(cond
|
||||
((and added fmt)
|
||||
(format "started %s \302\267 %s" added fmt))
|
||||
(added (format "started %s" added))
|
||||
(fmt (format "format: %s" fmt))
|
||||
(t ""))))
|
||||
("notes"
|
||||
(when notes
|
||||
(cl-loop for (date text) in notes
|
||||
collect (ht
|
||||
("date" (format "[%s]" date))
|
||||
("text" text))))))))
|
||||
)))
|
||||
(push model book-models)))
|
||||
(push (ht ("label" (cdr (assoc status status-labels)))
|
||||
("count" (length group-books))
|
||||
("books" (nreverse book-models)))
|
||||
groups)))))))
|
||||
("meta"
|
||||
(let ((added (plist-get book :added))
|
||||
(fmt (plist-get book :format)))
|
||||
(cond
|
||||
((and added fmt)
|
||||
(format "started %s \302\267 %s" added fmt))
|
||||
(added (format "started %s" added))
|
||||
(fmt (format "format: %s" fmt))
|
||||
(t ""))))
|
||||
("notes"
|
||||
(when notes
|
||||
(cl-loop for (date text) in notes
|
||||
collect (ht
|
||||
("date" (format "[%s]" date))
|
||||
("text" text))))))))
|
||||
)))
|
||||
(push model book-models)))
|
||||
(push (ht ("label" shelf)
|
||||
("count" (length shelf-books))
|
||||
("books" (nreverse book-models)))
|
||||
groups)))))
|
||||
(ht ("app_title" "spine")
|
||||
("total_books" total)
|
||||
("reading_count" reading-count)
|
||||
("current_filter" effective-filter)
|
||||
("summary_groups" (nreverse summary-groups))
|
||||
("shelf_count" (length all-shelves))
|
||||
("current_shelf" shelf-filter)
|
||||
("shelf_nav" shelf-nav)
|
||||
("groups" (nreverse groups))
|
||||
("minibuffer"
|
||||
(when selected-id
|
||||
@@ -184,37 +151,13 @@ SELECTED-ID expands the matching book's detail section."
|
||||
(puthash (plist-get b :id) b books-by-id))
|
||||
(let ((book (gethash selected-id books-by-id)))
|
||||
(when book
|
||||
(let ((status (or (plist-get book :status) "WANT")))
|
||||
(ht ("actions"
|
||||
(append
|
||||
(cond
|
||||
((equal status "WANT")
|
||||
(list
|
||||
(ht ("command" "status") ("label" "Mark reading")
|
||||
("href" (format "/edit?id=%s&action=status&value=READING" selected-id)))
|
||||
(ht ("command" "note") ("label" "Add note")
|
||||
("href" (format "/edit?id=%s&action=note" selected-id)))))
|
||||
((equal status "READING")
|
||||
(list
|
||||
(ht ("command" "status") ("label" "Mark read")
|
||||
("href" (format "/edit?id=%s&action=status&value=READ" selected-id)))
|
||||
(ht ("command" "status") ("label" "Abandon")
|
||||
("href" (format "/edit?id=%s&action=status&value=ABANDONED" selected-id)))
|
||||
(ht ("command" "note") ("label" "Add note")
|
||||
("href" (format "/edit?id=%s&action=note" selected-id)))))
|
||||
((equal status "READ")
|
||||
(list
|
||||
(ht ("command" "status") ("label" "Read again")
|
||||
("href" (format "/edit?id=%s&action=status&value=READING" selected-id)))
|
||||
(ht ("command" "note") ("label" "Add note")
|
||||
("href" (format "/edit?id=%s&action=note" selected-id)))))
|
||||
((equal status "ABANDONED")
|
||||
(list
|
||||
(ht ("command" "status") ("label" "Try again")
|
||||
("href" (format "/edit?id=%s&action=status&value=READING" selected-id)))))
|
||||
(t nil))
|
||||
nil))))))))
|
||||
)))))
|
||||
(ht ("actions"
|
||||
(list
|
||||
(ht ("command" "note") ("label" "Add note")
|
||||
("href" (format "/edit?id=%s&action=note" selected-id)))
|
||||
(ht ("command" "rating") ("label" "Set rating")
|
||||
("href" (format "/edit?id=%s&action=rating" selected-id)))))))))))
|
||||
))))
|
||||
(defun spine-add-book (&rest args)
|
||||
"Add a new book to shelf `:shelf' in `spine-org-file'.
|
||||
Keyword arguments: :title :author :shelf :format :isbn :cover
|
||||
|
||||
Reference in New Issue
Block a user