路径名大小写变换导致的不可访问问题

问题描述

如果你有使用英文命名的习惯,那么在使用 Hexo 的过程中可能会遇到这样一个问题:当将文件名的大小写改变时,重新 deploy 到 git 仓库后使用路径无法访问了。

另外,导致文件名的大小写改变也可以是间接导致的。如,改变文章的 tags 或 categories 的大小写,Hexo 会为你生成与其一致的文件夹。

而我遇到的就是后面一种问题,因为我习惯将 tags 命名为英文,但是一次大小写不统一于是统一改成了首字母大写,然后就导致了上面的问题。如无法访问 https://jamhus-tao.github.io/tags/Butterfly,因为其路径应该是 https://jamhus-tao.github.io/tags/butterfly

导致原因

其实遇到这类问题我们很自然的想到是是否忽略大小写不统一,这个问题我们日常编程中也很常见,Hexo 为我们提供了 hexo clean 的命令清除本地生成的数据,但这时似乎不顶用了。

导致这个问题的并不是本地的 Hexo 出了问题,而是远程的 Github 出了问题。使用 hexo clean 可以将本地生成的文件清理的很干净,但是我们忘记了将本地分支 push 到远程时并不清除远程的上一个分支,git 是增量式记录的,它会计算与上一次提交的差异,然后将差异附加。但是 git 忽略大小写,而网页路径区分大小写,这就导致了将本地分支 push 上去之后,远程的文件名依然为 butterfly,而在生成的网页文件中的链接是 Butterfly,网页路径又区分大小写而无法访问。

解决方案

知道了问题,解决这个方案就很简单,我们只需要绕过 git 的增量式记录即可。

首先在 .deploy_git 目录下打开命令行,删除除 .git 外的所有文件后 commit 然后拉取前一个 commit 重新 commit 一遍,这样就可以强制更新所有信息。

1
2
3
4
5
6
7
8
9
10
# 先删除所有除 .git 外的文件
git tag org # 新增 tag 方便切换
git add -A
git commit -m "site cleared."
git tag new # 新增 tag 方便切换
git reset --hard org # 拉取未删除版本, 并重置工作区
git reset new # 拉取删除版本, 但不重置工作区
git add -A
git commit -m "site fixed."
git tag -d org new # 删除临时 tag

URL 变换导致的外站链接失效问题

吐槽:HEXO 的 URL 令我诟病

hexo 中,URL 生成的默认规则是 :year/:month/:day/:title,也就是说一条生成的 URL 包含发布时间和文章名称这些信息。但我个人不是很喜欢这样的规则,查阅 hexo 官方文档也发现它是可配置的:

Permalinks | Hexo 官方文档

按照我个人对于 URL 的理解,URL 除了保证唯一、持久有效外。如果它希望被频繁转发,那么它至少要是短小的,最好还是方便记忆,特征鲜明的。例如我们来评价一下下面的 URL 命名:

1
2
3
4
5
https://jamhus-tao.github.io/posts/id/175/  # 短小精干,转发方便, 看着舒服
https://jamhus-tao.github.io/posts/afe23c723fe2 # 12-hash,也很短小方便
https://jamhus-tao.github.io/posts/blog-hexo-7-bugfix/ # 方便记忆,特征鲜明,看到就知道要讲什么
https://jamhus-tao.github.io/2023/10/01/%E5%BB%BA%E7%AB%99%E7%AC%94%E8%AE%B0/Github%20+%20Hexo%20+%20Butterfly%20%E5%BB%BA%E7%AB%99%E7%AC%94%E8%AE%B0(%E4%B8%83)%20-%20%E8%A7%A3%E5%86%B3%E4%B8%80%E4%BA%9B%E9%9A%90%E5%BD%A2%E9%97%AE%E9%A2%98/
# 看到这个我浑身难受,其实这个问题在中文环境特别突出,而 hexo 的最大用户还是中文用户

hexo 事实上也提供了一些清爽的 url 规则。比如 idhash 两个选项,但是它们依然存在一些问题:id 是不可持久的,也就是你 clean 仓库之后重新生成可能原来的 id 就打乱了,那么你分享在外站链接也就全部失效了;另外 hash 是由发文时间和文章标题生成的,有时候希望改下标题原链接同样会失效。

问题重述

所以我们希望解决的问题是:如何在 hexo 内维护稳定且方便的链接,这个链接不应与任何未来可能修改的文章信息关联。

最后我们还会解决博客长期维护的历史问题:即之前使用过的链接已经更换,我需要将所有历史链接重定向到新链接。

设置长期有效链接

这里建议你将配置文件中的默认 URL 生成规则设置为 hash,因为我个人更倾向这种清爽且相对稳定的方式。具体参考:Permalinks | Hexo 官方文档

