抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

今日、海を見た。もう怖くない

考完研了终于可以修博客了。虽然有一万个不再用 Hexo 作为博客引擎的理由,但是在更换其他的静态博客引擎的学习成本和折腾成本面前都显得苍白无力…… Wordpress 之流又用起来比较难受,所以只能继续用了啊()

总的来说,我的修理博客大概包含了三个方面的工作:

基本上也都完成了;总结一下做的工作和做的一些事情,以供参考。

图片存储

最开始想着用腾讯云的,毕竟又有看起来比较通俗易懂也令人安心的成本控制,还是大厂;但是最后还是又拍云了,原因有二 ——

  • 我很懒;腾讯云支持 rclone 进行备份,但是配置 rclone 需要学习,很麻烦;又拍云也给了一个备份工具 upx 勉强能用
  • 再怎么说,又拍云可以白嫖()至少启动的成本比较低

所以就是写了个脚本,匹配博客里所有图片的 URL,然后把所有是图片 URL 的下载下来批量上传又拍云了。遇到了一个问题,就是新版 sm.ms 的图片似乎在浏览器直接访问(可能是没有 Referer?)的情况下会跳转到图片详情页面,导致用 axios 会爬一个 HTML 下来,很愚蠢 —— 但是当时我也摆了,所以就手动保存剩下的图片了,只能说旗鼓相当== 毕竟能不要写代码就不要写代码嘛()

此外,又拍云开境外加速好像有一点贵,所以没有开 —— 这样的问题就是图片只能在大陆访问了;不过本来人就在境内,博客本身放在境外也只是为了省钱而已,不寒碜==

使用方案

图片只存一处那肯定不太安心;最好就是一边备份到 OneDrive 这种成本比较低,又比较安全,甚至有的时候还可以搞七搞八的地方;我就是使用又拍云提供的 upx,虽然它没有提供真正意义上的同步功能,但是基本的批量上传和下载还是可以的 —— 于是就在终端里设置了一个把又拍云全桶下载到某个 OneDrive 同步的文件夹里的 alias设置定期执行定期手动执行()

既然命令行只备份,那么上传就得通过别的方法 —— 不过反正有 PicGo 这种方便的东西,倒是也不用自己搭图床,又麻烦又不怎么安全==

评论系统

之前用的是 Valine,但是现在新版 Volantis 直接不支持了,也行()Twikoo 之前也用过,不错,但是一方面云开发收费了,另一方面也看到了有人的 Twikoo 被刷,而且甚至会导致 Vercel 封号…… 还挺搞人的,,于是直接摆烂选择 Github 系,完全放弃国内的可访问性,也不管游客了==

最后选择的是基于最新最热的 Github Discussion 的 Giscus;它提供了一个 Bootstrap 网站,可以根据他的指引一步步来,也可以私有部署不过我觉得没啥必要就是,它的公共部署似乎也是在 Vercel 上,访问的多所以应该总是在前台,不会有冷启动的损失。

Giscus 似乎是个云函数;如果有可能,说不定也可以自己部署来加速;不过那就是之后的故事了。

修改主题

现在 Hexo 已经迈进了 6.x 版本,在之前还算比较罕见的通过包引入主题现在已经是常规操作;所以最好的方法就是把之前整的 hexo-theme-ymd45921 改改,然后发个包。

最新的 Volantis 是 6.x 版本了,但是删除了我个人感觉不能没有的 Pjax —— 仅保留了一个不再更新的包含 Pjax 功能的分支;可能是因为现在的新活比较多,不支持 Pjax 的插件越来越多了吧,毕竟我好像还看到什么博客内置 GPT 的插件呢(笑)只能说 ChatGPT API 开放后,确实已经成为了目前最大的前端训练场,还挺…… 符合一些认知的()

所以修改主题也只能基于这个还有 Pjax 的分支;浅浅看了一下新加的东西,确实也没啥感兴趣的功能,硬要说之前的主题也是完全能用的()所以也没啥问题。

