# 前言
怎么说呢... 一开始根本没想花这么多时间去换主题的。
一开始我只是单纯想给老主题 anodyne 加一个『目录』的功能罢了...
看到一个 hexo-theme-particlex ,感觉不错。
又看到一个 hexo-theme-shoka,感觉也挺可以,Shoka 主题记得一开始是看的
再看一个 hexo-theme-icarus,感觉也不错啊 (✧◡✧)。
一顿操作后,越看越觉得自己的主题太简洁了。
对比了下几个主题,icarus 也是简洁风。而 Particlex 和 Shoka 都差不多类似类型,一开始想换成 Particlex。不过 Particlex 自定义的字体集有点大... 整个主题几十兆大半容量都在字体集上了。尝试去掉字体就变得难看起来,默认设置白茫茫地也不好看... 感觉头像 widget 还有点歪,主页文章列表内容预览截取字符没有处理标题以后的逻辑 (开始就是『前言』标题的话就空白了),而且也没有目录功能,换过去又改一次是吧?
于是对比了解了下,最终决定换 Shoka—— 感觉一定能省不少事(不会说是因为看重随机背景图片好看且方便 —— 简直是选择困难症的福音)
(虽然最后依然花了挺多时间研究去结合自己的改动)
# 过程
# 最新评论、随机文章数量修改
一开始本来不想展示这个的,后面又仔细一想:我这博客也没个评论提示,最近的留言不是正好可以当做一个提示吗?看到最近留言就可以知道谁谁加了评论 (虽然我觉得这个冷清的博客不大可能会有啥评论),不过多一个提示也是好的。
但是又感觉这个主题页面下面的随机文章和最近评论太长了:作者两者默认都是写死的 10 条,想减少一点。
随机文章还好说,直接改主题代码就行了
在 _config.shoka.yml 里边加了一个 count 的配置:
widgets: | |
# if true, will show random posts | |
random_posts: true | |
# if true, will show recent comments | |
recent_comments: true | |
count: 3 |
然后找到 widgets.njk 加入对数量参数的解析:
// 注意少了个取参的花括号,因为不知道为啥位于代码块也会被转义掉 | |
<ul class="leancloud-recent-comment count_{ theme.widgets.count }"></ul> |
然而最近评论的数量改起来就比较麻烦了,因为功能是直接写死在 MiniValine 的代码里的,没有支持自定义的功能。
如果想要减少,得改源码才行。
由于该主题的 js 是代码动态加载,并且在配置路径上说千万不要动:除非知道在干什么!
看起来,原因是作者对加速脚本做了合并处理,然后我调试了一下,感觉 MiniValine 并没有处于合并列表,相当于还是单个加载的。
于是就想着一下,fork 了一个工程,先在本地改着试试。
拉下来后,覆盖 vendors.js.valine 路径设置:
vendors: | |
js: | |
valine: /MiniValine/dist/MiniValine.min.js #gh/amehime/MiniValine@4.2.2-beta10/dist/MiniValine.min.js |
本地调试了下,感觉没什么大问题。
于是准备开改。
第一步就是编译 MiniValine ,因为没在 Readme 上看到编译方式,就自己研究了下,发现『可能』是用的 webpack。
于是尝试 npm i webpack
安装重新试,又缺失 webpack-cli
...... 一波安装下去,缺失 Python 都来了 —— 也不知道哪个依赖包需要 Python 编译。
现在这个电脑还没安装过 py,于是下载了最新版:编译确实开始了,结果又报新的错误。
npm ERR! path E:\项目\Blog\HexoBlog\MiniValine\node_modules\fibers | |
npm ERR! command failed | |
npm ERR! command C:\WINDOWS\system32\cmd.exe /d /s /c node build.js || nodejs build.js | |
npm ERR! gyp info it worked if it ends with ok | |
npm ERR! gyp info using node-gyp@3.8.0 | |
npm ERR! gyp info using node@18.12.1 | win32 | x64 | |
npm ERR! gyp ERR! configure error | |
npm ERR! gyp ERR! stack Error: Command failed: C:\Users\CWHIS\AppData\Local\Programs\Python\Python311\python.EXE -c import sys; print "%s.%s.%s" % sys.version_info[:3]; | |
npm ERR! gyp ERR! stack File "<string>", line 1 | |
npm ERR! gyp ERR! stack import sys; print "%s.%s.%s" % sys.version_info[:3]; | |
npm ERR! gyp ERR! stack ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
npm ERR! gyp ERR! stack SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)? | |
npm ERR! gyp ERR! stack | |
npm ERR! gyp ERR! stack at ChildProcess.exithandler (node:child_process:412:12) | |
npm ERR! gyp ERR! stack at ChildProcess.emit (node:events:513:28) | |
npm ERR! gyp ERR! stack at maybeClose (node:internal/child_process:1091:16) | |
npm ERR! gyp ERR! stack at ChildProcess._handle.onexit (node:internal/child_process:302:5) | |
npm ERR! gyp ERR! System Windows_NT 10.0.22621 | |
npm ERR! gyp ERR! command "D:\\Solfware\\GreenSolfware\\node-v18.12.1-win-x64\\node.exe" "E:\\项目\\Blog\\HexoBlog\\MiniValine\\node_modules\\node-gyp\\bin\\node-gyp.js" "rebuild" "--release" | |
npm ERR! gyp ERR! cwd E:\项目\Blog\HexoBlog\MiniValine\node_modules\fibers | |
npm ERR! gyp ERR! node -v v18.12.1 | |
npm ERR! gyp ERR! node-gyp -v v3.8.0 | |
npm ERR! gyp ERR! not ok | |
npm ERR! node-gyp exited with code: 1 | |
npm ERR! Please make sure you are using a supported platform and node version. If you | |
npm ERR! would like to compile fibers on this machine please make sure you have setup your | |
npm ERR! build environment-- | |
npm ERR! Windows + OS X instructions here: https://github.com/nodejs/node-gyp | |
npm ERR! Ubuntu users please run: `sudo apt-get install g++ build-essential` | |
npm ERR! RHEL users please run: `yum install gcc-c++` and `yum groupinstall 'Development Tools'` | |
npm ERR! Alpine users please run: `sudo apk add python make g++` | |
npm ERR! 'nodejs' �����ڲ����ⲿ���Ҳ���ǿ����еij��� | |
npm ERR! ���������ļ��� |
看着就像是 python 版本过高,依赖包还在用老版本的语法?
这已经是反复安装和卸载 npm 包管理尝试后了
毕竟我不是专业的... 这时候已经花了不少时间了 —— 就换个主题... 加个小功能而已,我还有更重要的东西要做,怎么能在这里费这么大功夫,干脆直接在 MiniValine.min.js
基础上修改算了。
于是打开控制台要网页调试窗口,下了个断点,很快就把 count_数量
这个配置解析成查询数量上限值了。
var recentCount = 10; | |
t && t.classList.forEach(x=>{ | |
if (x.toString().startsWith("count_")) | |
recentCount = x.toString().split("_")[1] | |
}); |
很简单的一行代码。
不过由于需要拉取工程改 MiniValine,如果有需要的话可以直接用我改好的 (... 如果真的还有人需要的话):
vendors: | |
js: | |
valine: gh/CWHISME/MiniValine/dist/MiniValine.min.js #gh/amehime/MiniValine@4.2.2-beta10/dist/MiniValine.min.js |
以及改过的 Shoka 地址。
# 标题显示开头显示成 # 号
最开始发现的是,文章目录标题不像作者那样显示 H1、H2 之类的,而是『#』号开头,比如:
对比了下作者和其它主题使用者的情况,发现是这个 markdownIt-Anchor class 的问题,手动将其改成 anchor 就正常了。
知道原因,就得找为什么会这样?
安装过程也是按照要求操作的,为何 anchor 变成了 markdownIt-Anchor?
于是先后经历了反复 安装、卸载,查询依赖包版本等等操作...... 检查得怀疑人生!
结果最后才发现:是安装说明中的 hexo-renderer-multi-markdown-it 配置没有沾下来!
主题自带的 _config.yml 并没有关于这个的默认配置,必须手动粘贴进自己的 _config.yml 然后我之前安装的时候估计漏了,然后就变成这样了... 😭
# 头像
看到作者在关于页面写的自设,并留下了一个 Picrew 的链接,点进去试了下,感觉还意外的不错。
于是把自己的上古头像也换了。
# 页顶图片添加网格蒙版
因为感觉有时候放大太糊了,参考 Lavender 的文章,增加了网格效果。
作者是直接修改的 themes\shoka\source\css\_common\outline\header\header.styl
其实在 source/_data/
目录建立一个 custom.styl
自定义样式也是可以的。
并且因为我没有把图改成全屏,所以代码有所差异:
#header { | |
&::before{ | |
background: url(/img/dot.png); | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 70vh; | |
z-index: -4; | |
background-attachment: fixed; | |
} | |
margin: 0 auto; | |
position: relative; | |
width: 100%; | |
height: 50vh; | |
text-shadow: 0rem .2rem .3rem alpha(#000, .5); | |
color: var(--header-text-color); | |
a:hover { | |
color: currentColor; | |
} | |
} |
相当于只是在 Shoka 本来基础上加了一个网格蒙版效果。
后边想着把普通 cover 也加了一个,不过降低了透明度。
说道这个,GIMP 中的颜色模式,就算黑白色也不能采用灰度,还是得 RGB 颜色才行。
想着减少大小 (其实根本没必要) 试了下感觉蒙版格子都不对味了。
# 音乐播放器
注意 QQ 音乐的链接必须带 .html 后缀,否则音乐播放器会坏掉的。
筛选使用的正则表达式规则如下:
['music.163.com.*song.*id=(\\d+)', 'netease', 'song'], | |
['music.163.com.*album.*id=(\\d+)', 'netease', 'album'], | |
['music.163.com.*artist.*id=(\\d+)', 'netease', 'artist'], | |
['music.163.com.*playlist.*id=(\\d+)', 'netease', 'playlist'], | |
['music.163.com.*discover/toplist.*id=(\\d+)', 'netease', 'playlist'], | |
['y.qq.com.*song/(\\w+).html', 'tencent', 'song'], | |
['y.qq.com.*album/(\\w+).html', 'tencent', 'album'], | |
['y.qq.com.*singer/(\\w+).html', 'tencent', 'artist'], | |
['y.qq.com.*playsquare/(\\w+).html', 'tencent', 'playlist'], | |
['y.qq.com.*playlist/(\\w+).html', 'tencent', 'playlist'], | |
['xiami.com.*song/(\\w+)', 'xiami', 'song'], | |
['xiami.com.*album/(\\w+)', 'xiami', 'album'], | |
['xiami.com.*artist/(\\w+)', 'xiami', 'artist'], | |
['xiami.com.*collect/(\\w+)', 'xiami', 'playlist'], |
# 歌单更换后错位问题
自定义的音乐,更换了音乐列表音乐需要手动清空一下 loacalStorage,因为作者为了优化,每个歌单数据拉取一次后都是直接存储本地的。
// 例如 utils.parse 返回数据 | |
meta: (3) ['tencent', 'playlist', '8733616535'] | |
// 本地有就取本地,否则解析存储本地 | |
var meta = utils.parse(raw) | |
if(meta[0]) { | |
var skey = JSON.stringify(meta) | |
var playlist = store.get(skey) | |
if(playlist) { | |
list.push.apply(list, JSON.parse(playlist)); | |
resolve(list); | |
} else { | |
fetch('https://api.i-meto.com/meting/api?server='+meta[0]+'&type='+meta[1]+'&id='+meta[2]+'&r='+ Math.random()) | |
.then(function(response) { | |
return response.json() | |
}).then(function(json) { | |
store.set(skey, JSON.stringify(json)) | |
list.push.apply(list, json); | |
resolve(list); | |
}).catch(function(ex) {}) | |
} | |
} else { | |
list.push(raw); | |
resolve(list); | |
} |
这样要是实时在另外一边更换了歌单中的歌曲就会出问题:比如歌曲错位啊、名字不对、播放失败之类的。
似乎访问一次后,歌单记录下来变不了了 (对访问者来说),因为貌似没有看到删除的地方...
# 自定义脚本执行时机问题
想把之前显示文章『多少天』前的脚本放进来,不过一时间没找到可以配置的地方。
最后还是直接改的主题代码,在 layout.njk 导入 app.js 主题代码之后添加自定义的脚本。
其中有个问题是,这个点击进去另外的页面貌似做了优化的,不会重新请求所有页面数据,因此导入脚本若是想每次页面发送改变执行,就不能直接用老方式了。
目前 Shoka 加载流程是:
- 页面初始化,绑定 DOMContentLoaded siteInit 事件
- 在该方法中绑定动态事件,例如 pjax 动态成功加载了新页面的处理 siteRefresh 函数
- 在该方法中调用一次 siteRefresh 执行一次手动刷新
- siteRefresh 中进行实际页面的刷新处理
所以关键点就在于 pjax:success 绑定的 siteRefresh 函数了。
主题里面的代码肯定是不好去改动的,于是尝试在自己导入的脚本中绑定 pjax:success 事件:
//shoka 版本 | |
const refreshDateTimeOfDay = function () { | |
$.each('time', x => { | |
if (x.innerText.indexOf("(") > -1) return; | |
var date = new Date(x.dateTime); | |
var dateNow = new Date(); | |
var dayCount = parseInt((dateNow - date) / 86400 / 1000); | |
var finalStr = dayCount == 0 ? nowStr : dayCount + dayStr; | |
x.innerText = x.innerText.concat("(", finalStr, ")"); | |
}); | |
} | |
window.addEventListener('pjax:success', refreshDateTimeOfDay) | |
refreshDateTimeOfDay(); |
试了下可以用:
那就先这样了。
引用 live2d 的方式也差不多,之前我已经完全分离了 live2d 库,只需要引用脚本就行了。
—— 不过由于 live2d 之前 ui 使用的是 FontAwesome 图标,导致显示不出来。
不可能为了 live2d 专门再导入一个 FontAwesome 库的...... 从 shoka 已有的 Iconfont 图标里找了几个替换一下吧。
# HTML 标签没有被解析
以前使用默认 markdown 渲染器的时候,因为没有修改字体颜色功能,都是直接采用 html 标签如 <font color=red> 静态对象的所有直接光照、间接光照、阴影均烘焙 </font>
这种方式实现的。
然后就发现 shoka 没有解析了,直接被当做纯文本给显示出来。
查了并试了下,是由于 hexo-renderer-multi-markdown-it 渲染器的配置问题,markdown.render.html 字段需要设置为 true—— 作者默认给出的模板是 false,所以作为纯文本显示了。
markdown: | |
render: # 渲染器设置 | |
html: false # 过滤 HTML 标签 ----- true 表示会转义 html 标签,否则作为纯文本 |
# 压缩插件问题
我这边 hexo-renderer-multi-markdown-it 自带压缩插件用起来有点问题,压缩自己的代码跟 Shoka 的竟然还产生了随机方法名的冲突,换回 hexo-all-minifier 了。
# 引入自定义脚本增加配置功能
上面直接通过直接修改主题模板引入了自定义脚本代码,不过感觉可以将这个修改转移到配置中。
于是仿照 已有的渲染方式,在 主题 _config.yml(_config.shoka.yml)
增加 customJs
配置,例如
customJs: | |
- /js/DateTimeAfeterCalc.js |
然后在 asset.js
注册方法:
hexo.extend.helper.register('_custom_js', () => { | |
const customJs = hexo.theme.config.customJs; | |
if (!customJs) return ''; | |
let str=''; | |
for(let i in customJs) | |
{ | |
str+= htmlTag('script', { src: customJs[i] },''); | |
} | |
return str; | |
}); |
最后在 layout.njk
调用:
{ _vendor_js() } | |
{ _js('app.js')} | |
{_custom_js()} |
如此后续要是还有引用自己的脚本,只需要改 _config.shoka.yml
配置,不会动到主题本身代码这边了。
# 搜索
最后是 algolia 搜索,这个没什么改的,虽然之前也没用过,安装好 hexo-algoliasearch,然后注册一个账号按照建议来就行了 —— 需要注意的是,algolia 配置必须配置到 Hexo 根目录的 _config.yml
中,如果配到了主题如 _config.shoka.yml
配置里边是不行的。
刚开始我就按照惯例配置在 _config.shoka.yml
内,结果发现没什么效果 —— 点按钮也没反应,查了代码发现发现实际是取的 config 配置。
const config = hexo.config; | |
const theme = hexo.theme.config; | |
if(config.algolia) { | |
siteConfig.search = { | |
appID : config.algolia.appId, | |
apiKey : config.algolia.apiKey, | |
indexName: config.algolia.indexName, | |
hits : theme.search.hits | |
} | |
} |
改到全局配置里就可以了。
# 总结
本来只是想:
- 为旧主题加个目录
- 想让目录 (目录同级头像不变) 可以跟随页面移动]
- 研究如何处理手机端兼容性问题
- 感觉主题不好看
- 换
- 换哪个?
- 研究 ing
- 换 Shoka 吧
- 研究 ing
- 自定义
- 研究功能如何实现的
- 解决问题
- 终于完成了,周末都过去了......
何况之前就早在看,只是周末才开始在实际动手而已,本来打算搞完周末补一下 Unity 内存知识的,现在已经是周日晚上 10 点多 周一 周二了!
花了这么多时间,算是对主题基本框架都有了个认识... 比如:
- 作者预留了很多自定义的口子:
source/_data/
目录创建对应 yml 或 styl 就可以覆盖主题本来配置和样式 (images.yml 配置也可以在这覆盖) - 动态加载功能使用 pjax 实现,自己加脚本想页面初始化执行需要绑对应事件
- 测试音乐播放器注意歌单缓存问题
- 引用自定义脚本
虽然自信下一次想改什么肯定能更快找到该改哪里,但忙活这么久,乍一看似乎像是又没改到啥的样子,还是感觉有点惨了。
处理一下收尾该睡觉了,本来总结还打算再写点什么的,算了算了。
弄完搜索和拖动问题,再看了下页面,突然又想把 cover 图片也加上一点蒙版,不过肯定需要降低透明度 —— 虽然 CSS 自带透明度调整,但是一开始我竟然意图通过编辑图片来控制...(ノД`),不熟悉的东西就是这样。另外发现使用 background-attachment
css 属性会导致在 cover 上 repeat 不正常,去掉就好了。
再看一眼老博客的样子:
最后,添加了最近评论及自定义脚本配置的: Shoka
添加解析 class='count_数量 ' 为最近评论数量功能的: MiniValine (只改了 *.min.js 那一个解析的地方)
后面再整理一下老文章的标签和分类,这次应该就真搞差不多了。
# 疑问
出现个疑问,而且没研究出来问题:指定随机的图片是如何实现的?
因为 Hexo 是静态的,只可能是后期修改。
然后却又没找到除了 engine.js
生成博客时调用 images.yml
随机图片的接口。也就是说,这个在生成时就应该已经被固定了,而且研究半天,发现动态刷新页面时查询相关接口的数据 (图片链接) 就已经变了。
所以要么这个功能是作者放在了其它的已合并的公共脚本中?但是又没发现哪里指定的 images.yml
中的配置图片值。
总不能由 Hexo 动态随机出来的吧?想不通。
经过多方面研究,破案了 —— 这东西还真是实时生成的!
测试方法是给 hexo.extend.helper
多绑一个计算时间的:
hexo.extend.helper.register('_date', function(language) { | |
return new Date().toString(); | |
}); |
然后在生成图片那给加上去:
<li class="item" data-background-image="{ image }" date="{ _date() }"></li> |
运行时,这个时间一直在变化:
说明不是静态的!Hexo 什么时候支持这种动态执行生成页面的方法了?
然而我们生成后上传的东的确又是纯静态的东西... 想不明白。
上传的东西确实变成静态的了... 这个随机图片仅限于本地调试的时候。一旦上传就全部固定了...
原来如此,还以为一直随机呢... 花这么多时间来找原因,本地图片实时随机真是让人误会。