「Mikusa Annual Issue」作为一档主攻博客小修小补的节目,如果不体现针对博客现状具体的改进方案,继续像以前一样水的话,长久下去恐怕难以服众。我从 2019 年起使用 VOID 主题到现在也快有 5 个年头了,虽说 VOID 已经有 3 年多没有更新了,但她依旧是我用得最舒服的主题,没有之一。A 酱牛逼!只是时移事迁,我也有想要在博客上增加的小东西。往年我可以说「我并不会任何编程语言,无法自行为博客添加其他功能」,所以可以冠冕堂皇地水一点简单的东西。但今年情况不一样了。ChatGPT 等 AI 的出现,让我等不懂代码之辈也能写出代码来。
于是乎,在 ChatGPT 和 New Bing 两位老师(主要是 Bing 老师,GPT 老师收费太高了)的帮助下,本代码小白,为博客添加了如下功能。
代码框复制按钮
众所周知,初之音是一个以开箱和水文为主的生活类博客,但在博主购买 NAS 继而写了一连串 NAS 相关的教程之后,水文的代码含量急剧升高。因此,为了方便大伙一键复制这些代码,我参考「亚灿网志」关于《代码块增加复制按钮》的内容,又从别处抄了个复制图标,简单实现了这一功能。可使用过程中我才发现,亚灿的代码并不支持手机端复制。
我尝试询问了下 ChatGPT,她这么说到:
document.execCommand
兼容性:document.execCommand
在现代浏览器中逐渐被废弃,不同浏览器对其的支持也有差异。您可以考虑使用更现代的 Clipboard 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>
分别包住 JS
和 CSS
代码,就可以拥有这个复制按钮啦!
首页头图动效
首页头图动效是从贰岛博客那里抄过来的,你可以现在返回本站首页(仅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 早期的版本自带了「一言」,后来移除了。就像这样:
我想着添加回来。官网的 使用示例 是这样的:
我们假设您的网页中存在一个块级元素用于显示一言的文本内容,且我们想让它能跳转到一言的指定页面用于后续的收藏、反馈。
<!-- 请注意,以下的示例包含超链接,您可能需要手动配置样式使其不变色。如果您嫌麻烦,可以移除。 --> <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》一文中我有照猫画虎地模仿其他博主自行增加了一些表情(其实我很好奇那些代码是如何生成的),现在那些表情我用腻了,全手动再添加不仅费时费力且不符合本次 Issue 的主旨。我通过 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》一文中,我提到用上了 Copyright 插件的事。那时还在碎碎念不能直接使用 markdown 格式的链接,这次依托 New Bing,也成功为其加上了 markdown 解析的功能。虽然看不懂原理而且像是有 Bug,但至少真的能直接用 markdown 了。
具体是将这一部分代码:
$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>';
就可以像这样直接在封面出处中使用 markdown 语法的链接了。
[XilmO@夕末 / sad #Pixiv](https://www.pixiv.net/artworks/109181977)
顺便把作者那块也改成能解析 markdown 的:
$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>';
就可以像这样直接使用 markdown 语法了:
[mikusa](https://www.himiku.com)
不过测试的时候发现 Copyright 插件好像不太兼容 Typecho 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酱那种直接请求源站数据,而是自己手把手,或者说用批量程序创建出来的全新站点。只是使用了我的名字、我的文章和我的图片而已,甚至没有直接使用来自小站的链接。所以我对它们完全没有办法。
第一个 盗版初之音 是今年五月份左右在博客后台的引用中发现的,不知道 Typecho 的哪个版本更新了的这一功能。总之多亏了它自投罗网,我才能第一时间发现这一复制站点。
我确实没想到过注册「初之音」的拼音域名,只是未曾预料这种防止商标被盗用的事竟然发生在了我身上。目前为止,它总共复制粘贴了我 11 篇文章、2张照片以及4个友链。
一开始我还想着及时发表声明,但冷静下来才发现,我对它根本无计可施。声明只会占用我宝贵的博客空间,大家好奇了还会过去点两下吸引走我所剩无几的流量。再说,它复制完那些文章之后就没有下一步动作了,所以我也就没去搭理它。
就是时间一长,谷歌已经把它收录了,还排在我下面。
另一个 盗版初之音 ……怎么说呢,它用了我的落地页不说,还扒走了我的随机图,自已开了个……
我大概就是在它建站的时间点,另一个图站经历了一次不小的图片请求,直接把我机子干爆了……
对于它我也是我无可奈何,只是希望它不要用我 ID 惹事生非。Z酱说被镜像的时候我还眼红自己咋没被镜像,是不是水的不够多。等到真被复制粘贴的时候却又头痛了……
最后
虽然这次看着像是干货满满,可很多代码我都看不懂。今年也是在 AI 大模型的加持下,我才得以对小站进行了这些修改。只是没有基础的代码知识,debug 起来还是困难重重的。我猜,肯定有值得优化的地方……不知道后续出问题的话,我还能不能依靠 AI 的力量修复小站的 bug。
总之,以上就是这一年的 Issue 啦!前阵子催 Z酱更新的时候,才注意到今年还有日志没水。那么,加上这篇日志,本年度博客的 KPI 就完成啦!感谢 Z酱 ヾ(≧∇≦*)ゝ或许我应该转变思路,想办法从Z 酱手里骗到电波站才对。
但是骗到手了我也不会修bug啊,好像陷入了死循环……
嘛,矫情的话就留到以后再说吧,让我们下个 Issue 再见ヾ( ̄▽ ̄)Bye~Bye~
本文作者:mikusa
本文链接:https://www.himiku.com/archives/mikusa-annual-issue-5.html
版权声明:所有文章除特别声明外均系本人自主创作,转载及引用请联系作者,并注明出处(作者、原文链接等)。
好家伙,学习了,写的比我的好多了,直接偷走~
哈哈哈,首图左右动的,我也抄走了
拿去吧 OωO
发现一个体验上的问题:
代码高亮模块在dark mode下完全看不到下面的滚动条
还真是……这是 VOID 自己的 bug (⌐■_■)
终于能访问了,前段时间一直间歇性访问不了,攻击解决了?
哦那天啊,挨了DDoS进黑洞封了一天 (⌐■_■)
原来 GPT 这些 AI 真能帮到人啊 ,感觉太好了!
其实还是需要懂点前端代码的人给ChatGPT擦屁股的(小声)
其实还是需要懂点前端代码的人给 ChatGPT 擦屁股的(大声)
年底了,所以大家都更新博客主题了吗?
其实是特意攒到年底更的
可惜我的站点不能在今年重构完,要不然就和大家一起新年新气象了
看来是可以元旦更完了?
有点困难...
真是厉害。
我的博客也是用了同一个主题十几年了,改了也不少,想写个总结,然后diff一下源码发现已经被自己改花了,完全想不起来当时为什么那么改,然后遗留问题仍然一大堆,我甚至还想自己写一个主题算了,然后看了下教程估计了一下工作量……罢了罢了。
罢了罢了,写啥主题,还是痛快水文吧!
我之前还给博客加了个头图点击放大功能,我这周有空在这篇文章里写一下实现。
谢谢!我抄走了! ΦωΦ
阿这,连友链都搬吗,太离谱了
是吧,我也觉得离谱
关键是,为什么我的站点名字怪怪的
我上班也用gpt
上班用不到GPT ΦωΦ