惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The GitHub Blog
The GitHub Blog
IT之家
IT之家
B
Blog RSS Feed
罗磊的独立博客
GbyAI
GbyAI
博客园 - Franky
Y
Y Combinator Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Google DeepMind News
Google DeepMind News
博客园 - 聂微东
N
Netflix TechBlog - Medium
博客园 - 三生石上(FineUI控件)
人人都是产品经理
人人都是产品经理
U
Unit 42
博客园 - 叶小钗
Jina AI
Jina AI
MyScale Blog
MyScale Blog
雷峰网
雷峰网
B
Blog
Hugging Face - Blog
Hugging Face - Blog
Blog — PlanetScale
Blog — PlanetScale
Recent Announcements
Recent Announcements
腾讯CDC
酷 壳 – CoolShell
酷 壳 – CoolShell

Taxodium - Tag: emacs

在 Emacs 中用漢典查詢倉頡碼 Elfeed 4.0.0 使用分享 导出 Elfeed 选中条目到 GitHub Page 作为 Reading List 在 Emacs 中用 Elfeed 阅读订阅流 让博客的代码高亮适配主题 如何上手 Emacs Emacs: 封装 webjump 搜索 symbol-at-point 使用 org-protocol 生成听歌排行 Emacs Elevator Pitch Emacs 实时预览 markdown 在 Emacs 随机切换主题
结合 denote 和 org-publish 写博客
Spike Leung · 2025-08-28 · via Taxodium - Tag: emacs

关于如何使用 org-publish 发布博客,见 使用 org-publish 发布博客,这里不再赘述。

对于 org-publish,由于我将所有的博客文件都放在了一个目录,要区分 draft 和 published,就不能通过原来的文件目录了。

尽管 org-publish 提供了 :inlucde 关键字,但它只支持一个文件列表的字符串,无法用正则匹配 denote 文件名上的 keyword,所以我目前只能用 :exclude 关键字,将不需要的 keyword 排除掉。

感谢 Tommie Mami 的回复,可以先 exlucde 所有文件,再 include 对应的 keyword,从而过滤需要的文件。

:include 需要传入一个文件列表,可以通过 denote 提供的方法 denote-diretory-files 获得,不过由于我用了 silos, denote-diretory-files 是不会去找 silos 中的文件的,我还需要临时改变一下 denote-directory 的值,使其指向 silos 的路径。

另外,因为 setq org-publish-project-alist 只执行了一次,后续新增了对应 keyword 的文章,org-publish-project-alist 里依然是上次设置的文件列表,而无法获取最新的文件列表。

因此,还需要在执行 org-publish 之前,再次更新 org-publish-project-alist

相关代码

源代码见: init-org-publish.el

重点关注 include 部分。

