Org to blog using org-publish
Table of Contents
1. Introduction
I have tried many org to html or markdown to generate blog posts using different framework. They are all not stable enough. This article describes my configuration step by step to use org-publish to build my blog.
The key point is to use org-publish variable org-publish-project-alist
.
2. The structure of configuration
The configuration should meet the following requirements:
- Most of the configuration should located inside
~/.emacs.d
folder and most of the configuration should be setup using elisp. - Suppose my org files are ready to be exported located at
/code/capture-org/publish
. After exported (recursively), they should be converted as html files using/code/blog/
as blog base folder. - During exporting, some templates such as headers and footers are added automatically.
- Different resources(org files, images, css, other assets, etc) probably are located in different projects or folders. When execute
M-x org-publish
, those resources will be exported into/code/blog/
with corresponding structure to form our blog.
3. Configurations in detail
3.1. Configuration put in Emacs
(use-package htmlize :defer t :commands (org-export-dispatch) :ensure t) (use-package ox-reveal :ensure t :commands (org-export-dispatch) :config (setq org-reveal-root "https://cdn.jsdelivr.net/npm/reveal.js") (setq org-reveal-mathjax t)) (setq blog-org-files-dir "~/code/capture-org/publish/") (setq emacs-config-base-dir "~/.emacs.d/") (setq blog-publish-base-dir "~/code/blog/") ;; ref: https://orgmode.org/worg/org-tutorials/org-publish-html-tutorial.html#org625a5c5 (after-load 'org (require 'ox-publish) (setq org-export-with-section-numbers nil org-export-with-smart-quotes t org-export-with-toc 3) (defvar this-date-format "%b %d, %Y") (setq org-html-divs '((preamble "header" "top") (content "main" "content") (postamble "footer" "postamble")) org-html-container-element "section" org-html-metadata-timestamp-format this-date-format org-html-checkbox-type 'html org-html-html5-fancy t org-html-validation-link t org-html-doctype "html5" org-html-htmlize-output-type 'css org-src-fontify-natively t) (defvar me/website-html-head "<link rel='icon' type='image/x-icon' href='/images/favicon.ico'/> <meta name='viewport' content='width=device-width, initial-scale=1'> <link rel='stylesheet' href='https://code.cdn.mozilla.net/fonts/fira.css'> <link rel='stylesheet' href='/css/site.css?v=2' type='text/css'/> <link rel='stylesheet' href='/css/custom.css' type='text/css'/> <link rel='stylesheet' href='/css/syntax-coloring.css' type='text/css'/>") (defun me/website-html-preamble (plist) "PLIST: An entry." (if (org-export-get-date plist this-date-format) (plist-put plist :subtitle (format "Published on %s by %s." (org-export-get-date plist this-date-format) (car (plist-get plist :author))))) ;; Preamble (with-temp-buffer (insert-file-contents "~/.emacs.d/html-templates/preamble.html") (buffer-string))) (defun me/website-html-postamble (plist) "PLIST." (concat (format (with-temp-buffer (insert-file-contents "~/.emacs.d/html-templates/postamble.html") (buffer-string)) (format-time-string this-date-format (plist-get plist :time)) (plist-get plist :creator)))) (defvar site-attachments (regexp-opt '("jpg" "jpeg" "gif" "png" "svg" "ico" "cur" "css" "js" "woff" "html" "pdf")) "File types that are published as static files.") (defun me/org-sitemap-format-entry (entry style project) "Format posts with author and published data in the index page. ENTRY: file-name STYLE: PROJECT: `posts in this case." (cond ((not (directory-name-p entry)) (format "*[[file:%s][%s]]* #+HTML: <p class='pubdate'>by %s on %s.</p>" entry (org-publish-find-title entry project) (car (org-publish-find-property entry :author project)) (format-time-string this-date-format (org-publish-find-date entry project)))) ((eq style 'tree) (file-name-nondirectory (directory-file-name entry))) (t entry))) (defun me/org-reveal-publish-to-html (plist filename pub-dir) "Publish an org file to reveal.js HTML Presentation. FILENAME is the filename of the Org file to be published. PLIST is the property list for the given project. PUB-DIR is the publishing directory. Returns output file name." (let ((org-reveal-root "http://cdn.jsdelivr.net/reveal.js/3.0.0/")) (org-publish-org-to 'reveal filename ".html" plist pub-dir))) (setq org-publish-project-alist `(("posts" :base-directory ,blog-org-files-dir :base-extension "org" :recursive t :publishing-function org-html-publish-to-html :publishing-directory ,blog-publish-base-dir :exclude ,(regexp-opt '("README.org" "draft")) :auto-sitemap t :sitemap-filename "index.org" :sitemap-title "Blog Index" :sitemap-format-entry me/org-sitemap-format-entry :sitemap-style list :sitemap-sort-files anti-chronologically :html-link-home "/" :html-link-up "../" :html-head-include-scripts t :html-head-include-default-style nil :html-head ,me/website-html-head :html-preamble me/website-html-preamble :html-postamble me/website-html-postamble) ("about" :base-directory ,(concat emacs-config-base-dir "about") :base-extension "org" :exclude ,(regexp-opt '("README.org" "draft")) :index-filename "index.org" :recursive nil :publishing-function org-html-publish-to-html :publishing-directory ,(concat blog-publish-base-dir "about") :html-link-home "/" :html-link-up "/" :html-head-include-scripts t :html-head-include-default-style nil :html-head ,me/website-html-head :html-preamble me/website-html-preamble :html-postamble me/website-html-postamble) ("css" :base-directory ,(concat emacs-config-base-dir "css") :base-extension "css" :publishing-directory ,(concat blog-publish-base-dir "css") :publishing-function org-publish-attachment :recursive t) ("images" :base-directory ,(concat blog-org-files-dir "images") :base-extension ,site-attachments :publishing-directory ,(concat blog-publish-base-dir "images") :publishing-function org-publish-attachment :recursive t) ("common-images" :base-directory ,(concat emacs-config-base-dir "images") :base-extension ,site-attachments :publishing-directory ,(concat blog-publish-base-dir "images") :publishing-function org-publish-attachment :recursive t) ("assets" :base-directory ,(concat blog-org-files-dir "assets") :base-extension ,site-attachments :publishing-directory ,(concat blog-publish-base-dir "assets") :publishing-function org-publish-attachment :recursive t) ("all" :components ("posts" "about" "css" "images" "common-images" "assets"))))) (provide 'init-org-html)
The most important settings are:
base-directory
, controls from where the org-publish export.publishing-directories
, controls to where the org-publish export.
With carefully arrangement, we could export files from multiple places into one destination.- The final “all” entry is the command we will choose when execute
M-x org-publish
. It will run other entry listed in its components one by one.
So, it will export for “post”, “about”, “css”, …, etc.
3.2. Templates
There are two kinds of templates. One is used by org file directly as org options. Another is used during export such that the generated html file will include those templates. So, the later one contains html related settings.
3.2.1. HTML templates
- Define templates
Since this kind of template will not likely to be changed, I put them into
/.emacs.d/html-templates
preamble.html
Zhao Wei
How can man die better than facing fearful odds, for the ashes of his fathers and the temples of his Gods? -- By Horatius.
This file controls header.
postamble.html
This fill controls the footer.
- Use HTML template files
They are used in init-org-html.el, see init-org-html.el
(setq org-html-divs '((preamble "header" "top") (content "main" "content") (postamble "footer" "postamble")) org-html-container-element "section" org-html-metadata-timestamp-format this-date-format org-html-checkbox-type 'html org-html-html5-fancy t org-html-validation-link t org-html-doctype "html5" org-html-htmlize-output-type 'css org-src-fontify-natively t)
3.2.2. CSS templates
Our website will use external css file to improve its looking. I put them all under foler /.emacs.d/css
. Then, refer them accordingly from org-publish settings defined using elisp shown above. For example, In my custom.css
, I define settings related to “table of content” such that it could make the generated “toc” hovering at the top right corner.
/* TOC inspired by http://jashkenas.github.com/coffee-script */ /* https://stackoverflow.com/questions/12232675/how-to-make-table-of-contents-floating */ #table-of-contents { font-size: 10pt; position: fixed; z-index: 1000000; background: rgba(255,255,255,.80); right: 0em; top: 3em; color: #dcdccc; -webkit-box-shadow: 0 0 1em #777777; -moz-box-shadow: 0 0 1em #777777; -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; text-align: right; /* ensure doesn't flow off the screen when expanded */ max-height: 80%; overflow: auto; line-height: 1em; } #table-of-contents h2 { font-size: 10pt; max-width: 8em; font-weight: normal; padding-left: 0.5em; padding-left: 0.5em; padding-top: 0.05em; padding-bottom: 0.05em; } #table-of-contents li { margin: 3px 0; } #table-of-contents ul { padding-left: 2em; } #table-of-contents #text-table-of-contents { display: none; text-align: left; } #table-of-contents:hover #text-table-of-contents { display: block; padding: 0.5em; margin-top: -1.5em; }
Check previous section about using elisp to configure html stylesheet part.
3.2.3. (Optional) Org template
Here, we setup a template called post.org in which it contains the following options:
#+AUTHOR: Zhao Wei #+OPTIONS: toc:t #+HTML_DOCTYPE: xhtml-strict
For a org file, we could use it as other org file options like:
#+title: Sample Post #+date: <2020-02-05 Wed> #+filetags: gitlab git org-mode orgmode emacs template sample #+setupfile: ~/.emacs.d/org-templates/post.org
4. How to use?
- Write org files ready to be published at
/code/capture-org/publish
. - Run
M-x org-publish RET
, chooseall
. - Check
/code/blog
to see html files and other static resources are exported correctly. Run the following command to start a simple python server to render our static blog website
# replace directory with your own blog base folder which stores the exported html files from org files # for python3.7 above python3 -m http.server --directory ~/code/blog/ 3000 # for python3.6, # cd into published content dir python3 -m http.server 3000
If there is no error, visit: http://localhost:3000
- Host our website on GitHub pages
- Following the instructions from GitHub to create an empty repository like
<your-username>.github.io
. - Git initialize our blog and set remote origin to be the repository we just created.
- After push and wait 3~5 mins, you should be able to visite the blog at: https://zwpdbh.github.io/
- Following the instructions from GitHub to create an empty repository like
5. Improvement
Sometimes, running export from inside Emacs is slow and bloc my current task. So, I need to execute an other emacs process to do org-pubish-all
.
First, define our publish.el which utilize the same configuration from my
/.emacs.d/
:
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory)) (defconst *spell-check-support-enabled* nil) ;; Enable with t if you prefer (defconst *is-a-mac* (eq system-type 'darwin)) (require 'init-const) (require 'init-utils) ;; the file provide useful common functions (require 'init-site-lisp) ;; Must come before elpa, as it may provide package.el (require 'init-elpa) ;; Machinery for installing required packages (require 'init-exec-path) ;; Set up $PATH (unless (package-installed-p 'use-package) (package-refresh-contents) (package-install 'use-package)) ;; load org export related configuration (require 'init-org) (require 'init-org-babel) (require 'init-org-html) (require 'org) (require 'ob) (require 'ob-js) (require 'org-eldoc) (require 'org-tempo) (require 'org-table) (if (member "t" command-line-args) (progn (print "force publish all org files") (org-publish-all t) ) (progn (print "only publish modified org files") (org-publish-all)))
Then, execute emacs to load publish.el
# By default, only modified files including new files are pubished emacs --script "~/.emacs.d/publish.el" # We could also force to publish all files emacs --script "~/.emacs.d/publish.el" t
- Last, cd into blog base dir
- python3 -m http.server 3000
- Visit http://localhost:3000
- python3 -m http.server 3000