Last modified on 01 Oct 2021.

This note is used for you who have already had the basic idea about jekyll and how to create a jekyll site. This note is only for quickly reference.

Using docker to run/deploy jekyll

Read this readme. An example is repo of this site.

Install and run Jekyll on fresh machine

Ubuntu

# install ruby-dev
sudo apt install ruby-dev

# install bundler
sudo gem install bundler

# clone a jekyll theme
# cd to that theme

# install gems in the theme
bundle install --path vendor/bundle

# serve
bundle exec jekyll serve

# If error "ExecJS and could not find a JavaScript runtime"
sudo apt-get install nodejs

Windows

Follow this guide using WSL2 on Windows.

Make jekyll build faster

# BENCHMARKING your site
bundle exec jekyll build --profile
# clean cache
bundle exec jekyll clean
  1. Disable jekyll-feed
  2. Run bundle exec jekyll serve -I (wuth -I) to generate the changed file only. If you create a new file, open a new terminal tab and run bundle exec jekyll build.
  3. Upgrade to Jekyll 4.0.
  4. Add gem "liquid-c" to Gemfile and make bundle update
  5. Use jekyll-include-cache (both in Gemfile and _config.yml)

Read more in this article.

Disable jekyll-feed

  1. Comment line jekyll-feed in Gemfile
  2. Comment line jekyll-feed in _config.yml
  3. Rebuild.

Sitemap

If in sitemap, there is error like <loc>/reading</loc>, check your _config.yml + make sure there is an url inside url field.

Loop through posts

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

List all posts in each category,

{% for category in site.data.categories %}
  {% if site.categories[category.name].size > 0 %}
    {% for post in site.categories[category.name] %}
      {{ post.title }}
    {% endfor %}
  {% endif %}
{% endfor %}

List all posts in ABC order[ref] ,

{% assign sortedPosts = site.posts | sort: 'title' %}
{% for post in sortedPosts %}
  {{ post.title }}
{% endfor %}

List of categories and tags in a single line with commas,

{% for category in site.categories reversed %}{% capture category_name %}{{ category | first }}{% endcapture %}<a href="{{site.url}}{{site.baseurl}}/#{{category_name | replace: " ","_"}}">{{ category_name }}</a>{% if forloop.length > 1 and forloop.last != true %}, {% else %}.{% endif %}{% endfor %}
{% for tag in site.tags %}{% capture test %}{{tag[0] | slice: 0}}{% endcapture %}{% capture testup %}{{tag[0] | slice: 0 | upcase}}{% endcapture %}<a href="#{{tag[0] | slugify}}{% if test == testup %}_cap{% endif %}">{{tag[0]}}</a>{% if forloop.length > 1 and forloop.last != true %}, {% else %}.{% endif %}{% endfor %}

Edit tags for all posts

Read this source code.

Using markdown syntax inside html tags

You can use directly by

<span markdown="span"></span>
<div markdown="1"></div>

of only once,

{::options parse_block_html="true" /}
<!-- other html + markdown inside -->

Or even shorter,

Testing {::nomarkdown}**see**{:/} and test.

Check version

  • Local gems: gem list jekyll.
  • Current jekyll version of website: check Gemfile. Need to run bundle update if change any version in this file.
[Name of Link]({% post_url 2010-07-21-name-of-post %})

Edit this post on github (put below link in your post layout),

https://github.com/dinhanhthi/dinhanhthi.com/edit/master/{{path.path}}

Custom domain & repository with Jekyll sites

There are several choices for you to choose, it depends on your need.

You don’t have a custom domain

  1. Suppose your github account is <username>.
  2. Create a repo <username>.github.io.
  3. Put your site in branch master (default).
  4. Your site is published at https://<username>.github.io

If you wanna store your site in a custom repo, e.g. mysite:

  1. Create a branch gh-pages + set it as default + store your site here.
  2. Remove content at url: in _config.yml.
  3. Your site is live at https://<username>.github.io/mysite/

You have a custom domain

  1. Create file CNAME at root and put <customdomain>.com in it.
  2. Create A or CNAME record in DNS provider. Check more.
  3. You can also use netlify to set all things up automatically.

Using custom plugins?

  1. Build your site locally and get a folder _site.
  2. Put it to github and see the results.

You can also use netlify, it accepts custom plugin as well.

Using _data with include

You can use,

{% include proud-of.html data=site.data.proudof-notes %}

where there is a data file located in _data/proudof-notes.yml.

Create a custom tags/blocks

Refs

Tag with single parameter

{% render_time page rendered at: %}
page rendered at: Tue June 22 23:38:47 –0500 2010

