「Mikusa An­nual Is­sue」作为一档主攻博客小修小补的栏目,如果不体现针对博客现状具体的改进方案,继续像以前一样水的话,长久下去恐怕难以服众。我从 2019 年起使用 VOID 主题到现在也快有 5 个年头了,虽说 VOID 已经有 3 年多没有更新了,但她依旧是我用得最舒服的主题,没有之一。A 酱牛逼!只是时移事迁,我也有想要在博客上增加的小东西。往年我可以说「我并不会任何编程语言,无法自行为博客添加其他功能」,所以可以冠冕堂皇地水一点简单的东西。但今年情况不一样了。Chat­GPT 等 AI 的出现,让我等不懂代码之辈也能写出代码来。

于是乎,在 Chat­GPT 和 New Bing 两位老师(主要是 Bing 老师,GPT 老师收费太高了)的帮助下,本代码小白,为博客添加了如下功能。

代码框复制按钮

众所周知,初之音是一个以开箱和水文为主的生活类博客,但在博主购买 NAS 继而写了一连串 NAS 相关的教程之后,水文的代码含量急剧升高。因此,为了方便大伙一键复制这些代码,我参考「亚灿网志」关于《代码块增加复制按钮》的内容,又从别处抄了个复制图标,简单实现了这一功能。可使用过程中我才发现,亚灿的代码并不支持手机端复制。

我尝试询问了下 Chat­GPT,她这么说到:

document.execCommand 兼容性:document.execCommand 在现代浏览器中逐渐被废弃,不同浏览器对其的支持也有差异。您可以考虑使用更现代的 Clip­board API 来执行复制操作。

另外,这个脚本似乎还不支持 PJAX,需要刷新页面才能使用。我又问 GPT 加上了 PJAX 重载,同时还加上了夜间模式。

以及复制提示:

总之,在同 GPT 进行多番友好而激烈的探讨之后,修缮过的代码框复制按钮的 JS 部分如下:

// 复制处理函数 function copyHandle(content, successMessage = "复制成功", errorMessage = "复制失败") { navigator.clipboard.writeText(content) .then(() => { VOID.alert(successMessage); }) .catch((error) => { console.error(errorMessage, error); VOID.alert(errorMessage); }); } // 点击事件,通过事件委托处理 function addClickListener() { $('.clipboard').on('click', function () { copyHandle($(this).next().text()); }); } // 初始化剪贴板功能 function loadClipboard() { $('pre').prepend('<div class="clipboard"><svg aria-hidden="true" role="img" class="clipboard-icon" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" style="display: inline-block; user-select: none; vertical-align: text-bottom;"><path fill-rule="evenodd" d="M5.75 1a.75.75 0 00-.75.75v3c0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75v-3a.75.75 0 00-.75-.75h-4.5zm.75 3V2.5h3V4h-3zm-2.874-.467a.75.75 0 00-.752-1.298A1.75 1.75 0 002 3.75v9.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 13.25v-9.5a1.75 1.75 0 00-.874-1.515.75.75 0 10-.752 1.298.25.25 0 01.126.217v9.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-9.5a.25.25 0 01.126-.217z"></path></svg></div>'); // 重新绑定事件处理程序 addClickListener(); } // 在页面加载时设置事件 $(document).ready(() => { loadClipboard(); });

增加了夜间模式的 CSS 部分:

