From 126330c1dc1b43240bfe41887e9388efbf68d9f3 Mon Sep 17 00:00:00 2001 From: James Brechtel Date: Sun, 21 Jun 2026 09:08:54 -0400 Subject: [PATCH] feat: add filter parameter to spine-index-model for concise default view --- spine.el | 133 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 82 insertions(+), 51 deletions(-) diff --git a/spine.el b/spine.el index a4d0316..8b45e4d 100644 --- a/spine.el +++ b/spine.el @@ -74,8 +74,13 @@ Returns the rendered string." ("existing_authors" (sort authors #'string<)) ("existing_categories" (sort categories #'string<))))) -(defun spine-index-model (books &optional selected-id) +(defun spine-index-model (books &optional 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 SELECTED-ID expands the matching book's detail section." (let* ((status-labels '(("WANT" . "on deck") @@ -87,6 +92,8 @@ SELECTED-ID expands the matching book's detail section." ("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)) @@ -96,64 +103,88 @@ SELECTED-ID expands the matching book's detail section." (dolist (book books) (let ((status (or (plist-get book :status) "WANT"))) (push book (gethash status grouped)))) - (let ((groups nil)) + (let ((groups nil) + (summary-groups nil)) (dolist (status order) (let ((group-books (nreverse (gethash status grouped)))) (when group-books - (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 - (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 + (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")) + (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)) + (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 (ht - ("meta" - (let ((added (plist-get book :added)) - (fmt (plist-get book :format))) - (cond - ((and added fmt) - (format "started %s ยท %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))))) + ("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))))))) (ht ("app_title" "spine") ("total_books" total) ("reading_count" reading-count) + ("current_filter" (or effective-filter "")) + ("summary_groups" (nreverse summary-groups)) ("groups" (nreverse groups)))))) - (defun spine-add-book (&rest args) "Add a new WANT book to `spine-org-file'. Keyword arguments: :title :author :category :format :isbn :cover