Jekyll with org-mode and twitter bootstrap

1 Introduction

This document describes how to integrate your articles (in Emacs org-mode format) into jekyll based HTML site with twitter bootstrap scaffolding style.

Note that this article is heavily influenced by Using org to Blog with Jekyll.

2 Document structure

.
├── _plugins          # custom Jekyll/Liquid plugins
├── org               # Your org files should be placed here
├── src               # Jekyll source directory
│   ├── _layouts
│   └── articles      # org generated html files are installed here
└── www               # Jekyll destination directory
    └── articles      # final destination of your .org based html files

3 Usage

All your .org files should be located in org/ directory.

Since, jekyll requires that all source files that Your .org file should have following header.

#+BEGIN_HTML
---
layout: org
title: Jekyll with org-mode and twitter bootstrap
---

⁠#+END_HTML

3.1 Tested Platform

3.1.1 Gentoo Linux

  • GNU Emacs 24.2.1 (Gentoo package: app-editors/emacs-23)
  • org-mode version 7.9.3d (package.el: org 20130114) or,

3.1.2 Mac (Darwin)

  • GNU Emacs 24.2 (from here)
  • org-mode version 7.9.1 (package.el: org 201209003)
  • Gentoo Linux: GNU Emacs 23.2.1, org-mode version 7.9.3d
  • MacOSX (darwin) 10.8.2:

3.2 Configuration

You MUST change the value of some keys in _config.yml, especially for bootstrap, prettify, and jquery.

bootstrap: "http://some.place.com/bootstrap"
prettify: "http://some.place.com/bootstrap"
jquery: "http://some.place.com/bootstrap/js/jquery.js"

You may need to modify both src/_layouts/default.html and src/_layouts/org.html to reflect the above configuration. For example, default.html contains a HTML tags such as:

<link href="http://some.place.com/bootstrap/css/bootstrap.css" rel="stylesheet"/>
<link href="http://some.place.com/bootstrap/css/bootstrap-responsive.css"
      rel="stylesheet"/>
<link href="http://some.place.com/bootstrap/css/docs.css" rel="stylesheet"/>
<link href="http://some.place.com/prettify.css" rel="stylesheet"/>
...
<script src="http://some.place.com/bootstrap/js/jquery.js"></script>

To use make(1) to build your html generation, the jekyll should not create HTTP server; make sure that you have "auto=false".

To use org-specific Jekyll plugins, make sure that you have "safe=false".

3.3 Page format

To use custom plug-ins provided in this package, you may need to provide several custom variable in the YAML front matter of each page.

To work with jekyll-org, it is recommended you provide following variables:

  • id: id of the page, this will be used as the internal identification of the page content. Note that if there are multiple pages written in several languages and their contents are the same, the id of these pages should be the same. (mandatory)
  • label: short name of the page, which may be used as the text of some hyperlinks. If omitted, the page id will be used for label value. (optional)
  • title: title(full name) of the page, which will be used as the text of some hyperlinks. (mandatory)
  • lead: descriptive sentence to describe the purpose of the page. (optional)
  • lang: mnemonic of the page locale. You can omit this variable, if the page is in the your primary language. In this case, jekyll-org will treat the page locale to 'en' (English).

So, the full example of the YAML front matter will look like:

---
layout: default
title: Jekyll with Org-mode
label: Home
id: home
lead: static site generated by Jekyll, with org-mode articles
---

3.4 Additional Plug-ins

3.4.1 'toplink' block

To help generating top-level page index, jekyll-org provides toplink block. toplink block will uses the list of page IDs, evaulating the contents in the block multiple times according to the number of elements in topnav-list of the _config.yml as in the following:

topnav-list: [ "home", "article", "pg1", "pg2" ]

toplink tag accepts five parameters separated by comma('=,=') character. For example:

{% toplink id, label, locale, url, self %}
  ...
{% endtoplink %}

Each parameter name will turn to the variable name which can be used in the toplink block. The meaning of the variables are:

  • 1st parameter (id): id of the page; this is the analogous to the each element in topnav-list.
  • 2nd parameter (label): label of the page; which can be used as the short name of the page.
  • 3rd parameter (locale): mnemonic of the page locale such as 'en' for English, 'ko' for Korean. If the locale of the evaluating page is the same as the current page, this will be an empty string (=""=).
  • 4th parameter (url): URL of the page
  • 5th parameter (self): true if the current page is the same as the evaluating page.

