I use org-mode for a heck of a lot of things nowadays: lecture notes, campaign notes for roleplaying games, and design documents for my projects. I often like to export these as HTML since everyone can fairly effortlessly view web pages on any device.

For large projects, it makes sense to break up your document into sub-documents. For example, in campaign notes, I might have an org-mode document per non-player character or location. For lecture notes, I’m designing courses to be extremely modular, so each component is split out into its own page. This strategy keeps things separate and allows you to easily make customizations per page. But I’d want to ensure that every page maintains some basic settings so I don’t need to maintain them across all the files in my project.

How do you do this?

Set up your project

In my .emacs file I have a section labelled “Projects”. I have the following elisp code:

(require 'org-publish)

(setq org-publish-project-alist
    (list
        '("limen"
            :base-directory "~/Projects/Limen/"
            :base-extension "org"
            :publishing-directory "~/Projects/Limen/html/"
            :publishing-function org-html-publish-to-html
            :exclude "settings.org"
            :recursive t
            :auto-sitemap t
            :makeindex t
        )
        '("thedayafter"
            :base-directory "~/Projects/theDayAfter/doc/"
            :base-extension "org"
            :publishing-directory "~/Projects/theDayAfter/doc/html/"
            :publishing-function org-html-publish-to-html
            :exclude "settings.org"
            :recursive t
            :auto-sitemap t
            :makeindex t
        )
    )
)

Here I have two projects: Limen’s House and The Day After. They all exist in their own projects directory, and publish to a html/ directory underneath that. All my files are org files, hence the :base-extension.

This sets my different projects so I can easily flick between them and export them as needed. I have F5 bound to org-publish-current-project so I can be working on a file in the project and just hit F5 to “compile” the entire project. All I need to do is be in the right directory.

You can have more elaborate setups but I’ll keep it like this.

Settings file

That’s all fine, but it doesn’t help you keep a consistent look-and-feel across all your webpages.

The way we achieve this is to first have a consistent, but minimal header for each page.

#+TITLE: Document title
#+DATE: <2015-03-23>

#+SETUPFILE: settings.org

That’s all! If you tend to have different authors per page, add the #+AUTHOR option per-page. Omit the date if you don’t care about it. Wrap the whole thing up into a yasnippet or skeleton so you can quickly add them to each new page.

The important part is the last line. Here we have a settings file that is included as a setup-file.

Why #+SETUPFILE: rather than #+INCLUDE? The former takes export settings from settings.org, whereas the latter brings in settings.org verbatim.

This is useful because you can add structure and comments to the settings file without that being brought into the main files.

Here’s an example settings.org file:

* Author details

#+AUTHOR: Brett Witty
#+EMAIL: [email protected]

* General export options

#+LANGUAGE: en

This means we can tag a heading with noexport and that won't be included in the publishing process.

:::text
#+EXCLUDE_TAGS: :noexport:

* Macros

Makes a macro that allows us to say {{{keyword(term)}}}
#+MACRO: keyword [[theindex.org][$1]]

* Style

Headlines are 5 levels deep
#+OPTIONS: H:5

Enable smart quotes
#+OPTIONS: ':t

Get rid of org-style stuff from our publishing process

#+OPTIONS: c:nil todo:nil d:nil inline:nil p:nil pri:nil stat:nil tags:nil tasks:nil

Default HTML stylesheet
#+HTML_HEAD: <link rel="stylesheet" type="text/css" href="stylesheet.css" />

Notice in our projects definition we :exclude this file? That’s so you can keep it in org-mode but not have it get turned into HTML along the way.

Note that you can include HTML HEAD in the publishing settings, but it’s nicer to have it in an org-mode file that you can just edit easily and have the changes picked up automatically.

One last point on this file: If you have a directory tree and want to use the same settings.org file, use a relative reference to it like #+SETUPFILE: ../settings.org. This is probably easier to work with than, say, soft file links in Unix.

Extra flair

You can have the CSS stylesheet exported as part of the publishing process. Set up a similar project in org-publish-project-alist:

(setq org-publish-project-alist
    '(("project-pages"
        :base-directory "~/Projects/blah/"
        :base-extension "org"
        :publishing-directory "~/Projects/blah/html/"
        :publishing-function org-html-publish-to-html
        :exclude "settings.org")

    ("project-css"
        :base-directory "~/Projects/blah/"
        :base-extension "css"
        :publishing-directory "~/Projects/blah/html/"
        :publishing-function org-publish-attachment)

    ("project" :components ("project-pages" "project-css"))))

When you hit F5 (org-publish-current-project) in the project directory, it’ll compile the org-mode files into html and export the current CSS file into the directory. This is achieved by that last line where you read this as a new project, but it has components equal to the other sub-projects.

You can also use this for multi-component projects coming from different users but combining in the one space.

Not only that, but this is how you can extract the source code listings from your org-mode files and turn them into individual files:

(setq org-publish-project-alist
    '(("project-pages"
        :base-directory "~/Projects/codeproj/"
        :base-extension "org"
        :publishing-directory "~/Projects/codeproj/html/"
        :publishing-function org-html-publish-to-html
        :exclude "settings.org")
    ("project-code"
        :base-directory "~/Projects/codeproj/"
        :base-extension "org"
        :exclude "settings.org"
        :publishing-directory "~/Projects/codeproj/source/"
        :publishing-function org-babel-tangle-publish)
    ("project-css"
        :base-directory "~/Projects/codeproj/"
        :base-extension "css"
        :publishing-directory "~/Projects/codeproj/html/"
        :publishing-function org-publish-attachment)

    ("project" :components ("project-pages" "project-code" "project-css"))))

Individual source code listings would look like:

#+begin_src C :file hello.c
int main(int argc, char *argv[]) {
    printf("Hello world!\n");
    return 0;
}
#+end_src

In the final published package, this will be wrapped up into hello.c and put in the correct directory.

In your settings.org file you can set global export properties like:

#+PROPERTY: header-args :export code :mkdirp yes

which sets each block of source code to export, and makes directories if they don’t exist. If you need to override this for a block of code, just add the header arguments to that source block.