Inside folder _plugins, create a file thi_single_tag.rb whose content is,

module Jekyll
  class RenderTimeTag < Liquid::Tag

    def initialize(tag_name, text, tokens)
      super
      @text = text
    end

    def render(context)
      "#{@text} #{Time.now}"
    end
  end
end

Liquid::Template.register_tag('render_time', Jekyll::RenderTimeTag)

Tag with two parameters

{% badge update | green %}
<span class="tbadge badge-green">update</span>

Inside folder _plugins, create a file thi_badge.rb whose content is,

class Badge < Liquid::Tag
  def initialize(tag_name, input, tokens)
    super
    @input = input
  end

  def render(context)
    # Split the input variable (omitting error checking)
    input_split = split_params(@input)
    text = input_split[0].strip
    color = input_split[1].strip

    # Write the output HTML string
    output = <<~EOS
      <span class="tbadge badge-#{color}">#{text}</span>
    EOS

    # Render it on the page by returning it
    return output;
  end

  def split_params(params)
    params.split("|")
  end
end
Liquid::Template.register_tag('badge', Badge)

Block with single parameter

For example, we wanna create a custom block alertbox using class from Bootstrap.

{% alertbox warning %}
Content
{% endalertbox %}
<div class="alert alert-warning" role="alert" markdown="1">
Content
</div>

Inside folder _plugins, create a file thi_alert.rb whose content is,

module Jekyll
  class Alertbox < Liquid::Block
    def initialize(tag_name, input, liquid_options)
      super
      @input = input.strip
    end

    def render(context)
      content = super

      case @input
      when "warning"
        box_type = 'warning'
      when "success"
        box_type = 'success'
      when "primary"
        box_type = 'primary'
      when "secondary"
        box_type = 'secondary'
      when "danger"
        box_type = 'danger'
      when "info"
        box_type = 'info'
      when "light"
        box_type = 'light'
      when "dark"
        box_type = 'dark'
      end

      output = <<~EOS
        <div class="alert alert-#{box_type}" markdown="1">
          #{content}
        </div>
      EOS
    end
  end
end

Liquid::Template.register_tag('alertbox', Jekyll::AlertBox)

Nested blocks with crossed-using variables

A more complicated example, suppose that you wanna create a hide/show box using Bootstrap’s Collapse, you can use below shortcode. Its advantage is that you don’t have to put manually the id for each box! Wonderful!

{% hsbox %}

{% hstitle %}
Box's title
{% endhstitle %}

{% hscontent %}
Box's content.
{% endhscontent %}

{% endhsbox %}
<div class="hide-show-box">
<button type="button" markdown="1" class="btn collapsed box-button" data-toggle="collapse" data-target="#box1ct">
Box's title
</button>
<div id="box1ct" markdown="1" class="collapse multi-collapse box-content">
Box's content.
</div>
</div>

Inside folder _plugins, create a file thi_hideshowbox.rb whose content is,

module Jekyll
  class HideShowBox < Liquid::Block

    def initialize(tag_name, contain, tokens)
      super
    end

    def generate_box_id(number)
      charset = Array('A'..'Z') + Array('a'..'z')
      Array.new(number) { charset.sample }.join
    end

    def render(context)
      context.stack do
        context["boxID"] = generate_box_id(20) # create the box's ID
        @content = super
      end
      "<div class=\"hide-show-box\">#{@content}</div>"
    end
  end

  class HSBtitle < Liquid::Tag
    def initialize(tag_name, contain, tokens)
      super
      @title = contain
    end

    def render(context)
      boxID = context["boxID"] # get the box's ID

      output = <<~EOS
        <button type="button" markdown="1" class="btn collapsed box-button" data-toggle="collapse" data-target="##{boxID}">#{@title}</button>
      EOS
    end
  end

  class HSBcontent < Liquid::Block
    def initialize(tag_name, contain, tokens)
      super
      @showBox = contain.strip
    end

    def render(context)
      boxID = context["boxID"] # get the box's ID
      if @showBox == 'show'
        classShow = 'show'
      else
        classShow = ''
      end
      output = <<~EOS
        <div id="#{boxID}" markdown="1" class="collapse multi-collapse box-content #{classShow}">
          #{super}
        </div>
      EOS

      output
    end
  end
end

Liquid::Template.register_tag('hsbox', Jekyll::HideShowBox)
Liquid::Template.register_tag('hstitle', Jekyll::HSBtitle)
Liquid::Template.register_tag('hscontent', Jekyll::HSBcontent)

:bulb: Actually, there is a simpler solution for this task. We can get

