From 94c310a0b3e461610acd3fdb7c92866f98139389 Mon Sep 17 00:00:00 2001 From: James Brechtel Date: Sat, 20 Jun 2026 16:59:06 -0400 Subject: [PATCH] feat: add spine-render-index and empty-state view functions --- spine.el | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/spine.el b/spine.el index 4a42125..2a84e4d 100644 --- a/spine.el +++ b/spine.el @@ -120,6 +120,107 @@ Returns nil if the file is missing or cannot be parsed." spine-org-file (error-message-string err)) nil)))) +(defun spine-render-index (books &optional selected-id) + "Render the index page HTML for BOOKS, expanding the book matching SELECTED-ID." + (let ((order '("WANT" "READING" "READ" "ABANDONED")) + (grouped (make-hash-table :test 'equal))) + ;; Group books by status + (dolist (book books) + (let* ((status (or (plist-get book :status) "WANT"))) + (push book (gethash status grouped)))) + ;; Render + (with-temp-buffer + (insert "\n\n\n") + (insert "\n") + (insert "\n") + (insert "Spine\n\n\n") + (insert "
\n") + (insert "
Spine
\n") + ;; Each group in order + (dolist (status order) + (let ((group-books (nreverse (gethash status grouped)))) + (when group-books + (insert (format "
%s (%d)
\n" + status (length group-books))) + (dolist (book group-books) + (let ((selected (equal (plist-get book :id) selected-id))) + (insert (format "
\n" + (if selected " selected" ""))) + ;; Format glyph + (let ((fmt (plist-get book :format))) + (insert (format "%s\n" + (cond ((equal fmt "hardcover") "[H]") + ((equal fmt "ebook") "[E]") + ((equal fmt "audiobook") "[A]") + (t ""))))) + ;; Title + author + (insert "") + (insert (spine--h (plist-get book :title))) + (let ((author (plist-get book :author))) + (when author + (insert (format " %s" + (spine--h author))))) + (insert "\n") + ;; Tags + (let ((tags (plist-get book :tags))) + (when tags + (insert (format "%s\n" + (mapconcat (lambda (t) (concat ":" t ":")) + tags " "))))) + ;; Recommendation trail + (let ((rec-by (plist-get book :rec_by)) + (rec-note (plist-get book :rec_note))) + (when (and rec-by (> (length rec-by) 0)) + (insert + (format "%s%s\n" + (spine--h rec-by) + (if (and rec-note (> (length rec-note) 0)) + (concat ": " (spine--h rec-note)) + ""))))) + ;; Status pill + (let ((status (or (plist-get book :status) "WANT"))) + (insert (format "%s\n" + (downcase status) status))) + ;; Expanded detail + (when selected + (insert "
\n") + (dolist (note (plist-get book :notes)) + (insert (format + "
[%s] %s
\n" + (spine--h (car note)) + (spine--h (cadr note))))) + (let ((rating (plist-get book :rating))) + (when (and rating (> (length rating) 0)) + (insert (format "
Rating: %s/5
\n" + (spine--h rating))))) + (let ((isbn (plist-get book :isbn))) + (when (and isbn (> (length isbn) 0)) + (insert (format "
ISBN: %s
\n" + (spine--h isbn))))) + (let ((fmt (plist-get book :format))) + (when (and fmt (> (length fmt) 0)) + (insert (format "
Format: %s
\n" + (spine--h fmt))))) + (let ((added (plist-get book :added))) + (when (and added (> (length added) 0)) + (insert (format "
Added: %s
\n" + (spine--h added))))) + (insert "
\n")) + (insert "
\n")))))) + (insert "
\n\n\n") + (buffer-string)))) + +(defun spine-render-empty-state () + "Render a page indicating no books file was found." + (concat "\n\n\n" + "\n" + "\n" + "Spine\n\n\n" + "
\n" + "
Spine
\n" + "
No books yet.
\n" + "
\n\n\n")) + (defvar spine-skip-server-start nil "When non-nil, do not start the HTTP server. Set by test harnesses that only need the model functions.")