diff options
Diffstat (limited to 'emacs/.emacs.d')
-rw-r--r-- | emacs/.emacs.d/init/ycp-markup.el | 3 | ||||
-rw-r--r-- | emacs/.emacs.d/init/ycp-org.el | 2 | ||||
-rw-r--r-- | emacs/.emacs.d/lisp/my/belf.el | 173 | ||||
-rw-r--r-- | emacs/.emacs.d/lisp/my/infobox.el | 23 | ||||
-rw-r--r-- | emacs/.emacs.d/lisp/my/my-emms.el | 2 | ||||
-rw-r--r-- | emacs/.emacs.d/lisp/my/my-github.el | 4 | ||||
-rw-r--r-- | emacs/.emacs.d/lisp/my/my-gitlab.el | 10 | ||||
-rw-r--r-- | emacs/.emacs.d/lisp/my/my-gnus.el | 2 | ||||
-rw-r--r-- | emacs/.emacs.d/lisp/my/my-nov.el | 12 | ||||
-rw-r--r-- | emacs/.emacs.d/lisp/my/my-web.el | 9 |
10 files changed, 214 insertions, 26 deletions
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/init/ycp-org.el b/emacs/.emacs.d/init/ycp-org.el index 6385a46..43ae6cb 100644 --- a/emacs/.emacs.d/init/ycp-org.el +++ b/emacs/.emacs.d/init/ycp-org.el @@ -450,7 +450,7 @@ ;; org man links (my-package ol-man (:delay 30) - (setq org-man-command 'woman)) + (setq org-man-command 'man)) (my-package ol (:delay 10) diff --git a/emacs/.emacs.d/lisp/my/belf.el b/emacs/.emacs.d/lisp/my/belf.el new file mode 100644 index 0000000..89f27c0 --- /dev/null +++ b/emacs/.emacs.d/lisp/my/belf.el @@ -0,0 +1,173 @@ +;;; belf.el -- Bookshelf, ebook library management -*- lexical-binding: t -*- + +;; Copyright (C) 2025 Free Software Foundation, Inc. + +;; Author: Yuchen Pei <id@ypei.org> +;; Package-Requires: ((emacs "29.4")) + +;; This file is part of dotted. + +;; dotted is free software: you can redistribute it and/or modify it under +;; the terms of the GNU Affero General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; dotted is distributed in the hope that it will be useful, but WITHOUT +;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General +;; Public License for more details. + +;; You should have received a copy of the GNU Affero General Public +;; License along with dotted. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Bookshelf, ebook library management. + +;;; Code: + +(require 'tabulated-list) +(require 'infobox) + +(defvar-keymap belf-mode-map + :parent tabulated-list-mode-map + "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 + ) + +(define-derived-mode belf-mode tabulated-list-mode "Bookshelf" + "Major mode for browsing a list of books." + (setq tabulated-list-format + [("Authors" 25 t) + ("Title" 48 t) + ("Year" 4 t)]) + (setq tabulated-list-padding 2) + (tabulated-list-init-header) + (setq revert-buffer-function #'belf-list-refresh-contents) + (hl-line-mode)) + +(defun belf-list-books () + (interactive) + (let ((buf (get-buffer-create "*Bookshelf*"))) + (with-current-buffer buf + (belf-mode) + (belf-list-refresh-contents)) + (pop-to-buffer-same-window buf))) + +(defun belf-list-refresh-contents (&rest _) + (setq-local tabulated-list-entries (belf-parse-all-file-names)) + (tabulated-list-print)) + +(defvar belf-dir "~/Documents" "Directory of books.") + +(defun belf-parse-all-file-names () + (seq-filter + #'identity + (seq-map + (lambda (f) + (when-let ((parsed (belf-parse-file-name f))) + (let-alist parsed + (list f (vector .authors .title .year))))) + (directory-files belf-dir t "\\.\\(epub\\|pdf\\|mobi\\|cbr\\|djvu\\)$")))) + +(defun belf-parse-file-name (file-name) + (let ((base (file-name-base file-name))) + (when (string-match "^\\(.*?\\) - \\(.*\\) (\\([0-9]*\\))" base) + `((authors . ,(match-string 1 base)) + (title . ,(match-string 2 base)) + (year . ,(match-string 3 base)))))) + +(defun belf-book-infobox (file-name) + (interactive) + (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) + (find-file (tabulated-list-get-id))) + +(defun belf-open-book-other-window () + (interactive) + (find-file-other-window (tabulated-list-get-id))) + +(provide 'belf) diff --git a/emacs/.emacs.d/lisp/my/infobox.el b/emacs/.emacs.d/lisp/my/infobox.el index 5698042..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 @@ -113,8 +118,8 @@ something like (end-of-line) (insert " -- " (buttonize "xdg-open" - (lambda (_) - (call-process "xdg-open" nil 0 nil filename)))) + (lambda (_) (call-process "xdg-open" nil 0 nil filename))) + " " (buttonize "find-file" (lambda (_) (find-file filename)))) (buffer-string)) `(infobox-exiftool ,filename) (called-interactively-p 'interactive) diff --git a/emacs/.emacs.d/lisp/my/my-emms.el b/emacs/.emacs.d/lisp/my/my-emms.el index e3e4d32..cdbe57a 100644 --- a/emacs/.emacs.d/lisp/my/my-emms.el +++ b/emacs/.emacs.d/lisp/my/my-emms.el @@ -387,7 +387,7 @@ artist/album/track." (emms-playlist-current-select-next) (if (string-equal (my-emms-current-album-name) current-album) (emms-start) - (my-emms-playlist-random-album nil)))) + (my-emms-playlist-random-album)))) (defvar-local my-emms-albums-cache (vector)) diff --git a/emacs/.emacs.d/lisp/my/my-github.el b/emacs/.emacs.d/lisp/my/my-github.el index 7caff57..e2d5f6a 100644 --- a/emacs/.emacs.d/lisp/my/my-github.el +++ b/emacs/.emacs.d/lisp/my/my-github.el @@ -25,7 +25,7 @@ ;; Github client. ;;; Code: - +(require 'my-web) (defun my-grok-github (url) "get github info of a project. @@ -93,7 +93,7 @@ License; name; description; homepage; created at" ) (defvar my-github-project-info-specs - `((html_url . "Clone") + `((html_url . ("URL" . my-forge-infobox-format-url)) (full_name . "Name") (description . "Description") (created_at . ("Created at" . my-gitlab-format-time-string)) diff --git a/emacs/.emacs.d/lisp/my/my-gitlab.el b/emacs/.emacs.d/lisp/my/my-gitlab.el index 27f3344..56542c0 100644 --- a/emacs/.emacs.d/lisp/my/my-gitlab.el +++ b/emacs/.emacs.d/lisp/my/my-gitlab.el @@ -75,17 +75,9 @@ (require 'my-buffer) (require 'my-web) (require 'my-magit) -(defun my-gitlab-format-url (url) - (concat url - " -- " (buttonize "clone" - (lambda (_) - (my-magit-clone url current-prefix-arg))) - " " (buttonize "context" - (lambda (_) - (funcall my-url-context-function url))))) (defvar my-gitlab-project-info-specs - `((http_url_to_repo . ("URL" . my-gitlab-format-url)) + `((http_url_to_repo . ("URL" . my-forge-infobox-format-url)) (name_with_namespace . "Name") (description . "Description") (created_at . ("Created at" . my-gitlab-format-time-string)) diff --git a/emacs/.emacs.d/lisp/my/my-gnus.el b/emacs/.emacs.d/lisp/my/my-gnus.el index 14dff82..6a2142b 100644 --- a/emacs/.emacs.d/lisp/my/my-gnus.el +++ b/emacs/.emacs.d/lisp/my/my-gnus.el @@ -162,7 +162,7 @@ The archiving target comes from `my-gnus-group-alist'." "The default inbox to be opened with `my-gnus-open-inbox'.") (defun my-gnus-open-inbox () (interactive) - (gnus-group-read-group t t my-gnus-inbox-group)) + (gnus-group-read-group 200 t my-gnus-inbox-group)) (defun my-gnus-start () (interactive) diff --git a/emacs/.emacs.d/lisp/my/my-nov.el b/emacs/.emacs.d/lisp/my/my-nov.el index 1bc8eca..816afc6 100644 --- a/emacs/.emacs.d/lisp/my/my-nov.el +++ b/emacs/.emacs.d/lisp/my/my-nov.el @@ -65,8 +65,16 @@ chapter title." nov-file-name dest staging))) (defun my-nov-set-margins () - (set-window-margins nil 3 2) - (set-window-fringes nil 0 0)) + ;; Does not work as well as setq left- and right-margin-width + ;; (set-window-margins nil 3 2) + (setq left-margin-width 3) + (setq right-margin-width 2) + ;; Does not work as well as setq left- and right-fringe-width + ;; (set-window-fringes nil 0 0) + (setq left-fringe-width 0) + (setq right-fringe-width 0) + (visual-line-mode) + ) (provide 'my-nov) ;;; my-nov.el ends here diff --git a/emacs/.emacs.d/lisp/my/my-web.el b/emacs/.emacs.d/lisp/my/my-web.el index 3d1f9d3..21b227d 100644 --- a/emacs/.emacs.d/lisp/my/my-web.el +++ b/emacs/.emacs.d/lisp/my/my-web.el @@ -223,5 +223,14 @@ https://emacs.stackexchange.com/questions/40887/in-org-mode-how-do-i-link-to-int (buffer-string) ))) +(defun my-forge-infobox-format-url (url) + (concat url + " -- " (buttonize "clone" + (lambda (_) + (my-magit-clone url current-prefix-arg))) + " " (buttonize "context" + (lambda (_) + (funcall my-url-context-function url))))) + (provide 'my-web) ;;; my-web.el ends here |