Reza Housseini

Publish a website with Emacs

A novice guide to org-mode

Basic principle

Customizing and publishing a static website with Emacs and org-mode is quite easy. Like with almost every static website generator you have rules to convert a specific set of files from one format into another. The two most important converters for website publishing in emacs are

  • org-html-publish-to-html converts org files to html files
  • org-publish-attachment copies files from input to output location (identity function, no conversion happens)

As depicted in the following images, org-publish-attachment is used for static content like stylesheets and images, while org-html-publish-to-html is used to convert .org files to .html files.

transformation-html.svg
transformation-css.svg
transformation-png.svg

To specify the set of files on which a converter acts, one specifies an element in the org-publish-project-alist list:

(setq org-publish-project-alist
      '(("orgfiles"
         :base-directory "."
         :base-extension "org"
         :publishing-directory "./build/html"
         :recursive t
         :publishing-function org-html-publish-to-html)
        ("images"
         :base-directory "img"
         :base-extension "png"
         :publishing-directory "./build/html/img"
         :publishing-function org-publish-attachment)
        ("website"
         :components ("orgfiles" "images"))))

In this listing a converter (a project in org-mode) named orgfiles is defined for recursively converting all .org files into the folder ./public and a converter named images which copies all files with the extension .png in the folder ./img into the folder ./public/img. It is this simple!

This configuration can be put into a file publish-html.sh and called from a Makefile as

./publish-html.sh

this involves some emacs script trickery which is explained here in all it’s detail.

Using GNU Guix

By employing GNU Guix to build the website we can solve several issues:

  • document (and collect) all necessary build tools
  • reproduce the build in the future, on another machine or architecture
  • build in a containerized environment

To achieve this, a manifest file and a channels file is sufficient. Our manifest file manifest.scm contains the following

(specifications->manifest
 '("emacs"
   "font-linuxlibertine"
   "font-fira-code"))

which is Emacs for compiling the source and a regular and a monospace font for serving in the website.

The channels file channels.scm is even easier, it serves pinning the software defined in the manifest file and can be updated with

guix describe --format=channels > channels.scm

Now for building the website, a containerized environment can be started with guix shell (inside the directory where manifest.scm resides) and subsequently the build command in listing 1 can be called.

Customizing the appearance

Naturally you want to tweak the appearance of your website to your desired style. There are several ways to achieve this

  1. use the customization options for publishing
  2. derive a new template from an existing one

For most use cases 1 is sufficient, but if you want more control over the HTML elements you have to use 2. We want go into detail for 2 (look up org-export-define-derived-backend) and will focus on 1 instead.

Stylesheet

To add a custom stylesheet there are the following options

org-html-head-extra
append to the head element
org-html-head-include-default-style
include default style
org-html-head
set the head element

and in action they look like

(defvar html-head-extra
  (concat
   (sxml-to-xml `(link (@ (href "/css/reset.css")
                          (rel "stylesheet"))))
   (sxml-to-xml `(link (@ (href "/css/fonts.css")
                          (rel "stylesheet"))))
   (sxml-to-xml `(link (@ (href "/css/site.css")
                          (rel "stylesheet"))))
   (sxml-to-xml `(link (@ (href "/css/code.css")
                          (rel "stylesheet"))))))

(setq org-html-include-default-style nil
      org-html-head-extra html-head-extra)

Layout

There are several options to adapt the HTML layout. According to the manual you can

org-html-container-element
change the element tag inside the content container
org-html-divs
set the element tag for pre-, postamble and content
org-html-doctype
change the doctype format
org-html-html5-fancy
use HTML5 tag elements in certain places
org-html-postamble-format
set the content of the postamble element
org-html-preamble-format
set the content of the preamble element
org-html-viewport
change the viewport meta tag in head

to add a custom site header set the preamble to

(defvar html-preamble-format
  `(("en" ,(concat
          (sxml-to-xml `(h1 (a (@ (href "/index.html")) "Reza Housseini")))
          (sxml-to-xml `(nav
                         (ul (li (a (@ (href "/about.html")) "About"))
                             (li (a (@ (href "/index.html")) "Blog"))
                             (li (a (@ (href "/projects.html")) "Projects")))))))))

(setq org-html-preamble t
      org-html-preamble-format html-preamble-format)

and for a custom footer, set the postamble to

(defvar html-postamble-format
`(("en" ,(concat
          (sxml-to-xml `(p (@ (class "copyright")) "© 2022 %a"
                           (a (@ (class "cc-button")
                                 (href "https://creativecommons.org/licenses/by-sa/4.0/"))
                              (img (@ (src"/img/button-creative-commons-by-sa-4.0-80x15.png"))))))
          (sxml-to-xml `(p "The text and images on this site are free culture works "
                           "available under the "
                           (a (@ (href "https://creativecommons.org/licenses/by-sa/4.0/"))
                              "Creative Commons Attribution Share-Alike 4.0 International")
                           " license"))
          (sxml-to-xml `(p "Made with %c, source code is available on "
                           (a (@ (href "https://git.sr.ht/~rhou/blog")) "sourcehut")
                           " and was inspired by "
                           (a (@ (href "https://dthompson.us")) "David Thompson's")
                           " website."))
          (sxml-to-xml `(p "Created at %d, last modified at %C."))))))

(setq org-html-postamble t
      org-html-postamble-format html-postamble-format)

The result can be inspected on this site.

Adding a RSS feed

A feed is a XML document with a list-like structure which in case of a blog has an entry for each blog post. The content of this entry can be choosen arbitrarely but in most cases it contains part or the whole blog post.

ox-rss.el

By using ox-rss.el we can stick to the same procedure as described in the first section:

("rss"
         :base-directory "./posts"
         :base-extension "org"
         :html-link-home "https://reza.housseini.me/"
         :exclude ".*"            ;; To exclude all files...
         :include ("sitemap.org") ;; ... except sitemap.org.
         :html-link-use-abs-url t
         :rss-extension "xml"
         :publishing-directory "./build/html"
         :publishing-function (org-rss-publish-to-rss)
         :section-numbers nil
         :table-of-contents nil)

we just add an entry to our org-publish-project-alist variable how to generate our RSS feed.

Acknowledge