From 1302f96df75f4f93247beaafee0b0a780829be18 Mon Sep 17 00:00:00 2001 From: Yuchen Pei Date: Fri, 7 Feb 2025 09:52:11 +1100 Subject: [emacs] Flesh out bookshelf mode a bit Add follow mode. Display book cover in infobox. Render html description in infobox. --- emacs/.emacs.d/init/ycp-markup.el | 3 +- emacs/.emacs.d/lisp/my/belf.el | 89 +++++++++++++++++++++++++++++++++++++-- emacs/.emacs.d/lisp/my/infobox.el | 19 ++++++--- 3 files changed, 99 insertions(+), 12 deletions(-) (limited to 'emacs') diff --git a/emacs/.emacs.d/init/ycp-markup.el b/emacs/.emacs.d/init/ycp-markup.el index 5f21da7..0c7bbb2 100644 --- a/emacs/.emacs.d/init/ycp-markup.el +++ b/emacs/.emacs.d/init/ycp-markup.el @@ -113,7 +113,8 @@ (my-override nov-render-title) (my-override nov-scroll-up) (my-keybind nov-mode-map - "Q" #'my-nov-copy-buffer-file-with-staging) + "Q" #'my-nov-copy-buffer-file-with-staging + "i" #'imenu) ) ;;; json-mode diff --git a/emacs/.emacs.d/lisp/my/belf.el b/emacs/.emacs.d/lisp/my/belf.el index 035f44d..89f27c0 100644 --- a/emacs/.emacs.d/lisp/my/belf.el +++ b/emacs/.emacs.d/lisp/my/belf.el @@ -31,9 +31,14 @@ (defvar-keymap belf-mode-map :parent tabulated-list-mode-map - "i" #'belf-book-infobox + "F" #'belf-toggle-follow-mode + "d" #'belf-show-in-dired + "i" #'belf-book-infobox-at-point + "n" #'belf-next-line + "o" #'belf-open-book-other-window + "p" #'belf-previous-line "RET" #'belf-open-book - "o" #'belf-open-book-other-window) + ) (define-derived-mode belf-mode tabulated-list-mode "Bookshelf" "Major mode for browsing a list of books." @@ -77,9 +82,85 @@ (title . ,(match-string 2 base)) (year . ,(match-string 3 base)))))) -(defun belf-book-infobox () +(defun belf-book-infobox (file-name) (interactive) - (infobox-exiftool (tabulated-list-get-id))) + (belf-book-render-info (belf-exiftool-info file-name) file-name)) + +(defvar belf-exiftool-program "exiftool" "The exiftool program.") + +(defun belf-exiftool-info (file-name) + "Given a video URL, return an alist of its properties." + (with-temp-buffer + (call-process belf-exiftool-program nil t nil "-j" file-name) + (let ((start (point))) + (call-process-region + nil nil "jq" nil t nil + ".[0]|pick(.Title, .Author, .Creator, .Keywords, .Subject, .Publisher, .Identifier, .Series, .Title_sort, .Author_sort, .PageCount, .ISBN, .Language, .FileType, .Description)") + (goto-char start) + (json-read))) + ) + +(defun belf-book-cover (file-name) + "Get book cover. + +First look for an image file with the same file name. +Then for PDF, extract the first page. +For EPUB, looks for a cover image in the file. If not found, extract the first page." + (cond ((file-exists-p (file-name-with-extension file-name "jpg")) + (format "file://%s" (file-name-with-extension file-name "jpg"))) + (t nil))) + +(defun belf-book-infobox-at-point () + (interactive) + (let ((help-window-select (not belf-follow-mode))) + (belf-book-infobox (tabulated-list-get-id))) + ) + +(defun belf-book-render-info (info file-name) + (setf (alist-get 'Title info) + (concat (alist-get 'Title info) + " -- " + (buttonize + "xdg-open" + (lambda (_) (call-process "xdg-open" nil 0 nil file-name))) + " " (buttonize "find-file" (lambda (_) (find-file file-name)))) + (alist-get 'Thumbnail info) + (belf-book-cover file-name) + (alist-get 'Description info) + (when-let ((text (alist-get 'Description info))) + (with-temp-buffer + (insert text) + (shr-render-region (point-min) (point-max)) + (goto-char (point-min)) + (insert "\n") + (buffer-string)))) + (infobox-render + (infobox-translate info (infobox-default-specs info)) + `(belf-book-infobox ,file-name) + (called-interactively-p 'interactive))) + +(defvar belf-follow-mode nil "Whether follow mode is on.") + +(defun belf-toggle-follow-mode () + (interactive) + (setq belf-follow-mode (not belf-follow-mode))) + + +(defun belf-previous-line () + (interactive) + (previous-line) + (when belf-follow-mode + (belf-book-infobox-at-point))) + +(defun belf-next-line () + (interactive) + (next-line) + (when belf-follow-mode + (belf-book-infobox-at-point))) + +(defun belf-show-in-dired () + (interactive) + (dired-jump-other-window (tabulated-list-get-id))) (defun belf-open-book () (interactive) diff --git a/emacs/.emacs.d/lisp/my/infobox.el b/emacs/.emacs.d/lisp/my/infobox.el index 9c5c7b1..2a17dc9 100644 --- a/emacs/.emacs.d/lisp/my/infobox.el +++ b/emacs/.emacs.d/lisp/my/infobox.el @@ -31,7 +31,11 @@ (cond ((stringp v) v) ((eq v t) "YES") ((eq v :json-false) "NO") - ((seqp v) (mapconcat #'identity v ", ")) + ((seqp v) + (mapconcat + (lambda (x) (if (stringp x) x (prin1-to-string x))) + v + ", ")) (t (format "%s" v)))) (defun infobox-default-specs (info) @@ -66,14 +70,15 @@ something like ;; TODO: use a more standard function than ;; `my-make-filename-from-url' (when-let* ((thumb-url (alist-get "Thumbnail" info nil nil 'equal)) - (file-name (file-name-concat - "/tmp" - (my-make-filename-from-url thumb-url)))) - (url-copy-file (message thumb-url) file-name t) + (file-name (make-temp-name "/tmp/infobox-"))) + (url-copy-file thumb-url file-name t) (insert-image (create-image file-name nil nil - :max-width (window-width nil t))) + :max-width (window-pixel-width) + :max-height (/ (window-pixel-height) 2))) (insert "\n") - (setq n-rows (1+ n-rows))) + (setq n-rows (1+ n-rows)) + (setq info (assoc-delete-all "Thumbnail" info)) + ) (seq-do (lambda (pair) (when pair -- cgit v1.2.3