然后我们应该养成为每篇文章命名长期有效链接的习惯,每篇文章的自定义链接可以写在 YAML Front Matter 中的 permalink 字段,如果该字段被设置,它将替代默认方案。具体参考:Front-matter | Hexo 官方文档

这里有个技巧,在 scaffolds 目录下可以书写文章模板,使用 hexo new post 实际就会生成一份到 source/_posts 目录下。下面是一个示例:

1
2
3
4
title: Github + Hexo + Butterfly 建站笔记(七) - 解决一些遗留问题
date: 2023-10-01 11:55:00
published: true
permalink: posts/blog-hexo-7-bugfix/ # 设置长期有效链接

重定向历史链接

我现在希望将历史链接全部重定向到新链接,hexo 本身并不支持这个功能,这需要我们自己外挂一些脚本。首先我们需要在 YAML Front Matter 中加入 otherlink 字段,并在这个字段中加入其他链接。

1
2
3
4
5
6
7
title: Github + Hexo + Butterfly 建站笔记(七) - 解决一些遗留问题
date: 2023-10-01 11:55:00
published: true
permalink: posts/blog-hexo-7-bugfix/ # 设置长期有效链接
otherlink:
- posts/id/175/
- 2023/10/01/%E5%BB%BA%E7%AB%99%E7%AC%94%E8%AE%B0/Github%20+%20Hexo%20+%20Butterfly%20%E5%BB%BA%E7%AB%99%E7%AC%94%E8%AE%B0(%E4%B8%83)%20-%20%E8%A7%A3%E5%86%B3%E4%B8%80%E4%BA%9B%E9%9A%90%E5%BD%A2%E9%97%AE%E9%A2%98/

然后为重定向网页创建一个模板到 scaffolds/skip.html。网页跳转的非常快,用最简单的网页即可,当然你也可以魔改,做些定时跳转,但是我对 html 不是很熟悉。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script>
<!-- {:} 表示待填充的链接 -->
window.location.href = "/{:}";
</script>
<title>Redirecting...</title>
</head>
<body>
</body>
</html>

然后我们需要提供脚本,为我们生成需要的跳转网页,将它合并到博客中,它的原理我就不具体介绍了,这里我们选择使用 python 来完成这项工作,将它命名为 otherlink.py

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import os
from urllib.parse import unquote

import yaml # 安装第三方库, pip install pyyaml


PATH_POST = "source/_posts"
PATH_PUBLIC = "public"
PATH_TEMPLATE = "scaffolds/skip.html"
AVAILABLE_EXT = (".md",)

with open(PATH_TEMPLATE, "r", encoding="utf-8") as file:
template = file.read()


def check_blank_line(line: str):
return line.strip() == ""


def check_yaml_line(line: str):
return line.count("-") == len(line.strip())


def split_yaml(fp: str):
with open(fp, "r", encoding="utf-8") as _io:
while True:
_line = _io.readline()
if check_blank_line(_line):
continue
if check_yaml_line(_line):
break
return {}
_data = []
while True:
_line = _io.readline()
if len(_line) == 0:
return {}
if check_blank_line(_line):
continue
if check_yaml_line(_line):
break
_data.append(_line)
return yaml.safe_load("".join(_data))


def check_link(new_fp: str, post_link):
if post_link is None:
return True
_fp = os.path.join(new_fp, "index.html")
with open(_fp, "r", encoding="utf-8") as _io:
return post_link in _io.read() # 这个验证是不严谨的


def other_link_generate():
for _path, _, _files in os.walk(PATH_POST):
for _file in _files:
if _file.startswith(".") or not _file.endswith(AVAILABLE_EXT):
continue
_fp = os.path.join(_path, _file)
_yaml = split_yaml(_fp)
_post_link = _yaml.get("permalink", None)
_other_link = _yaml.get("otherlink", [])
if _other_link is None:
continue
if not isinstance(_other_link, list):
_other_link = [_other_link]
for _item in _other_link:
_item = unquote(_item)
_new_fp = os.path.join(PATH_PUBLIC, _item)
os.makedirs(_new_fp, exist_ok=True)
with open(os.path.join(_new_fp, "index.html"), "w", encoding="utf-8") as _io:
_io.write(template.format(_post_link))
print("Create Skip Link: {} -> {}".format(_item, unquote(_post_link)))


if __name__ == '__main__':
other_link_generate()
# os.system("hexo g -d") # 如果希望完成后自动部署

在每次部署前你需要先运行这个脚本:

1
2
hexo g && hexo d  # 原先部署命令
otherlink.py && hexo g && hexo d # 新的部署命令

当然如果你明确重定向内容没有改变,可以不执行这行命令。另外,该命令是一次执行的,也就是说不能在 hexo s 中动态更新。


为什么我的锚不起作用

问题描述

