Blogging In Org Mode

Background

I’ve been telling myself to setup blogging in org mode for years, but I was worried about getting it to work with my existing org publish pipeline, but it turned out to actually be quite easy. Most of this has been shamelessly stolen from this post.

What it looks like

My directory structure looks something like this:

.
|-- archive.org
|-- build.sh
|-- hackathons.org
|-- index.org
|-- org.css
|-- posts
|   |-- blogging-in-org-mode.org
|   `-- test.org
|-- public
|   |-- archive.html
|   |-- hackathons.html
|   |-- index.html
|   |-- posts
|   |   |-- blogging-in-org-mode.html
|   |   |-- test.html
|   |   `-- test2.html
|   |-- resume.html
|   |-- settings.html
|   `-- statics
|       `-- cv-no-phone.pdf
|-- publish.el
|-- resume.org
|-- settings.org
`-- statics
    `-- cv-no-phone.pdf

The posts directory contains files that I want org-publish to consider blog posts.

How It Works

Styles

I use the awesome OrgCSS stylesheet to style this site.

Automated Publishing

I use a simple bash script to automate publishing and pushing to my webserver. I am aware that org-publish can automatically push to a web server, but I chose to rsync for convince.

#!/bin/bash
emacs --batch --eval "(progn (package-initialize) (package-refresh-contents) (package-install 'org))"
emacs --batch --no-init-file --load publish.el --funcall org-publish-all
rsync -avh --stats public/ nmccarty@10.0.5.4:/var/www/html/

My publish.el

First, require some packages and configure some org mode settings

(require 'package)
(package-initialize)
(add-to-list 'package-archives '("org" . "https://orgmode.org/elpa/") t)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(package-refresh-contents)
(package-install 'org-plus-contrib)
(package-install 'htmlize)

(require 'org)
(require 'htmlize)
(require 'ox-publish)
(require 'ox-html)
(require 'org-element)
(require 'ox-rss)
(require 's)
(setq org-html-htmlize-output-type 'css)

;; setting to nil, avoids "Author: x" at the bottom
(setq user-full-name "Nathan McCarty")

(setq org-export-with-section-numbers nil
      org-export-with-smart-quotes t
      org-export-with-toc nil)

(setq org-html-divs '((preamble "header" "top")
                      (content "main" "content")
                      (postamble "footer" "postamble"))
      org-html-container-element "section"
      org-html-metadata-timestamp-format "%Y-%m-%d"
      org-html-checkbox-type 'html
      org-html-html5-fancy t
      org-html-validation-link nil
      org-html-doctype "html5")

Next set a (admittedly ugly) header for all the .org files, and pull in the CSS

(defvar org-blog-head
  "<link rel=\"stylesheet\"  type=\"text/css\" href=\"https://mccarty.io/org.css\"/>")
(defun org-blog-preamble (_plist)
  "Pre-amble for whole blog."
  "
<center>
<div class=\"banner\">
    Nathan's Adventures in Programming
  </div>
</center>
<center>
  <ul class=\"banner-links\">
    <a href=\"/\"> Home </a> </li>
&nbsp&nbsp&nbsp
    <a href=\"/archive.html\"> Posts </a>
  </ul>
</center>
  <hr>")

Setup some functions for generating the sitemap. I chose to do this as a simple org mode list of dates and links, because it makes pulling the recent posts into the index easier later.

(defun org-blog-sitemap-format-entry (entry _style project)
  "Return string for each ENTRY in PROJECT."
  (when (s-starts-with-p "posts/" entry)
    (format " + %s [[file:%s][%s]]"
            (format-time-string "%h %d, %Y"
                                (org-publish-find-date entry project))
            entry
            (org-publish-find-title entry project))))

(defun org-blog-sitemap-function (title list)
  "Return sitemap using TITLE and LIST returned by `org-blog-sitemap-format-entry'."
  (concat
   "#+TITLE: Blog Posts \n"
   (mapconcat (lambda (li)
                (format "%s" (car li)))
              (seq-filter #'car (cdr list))
              "\n")
   "\n"))

Then set the list of attachment file types for the statics

(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.")

Setup the org-publish-project-alist I have it set to only generate the site map for org files in the posts directory, which corresponds to blog posts, and it lives at archive.org/archive.html

(setq org-publish-project-alist
      (list
       (list "site-org"
             :base-directory "."
             :base-extension "org"
             :recursive t
             :publishing-function '(org-html-publish-to-html)
             :publishing-directory "./public"
             :exclude (regexp-opt '("README" "draft"))
             :auto-sitemap t
             :sitemap-filename "archive.org"
             :sitemap-title "Blog Posts"
             :sitemap-style 'list
             :html-head-extra org-blog-head
             :html-preamble #'org-blog-preamble
             :html-infojs-options "home:https://mccarty.io"
             :sitemap-sort-files 'anti-chronologically
             :sitemap-format-entry #'org-blog-sitemap-format-entry
             :sitemap-function #'org-blog-sitemap-function)
       (list "site-static"
             :base-directory "."
             :exclude "public/"
             :base-extension site-attachments
             :publishing-directory "./public"
             :publishing-function 'org-publish-attachment
             :recursive t)
       (list "site" :components '("site-org"))))

(provide 'publish)

Pulling the most recent blog posts into index.org

I wanted to have the 10 or so most recent blog posts in a section in the index file, and the rest in a separate archive.org. This turned out to actually be quite easy to do. Org publish generates a full list of the blog posts in archive.org, and since I used a simple list format, I can just use an include statement in the index file to include only the top so many lines:

* Recent Blog Posts
  #+INCLUDE: "./archive.org" -16