给 Jeklly 生成 Tag Page
给 Jeklly 博客添加 API,然后用 python 读取 API 中的 tag 信息,按固定格式生成 Tag Page。
1. Introduction
在这里,我们将一步一步介绍 Jeklly 生成 Tag Page 的过程,也能管中窥豹,深入理解 Jeklly 由 markdown 文件生成静态网页的原理。
1.1. 给 posts 添加 tags
首先,当我们在 markdown 文档中书写时,要输入头文件信息 YAML front matter
,以这篇博客为例,我们输入的 front matter 为:
---
layout: post
title: "给 Jeklly 生成 Tag Page"
date: 2021-07-23 21:00:00
tags: blog
---
其中 tags: blog
就表示我们给这篇文章设定的标签为 blog
。
1.2. 收集所有 posts 中的 tags
在 _includes
文件夹下的 tags.html
中(其实这样起名并不合适,因为根目录下还有一个同名的tags.html
文件,两者容易混淆),有如下 Liquid 代码:
{% assign rawtags = "" %}
{% for post in site.posts %}
{% assign ttags = post.tags | join:'|' | append:'|' %}
{% assign rawtags = rawtags | append:ttags %}
{% endfor %}
{% assign rawtags = rawtags | split:'|' | sort %}
{% assign site.tags = "" %}
{% for tag in rawtags %}
{% if tag != "" %}
{% if tags == "" %}
{% assign tags = tag | split:'|' %}
{% endif %}
{% unless tags contains tag %}
{% assign tags = tags | join:'|' | append:'|' | append:tag | split:'|' %}
{% endunless %}
{% endif %}
{% endfor %}
其大概意思是遍历所有发布的页面 site.posts
,然后将所有页面的 tag 都汇入 rawtags
中,然后对 rawtags
分割后排序,这样就得到了一个有序的 tag 数组。然后将数组去重合并,就得到了所有 tag 的汇总 tags
。
1.3. 生成 site.tags
我一直没搞懂
site.tags
是怎么生成的,上面的代码一顿操作猛如虎,但仔细一看只是作用在tags
上,跟site.tags
完全无关。但其他代码中也没找到相关操作,非常神奇。
但不管怎么样,在一些不明觉厉的操作下, site.tags
被搞出来了。
这个博客当前的 site.tags
长这样:
{
“blog”=>[#<Jekyll::Document _posts/2021-7-21-introduction.md collection=posts>],
“3D”=>[
#<Jekyll::Document _posts/2021-7-22-pac.md collection=posts>,
#<Jekyll::Document _posts/2021-7-22-3D-rotation.md collection=posts>],
“ML”=>[#<Jekyll::Document _posts/2021-7-22-pac.md collection=posts>],
“test”=>[#<Jekyll::Document _posts/2021-7-22-pac.md collection=posts>]
}
首先是 tag 的名称,每个 tag 都对应一个数组,里面包含了所有与该 tag 相关的 posts。
我们可以用 tag[0]
提取该 tag 的名称,用 tag[1]
提取包含该 tag 的 posts 。
1.4. 显示当前 post 的tags
我们想将当前 page 的所有 tag 都显示出来,效果如下图。
那么可以通过以下代码来实现,并且点击 tag 链接,就能跳转到对应的 tag page,其相对网址在 /tag/tag_name
,例如 ML
的跳转链接为 https://theigrams.github.io/zjblog/tag/ML。
<span>[
{% for tag in page.tags %}
{% capture tag_name %}{{ tag }}{% endcapture %}
<a href="/tag/{{ tag_name }}">
<code class="highligher-rouge"><nobr>{{ tag_name }}</nobr></code>
</a>
{% endfor %}
]</span>
2. 生成 Tag Page
接下来就进入了我们的重头戏部分,我们要给每个 tag 都生成一个对应的网页,该网页中包含了所有与该 tag 相关的文章,大致的预期效果如下:
这对于一般的网站来说是小 case,但我们这个 Jeklly 是托管在 GitHub Page 上的静态网站,不能说临时执行一个脚本来生成网页。
2.1. 解决方案
不过办法还是有的,上网搜了一圈,主要的解决方案分为 2 种:
方案一:通过插件解决,但缺点是 GitHub 不支持这类插件,要么是定义一系列 GitHub Action 来自动化操作,或者在本地生成网页文件,然后推送到 gh 分支部署。
- How do I tag posts in Jekyll? Jekyll tagging made simple. (untangled.dev)
- Jekyll 添加 tag 专属页面 | TaoAlpha’s Blog
- 在 Github Pages 中使用Jekyll插件 | TaoAlpha’s Blog
方案二:生成 tag.md
或 tag.html
,然后套用 tagpage.html
模板进行显示。
tagpage.html
代码如下:
---
layout: default
---
<div class="post other-pages">
<h1>Tag: {{ page.tag }}</h1>
<ul>
{% for post in site.tags[page.tag] %}
<li><a href="{{ site.baseurl }}{{ post.url }}">{{ post.title }}</a> ({{ post.date | date_to_string }})<br> {{ post.description }}
</li>
{% endfor %}
</ul>
</div>
<hr>
tag.md
格式如下:
---
layout: tagpage
title: "Tag: ML"
tag: ML
---
可以看到,这个方案比较简单,甚至手动写一个 md 也行,但考虑到 tag 可能会有修改,那样的话就很麻烦了,所以还是通过代码自动化生成比较便捷。
现在的话,只需给定 tag 的名称,我们就能轻松写出一个 python 程序来生成 tag.md
文件。
那么问题在于,我们如何确定需要添加的 tag 呢?网上大部分的方案是搜索当前发布的文章,然后从中提取出 tag ,最后再比较确定出需要补充的 tag.md
文件。这个方案行得通,但不够优雅,更好的方法是我们可以构建一个 API,然后直接让 python 读取 API 来确定需要的 tag。
2.2. 为 Jeklly 添加 API
Jekyll 是静态博客生成器,只能生成静态页面,原则上是没法创建一个动态的 API。但灵活利用 Liquid 的语法,可生成一个包含 JSON 数据的文件,这就创建了一个简单的静态 API。
我们新建一个 api
文件夹,然后通过如下 Liquid 代码即可得到一个关于文章信息的 API。
---
layout: null
permalink: /api/post.json
---
[
{% for post in site.posts %}{
"title": {{ post.title | jsonify }},
"url": {{ post.url | jsonify }},
"category": {{ post.category | jsonify }},
"date": {{ post.date | jsonify }},
"tags": {{ post.tags | jsonify }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
我们将该代码保存为api
文件夹下的 post.json
文件,那么就能访问该 API 了。
在浏览器中输入地址 https://theigrams.github.io/zjblog/api/post.json 即可看到相关信息。
或者用 curl 指令在终端输入也完全 OK 👌。
有了之前的知识,我们可以很轻松地写出关于 tags 的API:
---
layout: null
permalink: /api/tags.json
---
{
{% for tag in site.tags %}
{{tag[0] | jsonify}}: {{tag[1].size | jsonify}}{% unless forloop.last %},{% endunless %}
{% endfor %}
}
该 API 可以通过 https://theigrams.github.io/zjblog/api/tags.json 访问。
2.3. Python 读取 API 并生成 tag.md 文件
import requests
import os
tags_get = requests.get('http://localhost:4000/zjblog/api/tags.json')
tags = tags_get.json()
# for tag in tags:
# print(tag+':',tags[tag])
build_tags = [tag for tag in tags]
for root, dirs, files in os.walk('./tag'):
for file_name in files:
tag = file_name[:-3]
if tag in tags and tag in build_tags:
build_tags.remove(tag)
print("Building new tags:", build_tags)
tag_pattern = '---\n'
tag_pattern += 'layout: tagpage\n'
tag_pattern += 'title: "Tag: %s"\n'
tag_pattern += 'tag: %s\n'
tag_pattern += '---\n'
for tag in build_tags:
with open('./tag/'+tag+'.md', 'w') as f:
f.write(tag_pattern % (tag, tag))
代码非常简单,就不赘述了,在需要时运行一下即可。
3. References
[1] Shopify, “Liquid 模板语言中文文档,” Liquid 模板语言中文文档.
[2] “给GitHub Pages上的Jekyll站点添加标签支持,” Zero[Z.W.T] Blog, Aug. 28, 2017.
[3] L. Qian, “Jekyll Tags on Github Pages,” Long Qian, Feb. 09, 2017.
[4] “为 Jekyll 添加一个简单的 API | Fooleap’s Blog.”
[5] “Jekyll • 简单静态博客网站生成器,” Jekyll • 简单静态博客网站生成器.