(defun spike-leung/get-file-list-from-denote-silo (silos tag)
  "Return files in SILOS match TAG.
SILO is a file path from `denote-silo-directories'.
TAG is string."
  (cl-letf ((denote-directory (expand-file-name silos)))
    (denote-directory-files tag)))

(defun spike-leung/setup-org-publish-project-alist (&rest _args)
  "Setup `org-publish-project-alist'."
  (message "setup org-publish-project-alist")
  (setq org-publish-project-alist
        `(("orgfiles"
           :base-directory "~/git/taxodium/posts"
           :base-extension "org"
           :exclude ".*" ;; 排除所有文件
           :include  ,(spike-leung/get-file-list-from-denote-silo "~/git/taxodium/posts" "_published") ;; 过滤获取对应 keyword 的文件
           :publishing-directory "~/git/taxodium/publish"
           :publishing-function spike-leung/org-html-publish-to-html-orgfiles
           :section-numbers nil
           :with-toc t
           :with-tags t
           :time-stamp-file nil
           :html-head ,spike-leung/html-head
           :html-preamble ,spike-leung/html-preamble-content
           :html-postamble ,spike-leung/html-postamble
           :auto-sitemap t
           :sitemap-filename "index.org"
           :sitemap-title "Taxodium"
           :sitemap-format-entry spike-leung/sitemap-format-entry
           :sitemap-sort-files anti-chronologically
           :sitemap-function spike-leung/sitemap-function
           :author "Spike Leung"
           :email "l-yanlei@hotmail.com")

          ;; 省略其他代码,具体可以看源码
          )))

(spike-leung/setup-org-publish-project-alist) ;; 初始化 org-publish-project-alist
;; 在执行 org-publish 之前更新 org-publish-project-alist
(advice-add 'org-publish :before #'spike-leung/setup-org-publish-project-alist)

另外,生成 sitemap 的时候,我希望使用 export_file_name 的作为链接名称,而不是 denote 的文件名,这需要调整 orgfiles 中 :sitemap-format-entry 的逻辑。

相关代码
(setq org-publish-project-alist
      `(("orgfiles"
         :base-directory "~/git/taxodium/posts"
         :base-extension "org"
         :exclude ,(spike-leung/org-publish-build-exclude-regexp '("draft" "blackhole") '("rss.org"))
         :publishing-directory "~/git/taxodium/publish"
         :publishing-function spike-leung/org-html-publish-to-html-orgfiles
         :section-numbers nil
         :with-toc t
         :with-tags t
         :time-stamp-file nil
         :html-head ,spike-leung/html-head
         :html-preamble ,spike-leung/html-preamble-content
         :html-postamble ,spike-leung/html-postamble
         :auto-sitemap t
         :sitemap-filename "index.org"
         :sitemap-title "Taxodium"
         :sitemap-format-entry spike-leung/sitemap-format-entry ;; <-- 调整 entry 的返回
         :sitemap-sort-files anti-chronologically
         :sitemap-function spike-leung/sitemap-function
         :author "Spike Leung"
         :email "l-yanlei@hotmail.com")

        ;; 省略部分代码
        ))

(defun spike-leung/sitemap-format-entry (entry style project)
  "Custom format for site map ENTRY, as a string.
ENTRY is a file name.  STYLE is the style of the sitemap.
PROJECT is the current project."
  (let* ((export-file-name (spike-leung/org-publish-get-org-keyword entry project "export_file_name")))
    (cond ((not (directory-name-p entry))
           (format "[[file:%s][%s]]"
                   (or
                    (if export-file-name
                        (format "%s.org" export-file-name)
                      nil)
                    entry)
                   (org-publish-find-title entry project)))
          ((eq style 'tree)
           ;; Return only last subdir.
           (file-name-nondirectory (directory-file-name entry)))
          (t entry))))

(defun spike-leung/org-publish-get-org-keyword (entry project keyword)
  "Get the value of the given KEYWORD in the current Org file.
KEYWORD is case-insensitive."
  (let ((file (org-publish--expand-file-name entry project)))
    (when (and (file-readable-p file) (not (directory-name-p file)))
      (org-with-file-buffer file
        (let* ((normalized-keyword (s-upcase keyword))
               (keywords (org-collect-keywords (list normalized-keyword))))
          (car (cdr (assoc normalized-keyword keywords))))))))

Denote Org 可以使用 dynamic blocks 插入 denote 链接,我现在的 sitemap(主页)是手工编写的,通过 dynamic blocks 插入对应的链接内容(你可以将首页 index.html 替换为 index.org 查看 org 文件)。

有的文章我会设置 #+subtitle ,我需要 denote 插入链接的时候能够将 subtitle 也显示在链接里。这可以通过自定义 denote-link-description-format 实现。

相关代码
(use-package denote
  :custom
  ;; 修改 `denote-link-description-format' 的返回,添加 subtitle
  (denote-link-description-format #'spike-leung/denote-link-description-with-signature-and-title-and-subtitle)
  :config
  (defun spike-leung/denote-link-description-with-signature-and-title-and-subtitle (file &optional file-type)
    "Return link description for FILE with FILE-TYPE.
For backward compatibility, FILE-TYPE is an optional parameter.  If it
is nil, then compute FILE-TYPE internally.

- If the region is active, use it as the description.

- If FILE has a signature, then format the description as a sequence of
  the signature text and the title with two spaces between them.

- If FILE does not have a signature, then use its title as the
  description.

- If FILE has subtitle, append subtitle

- If none of the above works, return an empty string.

This function is useful as the value of the user option
`denote-link-description-format' (which can optionally be bound to a
function)."
    (let* ((type (or file-type (denote-filetype-heuristics file)))
           (signature (denote-retrieve-filename-signature file))
           (title (denote-retrieve-title-or-filename file type))
           (region-text (denote--get-active-region-content))
           (subtitle (spike-leung/my-denote--get-subtitle file)))
      (cond
       (region-text region-text)
       ((and signature title) (format "%s  %s" signature title))
       ((and title subtitle) (format "%s - %s" title subtitle))
       (title (format "%s" title))
       (signature (format "%s" signature))
       (t ""))))
  )

