ssg  
Article Series

Jekyll in General

Jekyll Plain

Where to Discuss?

Local Group

Preface

Goal: Apply loop with Liquid to make custom archive pages.

Source Code

This article use tutor-03 theme. We will create it step by step.

Layout Preview for Tutor 03

Liquid: Layout Preview for Tutor 03


1: Populate the Content

This is required for loop demo.

Subgoal: Populate content for use with special custom index pages

As always, populate content with good reading is, as hard as giving meaningful variable. Luckily I have done it for you.

Custom Index Page

We are going to explore custom index page such as:

  1. Archive

  2. Tags and Categories

  3. Blog (Article List)

Illustration: Custom Layouts

For this to work, we need a proper blog content, with such diversity such as posting year, and tags for each post, so we can represent the list nicely, with sorting, and also grouping whenever necessary.

My content choice comes to song lyrics. We can tag song lyrics with its genre, and naturally song lyrics also has date released.

I the also add unique post, to examine what does it looks, to have entirely different folder category.

Configuration

I also add permalink configuration.

# Produces a cleaner folder structure when using categories
# permalink: /:year/:month/:title.html
permalink: /:categories/:year/:month/:day/:title:output_ext

Pattern

Our typical content is usually short quote from a lyric, with four frontmatter items as below:

---
layout      : post
title       : Julien Baker - Sprained Ankle
date        : 2018-09-13 07:35:05 +0700
categories  : lyric
tags        : [rock, 2010s]
author      : Julien Baker

excerpt: Wish I could write songs about anything other than death
---

A sprinter learning to wait  
A marathon runner, my ankles are sprained  
A marathon runner, my ankles are sprained  

All example content should follow the pattern above. All these content wear post layout.

Directory Tree: Posts

A few content is enough. I add ten lyrics to make sure the content have enough diversity.

$ tree _posts/
_posts/
├── 2016-01-01-winter.md
└── lyrics
    ├── 2017-03-15-nicole-atkins-a-litle-crazy.md
    ├── 2017-03-25-nicole-atkins-a-night-of-serious-drinking.md
    ├── 2018-01-15-emily-king-distance.md
    ├── 2018-02-15-emma-ruth-rundle-shadows-of-my-name.md
    ├── 2018-09-07-julien-baker-something.md
    ├── 2018-09-13-julien-baker-sprained-ankle.md
    ├── 2019-03-15-hemming-vitamins.md
    ├── 2019-05-15-brooke-annibale-by-your-side.md
    ├── 2019-05-25-brooke-annibale-yours-and-mine.md
    ├── 2019-07-15-mothers-no-crying-in-baseball.md
    └── 2020-03-15-company-of-thieves-oscar-wilde.md

1 directory, 12 files

Jekyll: All Content Sources

Also edit the frontmatter as necessary, for the rest of the content.

Rendered Posts

How does it looks in your file systems?

$ tree _site/lyric
_site/lyric
├── 2017
│   └── 03
│       ├── 15
│       │   └── nicole-atkins-a-litle-crazy.html
│       └── 25
│           └── nicole-atkins-a-night-of-serious-drinking.html
├── 2018
│   ├── 01
│   │   └── 15
│   │       └── emily-king-distance.html
│   ├── 02
│   │   └── 15
│   │       └── emma-ruth-rundle-shadows-of-my-name.html
│   └── 09
│       ├── 07
│       │   └── julien-baker-something.html
│       └── 13
│           └── julien-baker-sprained-ankle.html
├── 2019
│   ├── 03
│   │   └── 15
│   │       └── hemming-vitamins.html
│   ├── 05
│   │   ├── 15
│   │   │   └── brooke-annibale-by-your-side.html
│   │   └── 25
│   │       └── brooke-annibale-yours-and-mine.html
│   └── 07
│       └── 15
│           └── mothers-no-crying-in-baseball.html
└── 2020
    └── 03
        └── 15
            └── company-of-thieves-oscar-wilde.html

23 directories, 11 files

Jekyll: All Rendered Lyrics


