ssg  
Article Series

Jekyll in General

Jekyll Plain

Where to Discuss?

Local Group

Preface

Goal: Move complexity from liquid code to ruby plugin.

Source Code

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


1: Introduction

Why Plugin?

Having some fun!

I always want my code to be clean, especially my template. Instead of putting code in template, I prefer put the code somewhere else, such as in ruby plugin.

Example Plugin

Although plugin can do something that liquid cannot do. I haven’t explore plugin that far.

Most of the example provided here, is a direct port from liquid. My code building, always start from liquid. If the liquid code tends to be spaghetti, I moved the code to ruby plugin.

Of course we need better plugin example and tutorial. But all I’ve got is based on my limited experience. I hope this introduction is enough for beginner.

All plugin files placed in _plugins directory.

$ tree _plugins
_plugins
├── hello-world.rb
├── is-show-adjacent.rb
├── pagination-links.rb
├── tag-names.rb
├── term-array.rb
├── term-keywords.rb
└── text-year.rb

0 directories, 7 files

Caveat

Not always supported

Plugin is not always supported, depends on the CI/CD. If what you have is github pages, you’d better use liquid script, and stay away from ruby plugin.

Bundling Theme

Is it just me?

I have no luck to bundle plugin with theme in Gem. So I guess, jekyll theme does not support plugin neither.

Performance

I have done testing.

AFAIK, there is no significance performance, between plugin and liquid code.

However, I still a fan of plugin anyway.

Development

You have to restart the server, for every plugin changes, to get the effect.

This is why coding in liquid is easier for beginner.

Official Documentation

Plugin Flags

I have read the documentation. I have asked my friend. But I still don’t understand what these two below, useful for for my blog.

    safe true
    priority :low

1: Tag: First Plugin

My first plugin is, of course, copy paste from someone else.

Hello World Example by Jesse Clark

module Jekyll
  class HelloWorld < Liquid::Tag

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

    def render(context)
      "Hello World, #{@text}!"
    end
  end
end

Liquid::Template.register_tag('hello', Jekyll::HelloWorld)

Jekyll: ViM: Hello World

Content: Home

---
layout    : home
---

{% hello Folks %}

Jekyll: Qutebrowser: Hello World

Official Documentation


2: Filter: Simple Text Year

Remember our archive page?

Liquid: 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 %}

Ruby: Year Text

With plugin, this could be rewritten as:

    def text_year(post_year)
      localtime = Time.now
      current_year = localtime.strftime("%Y")

      if post_year == current_year
        year_text = "This year's posts (#{post_year})"
      else
        year_text = post_year
      end

      year_text
    end

Or you can make the code to be as short and cryptic. Then we have our final ruby code below:

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

Liquid::Template.register_filter(Jekyll::TextYear)

Jekyll: ViM: By Year

Includes: By Year

Now you can use the filter in liquid code:

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

<div id="archive">
{% for year in postsByYear %}
  <p>{{ year.name | text_year }}</p>
{% endfor %}
{% assign postsByYear = 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 | text_year }}</p>

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

{% endfor %}
</div>

The liquid code looks tidy now.

Jekyll: Custom Pages: Archive by Year


3: Filter: Content: Term Array

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

Liquid: Building Array of Terms

On our early articles, we have already built, this liquid code to represent our tags and 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 %}

Ruby: Array of Terms

Sorry for my Ruby. I’m not a native speaker.

With plugin, this could be rewritten as:

    def term_array_alternate_two(terms)
      a = []
      terms.each { |term| a << term.at(0) }
      a
    end

Or shorter, just return array [].

    def term_array_alternate_one(terms)
      terms.map { |term| term.at(0) }
    end

Or even more shorter. Then we have our final ruby code below:

module Jekyll
  module TermArray
    def term_array(terms)
      terms.keys
    end
  end
end

Jekyll: ViM: Term Array

Layout: Tag List

Now you can use the filter in liquid code:

---
layout: page
---

{% assign terms = site.tags %}
{% assign term_array = terms | term_array | sort %}
{% include index/terms-badge.html %}
{% include index/terms-tree.html %}

Jekyll: Custom Pages: Tags Tree

Layout: Category List

And also apply this filter to show categories.

---
layout: page
---

{% assign terms = site.categories %}
{% assign term_array = terms | term_array | sort %}
{% include index/terms-badge.html %}
{% include index/terms-tree.html %}

4: Filter: SEO: Term Keywords

again, remember our SEO in html head?

Liquid: Merge Keywords

We require long lines, just to merge array. The problem comes, when we have to whether the array empty, or not.

{% capture spaceless %}
  <!-- keywords -->
  {% assign cats  = '' | split: '' %}
  {% assign tags  = '' | split: '' %}
  {% assign words = '' | split: '' %}
  {% assign terms = '' | split: '' %}

  {% if page.categories  %}
    {% assign cats = page.categories %}
  {% endif %}
  
  {% if page.tags  %}
    {% assign tags = page.tags %}
  {% endif %}

  {% if page.keywords  %}
    {% assign words = page.keywords %}
  {% endif %}
  
  {% assign terms = cats | concat: tags  | concat: words %}
  {% assign termssize = terms | size %}
{% endcapture %}

Ruby: Merge Keywords

Sorry for my Ruby skill.

    def term_keywords(cats, tags, keywords)
      terms = []
      terms += cats     if !cats.nil?     && !cats.empty?
      terms += tags     if !tags.nil?     && !tags.empty?
      terms += keywords if !keywords.nil? && !keywords.empty?

      terms
    end

Or shorter, just return array []. We can have our final code as below:

module Jekyll
  module TermKeywords
    def term_keywords(cats, tags, keywords)
      [cats, tags, keywords].reject{ |array| array.nil? }.flatten
    end
  end
end

Liquid::Template.register_filter(Jekyll::TermKeywords)

Jekyll: ViM: Term Keywords

I’m not sure about cryptic versus readibility. I just want to have some fun with ruby features.

Using Filter

And here we are with the good parts, using the filter in partials.

{% capture spaceless %} 
  {% assign terms = page.categories
                  | term_keywords: page.tags
                                 , page.keywords %}
  {% assign terms_size = terms | size %}
{% endcapture %}

{% if terms_size != 0 %}
  <meta name="keywords"
        content="{{ terms | join: ", " }}">
{% endif %}

This will result as below html source code.

Jekyll SEO Keywords: Qutebrowser View Source


What is Next ?

Consider continue reading [ Jekyll Plain - Plugin - Pagination ].

Thank you for reading.