Adding post tagging to jekyll's Minima theme
[jekyll
coding
]
It’s almost Christmas Eve and I’m fiddling with a web page. I moved my content from a Wordpress site to a jekyll installation that can be hosted freely by GitHub. The ‘minima’ theme seems to scratch most of my itches, but there are a couple I want to address in the next couple days. Today’s is tagging.
I’d like to have a set of tags for posts that show visitors which posts are related to one another. Maybe later I can find a way to display posts by tag, to counts by tag, etc. Ruby’s Liquid language has to make that easy, right?
For now, I’ll just be happy with tags on each post. I found a post by Long Qian that looks promising, so I’m going to take notes here on the steps I take to get tags working on my site. Fingers crossed.
Front Matter
First, each tagged post needs a ‘tags’ line in its front matter. Tags are signle words separated by a single space. This is how I tagged this page:
---
title: Adding post tagging to jekyll's Minima theme
author: miller
date: 2020-12-23
tags: jekyll coding
---
Collecting Tags
Next, I made a file called collecttags.html
and saved it in my _includes
directory. The contents of this file (liquid commands) are shared below.
{% assign rawtags = "" %}
{% for post in site.posts %}
{% assign ttags = post.tags | join:'|' | append:'|' %}
{% assign rawtags = rawtags | append:ttags %}
{% endfor %}
{% assign rawtags = rawtags | split:'|' | sort %}
{% assign site.tags = "" %}
{% for tag in rawtags %}
{% if tag != "" %}
{% if tags == "" %}
{% assign tags = tag | split:'|' %}
{% endif %}
{% unless tags contains tag %}
{% assign tags = tags | join:'|' | append:'|' | append:tag | split:'|' %}
{% endunless %}
{% endif %}
{% endfor %}
This makes a list site.tags
.
I then added
{% if site.tags != "" %}
{% include collecttags.html %}
{% endif %}
to _include/head.html
below the line that reads `
. Qian writes that this allows jekyll to load the new HTML file (that creates the
site.tags list) before the
site.tags` list needs to be used.
The have the tags of a post displayed on the post, you need to modify the post layout. I insterted
<span>[
{% for tag in page.tags %}
{% capture tag_name %}{{ tag }}{% endcapture %}
<a href="/tag/{{ tag_name }}"><code class="highligher-rouge"><nobr>{{ tag_name }}</nobr></code> </a>
{% endfor %}
]</span>
in _layouts/post.html
starting at line 6. This seemed like what Qian did for his site. I’ll probably mess with some of the styling later (e.g., bacground color).
At this point in his instructions, Qian says
Note that a link is inserted to each of the tags of the post, at /tag/tag_name. We will come to that in the next step.
I don’t know what that means, yet.
Next, we make a layout for a new kind of page that will display for each tag all the posts for that tag. Here’s the text for a _layouts/tagpage.html
file:
---
layout: default
---
<div class="post">
<h1>Tag: {{ page.tag }}</h1>
<ul>
{% for post in site.tags[page.tag] %}
<li><a href="{{ post.url }}">{{ post.title }}</a> ({{ post.date | date_to_string }})<br>
{{ post.description }}
</li>
{% endfor %}
</ul>
</div>
<hr>
I’ll probably want to go play with that some, too.
At this point, Qian says that you either need to create a file for each tag, into which jekyll will render you list of posts with that tag, or you can run a python script he provides to do it each time you prepare to push your site to its production server.
A markdown template file for the tag uffda
would look like this:
---
layout: tagpage
title: "Tag: uffda"
tag: uffda
---
and it would be saved in a folder tag
in the root folder of your working directory.
For posterity, I’m copying Qian’s python file at the bottom of this page, too. Please respect his ownership of the work.
After setting the execute bit on the python script, I ran it at the command link and received this error:
~/path/ [main] ./tag-generator.py
Traceback (most recent call last):
File "./tag-generator.py", line 23, in <module>
f = open(filename, 'r', encoding='utf8')
TypeError: 'encoding' is an invalid keyword argument for this function
A quick Google search told me that my default install of python is probably version 2, and that I should try running python3. That worked, and I’ve changed the python code below.
After a successful run of the script, I received the following terminal message
Tags generated, count 5
Qian ends his great write-up with code that will create a clickable ‘cloud’ of tags on ths site. He creates a file archives.html
in the _includes
folder of the site that has the following content:
<h2>Archive</h2>
{% capture temptags %}
{% for tag in site.tags %}
{{ tag[1].size | plus: 1000 }}#{{ tag[0] }}#{{ tag[1].size }}
{% endfor %}
{% endcapture %}
{% assign sortedtemptags = temptags | split:' ' | sort | reverse %}
{% for temptag in sortedtemptags %}
{% assign tagitems = temptag | split: '#' %}
{% capture tagname %}{{ tagitems[1] }}{% endcapture %}
<a href="/tag/{{ tagname }}"><code class="highligher-rouge"><nobr>{{ tagname }}</nobr></code></a>
{% endfor %}
Qian writes the following about this chunk of liquid code:
This script fetches
site.tags
and sort them by the size of its referenced posts. The sorted list is insortedtemptags
. And it is iterated to be visualized. I am inspired by this stackoverflow answer.
The following line should display the ‘cloud’ of tags:
{% include archive.html %}
I added this to the end of the tagpage.html
file I created, above, according to Qian’s directions.
And I gotta tell you. This just works. And it looks great. Thanks Long Qian!
Python Script
#!/usr/bin/env python3
'''
tag_generator.py
Copyright 2017 Long Qian
Contact: lqian8@jhu.edu
This script creates tags for your Jekyll blog hosted by Github page.
No plugins required.
'''
import glob
import os
post_dir = '_posts/'
tag_dir = 'tag/'
filenames = glob.glob(post_dir + '*md')
total_tags = []
for filename in filenames:
f = open(filename, 'r', encoding='utf8')
crawl = False
for line in f:
if crawl:
current_tags = line.strip().split()
if current_tags[0] == 'tags:':
total_tags.extend(current_tags[1:])
crawl = False
break
if line.strip() == '---':
if not crawl:
crawl = True
else:
crawl = False
break
f.close()
total_tags = set(total_tags)
old_tags = glob.glob(tag_dir + '*.md')
for tag in old_tags:
os.remove(tag)
if not os.path.exists(tag_dir):
os.makedirs(tag_dir)
for tag in total_tags:
tag_filename = tag_dir + tag + '.md'
f = open(tag_filename, 'a')
write_str = '---\nlayout: tagpage\ntitle: \"Tag: ' + tag + '\"\ntag: ' + tag + '\nrobots: noindex\n---\n'
f.write(write_str)
f.close()
print("Tags generated, count", total_tags.__len__())