上一个修补琐记太古早了,再新建一个占个坑
2026/2/28: 真の缓存 & 一些小修小补
终于有时间倒腾一下小站了,把友链抽奖机的图片改成真·缓存,并且上传了近几次比赛写了的WP
原来的缓存是D老师写的,原理是预先访问一次,然后浏览器进行“缓存”
后来想起来为什么不用blob呢,于是就写了一个真·缓存,大致如下
1 2 3 4
| const response = await fetch(friend.avatar); const blob = await response.blob(); const cachedURL = URL.createObjectURL(blob); friend.avatar = cachedURL;
|
同时更新/修正了关于页的内容和样式,在友链抽奖机中加入了求友链的说明
2026/4/13: 友链++ & 小修小补 & 一个决定?
今天加了不少友链,开心~
对图片缓存逻辑进行了一点小修,try catch了一下url不支持CORS的情况,退回到浏览器自带缓存机制
以及决定择期上线一个每咕一题页面,闲的没事可以搓几道题史给大家尝尝
2026/4/17: 每咕一题调试完成上线
经过几天高强度的手搓代码,终于,每咕一题完成上线了!

本次代码保证纯手工打造,100%原汁原味(bushi
好了,其实还是让D老师帮忙写了一下HistoryAPI有关的代码的,这个东西我实在是不想吐槽了
下面展示一些技术细节
Tag组开关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| document.querySelectorAll('#index-page .tag').forEach(e => e.addEventListener("click", evt => { if (![...evt.srcElement.parentElement.children].filter(elem => elem.classList.contains('deactive')).length) { [...evt.srcElement.parentElement.children].forEach(elem => elem.classList.add("deactive")); } evt.srcElement.classList.toggle("deactive"); if (![...evt.srcElement.parentElement.children].filter(elem => !elem.classList.contains('deactive')).length) { [...evt.srcElement.parentElement.children].forEach(elem => elem.classList.remove("deactive")); } pagecount = 0; renderProblemList() }));
|
筛选功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function doSearch() { return Object.entries(problems).filter(kv => { let [pid, item] = kv; if (document.getElementById("search").value && !(item.title + item.desc.replace(/<.*?>/g, '')).toLowerCase().includes(document.getElementById("search").value.toLowerCase())) { return false; } if (![...document.querySelectorAll("#solve-tags .tag:not(.deactive)")].map(e => e.innerText).includes(!(solveData['solved'][pid] || []).length ? '未解出' : (solveData['solved'][pid].length == item['flag'] ? '已解出' : '部分解出'))) { return false; } if (!item["tags"].filter(t => [...document.querySelectorAll("#category-tags .tag:not(.deactive)")].map(e => e.innerText).includes(t)).length) { return false; } if (!item["tags"].filter(t => [...document.querySelectorAll("#desc-tags .tag:not(.deactive)")].map(e => e.innerText).includes(t)).length) { return false; } return true; }); }
|
强兼博客代码样式
为了渲染WP中的内容(尤其是代码),我试了不少方式,最后还是决定手动解决WP渲染,就只剩下了一个code的highlight
经过搜索,我发现hexo的默认渲染引擎是highlight.js,并且默认移除hljs-前缀
我懒得修改hexo配置,于是就写了一段代码移除hljs-前缀,就可以使用博客主题分亮暗主题高亮了~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function removeHljsPrefix(elem) { elem.classList.forEach(i => { if (i.startsWith('hljs-')) { elem.classList.replace(i, i.slice(5)); } }); [...elem.children].forEach(sub => removeHljsPrefix(sub)); }
hljs.highlightAll(); setTimeout(() => document.querySelectorAll('pre code').forEach( elem => { elem.classList.add("code"); elem.parentElement.classList.add("highlight"); removeHljsPrefix(elem); } ), 10)
|
hook <details> & <a>
为了在打开折叠框/下载黑盒文件前弹窗,对这两个元素进行了click事件的监听
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
| function hookDetailsOpen(elem) { elem.addEventListener("toggle", async (evt) => { if (elem.open && (!elem.dataset.confirmed || elem.dataset.confirmed == 'false')) { elem.open = false; if (await showConfirm('折叠框内含有与解题思路相关度较高的内容,您确定要展开吗?', 'Spoiler Alert!')) { elem.dataset.confirmed = true; let arr = solveData['confirmed'][currentDisplayedPid] || []; arr.push(elem.dataset.confirmId); solveData['confirmed'][currentDisplayedPid] = arr; saveData(); elem.open = true; } } }) }
document.querySelectorAll("#problem-page details").forEach(elem => hookDetailsOpen(elem)); document.querySelectorAll("a[download][data-blackbox=true]").forEach(e => e.addEventListener("click", evt => { evt.preventDefault(); showAlert('这是一个黑盒附件,请勿在解题时阅读其中内容!', '黑盒附件提醒', '!!', '我知道了', { danger: true }).then(() => { const tempA = document.createElement('a'); tempA.href = evt.srcElement.href; tempA.download = evt.srcElement.download; tempA.click(); tempA.remove(); }) }))
|
好了,其实代码里还是有很多细节的,欢迎查看源码哦~
2026/4/21: 更多窗口支持地址栏回退
在每咕一题中,题目页是支持地址栏回退的,这使得移动端可以通过返回按钮(或等效手势)退出问题页。
然而其他窗口对于这种操作并不支持,于是基于又爱又恨的HistoryAPI,在尽可能少改动代码的前提下,为弹出modal增加这一功能。
我选择了使用popstate来完成,核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| let popStateHandle = null; function registerPopStateHandle(handle) { popStateHandle = handle; history.pushState(null, null, ''); }
function cleanupPopStateHandle(){ if(popStateHandle){ popStateHandle = null; history.go(-1); } }
window.addEventListener("popstate", ()=>{ if(popStateHandle){ const handle = popStateHandle; popStateHandle = null; handle(); } })
|
popStateHandle是注册的返回键按下时触发的函数,用于关闭窗口
registerPopStateHandle是一个帮助函数,集成了注册handle和pushState两步
cleanupPopStateHandle是一个帮助函数,用于在handle未触发时清理handle和历史栈
- 最后的
addEventListener注册监听函数,当注册了handle时,则调用它,注意这里清除handle和调用handle的顺序,先清除再调用使得handle中会调用cleanup的情况下不会过度清理历史栈
使用时,在弹出窗口处通过registerPopStateHandle注册handle,并在所有关闭窗口的地方(或者最后关闭窗口逻辑中)加入cleanupPopStateHandle来确保不污染历史栈和下一次操作,这样就可以通过返回键关闭窗口了~