Blogging with Orgmode
Table of Contents
After several attempts of trying to get Orgmode working for my blog, I
think I’ve finally settled on a workable solution using
and a free static hosting service that works with Dropbox.
I tried to keep the scope limited while moving to Orgmode, to keep things simple.
- Use Orgmode, duh!
- Publish the entire site as a single Orgmode project
- Use the auto-generated sitemap as the index (don’t want to manually add links to new blog posts)
- Free static hosting with custom domain
Setting up the project
Here’s my website’s Orgmode project defintion defined in my Emacs config1:
1: (setq org-publish-project-alist 2: `(("website" 3: :base-directory "~/Projects/blog/" 4: :publishing-directory "~/Dropbox/Apps/updog/john2x/" 5: :recursive t 6: :exclude "level-.*\\|.*\.draft\.org" 7: :publishing-function org-html-publish-to-html 8: :auto-sitemap t 9: :sitemap-filename "index.org" 10: :sitemap-title "John Louis Del Rosario" 11: :sitemap-sort-files "chronologically" 12: :sitemap-function my-website-sitemap-function 13: :html-link-up "/" 14: :html-link-home "/" 15: :html-preamble "<p class=\"date\">Published: %d</p>" 16: :html-postamble my-website-html-postamble)))
Let’s go over the interesting properties:
:publishing-directory: Put our published files in the Dropbox folder so they are publicly accessible.
:recursive: Recursively export all org files within the
:exclude: Exclude some files from being exported and listed in the sitemap during publishing (e.g. drafts prefixed with
.draft.orgwill not be published).
:auto-sitemap: Turn on automatic sitemap creation/updating when re-publishing the project.
:sitemap-filename: Name the generated sitemap as
index.orgso it gets published as
:sitemap-sort-files: Sort sitemap links based on the files’ dates, useful for the blog posts.
:sitemap-function: Use a custom function to generate the sitemap. We’ll take a look at this shortly.
:html-preamble: Use custom preamble. Although the docs say we could specify the preamble as options (e.g.
#+OPTIONS: html-preamble:"foo"), which would have been ideal, it doesn’t seem to work with Orgmode 8.3.4. So we hardcode it at the project level.
:html-postamble: Use custom function for the postamble. See “Updates” below.
I wanted my index page to have all the links needed automatically added, which the sitemap function does nicely. But since the sitemap file is re-generated on every publish, I have to insert/remove any content from it via a custom sitemap generator function.
(defun my-website-sitemap-function (project &optional sitemap-filename) "Custom sitemap generator that inserts additional options." (let ((buffer (org-publish-org-sitemap project sitemap-filename))) (with-current-buffer buffer (insert "\n#+OPTIONS: html-preamble:nil") (insert "\n#+SUBTITLE: a.k.a. john2x") (insert "\n\n#+BEGIN_EXAMPLE") (insert "\nCopyright (c) 2016 John Louis Del Rosario") (insert "\n#+END_EXAMPLE") (save-buffer))))
It basically takes the output from the default sitemap function and adds/removes custom content.
Orgmode allows documents to reference a
SETUPFILE, which is basically an “included” template.
This is useful for defining common header options across documents. For example:
#+SETUPFILE: path/to/setup_file.org ...
For this site, I have three setup files defined:
- A base template,
- A template specific for blog posts,
- A template specific for one-off pages (e.g. the about page),
level-0 prefix was taken from Sebastian Rose’s publishing
tutorial2. It also makes excluding the templates easier.
The blog post template includes the base template, and it enables the rendering of the table of contents.
The page template includes the base template, and disables the preamble.
The previous version of this site was using Jekyll on Github Pages. I was hoping I could continue using Github Pages, but unfortunately there was no way to disable Jekyll processing. So my exported documents all came out incorrectly.
Bitbucket static file hosting
Next I looked into Bitbucket’s undocumented feature to serve static content (similar to Github Pages, but without Jekyll). It works great, but they don’t allow custom domains.
What’s up dog?
I finally stumbled on updog.co, a free service that serves files directly from your Dropbox folder. It also allows custom domains for each site. Since I already have a Dropbox account, I didn’t have to shell out any cash. And if/when the service goes down, the owner is cool enough to provide the source3, so if I can’t find an alternative, I’d just bite the bullet and probably run my own instance of the service.
So now that I have everything set up, writing a new post is a great experience.
I get to write using Org, which makes writing more enjoyable. For drafts that
aren’t ready for publishing yet (but which I’d like to view a render of), I just
need to name them
*.draft.org and they won’t be listed in the sitemap.
Once everything is ready, a simple
C-c C-e P p publishes everything
and Dropbox + updog.co does the rest!
There’s still some rough edges I’d like to iron out.
- The index/sitemap is very bare bones. I’ll probably write an improved sitemap function in the future.
- I’m still trying to figure out how to generate an RSS feed.
- I’d like to render the
KEYWORDSoption in a post (since Google ignores the
- My favicon doesn’t seem to be loaded. I wanted to use the Orgmode unicorn.
- The style is pretty much non-existent at the moment. Mostly due to laziness, but I kind of like the aesthetic of it (especially if using the Computer Modern font4).
Rendering keywords and improved postamble
I’ve figured out how to render
KEYWORDS in the postamble.
allows specifying a function for the
html-postamble value. The
function takes a single argument, a property list of the current
document’s options. So I just needed to build up a string taking
the values I needed from the property list. Here’s what it looks like:
(defun my-website-html-postamble (options) (concat "<hr>" (if (and (plist-get options ':keywords) (not (string= (plist-get options ':keywords) ""))) (format "<p>Keywords: %s</p>" (plist-get options ':keywords)) "") (format "<p class=\"date\">Modified: %s</p>" (format-time-string "%Y-%m-%d %H:%M:%S")) (format "<p>Copyright (c) %s %s</p>" (car (split-string (car (plist-get options ':date)) "-")) ;; TODO: get from custom document option (car (plist-get options ':author))) (format "<p>%s</p>" (plist-get options ':creator))))
Jesse (maintainer of updog.co) has enabled custom favicon support for updog. Thanks Jesse!