@@ 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