Skip to content

Instantly share code, notes, and snippets.

@nicolasdao
Last active February 17, 2022 04:44
Show Gist options
  • Save nicolasdao/e6b63134d25424578a7f2aab98a52af1 to your computer and use it in GitHub Desktop.
Save nicolasdao/e6b63134d25424578a7f2aab98a52af1 to your computer and use it in GitHub Desktop.
Jekyll guide. Keywords: jekyll jekyl ruby liquid

JEKYLL GUIDE

Table of contents

Before we begin

Jargon

Front matter

Front matter is a YAML block defined between --- delimiters that is placed at the very top of markdown files to define metadata for that specific file.

---
name: Nicolas Dao
title: 'Managing director'
image_path: /uploads/nic.png
email: [email protected]
linkedin: nicolasdao
position: 40
publish: true
---

Hello, I'm Nic, and this text is a content example for the page called {{ page.title }}.

Each variable in that YAML block is then accessible in your page via the page API.

IMPORTANT: The naming convention kind of matters. Use snake-case to name your properties (e.g., first_name insteasd of firstName). Nothing will break if you don't but if you do, those fields will be properly formatted when using CMS such as CloudCannon. A field named first_name will be displayed as an input with a First Name label.

Liquid

Liquid is the templating language used by Jekyll. This allows to insert Ruby logic inside your HTML.

<div>
{% assign sorted = site.people | sort: 'position' %}
{% for person in sorted %}
  {% if person.publish %} 
    <div class="row">
      <ul>
        <li>Name: {{ person.name }}</li>
        <li>Title: {{ person.title }}</li>
        <li>Intro: {{ person.content }}</li>
      </ul>
    </div>
  {% endif %}  
{% endfor %} 
</div>

Liquid syntax

Liquid is the templating language used by Jekyll. This allows to insert Ruby logic inside your HTML.

if else

<div class="{% if page.topmenu == 'home'  %}no-background{% elsif page.topmenu == 'contact' %}background{% endif %}">
	<!-- Some HTML -->
</div>

for loop

<div>
{% assign sorted = site.people | sort: 'position' %}
{% for person in sorted %}
  {% if person.publish %} 
    <div class="row">
      <ul>
        <li>Name: {{ person.name }}</li>
        <li>Title: {{ person.title }}</li>
        <li>Intro: {{ person.content }}</li>
      </ul>
    </div>
  {% endif %}  
{% endfor %} 
</div>

If you need the index in the loop:

<div>
	<ul>
	{% for person in site.people %}
        	<li>Name: {{ person.name }}</li>
        	<li>Zero-based index: {{ forloop.index0 }}</li>
        	<li>Index: {{ forloop.index }}</li>
        	<li>Is first: {{ forloop.first }}</li>
        	<li>Is last: {{ forloop.last }}</li>
        	<li>Position: {{ forloop.index }}/{{ forloop.length }}</li>
	{% endfor %} 
	</ul>
</div>

Full API details at https://learn.cloudcannon.com/jekyll/looping-in-liquid/

liquid block

This block is used to perform complex operations that would be hard to add inline. The next example shows how to create a new header_style variable:

{%- liquid
	assign header_style = 'font-weight:' | append: block.settings.font_weight | append: ';'
	if block.settings.font_size > 0
		assign header_style = header_style | append: 'font-size:' | append: block.settings.font_size | append: 'px;'
	endif
-%}

Collections

Collection conventions

  • Use plural: Try if possible to use plural in your collection. This helps Jekyll and CMS that use Jekyll to make smart decisions when managing collections.
  • Use lowercase dash separated: For each .md file under your collection folder, use dash-separated lowercase file name. This convention helps if the collection used to create web pages as this file name is used in the URL (e.g., _persons/nicolas-dao.md).

Collection basics

A collection is like a local DB in markdown which is globally available via the site.yourCollection API. An entry is a .md file. The metadata are expressed using liquid and content can also be added. For example, let's create a people collection:

  1. Create a new _persons folder in your root repository.
  2. Under the _persons repository, create a new nicolas-dao.md file as follow:
---
name: Nicolas Dao
title: 'Managing director'
image_path: /uploads/nic.png
email: [email protected]
linkedin: nicolasdao
position: 40
publish: true
---

Hello, I'm Nic, and this text is a content example.
  1. Refer it in an HTML page:
<div>
{% assign sorted = site.persons | sort: 'position' %}
{% for person in sorted %}
  {% if person.publish %} 
    <div class="row">
      <ul>
        <li>Name: {{ person.name }}</li>
        <li>Title: {{ person.title }}</li>
        <li>Intro: {{ person.content }}</li>
      </ul>
    </div>
  {% endif %}  
{% endfor %} 
</div>

If you wish Jekyll to generate a new page for each item in your collection, in your _config.yml file, declare the collection as follow:

collections:
  persons:
    output: true

Using collections has front matter input

This section is irrelevant to Jekyll itsefl. Some CMS (e.g., CloudCannon) use the collection naming convention to make some smart choices in regards to the type of input to use to manage the page's front matter inputs.

For example, lets imagine this page:

---
title: 'Jekyll tutorial 101'
author: Nicolas Dao
---

Blabla