For example, if you want to create hyperlinks to the top-level pages, you can do this by:

<ul>
{% toplink id, label, locale, url, self %}
  <li><a href="{{url}}">{{label}}</a></li>
{% endtoplink %}
</ul>

If you want to remove the hyperlink for the current page, among evaluating pages, you can check the 5th parameter as in the following:

<ul>
{% toplink id, label, locale, url, self %}
  {% if self == true %}
  <li>{{label}}</li>
  {% else %}
  <li><a href="{{url}}">{{label}}</a></li>
  {% endif %}
{% endtoplink %}
</ul>

3.4.2 'articles' block

articles block can enumerate pages which has a specified layouts.

articles block will be evaluated multiple times for each article. The usage is almost the same as toplink block.

articles will accept four parameters, and each meaning of the parameter is:

  • 1st parameter (layouts): a name of the variable in _config.yml where its value is a list of layout names.
  • 2nd parameter (id): id of the article page
  • 3rd parameter (title): title of the article page.
  • 4th parameter (lead): short description of the article page.
  • 5th parameter (url): URL of the article page
  • 6th parameter (lang): name of the locale of the article page. (such as "English" or "Korean") If the locale of the current page is the same as the evaluating article page, this will be an empty string (=""=).

For example, suppose that you have pages in either "myarticle1" layout or "myarticle2" layout. To enumerate these pages, you need to specify the list of layouts in _config.yml like:

article_layouts: [ "my_article1", "my_article2" ]

Then, you can enumerate these pages with article block like:

#+BEGINSRC html <ul> {% articles articlelayouts, id, title, lead, url, lang %} {% if lang = "" %} <li><a href"{{url}}">{{title}}</a></li> {% else %} <li><a href="{{url}}">{{title}}</a> ({{lang}}) </li> {% endif %} {% endarticles %} </ul> #+ENDSRC html

Or, if you want to insert 'lead' value, use the following:

#+BEGINSRC html <ul> {% articles articlelayouts, id, title, lead, url, lang %} {% if lead = nil or lead = "" %} {% assign leadsep = '' %} {% else %} {% assign leadsep = ': ' %} {% endif %}

{% if lang = "" %} <li><a href"{{url}}">{{title}}</a>{{leadsep}}{{lead}}</li> {% else %} <li><a href="{{url}}">{{title}}</a> ({{lang}}){{leadsep}}{{lead}}</li> {% endif %} {% endarticles %} </ul> #+ENDSRC html

3.5 Troubleshootings

3.5.1 I got additional list bullets on Table of Contents!

You may have some problem on the generated table of contents, which looks like following:

  • 1 Introduction
  • 2 Document structure
  • 3 Usage
      • 3.1 Troubleshootings
  • 4 Implementation
      • 4.1 Table of Content(TOC) Generation

The problem is that, your Emacs scripts set org-odd-levels-only to t, but the batch script, publish.el in the package, does not aware of it. To work around this, add "odd" in the STARTUP keyword.

#+STARTUP: odd

3.5.2 I got an error, Symbol's function definition is void: org-version

Your Emacs uses out-dated org-version probably 6.x. Install recent org-mode.

4 Implementation

4.1 Table of Content(TOC) Generation

At first, to convert org files into twitter's scaffolding style, I need a way to generate first-level headings into scaffolding way.

However, to parse org file directly to gereate scaffolding TOC is somewhat difficult to implement; I briefly overlooked org-html.el in org-mode, which is fairly complicated.

Since this is just my hobby to publish my own site, I decieded to make a Jekyll/Liquid plugin, that parses the HTML (which org-mode exported) to get the first-level headings, and use Liquid markup to generate scaffolding TOC.

Thankfully, org-mode HTML output is fairly simple. Here are an example of HTML that org-mode generated:

