A quick review of Jekyll
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 fornil
andfalse
. -
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
.
References & Resources
[2]. Shopify Cheat Sheet
[3]. Liquid Basics
[4]. Liquid for Designers
[6]. Jekyll Pure Liquid Table of Contents
[7]. A Jekyll TOC without Plugins or JavaScript
[8]. Date formatting