百般武艺,此乃 Emacs (一):用 Emacs 写博客
本文简要介绍了如何使用 Emacs 来生成静态博客网站。主要分为三个模块:
- 博客生成
- RSS 生成
- 文学编程网页导出
使用 ox-publish.el 来生成博客
为什么选择 ox-publish.el
在 Emacs 里写博客的方案有很多,比如说 Org + Hugo 、Markdown/Org + Jekyll 等等。它们各有各的优点,但这不是本文的讨论重点,有兴趣的读者可以自行查阅。我之所以使用 ox-publish.el ,主要有以下几点原因:
兼容性
我基本上所有的写作活动都是在 Org-mode 里完成的,对 Org-mode 有着比较深度的自定义配置,因此希望能直接基于 org 文件来生成博客。Hugo 虽然号称能够解析 org 格式,但是对于自定义的格式就无能为力了,比如说自定义的链接格式。而 ox-publish.el 采用的是 Org-mode 自带的导出功能,只需要你自己写好相关格式的导出方式,便可以完美地支持任何 org 文件。
内置
ox-publish.el 中的 ox
是 org-export
的缩写,从名字就能看出来它是 Org-mode 导出功能的一个模块。因此可以和 Org-mode 的其他功能功能无缝整合,任何需求都能较为简单地实现。内置的另一个好处是我不用担心这个项目后续的发展,只要 Org-mode 还能用,
ox-publish.el 就能用。
可拓展性
Org-mode 的 ox
模块提供了很多 hook ,比如能够分别在导出前后修改原始文本(的复制)和导出文本。这基本上满足我的绝大部分个性化需求,实在不行我也可以通过 advice 来修改原有函数,总之可扩展性非常好强。
博客结构
首先介绍一下博客的基本结构
./ ├── articles/ ├── css/ ├── orgs/ ├── scripts/ ├── static/ ├── config.html ├── index.html ├── rss.xml └── sitemap.xml
- articles
- 此文件夹包含所有的博客文章
- css
- 此文件夹包含所有的 CSS 文件
- orgs
- 此文件夹包含所有的 Org 源文件
- script
- 此文件夹包含所有的 JS 文件
- static
- 此文件夹包含所有的其他静态资源
- config.html
- 我的 Emacs 文学编程配置
- index.html
- 博客主页
- rss.xml
- RSS 订阅
- sitemap.xml
- 网站站点地图,用于在 Google Search Console 中建立索引
基本配置
ox-publish.el 的配置几乎只用一个变量 org-publish-project-alist
就能完成,非常简单。相关语法和用法请查阅 org-publish-project-alist
的文档和 Publishing (The Org
Manual) ,本文将侧重于分享我的个人配置。
org-publish-project-alist
中个每个元素就是一个发布项目,其由一个项目名和一个
property list 组成。如 :
1: ("eli's blog" 2: :base-directory ,eli/blog-base-dir 3: :publishing-directory ,(expand-file-name "articles" eli/blog-publish-dir) 4: :base-extension "org" 5: :recursive nil 6: :htmlized-source t 7: :publishing-function eli/org-blog-publish-to-html 8: :exclude "rss.org" 9: 10: :auto-sitemap t 11: :preparation-function eli/kill-sitemap-buffer 12: :completion-function eli/blog-publish-completion 13: :sitemap-filename ,eli/blog-sitamap 14: :sitemap-title "Eli's Blog" 15: :sitemap-sort-files anti-chronologically 16: :sitemap-function eli/org-publish-sitemap 17: :sitemap-format-entry eli/sitemap-dated-entry-format 18: 19: :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/style.css\" /> 20: <link rel=\"stylesheet\" type=\"text/css\" href=\"/css/htmlize.css\" /> 21: <script src=\"/scripts/script.js\"></script> 22: <script src=\"/scripts/toc.js\"></script>" 23: :html-preamble t 24: :html-preamble-format (("en" "<nav class=\"nav\"> 25: <a href=\"/index.html\" class=\"button\">Home</a> 26: <a href=\"/rss.xml\" class=\"button\">RSS</a> 27: <a href=\"/config.html\" class=\"button\">Literate Emacs Config</a> 28: </nav> 29: <hr>")) 30: :html-postamble t 31: :html-postamble-format (("en" "<hr class=\"Solid\"> 32: <div class=\"info\"> 33: <span class=\"author\">Author: %a (%e)</span> 34: <span class=\"date\">Create Date: %d</span> 35: <span class=\"date\">Last modified: %C</span> 36: <span>Creator: %c</span> 37: </div>")) 38: :with-creator nil 39: )
我们的主要工作就是配置这个 property list 以满足我们不同的需求,接下来的几个部分将介绍我们所使用的属性:
自定义导出后端
我希望博客的导出和常规 HTML 之间的行为有所区别,所以首先让我们定义一个专门用于博客导出的 org 导出后端 blog
,这是为了方便修改html
后端的默认行为(通过
:translate-alist
等其他方式实现)。
1: (org-export-define-derived-backend 'blog 'html 2: :translate-alist '((src-block . eli/org-blog-src-block) 3: (footnote-reference . eli/org-blog-footnote-reference) 4: (template . eli/org-blog-template)))
我们分别修改了代码块、文档注释和导出模板:
代码块
由于博客文章中的代码块的组织方式是以文学编程编程的方式来处理的,而我的习惯是仅使用 name
属性来命名代码块,所以我希望在没有 caption
的时候用 name
属性来作为代码块的标签:
1: (defun eli/org-blog-src-block (src-block _contents info) 2: "Transcode a SRC-BLOCK element from Org to HTML. 3: CONTENTS holds the contents of the item. INFO is a plist holding 4: contextual information." 5: (if (org-export-read-attribute :attr_html src-block :textarea) 6: (org-html--textarea-block src-block) 7: (let* ((lang (org-element-property :language src-block)) 8: (code (org-html-format-code src-block info)) 9: (label (let ((lbl (org-html--reference src-block info t))) 10: (if lbl (format " id=\"%s\"" lbl) ""))) 11: (klipsify (and (plist-get info :html-klipsify-src) 12: (member lang '("javascript" "js" 13: "ruby" "scheme" "clojure" "php" "html"))))) 14: (if (not lang) (format "<pre class=\"example\"%s><code>\n%s</code></pre>" label code) 15: (format "<div class=\"org-src-container\">\n%s%s\n</div>" 16: ;; Build caption. 17: (let ((caption (or (org-export-get-caption src-block) 18: (org-element-property :name src-block)))) 19: (if (not caption) "" 20: (let ((listing-number 21: (format 22: "<span class=\"listing-number\">%s </span>" 23: "Listing: "))) 24: (format "<div class=\"org-src-name\">%s%s</div>" 25: listing-number 26: (org-trim (org-export-data caption info)))))) 27: ;; Contents. 28: (if klipsify 29: (format "<pre><code class=\"src src-%s\"%s%s>%s</code></pre>" 30: lang 31: label 32: (if (string= lang "html") 33: " data-editor-type=\"html\"" 34: "") 35: code) 36: (format "<pre class=\"src src-%s\"%s><code>%s</code></pre>" 37: lang label code)))))))
注释
我们对注释也需要额外处理。默认的处理方式是放在页面的末尾,这其实是不利于读者阅读的,经常前后跳转可能会打断读者的心流。所以我们更希望采用侧注的方式,方便读者就近查阅。而对于移动设备,我们希望采用弹出注释的方式:
为了实现上述需求,仅使用 HTML 是不够的,还需要 CSS/JS 的帮助,这部分细节可以在仓库 GitHub - Elilif/Elilif.github.io 查看,本文专注于导出部分。下面的代码在原有基础上添加了几个标签,方便后续处理。
1: (defun eli/org-blog-footnote-reference (footnote-reference _contents info) 2: "Transcode a FOOTNOTE-REFERENCE element from Org to HTML. 3: CONTENTS is nil. INFO is a plist holding contextual information." 4: (concat 5: ;; Insert separator between two footnotes in a row. 6: (let ((prev (org-export-get-previous-element footnote-reference info))) 7: (when (eq (org-element-type prev) 'footnote-reference) 8: (plist-get info :html-footnote-separator))) 9: (let* ((n (org-export-get-footnote-number footnote-reference info)) 10: (id (format "fnr.%d%s" 11: n 12: (if (org-export-footnote-first-reference-p 13: footnote-reference info) 14: "" 15: ".100")))) 16: (format 17: (concat (plist-get info :html-footnote-format) 18: "<input id=\"%s\" class=\"footref-toggle\" type=\"checkbox\">") 19: (format "<label for=\"%s\" class=\"footref\">%s</label>" 20: id n) 21: id))))
导出模板
最后我们需要修改下默认的导出模板:
1: (defun eli/org-blog-template (contents info) 2: "Return complete document string after HTML conversion. 3: CONTENTS is the transcoded contents string. INFO is a plist 4: holding export options." 5: (setq eli-test info) 6: (concat 7: (when (and (not (org-html-html5-p info)) (org-html-xhtml-p info)) 8: (let* ((xml-declaration (plist-get info :html-xml-declaration)) 9: (decl (or (and (stringp xml-declaration) xml-declaration) 10: (cdr (assoc (plist-get info :html-extension) 11: xml-declaration)) 12: (cdr (assoc "html" xml-declaration)) 13: ""))) 14: (when (not (or (not decl) (string= "" decl))) 15: (format "%s\n" 16: (format decl 17: (or (and org-html-coding-system 18: ;; FIXME: Use Emacs 22 style here, see `coding-system-get'. 19: (coding-system-get org-html-coding-system 'mime-charset)) 20: "iso-8859-1")))))) 21: (org-html-doctype info) 22: "\n" 23: (concat "<html" 24: (cond ((org-html-xhtml-p info) 25: (format 26: " xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"%s\" xml:lang=\"%s\"" 27: (plist-get info :language) (plist-get info :language))) 28: ((org-html-html5-p info) 29: (format " lang=\"%s\"" (plist-get info :language)))) 30: ">\n") 31: "<head>\n" 32: (org-html--build-meta-info info) 33: (org-html--build-head info) 34: (org-html--build-mathjax-config info) 35: "</head>\n" 36: "<body>\n" 37: (let ((link-up (org-trim (plist-get info :html-link-up))) 38: (link-home (org-trim (plist-get info :html-link-home)))) 39: (unless (and (string= link-up "") (string= link-home "")) 40: (format (plist-get info :html-home/up-format) 41: (or link-up link-home) 42: (or link-home link-up)))) 43: ;; Preamble. 44: (org-html--build-pre/postamble 'preamble info) 45: ;; Document contents. 46: (let ((div (assq 'content (plist-get info :html-divs)))) 47: (format "<%s id=\"%s\" class=\"%s\">\n" 48: (nth 1 div) 49: (nth 2 div) 50: (plist-get info :html-content-class))) 51: ;; Document title. 52: (when (plist-get info :with-title) 53: (let ((title (and (plist-get info :with-title) 54: (plist-get info :title))) 55: (subtitle (plist-get info :subtitle)) 56: (html5-fancy (org-html--html5-fancy-p info))) 57: (when title 58: (format 59: (if html5-fancy 60: "<header>\n<h1 class=\"title\">%s</h1>\n%s</header>" 61: "<h1 class=\"title\">%s%s</h1>\n") 62: (org-export-data title info) 63: (if subtitle 64: (format 65: (if html5-fancy 66: "<p class=\"subtitle\" role=\"doc-subtitle\">%s</p>\n" 67: (concat "\n" (org-html-close-tag "br" nil info) "\n" 68: "<span class=\"subtitle\">%s</span>\n")) 69: (org-export-data subtitle info)) 70: ""))))) 71: ;; add article status 72: (eli/blog-build-article-status info) 73: contents 74: (format "</%s>\n" (nth 1 (assq 'content (plist-get info :html-divs)))) 75: ;; gisus 76: (eli/blog-build-giscus info) 77: ;; Postamble. 78: (org-html--build-pre/postamble 'postamble info) 79: ;; Possibly use the Klipse library live code blocks. 80: (when (plist-get info :html-klipsify-src) 81: (concat "<script>" (plist-get info :html-klipse-selection-script) 82: "</script><script src=\"" 83: org-html-klipse-js 84: "\"></script><link rel=\"stylesheet\" type=\"text/css\" href=\"" 85: org-html-klipse-css "\"/>")) 86: ;; Closing document. 87: "</body>\n</html>"))
目前主要是两个部分:一是增加标题下的文章信息;二是添加评论模块(使用 giscus )。注意这两个信息我们都不希望添加到主页中,所以在后面的代码中都做了相应的判断。
添加文章信息部分逻辑很简单,就是组合一些字符串,代码如下:
1: (defvar eli/blog-status-format "<span><i class='bx bx-calendar'></i> 2: <span>%d</span></span>\n<span><i class='bx bx-edit'></i><span>%C</span></span>") 3: (defvar eli/blog-history-base-url "https://github.com/Elilif/Elilif.github.io/commits/master/orgs/") 4: 5: (defun eli/blog-build-article-status (info) 6: (let ((input-file (file-name-nondirectory (plist-get info :input-file)))) 7: (unless (string-equal input-file eli/blog-sitamap) 8: (let ((spec (org-html-format-spec info)) 9: (history-url (concat eli/blog-history-base-url input-file))) 10: (concat 11: "<div class=\"post-status\">" 12: (format-spec eli/blog-status-format spec) 13: (format "<span><i class='bx bx-history'></i><span><a href=\"%s\">history</a></span></span>" 14: history-url) 15: "</div>")))))
效果:
添加评论模块的代码如下:
1: (defvar eli/blog-giscus-script "<script src=\"https://giscus.app/client.js\" 2: data-repo=\"Elilif/Elilif.github.io\" 3: data-repo-id=\"MDEwOlJlcG9zaXRvcnkyOTgxNjM5ODg=\" 4: data-category=\"Announcements\" 5: data-category-id=\"DIC_kwDOEcWfFM4Cdz5V\" 6: data-mapping=\"pathname\" 7: data-strict=\"0\" 8: data-reactions-enabled=\"1\" 9: data-emit-metadata=\"0\" 10: data-input-position=\"top\" 11: data-theme=\"light\" 12: data-lang=\"zh-CN\" 13: crossorigin=\"anonymous\" 14: async> 15: </script>") 16: 17: (defun eli/blog-build-giscus (info) 18: (let ((input-file (file-name-nondirectory (plist-get info :input-file)))) 19: (unless (string-equal input-file eli/blog-sitamap) 20: eli/blog-giscus-script)))
到这里,我们自定义的导出后端就定义完成了。最后我们需要为 ox-publish 定义一个导出后端为 blog
的发布函数:
1: ;;;###autoload 2: (defun eli/org-blog-publish-to-html (plist filename pub-dir) 3: "Publish an org file to HTML. 4: 5: FILENAME is the filename of the Org file to be published. PLIST 6: is the property list for the given project. PUB-DIR is the 7: publishing directory. 8: 9: Return output file name." 10: (org-publish-org-to 'blog filename 11: (concat (when (> (length org-html-extension) 0) ".") 12: (or (plist-get plist :html-extension) 13: org-html-extension 14: "html")) 15: plist pub-dir))
杂项部分
为了方便后续的编辑,我们定义如下几个变量:
1: (setq eli/blog-base-dir "path-to/blog/" 2: eli/blog-publish-dir "your-blog-site-dir" 3: eli/blog-sitamap "index.org")
property list 中的杂项部分如下,基本上看名字就知道是什么意思,下面这些未指出的属性读者可以查阅Publishing (The Org Manual) 等文档,这里就不一一说明了。
1: :base-directory ,eli/blog-base-dir 2: :publishing-directory ,(expand-file-name "articles" eli/blog-publish-dir) 3: :base-extension "org" 4: :recursive nil 5: :htmlized-source t 6: :publishing-function eli/org-blog-publish-to-html 7: :exclude "rss.org"
HTML 部分
org 文件导出后的 HTML 主要由 preamble
、content
和 postamble
三个部分组成,我们的文章内容填充的是 content
部分,其他两个部分由变量 org-html-preamble
、
org-html-preamble-format
、 org-html-postamble
和 org-html-postamble-format
分别控制。其具体用途可任由读者发挥,这里我们将 preamle
用做导航栏,而 postamble
则用作提供文章信息。
导航栏是几个很简单的标签:
1: (("en" "<nav class=\"nav\"> 2: <a href=\"/index.html\" class=\"button\">Home</a> 3: <a href=\"/rss.xml\" class=\"button\">RSS</a> 4: <a href=\"/config.html\" class=\"button\">Literate Emacs Config</a> 5: </nav> 6: <hr>"))
postamble 则提供了作者、创建时间和修改时间等信息:
1: (("en" "<hr class=\"Solid\"> 2: <div class=\"info\"> 3: <span class=\"author\">Author: %a (%e)</span> 4: <span class=\"date\">Create Date: %d</span> 5: <span class=\"date\">Last modified: %C</span> 6: <span>Creator: %c</span> 7: </div>"))
此外,我们还可以为导出的 HTML 提供 CSS 和 JS 文件,以获取更舒适的浏览体验:
1: "<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/style.css\" /> 2: <link rel=\"stylesheet\" type=\"text/css\" href=\"/css/htmlize.css\" /> 3: <script src=\"/scripts/script.js\"></script> 4: <script src=\"/scripts/toc.js\"></script>"
至此,property list 中的 HTML 部分就大致完成了:
1: :html-head <<head>> 2: :html-preamble t 3: :html-preamble-format <<blog-preamble>> 4: :html-postamble t 5: :html-postamble-format <<blog-postamble>> 6: :with-creator nil
sitemap 部分
sitemap 就是站点地图,其中列举了项目中的所有文章链接,这样通过 sitemap 就可以访问全部的文章内容。因此我们可以把 sitemap 稍作修改,用作我们博客的主页。
首先我们为博客主页固定一个创建时间:
1: (defun eli/org-publish-sitemap (title list) 2: "Generate the sitemap with title." 3: (concat "#+TITLE: " title 4: "\n" 5: "#+DATE: 2023-10-10" 6: "\n\n" 7: (org-list-to-org list)))
其次我们可以为 sitemap 中的每一个 entry 添加时间前缀:
1: (defun eli/sitemap-dated-entry-format (entry _style project) 2: "Sitemap PROJECT ENTRY STYLE format that includes date." 3: (let* ((file (org-publish--expand-file-name entry project)) 4: (parsed-title (org-publish-find-property file :title project)) 5: (title 6: (if parsed-title 7: (org-no-properties 8: (org-element-interpret-data parsed-title)) 9: (file-name-nondirectory (file-name-sans-extension file))))) 10: (org-publish-cache-set-file-property file :title title) 11: (if (= (length title) 0) 12: (format "*%s*" entry) 13: (format "{{{timestamp(%s)}}} [[file:%s][%s]]" 14: (car (org-publish-find-property file :date project)) 15: (concat "articles/" entry) 16: title))))
注意 eli/sitemap-dated-entry-format
里的 {{{timestamp(%s)}}}
是一个导出宏:
1: (add-to-list 'org-export-global-macros 2: '("timestamp" . "@@html:<span class=\"timestamp\">[$1]</span>@@"))
一般情况下 sitemap 会和同一 :base-directory
目录下的其他文件一起导出到
:publishing-directory
,但从博客结构一节可以看出,我们希望把 sitemap 导出到博客的根目录来充当主页,所以需要利用 :completion-function
属性来在导出完成后把
sitemap 移到根目录
1: (defun eli/blog-publish-completion (project) 2: (let* ((publishing-directory (plist-get project :publishing-directory)) 3: (sitamap (file-name-with-extension eli/blog-sitamap "html")) 4: (orig-file (expand-file-name sitamap publishing-directory)) 5: (target-file (expand-file-name 6: sitamap 7: (file-name-directory publishing-directory)))) 8: (rename-file orig-file target-file t)))
最后还有一点需要注意,在 ox-publish.el 的实现过程中,ox-publish 会优先使用正在访问博客文件的 buffer 来作为导出的来源,这在一般情况下没什么问题,但是如果你在导出前在 buffera 打开了 sitemap 文件,那么 ox-publish 就会在重新生成 sitemap 后继续使用 buffera 中的内容。此时的行为会受 auto-revert
或其他相关设置的影响,比如说此时 auto-revert-timer
的定时还没有到,那么 ox-publish 就会使用旧的 sitemap 内容。这不是我们想要的,所以我的方案是在导出前关闭访问 sitemap 的 buffer ,反正
sitemap 是自动生成的,我们也不需要修改。
1: (defun eli/kill-sitemap-buffer (project) 2: (let* ((sitemap-filename (plist-get project :sitemap-filename)) 3: (base-dir (plist-get project :base-directory)) 4: (sitemap-filepath (expand-file-name sitemap-filename base-dir))) 5: (when-let ((sitemap-buffer (find-buffer-visiting sitemap-filepath))) 6: (kill-buffer sitemap-buffer))))
至此,一个基本的博客主页就完成了,下面是相应的属性:
1: :auto-sitemap t 2: :preparation-function eli/kill-sitemap-buffer 3: :completion-function eli/blog-publish-completion 4: :sitemap-filename ,eli/blog-sitamap 5: :sitemap-title "Eli's Blog" 6: :sitemap-sort-files anti-chronologically 7: :sitemap-function eli/org-publish-sitemap 8: :sitemap-format-entry eli/sitemap-dated-entry-format
本章总结
现在我们已经介绍完了所有需要的属性,接下来让我们让我们把这些属性合到一起,加上名字组成一个完整的 project 。至此,一个简单的博客导出工具就完成了:
1: <<eli/kill-sitemap-buffer>> 2: 3: <<eli/blog-publish-completion>> 4: 5: <<timestamp-macro>> 6: 7: <<eli/sitemap-dated-entry-format>> 8: 9: <<eli/org-publish-sitemap>> 10: 11: <<blog-variables>> 12: 13: <<eli/org-blog-publish-to-html>> 14: 15: <<eli/blog-build-giscus>> 16: 17: <<eli/blog-build-article-status>> 18: 19: <<eli/org-blog-template>> 20: 21: <<eli/org-blog-footnote-reference>> 22: 23: <<eli/org-blog-src-block>> 24: 25: <<custom-backend>>
1: (defun eli/kill-sitemap-buffer (project) 2: (let* ((sitemap-filename (plist-get project :sitemap-filename)) 3: (base-dir (plist-get project :base-directory)) 4: (sitemap-filepath (expand-file-name sitemap-filename base-dir))) 5: (when-let ((sitemap-buffer (find-buffer-visiting sitemap-filepath))) 6: (kill-buffer sitemap-buffer)))) 7: 8: (defun eli/blog-publish-completion (project) 9: (let* ((publishing-directory (plist-get project :publishing-directory)) 10: (sitamap (file-name-with-extension eli/blog-sitamap "html")) 11: (orig-file (expand-file-name sitamap publishing-directory)) 12: (target-file (expand-file-name 13: sitamap 14: (file-name-directory publishing-directory)))) 15: (rename-file orig-file target-file t))) 16: 17: (add-to-list 'org-export-global-macros 18: '("timestamp" . "@@html:<span class=\"timestamp\">[$1]</span>@@")) 19: 20: (defun eli/sitemap-dated-entry-format (entry _style project) 21: "Sitemap PROJECT ENTRY STYLE format that includes date." 22: (let* ((file (org-publish--expand-file-name entry project)) 23: (parsed-title (org-publish-find-property file :title project)) 24: (title 25: (if parsed-title 26: (org-no-properties 27: (org-element-interpret-data parsed-title)) 28: (file-name-nondirectory (file-name-sans-extension file))))) 29: (org-publish-cache-set-file-property file :title title) 30: (if (= (length title) 0) 31: (format "*%s*" entry) 32: (format "{{{timestamp(%s)}}} [[file:%s][%s]]" 33: (car (org-publish-find-property file :date project)) 34: (concat "articles/" entry) 35: title)))) 36: 37: (defun eli/org-publish-sitemap (title list) 38: "Generate the sitemap with title." 39: (concat "#+TITLE: " title 40: "\n" 41: "#+DATE: 2023-10-10" 42: "\n\n" 43: (org-list-to-org list))) 44: 45: (setq eli/blog-base-dir "path-to/blog/" 46: eli/blog-publish-dir "your-blog-site-dir" 47: eli/blog-sitamap "index.org") 48: 49: ;;;###autoload 50: (defun eli/org-blog-publish-to-html (plist filename pub-dir) 51: "Publish an org file to HTML. 52: 53: FILENAME is the filename of the Org file to be published. PLIST 54: is the property list for the given project. PUB-DIR is the 55: publishing directory. 56: 57: Return output file name." 58: (org-publish-org-to 'blog filename 59: (concat (when (> (length org-html-extension) 0) ".") 60: (or (plist-get plist :html-extension) 61: org-html-extension 62: "html")) 63: plist pub-dir)) 64: 65: (defvar eli/blog-giscus-script "<script src=\"https://giscus.app/client.js\" 66: data-repo=\"Elilif/Elilif.github.io\" 67: data-repo-id=\"MDEwOlJlcG9zaXRvcnkyOTgxNjM5ODg=\" 68: data-category=\"Announcements\" 69: data-category-id=\"DIC_kwDOEcWfFM4Cdz5V\" 70: data-mapping=\"pathname\" 71: data-strict=\"0\" 72: data-reactions-enabled=\"1\" 73: data-emit-metadata=\"0\" 74: data-input-position=\"top\" 75: data-theme=\"light\" 76: data-lang=\"zh-CN\" 77: crossorigin=\"anonymous\" 78: async> 79: </script>") 80: 81: (defun eli/blog-build-giscus (info) 82: (let ((input-file (file-name-nondirectory (plist-get info :input-file)))) 83: (unless (string-equal input-file eli/blog-sitamap) 84: eli/blog-giscus-script))) 85: 86: (defvar eli/blog-status-format "<span><i class='bx bx-calendar'></i> 87: <span>%d</span></span>\n<span><i class='bx bx-edit'></i><span>%C</span></span>") 88: (defvar eli/blog-history-base-url "https://github.com/Elilif/Elilif.github.io/commits/master/orgs/") 89: 90: (defun eli/blog-build-article-status (info) 91: (let ((input-file (file-name-nondirectory (plist-get info :input-file)))) 92: (unless (string-equal input-file eli/blog-sitamap) 93: (let ((spec (org-html-format-spec info)) 94: (history-url (concat eli/blog-history-base-url input-file))) 95: (concat 96: "<div class=\"post-status\">" 97: (format-spec eli/blog-status-format spec) 98: (format "<span><i class='bx bx-history'></i><span><a href=\"%s\">history</a></span></span>" 99: history-url) 100: "</div>"))))) 101: 102: (defun eli/org-blog-template (contents info) 103: "Return complete document string after HTML conversion. 104: CONTENTS is the transcoded contents string. INFO is a plist 105: holding export options." 106: (setq eli-test info) 107: (concat 108: (when (and (not (org-html-html5-p info)) (org-html-xhtml-p info)) 109: (let* ((xml-declaration (plist-get info :html-xml-declaration)) 110: (decl (or (and (stringp xml-declaration) xml-declaration) 111: (cdr (assoc (plist-get info :html-extension) 112: xml-declaration)) 113: (cdr (assoc "html" xml-declaration)) 114: ""))) 115: (when (not (or (not decl) (string= "" decl))) 116: (format "%s\n" 117: (format decl 118: (or (and org-html-coding-system 119: ;; FIXME: Use Emacs 22 style here, see `coding-system-get'. 120: (coding-system-get org-html-coding-system 'mime-charset)) 121: "iso-8859-1")))))) 122: (org-html-doctype info) 123: "\n" 124: (concat "<html" 125: (cond ((org-html-xhtml-p info) 126: (format 127: " xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"%s\" xml:lang=\"%s\"" 128: (plist-get info :language) (plist-get info :language))) 129: ((org-html-html5-p info) 130: (format " lang=\"%s\"" (plist-get info :language)))) 131: ">\n") 132: "<head>\n" 133: (org-html--build-meta-info info) 134: (org-html--build-head info) 135: (org-html--build-mathjax-config info) 136: "</head>\n" 137: "<body>\n" 138: (let ((link-up (org-trim (plist-get info :html-link-up))) 139: (link-home (org-trim (plist-get info :html-link-home)))) 140: (unless (and (string= link-up "") (string= link-home "")) 141: (format (plist-get info :html-home/up-format) 142: (or link-up link-home) 143: (or link-home link-up)))) 144: ;; Preamble. 145: (org-html--build-pre/postamble 'preamble info) 146: ;; Document contents. 147: (let ((div (assq 'content (plist-get info :html-divs)))) 148: (format "<%s id=\"%s\" class=\"%s\">\n" 149: (nth 1 div) 150: (nth 2 div) 151: (plist-get info :html-content-class))) 152: ;; Document title. 153: (when (plist-get info :with-title) 154: (let ((title (and (plist-get info :with-title) 155: (plist-get info :title))) 156: (subtitle (plist-get info :subtitle)) 157: (html5-fancy (org-html--html5-fancy-p info))) 158: (when title 159: (format 160: (if html5-fancy 161: "<header>\n<h1 class=\"title\">%s</h1>\n%s</header>" 162: "<h1 class=\"title\">%s%s</h1>\n") 163: (org-export-data title info) 164: (if subtitle 165: (format 166: (if html5-fancy 167: "<p class=\"subtitle\" role=\"doc-subtitle\">%s</p>\n" 168: (concat "\n" (org-html-close-tag "br" nil info) "\n" 169: "<span class=\"subtitle\">%s</span>\n")) 170: (org-export-data subtitle info)) 171: ""))))) 172: ;; add article status 173: (eli/blog-build-article-status info) 174: contents 175: (format "</%s>\n" (nth 1 (assq 'content (plist-get info :html-divs)))) 176: ;; gisus 177: (eli/blog-build-giscus info) 178: ;; Postamble. 179: (org-html--build-pre/postamble 'postamble info) 180: ;; Possibly use the Klipse library live code blocks. 181: (when (plist-get info :html-klipsify-src) 182: (concat "<script>" (plist-get info :html-klipse-selection-script) 183: "</script><script src=\"" 184: org-html-klipse-js 185: "\"></script><link rel=\"stylesheet\" type=\"text/css\" href=\"" 186: org-html-klipse-css "\"/>")) 187: ;; Closing document. 188: "</body>\n</html>")) 189: 190: (defun eli/org-blog-footnote-reference (footnote-reference _contents info) 191: "Transcode a FOOTNOTE-REFERENCE element from Org to HTML. 192: CONTENTS is nil. INFO is a plist holding contextual information." 193: (concat 194: ;; Insert separator between two footnotes in a row. 195: (let ((prev (org-export-get-previous-element footnote-reference info))) 196: (when (eq (org-element-type prev) 'footnote-reference) 197: (plist-get info :html-footnote-separator))) 198: (let* ((n (org-export-get-footnote-number footnote-reference info)) 199: (id (format "fnr.%d%s" 200: n 201: (if (org-export-footnote-first-reference-p 202: footnote-reference info) 203: "" 204: ".100")))) 205: (format 206: (concat (plist-get info :html-footnote-format) 207: "<input id=\"%s\" class=\"footref-toggle\" type=\"checkbox\">") 208: (format "<label for=\"%s\" class=\"footref\">%s</label>" 209: id n) 210: id)))) 211: 212: (defun eli/org-blog-src-block (src-block _contents info) 213: "Transcode a SRC-BLOCK element from Org to HTML. 214: CONTENTS holds the contents of the item. INFO is a plist holding 215: contextual information." 216: (if (org-export-read-attribute :attr_html src-block :textarea) 217: (org-html--textarea-block src-block) 218: (let* ((lang (org-element-property :language src-block)) 219: (code (org-html-format-code src-block info)) 220: (label (let ((lbl (org-html--reference src-block info t))) 221: (if lbl (format " id=\"%s\"" lbl) ""))) 222: (klipsify (and (plist-get info :html-klipsify-src) 223: (member lang '("javascript" "js" 224: "ruby" "scheme" "clojure" "php" "html"))))) 225: (if (not lang) (format "<pre class=\"example\"%s><code>\n%s</code></pre>" label code) 226: (format "<div class=\"org-src-container\">\n%s%s\n</div>" 227: ;; Build caption. 228: (let ((caption (or (org-export-get-caption src-block) 229: (org-element-property :name src-block)))) 230: (if (not caption) "" 231: (let ((listing-number 232: (format 233: "<span class=\"listing-number\">%s </span>" 234: "Listing: "))) 235: (format "<div class=\"org-src-name\">%s%s</div>" 236: listing-number 237: (org-trim (org-export-data caption info)))))) 238: ;; Contents. 239: (if klipsify 240: (format "<pre><code class=\"src src-%s\"%s%s>%s</code></pre>" 241: lang 242: label 243: (if (string= lang "html") 244: " data-editor-type=\"html\"" 245: "") 246: code) 247: (format "<pre class=\"src src-%s\"%s><code>%s</code></pre>" 248: lang label code))))))) 249: 250: (org-export-define-derived-backend 'blog 'html 251: :translate-alist '((src-block . eli/org-blog-src-block) 252: (footnote-reference . eli/org-blog-footnote-reference) 253: (template . eli/org-blog-template)))
1: <<blog-helper--functions>> 2: 3: (setq org-publish-project-alist `( 4: <<my-blog>> 5: ))
1: (defun eli/kill-sitemap-buffer (project) 2: (let* ((sitemap-filename (plist-get project :sitemap-filename)) 3: (base-dir (plist-get project :base-directory)) 4: (sitemap-filepath (expand-file-name sitemap-filename base-dir))) 5: (when-let ((sitemap-buffer (find-buffer-visiting sitemap-filepath))) 6: (kill-buffer sitemap-buffer)))) 7: 8: (defun eli/blog-publish-completion (project) 9: (let* ((publishing-directory (plist-get project :publishing-directory)) 10: (sitamap (file-name-with-extension eli/blog-sitamap "html")) 11: (orig-file (expand-file-name sitamap publishing-directory)) 12: (target-file (expand-file-name 13: sitamap 14: (file-name-directory publishing-directory)))) 15: (rename-file orig-file target-file t))) 16: 17: (add-to-list 'org-export-global-macros 18: '("timestamp" . "@@html:<span class=\"timestamp\">[$1]</span>@@")) 19: 20: (defun eli/sitemap-dated-entry-format (entry _style project) 21: "Sitemap PROJECT ENTRY STYLE format that includes date." 22: (let* ((file (org-publish--expand-file-name entry project)) 23: (parsed-title (org-publish-find-property file :title project)) 24: (title 25: (if parsed-title 26: (org-no-properties 27: (org-element-interpret-data parsed-title)) 28: (file-name-nondirectory (file-name-sans-extension file))))) 29: (org-publish-cache-set-file-property file :title title) 30: (if (= (length title) 0) 31: (format "*%s*" entry) 32: (format "{{{timestamp(%s)}}} [[file:%s][%s]]" 33: (car (org-publish-find-property file :date project)) 34: (concat "articles/" entry) 35: title)))) 36: 37: (defun eli/org-publish-sitemap (title list) 38: "Generate the sitemap with title." 39: (concat "#+TITLE: " title 40: "\n" 41: "#+DATE: 2023-10-10" 42: "\n\n" 43: (org-list-to-org list))) 44: 45: (setq eli/blog-base-dir "path-to/blog/" 46: eli/blog-publish-dir "your-blog-site-dir" 47: eli/blog-sitamap "index.org") 48: 49: ;;;###autoload 50: (defun eli/org-blog-publish-to-html (plist filename pub-dir) 51: "Publish an org file to HTML. 52: 53: FILENAME is the filename of the Org file to be published. PLIST 54: is the property list for the given project. PUB-DIR is the 55: publishing directory. 56: 57: Return output file name." 58: (org-publish-org-to 'blog filename 59: (concat (when (> (length org-html-extension) 0) ".") 60: (or (plist-get plist :html-extension) 61: org-html-extension 62: "html")) 63: plist pub-dir)) 64: 65: (defvar eli/blog-giscus-script "<script src=\"https://giscus.app/client.js\" 66: data-repo=\"Elilif/Elilif.github.io\" 67: data-repo-id=\"MDEwOlJlcG9zaXRvcnkyOTgxNjM5ODg=\" 68: data-category=\"Announcements\" 69: data-category-id=\"DIC_kwDOEcWfFM4Cdz5V\" 70: data-mapping=\"pathname\" 71: data-strict=\"0\" 72: data-reactions-enabled=\"1\" 73: data-emit-metadata=\"0\" 74: data-input-position=\"top\" 75: data-theme=\"light\" 76: data-lang=\"zh-CN\" 77: crossorigin=\"anonymous\" 78: async> 79: </script>") 80: 81: (defun eli/blog-build-giscus (info) 82: (let ((input-file (file-name-nondirectory (plist-get info :input-file)))) 83: (unless (string-equal input-file eli/blog-sitamap) 84: eli/blog-giscus-script))) 85: 86: (defvar eli/blog-status-format "<span><i class='bx bx-calendar'></i> 87: <span>%d</span></span>\n<span><i class='bx bx-edit'></i><span>%C</span></span>") 88: (defvar eli/blog-history-base-url "https://github.com/Elilif/Elilif.github.io/commits/master/orgs/") 89: 90: (defun eli/blog-build-article-status (info) 91: (let ((input-file (file-name-nondirectory (plist-get info :input-file)))) 92: (unless (string-equal input-file eli/blog-sitamap) 93: (let ((spec (org-html-format-spec info)) 94: (history-url (concat eli/blog-history-base-url input-file))) 95: (concat 96: "<div class=\"post-status\">" 97: (format-spec eli/blog-status-format spec) 98: (format "<span><i class='bx bx-history'></i><span><a href=\"%s\">history</a></span></span>" 99: history-url) 100: "</div>"))))) 101: 102: (defun eli/org-blog-template (contents info) 103: "Return complete document string after HTML conversion. 104: CONTENTS is the transcoded contents string. INFO is a plist 105: holding export options." 106: (setq eli-test info) 107: (concat 108: (when (and (not (org-html-html5-p info)) (org-html-xhtml-p info)) 109: (let* ((xml-declaration (plist-get info :html-xml-declaration)) 110: (decl (or (and (stringp xml-declaration) xml-declaration) 111: (cdr (assoc (plist-get info :html-extension) 112: xml-declaration)) 113: (cdr (assoc "html" xml-declaration)) 114: ""))) 115: (when (not (or (not decl) (string= "" decl))) 116: (format "%s\n" 117: (format decl 118: (or (and org-html-coding-system 119: ;; FIXME: Use Emacs 22 style here, see `coding-system-get'. 120: (coding-system-get org-html-coding-system 'mime-charset)) 121: "iso-8859-1")))))) 122: (org-html-doctype info) 123: "\n" 124: (concat "<html" 125: (cond ((org-html-xhtml-p info) 126: (format 127: " xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"%s\" xml:lang=\"%s\"" 128: (plist-get info :language) (plist-get info :language))) 129: ((org-html-html5-p info) 130: (format " lang=\"%s\"" (plist-get info :language)))) 131: ">\n") 132: "<head>\n" 133: (org-html--build-meta-info info) 134: (org-html--build-head info) 135: (org-html--build-mathjax-config info) 136: "</head>\n" 137: "<body>\n" 138: (let ((link-up (org-trim (plist-get info :html-link-up))) 139: (link-home (org-trim (plist-get info :html-link-home)))) 140: (unless (and (string= link-up "") (string= link-home "")) 141: (format (plist-get info :html-home/up-format) 142: (or link-up link-home) 143: (or link-home link-up)))) 144: ;; Preamble. 145: (org-html--build-pre/postamble 'preamble info) 146: ;; Document contents. 147: (let ((div (assq 'content (plist-get info :html-divs)))) 148: (format "<%s id=\"%s\" class=\"%s\">\n" 149: (nth 1 div) 150: (nth 2 div) 151: (plist-get info :html-content-class))) 152: ;; Document title. 153: (when (plist-get info :with-title) 154: (let ((title (and (plist-get info :with-title) 155: (plist-get info :title))) 156: (subtitle (plist-get info :subtitle)) 157: (html5-fancy (org-html--html5-fancy-p info))) 158: (when title 159: (format 160: (if html5-fancy 161: "<header>\n<h1 class=\"title\">%s</h1>\n%s</header>" 162: "<h1 class=\"title\">%s%s</h1>\n") 163: (org-export-data title info) 164: (if subtitle 165: (format 166: (if html5-fancy 167: "<p class=\"subtitle\" role=\"doc-subtitle\">%s</p>\n" 168: (concat "\n" (org-html-close-tag "br" nil info) "\n" 169: "<span class=\"subtitle\">%s</span>\n")) 170: (org-export-data subtitle info)) 171: ""))))) 172: ;; add article status 173: (eli/blog-build-article-status info) 174: contents 175: (format "</%s>\n" (nth 1 (assq 'content (plist-get info :html-divs)))) 176: ;; gisus 177: (eli/blog-build-giscus info) 178: ;; Postamble. 179: (org-html--build-pre/postamble 'postamble info) 180: ;; Possibly use the Klipse library live code blocks. 181: (when (plist-get info :html-klipsify-src) 182: (concat "<script>" (plist-get info :html-klipse-selection-script) 183: "</script><script src=\"" 184: org-html-klipse-js 185: "\"></script><link rel=\"stylesheet\" type=\"text/css\" href=\"" 186: org-html-klipse-css "\"/>")) 187: ;; Closing document. 188: "</body>\n</html>")) 189: 190: (defun eli/org-blog-footnote-reference (footnote-reference _contents info) 191: "Transcode a FOOTNOTE-REFERENCE element from Org to HTML. 192: CONTENTS is nil. INFO is a plist holding contextual information." 193: (concat 194: ;; Insert separator between two footnotes in a row. 195: (let ((prev (org-export-get-previous-element footnote-reference info))) 196: (when (eq (org-element-type prev) 'footnote-reference) 197: (plist-get info :html-footnote-separator))) 198: (let* ((n (org-export-get-footnote-number footnote-reference info)) 199: (id (format "fnr.%d%s" 200: n 201: (if (org-export-footnote-first-reference-p 202: footnote-reference info) 203: "" 204: ".100")))) 205: (format 206: (concat (plist-get info :html-footnote-format) 207: "<input id=\"%s\" class=\"footref-toggle\" type=\"checkbox\">") 208: (format "<label for=\"%s\" class=\"footref\">%s</label>" 209: id n) 210: id)))) 211: 212: (defun eli/org-blog-src-block (src-block _contents info) 213: "Transcode a SRC-BLOCK element from Org to HTML. 214: CONTENTS holds the contents of the item. INFO is a plist holding 215: contextual information." 216: (if (org-export-read-attribute :attr_html src-block :textarea) 217: (org-html--textarea-block src-block) 218: (let* ((lang (org-element-property :language src-block)) 219: (code (org-html-format-code src-block info)) 220: (label (let ((lbl (org-html--reference src-block info t))) 221: (if lbl (format " id=\"%s\"" lbl) ""))) 222: (klipsify (and (plist-get info :html-klipsify-src) 223: (member lang '("javascript" "js" 224: "ruby" "scheme" "clojure" "php" "html"))))) 225: (if (not lang) (format "<pre class=\"example\"%s><code>\n%s</code></pre>" label code) 226: (format "<div class=\"org-src-container\">\n%s%s\n</div>" 227: ;; Build caption. 228: (let ((caption (or (org-export-get-caption src-block) 229: (org-element-property :name src-block)))) 230: (if (not caption) "" 231: (let ((listing-number 232: (format 233: "<span class=\"listing-number\">%s </span>" 234: "Listing: "))) 235: (format "<div class=\"org-src-name\">%s%s</div>" 236: listing-number 237: (org-trim (org-export-data caption info)))))) 238: ;; Contents. 239: (if klipsify 240: (format "<pre><code class=\"src src-%s\"%s%s>%s</code></pre>" 241: lang 242: label 243: (if (string= lang "html") 244: " data-editor-type=\"html\"" 245: "") 246: code) 247: (format "<pre class=\"src src-%s\"%s><code>%s</code></pre>" 248: lang label code))))))) 249: 250: (org-export-define-derived-backend 'blog 'html 251: :translate-alist '((src-block . eli/org-blog-src-block) 252: (footnote-reference . eli/org-blog-footnote-reference) 253: (template . eli/org-blog-template))) 254: 255: (setq org-publish-project-alist `( 256: ("eli's blog" 257: :base-directory ,eli/blog-base-dir 258: :publishing-directory ,(expand-file-name "articles" eli/blog-publish-dir) 259: :base-extension "org" 260: :recursive nil 261: :htmlized-source t 262: :publishing-function eli/org-blog-publish-to-html 263: :exclude "rss.org" 264: 265: :auto-sitemap t 266: :preparation-function eli/kill-sitemap-buffer 267: :completion-function eli/blog-publish-completion 268: :sitemap-filename ,eli/blog-sitamap 269: :sitemap-title "Eli's Blog" 270: :sitemap-sort-files anti-chronologically 271: :sitemap-function eli/org-publish-sitemap 272: :sitemap-format-entry eli/sitemap-dated-entry-format 273: 274: :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/style.css\" /> 275: <link rel=\"stylesheet\" type=\"text/css\" href=\"/css/htmlize.css\" /> 276: <script src=\"/scripts/script.js\"></script> 277: <script src=\"/scripts/toc.js\"></script>" 278: :html-preamble t 279: :html-preamble-format (("en" "<nav class=\"nav\"> 280: <a href=\"/index.html\" class=\"button\">Home</a> 281: <a href=\"/rss.xml\" class=\"button\">RSS</a> 282: <a href=\"/config.html\" class=\"button\">Literate Emacs Config</a> 283: </nav> 284: <hr>")) 285: :html-postamble t 286: :html-postamble-format (("en" "<hr class=\"Solid\"> 287: <div class=\"info\"> 288: <span class=\"author\">Author: %a (%e)</span> 289: <span class=\"date\">Create Date: %d</span> 290: <span class=\"date\">Last modified: %C</span> 291: <span>Creator: %c</span> 292: </div>")) 293: :with-creator nil 294: ) 295: ))
RSS 生成
RSS 对于一个博客来说是比较重要的,可惜的是 ox-publish.el 没有原生功能支持。不过好在有 GitHub - BenedictHW/ox-rss 。然而 ox-rss 也有缺点,它只能用于单个文件里的 headlines 。因此我们需要曲线救国,新建一个 publish 项目,使用 sitemap 来收集 RSS entry ,生成一个 rss.org,最后把他导出成我们需要的 rss.xml 。
1: (defun eli/org-publish-rss-entry (entry _style project) 2: "Format ENTRY for the posts RSS feed in PROJECT." 3: (let* ((file (org-publish--expand-file-name entry project)) 4: (preview (eli/blog-get-preview file)) 5: (parsed-title (org-publish-find-property file :title project)) 6: (title 7: (if parsed-title 8: (org-no-properties 9: (org-element-interpret-data parsed-title)) 10: (file-name-nondirectory (file-name-sans-extension file)))) 11: (root (org-publish-property :html-link-home project)) 12: (link (concat 13: "articles/" 14: (file-name-sans-extension entry) ".html")) 15: (pubdate (car (org-publish-find-property file :date project)))) 16: (org-publish-cache-set-file-property file :title title) 17: (format "%s 18: :properties: 19: :rss_permalink: %s 20: :pubdate: %s 21: :end:\n%s\n[[%s][Read More]]" 22: title 23: link 24: pubdate 25: preview 26: (concat 27: root 28: link))))
1: (defun eli/org-publish-rss-sitemap (title list) 2: "Generate a sitemap of posts that is exported as a RSS feed. 3: TITLE is the title of the RSS feed. LIST is an internal 4: representation for the files to include. PROJECT is the current 5: project." 6: (concat 7: "#+TITLE: " title 8: "\n\n" 9: (org-list-to-subtree list)))
1: (defun eli/org-publish-rss-feed (plist filename dir) 2: "Publish PLIST to Rss when FILENAME is rss.org. 3: DIR is the location of the output." 4: (if (equal "rss.org" (file-name-nondirectory filename)) 5: (org-rss-publish-to-rss plist filename dir)))
1: :publishing-function eli/org-publish-rss-feed 2: :auto-sitemap t 3: :sitemap-function eli/org-publish-rss-sitemap 4: :sitemap-title "Eli's Blog" 5: :sitemap-filename "rss.org" 6: :sitemap-sort-files anti-chronologically 7: :sitemap-format-entry eli/org-publish-rss-entry
在导出的时候我们只希望导出 rss.org ,所以需要设置 :include
属性为 ("rss.org")
,同时我们不希望收集 RSS entry 时把 index.org 中的内容也收集进去,所以需要设置
:exclude
属性为 "index.org"
。
剩余的属性如下:
1: :preparation-function eli/kill-sitemap-buffer 2: :publishing-directory ,eli/blog-publish-dir 3: :base-directory ,eli/blog-base-dir 4: :rss-extension "xml" 5: :base-extension "org" 6: :html-link-home "https://elilif.github.io/" 7: :html-link-use-abs-url t 8: :html-link-org-files-as-html t 9: :include ("rss.org") 10: :exclude "index.org"
现在整个 rss 项目就完成了。
1: ("eli's blog rss" 2: <<rss-sitemap>> 3: <<rss-misc>>)
1: ("eli's blog rss" 2: :publishing-function eli/org-publish-rss-feed 3: :auto-sitemap t 4: :sitemap-function eli/org-publish-rss-sitemap 5: :sitemap-title "Eli's Blog" 6: :sitemap-filename "rss.org" 7: :sitemap-sort-files anti-chronologically 8: :sitemap-format-entry eli/org-publish-rss-entry 9: :preparation-function eli/kill-sitemap-buffer 10: :publishing-directory ,eli/blog-publish-dir 11: :base-directory ,eli/blog-base-dir 12: :rss-extension "xml" 13: :base-extension "org" 14: :html-link-home "https://elilif.github.io/" 15: :html-link-use-abs-url t 16: :html-link-org-files-as-html t 17: :include ("rss.org") 18: :exclude "index.org")
下面是完整的代码:
1: <<eli/org-publish-rss-feed>> 2: 3: <<eli/org-publish-rss-sitemap>> 4: 5: <<eli/org-publish-rss-entry>>
1: (defun eli/org-publish-rss-feed (plist filename dir) 2: "Publish PLIST to Rss when FILENAME is rss.org. 3: DIR is the location of the output." 4: (if (equal "rss.org" (file-name-nondirectory filename)) 5: (org-rss-publish-to-rss plist filename dir))) 6: 7: (defun eli/org-publish-rss-sitemap (title list) 8: "Generate a sitemap of posts that is exported as a RSS feed. 9: TITLE is the title of the RSS feed. LIST is an internal 10: representation for the files to include. PROJECT is the current 11: project." 12: (concat 13: "#+TITLE: " title 14: "\n\n" 15: (org-list-to-subtree list))) 16: 17: (defun eli/org-publish-rss-entry (entry _style project) 18: "Format ENTRY for the posts RSS feed in PROJECT." 19: (let* ((file (org-publish--expand-file-name entry project)) 20: (preview (eli/blog-get-preview file)) 21: (parsed-title (org-publish-find-property file :title project)) 22: (title 23: (if parsed-title 24: (org-no-properties 25: (org-element-interpret-data parsed-title)) 26: (file-name-nondirectory (file-name-sans-extension file)))) 27: (root (org-publish-property :html-link-home project)) 28: (link (concat 29: "articles/" 30: (file-name-sans-extension entry) ".html")) 31: (pubdate (car (org-publish-find-property file :date project)))) 32: (org-publish-cache-set-file-property file :title title) 33: (format "%s 34: :properties: 35: :rss_permalink: %s 36: :pubdate: %s 37: :end:\n%s\n[[%s][Read More]]" 38: title 39: link 40: pubdate 41: preview 42: (concat 43: root 44: link))))
1: <<rss-helper--functions>> 2: 3: <<rss>>
1: (defun eli/org-publish-rss-feed (plist filename dir) 2: "Publish PLIST to Rss when FILENAME is rss.org. 3: DIR is the location of the output." 4: (if (equal "rss.org" (file-name-nondirectory filename)) 5: (org-rss-publish-to-rss plist filename dir))) 6: 7: (defun eli/org-publish-rss-sitemap (title list) 8: "Generate a sitemap of posts that is exported as a RSS feed. 9: TITLE is the title of the RSS feed. LIST is an internal 10: representation for the files to include. PROJECT is the current 11: project." 12: (concat 13: "#+TITLE: " title 14: "\n\n" 15: (org-list-to-subtree list))) 16: 17: (defun eli/org-publish-rss-entry (entry _style project) 18: "Format ENTRY for the posts RSS feed in PROJECT." 19: (let* ((file (org-publish--expand-file-name entry project)) 20: (preview (eli/blog-get-preview file)) 21: (parsed-title (org-publish-find-property file :title project)) 22: (title 23: (if parsed-title 24: (org-no-properties 25: (org-element-interpret-data parsed-title)) 26: (file-name-nondirectory (file-name-sans-extension file)))) 27: (root (org-publish-property :html-link-home project)) 28: (link (concat 29: "articles/" 30: (file-name-sans-extension entry) ".html")) 31: (pubdate (car (org-publish-find-property file :date project)))) 32: (org-publish-cache-set-file-property file :title title) 33: (format "%s 34: :properties: 35: :rss_permalink: %s 36: :pubdate: %s 37: :end:\n%s\n[[%s][Read More]]" 38: title 39: link 40: pubdate 41: preview 42: (concat 43: root 44: link)))) 45: 46: ("eli's blog rss" 47: :publishing-function eli/org-publish-rss-feed 48: :auto-sitemap t 49: :sitemap-function eli/org-publish-rss-sitemap 50: :sitemap-title "Eli's Blog" 51: :sitemap-filename "rss.org" 52: :sitemap-sort-files anti-chronologically 53: :sitemap-format-entry eli/org-publish-rss-entry 54: :preparation-function eli/kill-sitemap-buffer 55: :publishing-directory ,eli/blog-publish-dir 56: :base-directory ,eli/blog-base-dir 57: :rss-extension "xml" 58: :base-extension "org" 59: :html-link-home "https://elilif.github.io/" 60: :html-link-use-abs-url t 61: :html-link-org-files-as-html t 62: :include ("rss.org") 63: :exclude "index.org")
Org 文学编程网页导出
我希望在导出时能够把代码块中的 noweb 展开,并且在网页中同时提供不展开和展开两种版本。这样就能在保持原汁原味的文学编程的同时又方便读者查看。下面是大致实现思路:
通过 org-export-before-processing-functions
在导出时复制一遍代码块,并在添上
:noweb yes
参数后和原代码块放到一个 special block (#+begin_multilang
、
#+end_multilang
) 之间。这样在 HTML 就是一个 class 为 multilang
的 div
。然后使用
js 添加一个按钮,实现不同版本间的切换。
1: (defun eli/org-export-src-babel-duplicate (backend) 2: "Duplicate every src babels in the current buffer. 3: 4: add \":noweb yes\" to duplicated src babels." 5: (when (eq backend 'blog) 6: (save-excursion 7: (goto-char (point-min)) 8: (while (re-search-forward org-babel-src-block-regexp nil t) 9: (let ((end (copy-marker (match-end 0))) 10: (string (match-string 0)) 11: (block (org-element-at-point))) 12: (goto-char (org-element-property :begin block)) 13: (insert "#+begin_multilang") 14: (insert "\n") 15: (goto-char end) 16: (insert "\n") 17: (insert string) 18: (save-excursion 19: (goto-char (1+ end)) 20: (end-of-line) 21: (insert " :noweb yes")) 22: (insert "\n") 23: (insert "#+end_multilang")))))) 24: 25: (add-hook 'org-export-before-processing-functions #'eli/org-export-src-babel-duplicate)
Emacs 配置部分的 project 如下:
1: (defun eli/org-export-src-babel-duplicate (backend) 2: "Duplicate every src babels in the current buffer. 3: 4: add \":noweb yes\" to duplicated src babels." 5: (when (eq backend 'blog) 6: (save-excursion 7: (goto-char (point-min)) 8: (while (re-search-forward org-babel-src-block-regexp nil t) 9: (let ((end (copy-marker (match-end 0))) 10: (string (match-string 0)) 11: (block (org-element-at-point))) 12: (goto-char (org-element-property :begin block)) 13: (insert "#+begin_multilang") 14: (insert "\n") 15: (goto-char end) 16: (insert "\n") 17: (insert string) 18: (save-excursion 19: (goto-char (1+ end)) 20: (end-of-line) 21: (insert " :noweb yes")) 22: (insert "\n") 23: (insert "#+end_multilang")))))) 24: 25: (add-hook 'org-export-before-processing-functions #'eli/org-export-src-babel-duplicate)
1: ("Emacs config" 2: :publishing-directory ,eli/blog-publish-dir 3: :base-directory ,user-emacs-directory 4: :include ("config.org") 5: :publishing-function eli/org-blog-publish-to-html 6: <<html>>)
1: ("Emacs config" 2: :publishing-directory ,eli/blog-publish-dir 3: :base-directory ,user-emacs-directory 4: :include ("config.org") 5: :publishing-function eli/org-blog-publish-to-html 6: :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/style.css\" /> 7: <link rel=\"stylesheet\" type=\"text/css\" href=\"/css/htmlize.css\" /> 8: <script src=\"/scripts/script.js\"></script> 9: <script src=\"/scripts/toc.js\"></script>" 10: :html-preamble t 11: :html-preamble-format (("en" "<nav class=\"nav\"> 12: <a href=\"/index.html\" class=\"button\">Home</a> 13: <a href=\"/rss.xml\" class=\"button\">RSS</a> 14: <a href=\"/config.html\" class=\"button\">Literate Emacs Config</a> 15: </nav> 16: <hr>")) 17: :html-postamble t 18: :html-postamble-format (("en" "<hr class=\"Solid\"> 19: <div class=\"info\"> 20: <span class=\"author\">Author: %a (%e)</span> 21: <span class=\"date\">Create Date: %d</span> 22: <span class=\"date\">Last modified: %C</span> 23: <span>Creator: %c</span> 24: </div>")) 25: :with-creator nil)
总结
以下代码完整地包括了前文提到的内容:
1: <<blog-helper--functions>> 2: 3: <<rss-helper--functions>> 4: 5: <<config-helper--functions>> 6: 7: (setq org-publish-project-alist `( 8: <<my-blog>> 9: <<rss>> 10: <<emacs-config>> 11: ))
1: (defun eli/kill-sitemap-buffer (project) 2: (let* ((sitemap-filename (plist-get project :sitemap-filename)) 3: (base-dir (plist-get project :base-directory)) 4: (sitemap-filepath (expand-file-name sitemap-filename base-dir))) 5: (when-let ((sitemap-buffer (find-buffer-visiting sitemap-filepath))) 6: (kill-buffer sitemap-buffer)))) 7: 8: (defun eli/blog-publish-completion (project) 9: (let* ((publishing-directory (plist-get project :publishing-directory)) 10: (sitamap (file-name-with-extension eli/blog-sitamap "html")) 11: (orig-file (expand-file-name sitamap publishing-directory)) 12: (target-file (expand-file-name 13: sitamap 14: (file-name-directory publishing-directory)))) 15: (rename-file orig-file target-file t))) 16: 17: (add-to-list 'org-export-global-macros 18: '("timestamp" . "@@html:<span class=\"timestamp\">[$1]</span>@@")) 19: 20: (defun eli/sitemap-dated-entry-format (entry _style project) 21: "Sitemap PROJECT ENTRY STYLE format that includes date." 22: (let* ((file (org-publish--expand-file-name entry project)) 23: (parsed-title (org-publish-find-property file :title project)) 24: (title 25: (if parsed-title 26: (org-no-properties 27: (org-element-interpret-data parsed-title)) 28: (file-name-nondirectory (file-name-sans-extension file))))) 29: (org-publish-cache-set-file-property file :title title) 30: (if (= (length title) 0) 31: (format "*%s*" entry) 32: (format "{{{timestamp(%s)}}} [[file:%s][%s]]" 33: (car (org-publish-find-property file :date project)) 34: (concat "articles/" entry) 35: title)))) 36: 37: (defun eli/org-publish-sitemap (title list) 38: "Generate the sitemap with title." 39: (concat "#+TITLE: " title 40: "\n" 41: "#+DATE: 2023-10-10" 42: "\n\n" 43: (org-list-to-org list))) 44: 45: (setq eli/blog-base-dir "path-to/blog/" 46: eli/blog-publish-dir "your-blog-site-dir" 47: eli/blog-sitamap "index.org") 48: 49: ;;;###autoload 50: (defun eli/org-blog-publish-to-html (plist filename pub-dir) 51: "Publish an org file to HTML. 52: 53: FILENAME is the filename of the Org file to be published. PLIST 54: is the property list for the given project. PUB-DIR is the 55: publishing directory. 56: 57: Return output file name." 58: (org-publish-org-to 'blog filename 59: (concat (when (> (length org-html-extension) 0) ".") 60: (or (plist-get plist :html-extension) 61: org-html-extension 62: "html")) 63: plist pub-dir)) 64: 65: (defvar eli/blog-giscus-script "<script src=\"https://giscus.app/client.js\" 66: data-repo=\"Elilif/Elilif.github.io\" 67: data-repo-id=\"MDEwOlJlcG9zaXRvcnkyOTgxNjM5ODg=\" 68: data-category=\"Announcements\" 69: data-category-id=\"DIC_kwDOEcWfFM4Cdz5V\" 70: data-mapping=\"pathname\" 71: data-strict=\"0\" 72: data-reactions-enabled=\"1\" 73: data-emit-metadata=\"0\" 74: data-input-position=\"top\" 75: data-theme=\"light\" 76: data-lang=\"zh-CN\" 77: crossorigin=\"anonymous\" 78: async> 79: </script>") 80: 81: (defun eli/blog-build-giscus (info) 82: (let ((input-file (file-name-nondirectory (plist-get info :input-file)))) 83: (unless (string-equal input-file eli/blog-sitamap) 84: eli/blog-giscus-script))) 85: 86: (defvar eli/blog-status-format "<span><i class='bx bx-calendar'></i> 87: <span>%d</span></span>\n<span><i class='bx bx-edit'></i><span>%C</span></span>") 88: (defvar eli/blog-history-base-url "https://github.com/Elilif/Elilif.github.io/commits/master/orgs/") 89: 90: (defun eli/blog-build-article-status (info) 91: (let ((input-file (file-name-nondirectory (plist-get info :input-file)))) 92: (unless (string-equal input-file eli/blog-sitamap) 93: (let ((spec (org-html-format-spec info)) 94: (history-url (concat eli/blog-history-base-url input-file))) 95: (concat 96: "<div class=\"post-status\">" 97: (format-spec eli/blog-status-format spec) 98: (format "<span><i class='bx bx-history'></i><span><a href=\"%s\">history</a></span></span>" 99: history-url) 100: "</div>"))))) 101: 102: (defun eli/org-blog-template (contents info) 103: "Return complete document string after HTML conversion. 104: CONTENTS is the transcoded contents string. INFO is a plist 105: holding export options." 106: (setq eli-test info) 107: (concat 108: (when (and (not (org-html-html5-p info)) (org-html-xhtml-p info)) 109: (let* ((xml-declaration (plist-get info :html-xml-declaration)) 110: (decl (or (and (stringp xml-declaration) xml-declaration) 111: (cdr (assoc (plist-get info :html-extension) 112: xml-declaration)) 113: (cdr (assoc "html" xml-declaration)) 114: ""))) 115: (when (not (or (not decl) (string= "" decl))) 116: (format "%s\n" 117: (format decl 118: (or (and org-html-coding-system 119: ;; FIXME: Use Emacs 22 style here, see `coding-system-get'. 120: (coding-system-get org-html-coding-system 'mime-charset)) 121: "iso-8859-1")))))) 122: (org-html-doctype info) 123: "\n" 124: (concat "<html" 125: (cond ((org-html-xhtml-p info) 126: (format 127: " xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"%s\" xml:lang=\"%s\"" 128: (plist-get info :language) (plist-get info :language))) 129: ((org-html-html5-p info) 130: (format " lang=\"%s\"" (plist-get info :language)))) 131: ">\n") 132: "<head>\n" 133: (org-html--build-meta-info info) 134: (org-html--build-head info) 135: (org-html--build-mathjax-config info) 136: "</head>\n" 137: "<body>\n" 138: (let ((link-up (org-trim (plist-get info :html-link-up))) 139: (link-home (org-trim (plist-get info :html-link-home)))) 140: (unless (and (string= link-up "") (string= link-home "")) 141: (format (plist-get info :html-home/up-format) 142: (or link-up link-home) 143: (or link-home link-up)))) 144: ;; Preamble. 145: (org-html--build-pre/postamble 'preamble info) 146: ;; Document contents. 147: (let ((div (assq 'content (plist-get info :html-divs)))) 148: (format "<%s id=\"%s\" class=\"%s\">\n" 149: (nth 1 div) 150: (nth 2 div) 151: (plist-get info :html-content-class))) 152: ;; Document title. 153: (when (plist-get info :with-title) 154: (let ((title (and (plist-get info :with-title) 155: (plist-get info :title))) 156: (subtitle (plist-get info :subtitle)) 157: (html5-fancy (org-html--html5-fancy-p info))) 158: (when title 159: (format 160: (if html5-fancy 161: "<header>\n<h1 class=\"title\">%s</h1>\n%s</header>" 162: "<h1 class=\"title\">%s%s</h1>\n") 163: (org-export-data title info) 164: (if subtitle 165: (format 166: (if html5-fancy 167: "<p class=\"subtitle\" role=\"doc-subtitle\">%s</p>\n" 168: (concat "\n" (org-html-close-tag "br" nil info) "\n" 169: "<span class=\"subtitle\">%s</span>\n")) 170: (org-export-data subtitle info)) 171: ""))))) 172: ;; add article status 173: (eli/blog-build-article-status info) 174: contents 175: (format "</%s>\n" (nth 1 (assq 'content (plist-get info :html-divs)))) 176: ;; gisus 177: (eli/blog-build-giscus info) 178: ;; Postamble. 179: (org-html--build-pre/postamble 'postamble info) 180: ;; Possibly use the Klipse library live code blocks. 181: (when (plist-get info :html-klipsify-src) 182: (concat "<script>" (plist-get info :html-klipse-selection-script) 183: "</script><script src=\"" 184: org-html-klipse-js 185: "\"></script><link rel=\"stylesheet\" type=\"text/css\" href=\"" 186: org-html-klipse-css "\"/>")) 187: ;; Closing document. 188: "</body>\n</html>")) 189: 190: (defun eli/org-blog-footnote-reference (footnote-reference _contents info) 191: "Transcode a FOOTNOTE-REFERENCE element from Org to HTML. 192: CONTENTS is nil. INFO is a plist holding contextual information." 193: (concat 194: ;; Insert separator between two footnotes in a row. 195: (let ((prev (org-export-get-previous-element footnote-reference info))) 196: (when (eq (org-element-type prev) 'footnote-reference) 197: (plist-get info :html-footnote-separator))) 198: (let* ((n (org-export-get-footnote-number footnote-reference info)) 199: (id (format "fnr.%d%s" 200: n 201: (if (org-export-footnote-first-reference-p 202: footnote-reference info) 203: "" 204: ".100")))) 205: (format 206: (concat (plist-get info :html-footnote-format) 207: "<input id=\"%s\" class=\"footref-toggle\" type=\"checkbox\">") 208: (format "<label for=\"%s\" class=\"footref\">%s</label>" 209: id n) 210: id)))) 211: 212: (defun eli/org-blog-src-block (src-block _contents info) 213: "Transcode a SRC-BLOCK element from Org to HTML. 214: CONTENTS holds the contents of the item. INFO is a plist holding 215: contextual information." 216: (if (org-export-read-attribute :attr_html src-block :textarea) 217: (org-html--textarea-block src-block) 218: (let* ((lang (org-element-property :language src-block)) 219: (code (org-html-format-code src-block info)) 220: (label (let ((lbl (org-html--reference src-block info t))) 221: (if lbl (format " id=\"%s\"" lbl) ""))) 222: (klipsify (and (plist-get info :html-klipsify-src) 223: (member lang '("javascript" "js" 224: "ruby" "scheme" "clojure" "php" "html"))))) 225: (if (not lang) (format "<pre class=\"example\"%s><code>\n%s</code></pre>" label code) 226: (format "<div class=\"org-src-container\">\n%s%s\n</div>" 227: ;; Build caption. 228: (let ((caption (or (org-export-get-caption src-block) 229: (org-element-property :name src-block)))) 230: (if (not caption) "" 231: (let ((listing-number 232: (format 233: "<span class=\"listing-number\">%s </span>" 234: "Listing: "))) 235: (format "<div class=\"org-src-name\">%s%s</div>" 236: listing-number 237: (org-trim (org-export-data caption info)))))) 238: ;; Contents. 239: (if klipsify 240: (format "<pre><code class=\"src src-%s\"%s%s>%s</code></pre>" 241: lang 242: label 243: (if (string= lang "html") 244: " data-editor-type=\"html\"" 245: "") 246: code) 247: (format "<pre class=\"src src-%s\"%s><code>%s</code></pre>" 248: lang label code))))))) 249: 250: (org-export-define-derived-backend 'blog 'html 251: :translate-alist '((src-block . eli/org-blog-src-block) 252: (footnote-reference . eli/org-blog-footnote-reference) 253: (template . eli/org-blog-template))) 254: 255: (defun eli/org-publish-rss-feed (plist filename dir) 256: "Publish PLIST to Rss when FILENAME is rss.org. 257: DIR is the location of the output." 258: (if (equal "rss.org" (file-name-nondirectory filename)) 259: (org-rss-publish-to-rss plist filename dir))) 260: 261: (defun eli/org-publish-rss-sitemap (title list) 262: "Generate a sitemap of posts that is exported as a RSS feed. 263: TITLE is the title of the RSS feed. LIST is an internal 264: representation for the files to include. PROJECT is the current 265: project." 266: (concat 267: "#+TITLE: " title 268: "\n\n" 269: (org-list-to-subtree list))) 270: 271: (defun eli/org-publish-rss-entry (entry _style project) 272: "Format ENTRY for the posts RSS feed in PROJECT." 273: (let* ((file (org-publish--expand-file-name entry project)) 274: (preview (eli/blog-get-preview file)) 275: (parsed-title (org-publish-find-property file :title project)) 276: (title 277: (if parsed-title 278: (org-no-properties 279: (org-element-interpret-data parsed-title)) 280: (file-name-nondirectory (file-name-sans-extension file)))) 281: (root (org-publish-property :html-link-home project)) 282: (link (concat 283: "articles/" 284: (file-name-sans-extension entry) ".html")) 285: (pubdate (car (org-publish-find-property file :date project)))) 286: (org-publish-cache-set-file-property file :title title) 287: (format "%s 288: :properties: 289: :rss_permalink: %s 290: :pubdate: %s 291: :end:\n%s\n[[%s][Read More]]" 292: title 293: link 294: pubdate 295: preview 296: (concat 297: root 298: link)))) 299: 300: (defun eli/org-export-src-babel-duplicate (backend) 301: "Duplicate every src babels in the current buffer. 302: 303: add \":noweb yes\" to duplicated src babels." 304: (when (eq backend 'blog) 305: (save-excursion 306: (goto-char (point-min)) 307: (while (re-search-forward org-babel-src-block-regexp nil t) 308: (let ((end (copy-marker (match-end 0))) 309: (string (match-string 0)) 310: (block (org-element-at-point))) 311: (goto-char (org-element-property :begin block)) 312: (insert "#+begin_multilang") 313: (insert "\n") 314: (goto-char end) 315: (insert "\n") 316: (insert string) 317: (save-excursion 318: (goto-char (1+ end)) 319: (end-of-line) 320: (insert " :noweb yes")) 321: (insert "\n") 322: (insert "#+end_multilang")))))) 323: 324: (add-hook 'org-export-before-processing-functions #'eli/org-export-src-babel-duplicate) 325: 326: (setq org-publish-project-alist `( 327: ("eli's blog" 328: :base-directory ,eli/blog-base-dir 329: :publishing-directory ,(expand-file-name "articles" eli/blog-publish-dir) 330: :base-extension "org" 331: :recursive nil 332: :htmlized-source t 333: :publishing-function eli/org-blog-publish-to-html 334: :exclude "rss.org" 335: 336: :auto-sitemap t 337: :preparation-function eli/kill-sitemap-buffer 338: :completion-function eli/blog-publish-completion 339: :sitemap-filename ,eli/blog-sitamap 340: :sitemap-title "Eli's Blog" 341: :sitemap-sort-files anti-chronologically 342: :sitemap-function eli/org-publish-sitemap 343: :sitemap-format-entry eli/sitemap-dated-entry-format 344: 345: :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/style.css\" /> 346: <link rel=\"stylesheet\" type=\"text/css\" href=\"/css/htmlize.css\" /> 347: <script src=\"/scripts/script.js\"></script> 348: <script src=\"/scripts/toc.js\"></script>" 349: :html-preamble t 350: :html-preamble-format (("en" "<nav class=\"nav\"> 351: <a href=\"/index.html\" class=\"button\">Home</a> 352: <a href=\"/rss.xml\" class=\"button\">RSS</a> 353: <a href=\"/config.html\" class=\"button\">Literate Emacs Config</a> 354: </nav> 355: <hr>")) 356: :html-postamble t 357: :html-postamble-format (("en" "<hr class=\"Solid\"> 358: <div class=\"info\"> 359: <span class=\"author\">Author: %a (%e)</span> 360: <span class=\"date\">Create Date: %d</span> 361: <span class=\"date\">Last modified: %C</span> 362: <span>Creator: %c</span> 363: </div>")) 364: :with-creator nil 365: ) 366: ("eli's blog rss" 367: :publishing-function eli/org-publish-rss-feed 368: :auto-sitemap t 369: :sitemap-function eli/org-publish-rss-sitemap 370: :sitemap-title "Eli's Blog" 371: :sitemap-filename "rss.org" 372: :sitemap-sort-files anti-chronologically 373: :sitemap-format-entry eli/org-publish-rss-entry 374: :preparation-function eli/kill-sitemap-buffer 375: :publishing-directory ,eli/blog-publish-dir 376: :base-directory ,eli/blog-base-dir 377: :rss-extension "xml" 378: :base-extension "org" 379: :html-link-home "https://elilif.github.io/" 380: :html-link-use-abs-url t 381: :html-link-org-files-as-html t 382: :include ("rss.org") 383: :exclude "index.org") 384: ("Emacs config" 385: :publishing-directory ,eli/blog-publish-dir 386: :base-directory ,user-emacs-directory 387: :include ("config.org") 388: :publishing-function eli/org-blog-publish-to-html 389: :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/style.css\" /> 390: <link rel=\"stylesheet\" type=\"text/css\" href=\"/css/htmlize.css\" /> 391: <script src=\"/scripts/script.js\"></script> 392: <script src=\"/scripts/toc.js\"></script>" 393: :html-preamble t 394: :html-preamble-format (("en" "<nav class=\"nav\"> 395: <a href=\"/index.html\" class=\"button\">Home</a> 396: <a href=\"/rss.xml\" class=\"button\">RSS</a> 397: <a href=\"/config.html\" class=\"button\">Literate Emacs Config</a> 398: </nav> 399: <hr>")) 400: :html-postamble t 401: :html-postamble-format (("en" "<hr class=\"Solid\"> 402: <div class=\"info\"> 403: <span class=\"author\">Author: %a (%e)</span> 404: <span class=\"date\">Create Date: %d</span> 405: <span class=\"date\">Last modified: %C</span> 406: <span>Creator: %c</span> 407: </div>")) 408: :with-creator nil) 409: ))
Footnotes:
GitHub - niklasfasching/go-org :the goal for the parser is to support a reasonable subset of Org mode. Org mode is huge and I like to follow the 80/20 rule.
代码块中如果有 <<xxx>>
之类的文本,可以点击代码块上的 expand
按钮展开,也可以直接点击 <<xxx>>
跳转到定义位置。
比如说这条注释。
具体可以查阅变量 org-html-preamble-format
的文档。
具体可以查阅变量 org-html-postamble-format
的文档。
具体可以查阅变量 org-html-head
的文档。