Adding a Wiki to the Site

Some things on the blog are supposed to have a longer shelf-life. But the nature of a blog is to present things in a timeline.

I employ cross-links from older posts to newer posts to encourage exploration with the introduction of the “linked posts” part at the bottom of each post. And I have a structured overview to help with discovery. Even then I branched out into other topical pages, like the TextKit overview, or the even larger FastSpring/Distribute outside the MAS page. To make sense of the timeline, I introduce what’s basically a ‘garden’ to my ‘stream’. It’s not a new idea, but I find not having these overview pages to hamper my writing. Some things need systematic overviews, and I enjoy making these, but there’s no good place for them.

After Denis Defreyne, maker of the static site generator nanoc I use, mentioned in passing that he’s experimenting with wiki links in his own project to adopt the Zettelkasten style, I wanted to look into this again after failed attempts in the past.

To make this addition work, [[wiki links]] are very helpful. A regex applied to the text doesn’t cut it, though, otherwise the verbatim <code> tag just now would be treated as a link, too. Extending the Markdown parser for inline elements is way better. Denis pointed out that the kramdown Ruby Markdown library supports syntax extensions. I was able to whip up a first attempt to inject wiki link detection as inline elements and maintain compatibility with pipes used for table block elements. I published this as a Gist, the code is:

require 'kramdown/parser/kramdown'
require 'kramdown-parser-gfm'

# Based on the API doc comment: https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown.rb
class Kramdown::Parser::GFMWikiLink < Kramdown::Parser::GFM
  def initialize(source, options)
    super

    # Override existing Table parser to use our own start Regex which adds a check for wikilinks
    @@parsers.delete(:table) #Data(:table, TABLE_START, nil, "parse_table")
    self.class.define_parser(:table, TABLE_START)

    @span_parsers.unshift(:wikilinks)
  end

  # Override Kramdown table pipe check so we can write `[[pagename|Anchor Text]]`.
  # https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown/table.rb
  # Regex test suite: https://regexr.com/5rb9q
  TABLE_PIPE_CHECK = /^(?:\|(?!\[\[)|[^\[]*?(?!\[\[)[^\[]*?\||.*?(?:\[\[[^\]]+\]\]).*?\|)/.freeze  # Fail for wikilinks in same line
  TABLE_LINE = /#{TABLE_PIPE_CHECK}.*?\n/.freeze  # Unchanged
  TABLE_START = /^#{OPT_SPACE}(?=\S)#{TABLE_LINE}/.freeze  # Unchanged

  WIKILINKS_MATCH = /\[\[(.*?)\]\]/.freeze
  define_parser(:wikilinks, WIKILINKS_MATCH, '\[\[')

  def parse_wikilinks
    line_number = @src.current_line_number

    # Advance parser position
    @src.pos += @src.matched_size

    wikilink = Wikilink.parse(@src[1])
    el = Element.new(:a, nil, {'href' => wikilink.url, 'title' => wikilink.title}, location: line_number)
    add_text(wikilink.title, el)
    @tree.children << el

    el
  end

  # [[page_name|Optional title]]
  # For a converter that uses the available pages, see: <https://github.com/metala/jekyll-wikilinks-plugin/blob/master/wikilinks.rb>
  class Wikilink
    def self.parse(text)
      name, title = text.split('|', 2)
      title = name if title.nil?
      self.new(name, title)
    end

    attr_accessor :name, :title
    attr_reader :match

    def initialize(name, title)
      @name = name.strip.gsub(/ +/, '-')
      @title = title
    end

    def title
      @title || @name
    end

    def url
      "/wiki/#{@name.downcase}"
    end
  end
end

The original regex to see if a line of text denotes a table needed to be replaced, though. I patched this in by overriding the table detection, but keeping the table handling as-is.

All the lookaheads and lookbehinds in the TABLE_PIPE_CHECK make compilation of my page even slower, so I limit the GFMWikiParser to the /wiki/**/* route in nanoc’s compilation rules.

It’s functional already, but the result doesn’t have very many things to look at just yet. I’ll add the new wiki to the navigation once I ported old wiki stuff I had abandoned over.