~cytrogen/.emacs.d

2274abff2c30596eb2b0f0d484edee5b2013e303 — Cytrogen 3 months ago 416a231
feat: 添加书单功能和 org-agenda 自定义视图

- org-structure.org: 添加"想读"和"读完"两个 capture 模板
- pkg-org.el: 添加 org-agenda 自定义视图 (C-c a r)
  - rw: 查看想读的书
  - rb: 查看正在读的书
  - rd: 查看读完的书
  - is: 查看分享的项目
- pkg-org.el: 添加书单操作快捷键
  - S: 开始阅读(从想读转为书籍笔记)
  - F: 完成阅读(创建读后感)
2 files changed, 105 insertions(+), 0 deletions(-)

M config/pkg-org.el
M org-structure.org
M config/pkg-org.el => config/pkg-org.el +93 -0
@@ 139,6 139,99 @@ Format: ((key label file ((subkey sublabel headline template override-file) ...)
;; Org Workflow Configuration
;; Agenda, refile, and capture setup
(with-eval-after-load 'org
  ;; --- Custom Agenda Views ---
  ;; 将"想读"的书转为"正在读"(创建书籍笔记并删除原条目)
  (defun my/reading-start-book ()
    "Start reading a book: create a note entry and remove from wishlist."
    (interactive)
    (org-agenda-check-type t 'agenda 'tags)
    (let* ((marker (or (org-get-at-bol 'org-marker)
                       (org-agenda-error)))
           (buffer (marker-buffer marker))
           (pos (marker-position marker))
           book-name)
      ;; 获取书名
      (with-current-buffer buffer
        (goto-char pos)
        (setq book-name (org-get-heading t t t t)))
      ;; 确认操作
      (when (y-or-n-p (format "开始阅读《%s》?" book-name))
        ;; 删除原条目
        (org-agenda-kill)
        ;; 创建书籍笔记
        (let ((org-capture-templates
               `(("b" "书籍笔记" entry
                  (file+headline ,(expand-file-name "reading.org" org-directory) "书籍笔记")
                  ,(format "** 《%s》笔记 %%^{序号}: %%^{标题}\n%%U\n\n%%?" book-name)))))
          (org-capture nil "b")))))

  ;; 将"正在读"的书标记为"读完"(创建读后感)
  (defun my/reading-finish-book ()
    "Finish reading a book: create a review entry."
    (interactive)
    (org-agenda-check-type t 'agenda 'tags)
    (let* ((marker (or (org-get-at-bol 'org-marker)
                       (org-agenda-error)))
           (buffer (marker-buffer marker))
           (pos (marker-position marker))
           book-name)
      ;; 从标题中提取书名(格式:《书名》笔记 X: 标题)
      (with-current-buffer buffer
        (goto-char pos)
        (let ((heading (org-get-heading t t t t)))
          (if (string-match "《\\([^》]+\\)》" heading)
              (setq book-name (match-string 1 heading))
            (setq book-name (read-string "书名: ")))))
      ;; 创建读后感
      (let ((org-capture-templates
             `(("d" "读后感" entry
                (file+headline ,(expand-file-name "reading.org" org-directory) "读完")
                ,(format "** 《%s》读后感\n:PROPERTIES:\n:FINISHED: %%U\n:END:\n\n%%?" book-name)))))
        (org-capture nil "d"))))

  ;; 辅助函数:跳过不在指定父标题下的条目
  (defun my/org-agenda-skip-unless-parent (parent-headline)
    "Skip entry unless its parent headline matches PARENT-HEADLINE."
    (let ((dominated nil))
      (save-excursion
        (while (and (not dominated) (org-up-heading-safe))
          (when (string= (org-get-heading t t t t) parent-headline)
            (setq dominated t))))
      (if dominated
          nil  ; 不跳过
        (save-excursion (org-end-of-subtree t)))))  ; 跳过

  (setq org-agenda-custom-commands
        '(;; Reading lists
          ("r" . "Reading (书单)")
          ("rw" "想读的书" tags "LEVEL=2"
           ((org-agenda-files (list (expand-file-name "reading.org" org-directory)))
            (org-agenda-overriding-header "想读的书")
            (org-agenda-prefix-format "  ")
            (org-agenda-skip-function '(my/org-agenda-skip-unless-parent "想读"))))
          ("rb" "正在读 (书籍笔记)" tags "LEVEL=2"
           ((org-agenda-files (list (expand-file-name "reading.org" org-directory)))
            (org-agenda-overriding-header "正在读的书")
            (org-agenda-prefix-format "  ")
            (org-agenda-skip-function '(my/org-agenda-skip-unless-parent "书籍笔记"))))
          ("rd" "读完的书" tags "LEVEL=2"
           ((org-agenda-files (list (expand-file-name "reading.org" org-directory)))
            (org-agenda-overriding-header "读完的书")
            (org-agenda-prefix-format "  ")
            (org-agenda-skip-function '(my/org-agenda-skip-unless-parent "读完"))))
          ;; Inbox items
          ("i" . "Inbox")
          ("is" "分享项目" tags "LEVEL=2"
           ((org-agenda-files (list (expand-file-name "inbox.org" org-directory)))
            (org-agenda-overriding-header "分享的项目/工具")
            (org-agenda-prefix-format "  ")
            (org-agenda-skip-function '(my/org-agenda-skip-unless-parent "分享"))))))

  ;; Agenda 快捷键:书单操作
  (with-eval-after-load 'org-agenda
    (define-key org-agenda-mode-map (kbd "S") #'my/reading-start-book)
    (define-key org-agenda-mode-map (kbd "F") #'my/reading-finish-book))

  ;; --- Refile Configuration ---
  ;; 强化 Refile 功能,允许将条目移动到项目或分类的具体标题下
  (setq org-refile-use-outline-path 'file)

M org-structure.org => org-structure.org +12 -0
@@ 182,12 182,24 @@
   :HEADLINE: Inbox
   :TEMPLATE: * TODO Read: %^{Title}\n  :PROPERTIES:\n  :SOURCE: %^{Source|Web|Book|Paper|Other}\n  :URL: %^{Url}\n  :ADDED: %U\n  :END:\n  %?
   :END:
** 想读
   :PROPERTIES:
   :KEY:      w
   :HEADLINE: 想读
   :TEMPLATE: ** %^{书名}\n:PROPERTIES:\n:SOURCE: %^{来源}\n:ADDED: %U\n:END:\n%?
   :END:
** 书籍笔记
   :PROPERTIES:
   :KEY:      b
   :HEADLINE: 书籍笔记
   :TEMPLATE: ** 《%^{书名}》笔记 %^{序号}: %^{标题}\n%U\n\n%?
   :END:
** 读完
   :PROPERTIES:
   :KEY:      d
   :HEADLINE: 读完
   :TEMPLATE: ** 《%^{书名}》读后感\n:PROPERTIES:\n:FINISHED: %U\n:END:\n\n%?
   :END:
** 课程笔记
   :PROPERTIES:
   :KEY:      c