Jekyll - How to use post parameters as custom taxonomy

I decided to drop tags and categories during the migration form WordPress to Jekyll but as it turned out, that was not the best decision.

I decided to use topics to organize entries as it feels just right because I can quickly separate group of entries and automatically provide RSS feed.

I will shortly describe how I am using mentioned earlier post parameter because it is fairly easy to achieve and modify as you are limited only by your imagination.

How to define one or more topics inside the blog post?

Use topics key to list topics.

---
layout: post
topwords: topic site topics index posts data self list base category html atom layouts name layout jekyll file write join pages include feed downcase source production generator generate create yaml updated
title:  Jekyll - How to use custom taxonomy or post parameters
tags:
- Jekyll
- Plugins
---
<p>contents</p>
Always treat topics key as an array. Even when it contains only one topic.

The origin

I will use generator code available at the Jekyll's plugins page and just modify it slightly each time to display topics, list posts, and create RSS feed.

module Jekyll

  class CategoryPage < Page
    def initialize(site, base, dir, category)
      @site = site
      @base = base
      @dir = dir
      @name = 'index.html'

      self.process(@name)
      self.read_yaml(File.join(base, '_layouts'), 'category_index.html')
      self.data['category'] = category

      category_title_prefix = site.config['category_title_prefix'] || 'Category: '
      self.data['title'] = "#{category_title_prefix}#{category}"
    end
  end

  class CategoryPageGenerator < Generator
    safe true

    def generate(site)
      if site.layouts.key? 'category_index'
        dir = site.config['category_dir'] || 'categories'
        site.categories.keys.each do |category|
          site.pages << CategoryPage.new(site, site.source, File.join(dir, category), category)
        end
      end
    end
  end

end

List of the topics

To create list of the topics (/topics/index.html file) look at the following plugin source code which uses topic_list.html layout.

module Jekyll
  class TopicList < Page    
    def initialize(site, base, dir, topics)
      @site = site
      @base = base
      @dir = dir
      @name = 'index.html'

      self.process(@name)
      self.read_yaml(File.join(base, '_layouts'), 'topic_list.html')
      self.data['topics'] = topics
      self.data['title'] = "Topics"
    end
  end

  class TopicListGenerator < Generator
    safe true
    
    def generate(site)
      if site.layouts.key? 'topic_list'
        dir = 'topics'

        # get list of the unique topics
        topics = [];
        site.posts.each do |p|
          p.data['topics'].each do |t|
            topics << t if !topics.include? t
          end if p.data['topics']
        end

        write_topic_list(site, dir, topics)
      end
    end
  
    def write_topic_list(site, dir, topics)
      index = TopicList.new(site, site.source, dir, topics)
      index.render(site.layouts, site.site_payload)
      index.write(site.dest)
      site.pages << index
    end
  end
end

Notice that generate function inside TopicListGenerator class use topics array to pass further the list of the unique topics.

Use page.topics inside topic_list.html layout to access above-mentioned topic list.

---
layout: index
topwords: topic site topics index posts data self list base category html atom layouts name layout jekyll file write join pages include feed downcase source production generator generate create yaml updated
---
{% for topic in page.topics %}
  <div class="topic">
    <h2>
      <a href="/topic/{{ topic | downcase }}/">{{ topic }}</a>
    </h2>
    <div>
      <small>
        <a href="/topic/{{ topic | downcase }}/atom.xml">RSS</a>
      </small>
    </div>
  </div>
{% endfor %}

Index pages

To create index pages for each topic (/topic/jekyll/index.html file and so on) containing only selected posts look at the following plugin.

