diff options
Diffstat (limited to 'modules/calibredb-epub-config.el')
| -rw-r--r-- | modules/calibredb-epub-config.el | 134 |
1 files changed, 74 insertions, 60 deletions
diff --git a/modules/calibredb-epub-config.el b/modules/calibredb-epub-config.el index 1e6437d26..38aa0de05 100644 --- a/modules/calibredb-epub-config.el +++ b/modules/calibredb-epub-config.el @@ -6,46 +6,17 @@ ;; Layer: 4 (Optional). ;; Category: O/D/P. ;; Load shape: eager. -;; Eager reason: none; optional ebook workflow, a command-loaded deferral -;; candidate for Phase 4. -;; Top-level side effects: one add-hook, one advice-add, package config. -;; Runtime requires: user-constants, subr-x. +;; Eager reason: none; ebook commands can load by command. +;; Top-level side effects: one hook, one advice, package config. +;; Runtime requires: user-constants, subr-x, transient. ;; Direct test load: yes. ;; -;; This module provides a comprehensive ebook management and reading experience -;; within Emacs, integrating CalibreDB for library management and Nov for EPUB -;; reading. +;; CalibreDB and Nov integration for browsing the Calibre library and reading +;; EPUBs inside Emacs. The module adds a curated CalibreDB transient, filter +;; helpers, Nov typography, image centering, and reader-to-library navigation. ;; -;; FEATURES: -;; - CalibreDB integration for managing your Calibre ebook library -;; - Nov mode for reading EPUB files with customized typography and layout -;; - Seamless navigation between Nov reading buffers and CalibreDB entries -;; - Image centering in EPUB documents without modifying buffer text -;; - Quick filtering and searching within your ebook library -;; -;; KEY BINDINGS: -;; - M-B: Open CalibreDB library browser -;; - In CalibreDB search mode: -;; - l: Filter by tag -;; - L: Clear all filters -;; - In Nov mode: -;; - z: Open current EPUB in external viewer (zathura) -;; - C-c C-b: Jump to CalibreDB entry for current book -;; - m: Set bookmark -;; - b: List bookmarks -;; -;; WORKFLOW: -;; 1. Press M-B to browse your Calibre library -;; 2. Use filters (l for tags, L to clear) to narrow results -;; 3. Open an EPUB to read it in Nov with optimized typography -;; 4. While reading, use C-c C-b to jump back to the book's metadata -;; 5. Use z to open in external reader when needed -;; -;; CONFIGURATION NOTES: -;; - Prefers EPUB format when available, falls back to PDF -;; - Centers images in EPUB documents using display properties -;; - Applies custom typography with larger fonts for comfortable reading -;; - Uses visual-fill-column for centered text with appropriate margins +;; EPUB is preferred when available; external opening remains available for +;; formats or workflows better handled outside Emacs. ;;; Code: @@ -62,6 +33,15 @@ ;; calibredb commands the curated menu drives (all autoloaded by calibredb) (declare-function calibredb-switch-library "calibredb" ()) +(declare-function calibredb-search-keyword-filter "calibredb-search") + +;; calibredb's filter-scope flags (set in `cj/--calibredb-open-to-favorites'); +;; declared special so the assignments compile clean when calibredb is absent. +(defvar calibredb-tag-filter-p) +(defvar calibredb-favorite-filter-p) +(defvar calibredb-author-filter-p) +(defvar calibredb-date-filter-p) +(defvar calibredb-format-filter-p) (declare-function calibredb-filter-by-book-format "calibredb" ()) (declare-function calibredb-filter-by-author-sort "calibredb" ()) (declare-function calibredb-search-clear-filter "calibredb" ()) @@ -116,6 +96,26 @@ which re-applies `calibredb-search-filter' instead." (setq calibredb-sort-by field) (calibredb-search-refresh-or-resume)) +(defun cj/--calibredb-open-to-favorites (&rest _) + "Filter the calibredb search to books tagged `calibredb-favorite-keyword'. +Advice (:after) on `calibredb' so every launch lands on the favorite-keyword +books (Craig's \"in-progress\" reading list); clear with L / x to see the +whole library. Scopes to the tag field (sets `calibredb-tag-filter-p', +clears the other filter-scope flags), because a bare keyword filter matches +the keyword in any field -- title, author, or the description -- and would +surface books that merely mention it. No-op unless a non-empty string +keyword is set." + (when (and (boundp 'calibredb-favorite-keyword) + (stringp calibredb-favorite-keyword) + (not (string-empty-p calibredb-favorite-keyword)) + (fboundp 'calibredb-search-keyword-filter)) + (setq calibredb-tag-filter-p t + calibredb-favorite-filter-p nil + calibredb-author-filter-p nil + calibredb-date-filter-p nil + calibredb-format-filter-p nil) + (calibredb-search-keyword-filter calibredb-favorite-keyword))) + (use-package calibredb :commands calibredb :bind @@ -184,7 +184,10 @@ which re-applies `calibredb-search-filter' instead." (setq calibredb-order "asc") (setq calibredb-id-width 7) (setq calibredb-favorite-icon "🔖") - (setq calibredb-favorite-keyword "in-progress")) + (setq calibredb-favorite-keyword "in-progress") + ;; Open every calibredb launch (dashboard, M-x, elsewhere) filtered to the + ;; in-progress favorites; L / x clears to the whole library. + (advice-add 'calibredb :after #'cj/--calibredb-open-to-favorites)) ;; ------------------------------ Nov Epub Reader ------------------------------ @@ -207,7 +210,6 @@ Adjust it live with `cj/nov-widen-text' and `cj/nov-narrow-text'.") (if (and buffer-file-name (string-match-p "\\.epub\\'" buffer-file-name)) (progn - ;; Load nov if not already loaded (unless (featurep 'nov) (require 'nov nil t)) ;; Call nov-mode if available, otherwise fallback to default behavior @@ -404,6 +406,12 @@ Try to use the Calibre book id from the parent folder name (for example, (calibredb-search-keyword-filter "") (message "CalibreDB: no metadata; showing all")))))) +(require 'system-lib) +;; nov renders epub via shr, which paints with manual `face' properties. Left in +;; `global-font-lock-mode' font-lock overwrites them and the book loses its +;; colors, the same issue as elfeed-show and mu4e-view. Exclude nov-mode. +(cj/exclude-from-global-font-lock 'nov-mode) + (use-package nov :mode ("\\.epub\\'" . nov-mode) @@ -432,14 +440,15 @@ Try to use the Calibre book id from the parent folder name (for example, ;; ------------------------- Nov bookmark naming ------------------------------- ;; In a nov buffer "m" is bound to `bookmark-set' (above). nov's -;; `nov-bookmark-make-record' names the record after `(buffer-name)' -- the EPUB -;; filename, extension and all. Rebuild it as "Author, Title" parsed from the -;; filename: under Calibre's "<Title> - <Author>.epub" naming the filename is -;; more complete than the EPUB's embedded metadata (which carries truncated -;; titles and author-sort "Last, First" forms). - -(defun cj/--nov-clean-title (s) - "Clean a title or author S parsed from an EPUB filename, or nil when blank. +;; Both nov (EPUB) and pdf-view (PDF) name a new bookmark after the buffer -- +;; the file's name, extension and all. Rebuild it as "Author, Title" parsed +;; from the filename: under Calibre's "<Title> - <Author>.<ext>" naming the +;; filename is more complete than the file's embedded metadata (which carries +;; truncated titles and author-sort "Last, First" forms). One :filter-return +;; advice serves both record functions; the parser is extension-agnostic. + +(defun cj/--reading-clean-title (s) + "Clean a title or author S parsed from a book filename, or nil when blank. Restores a colon where Calibre sanitized \":\" to \"_\" (\"Frege_ A Guide\" -> \"Frege: A Guide\"), turns any leftover underscore into a space, and collapses runs of whitespace." @@ -449,34 +458,39 @@ collapses runs of whitespace." (out (string-trim (replace-regexp-in-string "[ \t]+" " " spaced)))) (and (not (string-empty-p out)) out)))) -(defun cj/--nov-bookmark-name-from-file (path) - "Return \"Author, Title\" derived from an EPUB PATH's filename, or nil. +(defun cj/--reading-bookmark-name-from-file (path) + "Return \"Author, Title\" derived from a book PATH's filename, or nil. Splits the filename (sans extension) on its last \" - \" into title and author per Calibre's \"<Title> - <Author>\" convention, restoring colons and reordering to \"Author, Title\". Falls back to the cleaned whole name when -there is no \" - \" separator." +there is no \" - \" separator. Extension-agnostic, so it serves EPUB and PDF." (when (and (stringp path) (not (string-empty-p path))) (let ((base (file-name-sans-extension (file-name-nondirectory path)))) (if (string-match "\\`\\(.+\\) - \\(.+\\)\\'" base) - (let ((title (cj/--nov-clean-title (match-string 1 base))) - (author (cj/--nov-clean-title (match-string 2 base)))) + (let ((title (cj/--reading-clean-title (match-string 1 base))) + (author (cj/--reading-clean-title (match-string 2 base)))) (cond ((and author title) (format "%s, %s" author title)) (title title) (author author) (t nil))) - (cj/--nov-clean-title base))))) - -(defun cj/--nov-bookmark-rename-record (record) - "Replace RECORD's bookmark name with \"Author, Title\" from its EPUB filename. -Advice (:filter-return) on `nov-bookmark-make-record'. RECORD is -\(NAME . ALIST) carrying a `filename'; left unchanged when no name derives." - (let ((name (cj/--nov-bookmark-name-from-file + (cj/--reading-clean-title base))))) + +(defun cj/--reading-bookmark-rename-record (record) + "Replace RECORD's bookmark name with \"Author, Title\" from its filename. +Advice (:filter-return) on `nov-bookmark-make-record' and +`pdf-view-bookmark-make-record'. RECORD is (NAME . ALIST) carrying a +`filename'; left unchanged when no name derives." + (let ((name (cj/--reading-bookmark-name-from-file (alist-get 'filename (cdr record))))) (if name (cons name (cdr record)) record))) (with-eval-after-load 'nov (advice-add 'nov-bookmark-make-record :filter-return - #'cj/--nov-bookmark-rename-record)) + #'cj/--reading-bookmark-rename-record)) + +(with-eval-after-load 'pdf-view + (advice-add 'pdf-view-bookmark-make-record :filter-return + #'cj/--reading-bookmark-rename-record)) (defun cj/--nov-image-padding-cols (col-width img-px font-width-px) "Return left-padding columns to center an IMG-PX-wide image in COL-WIDTH cols. |