2: Includes: Refactoring

Layout: Header

We need to modify the header a little, so we have simple navigation in plain HTML.

  <p>
    [ <a href="/">Home</a> ]
    [ <a href="/pages/">Blog</a> ]
    [ <a href="/pages/about">About</a> ]
    [ <a href="/tags/">Tags</a> ]
    [ <a href="/categories/">Categories</a> ]
    [ <a href="/by-year/">By Year</a> ]
  </p>

  <hr/>

Jekyll: Home Navigation

Includes: Site Directory

Also prepare for later refactoring, we need to put those three files in their own folder.

$ tree _includes
_includes
└── site
    ├── footer.html
    ├── header.html
    └── head.html

1 directory, 3 files

Jekyll: Directory NERDTree with Navigation in Header

Layout: Liquid Default

Repecstively, we need to modify includes toi reflect the right path.

<!DOCTYPE html>
<html>

<head>
  {% include site/head.html %}
</head>

<body>
  {% include site/header.html %}

  <main role="main">
    {{ content }}
  </main>

  {% include site/footer.html %}
</body>

</html>

3: Content: Index

There is no significance changes for index.html, except you can change the name to blog.html, and add the right permalink.

---
layout    : page
title     : Blog Posts
permalink : /pages/
---

 {% assign posts = site.posts %}

  <ul>
  {% for post in posts %}
    <li>
      <a href="{{ site.baseurl }}{{ post.url }}">
        {{ post.title }}
      </a>
    </li>
  {% endfor %}
  </ul>

Now see how it looks like this page with new content.

Jekyll: Cutom Pages: Blog

Filename Caveat

There is caveat of using blog.html. Jekyll pagination-v1 would only work with index.html. This issue has been fixed with pagination-v2. So for safety issue, we use index.html instead, but leave the /pages/ permalink, just instead we decide to use pagination-v2, and rename the file to blog.html in later chapter.


4: Content: Category and Tag

On most SSG, dealing with tags and categories, need understanding of its data structures.

Content: Frontmatter

Example of Tags and categories in content is as below:

---
layout      : post
title       : Julien Baker - Sprained Ankle
date        : 2018-09-13 07:35:05 +0700
categories  : lyric
tags        : [rock, 2010s]
---

Data Structure

Both category and tag have very similar structure. While tag use site.tags, category use site.categories. The issue is extracting the tags collection.

In Jekyll, site.tags is actually a hash (key+value), that contains array of posts related to that tag.

You can print with related with soul tag

{{ site.tags['soul'] }}

And check how many post related with that soul tag

{{ site.tags['soul'] | size }}

In liquid site.tags can be presented as an array of two:

  1. tag[0] contain tag name.
  2. tag[1] contain array of posts.

You can check yourself with this snippet.

  {% assign tag = site.tags | first %}
  {{ tag[0] }}
  {{ tag[1] }}

Building Array of Terms

Now all we have to do is to create an array contain all unique posts. Since liquid has no built in way to create array, we have to use split:

{% assign term_array = "" | split: "|" %}

To avoid useless whitespace, we put the code between capture tag.

{% capture spaceless %}
  ...
{% endcapture %}

And finally we have the code

{% capture spaceless %}
  {% assign term_array = "" | split: "|" %}
  {% for tag in terms %}
    {% assign term_first = tag | first %}
    {% assign term_array = term_array | push: term_first %}
  {% endfor %}
  
  {% assign term_array = term_array | sort %}
{% endcapture %}

I know this code above looks odd. With ruby filter for liquid we can make it shorter as below:

    def term_array(terms)
      terms.keys
    end

But we won’t be using any filter in this chapter.

Tags

Now we can apply above snippets to custom tags pages.

---
layout    : page
title     : All Tags
permalink : /tags/
---

{% assign terms = site.tags %}

{% capture spaceless %}
  {% assign term_array = "" | split: "|" %}
  {% for tag in terms %}
    {% assign term_first = tag | first %}
    {% assign term_array = term_array | push: term_first %}
  {% endfor %}
  
  {% assign term_array = term_array | sort %}
{% endcapture %}

  <p>Tag List:
  <ul>
  {% for item in (0..terms.size) %}{% unless forloop.last %}
    {% assign this_word = term_array[item] | strip_newlines %}
    <li id="{{ this_word | slugify }}" class ="anchor-target">
      {{ this_word }}
    </li>
  {% endunless %}{% endfor %}
  </ul>
  </p>

Jekyll: Cutom Pages: Tags

The forloop reference can be read here:

Categories

---
layout    : page
title     : All Categories
permalink : /categories/
---

{% assign terms = site.categories %}

{% capture spaceless %}
  {% assign term_array = "" | split: "|" %}
  {% for tag in terms %}
    {% assign term_first = tag | first %}
    {% assign term_array = term_array | push: term_first %}
  {% endfor %}
  
  {% assign term_array = term_array | sort %}
{% endcapture %}

  <p>Tag List:
  <ul>
  {% for item in (0..terms.size) %}{% unless forloop.last %}
    {% assign this_word = term_array[item] | strip_newlines %}
    <li id="{{ this_word | slugify }}" class ="anchor-target">
      {{ this_word }}
    </li>
  {% endunless %}{% endfor %}
  </ul>
  </p>

Jekyll: Cutom Pages: Tags


5: Content: Archive by Year

Grouping

Instead of showing list of pages in plain fashioned, we can show list of pages, grouped by year, or even by year+month. To do this we utilize group_by_exp filter.

{% assign postsByYear = site.posts
          | group_by_exp: "post", "post.date | date: '%Y'"  %}

With code above we can loop to show each group, and then having inner loop contain each posts

{% assign postsByYear = site.posts
          | group_by_exp: "post", "post.date | date: '%Y'"  %}

<div id="archive">
{% for year in postsByYear %}

  <section>
    <p class ="anchor-target" 
       id="{{ year.name }}"
      >{{ year.name }}</p>

    <ul>
      {% for post in year.items %}
      <li><a href="{{ site.baseurl }}{{ post.url }}">
          {{ post.title }}
      </a></li>
      {% endfor %}
    </ul>
  </section>

{% endfor %}
</div>

Year Text

We can creatively change the text to show nicer name such as This year’s posts (2020) instead just 2020.

  {% capture spaceless %}
    {% assign current_year = 'now' | date: '%Y' %}
    {% assign year_text = nil %}

    {% if year.name == current_year %}
      {% assign year_text = year.name
                | prepend: "This year's posts (" | append: ')' %}
    {% else %}
      {% assign year_text = year.name %}
    {% endif %}
  {% endcapture %}

With plugin, this could be as short as:

    def text_year(post_year)
      (post_year == Time.now.strftime("%Y")) ?
        "This year's posts (#{post_year})" : post_year
    end

But we won’t be using plugin in this chapter.

Finally

The complete code is as below:

---
layout    : page
title     : Archive by Year
permalink : /by-year/
---

{% assign postsByYear = site.posts
          | group_by_exp: "post", "post.date | date: '%Y'"  %}

<div id="archive">
{% for year in postsByYear %}

  {% capture spaceless %}
    {% assign current_year = 'now' | date: '%Y' %}
    {% assign year_text = nil %}

    {% if year.name == current_year %}
      {% assign year_text = year.name
                | prepend: "This year's posts (" | append: ')' %}
    {% else %}
      {% assign year_text = year.name %}
    {% endif %}
  {% endcapture %}

  <section>
    <p class ="anchor-target" 
       id="{{ year.name }}"
      >{{ year_text }}</p>

    <ul>
      {% for post in year.items %}
      <li><a href="{{ site.baseurl }}{{ post.url }}">
          {{ post.title }}
      </a></li>
      {% endfor %}
    </ul>
  </section>

{% endfor %}
</div>

Jekyll: Cutom Pages: Archives

You can compare with lyrics directory tree above.


What’s Next?

Consider continue reading [ Jekyll - Plain - Custom Output ].

Thank you for reading.