<div id="table-of-contents">
<h2>Table of Contents</h2>
<div id="text-table-of-contents">
<ul>
<li><a href="#sec-1">1 Introduction</a>
<ul>
<li><a href="#sec-1-1">1.1 Conventions in this article</a></li>
<li><a href="#id-core">1.2 ID management</a></li>
</ul>
</li>
<li><a href="#sec-2">2 Service Components Layout</a>
<ul>
<li><a href="#sec-2-1">2.1 Permanent Cluster</a></li>
<li><a href="#sec-2-2">2.2 Caching Cluster</a></li>
</ul>
</ul>
</div>
</div>

As you can see, all hyperlink IDs are in the form of sec-N-M, so it can be easily extracted with the following regular expression:

/^ *<li> *<a +href *= *"(#sec-[0-9]+)" *>(.*?)<\/a>/

So, I made a Jekyll plugin, which parses the org-generated HTML file, and put the metadata of the each top-level headings in the Jekyll context, so that the Jekyll layout pages can take benefits from the context:

module Jekyll
  class OrgToc < Liquid::Block
    ...
    def render(context)
      contents = context['page']['content']

      thetoc = []

      contents.each_line { |line|
        m = /^ *<li> *<a +href *= *"(#sec-[0-9]+)" *>(.*?)<\/a>/.match line
        if m
          thetoc.push( { "id" => m[1], "title" => m[2] })
        end

        break if / *<div +id *= *"outline-container-[0-9]+\"/.match line
      }

      context['page']['orgtoc'] = thetoc

      super
    end
    ...
  end
end

Liquid::Template.register_tag('orgtoc', Jekyll::OrgToc)

Then, the Jekyll layout for org HTML files will uses page.orgtoc to generate scaffolding TOC:

<div class="span3 bs-doc-sidebar">
  <ul class="nav nav-list bs-docs-sidenav">
  {% orgtoc %}
    {% for item in page.orgtoc %}
      <li><a href="{{item.id}}"><i class="icon-chevron-right"></i>
      {{ item.title }}</a></li>
    {% endfor %}
  {% endorgtoc %}
  </ul>
</div>

4.1.1 Known Issues

However, there are some problems with this approach.

In scaffolding page, one of the left-side navigation item is highlighted, depending on the current location of the contents.

I cannot make this happen on org-generated page, since the contents generation is handled by org-mode, which I cannot control manually.

In detail, the scaffolding has following structures:

<!-- This is the left-side navigation list -->
<div class="span3 bs-docs-sidebar">
  <ul class="nav nav-list bs-docs-sidenav">
    <li><a href="#sec-1"><i class="icon-chevron-right"></i>Section 1</a></li>
    <li><a href="#sec-2"><i class="icon-chevron-right"></i>Section 2</a></li>
    <li><a href="#sec-3"><i class="icon-chevron-right"></i>Section 3</a></li>
    ...
  </ul>
</div>

<!-- This is the right-side, contents -->
<div class="span9">
  <section id="sec-1">
    <div class="page-header">
      <h1>Section 1</h1>
    </div>
    <p>...</p>
  </section>

  <section id="sec-2">
    <div class="page-header">
      <h1>Section 2</h1>
    </div>
    <p>...</p>
  </section>

  ...
</div>

Following is the structure that I implemented:

<!-- This is the left-side navigation list -->
<div class="span3 bs-docs-sidebar">
  <ul class="nav nav-list bs-docs-sidenav">
    <li><a href="#sec-1"><i class="icon-chevron-right"></i>Section 1</a></li>
    <li><a href="#sec-2"><i class="icon-chevron-right"></i>Section 2</a></li>
    <li><a href="#sec-3"><i class="icon-chevron-right"></i>Section 3</a></li>
    ...
  </ul>
</div>

<!-- This is the right-side, contents -->
<div class="span9">
  <!-- from now on, this is org-mode generated contents -->
  <div id="outline-container-1" class="outline-2">
    <h2 id="sec-1">Section 1</h2>
    ...
  </div>

  <div id="outline-container-2" class="outline-2">
    <h2 id="sec-1">Section 2</h2>
    ...
  </div>
  ...
</div>
  1. Since the body content itself is generated by org-mode, I cannot make the highlighting feature of the navigation bar (in the left side of the page).

jekyll-org


comments powered by Disqus