开发

先在主题仓库下运行 yarn link 以暂时将它注册为全局可用的依赖;然后回到博客仓库目录下,运行 yarn link hexo-theme-ymd45921,就将这个依赖暂时关联到博客项目,就可以边开发边看效果了;之前那样每次测试都要发一个版本实在是有点谔谔……

为了适应旧版本,fetch 新版本之后开发之前除了要处理配置文件的冲突之外,还要引入一些已经被新版本废弃的资源,比如 Font Awesome 5 Pro;虽然现在有 6 了,盗版也满天飞啊,但是反正博客用 5 也绰绰有余,还省的自己布置静态资源。

发布

本身发布很简单,就是 yarn publish 完事了;但是由于 ymd45921 这个用户名实在是过于钓鱼,怎么看怎么像机器操作随机生成的,所以用这个用户名命名的主题 hexo-theme-ymd45921 被 npm 识别成垃圾了== 本来想着发布到用户的命名空间下吧,结果 hexo 还找不到 @ 开头的包…… 明明是渲染器就能找到的…… 所以只好改名字了。

因为是 Volantis,然后又是自用自改,在种子站上又能经常看到同义词“自炊”,所以干脆就叫“自炊 Volantis”了()反正也没打算给很多人用,名字什么的无所谓啦(

功能修改

我认为对博客的修改包括两大类:一种是会影响功能的,一种只是修改样式;虽然说后者是推荐在自己的博客仓库里搞一个样式表然后外挂嘛,但是毕竟在写主题,有些东西明显比原来更好或者是原来存在一些问题的,直接在主题改改就完事了。

友链格式

我一直觉得 Volantis 的友链格式是比较奇怪的 —— 只有一个 title,但是网站名和用户名还是有着较大差距,所以就自己动手加上了对 author 的展示;因为我没想好 Volantis 的 traditional 友链视图应该改成怎么样,所以只改了 simple 视图。

友链的视图出现在 friends.ejs 中:

1
2
3
4
5
6
7
8
9
<% if (item.url && (item.title || item.author)) { %>
<a class="simpleuser" target="_blank" rel="external noopener noreferrer" href="<%- url_for(item.url || '/') %>">
<img src="<%- item.avatar || (theme.plugins.lazyload && theme.plugins.lazyload.loadingImg) %>"/>
<span class="friend-site-title"><%- item.title || (item.author + '的网站') %></span>
<% if (item.title && item.author) { %>
<span class="friend-name"><%- item.author %></span>
<% } %>
</a>
<% } %>

为了保留对 Volantis 的兼容,新加的 author 并不是必须的属性;只有在判断存在 author 的时候才使用新的模板其实也就是在原来的标题下显示一行小字;再加上对应的样式就行了。

数学公式渲染

曾经,我还是使用 pandoc 作为 Hexo 的渲染器的小混子 —— 其实 pandoc 还挺好的,它本身渲染 Markdown 就支持了数学公式(而且支持的很好),根本不用像其他的渲染器一样还得装数学插件然后搞七搞八…… 但是这样就没法用 Vercel 搞 CI/CD 了,因为后者不能安装 pandoc 的二进制,而这是 Hexo 的那个渲染器所必需的。换当然也有这之外的动力 —— 毕竟新电脑上也没有 pandoc,虽然装起来也不麻烦就是了……

那么用其他的渲染器,就会面临数学公式渲染出错的问题;这也算是 MathJax 的老毛病了,因为它是个文件引入嘛,所以你文章里的公式就会被原模原样地复制到最终的 HTML 文件里,当用户浏览器加载了这个网页的时候才会进行渲染;但是这样就会面临一个问题 —— Markdown 渲染本身也是有一些转义符的,这些公式在复制的过程中其实或多或少都受到了影响 ——

毕竟作为一个拓展标准,实际上并没有人规定 Markdown 的公式块里的公式的 \ 是否应该再被转义一次(至少我不知道);虽然看起来并不应该转义,毕竟嘛,每个命令都要加两个 \ 实在是太愚蠢了对吧()但是因为没有规定,所以怎么实现都算是实现了…… 嗯==

使用 KaTeX

当时在选择 pandoc 作为渲染器之前就试过使用 KaTeX,虽然它确实被广泛认为比 MathJax 快,而且明确地支持服务端渲染,所以似乎也没有转义的问题,但是它支持的不够完善 —— 比如不支持把 \and\or 翻译成逻辑运算符,然后没有 align 但是却有完全一致的 aligned

精心挑选了看起来比较好懂的渲染器 markdown-it,它也有比较浅显易懂的 KaTeX 插件;简单阅读后发现是识别 $...$$$...$$ 内的文字后,调用 KaTeX 的函数得到渲染结果 —— HTML 格式或者是 MathML 格式的,不是 svg 这种高级东西。最简单的思路就是在渲染器处理完文法得到待渲染公式的时候,先手动进行一些替换再交给 KaTeX —— 比如 align

1
2
3
token.content = token.content
        .replace('\\begin\{align\}', '\\begin\{aligned\}')
        .replace('\\end\{align\}', '\\end\{aligned\}')

已经解决了最令人头疼的 align 问题了;又看了看 KaTeX 的官方文档,还可以手动增加命令宏 —— 这样就可以解决剩下的问题了,善哉善哉;把修改后的渲染器打个包发布,就可以用起来了;不过话说回来 KaTeX 不支持的符号也实在是太多了…… 还听说 MathJax 3 的渲染性能大有改进,不会是五零年入国军把,,

最后……

总的来说,就是先卸载已有的渲染器,换成七海特制渲染器:

1
yarn add @kohaku/hexo-renderer-markdown-it

这里 Hexo 明明就可以跨过 @ 找到渲染器,为什么主题就不能呢()

KaTeX 有两种渲染方式 —— 渲染为 HTML 和 MathML;前者肯定是广泛支持,但是需要设置样式辅助;需要引入它的样式表:

1
<link rel="stylesheet" href="https://unpkg.com/katex@0.16.7/dist/katex.min.css">

后者则是通过 <math> 块,而这是一个新的特性,支持情况如下:

math的浏览器支持情况

虽然我还是更喜欢 MathML 的样式,而且实际上 MathML 渲染更快,但是为了可恶的兼容性……

因为块状的公式可能会超长,为了在这种时候能够左右滚动查看公式,可能需要为公式块增加滚动样式;我觉得 Volantis 应该本身就做了这个,不需要手动加,但是以防万一提一嘴;KaTeX 会把渲染结果丢进 .katex-block 中,所以你需要为它设置样式 overflow-x: auto

复制到剪贴板

新版的 Volantis 不再使用丑陋的 document.execCommand() 或者是麻烦的外部库复制到剪贴板,而是使用浏览器内建的 navigator.clipboard 对象 —— 但由于浏览器新的安全策略,它只在 HTTPS 这种安全的环境下才能工作;~~没有搞 HTTPS 的人有难了!~~但是因为也确实还遇到了 SSL 证书的相关问题,这种时候用不了还挺麻烦的,所以想着要改成失败的时候 Fallback 丑办法:

1
2
3
4
5
6
7
8
9
10
11
12
// 使用 document.execCommand('copy') 的蠢办法
const fallbackDocumentExecCommandCopy = str => {...};
// 复制到剪贴板调用的函数内
// ...
if (!navigator.clipboard) {
try {
let result = fallbackDocumentExecCommandCopy(str);
if (!result || result === 'unsuccessful')
return Promise.reject();
else return Promise.resolve();
} catch (e) { return Promise.reject(e); }
}

这样,在不安全的环境下也可以复制文本了,善 —— 虽然对我来说,不安全环境的场合也只有本地就是了(通过绑定了局域网 IP 的域名访问开发服务器)

右键音乐播放器

虽然不管是示例博客还是使用了这个主题的其他博客,甚至还是我的博客怀旧服,右键音乐播放器总归是会正常显示的;但是在更新了新版本主题代码之后,它却时常缺席,怎么回事捏?

可以大概确定,每次页面加载的时候,右键菜单会确认一次 APlayer 是否装载,如果没有装载的话就不显示;这样就有一个显而易见的解释 —— 右键菜单确认的时候如果 APlayer 确实没有加载好,那么就会导致右键音乐播放器一直不显示。

跳转到 aplayer.js:整个文件就声明了一个 RightMenuAplayer 对象,然后将它冻结,并且最后调用 requestAnimationFrame 方法注册更新状态的函数 checkAPlayer —— 就是这个函数更新了 APlayer 的装载状态 —— 但是却只注册了一次,然而:

备注: 若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用 requestAnimationFrame()requestAnimationFrame() 是一次性的。

只更新一次那确实就不太对了,但是真要让这玩意每秒执行六十次也挺脑残的== 最后的解决方案是在右键菜单的检查函数里增加了一次调用 —— 在检测到 APlayer 装载之前:

1
2
3
4
5
6
7
8
9
10
11
if (volantis.GLOBAL_CONFIG.plugins.aplayer?.enable) {
if (typeof RightMenuAplayer !== 'undefined') {
RightMenuAplayer.checkAPlayer();
if (RightMenuAplayer.APlayer.player !== undefined
&& (rightMenuConfig.options.musicAlwaysShow
|| RightMenuAplayer.APlayer.status === 'play'
|| RightMenuAplayer.APlayer.status === 'undefined')) {
globalData.isShowMusic = true;
}
}
}

这样确实就正常了 —— 但是看了一下 Volantis 的 Commit Message,这个文件在那之后也并没有什么改动…… 之前的版本都是这么写的啊不都没事,为啥会出这个问题呢()

各种美化

感觉现在已经几乎习惯了 Stylus 的写法了;而且因为 Hexo 实际上是一个静态服务器,但是加上了很多的 Loader 插件,对不同的文件经过中间件(渲染器)转化后再发给浏览器/保存为文件;所以实际上博客仓库里的外挂的样式表也可以是 .styl 格式的。整挺好。

导航栏暗黑替代图标

一种很直接的方法就是在导航栏里塞进两个图标,但是用 CSS 控制它们的显示;为了保留兼容性,会先检测配置文件是不是字符串;如果是就保留 Volantis 的行为,不是就按照上述方法进行。

首先打开导航栏所在的 header.ejs 并作出如下的修改:

1
2
3
4
5
6
7
8
<% if (logo.img) { %>
<% if (typeof logo.img === 'string') { %>
<img no-lazy class='logo' src='<%- logo.img %>'/>
<% } else if (logo.img.light && logo.img.dark) { %>
<img no-lazy class='logo-light' src='<%- logo.img.light %>'/>
<img no-lazy class='logo-dark' src='<%- logo.img.dark %>'/>
<% } %>
<% } %>

并在 CSS 中设置要隐藏的 Logo 的样式为 display:none,将要显示的 Logo 样式设置为 display:block 即可。

导航栏的收起动画

在 Volantis 还叫 Material-X 的时期就有的 Feature,但是改名后就被删掉了;只需要从之前的老代码里抄过来就可以了:

1
2
3
4
5
6
.l_header.auto.show
transform: translateY(0) scale(1)
.l_header.auto
transition: all .35s ease
transform: translateY(-100%)
visibility: hidden

狠狠地文艺复兴!

右键播放器的音量条

Volantis 的右键播放器的音量条两侧的两个音量图标总是一个颜色,这样当主题色是深色的时候看的很不清楚;理想是随着音量逐渐调大,左右侧的两个喇叭图标由深色变成浅色;变色的操作应该是由 JS 控制的,并且只会在音量改变时发生,所以只需要找到修改音量的回调并修改就行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn.onUpdateAPlayerVolume = () => {
try {
const volume = APlayer.player.audio.volume,
left = APlayer.volumeBar.children[1],
right = APlayer.volumeBar.children[2];
APlayer.volumeBar.children[0].style.width = volume * 100 + '%';
// 这里可以修改音量条两侧的图标属性!设置阈值,目标类 filled
const threshold = [0.15, 0.9], tr = 'filled'
if (volume > threshold[0]) left.classList.add(tr);
else left.classList.remove(tr);
if (volume > threshold[1]) right.classList.add(tr);
else right.classList.remove(tr);
} catch (error) {
console.log(error);
}
}

我们设定:当填充到 15% 时左侧图标变色,填充到 90% 时右侧图标变色;并且指定变色后的图标具有 filled 类;然后再修改 CSS,为 i.filled 增加新的颜色。搞定!

首页大图切换动画

现在 Volantis 用的已经不是 jquery-parallax 了,而是自己写了一个;本来也没啥,但是它切换图片的实现十分迫真 —— JS 分成一些步骤,每次透明度加一点 —— 这本来也没什么,但是它的步骤实在是太少了,所以看起来就一卡一卡很抽象()

加步数是不可能的,这辈子都不可能的,看着都丑;而要想要流畅的动画,一般都会想到 CSS 的 transition 属性;所以一种可能的解决方法是把步数改成一步,再增加一个 transition-duration 并让它等于配置文件中设置的时间的透明度渐变就行。

1
2
3
4
5
if (opac !== 1) 
if (Parallax.mirrors.length >= 2) {
slider.style.opacity = opac = 1;
setTimeout(Parallax.slidein, Parallax.options.fade);
} else slider.style.opacity = 1;

这个本来会执行步数次数的函数现在只会执行两次:一次修改透明度为 1,一次清除旧的图片。

1
2
img.parallax-slider
transition: opacity linear unit(hexo-config('plugins.parallax.fade'), ms)

利用 hexo-renderer-stylus 提供的 Helper 读取配置文件中的时间,并直接用于样式中。

但是这样也有一个小问题:不同浏览器对 CSS 动画的处理也不仅相同,比如 Firefox 似乎就会在窗口在后台是不播放这个动画,但是比起它流畅起来的样子,这些都是小问题==

其他

在维修博客的过程中遇到的一些杂七杂八。

SSL 证书的问题

又拍云自动申请图床的 SSL 证书却总是失败,不知道什么毛病,,,发了工单说是域名有 CAA 记录但是显然我上 DNSPod 完全都查不到,真是令人费解。本来还以为是又拍云草台班子,但是当我上腾讯云申请证书的时候却同样不行,也说是 CAA,怎么回事呢?

CAA(Certification Authority Authorization)记录是一种 DNS 记录类型,它允许域名所有者指定哪些证书颁发机构(CA)有权颁发 SSL/TLS 证书。它的作用是提高 SSL/TLS 证书的安全性和可信度,防止不良的证书颁发机构颁发伪造的证书,从而保护网站的安全。

通过 CAA 记录,域名所有者可以限制哪些 CA 可以颁发 SSL/TLS 证书,如果不在指定的 CA 列表中,那么这个 CA 就无法颁发证书。这样可以降低 SSL/TLS 证书被恶意颁发的风险,提高证书的可信度。

要解释原因,首先要说明 CAA 记录的查询规则;当我们要为域名 xx.example.com 申请证书时,我们申请的 CA 会:

  • 查询该域名是否具有 CAA 记录
  • 如果没有,但是域名指向其它 CNAME 记录,则查询该 CNAME 的 CAA 记录
  • 如果没有,则查询 example.com 的 CAA 记录
  • 同样遵循上述规则:如果没有但是 CNAME,就查询该 CNAME 的 CAA 记录

如果最后还是没有查询到 CAA 记录,就说明没有限制,CA 可以颁发证书;否则,则只允许查询到的 CA 办法证书,CA 会比对自己的信息然后好自为之 —— 结果就是颁发失败。

而之前我的博客是 CNAME 指向 Github 的,而听说 Github 遵循了最新最热的建议,设置了 CAA 记录;根据上面的查询规则,当我为图床申请证书的时候,会在第四步查询到 Github 设置的 CAA 记录:显然,Github 是不会允许免费证书 CA 为其颁发证书的。

于是就有两种解决方法:

  • 在申请证书的时候,暂时关停对 Github 的 CNAME 解析
  • 为自己的域名增加允许免费 CA 的 CAA 记录

前者十分方便有效,一关秒好 —— 也不会再开了,现在博客搬到 Vercel 上了;后者需要查询我们要申请的免费 CA 的记录值,一般也就是亚洲诚信或者是 Let’s Encrypt,像是腾讯云这种云服务提供商一般会提供。

顺便一提,Vercel 自动生成的 SSL 证书是 Let’s Encrypt 的;但 Let’s Encrypt 单域名下的证书的申请是有上线的,每周五十个;虽然几乎不会用到上限,但是由于并不知道 Vercel 内部是怎么实现的,有的时候它生成证书也会失败 —— 不过一般等等就好了虽然我等了二十几天

改善新建文章的体验

虽然说 Hexo 文章本身就是 Markdown 文件,随便用 Markdown 编辑器创建文件就可以了;但是在 Hexo 中有文章模板,使用命令行创建文章会自动地填写一些 Front-matter;但是命令行只能创建文章而不能打开已有的文章,在这之后在 _posts 文件夹里找文章还是挺抽象的== 如果有一个命令在没有一个文件的时候创建文件,有文件的时候自动打开就好了——

观察到每次新建文件后会控制台输出新建的 Markdown 文件的路径,所以写了下面的脚本:它会再调用 hexo new 之前检查文件是否存在,并在存在的时候直接使用 Markdown 编辑器打开。

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
const process = require('process');
const path = require('path');
const fs = require('fs');
const { exec, execSync } = require('child_process');

const hexoNew = (scaffold, title) =>
execSync(`hexo new ${scaffold} ${title}`).toString();
const simulateOutput = path =>
`\x1B[32mINFO \x1B[39m Created: \x1B[35m${path}\x1B[39m`;
const fileShouldBeCreated = (scaffold, title) => {
switch (scaffold) {
case 'page': return path.join(process.cwd(), 'source', title, 'index.md');
case 'draft': return path.join(process.cwd(), 'source', '_drafts', title + '.md');
default: return path.join(process.cwd(), 'source', '_posts', title + '.md');
}
}
const tryNewFile = (scaffold, title) => {
const dir = fileShouldBeCreated(scaffold, title);
if (fs.existsSync(dir)) return simulateOutput(dir);
else return hexoNew(scaffold, title);
}
(() => {
if (process.argv.length < 4) return;
const scaffold = process.argv[2], title = process.argv[3];
const content = tryNewFile(scaffold, title);
const lines = content.split('\n').reverse(), head = '\x1B[32mINFO \x1B[39m Created: \x1B[35m';
let pathMd = ''
for (const line of lines)
if (line.startsWith(head)) {
pathMd = line.replace(head, '').replace('\x1B[39m', '').trim();
break;
}
if (pathMd !== '') {
exec(`typora ${pathMd}`);
}
})()

将该脚本保存至一个文件,然后再在 package.json 里的 scripts 栏目加入一项以方便地使用它:

1
"open": "node ./helpers/new-or-open.js",

这样就可以通过 yarn open 来调用该脚本了;紧随其后的所有参数都会被传递给该脚本。

后记

博客修好了就该写文章了!真的该动手了!

这篇文章甚至都是在博客修好前就列好小标题了,却一直拖到星奏的生日才想起来补好……

可能还是存在小问题,比如手机版 Firefox 限定的圆角矩形渲染错误(这 jb 怎么修),又比如在各种手机上 Pjax 存在延迟(从 click 事件到 pjax:send 事件之间存在随着网页打开时间而增加的延迟,感觉无从下手),但是不管了……

评论