Written by {{ page.author }}

If no authors collection exist, CloudCannon will maintain this page's author field as a text input. However, CloudCannon automatically change the text input to a select input using the specific author in the option list if we add an authors collection to the website as follow:

_authors/
    |____nicolas-dao.md
    |____stephen-king.md

and replace the above page's front matter as follow:

---
title: 'Jekyll tutorial 101'
author: nicolas-dao
---

Blabla

{% for author in site.authors %}
	{% if page.author and author.name and author.path contains page.author %}
		Written by {{ author.name }}
	{% endif %}
{% endfor %}

If the front matter is updated as follow:

---
title: 'Jekyll tutorial 101'
authors: 
	- nicolas-dao
	- stephen-king
---

Then CloudCannon changes the input to a multi select.

Blog

Blog posts are nothing more than a special type of collection. The only aspects that make it special are:

  • The root folder must be named _posts (therefore posts are globally accessible via site.posts).
  • The .md file must be prefixed with the post's timestamp (e.g., 2020-10-21-your-post-title.md).

This section covers the following configurations:

URL paths

By default, assuming that there is a post under _posts/2020-10-21-your-post-title.md, the path to the blog is formatted as follow: https://example.com/2020/10/21/your-post-title

To replace the 2020/10/21 prefix with your own, add a permalink property in the _config.yml:

permalink: /:title/

With this config, the previous post is accessible at https://example.com/your-post-title

However, the most likely scenario is that the blog post lives under specific categories. You can specify those categories in the post's front matter

---
layout: default
categories:
  - blog
  - tech
---

Hello, I'm a tech post

To use those categories in the URL path, update the permalink as follow:

permalink: /:categories/:title/

When that's done, the previous post is accessible at https://example.com/blog/tech/your-post-title

Post - Liquid properties

Depending on the use case, you may have to access various post metadata via the site.posts API. The following snippet demonstrates how to list some of the most common properties:

{% for post in site.posts %}
	<h3>{{ post.title }}</h3>
	<ul>
		<li>{{ post.url }}</li> 
		<li>{{ post.path }}</li>
		<li>{{ post.date }}</li>
		{% for category in post.categories %}
			<li>{{ category }}</li>
		{% endfor %}
	</ul>
{% endfor %}

The above snippet prints something similar to this:

Hello Post Title

  • /blog/tech/hello-again/
  • _posts/2020-12-06-hello-again.md
  • 2020-12-06 00:00:00 +1100
  • blog
  • tech

Blog landing page

Creating a landing page that lists all the blog posts is equivalent to creating an standard HTML page and use the site.posts variable to loop through all posts. Example, blog/index.html

{% for post in site.posts %}
	<!--Services Block-->
	<div id="post_{{forloop.index}}" class="blog-post services-block col-md-6 col-sm-6 col-xs-12">
		<div class="inner-box">
			<div class="image">
				<a href="{{post.url}}"><img src="{{post.image}}" alt="" /></a>
			</div>
			<div class="lower-content" style="text-align: unset;">
				<h3 style="height: 70px;"><a href="{{post.url}}">{{post.title}}</a></h3>
				<p style="margin-top: 30px;height: 130px;">{{ post.content | extract_text }}</p>
				<div class="row clearfix" style="margin-bottom: 40px;margin-left: 0px;">
					{% for author in site.authors %}
						{% if post.author and author.name and author.path contains post.author %}
							<div class="row" style="width:380px;">
								<div class="col-xs-3">
									<a href="{{ author.linkedin }}" target="_blank">
										<img src="{{ author.image_path }}" style="width: 60px; border-radius: 32px; border: 1px solid #ececec;">
									</a>
								</div>
								<div class="col-xs-9">
									<div class="row">
										<div class="col-xs-12">
											<a href="{{ author.linkedin }}" target="_blank">{{ author.name }}</a>
										</div>
									</div>
									<div class="row">
										<div class="col-xs-12">
											<p style="font-size: small;font-style: italic;">{{ post.date | format_date }}</p>
										</div>
									</div>
								</div>
							</div>

						{% endif %}
					{% endfor %}
				</div>
			</div>
		</div>
	</div>
{% endfor %}

Adding a search functionality is as simple as using some basic Javascript to filter tiles based on text.

Plugins & custom Ruby logic

Jekyll allows to add custom logic via Plugins. To create your own custom logic:

  1. Create a new _plugins folder.
  2. Create a new .rb file under the _plugins folder (e.g., extract_text.rb).
  3. Define your custom module and register it to Liquid. The next example removes HTML tags and truncate the text to its first 200 characters:
# Declare module of your plugin under Jekyll module
module Jekyll::ExtractText

	# Each method of the module creates a custom Jekyll filter
	def extract_text(input)
		# extract_text filter business logic

		# Return result
		"#{input.gsub(/<(.*?)>/, '')[0...200]}..."
	end
end

# Register your custom filter
Liquid::Template.register_filter(Jekyll::ExtractText)
  1. Apply the filter function to some text in your Liquid template:
<p>{{ post.content | extract_text }}</p>

FAQ

How to create a blog?

Please refer to the Blog section.

How to stringify a Liquid object in HTML?

{{ section | jsonify }}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment