如何使用 Org 文档写 Hexo 博客

1. Hexo 与 Org-mode

Hexo 是一个快速、简洁且高效的用于生成静态网页的博客框架, 生成的网页可以方便地托管到 Github Pages , 可以快速方便地创建个人博客.

Hexo 默认使用 Markdown 格式解析文章, 本文介绍如何使用 org 文档生成 Hexo 博客文章.

2. hexo-renderer-org

hexo-renderer-org 是 hexo 的一个插件, 用于将 org 文档生成 hexo 的博客文章. Hexo 解析 Markdown 文档的能力保持不变, 相当于给 Hexo 加上 org 文档的支持.

使用我维护的 hexo-renderer-org 项目1.

2.1. 安装

在 Hexo 博客文件夹下安装 hexo-renderer-org

npm install https://github.com/mpwang/hexo-renderer-org#master --save

2.2. 工作原理

安装了 hexo-renderer-org 之后, hexo 命令会使用 emacsclient 去连接 emacs server, 使 用 org export 功能将 org 文档转换成 html, 所以要求 emacs 开启 server 进程.

2.3. emacs 的配置

spacemacs 用户配置

启用 emacs server 进程以及设置 emacs server socket 文件所在的文件夹.

;; If non-nil, start an Emacs server if one is not already running.
;; (default nil)
dotspacemacs-enable-server t

;; Set the emacs server socket location.
;; If nil, uses whatever the Emacs default is, otherwise a directory path
;; like \"~/.emacs.d/server\". It has no effect if
;; `dotspacemacs-enable-server' is nil.
;; (default nil)
dotspacemacs-server-socket-dir "~/.emacs.d/server"

2.4. hexo 的配置

在 hexo 博客的配置文件 _config.yml 中添加

org:
  # 指定 emacs 执行文件路径
  emacs: '/usr/local/bin/emacs'
  # 指定 emacsclient 执行文件路径
  emacsclient: '/usr/local/bin/emacsclient'
  # 是否生成自动注脚, t 为开启, nil 为关闭
  common: |
    #+OPTIONS: html-postamble:nil
  clean_cache: true
  daemonize: true
  # emacs server file 路径, 如果不指定, 以下为默认值
  server_file: "~/.emacs.d/server/server"

2.4.1. 不要使用 emacs htmlize 功能

README 文档里的 htmlize 功能我没有成功过, 设置 htmlize: true 之后生成的内容会 出错. 不过这个问题影响不大, 代码高亮交给 highlight.js 去处理.

2.5. Enjoy

现在在开启 emacs 的情况下, 执行以下命令即可以享受用强大的 org-mode 写文档, 并生成 Hexo 博客.

hexo clean && hexo generate && hexo server --debug

3. 配合使用 Hexo 的文章资源文件夹

Hexo3 加入了文章资源文件夹 的支持, 文章相关的图片可以放在文章同名文件夹里面, 并使用以下代码引用图片

{% asset_img slug [title] %}

而在 org 文档中配合 org-download, 我们还可以实现更强大的功能. 在 Hexo 博客文件夹中创建 .dir-local.el, 添加以下代码

((nil .
((eval .
(progn

;; make drag-and-drop image save in the same name folder as org file
;; ex: `aa-bb-cc.org' then save image test.png to `aa-bb-cc/test.png'
(defun my-org-download-method (link)
(let ((filename
(file-name-nondirectory
(car (url-path-and-query
(url-generic-parse-url link)))
)
)

(dirname (file-name-sans-extension buffer-file-name ) ))

;; if directory not exist, create it
(unless (file-exists-p dirname)
(make-directory dirname))

;; return the path to save the download files
(expand-file-name filename dirname)))


;; only modify `org-download-method' in this project
(setq-local org-download-method 'my-org-download-method)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; for using hexo config post_asset_folder: true ;;
;; https://hexo.io/docs/asset-folders.html#Post-Asset-Folder ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; only modify `org-download-link-format' in this project
(setq-local org-download-link-format "{%% asset_img %s %%}")

