Soptq

Soptq

Probably a full-stack, mainly focusing on Distributed System / Consensus / Privacy-preserving Tech etc. Decentralization is a trend, privacy must be protected.
twitter
github
bilibili

Implementing your own Jekyll plugin from scratch: Filter

What is the Filter plugin?#

The Filter plugin is the most common type of plugin in Jekyll plugins. It allows exposing an internal Ruby method to Liquid, which can be called on a page. For example, if you are reading a static webpage built using Jekyll, you may notice content like the following:

{% assign foo = 10 %}
{% foo | plus: 4 %}
{{ foo }}

This is the Liquid language. The first and second lines are surrounded by {% %} and represent expressions, while the third line is surrounded by {{ }} and represents the output of the variable. Liquid is a simple template language that can be learned in half a day. If you are interested in learning more about Liquid, you can check out the official documentation on this website: https://liquid.bootcss.com.

So, what exactly is a Filter? In the second line of the code above, plus is a Filter. It takes foo as input, adds 4 to it, and assigns the result back to foo, which is equivalent to foo = foo + 4.

What are Filters used for?#

Filters are one of the most useful types of plugins allowed in Jekyll (the first being Hooks). They have a wide range of applications, including but not limited to:

  • Calculating the difference between two dates.
  • Generating a table of contents for articles.
  • Automatically obfuscating email addresses.
  • Calculating the word count and reading time of articles.

Why do I think these can all be implemented using JavaScript?#

Indeed, theoretically, everything that can be done using Filter plugins can also be achieved using JavaScript at runtime. However, using plugins to accomplish these tasks during compilation can enhance the performance of the website in the browser. Therefore, they are still very useful.

Note! Plugins cannot be used on Pages#

GitHub Pages (or most other Pages platforms) disable plugin functionality for security reasons using the --safe option. Therefore, if your website is deployed on GitHub Pages, your plugins will not work.

However, there is still a way to publish to GitHub Pages. You just need to perform some conversions locally and upload the generated files to GitHub instead of using Jekyll.

Let's get started#

In this article, we will start with the simplest Filter and implement a Filter that calculates the word count and estimated reading time of an article. This Filter can help display the word count and estimated reading time of a webpage when displaying an article.

According to the official documentation of Jekyll, all Jekyll plugins must be stored in the _plugins folder in the project directory. So, if there is no _plugins folder in the root directory of your project, you need to manually create one. Then, create a Ruby file named jekyll_reading_time.rb in the newly created _plugins folder. Open this file and name our plugin ReadingTime.

module ReadingTime

end 

We need to expose two methods to Liquid. The first one is the count_words(html) method, which takes a webpage as input and calculates the word count of the article. The second one is the reading_time(html) method, which takes a webpage as input and calculates the approximate reading time. The reading_time(html) method is easy to implement. We just need to get the word count and divide it by the reading speed per minute, then round up to get the reading time.

module ReadingTime

	def count_words(html)
		
	end

	def reading_time(html)
		(count_words(html) / 140.0).ceil
	end

end 

To calculate the word count of a webpage, we can first use the Ruby library nokogiri to parse the html into an object. Then, recursively iterate through each html object, ignore some objects that definitely do not contain text, and check if other objects contain text. We use a private function to handle this process.

require 'nokogiri'

module ReadingTime

	def count_words(html)
		words(html)
	end

	def reading_time(html)
		(count_words(html) / 140.0).ceil
	end

	private

	def text_nodes(root)
		ignored_tags = %w[ area audio canvas code embed footer form img map math nav object pre script svg table track video ]

		texts = 0
		root.children.each { |node|
			if node.text?
		    	text_array = node.text.unpack("U*")
		    	text_array.each { |c|
		        	texts = texts + 1
		    	}
			elsif not ignored_tags.include? node.name
				texts = texts + text_nodes(node)
			end
		}
		texts
	end

	def words(html)
		fragment = Nokogiri::HTML.fragment html
		text_nodes(fragment)
	end

end 

As you can see, our plugin is almost complete. We just need to take one final step, which is to register this plugin with Jekyll.

To register our new plugin with Jekyll, we just need to call Jekyll's registration method outside the module.

require 'nokogiri'

module ReadingTime

	def count_words(html)
		words(html)
	end

	def reading_time(html)
		(count_words(html) / 140.0).ceil
	end

	private

	def text_nodes(root)
		ignored_tags = %w[ area audio canvas code embed footer form img map math nav object pre script svg table track video ]

		texts = 0
		root.children.each { |node|
			if node.text?
		    	text_array = node.text.unpack("U*")
		    	text_array.each { |c|
		        	texts = texts + 1
		    	}
			elsif not ignored_tags.include? node.name
				texts = texts + text_nodes(node)
			end
		}
		texts
	end

	def words(html)
		fragment = Nokogiri::HTML.fragment html
		text_nodes(fragment)
	end

end 

Liquid::Template.register_filter(ReadingTime)

Done! Our first plugin is now complete! In the future, when writing webpages, if you want to get the word count and estimated reading time of an article, you just need to write the following code in the HTML:

This article has {{ post.content | count_words }} words.
It will take approximately {{ post.content | reading_time }} minutes to read.
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.