/* 剪切板 */ .clipboard { position: absolute; top: 3px; right: 10px; color: #22252b; z-index: 100; text-align: center; cursor: pointer; line-height: 18px; } body.theme-dark .clipboard { color: white; }

以及最重要的 PJAX 重载函数。如果有使用 PJAX 的话,别忘了在 VOID 主题设置的「PJAX 重载函数」中填入这个函数:

loadClipboard();

你只需在 VOID 主题设置的「head 标签输出内容」中用 <script> </script><style> </style> 分别包住 JSCSS 代码,就可以拥有这个复制按钮啦!

首页头图动效

首页头图动效是从贰岛博客那里抄过来的,你可以现在返回本站首页(仅 PC 端)查看一下效果。

具体是在 VOID/includes/banner.php 最后的 <?php elseif($this->is('index')): ?> 条件判断中添加一段脚本,详细代码如下:

<?php elseif($this->is('index')): ?> <?php $title = Helper::options()->title; if($setting['indexBannerTitle']!='') $title = $setting['indexBannerTitle']; $subtitle = Helper::options()->description; if($setting['indexBannerSubtitle']!='') $subtitle = $setting['indexBannerSubtitle']; ?> <div class="banner-title index<?php if(!empty($banner)) echo ' force-normal'; ?>"> <h1 class="post-title"><span class="brand"><span><?php echo $title; ?></span></span><br><span class="subtitle"><?php echo $subtitle; ?></span></h1> </div> <!-- 首页头图动效开始 --> <script> detect = document.querySelector(".index"); banner_img = document.querySelector("#banner > img"); banner_img.style.width = "110%"; banner_img.style.left = "-5%"; detect.addEventListener("mouseenter", function(n) { this.x = n.clientX, banner_img.style.transition = "none" }); detect.addEventListener("mousemove", function(n) { this._x = n.clientX; n = 0 - (this._x - this.x) / -30; banner_img.style.transform = "translate(" + n + "px, 0px)" }); detect.addEventListener("mouseleave", function(n) { banner_img.style.transition = ".3s", banner_img.style.transform = "translate(0,0)" }); </script> <!-- 首页头图动效结束 --> <?php endif; ?> </div>

只是几天没见他又更新了,我已经抄过来了,嘿嘿。

首先是一段 css

#scrollButton { position: absolute; left: 50%; bottom: 10px; color: white; transform: translateX(-50%); } #scrollButton:hover { filter: drop-shadow(2px 5px 6px white); } #scrollButton.rotated { transform: rotateX(180deg); /* Rotates the button 180 degrees around the X-axis */ transition: transform 0.5s ease; /* Smooth transition for rotation */ } .lazy-wrap:not(.no-banner) { min-height: 0vh; filter: brightness(1); transition: min-height 0.5s ease, filter 1s ease; }

再是一段脚本,也是在 VOID/includes/banner.php 最后的 <?php elseif($this->is('index')): ?> 条件判断中添加:

<!-- 首页头图放大动效 --> <?php if (!Utils::isMobile()): ?> <div id="scrollButton" aria-label="Scroll Down"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="white" height="15px" width="15px" version="1.1" id="Layer_1" viewBox="0 0 330 330" xml:space="preserve"> <path id="XMLID_225_" d="M325.607,79.393c-5.857-5.857-15.355-5.858-21.213,0.001l-139.39,139.393L25.607,79.393 c-5.857-5.857-15.355-5.858-21.213,0.001c-5.858,5.858-5.858,15.355,0,21.213l150.004,150c2.813,2.813,6.628,4.393,10.606,4.393 s7.794-1.581,10.606-4.394l149.996-150C331.465,94.749,331.465,85.251,325.607,79.393z" /> </svg> </div> <script> document.getElementById('scrollButton').addEventListener('click', function () { var lazyWrap = document.querySelector('.lazy-wrap:not(.no-banner)'); var button = document.getElementById('scrollButton'); if (lazyWrap) { lazyWrap.style.minHeight = lazyWrap.style.minHeight === '89vh' ? '' : '89vh'; lazyWrap.style.filter = lazyWrap.style.filter === 'brightness(1.3)' ? '' : 'brightness(1.3)'; } // Toggle the 'rotated' class on the button button.classList.toggle('rotated'); }); </script> <?php endif; ?>

页脚一言

VOID 早期的版本自带了「一言」,后来移除了。就像这样:

早期的 VOID 也超好看!
早期的 VOID 也超好看!

我想着添加回来。官网的 使用示例 是这样的:

我们假设您的网页中存在一个块级元素用于显示一言的文本内容,且我们想让它能跳转到一言的指定页面用于后续的收藏、反馈。

<!-- 请注意,以下的示例包含超链接,您可能需要手动配置样式使其不变色。如果您嫌麻烦,可以移除。 --> <p id="hitokoto"> <a href="#" id="hitokoto_text">:D 获取中...</a> </p>

那我们可以在 <script></script> 中 或者 .js 文件中使用我们的接口:

<!-- 本例不能添加链接内容,放在此处只是因为此接口比较方便,也许能够解决大部分的需求--> <script src="https://v1.hitokoto.cn/?encode=js&select=%23hitokoto" defer></script>

或者使用 Fetch API

// 请注意此 Web API 的兼容性, // 不支持 IE, iOS Safari < 10.1, // 完整支持列表参考:https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API fetch('https://v1.hitokoto.cn') .then(response => response.json()) .then(data => { const hitokoto = document.querySelector('#hitokoto_text') hitokoto.href = `https://hitokoto.cn/?uuid=${data.uuid}` hitokoto.innerText = data.hitokoto }) .catch(console.error)

我最终选择了 Fetch API 的方案。但这样出现的一言没有出处,我希望的一言格式是像早期 VOID 那样带出处的格式。在询问了 New Bing 之后,这段代码改成了这样:

fetch('https://v1.hitokoto.cn') .then(response => response.json()) .then(data => { const hitokoto = document.querySelector('#hitokoto_text') hitokoto.href = `https://hitokoto.cn/?uuid=${data.uuid}` hitokoto.innerText = data.hitokoto + " —— " + "「" + data.from + "」" }) .catch(console.error)

一言的效果就会是这样:

你指尖跃动的电光,是我此生不变的信仰,唯我超电磁炮永世长存。 —— 「某科学的超电磁炮」

可这样还不够,这是一条带链接的一言,可以跳转到一言官网。说实话我不希望是这样的效果,我只要文字就够了。继续询问了 New Bing 之后,引用一言的代码改成了这样:

<p id="hitokoto"><span id="hitokoto_text">少女祈祷中...</span></p>

打开 VOID 主题文件夹,在 /VOID/includes/ 里找到 footer.php ,在「感谢陪伴」下面插入这段代码:

<p>感谢陪伴:<span id="uptime"></span></p> <p><span id="hitokoto_text">少女祈祷中...</span></p>

那既然不需要链接,原来的脚本就改成了这样:

function loadHitokoto() { fetch('https://v1.hitokoto.cn/?c=a&c=c') .then(response => response.json()) .then(data => { const hitokoto = document.querySelector('#hitokoto_text') hitokoto.innerText = data.hitokoto + " —— " + "「" + data.from + "」" }) .catch(console.error) }

这还没结束。因为我启用了 VOID 的 PJAX 功能,所以需要让这段脚本能在切换页面的时候跟着重载。同时,我还希望能在一言服务中断的时候,能输出错误提示而不是显示「少女祈祷中...」。于是在 New Bing 的帮助下,这段脚本就适配上了 PJAX

// 定义一个函数来加载一言 function loadHitokoto() { // 使用 fetch API 从一言服务器获取数据 fetch('https://v1.hitokoto.cn/?c=a&c=c') // 当响应到达时,将其解析为 JSON .then(response => response.json()) // 当数据被解析时,更新 #hitokoto_text 元素的文本 .then(data => { const hitokoto = document.querySelector('#hitokoto_text') hitokoto.innerText = data.hitokoto + " —— " + "「" + data.from + "」" }) // 如果在上述过程中出现错误,将 #hitokoto_text 元素的文本更改为错误消息 .catch(error => { console.error(error); const hitokoto = document.querySelector('#hitokoto_text') hitokoto.innerText = "一言服务失效啦~"; }); } $(document).ready(function () { loadHitokoto(); });

最后,在 VOID 主题设置中添加一言的重载函数:

loadHitokoto();

表情包脚本

关于 VOID 的表情包,在《Mikusa Yearly Issue 2》一文中我有照猫画虎地模仿其他博主自行增加了一些表情(其实我很好奇那些代码是如何生成的),现在那些表情我用腻了,全手动再添加不仅费时费力且不符合本次 Is­sue 的主旨。我通过 New Bing 写了一段脚本,把这一过程尽可能地自动化了:

# 创建一个空的ArrayList来存储结果 $jsonArray = New-Object System.Collections.ArrayList # 遍历当前目录下的所有.png文件 Get-ChildItem -Filter "*.png" | ForEach-Object { # $_ 是一个特殊变量,表示当前正在处理的对象(在这里,它表示当前正在处理的文件) # 获取当前文件的文件名(不包括扩展名) $oldName = $_.BaseName # 获取当前文件的扩展名 $extension = $_.Extension # 使用URL编码对文件名进行编码 $newName = [System.Web.HttpUtility]::UrlEncode($oldName) # 删除文件名中的% $newName = $newName.Replace("%", "") # 将文件名转换为大写 $newName = $newName.ToUpper() # 将文件名和扩展名合并,得到新的文件名 $newName = $newName + $extension # 重命名文件 Rename-Item -Path $_.Name -NewName $newName # 创建一个Hashtable来存储当前文件的信息 $jsonObject = New-Object PSObject # 添加"icon"字段到Hashtable中 $jsonObject | Add-Member -MemberType NoteProperty -Name "icon" -Value ("<img class=`"biaoqing`" data-src=`"/usr/themes/VOID/assets/libs/owo/biaoqing/mihoyo/$newName`">") # 添加"data"字段到Hashtable中,!($oldName)前面的斜杠 \ 请手动删除,会被 VOID 转义 $jsonObject | Add-Member -MemberType NoteProperty -Name "data" -Value (":\!($oldName)") # 添加"text"字段到Hashtable中 $jsonObject | Add-Member -MemberType NoteProperty -Name "text" -Value ("$oldName") # 将当前文件的信息添加到结果中 $jsonArray.Add($jsonObject) | Out-Null } # 将结果转换为JSON格式,并输出到名为owo.json的文件中 $jsonArray | ConvertTo-Json | Out-File -FilePath owo.json

现在只需手动准备好表情图片,比如上米游社扒一些藿藿。表情包文件名称最好为图中所示格式。

然后新建一个文本文档,把上面的脚本填入其中,修改 .txt 后缀为 .ps1

Win 11 的话自带终端。右键当前目录打开终端,输入

.\owo.ps1

回车后,表情文件名就自动修改成了删除 % 后的 URL 编码格式,同时生成符合 VOID 表情格式的 JSON 文件。

[ { "icon": "<img class=\"biaoqing\" data-src=\"/usr/themes/VOID/assets/libs/owo/biaoqing/mihoyo/E897BFE897BF_E5A5BDE7979B.png\">", "data": ":/!(藿藿_好痛)", "text": "藿藿_好痛" }, { "icon": "<img class=\"biaoqing\" data-src=\"/usr/themes/VOID/assets/libs/owo/biaoqing/mihoyo/E897BFE897BF_E68D8FE884B8.png\">", "data": ":/!(藿藿_捏脸)", "text": "藿藿_捏脸" }, { "icon": "<img class=\"biaoqing\" data-src=\"/usr/themes/VOID/assets/libs/owo/biaoqing/mihoyo/E897BFE897BF_E68A93E78B82.png\">", "data": ":/!(藿藿_抓狂)", "text": "藿藿_抓狂" } ]

参考《Mikusa Yearly Issue 2》中的步骤,修改表情包相关文件,就可以拥有崭新的表情包啦!!

如果能外挂表情包就好了,可惜我不知道如何就这个功能怎么向 GPT 提问。

Copyright 插件

同样是在《Mikusa Yearly Issue 2》一文中,我提到用上了 Copy­right 插件的事。那时还在碎碎念不能直接使用 mark­down 格式的链接,这次依托 New Bing,也成功为其加上了 mark­down 解析的功能。虽然看不懂原理而且像是有 Bug,但至少真的能直接用 mark­down 了。

具体是将这一部分代码:

$t_cover = '<p class="content-copyright"><strong>封面出处:</strong>' . $cr['cover'] . '</p>';

修改成这样:

$parsedCover = Typecho_Widget::widget('Widget_Abstract_Contents')->markdown($cr['cover']); $parsedCover = strip_tags($parsedCover, '<a><em><strong>'); // 保留链接和强调标签 $t_cover = '<p class="content-copyright"><strong>封面出处:</strong>' . $parsedCover . '</p>';

就可以像这样直接在封面出处中使用 mark­down 语法的链接了。

[XilmO@夕末 / sad #Pixiv](https://www.pixiv.net/artworks/109181977)

顺便把作者那块也改成能解析 mark­down 的:

$t_author = '<p class="content-copyright"><strong>本文作者:</strong>' . $cr['author'] . '</p>';

修改成这样:

$parsedAuthor = Typecho_Widget::widget('Widget_Abstract_Contents')->markdown($cr['author']); $parsedAuthor = strip_tags($parsedAuthor, '<a><em><strong>'); // 保留链接和强调标签 $t_author = '<p class="content-copyright"><strong>本文作者:</strong>' . $parsedAuthor . '</p>';

就可以像这样直接使用 mark­down 语法了:

[mikusa](https://www.himiku.com)

不过测试的时候发现 Copy­right 插件好像不太兼容 Type­cho 1.2,禁用后再启用功能会全部失效。所以不要贸然禁用插件,否则只能手动修改数据库才能启用相关功能。

但也可以手动修改插件,让它默认启用「显示原(本)文链接」和「在文章显示」两个功能。即在插件 Plugin.php 中搜索 NULL, NULL, NULL,把第一个 NULL 改成 1 就行。

$form->addInput($notice); $showURL = new Typecho_Widget_Helper_Form_Element_Checkbox('showURL', array(1 => _t('显示原(本)文链接')), 1, NULL, NULL); $form->addInput($showURL); $showOnPost = new Typecho_Widget_Helper_Form_Element_Checkbox('showOnPost', array(1 => _t('在文章显示')), 1, NULL, NULL); $form->addInput($showOnPost); $showOnPage = new Typecho_Widget_Helper_Form_Element_Checkbox('showOnPage', array(1 => _t('在独立页面显示')), NULL, NULL, NULL); $form->addInput($showOnPage);

镜像站

本来我不想写这一部分的,但怕不明真相的小伙伴们觉得我钱多没地方花买那么多域名。虽然我确实买了好些个域名。

也许我这 PHP 程序不咋会「时刻暴露在危险之中

1,即使真的有危险我也不知道如何从日志发现蛛丝马迹,但我会自搜啊!所以就真让我逮到了这么俩家伙。这俩镜像站不像 Z 酱那种直接请求源站数据,而是自己手把手,或者说用批量程序创建出来的全新站点。只是使用了我的名字、我的文章和我的图片而已,甚至没有直接使用来自小站的链接。所以我对它们完全没有办法。

第一个 盗版初之音 是今年五月份左右在博客后台的引用中发现的,不知道 Type­cho 的哪个版本更新了的这一功能。总之多亏了它自投罗网,我才能第一时间发现这一复制站点。

我确实没想到过注册「初之音」的拼音域名,只是未曾预料这种防止商标被盗用的事竟然发生在了我身上。目前为止,它总共复制粘贴了我 11 篇文章、2 张照片以及 4 个友链。

一开始我还想着及时发表声明,但冷静下来才发现,我对它根本无计可施。声明只会占用我宝贵的博客空间,大家好奇了还会过去点两下吸引走我所剩无几的流量。再说,它复制完那些文章之后就没有下一步动作了,所以我也就没去搭理它。

就是时间一长,谷歌已经把它收录了,还排在我下面。

另一个 盗版初之音 …… 怎么说呢,它用了我的落地页不说,还扒走了我的随机图,自已开了个……

我大概就是在它建站的时间点,另一个图站经历了一次不小的图片请求,直接把我机子干爆了……

对于它我也是我无可奈何,只是希望它不要用我 ID 惹事生非。Z 酱说被镜像的时候我还眼红自己咋没被镜像,是不是水的不够多。等到真被复制粘贴的时候却又头痛了……

最后

虽然这次看着像是干货满满,可很多代码我都看不懂。今年也是在 AI 大模型的加持下,我才得以对小站进行了这些修改。只是没有基础的代码知识,de­bug 起来还是困难重重的。我猜,肯定有值得优化的地方…… 不知道后续出问题的话,我还能不能依靠 AI 的力量修复小站的 bug。

总之,以上就是这一年的 Is­sue 啦!前阵子催 Z 酱更新的时候,才注意到今年还有日志没水。那么,加上这篇日志,本年度博客的 KPI 就完成啦!感谢 Z 酱 ヾ (≧∇≦*) ゝ或许我应该转变思路,想办法从 Z 酱手里骗到电波站才对。

但是骗到手了我也不会修 bug 啊,好像陷入了死循环……

嘛,矫情的话就留到以后再说吧,让我们下个 Is­sue 再见ヾ ( ̄▽ ̄) Bye~Bye~