tags in Jekyll
So it took me a while to figure out how to implement tags in Jekyll the way I wanted.
What I wanted was two-fold:
(1) http://mahiwaga.net/tag/*tag* would link to a list of all the posts tagged tag
(2)
http://mahiwaga.net/tag/
would link to a list of all tags and under each tag would be all the posts tagged with that tag
While searching through Google yields a bunch of old plugins and some Liquid kludgery that can approximate this, (1) was most easily achieved by installing jekyll-archives which can automatically generate pages for each tag as well as for each year and for each month.
I then added the following lines to _config.yml:
gems:
- jekyll-archives
jekyll-archives:
enabled:
- year
- month
- tags
layouts:
year: year-archive
month: month-archive
tag: tag-archive
permalinks:
year: '/:year/'
month: '/:year/:month/'
tag: '/tag/:name/'
(This is also enables links to lists of posts by year—http://mahiwaga.net/*yyyy*/ and lists of posts by month—http://mahiwaga.net/*yyyy*/*mm*/)
I then created the file _layouts/tag-archive.html:
---
layout: default
---
<h1>Archive of posts with {{ page.type }} '{{ page.title }}'</h1>
<ul class="posts">
{% for post in page.posts %}
<li>
<span class="post-date">{{ post.date | date: "%b %-d, %Y" }}</span>
<a class="post-link" href="{{ post.url | prepend: site.baseurl }}">{{ post.title }}</a>
</li>
{% endfor %}
</ul>
This layout could probably use a little a lot more work, but it will do for now.
Just as an aside, at least as of jekyll-2.5.3, tags that are just numbers—
tags:
- 2001
- 42
- 1337
—will cause jekyll to crashout with non-obvious error messages.
Also, another thing I learned was that using tags with colons(:)—
tags:
- 2001: A Space Odyssey
- Star Trek: Voyager
—will get misinterpreted as YAML hashes.
Implementing (2) was a bit trickier.
It’s not too difficult to just iterate through all the tags to get to all the posts, but the tags are stored in the order of their occurrence, which seems quite haphazard and not that useful.
I did find a page explaining how to get a sorted list of tags using only Liquid, but ASCII sorting instead of alphabetically sorting still seemed rather messy.
(As an aside, I was highly amused by these rants regarding the fact that programming languages tend to always default to ASCII sorting and it’s rare to find a natively-implemented natural sorting function or subroutine, leading people to recurrently reinvent the wheel:
- Alphabetical != ASCIIbetical • 2007 Dec 11 • weblog.masukomi.org
- Sorting for Humans: Natural Sort Order • 2007 Dec 12 • Coding Horror
I laughed at this particular quote: “…what sane person would want ASCII order?”)
The specific issue is that the sort Liquid filter implemented by Jekyll can only do an ASCII sort. While the master branch of Liquid has partially implemented a natural sort (really, it’s just a case-insensitive sort) I couldn’t figure out how to get Jekyll to use the liquid-4.0.0.alpha gem and it kept pulling in liquid-3.0.3 instead.
So I decided to implement this custom filter (adapted from Tanguy Krotoff):
I created the file plugins/customfilter.rb which implements a Liquid filter named sortcasecmp:
module Jekyll
module CustomFilter
def sort_casecmp(input)
input.sort { |apple, orange| apple.casecmp(orange) }
end
end
end
Liquid::Template.register_filter(Jekyll::CustomFilter)
Then I created tag.md in the top level directory:
---
layout: page
title: tags
permalink: /tag/
---
{% capture unsortedtags %}{% for tag in site.tags %}{{ tag | first }}{% unless forloop.last %}|{% endunless %}{% endfor %}{% endcapture %}
{% assign sortedtags = unsortedtags | split:'|' | sort_casecmp %}
{% for tagindex in (0..site.tags.size) %}{% unless forloop.last %}
{% capture currenttag %}{{ sortedtags[tagindex] }}{% endcapture %}
<a href="/tag/{{ currenttag | downcase | slugify }}/" title="posts tagged {{ currenttag }} ">{{ currenttag }}</a>
<ul>
{% for post in site.tags[currenttag] %}
<li>
<a href="{{ post.url }}">{{ post.title }}</a>
<span class="date">{{ post.date | date: "%B %-d, %Y" }}</span>
</li>
{% endfor %}
</ul>
{% endunless %}{% endfor %}
Voila!