;; only modify `org-download-abbreviate-filename-function' in
;; this project
(setq-local org-download-abbreviate-filename-function #'file-name-nondirectory)
))
)
)
)

现在你可以将图片支持拖拽到 org 文档中, emacs 会自动创建文章同名文件夹并异步下载图片, 在光标所在处自动插入对应的引用代码.

使用本功能必须配置 org 文档导出时忽略 _ 字符的处理, 否则处理 asset_img 时会引起报错.

(with-eval-after-load 'ox
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; disable interpret "_" and "^" for export ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(setq org-export-with-sub-superscripts nil))

4. 配合 Hexo 主题: NexT

本网站采用了漂亮成熟的主题 NexT 6.0.

以下是我使用 hexo-renderer-org 配合 NexT 主题的的一些调优.

spacemacs 用户配置

使用方式: 在 .spacemacs 文件的 dotspacemacs/user-config 中添加

(load "~/.emacs.d/config-hexo.el")

~/.emacs.d/ 文件夹中创建 config-hexo.el 添加以下代码

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; customization for using hexo-renderer-org to generate hexo static web pages ;;
;; ;;
;; using themes/next https://theme-next.org/ ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun hexo-theme-next/link-add-font-awesome (text backend info)
"Add fa-external-link class to element."
(let ((fa-external-element ""))
(when (and (eq backend 'hexo-html)
(not (string-match-p fa-external-element text)))

(replace-regexp-in-string "" (concat " " fa-external-element) text)
))
)


(defun hexo-theme-next/center-quote (text backend info)
"Transcode a CENTER-BLOCK from org to hexo theme/next centerquote tag plugin."
(let ((blockquote-tag-start "
"
)

(blockqoute-tag-end ""))

(when (and (eq backend 'hexo-html)
(not (string-match-p blockquote-tag-start text)))

(let* ((result text)
(result (replace-regexp-in-string "
"
blockquote-tag-start result)
)

(result (replace-regexp-in-string "
" blockqoute-tag-end result)))
result))))

(with-eval-after-load 'ox
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; disable interpret "_" and "^" for export ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(setq org-export-with-sub-superscripts nil)

(add-to-list 'org-export-filter-link-functions #'hexo-theme-next/link-add-font-awesome)
(add-to-list 'org-export-filter-center-block-functions #'hexo-theme-next/center-quote)
)


(with-eval-after-load 'ox-html
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; todo keyword use theme/next label CSS class ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(setq org-html-todo-kwd-class-prefix "label warning ")

(setq org-html-postamble-format
'(("en"
"

Generated using %c

"
)

))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; generate TOC using hexo theme/next tabs plugin at the top of article ;;
;; ;;
;; redefine org-html-toc ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun org-html-toc (depth info &optional scope)
"Build a table of contents.
DEPTH is an integer specifying the depth of the table. INFO is
a plist used as a communication channel. Optional argument SCOPE
is an element defining the scope of the table. Return the table
of contents as a string, or nil if it is empty."

(let ((toc-entries
(mapcar (lambda (headline)
(cons (org-html--format-toc-headline headline info)
(org-export-get-relative-level headline info)))

(org-export-collect-headlines info depth scope)))
)

(when toc-entries
(let ((toc (concat "
    \n"

(hexo-theme-next/toc-nav-tabs toc-entries)
""
"
\n"

(hexo-theme-next/toc-text toc-entries)
"
\n"
)))
(if scope toc
(concat "
\n"

toc
"
\n" ))
))))

(defun hexo-theme-next/get-href (headline)
(if (string-match "href=\"#\\(.*\\)\"" headline)
(match-string-no-properties 1 headline)
""))


(defun hexo-theme-next/toc-nav-tabs (toc-entries)
(mapconcat
(lambda (entry)
(let ((headline (car entry))
(level (cdr entry)))

(when (= level 1)
(let ((ahref (hexo-theme-next/get-href headline)))
(concat "
  • \n"

  • (replace-regexp-in-string
    "
    "
    (replace-regexp-in-string ahref (concat "tab-" ahref) headline))

    "\n"))
    )
    )
    )

    toc-entries ""))


    (defun hexo-theme-next/toc-text (toc-entries)
    "Return innards of a table of contents, as a string.
    TOC-ENTRIES is an alist where key is an entry title, as a string,
    and value is its relative level, as an integer."

    (let* ((prev-level (1- (cdar toc-entries)))
    (start-level prev-level))

    (concat
    (mapconcat
    (lambda (entry)
    (let ((headline (car entry))
    (level (cdr entry)))

    (let ((ahref (hexo-theme-next/get-href headline )))
    (concat
    (let* ((cnt (- level prev-level))
    (times (if (> cnt 0) (1- cnt) (- cnt))))

    (setq prev-level level)
    (if (> cnt 0)
    (cond ((= level 1)
    (format "
    \n
      \n"

    (concat "tab-" ahref)))

    ((> level 1)
    "
  • \n"
  • )
    )

    (cond ((= level 1)
    (format "\n
    \n
    \n
      \n"
      (concat "tab-" ahref)))
      ((> level 1)
      "\n
    • \n"
    • )
      )
      ))
      headline))))
      toc-entries "")
      "
    "
    )))
    )
      功能
    hexo-theme-next/link-add-font-awesome 将所有链接后插入 font awesome 箭头图标
    hexo-theme-next/center-quote 将 org 的 #+BEGIN_CENTER 代码块转换为 NexT 的 centerquote 标签
    org-html-todo-kwd-class-prefix 给标题中的 TODO 关键字加上 NexT 的 label 样式
    org-html-postamble-format 配合 _config.ymlpostamble:t 使用, 自动在文章末尾加上 Generated 文字
    org-html-toc 将文章开头 org 自动生成的 Table of Content 转换成 NexT 漂亮的 tabs 标签

    5. 技巧

    有时候需要直接在 org 文档中包含 hexo 特殊标签字符时会 hexo g 会出错, 或者输出会消失, 比如本文中的 {%% asset_img %%} {% asset_img %} .

    这是因为这些字符会被 Hexo 引擎特殊处理, 当你需要输出这些特殊字符, 可以在外面用 raw 标签包围, 这样 hexo 引擎就不会处理这些字符了.

    {% raw %}
    {% asset_img %}
    {% endraw %}

    Footnotes:

    1

    hexo-renderer-org 最早是 coldnew 开发的, 但是已经停止维护了,emacs 升级到 26.1 之后不能使用.

    MephistoMMM 修复了问题, 我自己 fork 之后又修复了几个问题, 目前已经可以稳定使用.

    Generated using Emacs 29.0.50 (Org mode 9.4.6)