Notice that we pass further the topic and posts belonging to this topic.

 class TopicIndex < Page    
 
    def initialize(site, base, dir, topic)
      @site = site
      @base = base
      @dir = dir
      @name = 'index.html'

      # get topic's posts
      topic_posts = []
      site.posts.each do |p|
        ((topic_posts << p) if p.data['topics'].include? topic) if p.data['topics']
      end

      self.process(@name)
      self.read_yaml(File.join(base, '_layouts'), 'topic_index.html')
      self.data['posts'] = topic_posts.reverse
      self.data['topic'] = topic
      self.data['title'] = "Topic \""+topic+"\""
    end
  end

  class TopicIndexGenerator < Generator
    safe true
    
    def generate(site)
      if site.layouts.key? 'topic_index'
        dir = 'topic'
        
        # get list of the unique topics
        topics = [];
        site.posts.each do |p|
          p.data['topics'].each do |t|
            topics << t if !topics.include? t
          end if p.data['topics']
        end
        
        # create page for each topic
        topics.each do |topic|
          write_topic_index(site, File.join(dir, topic.downcase), topic)
        end
      end
    end
  
    def write_topic_index(site, dir, topic)
      index = TopicIndex.new(site, site.source, dir, topic)
      index.render(site.layouts, site.site_payload)
      index.write(site.dest)
      site.pages << index
    end
  end
end

topic_index.html layout doesn't contain anything new.

---
layout: index
topwords: topic site topics index posts data self list base category html atom layouts name layout jekyll file write join pages include feed downcase source production generator generate create yaml updated
---
<h1 class="page-title">{{page.title}}</h1>
{% for post in page.posts %}
	{% include content-on-the-list.html %}
{% endfor %}

Atom feeds

It is almost identical as above plugin as I just changed names and layout (/topic/jekyll/atom.xml file and so on).

  class TopicAtom < Page    
  
    def initialize(site, base, dir, topic)
      @site = site
      @base = base
      @dir = dir
      @name = 'atom.xml'

      # get topic's posts
      topic_posts = []
      site.posts.each do |p|
        ((topic_posts << p) if p.data['topics'].include? topic) if p.data['topics']
      end
      
      self.process(@name)
      self.read_yaml(File.join(base, '_layouts'), 'topic_atom.xml')
      self.data['posts'] = topic_posts.reverse
      self.data['topic'] = topic
      self.data['title'] = "Topic \""+topic+"\""
    end
  end

  class TopicAtomGenerator < Generator
    safe true
    
    def generate(site)
      if site.layouts.key? 'topic_atom'
        dir = 'topic'
        topics = [];
        
        # get list of the unique topics
        site.posts.each do |p|
          p.data['topics'].each do |t|
            topics << t if !topics.include? t
          end if p.data['topics']
        end
        
        # create RSS feed for each topic
        topics.each do |topic|
          write_topic_atom(site, File.join(dir, topic.downcase), topic)
        end
      end
    end
  
    def write_topic_atom(site, dir, topic)
      index = TopicAtom.new(site, site.source, dir, topic)
      index.render(site.layouts, site.site_payload)
      index.write(site.dest)
      site.pages << index
    end
  end
end

topic_atom.xml does not use any other layout as it is just simple Atom feed.

---
layout: nil
topwords: topic site topics index posts data self list base category html atom layouts name layout jekyll file write join pages include feed downcase source production generator generate create yaml updated
---
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
 <title>sleeplessbeastie's notes - {{ page.topic }}</title>
 <link href="{{ site.production_url }}/topic/{{ page.topic | downcase }}/atom.xml" rel="self" />
 <link href="{{ site.production_url }}/topic/{{ page.topic | downcase }}/" />
 <updated>{{ page.posts.first.date | date_to_xmlschema }}</updated>
 <id>{{ site.production_url }}/</id>
 <author>
   <name>Milosz Galazka</name>
 </author>
 {% for post in page.posts offset: 0 limit: 10 %}
 <entry>
   <title>{{ post.title }}</title>
   <link href="{{ site.production_url }}{{ post.url }}/" />
   <updated>{{ post.date | date_to_xmlschema }}</updated>
   <id>{{ site.production_url }}{{ post.url }}/</id>
   <content type="html">{{ post.content | postmorefilter: post.url | xml_escape }}</content>
 </entry>
 {% endfor %}
</feed>
Milosz Galazka's Picture

About Milosz Galazka

Milosz is a Linux Foundation Certified Engineer working for a successful Polish company as a system administrator and a long time supporter of Free Software Foundation and Debian operating system. He is also open for new opportunities and challenges.

Gdansk, Poland https://sleeplessbeastie.eu