Filter 插件是什么?

Filters 插件是 Jekyll 插件中最为常见的一种类型,它可以将一种 Ruby 的内部方法暴露给 Liquid ,最后在页面调用。比如说,如果有阅读使用 Jekyll 搭建的静态网页的话,我们可能会注意到很多形如下面的内容:

1
2
3
{% assign foo = 10 %}
{% foo | plus: 4 %}
{{ foo }}

这就是 Liquid 语言,第一行和第二行使用 {% %} 包着的是运算式,而第三行使用 {{ }} 包着的表示将抱着的变量输出。Liquid 是一种很简单的模版语言,半天就可以学完,对 Liquid 有更多兴趣的可以去这个网站查看一下官方的文档。

所以说了这么多,Filter 到底是什么呢?其实在上面的代码中的第二行,plus 就是一个 Filter,表示把 foo 作为输入,把 foo 加 4 后重新赋值给 foo ,相当于 foo = foo + 4

Filter 有什么用?

Filter 实际上或许是 Jekyll 所允许的插件中第二有用的插件类型(第一有用的插件类型应该是 Hook),其受用面非常广,包括但不限于:

  • 快捷计算两个日期之间的差距。
  • 对文章生成目录。
  • 自动混淆邮件地址。
  • 计算文章字数和阅读时间。

为什么我觉得这些都可以用 JS 实现?

确实,理论上来说所有使用 Filter 插件可以做到的事情都可以使用 javascript 在运行时完成,但如果我们使用插件来使这些事情在编译时就完成了的话,无疑可以增强网站在游览器上的性能,所以还是非常有用的。

注意!Pages 上无法使用插件

GitHub Pages ( 或者其他大部分 Pages ) 考虑到安全因素,都通过 —safe 选项禁用了插件功能。因此如果你的网站部署在 Github Pages ,那么你的插件不会工作。

不过仍然有办法发布到 GitHub Pages,你只需在本地做一些转换,并把生成好的文件上传到 Github 替代 Jekyll 就可以了。

让我们开始吧

本篇将从最简单的 Filter 开始,实现一个简单的计算文章字数和阅读时间的 Filter。这个 Filter 可以帮助网页在展示文章的同时展示字数和预计的阅读时间。

根据 Jekyll 的官方文档,所有的 Jekyll 插件都要存储在项目目录下的 _plugins 文件夹,所以如果项目根目录下没有 _plugins 文件夹的话需要先手动新建一个 _plugins 文件夹。然后我们在新建的 _plugins 文件夹中新建一个 Ruby 文件叫做 jekyll_reading_time.rb。然后打开这个文件,我们将我们的这个插件命名为 ReadingTime

1
2
3
module ReadingTime

end 

我们需要暴露给 Liquid 的方法有两个。第一个是 count_words(html) 方法:传入网页,计算文章中的字数;第二个是 reading_time(html) 方法:传入网页,计算大概的阅读时间。reading_time(html) 方法很好写,我们只需要得到有多少字数,然后除以每分钟的阅读字数,向上取整就是阅读时间了。

1
2
3
4
5
6
7
8
9
10
11
module ReadingTime

	def count_words(html)
		
	end

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

end 

为了计算网页的字数,我们可以首先使用 Ruby 库 nokogiri来解析 html 为一个对象,然后递归每一个 html 对象,忽略一些肯定没有文字的对象,对其他的对象查询其中有没有文字。我们使用私有函数来处理这个过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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 

可以看到我们的插件整体已经成型了,只差最后一步了,就是去 Jekyll 注册这个插件。

为了向 Jekyll 注册我们的新插件,我们只需要在模块外调用 Jekyll 的注册方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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)

完成了!我们的第一个插件就这样完成了!以后在编写网页的时候,如果想得到一篇文章的字数和估计的阅读时间,只需要在 html 代码中这样写就可以了:

1
2
这篇文章有 {{ post.content | count_words }} 字
大概需要 {{ post.content | reading_time }} 分钟的阅读


发现存在错别字或者事实错误?请麻烦您点击 这里 汇报。谢谢您!