From 47940f707aceea6066f8c9860207724c5d30f9e0 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 6 Jun 2026 15:19:20 -0500 Subject: fix(nov): name EPUB bookmarks "Author, Title" from the filename In a nov buffer m is bound to bookmark-set. nov's nov-bookmark-make-record names the record after the buffer name, the raw EPUB filename. So bookmarks read like "Frege_ A Guide for the Perplexed - Edward Kanterian.epub". I advise nov-bookmark-make-record to rebuild the name from the record's filename: split on the last " - " into title and author per Calibre's " - <Author>.epub" naming, restore the colon Calibre sanitized to "_ ", and reorder to "Author, Title". That book becomes "Edward Kanterian, Frege: A Guide for the Perplexed". I pulled the name from the filename rather than the embedded EPUB metadata on purpose. In real books the embedded metadata is the worse copy (truncated titles, authors in "Last, First" sort form, lost punctuation), while the filenames come from Calibre's curated database. A separate task tracks embedding the good metadata back into the files. cj/--nov-clean-title and cj/--nov-bookmark-name-from-file are pure and carry the logic. The advice is a thin wrapper. 10 ERT tests cover colon restoration, the last-separator split, and the no-separator fallback. --- modules/calibredb-epub-config.el | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'modules') diff --git a/modules/calibredb-epub-config.el b/modules/calibredb-epub-config.el index 4243e509..9f09e392 100644 --- a/modules/calibredb-epub-config.el +++ b/modules/calibredb-epub-config.el @@ -327,6 +327,54 @@ Try to use the Calibre book id from the parent folder name (for example, ("t" . nov-goto-toc) ("C-c C-b" . cj/nov-jump-to-calibredb))) +;; ------------------------- 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. +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." + (when (stringp s) + (let* ((colon (replace-regexp-in-string "_ " ": " s)) + (spaced (replace-regexp-in-string "_" " " colon)) + (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. +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." + (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)))) + (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 + (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)) + (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. FONT-WIDTH-PX is the column width in pixels; clamped up to 1 so a zero or -- cgit v1.2.3