锚其实就是目录中的一个链接,它起到在同一篇文章中跳转子标题的作用,我们有时候会遇到一个麻烦:分明我的目录加了标题,为什么生成的网页无法实现跳转?

导致原因

这个问题实际上并不是 hexo 的问题,当然也不一定是你的问题,如果你经常遇到这个问题,可能是你的习惯不符合标准。当然,我好像也没听说 Markdown 有严格的规范的说法,所以我说这只是约定不统一的问题。

那么 hexo 认为的规范是什么? hexo 认为一篇文章应该只有(至多)一个一级标题,因此它不会为一级标题生成跳转链接,至于是否会在侧边栏生成标题,具体的行为还得看你的主题。

锚本质是什么? 其实锚本质就是网页中一个标签的 id 属性,现在的浏览器都支持输入 #id 来定位到特定位置。当然,hexo 也不会为你的一级标题生成它的 id

解决方案

那么如何解决这个问题,其实答案也很显然了,我们有两个解决方案:

  • 遵循 hexoMarkdown 规范,全部为你的标题添加一级。这个工作量其实不大,我们可以借助一些工具。
  • 使用外挂脚本,为缺失的标签重新生成 id,这步需要解析 html 源码,同时需要修改原文和侧边栏两个部分。

我这边选择了第一个方法,因为我们将使用的 正则表达式 可以很快的解决这个问题(OK,如果你熟悉正则表达式,看到这里就结束了)。后面我们尽量遵循 hexo 规范即可。

  1. 使用你的任何主流本地编辑器(或者 IDE)打开文章目录,然后打开替换功能。VSCode 的替换快捷键是 Ctrl+H,全局替换是 Ctrl+Shift+H;Jetbrain 的替换快捷键是 Ctrl+R,全局替换是 Ctrl+Shift+R。点击替换栏的 .* 按钮即可使用正则替换,主流编辑器应该都支持使用正则匹配替换。
  2. 输入查找栏 ^(#+? )、替换栏 #$1,注意请不要立即开始替换,因为我还将提供一些警告和备选方案。
  3. 明确警告并选择适合的备选方案后点击全部替换(Replace All),完成。

警告和备选方案↓↓↓

警告一: 你的文章可能包含一些代码注释,很多语言的注释可能是以 # 提示的,这些被包含在正则替换中。下面提供一些备选方案:

  • 你的注释 # 会被替换为 ## ,这依然符合注释,你认为这无伤大雅,直接替换即可。
  • 标题前和后通常都会换行,如果你也是严格这么做的,那么可以采取此方案。输入查找栏 ^(#+? .+)\n\n、替换栏 #$1\n\n
  • 如果你没有使用 ## 等对注释分级的习惯,那么可以采取此方案。输入查找栏 ^(#{2,}? )、替换栏 #$1,这样做可以完成二级以上标题替换,后面使用原方案逐条筛选。
  • 最坏的方案,使用原方案高亮后逐条筛选替换。

警告二: 不要直接使用全局替换,如果你的一些文章原先保有使用二级标题的习惯。你可以使用全局查找 # 定位这些包含大量一级标题的文章。


关于 Butterfly 标签外挂 label 在行首的 BUG

问题描述

问题描述: `Butterfly` 自己也说,当标签外挂中的 label 出现在行首时存在 BUG,并称这是 `hexo` 自己存在的问题。例如公式显示异常:$x^2+y^2=r^2$,引用显示异常:`Butterfly`, 以及无法正常换行 ,不过图片倒是可以正常显示。

但我就问了,为什么我有办法修好,你修不好???好吧,可能官方已经忘记要修复这个问题了。

解决方案

在每个 {% label "..." %} 前增加一个空字符就可以解决这个问题,​我问 ChatGPT ,它告诉我存在一个零宽空格 U+200B ,这样就又解决了美观问题。

1
2
3
4
5
6
7
8
&#x200B;{% label "问题描述:" %} `Butterfly` 自己也说,当标签外挂中的 label 出现在行首时存在 BUG,
并称这是 `hexo` 自己存在的问题。例如公式显示异常:$x^2+y^2=r^2$,引用显示异常:`Butterfly`

&#x200B;{% label "以及无法正常换行" %},不过图片倒是可以正常显示。

![ ](https://xx.xxx.net/xxxxx.png)

但我就问了,为什么我有办法修好,你修不好???好吧,可能官方已经忘记要修复这个问题了。

问题描述: Butterfly 自己也说,当标签外挂中的 label 出现在行首时存在 BUG,
并称这是 hexo 自己存在的问题。例如公式显示异常:x2+y2=r2x^2+y^2=r^2,引用显示异常:Butterfly

以及无法正常换行 ,不过图片倒是可以正常显示。

但我就问了,为什么我有办法修好,你修不好???好吧,可能官方已经忘记要修复这个问题了。