~cytrogen/.emacs.d

b917a65585691f21847821e0a042de1f4e75de42 — Cytrogen a month ago b255015
feat(reading): 添加 URL 阅读模式和增强 EPUB/PDF 体验

- 修正 Copyright 年份为 2026
- 新增 URL 阅读模式:C-x C-f 直接打开 URL,自动启用 olivetti 居中
  + visual-line 排版
- 添加 my/toggle-reading-mode 手动切换阅读模式
- EPUB 阅读(nov.el)自动启用 olivetti 居中
- 添加 olivetti 包安装
1 files changed, 117 insertions(+), 5 deletions(-)

M config/pkg-reading.el
M config/pkg-reading.el => config/pkg-reading.el +117 -5
@@ 1,6 1,6 @@
;;; pkg-reading.el --- RSS reader, OPDS browser, and ebook tools -*- lexical-binding: t -*-

;; Copyright (C) 2024 Cytrogen
;; Copyright (C) 2026 Cytrogen

;;; Commentary:



@@ 52,6 52,9 @@
  (package-refresh-contents)
  (package-install 'elfeed))

(unless (package-installed-p 'olivetti)
  (package-install 'olivetti))

(unless (package-installed-p 'elfeed-protocol)
  (package-install 'elfeed-protocol))



@@ 62,6 65,45 @@
  (package-install 'pdf-tools))

;;; ============================================================
;;; URL Reading Mode
;;; ============================================================

;; 启用 url-handler-mode,可以直接 C-x C-f 打开 URL
(url-handler-mode 1)

;; 配置 olivetti 居中宽度
(setq olivetti-body-width 80)

(defun my/url-buffer-p ()
  "Check if current buffer was opened from a URL."
  (and buffer-file-name
       (string-match-p "^https?://" buffer-file-name)))

(defun my/setup-url-reading-mode ()
  "Setup comfortable reading mode for URL buffers."
  (when (my/url-buffer-p)
    (visual-line-mode 1)
    (olivetti-mode 1)
    (read-only-mode 1)
    (message "URL reading mode enabled")))

;; 打开 URL 文件时自动启用阅读模式
(add-hook 'find-file-hook #'my/setup-url-reading-mode)

;; 手动切换阅读模式的命令
(defun my/toggle-reading-mode ()
  "Toggle reading mode (visual-line + olivetti)."
  (interactive)
  (if olivetti-mode
      (progn
        (visual-line-mode -1)
        (olivetti-mode -1)
        (message "Reading mode disabled"))
    (visual-line-mode 1)
    (olivetti-mode 1)
    (message "Reading mode enabled")))

;;; ============================================================
;;; EPUB Reader (nov.el)
;;; ============================================================



@@ 70,7 112,8 @@

(with-eval-after-load 'nov
  (setq nov-text-width t)
  (add-hook 'nov-mode-hook 'visual-line-mode))
  (add-hook 'nov-mode-hook 'visual-line-mode)
  (add-hook 'nov-mode-hook 'olivetti-mode))

;;; ============================================================
;;; PDF Reader (pdf-tools)


@@ 136,6 179,23 @@
  (elfeed-update))

;;; ============================================================
;;; elfeed + eww Integration
;;; ============================================================

(defun my/elfeed-show-eww ()
  "View current elfeed article in eww browser."
  (interactive)
  (let ((link (elfeed-entry-link elfeed-show-entry)))
    (when link
      (eww link))))

(with-eval-after-load 'elfeed-show
  (define-key elfeed-show-mode-map (kbd "b") #'my/elfeed-show-eww))

(add-hook 'eww-mode-hook #'visual-line-mode)
(add-hook 'eww-mode-hook #'olivetti-mode)

;;; ============================================================
;;; OPDS Browser (Calibre)
;;; ============================================================



@@ 173,8 233,19 @@
        (prog1 (libxml-parse-xml-region (point) (point-max))
          (kill-buffer))))))

(defvar my/opds--next-page-url nil
  "URL for the next page of OPDS results.")

(defun my/opds--parse-entries (xml)
  "Parse OPDS XML and return list of entries."
  ;; 解析下一页链接
  (let ((links (dom-by-tag xml 'link)))
    (setq my/opds--next-page-url
          (dom-attr (cl-find-if (lambda (link)
                                   (string= "next" (dom-attr link 'rel)))
                                 links)
                    'href)))
  ;; 解析书籍条目
  (let ((entries (dom-by-tag xml 'entry)))
    (mapcar (lambda (entry)
              (let* ((title (dom-text (dom-by-tag entry 'title)))


@@ 237,9 308,20 @@
          (let* ((choices (mapcar (lambda (e)
                                    (cons (my/opds--format-entry e) e))
                                  entries))
                 (selection (completing-read "OPDS: " choices nil t)))
                 ;; 如果有下一页,添加选项
                 (choices (if my/opds--next-page-url
                              (append choices '(("▶ 下一页..." . :next-page)))
                            choices))
                 (selection (completing-read
                             (format "OPDS (%d items%s): "
                                     (length entries)
                                     (if my/opds--next-page-url " + more" ""))
                             choices nil t)))
            (when selection
              (my/opds--select-entry (cdr (assoc selection choices))))))
              (let ((selected (cdr (assoc selection choices))))
                (if (eq selected :next-page)
                    (my/opds--browse-url my/opds--next-page-url)
                  (my/opds--select-entry selected))))))
      (error (message "OPDS error: %s" (error-message-string err))))))

(defun my/opds-browse ()


@@ 298,7 380,8 @@
  (my/reading--load-config)
  (let* ((query (read-string "Search books: "))
         (base-url (my/reading--get-config :opds :url))
         (search-url (format "%s/search?query=%s"
         ;; Calibre 搜索格式: /opds/search/关键词
         (search-url (format "%s/search/%s"
                             (replace-regexp-in-string "/opds/?$" "/opds" base-url)
                             (url-hexify-string query))))
    (my/opds--browse-url search-url)))


@@ 315,5 398,34 @@
    (my/reading--setup-elfeed))
  (message "Reading configuration reloaded"))

;;; ============================================================
;;; PDF Tools Enhancement
;;; ============================================================

;; PDF 页码偏移量
(defvar-local my/pdf-page-offset 0
  "当前 PDF 的页码偏移量。书籍页码 = 物理页码 - 偏移量")

(defun my/pdf-set-offset ()
  "设置当前 PDF 的页码偏移量。"
  (interactive)
  (let* ((current-page (pdf-view-current-page))
         (book-page (read-number (format "当前是 PDF 第 %d 页,对应书籍第几页?" current-page)))
         (offset (- current-page book-page)))
    (setq my/pdf-page-offset offset)
    (message "偏移量设为 %d(书籍第1页 = PDF第%d页)" offset (1+ offset))))

(defun my/pdf-goto-page ()
  "跳转到书籍页码(自动应用偏移量)。"
  (interactive)
  (let* ((book-page (read-number "跳转到书籍第几页: "))
         (pdf-page (+ book-page my/pdf-page-offset)))
    (pdf-view-goto-page pdf-page)))

;; 绑定快捷键
(with-eval-after-load 'pdf-view
  (define-key pdf-view-mode-map (kbd "O") 'my/pdf-set-offset)
  (define-key pdf-view-mode-map (kbd "G") 'my/pdf-goto-page))

(provide 'pkg-reading)
;;; pkg-reading.el ends here