2 minute read

From now on I will be able to write posts in Org Mode for this blog, besides the existing Markdown method. Here’s how I configure it. This article is written in an Org file and also serves as a demonstration of this new feature.

First, you can do everything in the original Markdown way, which is kept intact. Now you can write everything in Org Mode with the extra benefit of using front matter, Liquid syntax, figures, galleries, etc. exactly the same as in Markdown.

Here are the basic elements demonstrated:

Org Elements Examples

Tables

Here is the morphological breakdown:

Component Origin Meaning
Erythro- Greek (erythros) Red
-xyl- Greek (xylon) Wood
-aceae Latin (Suffix) Belonging to the family of

Lists

A multi-level list of mixed types:

  1. First item
  2. Second item
    1. Sub-item A (Indented 3 spaces)

      Some text

      1. Sub-item A (Indented 3 spaces)
      2. Sub-item A (Indented 3 spaces)
        1. Sub-item A (Indented 3 spaces)
        2. Sub-item A (Indented 3 spaces)
      3. Sub-item A (Indented 3 spaces)
    2. Sub-item B
      • Deeply nested bullet (Indented another 3 spaces)
      • Deeply nested bullet (Indented another 3 spaces)
  3. Third item

Images

A simple image:

https://i.postimg.cc/Vv8jFw8D/unsplash-gallery-image-1.jpg

A figure:

This is a figure alt text
This is a figure caption

A gallery (with customization):

This is a gallery caption

Code highlighting

Inline codes: print, hello

Using : to start a line of code:

descriptor + subject + taxonomic rank

Using structure template (code block):

def print_hi(name)
  puts "Hi, #{name}"
end
print_hi('Tom')

#=> prints 'Hi, Tom' to STDOUT.

Configurations

Here’s how I did the configurations.

  1. Add org-ruby to your Gemfile:
    group :jekyll_plugins do
      ...
      gem "org-ruby"
    end
  2. Write a new plugin: _plugins/org_converter.rb
    require 'nokogiri'
    require 'rouge'
    
    Jekyll::Hooks.register [:pages, :documents], :pre_render do |doc|
      # Enable Liquid syntax
      if doc.extname.downcase == '.org'
        # Enable {% raw %}...{% endraw %}
        doc.content = doc.content.gsub(/^[ \t]*(\{%[ \t]*raw[ \t]*%\})[ \t]*\n?/, '\1')
        doc.content = doc.content.gsub(/^[ \t]*(\{%[ \t]*endraw[ \t]*%\})[ \t]*\n?/, '\1')
        # Enable figures, galleries, and links to posts
        doc.content = doc.content.gsub(/^[ \t]*(\{%[ \t]*include[ \t]+(?:figure|gallery)(?:(?!%\}).|\{%.*?%\})*%\})[ \t]*$/m) do |match|
          "\n#+BEGIN_HTML\n#{$1}\n#+END_HTML\n"
        end
    
        block_regex = /(?mi:^[ \t]*#\+(?:begin_src|BEGIN_HTML).*?^[ \t]*#\+(?:end_src|END_HTML))/
        inline_code = /[=~][^=~\n]+[=~]/
        markdown_link = /(?<!\!)\[([^\]]+)\]\(([^)]+)\)/
    
        doc.content = doc.content.gsub(/#{block_regex}|^[ \t]*:[^\n]*$|#{inline_code}|#{markdown_link}/) do |match|
          match.start_with?('[') && !match.start_with?('[[') ? "[[#{$2}][#{$1}]]" : match
        end
      end
    end
    
    module Jekyll
      class OrgConverter < Converter
        safe true
        priority :low
    
        def matches(ext)
          ext =~ /^\.org$/i
        end
    
        def output_ext(ext)
          ".html"
        end
    
        def convert(content)
          require 'org-ruby'
          html = Orgmode::Parser.new(content).to_html
          doc = Nokogiri::HTML.fragment(html)
    
          doc.css('h1, h2, h3, h4, h5, h6').each do |node|
            node['id'] = node.text.downcase.strip.gsub(/[^a-z0-9\s-]/, '').gsub(/\s+/, '-')
          end
    
          # Add <thead> to tables
          doc.css('table').each do |table|
            trs = table.xpath('./tr')
            next if trs.empty?
    
            if trs.first.at_xpath('./th')
              thead = Nokogiri::XML::Node.new('thead', doc)
              thead.add_child(trs.first)
              tbody = Nokogiri::XML::Node.new('tbody', doc)
              trs[1..-1].each { |tr| tbody.add_child(tr) }
              table.add_child(thead)
              table.add_child(tbody)
            else
              tbody = Nokogiri::XML::Node.new('tbody', doc)
              trs.each { |tr| tbody.add_child(tr) }
              table.add_child(tbody)
            end
          end
    
          # Process code blocks to enable code highlighting
          doc.css('pre.src[lang]').each do |pre|
            lang = pre['lang']
            lexer = Rouge::Lexer.find_fancy(lang) || Rouge::Lexers::PlainText
            formatter = Rouge::Formatters::HTML.new
    
            # .sub(/\A[\r\n]+/, '') targets the absolute beginning of the string
            # (\A) and removes any leading line breaks or carriage returns.
            # .sub(/\s+\z/, '') targets the absolute end of the string (\z) and
            # removes any trailing whitespace, including empty lines and spaces.
            code_text = pre.text.sub(/\A[\r\n]+/, '').sub(/\s+\z/, '')
    
            highlighted = formatter.format(lexer.lex(code_text))
            new_node = Nokogiri::HTML.fragment(%Q{
                <div class="language-#{lang} highlighter-rouge">
                  <div class="highlight"><pre class="highlight"><code>#{highlighted}</code></pre></div>
                </div>
              })
            pre.replace(new_node)
          end
    
          doc.to_html
        end
      end
    end
  3. Install the gems with bundler and run:
    bundle install
    bundle exec jekyll serve

Comments