{% hsbox **Box's title** | show %}
Box's content.
{% endhsbox %}
<div class="hide-show-box">
<button type="button" markdown="1" class="btn collapsed box-button" data-toggle="collapse" data-target="#something">
<strong>Box's title</strong>
</button>
<div id="something" markdown="1" class="collapse multi-collapse box-content">
Box's content.
</div>
</div>

by using

module Jekyll
  class HideShowBox < Liquid::Block

    def initialize(tag_name, contain, tokens)
      super
      @input = contain
    end

    def generate_box_id(number)
      charset = Array('A'..'Z') + Array('a'..'z')
      Array.new(number) { charset.sample }.join
    end

    def render(context)
      # Split the input variable (omitting error checking)
      input_split = split_params(@input)
      title = input_split[0]
      boxid = generate_box_id(20)
      if input_split[1] != nil
        if input_split[1].strip == 'show'
          showbox = "show"
        else
          showbox = ""
        end
      else
        showbox = ""
      end
      content = super

      output = <<~EOS
        <div class="hide-show-box">
          <button type="button" markdown="1" class="btn collapsed box-button" data-toggle="collapse" data-target="##{boxid}">
            #{title}
          </button>
          <div id="#{boxid}" markdown="1" class="collapse multi-collapse box-content #{showbox}">
            #{content}
          </div>
        </div>
      EOS
    end

    def split_params(params)
      params.split("|")
    end
  end
end

Liquid::Template.register_tag('hsbox', Jekyll::HideShowBox)

Problem with kramdown

Somtimes, we cannot use markdown="1" directly in ruby file. For example, below block of code produces a block of codes (<pre>) instead of a single text,

def initialize(tag_name, input, liquid_options)
  super
  @title = input
end

def render(context)
  content = super
  output = <<~EOS
    <div class="def-box" id="dn1">
      <div class="box-title" markdown="1">
        #{@title}
      </div>
      <div class="box-content" markdown="1">
        #{content}
      </div>
    </div>
  EOS
end

Instead, we change a little bit like this,

<div class="box-title">
  <span markdown="span">#{@title}</span>
</div>

Run with draft

Inside the root folder, create a folder named _drafts. You can put your draft posts inside this folder and whenever you wanna show it in your site, use this command,

bundle exec jekyll serve --draft

In the case you have already build your site (all new posts are rendered to _site), you only changes some small things in some post and you don’t want jekyll to render again all things on your site (just look at your current post), use this,

bundle exec jekyll serve -I

Serve in background

# start
bundle exec jekyll serve 2>&1 &
bundle exec jekyll serve -I 2>&1 &
# stop
# find jekyll server process
ps -ef | grep jekyll
# substitute pid# with process id
kill -9 pid#

Using markdown syntax inside a HTML tag/block

For a block, we use markdown="1",

<div markdown="1">paragraph</div>

For a tag, we use markdown="span",

<mark markdown="span">text</span>

Add search with lunrjs

Download lunr.min.js and search.js and put them in root/js/. The newest version of lunrjs given here but I’m not sur if it works with this technique or not.

Create a file search.html in the root folder with content:

---
layout: page
title: Search on this page
---

<p class="p-intro">
  <span id="search-process">{{re_loading}}</span> {{re_result}} <span id="search-query-container" style="display: none;">{{re_forkey}} "<strong id="search-query"></strong>"</span>
</p>
<ul id="search-results"></ul>

<script type="text/javascript">
  window.data = {
    {% for post in site.posts %}
      {% if post.title %}
        {% unless post.excluded_in_search %}
          {% if added %},{% endif %}
          {% assign added = false %}
          "{{ post.url | slugify }}": {
            "id": "{{ post.url | slugify }}",
            "title": "{{ post.title | xml_escape }}",
            "categories": "{{ post.categories | join: ", " | xml_escape }}",
            "tags": "{{ post.tags | join: ", " | xml_escape }}",
            "url": " {{ post.url | xml_escape }}",
            "content": {{ post.content | strip_html | replace_regex: "[\s/\n]+"," " | strip | jsonify }}
          }
          {% assign added = true %}
        {% endunless %}
      {% endif %}
    {% endfor %}
  };
</script>
<script src="{{ site.baseurl }}/js/lunr.min.js"></script>
<script src="{{ site.baseurl }}/js/search.js"></script>

Note that, you can change some personal settings in the files search.js and search.html if you like.

Remark: if your site has so many posts, you can remove the last line ("content"....) to ignore the content from the search. You can even add “keywords” (resplace for “content”) and put that “keywords” in the frontmatter, change also the term “content” in search.js by “keywords”. That’s what I did on this site.