This post is a summary of my initial blog building. It offers some guidance on how to use Jekyll and some clues to delve into details. To better understand some features, it interprets them using some javascript concepts.


Liquid Basics

Liquid is an open-source template language created by Shopify and written in Ruby. It has been in production use at Shopify since 2006 and is now used by many other hosted web applications.

Data Types

Six types: String, Number, Boolean, Nil, Array, or EmptyDrop.

Liquid variables can be initialized by using the{% assign %} or {% capture %} tags.

  • Boolean:  true, false. e.g. {% assign flag = true %}

    Note that All values in Liquid are treated as true, except for nil and false.

  • Number:  floats and integers. e.g. {% assign num = 10 %}

  • String:  e.g. {% assign str = ‘Hello World’ %}

  • Array:  a list of variables can be accessed using indexes e.g. site.posts[0] or iterated through using{% for post in site.posts %}statement.

Note:

.first or .last can be used to access the first or last array element.

.size can be used to get the number of elements in an array or object.

In the case of a string, .size will return the number of characters.

Operators

== != > < >= <= or and contains

Tags

  • Control Flow
{% if %} {% elsif %} {% else %} {% endif %}
{% case %} {% when %}
{% unless %}

Note that: elsif is not “elseif”. unless means “if not”.

  • Iteration
{% for %} {% else %} {% endfor %}
{% break %} {% continue %}
{% cycle %}
{% tablerow %}
  • Other tags
{% assign %} {% include %}
{% raw %} {% endraw % }
{% endraw % }
{% comment %} {% endcomment %}

Filters

append prepend join split default date map where
plus minus times divided_by modulo floor ceil abs round at_least at_most
sort sort_natural reverse first last size
replace replace_first newline_to_br relative_url
strip strip_html strip_newlines rstrip lstrip
slice concat escape escape_once
remove remove_first truncate truncatewords compact
capitalize downcase upcase
uniq url_decode url_encode 

TOC

GitHub Pages can’t run custom Jekyll plug-ins so when generating Tables of Contents (TOCs), you’re stuck with either a JavaScript solution or using kramdown’s {:toc} option. However, by using {:toc}, you are forced to have that code next to your actual markdown and you can’t place it in a layout. This means every. single. post. will need to have the snippet. If you choose the JavaScript approach, that’s perfectly fine but what if JS is disabled on the someone’s browser or your page is just really long and it becomes inefficient.

This toc.html solution is written entirely in Liquid and can be used as an {% include %}. Use it in your template layout where you have {{ content }}.

Jekyll Pure Liquid Table of Contents

A Jekyll TOC without Plugins or JavaScript

Note: It seems that the table of contents can only be placed on the top of the post if using toc.html. When inserted into the post it doesn’t work as expected.

Built-in Jekyll Variables

Object and List

There are two types of built-in variables:

  • Object variable is like a javascript object with key/name pairs.

e.g., suppose there is a foo: bar property in the site scope object, you can either use dot operator site.foo or string site["foo"] to access it.

To iterate through all the keys in an object use{% for %}statement. e.g.:

{% for item in site %}
  {{ item }}
{% endfor %}
  • List variable can be accessed using indexes like an array, e.g. site.posts[0] .

To iterate through a list use{% for %}statement. e.g.:

{% for post in site.posts %}
  {{ post.title }}
{% endfor %}

Note that although we use javascript concept of object and List(or Array) to describe these built-in variables, the difference is they are immutable.

e.g., the following code will still print out the title of the first post without any warning prompted:

{% assign site.posts[0] = nil %}
{{ site.posts[0].title }}

The good news is they can be assigned to custom variables. e.g.:

{% assign test = site %}
{% for post in test.posts %}
  {% assign pos = post %}
  {{ pos.title }}
{% endfor %}

Global Variables

  • site An Object variable refers to the entire site scope containing built-in site variables and custom site variables set via configurations in _config.yml.

  • page An Object variable refers to the current page scope containing built-in page variables and custom page variables set via the front matter.

Note that the default value of undefined or unassigned custom variables in the above scopes is nil.


For other global Variables (e.g. layout, content, paginator), see “Jekyll Docs - Variables”

Variables in the Site Scope Object

  • site.posts A reverse chronological list of all Posts in the _posts directory. (from newest to oldest)

  • site.categories An object stores all categories appeared in posts as property names whose values are lists of correspoding Posts.

  • site.categories.CATEGORY_X The list of all Posts with a category called “CATEGORY_X”.

  • site.tags An object stores all tags appeared in posts as property names whose values are lists of correspoding Posts.

  • site.tags.TAG_X The list of all Posts with a tag called “TAG_X”.

  • site.collections A list of all the collections (including posts).


All the custom site variables set via _config.yml are available through the site variable.

For more site Variables, see  Jekyll Docs - Variables

Variables in the Page Scope Object

To print out all the page variables in the first post:

{% for post in site.posts %}
  {% for item in post %}
    {{ item }}
  {% endfor %}
  {% break %}
{% endfor %}

The result:

relative_path excerpt previous title categories collection url content id output date next tags path draft slug ext layout

(Note that custom variables in the page object are not included in the result)

More infomation about page variables, see Jekyll Docs - Variables

site.tags and site.categories

Note that as of version ‘4.1.1’, Jekyll automatically fills the site.tags without user intervention with tags used in at least one post. Each tag property in the site.tags object points to a list of posts that contain it. However, it doesn’t count in the tags in other non-posts collections. site.categories dittoes.


Print out site.categories:

{{ site.categories }}

The result:

{"my"=>[#, #, #, #, #, #], "cat2"=>[#, #, #, #, #], "cat1"=>[#, #, #, #]}


Access the values in site.categories object:

{{ site.categories["my"].size }}
{{ site.categories.my.size }}
{{ site.categories.my[0].title }}
{{ site.categories["my"][0].title }}

However, when you loop through site.tags or site.categories, it will return an array(not property key) with two elements every time. The first element is the tag name or category name. The second element is a list of corresponding posts.

{% for tag in site.tags %}
  {{ tag[0] }} / {{ tag[1].size }}
  {% break %}
{% endfor %}

{% for tag in site.tags %}
  {{ tag.size }}
  {% break %}
{% endfor %}

This is inconsistent with the behavior of other “objects”. e.g., the following code failed:

{% for item in site %}
  {{ item[0] }} / {{ item[1] }}
  {% break %}
{% endfor %}

Custom Array and Object

You cannot initialize arrays or objects using only Liquid.

Custom Array

You can, however, use the split filter to break a string into an array of substrings.

Note that arrays created using the split filter are immutable. e.g., the following code will output: aa-bb-cc.

{% assign arr = "aa|bb|cc" | split: "|" %}
{% assign arr[0] = "dd" %}
{{ arr | join: "-" }}

Custom Object

You can also use custom collection to create a predefined, immutable map with key/value pairs. For example:

1 . Create a new custom collection called metas, set output to false to stop outputting HTML files in this directory.

collections:
  metas:
    output: false

2 . Put the following Front Matter key/value pairs into a file called categories.md in the metas collection.

---
name: categories
cat1: category one
cat2: category two
---

3 . Create a file called category_info.html containing the following code.

{% assign category_info = nil %}
{% for meta in site.metas %}
  {% if meta.name == "categories" %} {% assign category_info = meta %} {% break %} {% endif %}
{% endfor %}

4 . Use {% include %} to extract the “string map” from custom collection site.metas.

{% include category-info.html %}
{% if category_info and category_info[page.category] %}<p>({{ category_info[page.category] }})</p>{% endif %}


Note that you are not supposed to loop through this “string map” using {% for %} statement because there are also multiple built-in variables inside the “string map”.

Nested Square Bracket Operator

Another problem is that nested square bracket operator are not supported in Jekyll.

e.g., if you execute {{ site.metas[0]["name"] }}, it will output categories as we defined above in step 2.

But the following code won’t work:

{% assign arr = "name|title" | split: "|" %}
{{ site.metas[0][arr[0]] }}

To fix this problem, you need to dismantle the nested square brackets:

{% assign arr = "name|title" | split: "|" %}
{% assign temp = arr[0] %}
{{ site.metas[0][temp] }}

or

{% assign arr = "name|title" | split: "|" %}
{% capture key %}{{ arr[0] }}{% endcapture %}
{{ site.metas[0][key] }}

Others

Date & Time

The date set in a page such as date: 2020-07-10 16:54:16 is in UTC. The default timezone is +0000 if no timezone attached. It has nothing to do with the timezone setting in _config.yml, which tells Jekyll which timezone to display. So if your local timezone is +0500 and your local time is 2020-07-10 16:54:16, when you put 2020-07-10 16:54:16 in the page’s front matter, it is 2020-07-10 16:54:16+0000 in effect. By executing {{ page.date | date: "%F %R%z" }} , the actual time displayed will be 2020-07-10 21:54:16+0500 which is not what we expect.

You should specify a date in the front matter like this: date: 2020-07-10T16:54:16+0500.

More Information about date formatting

Github Pages

Uncommmnt these lines in the Gemfile:

source 'https://rubygems.org'
gem 'github-pages', group: :jekyll_plugins

Set baseurl in _config.yml to your repository name if your repository name is not the github domain name <username>.github.io.


(Note that all the Jekyll features discussed in this post are based on Jekyll Version 4.1.1, there may be changes made in the future version of Jekyll)

References & Resources

[1]. Jekyll Docs - Variables

[2]. Shopify Cheat Sheet

[3]. Liquid Basics

[4]. Liquid for Designers

[5]. Shopify Liquid Tutorial

[6]. Jekyll Pure Liquid Table of Contents

[7]. A Jekyll TOC without Plugins or JavaScript

[8]. Date formatting

[9]. Types of Github Pages Sites