(defun spike-leung/my-denote--get-subtitle (file)
  "Find #+subtitle in FILE and return its value.
Return nil if not found or FILE does not exist."
  (when (and file (file-exists-p file))
    (with-temp-buffer
      (insert-file-contents file)
      (goto-char (point-min))
      (when (re-search-forward "^#\\+subtitle: \\(.*\\)$" nil t)
      (string-trim (match-string-no-properties 1))))))

完整的 denote 设置见 init-denote.el

index.org 转换成 index.html 时,我还希望将 subtitle 从链接中移除,放到链接下面呈现。我是通过在导出之后,修改 HTML 内容实现的。

我调整了生成 sitemap 的 :publishing-function 实现:

  (defun spike-leung/setup-org-publish-project-alist (&rest _args)
    "Setup `org-publish-project-alist'."
    (message "setup org-publish-project-alist")
    (setq org-html-htmlize-output-type 'css)
    (setq org-publish-project-alist
          `(("sitemap"
             :base-directory "~/git/taxodium/posts"
             :base-extension "org"
             :include ("index.org")
             :exclude ".*"
             :publishing-directory ,spike-leung/org-publish-default-publishing-directory
             :time-stamp-file nil
             :section-numbers nil
             :html-head ,spike-leung/html-head-sitemap
             :html-preamble ,spike-leung/html-preamble-content
             :html-postamble ,spike-leung/html-postamble-sitemap
             :publishing-function spike-leung/org-html-publish-sitemap (publishing-function)
             :html-htmlize-output-type css
             :author "Spike Leung"
             :email "l-yanlei@hotmail.com"))))

(defun spike-leung/org-html-publish-sitemap (plist filename pub-dir)
  "`org-publish' `:publishing-function' for sitemap.
Add subtitle to links which has subtitle.
See `org-html-publish-to-html' for param PLIST,FILENAME,PUB-DIR."
  (let ((output-filename (org-html-publish-to-html plist filename pub-dir)))
    ;; 这里应该用 `with-temp-buffer' 而不要用 `with-current-buffer' 和 '`find-file-noselect'
    ;; see: https://emacs.stackexchange.com/questions/2868/whats-wrong-with-find-file-noselect
    (with-temp-buffer
      (insert-file-contents output-filename)
      (goto-char (point-min))
      (while (re-search-forward
              (rx (group "<a" (*? anychar) ">" (*? anychar)) " - " (group (*? anychar)) (group "</a>"))
              nil t)
        (let ((subtitle (match-string 2)))
          (replace-match (format "\\1\\3<span class=\"sitemap-subtitle\">%s</span>" subtitle))))
      (write-region (point-min) (point-max) output-filename))
    output-filename))

完整的 org-publish 实现见: init-org-publish.el