mirror of
https://github.com/Mabbs/mabbs.github.io
synced 2026-08-05 01:18:59 +08:00
Compare commits
7 Commits
b8b29f86d5
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
987c604dea | ||
|
|
8e0b6e11b3 | ||
|
|
317f5bdce7 | ||
|
|
89a0af5632 | ||
|
|
798f65eb97 | ||
|
|
05998cdc3f | ||
|
|
5d87b2c9d4 |
@@ -77,7 +77,7 @@
|
|||||||
y="0"
|
y="0"
|
||||||
class="header"
|
class="header"
|
||||||
data-testid="header"
|
data-testid="header"
|
||||||
>Mayx's Blog Article</text>
|
>Mayx's Blog Posts</text>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
|
|||||||
@@ -179,5 +179,6 @@
|
|||||||
"/2026/04/14/ai-agent.html": "这篇文章介绍了“AI个人助理”Agent的发展现状和各种尝试。作者体验了OpenClaw、QClaw、WorkBuddy、Cline、LuckClaw和ApkClaw等不同的Agent项目,发现它们在功能、易用性和性能上各有优劣。OpenClaw安装和使用存在困难,国内大厂的QClaw和WorkBuddy则更易于上手,但免费额度有限。作者认为,开发任务更适合在编辑器集成AI中进行,如GitHub Copilot。LuckClaw在微型开发板上运行表现出色,ApkClaw则利用手机的优势操作移动应用。尽管Agent技术仍存在诸多问题,但其应用场景不断拓展,有望吸引更多人参与其中,推动AI应用化进程。",
|
"/2026/04/14/ai-agent.html": "这篇文章介绍了“AI个人助理”Agent的发展现状和各种尝试。作者体验了OpenClaw、QClaw、WorkBuddy、Cline、LuckClaw和ApkClaw等不同的Agent项目,发现它们在功能、易用性和性能上各有优劣。OpenClaw安装和使用存在困难,国内大厂的QClaw和WorkBuddy则更易于上手,但免费额度有限。作者认为,开发任务更适合在编辑器集成AI中进行,如GitHub Copilot。LuckClaw在微型开发板上运行表现出色,ApkClaw则利用手机的优势操作移动应用。尽管Agent技术仍存在诸多问题,但其应用场景不断拓展,有望吸引更多人参与其中,推动AI应用化进程。",
|
||||||
"/2026/05/01/virtual-net.html": "这篇文章介绍了作者在尝试异地组网搭建虚拟局域网时,对多种组网工具的探索和体验。作者首先尝试了n2n,但由于其项目停止更新且在NAT后的机器间存在掉线问题,最终选择了WireGuard作为主要方案。文章详细描述了WireGuard的配置过程,包括在Linux、OpenWrt和openEuler系统上的安装和配置,并遇到了各种各样的挑战,例如在红米AX3000路由器上找不到内核模块,以及在openEuler上缺少WireGuard相关包。此外,文章还提到了Netmaker和Headscale等WireGuard的控制平面,以及VNT和EasyTier等其他组网工具,并总结了最终选择WireGuard的原因:简单、够用、可靠性不错,且已经投入了大量配置。",
|
"/2026/05/01/virtual-net.html": "这篇文章介绍了作者在尝试异地组网搭建虚拟局域网时,对多种组网工具的探索和体验。作者首先尝试了n2n,但由于其项目停止更新且在NAT后的机器间存在掉线问题,最终选择了WireGuard作为主要方案。文章详细描述了WireGuard的配置过程,包括在Linux、OpenWrt和openEuler系统上的安装和配置,并遇到了各种各样的挑战,例如在红米AX3000路由器上找不到内核模块,以及在openEuler上缺少WireGuard相关包。此外,文章还提到了Netmaker和Headscale等WireGuard的控制平面,以及VNT和EasyTier等其他组网工具,并总结了最终选择WireGuard的原因:简单、够用、可靠性不错,且已经投入了大量配置。",
|
||||||
"/2026/06/01/dedupe.html": "这篇文章介绍了如何通过优化游戏资源文件来节约游戏占用的硬盘空间。作者由于Mac只有256GiB的硬盘存储空间,在下载了几十部游戏后空间不足,于是寻找解决方法。文章主要介绍了以下几点:\n\n1. 使用jdupes工具去除重复素材:作者发现许多游戏使用相同的引擎和素材,导致硬盘空间浪费。通过使用jdupes工具进行硬链接,可以有效去除重复文件,节约空间。\n\n2. 不同引擎的处理方式:\n - 对于RPG制作大师MV/MZ,作者使用RPG Maker Decrypter工具解密资源文件,并将图片转换为WebP格式以压缩空间。\n - 对于RPG制作大师XP/VX/VA,作者使用mkxp-z工具跨平台运行游戏,并通过RPG Maker Decrypter解包资源文件,与RTP素材合并后进行去重。\n - 对于Ren'Py游戏,作者使用unrpa工具解包rpa文件,但由于公共资源不多,仅在系列游戏情况下进行解包。\n\n3. 最终效果:通过以上优化,作者成功将游戏文件夹大小从47G降至33G,节约了大量硬盘空间。",
|
"/2026/06/01/dedupe.html": "这篇文章介绍了如何通过优化游戏资源文件来节约游戏占用的硬盘空间。作者由于Mac只有256GiB的硬盘存储空间,在下载了几十部游戏后空间不足,于是寻找解决方法。文章主要介绍了以下几点:\n\n1. 使用jdupes工具去除重复素材:作者发现许多游戏使用相同的引擎和素材,导致硬盘空间浪费。通过使用jdupes工具进行硬链接,可以有效去除重复文件,节约空间。\n\n2. 不同引擎的处理方式:\n - 对于RPG制作大师MV/MZ,作者使用RPG Maker Decrypter工具解密资源文件,并将图片转换为WebP格式以压缩空间。\n - 对于RPG制作大师XP/VX/VA,作者使用mkxp-z工具跨平台运行游戏,并通过RPG Maker Decrypter解包资源文件,与RTP素材合并后进行去重。\n - 对于Ren'Py游戏,作者使用unrpa工具解包rpa文件,但由于公共资源不多,仅在系列游戏情况下进行解包。\n\n3. 最终效果:通过以上优化,作者成功将游戏文件夹大小从47G降至33G,节约了大量硬盘空间。",
|
||||||
"/2026/07/01/vibe-coding.html": "这篇文章介绍了作者使用AI Agent进行Vibe Coding的体验。作者通过AI Agent生成代码,实现了博客的PJAX功能和一个立直麻将Wordle小游戏。在使用AI Agent的过程中,作者发现它能够理解整个项目的代码,并且能够根据需求进行修改和优化。然而,作者也遇到了一些问题,例如生成的代码质量不高,需要手动优化和调试。\n\n作者首先使用AI Agent为博客添加了PJAX功能,实现了无刷新加载页面。虽然生成的代码质量不高,但作者还是成功地实现了该功能。接着,作者使用AI Agent生成了一个立直麻将Wordle小游戏,并对其进行了优化和修改。作者还遇到了一些技术问题,例如字体兼容性和役种计算问题,但最终都通过AI Agent解决了。\n\n通过这次体验,作者认为Vibe Coding的体验相当不错,但也存在一些问题。完全由AI生成的代码可能会出现问题,需要手动优化和调试。然而,如果能够正确地使用AI Agent,也可以实现高质量的代码。作者还提到,有时候自己调查和研究比问AI更有效,可以避免浪费时间和精力。"
|
"/2026/07/01/vibe-coding.html": "这篇文章介绍了作者使用AI Agent进行Vibe Coding的体验。作者通过AI Agent生成代码,实现了博客的PJAX功能和一个立直麻将Wordle小游戏。在使用AI Agent的过程中,作者发现它能够理解整个项目的代码,并且能够根据需求进行修改和优化。然而,作者也遇到了一些问题,例如生成的代码质量不高,需要手动优化和调试。\n\n作者首先使用AI Agent为博客添加了PJAX功能,实现了无刷新加载页面。虽然生成的代码质量不高,但作者还是成功地实现了该功能。接着,作者使用AI Agent生成了一个立直麻将Wordle小游戏,并对其进行了优化和修改。作者还遇到了一些技术问题,例如字体兼容性和役种计算问题,但最终都通过AI Agent解决了。\n\n通过这次体验,作者认为Vibe Coding的体验相当不错,但也存在一些问题。完全由AI生成的代码可能会出现问题,需要手动优化和调试。然而,如果能够正确地使用AI Agent,也可以实现高质量的代码。作者还提到,有时候自己调查和研究比问AI更有效,可以避免浪费时间和精力。",
|
||||||
|
"/2026/08/01/terminal.html": "这篇文章介绍了作者利用AI助手制作终端版博客的经历。起初,作者想在博客上添加一个终端功能,允许用户在浏览器中使用真正的Linux访问博客。作者选择了基于WASM Linux的方案,并让AI助手开发了一个可以在浏览器中执行JS的设备文件,以实现与外界的通信。在此基础上,作者实现了列出文章、查看文章、打开文章和查看评论等功能。然而,该系统存在一些限制,如不能使用管道符和异步操作。作者还尝试了另一种方案,即基于JSNix的版本,虽然不是真正的Linux,但模拟得非常到位,且具有更好的兼容性和加载速度。最终,作者成功制作了两个版本的终端版博客,并表示对AI助手的强大功能感到满意。"
|
||||||
}
|
}
|
||||||
@@ -24,3 +24,4 @@ RavelloH's Blog,https://ravelloh.com,https://ravelloh.com/feed.xml,Beginning of
|
|||||||
初然忆,https://www.imcry.vip,https://www.imcry.vip/index.xml,初心如初见,然忆似流年。
|
初然忆,https://www.imcry.vip,https://www.imcry.vip/index.xml,初心如初见,然忆似流年。
|
||||||
姓王者,https://xingwangzhe.fun/,https://xingwangzhe.fun/rss.xml,探索、学习、进步、创造无限可能!
|
姓王者,https://xingwangzhe.fun/,https://xingwangzhe.fun/rss.xml,探索、学习、进步、创造无限可能!
|
||||||
EXYONE@BLOG:~$,https://exyon.ee,https://exyon.ee/feed.xml,一隅藏天地,流水遇知音。
|
EXYONE@BLOG:~$,https://exyon.ee,https://exyon.ee/feed.xml,一隅藏天地,流水遇知音。
|
||||||
|
小景,https://jingyuan-zheng.github.io/zh/,https://jingyuan-zheng.github.io/zh/index.xml,随心分享有趣的技术、开源项目与生活记录。
|
||||||
|
|||||||
|
@@ -2,9 +2,6 @@ repo_url
|
|||||||
http://gdatura24gtdy23lxd7ht3xzx6mi7mdlkabpvuefhrjn4t5jduviw5ad.onion/Mayx/mayx
|
http://gdatura24gtdy23lxd7ht3xzx6mi7mdlkabpvuefhrjn4t5jduviw5ad.onion/Mayx/mayx
|
||||||
http://git.dkforestseeaaq2dqz2uflmlsybvnq2irzn4ygyvu53oazyorednviid.onion/mayx/blog
|
http://git.dkforestseeaaq2dqz2uflmlsybvnq2irzn4ygyvu53oazyorednviid.onion/mayx/blog
|
||||||
http://giteabolfdejtdzblkooalqei6jr67imiugmhtsh6ocw4hlj5a4q.b32.i2p/mayx/blog
|
http://giteabolfdejtdzblkooalqei6jr67imiugmhtsh6ocw4hlj5a4q.b32.i2p/mayx/blog
|
||||||
https://lavaforge.org/mayx/blog
|
|
||||||
https://sij.ai/mayx/blog
|
|
||||||
https://repobase.net/mayx/blog
|
|
||||||
https://gitlab.lain.la/mayx/mayx.pages.lain.la
|
https://gitlab.lain.la/mayx/mayx.pages.lain.la
|
||||||
https://gitplac.si/mayx/mayx.gitpage.si
|
https://gitplac.si/mayx/mayx.gitpage.si
|
||||||
https://gitnet.fr/mayx/blog
|
https://gitnet.fr/mayx/blog
|
||||||
@@ -14,7 +11,11 @@ https://git.envs.net/Mayx/mayx
|
|||||||
https://tildegit.org/Mayx/mayx
|
https://tildegit.org/Mayx/mayx
|
||||||
https://git.tilde.town/mayx/blog
|
https://git.tilde.town/mayx/blog
|
||||||
https://gitlab.haskell.org/mayx/mayx
|
https://gitlab.haskell.org/mayx/mayx
|
||||||
|
https://lavaforge.org/mayx/blog
|
||||||
https://repo2.serv00.com/git/pub/Mayx/mayx/
|
https://repo2.serv00.com/git/pub/Mayx/mayx/
|
||||||
|
https://sij.ai/mayx/blog
|
||||||
|
https://repobase.net/mayx/blog
|
||||||
|
https://git.projectnightcrawler.dev/mayx/blog
|
||||||
https://codefloe.com/mayx/blog
|
https://codefloe.com/mayx/blog
|
||||||
https://git.pixie.town/mayx/blog
|
https://git.pixie.town/mayx/blog
|
||||||
https://git.minetest.land/Mayx/blog
|
https://git.minetest.land/Mayx/blog
|
||||||
@@ -23,7 +24,6 @@ https://code.dsconce.space/mayx/blog
|
|||||||
https://mygit.iexercice.com/mayx/blog
|
https://mygit.iexercice.com/mayx/blog
|
||||||
http://162.215.134.149:4000/mayx/blog
|
http://162.215.134.149:4000/mayx/blog
|
||||||
http://45.33.87.87/mayx/blog
|
http://45.33.87.87/mayx/blog
|
||||||
https://gitea.belanjaparts.com/mayx/blog
|
|
||||||
http://185.208.225.190:3000/mayx/blog
|
http://185.208.225.190:3000/mayx/blog
|
||||||
https://git.Apture.io/mayx/blog
|
https://git.Apture.io/mayx/blog
|
||||||
http://8.134.61.107:3000/mayx/blog
|
http://8.134.61.107:3000/mayx/blog
|
||||||
@@ -35,7 +35,6 @@ https://gitea.synapsetec.cn/mayx/blog
|
|||||||
http://gitea.yunshanghub.com:8081/mayx/blog
|
http://gitea.yunshanghub.com:8081/mayx/blog
|
||||||
http://113.177.27.200:2033/mayx/blog
|
http://113.177.27.200:2033/mayx/blog
|
||||||
http://152.69.204.151:3000/mayx/blog
|
http://152.69.204.151:3000/mayx/blog
|
||||||
https://gitea.ontoast.uk/mayx/blog
|
|
||||||
http://187.216.152.151:9999/mayx/blog
|
http://187.216.152.151:9999/mayx/blog
|
||||||
http://116.63.173.179:8001/mayx/blog
|
http://116.63.173.179:8001/mayx/blog
|
||||||
https://k0ki-dev.com/mayx/blog
|
https://k0ki-dev.com/mayx/blog
|
||||||
@@ -58,7 +57,6 @@ http://120.211.66.170:8418/mayx/blog
|
|||||||
https://gitea.vilcap.com/mayx/blog
|
https://gitea.vilcap.com/mayx/blog
|
||||||
http://8.138.142.17:3000/mayx/blog
|
http://8.138.142.17:3000/mayx/blog
|
||||||
http://209.127.59.74:3000/mayx/blog
|
http://209.127.59.74:3000/mayx/blog
|
||||||
https://git.dotb.cloud/mayx/blog
|
|
||||||
http://121.41.2.71:3000/mayx/blog
|
http://121.41.2.71:3000/mayx/blog
|
||||||
https://git.imvictor.tech:2/mayx/blog
|
https://git.imvictor.tech:2/mayx/blog
|
||||||
http://e19510c831.iok.la/mayx/blog
|
http://e19510c831.iok.la/mayx/blog
|
||||||
@@ -69,45 +67,37 @@ https://code.wxk8.com/mayx/blog
|
|||||||
http://fanlibo.i234.me:8418/mayx/blog
|
http://fanlibo.i234.me:8418/mayx/blog
|
||||||
https://git.temporaryname.org/mayx/blog
|
https://git.temporaryname.org/mayx/blog
|
||||||
https://Repo.gusdya.net/mayx/blog
|
https://Repo.gusdya.net/mayx/blog
|
||||||
https://git.flymiracle.com/mayx/blog
|
|
||||||
https://git.stormrain.cn/mayx/blog
|
https://git.stormrain.cn/mayx/blog
|
||||||
https://git.deuxfleurs.fr/mayx/blog
|
https://git.deuxfleurs.fr/mayx/blog
|
||||||
https://www.simpra.org:3000/mayx/blog
|
https://www.simpra.org:3000/mayx/blog
|
||||||
https://www.arcbyte.dev/mayx/blog
|
https://www.arcbyte.dev/mayx/blog
|
||||||
https://scm.bcorex.e3labs.net/mayx/blog
|
https://scm.bcorex.e3labs.net/mayx/blog
|
||||||
https://gitea.tecamino.com/mayx/blog
|
|
||||||
https://git.tobiasweise.dev/mayx/blog
|
https://git.tobiasweise.dev/mayx/blog
|
||||||
https://code.cif.su/mayx/blog
|
https://code.cif.su/mayx/blog
|
||||||
https://git.cynic.moe/mayx/blog
|
https://git.cynic.moe/mayx/blog
|
||||||
https://git.muellers-software.org/mayx/blog
|
https://git.muellers-software.org/mayx/blog
|
||||||
https://repos.fbpx.io/mayx/blog
|
|
||||||
https://git.cavemanon.xyz/mayx/blog
|
https://git.cavemanon.xyz/mayx/blog
|
||||||
http://svn.rivastudio.cn/mayx/blog
|
http://svn.rivastudio.cn/mayx/blog
|
||||||
http://118.178.172.49:3000/mayx/blog
|
http://118.178.172.49:3000/mayx/blog
|
||||||
https://gitea.lasallesaintdenis.com/mayx/blog
|
https://gitea.lasallesaintdenis.com/mayx/blog
|
||||||
http://8.130.135.159:3000/mayx/blog
|
http://8.130.135.159:3000/mayx/blog
|
||||||
https://git.suo0.com/mayx/blog
|
|
||||||
https://git.dshkabatur.ru/mayx/blog
|
https://git.dshkabatur.ru/mayx/blog
|
||||||
http://47.103.78.70:3000/mayx/blog
|
http://47.103.78.70:3000/mayx/blog
|
||||||
http://47.100.208.160:51300/mayx/blog
|
|
||||||
http://120.210.80.160:3000/mayx/blog
|
http://120.210.80.160:3000/mayx/blog
|
||||||
http://194.5.152.156:3000/mayx/blog
|
http://194.5.152.156:3000/mayx/blog
|
||||||
http://58.65.162.118:3000/mayx/blog
|
http://58.65.162.118:3000/mayx/blog
|
||||||
https://git.arkon.solutions/mayx/blog
|
https://git.arkon.solutions/mayx/blog
|
||||||
http://8.131.93.145:54082/mayx/blog
|
|
||||||
https://git.7af.ru/mayx/blog
|
https://git.7af.ru/mayx/blog
|
||||||
https://gitea.yimoyuyan.cn/mayx/blog
|
https://gitea.yimoyuyan.cn/mayx/blog
|
||||||
http://221.203.14.217:3000/mayx/blog
|
http://221.203.14.217:3000/mayx/blog
|
||||||
https://dev.kiramtech.com/mayx/blog
|
https://dev.kiramtech.com/mayx/blog
|
||||||
https://git.f4e.lol/mayx/blog
|
https://git.f4e.lol/mayx/blog
|
||||||
http://72.61.229.93:4000/mayx/blog
|
http://72.61.229.93:4000/mayx/blog
|
||||||
https://lishan148.synology.me:3014/mayx/blog_cn
|
|
||||||
http://110.42.101.39:13000/mayx/blog_cn
|
http://110.42.101.39:13000/mayx/blog_cn
|
||||||
http://39.101.74.135:5000/mayx/blog_cn
|
http://39.101.74.135:5000/mayx/blog_cn
|
||||||
http://123.57.130.140:3000/mayx/blog_cn
|
http://123.57.130.140:3000/mayx/blog_cn
|
||||||
http://118.24.46.223:3000/mayx/blog_cn
|
http://118.24.46.223:3000/mayx/blog_cn
|
||||||
http://s3.v100.vip:31057/mayx/blog_cn
|
http://s3.v100.vip:31057/mayx/blog_cn
|
||||||
https://www.syq.im:2025/mayx/blog_cn
|
|
||||||
http://101.33.225.95:3000/mayx/blog_cn
|
http://101.33.225.95:3000/mayx/blog_cn
|
||||||
http://45.55.138.82:3000/mayx/blog_cn
|
http://45.55.138.82:3000/mayx/blog_cn
|
||||||
http://118.24.129.148:3000/mayx/blog_cn
|
http://118.24.129.148:3000/mayx/blog_cn
|
||||||
@@ -130,15 +120,12 @@ http://git.mynas71.myds.me/mayx/blog
|
|||||||
https://git.veraskolivna.net/mayx/blog
|
https://git.veraskolivna.net/mayx/blog
|
||||||
https://git.lifetop.net/mayx/blog
|
https://git.lifetop.net/mayx/blog
|
||||||
https://gitea.jnyuxia.com/mayx/blog
|
https://gitea.jnyuxia.com/mayx/blog
|
||||||
https://gitea.hello.faith/mayx/blog
|
|
||||||
https://git.violka-it.net/mayx/blog
|
|
||||||
https://git.danpeak.co.uk/mayx/blog
|
https://git.danpeak.co.uk/mayx/blog
|
||||||
https://gitea.mynas-lechner.de/mayx/blog
|
https://gitea.mynas-lechner.de/mayx/blog
|
||||||
https://git.esen.gay/mayx/blog
|
https://git.esen.gay/mayx/blog
|
||||||
https://git.webtims.ru/mayx/blog
|
https://git.webtims.ru/mayx/blog
|
||||||
https://gitlab.iplusus.com/mayx/blog
|
https://gitlab.iplusus.com/mayx/blog
|
||||||
https://git.miasma-os.com/mayx/blog
|
https://git.miasma-os.com/mayx/blog
|
||||||
https://gitea.nacsity.cn/mayx/blog
|
|
||||||
https://git.zakum.cn/mayx/blog
|
https://git.zakum.cn/mayx/blog
|
||||||
http://47.109.103.110:9000/mayx/blog_cn
|
http://47.109.103.110:9000/mayx/blog_cn
|
||||||
http://51.159.198.233:3000/mayx/blog
|
http://51.159.198.233:3000/mayx/blog
|
||||||
@@ -150,10 +137,8 @@ http://119.96.62.56:3000/mayx/blog_cn
|
|||||||
http://43.143.209.246:6300/mayx/blog_cn
|
http://43.143.209.246:6300/mayx/blog_cn
|
||||||
http://39.100.183.95:88/mayx/blog_cn
|
http://39.100.183.95:88/mayx/blog_cn
|
||||||
http://www.xshideserver.com:3000/mayx/blog_cn
|
http://www.xshideserver.com:3000/mayx/blog_cn
|
||||||
http://159.75.131.235:3001/mayx/blog_cn
|
|
||||||
http://1.117.67.95:3000/mayx/blog_cn
|
http://1.117.67.95:3000/mayx/blog_cn
|
||||||
http://81.70.102.154/mayx/blog_cn
|
http://81.70.102.154/mayx/blog_cn
|
||||||
http://111.228.34.40:3000/mayx/blog_cn
|
|
||||||
http://36.133.248.69:3088/mayx/blog_cn
|
http://36.133.248.69:3088/mayx/blog_cn
|
||||||
http://220.205.16.27:18081/mayx/blog_cn
|
http://220.205.16.27:18081/mayx/blog_cn
|
||||||
http://183.204.60.122:10081/mayx/blog_cn
|
http://183.204.60.122:10081/mayx/blog_cn
|
||||||
@@ -161,25 +146,18 @@ https://gitea.wuyuank.com/mayx/blog_cn
|
|||||||
http://47.106.222.181:20511/mayx/blog_cn
|
http://47.106.222.181:20511/mayx/blog_cn
|
||||||
https://gitea.myat4.com/mayx/blog_cn
|
https://gitea.myat4.com/mayx/blog_cn
|
||||||
http://118.24.161.24:3000/mayx/blog_cn
|
http://118.24.161.24:3000/mayx/blog_cn
|
||||||
http://githanea.s.odn.cc/mayx/blog_cn
|
|
||||||
https://git.dev.chuweizi.com/mayx/blog
|
https://git.dev.chuweizi.com/mayx/blog
|
||||||
http://36.138.125.206:3000/mayx/blog_cn
|
http://36.138.125.206:3000/mayx/blog_cn
|
||||||
https://silica.codes/mayx/blog
|
|
||||||
https://git.crystalyx.net/mayx/blog
|
https://git.crystalyx.net/mayx/blog
|
||||||
https://gittea.dev/mayx/blog
|
https://gittea.dev/mayx/blog
|
||||||
https://code.infininov.com/mayx/blog
|
|
||||||
https://src.enterpriselinux.social/mayx/blog
|
|
||||||
https://gitea.grxe.io/mayx/blog
|
https://gitea.grxe.io/mayx/blog
|
||||||
https://gitea.accept.dev.dbf.nl/mayx/blog
|
https://gitea.accept.dev.dbf.nl/mayx/blog
|
||||||
https://git.jhossain.online/mayx/blog
|
https://git.jhossain.online/mayx/blog
|
||||||
https://barhoum-lab.fr/mayx/blog
|
https://barhoum-lab.fr/mayx/blog
|
||||||
https://git.fedaya.net/mayx/blog
|
|
||||||
https://redev.lol/mayx/blog
|
https://redev.lol/mayx/blog
|
||||||
https://papi.tkpups.com/mayx/blog
|
https://papi.tkpups.com/mayx/blog
|
||||||
https://git.beyond-a-i.org/mayx/blog
|
https://git.beyond-a-i.org/mayx/blog
|
||||||
http://huanghomenas2.myqnapcloud.com:4000/mayx/blog_cn
|
|
||||||
http://git.zhmight.com/mayx/blog_cn
|
http://git.zhmight.com/mayx/blog_cn
|
||||||
https://intl-dev.gaia888.com/mayx/blog_cn
|
|
||||||
http://gitea.snailtrack.cn/mayx/blog_cn
|
http://gitea.snailtrack.cn/mayx/blog_cn
|
||||||
https://code.draussenfunker.de/mayx/blog_cn
|
https://code.draussenfunker.de/mayx/blog_cn
|
||||||
https://git.dinsor.co.th/mayx/blog
|
https://git.dinsor.co.th/mayx/blog
|
||||||
@@ -188,7 +166,6 @@ http://www.bkandssp.cn:30/mayx/blog
|
|||||||
http://gitea.ydxtool.com/mayx/blog
|
http://gitea.ydxtool.com/mayx/blog
|
||||||
https://gitea.malxte.de/mayx/blog
|
https://gitea.malxte.de/mayx/blog
|
||||||
https://git.0xee.eu/mayx/blog
|
https://git.0xee.eu/mayx/blog
|
||||||
https://git.crwlr.ir/mayx/blog
|
|
||||||
https://git.nozora.top/mayx/blog
|
https://git.nozora.top/mayx/blog
|
||||||
https://git.edenit.co.kr/mayx/blog
|
https://git.edenit.co.kr/mayx/blog
|
||||||
https://git.catgirlsneed.homes/mayx/blog
|
https://git.catgirlsneed.homes/mayx/blog
|
||||||
@@ -211,37 +188,27 @@ https://git.kaki87.net/mayx/blog
|
|||||||
https://forgejo.vanten-s.com/mayx/blog
|
https://forgejo.vanten-s.com/mayx/blog
|
||||||
http://ysx.myds.me:3005/mayx/blog
|
http://ysx.myds.me:3005/mayx/blog
|
||||||
http://124.71.169.183:3000/mayx/blog
|
http://124.71.169.183:3000/mayx/blog
|
||||||
https://git.csi-kjsce.org/mayx/blog
|
|
||||||
https://hiwifi.denq.us:8418/mayx/blog
|
https://hiwifi.denq.us:8418/mayx/blog
|
||||||
https://git.msoucy.me/mayx/blog
|
https://git.msoucy.me/mayx/blog
|
||||||
http://140.120.108.238:49308/mayx/blog
|
http://140.120.108.238:49308/mayx/blog
|
||||||
http://git.chilidoginteractive.com:3000/mayx/blog_cn
|
http://git.chilidoginteractive.com:3000/mayx/blog_cn
|
||||||
https://git.thunder-data.cn/mayx/blog_cn
|
https://git.thunder-data.cn/mayx/blog_cn
|
||||||
https://git.cukak.com/mayx/blog_cn
|
|
||||||
https://git.ixu.me/mayx/blog_cn
|
https://git.ixu.me/mayx/blog_cn
|
||||||
http://git.uhfdemo.com/mayx/blog_cn
|
http://git.uhfdemo.com/mayx/blog_cn
|
||||||
http://43.143.142.38:7001/mayx/blog_cn
|
http://43.143.142.38:7001/mayx/blog_cn
|
||||||
https://gitea.smartechouse.com/mayx/blog_cn
|
|
||||||
http://git.feashow.cn/mayx/blog_cn
|
|
||||||
http://git.yangben365.com/mayx/blog_cn
|
|
||||||
https://git.wieerwill.dev/mayx/blog
|
https://git.wieerwill.dev/mayx/blog
|
||||||
https://git.etwo.dev/mayx/blog
|
https://git.etwo.dev/mayx/blog
|
||||||
https://git.panda-number.one/mayx/blog
|
https://git.panda-number.one/mayx/blog
|
||||||
https://datacortex.pl/mayx/blog
|
https://datacortex.pl/mayx/blog
|
||||||
https://git.trevorbotha.net/mayx/blog
|
https://git.trevorbotha.net/mayx/blog
|
||||||
https://viacivis.de/mayx/blog
|
|
||||||
http://www.wybpohro.com:3000/mayx/blog_cn
|
|
||||||
https://gitea.jobiglo.com/mayx/blog_cn
|
https://gitea.jobiglo.com/mayx/blog_cn
|
||||||
http://118.31.7.2:3000/mayx/blog_cn
|
http://118.31.7.2:3000/mayx/blog_cn
|
||||||
http://www.dothing.com:6999/mayx/blog_cn
|
|
||||||
http://101.37.147.115:3000/mayx/blog_cn
|
http://101.37.147.115:3000/mayx/blog_cn
|
||||||
https://gitea.originaltech.cn/mayx/blog_cn
|
https://gitea.originaltech.cn/mayx/blog_cn
|
||||||
https://git.akarmain.ru/mayx/blog_cn
|
https://git.akarmain.ru/mayx/blog_cn
|
||||||
https://git.georgew.dev/mayx/blog
|
https://git.georgew.dev/mayx/blog
|
||||||
https://gitea.keymantek.com:777/mayx/blog
|
https://gitea.keymantek.com:777/mayx/blog
|
||||||
http://git.realrobots.net/mayx/blog
|
http://git.realrobots.net/mayx/blog
|
||||||
https://www.tksea.top/mayx/blog
|
|
||||||
https://wtrb.uk:8443/mayx/blog
|
|
||||||
http://www.souper.ee/mayx/blog
|
http://www.souper.ee/mayx/blog
|
||||||
https://git.tirtapakuan.co.id/mayx/blog
|
https://git.tirtapakuan.co.id/mayx/blog
|
||||||
https://tacticallysolved.com/mayx/blog
|
https://tacticallysolved.com/mayx/blog
|
||||||
@@ -251,7 +218,6 @@ https://gitea.viperlance.net/mayx/blog
|
|||||||
https://git.woopwoopserver.com/mayx/blog
|
https://git.woopwoopserver.com/mayx/blog
|
||||||
http://tian-you.top:7020/mayx/blog
|
http://tian-you.top:7020/mayx/blog
|
||||||
http://34.17.182.140/mayx-gogs/blog
|
http://34.17.182.140/mayx-gogs/blog
|
||||||
https://gitea.octifor.synology.me:60443/mayx/blog
|
|
||||||
http://gitea.mikarsoft.com/mayx/blog
|
http://gitea.mikarsoft.com/mayx/blog
|
||||||
https://git.zhewen-tong.cc/mayx/blog
|
https://git.zhewen-tong.cc/mayx/blog
|
||||||
https://mcgit.place/mayx/blog
|
https://mcgit.place/mayx/blog
|
||||||
@@ -266,42 +232,31 @@ https://git.sleepeesoftware.fr/mayx/blog
|
|||||||
https://git.everestlinux.org/mayx/blog
|
https://git.everestlinux.org/mayx/blog
|
||||||
https://git.devinmajor.com/mayx/blog
|
https://git.devinmajor.com/mayx/blog
|
||||||
https://git.netzbyte.com/mayx/blog
|
https://git.netzbyte.com/mayx/blog
|
||||||
https://repo.saticogroup.com/mayx/blog
|
|
||||||
https://ataymakhzan.com/mayx/blog
|
https://ataymakhzan.com/mayx/blog
|
||||||
https://repo.qruize.com/mayx/blog
|
https://repo.qruize.com/mayx/blog
|
||||||
https://git.xneon.org/mayx/blog
|
https://git.xneon.org/mayx/blog
|
||||||
https://git.lamby.gay/mayx/blog
|
https://git.lamby.gay/mayx/blog
|
||||||
https://imperionblast.org/mayx/blog
|
|
||||||
https://donkey.abanbytes.eu/mayx/blog
|
https://donkey.abanbytes.eu/mayx/blog
|
||||||
https://lab.dutt.ch/mayx/blog
|
https://lab.dutt.ch/mayx/blog
|
||||||
https://git.liestral.io/mayx/blog
|
https://git.liestral.io/mayx/blog
|
||||||
https://gitea.dabit.synology.me/mayx/blog
|
https://gitea.dabit.synology.me/mayx/blog
|
||||||
http://www.javabrew.org/mayx/blog
|
|
||||||
https://git.joinplu.me/mayx/blog
|
https://git.joinplu.me/mayx/blog
|
||||||
http://otter.top/mayx/blog
|
http://otter.top/mayx/blog
|
||||||
https://git.xiongyi.xin/mayx/blog
|
|
||||||
https://git.pdy.ph/mayx/blog
|
|
||||||
http://188.66.0.25:3000/mayx/blog
|
http://188.66.0.25:3000/mayx/blog
|
||||||
https://gitea.deliverables.io/mayx/blog
|
https://gitea.deliverables.io/mayx/blog
|
||||||
https://git.sevenvm.de/mayx/blog
|
https://git.sevenvm.de/mayx/blog
|
||||||
https://code.letsbe.solutions/mayx/blog
|
https://code.letsbe.solutions/mayx/blog
|
||||||
https://git.mlhub.it/mayx/blog
|
https://git.mlhub.it/mayx/blog
|
||||||
https://git.coastit.co.za/mayx/blog
|
|
||||||
https://git.0bin.in/mayx/blog
|
|
||||||
https://jsuse.com/mayx/blog
|
https://jsuse.com/mayx/blog
|
||||||
https://git.truncgil.com/mayx/blog
|
|
||||||
http://23.182.16.150:3000/mayx/blog
|
http://23.182.16.150:3000/mayx/blog
|
||||||
https://git.jingflowai.com/mayx/blog
|
|
||||||
https://git.ritonquilol.fr/mayx/blog
|
https://git.ritonquilol.fr/mayx/blog
|
||||||
https://bis127.vse.cz/mayx/blog
|
https://bis127.vse.cz/mayx/blog
|
||||||
https://gitea.diereimers.org/mayx/blog
|
|
||||||
https://app.gitpasha.com/mayx/blog
|
https://app.gitpasha.com/mayx/blog
|
||||||
http://www.flowzl.top/mayx/blog
|
http://www.flowzl.top/mayx/blog
|
||||||
https://git.psg.net.au/mayx/blog
|
https://git.psg.net.au/mayx/blog
|
||||||
https://shaman2025.site/mayx/blog
|
https://shaman2025.site/mayx/blog
|
||||||
https://gitea.ccllc.pro/mayx/blog
|
https://gitea.ccllc.pro/mayx/blog
|
||||||
https://gitea.hpdocker.hpress.de/mayx/blog
|
https://gitea.hpdocker.hpress.de/mayx/blog
|
||||||
https://vertpartneerss.club/mayx/blog
|
|
||||||
https://git.jhammah.com/mayx/blog
|
https://git.jhammah.com/mayx/blog
|
||||||
https://git.kemomimi.site/mayx/blog
|
https://git.kemomimi.site/mayx/blog
|
||||||
https://gitea.kolesarhome.com/mayx/blog
|
https://gitea.kolesarhome.com/mayx/blog
|
||||||
@@ -309,9 +264,7 @@ http://sexy6688.vip/mayx/blog
|
|||||||
https://gitbucket.aint-no.info/mayx/blog
|
https://gitbucket.aint-no.info/mayx/blog
|
||||||
https://gitea.emulab.it/mayx/blog
|
https://gitea.emulab.it/mayx/blog
|
||||||
https://git.wikeo.ovh/mayx/blog
|
https://git.wikeo.ovh/mayx/blog
|
||||||
https://gitea.finesoft.club:4431/mayx/blog
|
|
||||||
https://gitea.precia.site/mayx/blog
|
https://gitea.precia.site/mayx/blog
|
||||||
https://git.bitscat.vn/mayx/blog
|
|
||||||
http://arch52.co.uk/mayx/blog
|
http://arch52.co.uk/mayx/blog
|
||||||
https://devcraft-dot.tech/mayx/blog
|
https://devcraft-dot.tech/mayx/blog
|
||||||
https://a-t-g.ru/mayx/blog
|
https://a-t-g.ru/mayx/blog
|
||||||
@@ -321,17 +274,14 @@ http://8.141.82.163:20000/mayx/blog_cn
|
|||||||
http://152.136.126.252:3000/mayx/blog_cn
|
http://152.136.126.252:3000/mayx/blog_cn
|
||||||
https://home.zhupei.me:3000/mayx/blog_cn
|
https://home.zhupei.me:3000/mayx/blog_cn
|
||||||
http://gitea.coderpath.com/mayx/blog_cn
|
http://gitea.coderpath.com/mayx/blog_cn
|
||||||
https://gitea.quiztimes.nl/mayx/blog_cn
|
|
||||||
https://www.chenisgod.com:3096/mayx/blog_cn
|
https://www.chenisgod.com:3096/mayx/blog_cn
|
||||||
http://81.70.30.91:3000/mayx/blog_cn
|
http://81.70.30.91:3000/mayx/blog_cn
|
||||||
http://yidaima.cn:6008/mayx/blog_cn
|
http://yidaima.cn:6008/mayx/blog_cn
|
||||||
https://gitea.kdlsvps.top/mayx/blog_cn
|
|
||||||
http://8.148.31.14:3000/mayx/blog_cn
|
http://8.148.31.14:3000/mayx/blog_cn
|
||||||
http://58.17.14.95:8001/mayx/blog_cn
|
http://58.17.14.95:8001/mayx/blog_cn
|
||||||
http://39.99.175.172:8000/mayx/blog_cn
|
http://39.99.175.172:8000/mayx/blog_cn
|
||||||
http://39.171.252.63:3000/mayx/blog_cn
|
http://39.171.252.63:3000/mayx/blog_cn
|
||||||
https://git.lucas-michel.fr/mayx/blog_cn
|
https://git.lucas-michel.fr/mayx/blog_cn
|
||||||
https://git.archieri.fr/mayx/blog_cn
|
|
||||||
https://git.cdev.su/mayx/blog
|
https://git.cdev.su/mayx/blog
|
||||||
https://root-kit.ru/mayx/blog
|
https://root-kit.ru/mayx/blog
|
||||||
https://git.gnyra.com/mayx/blog_cn
|
https://git.gnyra.com/mayx/blog_cn
|
||||||
@@ -343,17 +293,72 @@ http://frp5.mmszxc.xin:55469/mayx/blog_cn
|
|||||||
http://gitea.jb1000.com:88/mayx/blog_cn
|
http://gitea.jb1000.com:88/mayx/blog_cn
|
||||||
https://gitea.liyongzheng.fun/mayx/blog_cn
|
https://gitea.liyongzheng.fun/mayx/blog_cn
|
||||||
http://www.22ai.vip/mayx/blog_cn
|
http://www.22ai.vip/mayx/blog_cn
|
||||||
https://git.tutulab.online/mayx/blog_cn
|
|
||||||
http://voodoo.plus/mayx/blog_cn
|
http://voodoo.plus/mayx/blog_cn
|
||||||
https://git.extra.eiffel.com/mayx/blog_cn
|
https://git.extra.eiffel.com/mayx/blog_cn
|
||||||
https://hdtime.space/mayx/blog_cn
|
|
||||||
http://219.151.177.172:3000/mayx/blog_cn
|
http://219.151.177.172:3000/mayx/blog_cn
|
||||||
http://62.234.20.54:3000/mayx/blog_cn
|
http://62.234.20.54:3000/mayx/blog_cn
|
||||||
https://git.urnas.cn/mayx/blog_cn
|
https://git.urnas.cn/mayx/blog_cn
|
||||||
http://20.6.159.133:3000/mayx/blog_cn
|
|
||||||
https://gitea.4gunn.cn:52443/mayx/blog_cn
|
https://gitea.4gunn.cn:52443/mayx/blog_cn
|
||||||
https://git.4tempo.com/mayx/blog_cn
|
|
||||||
https://gitea.nightdev.cn:9527/mayx/blog_cn
|
https://gitea.nightdev.cn:9527/mayx/blog_cn
|
||||||
https://git.hivetasks.com/mayx/blog_cn
|
https://git.hivetasks.com/mayx/blog_cn
|
||||||
https://git.ddns.net/mayx/blog_cn
|
https://git.ddns.net/mayx/blog_cn
|
||||||
http://jdunlap.com/mayx/blog
|
http://jdunlap.com/mayx/blog
|
||||||
|
https://git.silica.codes/mayx/blog
|
||||||
|
http://xiaolee.space/mayx/blog
|
||||||
|
https://www.vitelgrid.app/mayx/blog
|
||||||
|
https://gitruhub.ru/mayx/blog
|
||||||
|
https://git.linuxposting.xyz/mayx/blog
|
||||||
|
http://www.jingdujiaoyu.net/mayx/blog
|
||||||
|
https://git.techworkshop42.ru/mayx/blog
|
||||||
|
http://speedyfox.app/mayx/blog
|
||||||
|
https://mjjtop.com/mayx/blog
|
||||||
|
https://git.noosfera.digital/mayx/blog
|
||||||
|
https://git.birdideas.net/mayx/blog
|
||||||
|
https://salamihub.ru/mayx/blog
|
||||||
|
https://forgejo.wanderingmonster.dev/mayx/blog
|
||||||
|
http://v.udvip.com/mayx/blog
|
||||||
|
https://gitea-fs0kwo8kccc4g88g0kk8k88c.gnextd.io/mayx/blog
|
||||||
|
https://git.edavmig.ru/mayx/blog
|
||||||
|
https://gitea.ns5001k.sigma2.no/mayx/blog
|
||||||
|
https://git.dongshan.tech/mayx/blog
|
||||||
|
https://fzsimo.com/mayx/blog
|
||||||
|
https://www.webetter.co.jp/mayx/blog
|
||||||
|
https://git.hemangvyas.com/mayx/blog
|
||||||
|
https://git.zotadevices.ru/mayx/blog
|
||||||
|
https://gitav.ru/mayx/blog
|
||||||
|
https://git.cribdev.com/mayx/blog
|
||||||
|
https://sapkyy.ru/mayx/blog
|
||||||
|
https://git.sakuzyo.net/mayx/blog
|
||||||
|
https://git.zefie.net/mayx/blog
|
||||||
|
https://gitea.redpowerfuture.com/mayx/blog
|
||||||
|
https://git.jinzhao.me/mayx/blog
|
||||||
|
http://www.365rise.top/mayx/blog
|
||||||
|
https://git.ifuntanhub.dev/mayx/blog
|
||||||
|
https://mahta-tech.com/mayx/blog
|
||||||
|
http://kuiwisen.asia/mayx/blog
|
||||||
|
https://git.aptcloud.ru/mayx/blog
|
||||||
|
https://gitea.simssoftware.in/mayx/blog
|
||||||
|
https://gitlab.dev.genai-team.ru/mayx/blog
|
||||||
|
https://gitea.garandplg.com/mayx/blog
|
||||||
|
https://d.roxyipt.com/mayx/blog
|
||||||
|
https://git.umervtilte.lol/mayx/blog
|
||||||
|
https://infrared.xxx/mayx/blog
|
||||||
|
https://git.bitpak.ru/mayx/blog
|
||||||
|
https://git.amamedis.de/mayx/blog
|
||||||
|
https://gitea.neanderhub.com/mayx/blog
|
||||||
|
https://git.kunstglass.de/mayx/blog
|
||||||
|
https://git.kry008.xyz/mayx/blog
|
||||||
|
https://gitea.onfing.ir/mayx/blog
|
||||||
|
http://59.57.4.66:3000/mayx/blog_cn
|
||||||
|
https://gitea.xala.dev/mayx/blog
|
||||||
|
https://gitea.augeo.dev/mayx/blog_cn
|
||||||
|
http://36.213.200.127:23000/mayx/blog_cn
|
||||||
|
http://20.219.0.85:3000/mayx/blog_cn
|
||||||
|
https://git.niisse.net/mayx/blog_cn
|
||||||
|
https://gitea.yfandxt.asia/mayx/blog_cn
|
||||||
|
http://81.69.57.215:3000/mayx/blog_cn
|
||||||
|
https://git.dglyoo.com/mayx/blog_cn
|
||||||
|
http://c8202.top:3000/mayx/blog_cn
|
||||||
|
http://nojava.fun/mayx/blog_cn
|
||||||
|
http://www.zhima.life/mayx/blog_cn
|
||||||
|
https://git.healparts.ru/mayx/blog_cn
|
||||||
|
|||||||
|
@@ -117,6 +117,7 @@ layout: xslt_container
|
|||||||
<script src="{{ "/assets/js/scale.fix.js" | relative_url }}"></script>
|
<script src="{{ "/assets/js/scale.fix.js" | relative_url }}"></script>
|
||||||
<!--[if !IE]> -->
|
<!--[if !IE]> -->
|
||||||
<script src="/assets/js/main_new.js"></script>
|
<script src="/assets/js/main_new.js"></script>
|
||||||
|
<script src="/assets/js/blog-console.js"></script>
|
||||||
<script src="/Live2dHistoire/live2d/js/live2d.js"></script>
|
<script src="/Live2dHistoire/live2d/js/live2d.js"></script>
|
||||||
<script src="/Live2dHistoire/live2d/js/message.js"></script>
|
<script src="/Live2dHistoire/live2d/js/message.js"></script>
|
||||||
<script src="/assets/js/jquery.pjax.min.js"></script>
|
<script src="/assets/js/jquery.pjax.min.js"></script>
|
||||||
|
|||||||
31
_posts/2026-08-01-terminal.md
Normal file
31
_posts/2026-08-01-terminal.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: 一次制作终端版博客的经历
|
||||||
|
tags: [WASM, 终端, Linux, 博客]
|
||||||
|
---
|
||||||
|
|
||||||
|
在浏览器上用真的Linux访问博客会是什么样的感觉?<!--more-->
|
||||||
|
|
||||||
|
# 起因
|
||||||
|
在去年的时候我为了想在博客上加个终端,写过一篇关于[在浏览器中运行Linux](/2025/12/01/linux.html)的文章。不过那时候只是调查了有哪些可能的方法,并没有真的做出来,主要是自己做感觉太麻烦了😂。
|
||||||
|
但从上次进行了[Vibe Coding](/2026/07/01/vibe-coding.html)之后,我意识到这可能不再是什么麻烦事,也许只要和AI聊聊天就能轻松做出来了,于是接着之前的方案,我开始着手完成我的想法。
|
||||||
|
|
||||||
|
# 终端版博客的不同形式
|
||||||
|
## 基于WASM Linux的版本
|
||||||
|
在之前的文章里,我其实更倾向于WASM Linux的方案,尤其是[Thomas Stokes](https://github.com/tombl)制作的[WASM Linux](https://github.com/tombl/linux),因为它相比于虚拟机更轻量,效率更高,而且还能完美地在Safari中运行(毕竟我用的是macOS😂),因此接下来我计划用它为基础制作我的终端版博客。
|
||||||
|
不过之前我也说过,目前这个WASM Linux还不能和外界通信,不能和外界通信就不能读取我的文章。于是我打算先让AI解决这个问题。但如果我直接给它说想要和外界通信也许它会给我整一堆奇怪的实现,所以我得自己先想一个实现起来最简单的方案。之前我记得Fabrice Bellard大神写的JSLinux中有一个剪贴板设备文件可以和外部通信,那么干脆让AI写一个能在浏览器中执行JS的设备文件吧,这样做出来只需要写简单的Shell脚本就可以和浏览器交互了,也不需要考虑如何编译WASM程序,于是就这样我开始让它实现这个功能。
|
||||||
|
最终经过几次调试和修改之后AI真的把这个设备文件给做出来了,不过刚做出来的时候有些问题。因为在这个Linux中的进程都是在Web Worker中运行,执行的JS也会在Web Worker中运行。但是Web Worker的局限性很大,并不能和当前网页进行交互,不过这种问题也都是小事,发现了之后只要给AI一反馈也是轻轻松松就改好了……最终实现的效果我感觉相当不错,既然如此,那我就把AI做的这个驱动给上游[PR](https://github.com/tombl/linux/pull/24)一下吧,也算做个贡献。不过最终原作者不太喜欢这种实现方式,他好像更喜欢标准化的方式,说是已经做了一个[VsockDevice](https://github.com/tombl/linux/blob/b36416c7f266a465361ab7dde7622eb37239cd7e/tools/wasm/src/virtio.ts#L354),可以直接使用Linux自带的驱动和外界通信,所以把我的PR拒了(看来AI Slop还是入不了大佬的法眼😂)。 ~~不过我问了一下AI,它说用这个设备的话可能需要`ioctl()`系统调用,就没办法直接用Shell操作了,只能用别的语言写的程序才能用它通信,所以我只好自己用这种不标准的方式了。~~ 虽然VsockDevice确实是不太好操作,但是大佬随后又新增了一个ethernetDevice可以用来上网,考虑到维护自己的分支实在是太麻烦了,所以我让AI写了一个虚拟的HTTP服务,使其能够执行JS,顺便还增加了连接互联网的功能。这样我就可以直接使用上游的代码,不需要再维护之前写的驱动了(其实以太网比Vsock复杂多了,只是因为可以用wget之类的客户端请求所以才简单😋)。
|
||||||
|
在做好与外界通信的通道后我就开始让AI写能和我博客交互的脚本,不过在这个过程中也遇到了不少问题,最开始的时候发现这个Shell的管道有些问题,执行的时候会忽略管道前的命令,后面的命令就没法执行了……感觉有点奇怪,所以我要求它在实现交互脚本时不要使用管道符。除此之外我还发现反引号和`$()`这些能把执行结果插入字符串的语法也不能用……总之就是局限相当多,但它还是克服了这些困难最终把脚本给写出来了,最终实现了列出文章、查看文章、打开文章和查看评论等功能,效果还是相当不错。 ~~唯一的缺陷就是它不能进行异步操作,只能同步请求,所以在进行请求的时候会把浏览器卡住,体验稍有打折。~~ 后来用了网络版的JS执行服务后就可以异步了,避免出现把浏览器卡住的情况。
|
||||||
|
不过这个系统如果管道符不能用的话效果会差一大截,这可是Shell的灵魂啊,所以我提交了一个[Issue](https://github.com/tombl/linux/issues/25),希望作者能修一下。结果作者真的强得离谱,提出问题之后第二天就修复了,实在是太强了😨。但修好这个问题之后作者又整了一堆别的花活,把内存限制给取消了,结果我在Safari上一运行就报OOM的错误……于是我把这个问题又[反馈](https://github.com/tombl/linux/pull/26#issuecomment-5018155963)了一下,顺便让AI看看这个问题怎么解决,结果AI也不辜负我的期待,轻轻松松就解决了🤣。最终我除了会提出问题之外什么也不会做,只能看着大佬和AI完成我的想法了😝。
|
||||||
|
后来增加的以太网网关也挺折腾,原理其实并不复杂,但直接给AI说想用这个接口上网的时候它会自己想办法搓一套漏洞百出的TCP/IP协议栈,所以后来我在GitHub找了个[tcpip.js](https://github.com/chipmk/tcpip.js)库让它集成,BUG就比它自己手搓的少了不少。后来我又想加一个TLS终止功能,因为在浏览器上没办法直接让系统里的程序发送原始的TLS数据包去请求网站,但我又希望它能够请求带有https协议的网址,所以要提前对TLS进行解密再转换成`fetch()`,这时候我就没让AI自己手搓了,在网上找了个[node-forge](https://github.com/digitalbazaar/forge)库让AI对接,就可以避免出现一堆奇怪的BUG了。
|
||||||
|
做好网关之后我又突发奇想,想做一个展示终端内状态的页面,但是目前JS并不能主动和里面的系统直接通信,所以又让AI搓了一个通过Service Worker访问系统内网络服务的功能,然后用busybox的httpd执行sh写的cgi程序,就可以被前端读取到了。随后我还给原作者发了个[Issue](https://github.com/tombl/linux/issues/34)展示这个Service Worker的效果,结果人家已经做了这个功能,我还是太Naive了😂。
|
||||||
|
最终做好的网站可以在[这里](https://mabbs.github.io/linux/)查看,效果真的不是一般的好,相比于其他模仿终端形式的博客简直是降维打击,毕竟这是真的Linux啊~虽然目前依然有些BUG,但是原作者修BUG的速度还是挺快的,相信等他发布正式版之后,体验会更进一步。
|
||||||
|
|
||||||
|
## 基于JSNix的版本
|
||||||
|
虽然我觉得追求真实感很重要,但是我又回去看了一下为我提供想法的[JSNix](https://github.com/Erzbir/jsnix)。虽然这个项目并不是真的Linux,但是我让AI看了一遍之后它对这个项目的评价也很高,说是作者对Linux的了解非常深入,很多细节模拟的也非常到位。而且为WASM Linux制作的那些脚本,在这个JSNix上其实完全可以实现,还更简单,于是我直接让AI参考给WASM Linux写的脚本把那些功能全部移植到这个JSNix上。也许是因为AI更擅长编写网页,很快它就完成了这些功能,而且效果非常不错,基本上和WASM Linux上面的效果一样,表现也相当不错,最终生成的结果可以在[这里](https://mabbs.github.io/jsnix/)查看。
|
||||||
|
不过假的毕竟还是假的,和真的Linux差异还是有不少的,像正经的Shell可以在终端中直接用if、for和while之类的语法写一些东西,它这个就必须要编写成脚本才可以,而且解析也有相当多的问题。各种命令的运行结果很多都是硬编码的,只是编了一段对应命令的输出,参数做得也不全。另外著名的Linux文本三剑客grep、sed和awk在这个系统里实现得也非常差,只做了一些非常基础的功能,基本上就是完全不可用。所以说这个系统还是不够真实,只能做最基础的体验,不能够深究。
|
||||||
|
但并不是说它没有优点,它作为纯粹的JS不像WASM Linux那样容易出现兼容性问题,比如很多手机浏览器不支持跨源隔离,导致SharedArrayBuffer之类的API没法用,所以手机上只能体验这个JSNix的版本;另外WASM Linux的体积虽然说并不算很大,但也有好几MiB,不像这个JSNix连1MiB都不到;还有这个系统没有启动过程,相比于WASM Linux来说加载更快;而且在编写和修改功能的时候比WASM Linux简单不少,像给WASM Linux做的终端状态展示在JSNix上根本不需要折腾什么Service Worker,以及请求网络之类的也非常简单,直接`fetch()`就可以(也正是如此,JSNix的版本AI没一会儿就做完了)……总之就是只要不涉及具体功能方面JSNix都比WASM Linux要强不少,不过这样说有点“抛开事实不谈”的感觉了😂。
|
||||||
|
|
||||||
|
# 感想
|
||||||
|
最终做出来的这两个版本的终端版博客基本上所有的代码都不是我写的,真的做到了仅仅是和AI聊聊天就把东西做出来了……只能说AI现在真的不是一般的强啊~当然方案还是得自己考虑,因为AI一旦走错了方向就很容易往一个方向不停地钻,结果就是花了很长时间可能效果会很差,尤其是现在的AI还特别喜欢自己造轮子,所以这时候还是自己找找看已经有的方案然后告诉它会比较好。
|
||||||
|
从效果来说这次写的东西也让我非常满意,我想我这次做的这个博客界面估计是前所未有的,毕竟这可是在浏览器上用**真的**Linux访问博客啊,其他人做的都是假的。至于虚拟机那些方案可能也是因为太重,所以基本上没人用,而WASM Linux只有Thomas Stokes做的效果最好,但是又没有受到应有的关注……总之从我个人的角度来看这次做的东西还挺Cool,就是不知道会不会吸引其他人来看呢……可能还是太小众了吧😂。
|
||||||
@@ -6,7 +6,8 @@ cd blog
|
|||||||
mkdir Mabbs
|
mkdir Mabbs
|
||||||
curl -L -o Mabbs/README.md https://github.com/Mabbs/Mabbs/raw/main/README.md
|
curl -L -o Mabbs/README.md https://github.com/Mabbs/Mabbs/raw/main/README.md
|
||||||
bundle exec jekyll build -d ../public_html
|
bundle exec jekyll build -d ../public_html
|
||||||
rsync --delete -rv ../public_html/ mayx@pgs.sh:/blog
|
ssh pgs.sh rm blog --write
|
||||||
|
rsync --delete --exclude='.git' -rv ../public_html/ mayx@pgs.sh:/blog
|
||||||
tar czvf MayxBlog.tgz --exclude-vcs ../public_html/
|
tar czvf MayxBlog.tgz --exclude-vcs ../public_html/
|
||||||
mv MayxBlog.tgz ../public_html/
|
mv MayxBlog.tgz ../public_html/
|
||||||
cd ../public_html/
|
cd ../public_html/
|
||||||
|
|||||||
866
assets/js/blog-console.js
Normal file
866
assets/js/blog-console.js
Normal file
@@ -0,0 +1,866 @@
|
|||||||
|
/*!
|
||||||
|
* blog-console.js — Mayx's Blog DevTools Console API
|
||||||
|
*/
|
||||||
|
(function (global) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
* 0. 常量与配置
|
||||||
|
* ===================================================================== */
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
/** list() 默认每页条数 */
|
||||||
|
pageSize: 10,
|
||||||
|
/** show() 单次最多渲染的行数,防止超长文章刷屏 */
|
||||||
|
maxShowLines: 600,
|
||||||
|
/** 摘要/预览截断长度 */
|
||||||
|
previewLength: 120
|
||||||
|
};
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
* 1. 控制台样式(DevTools 用 %c + CSS)
|
||||||
|
* ===================================================================== */
|
||||||
|
|
||||||
|
var S = {
|
||||||
|
reset: '',
|
||||||
|
title: 'font-weight:bold;font-size:13px;color:#3fb950',
|
||||||
|
sub: 'color:#8b949e',
|
||||||
|
num: 'color:#d29922;font-weight:bold',
|
||||||
|
date: 'color:#58a6ff',
|
||||||
|
strong: 'font-weight:bold',
|
||||||
|
link: 'color:#58a6ff;text-decoration:underline',
|
||||||
|
ok: 'color:#3fb950;font-weight:bold',
|
||||||
|
warn: 'color:#d29922',
|
||||||
|
err: 'color:#f85149;font-weight:bold',
|
||||||
|
dim: 'color:#8b949e',
|
||||||
|
code: 'color:#e3b341;font-family:ui-monospace,Consolas,monospace;background:rgba(110,118,129,.18);padding:0 3px;border-radius:3px',
|
||||||
|
quote: 'color:#8b949e;font-style:italic',
|
||||||
|
hr: 'color:#6e7781',
|
||||||
|
tag: 'color:#a371f7',
|
||||||
|
h: [
|
||||||
|
'',
|
||||||
|
'font-weight:bold;font-size:16px;color:#f85149',
|
||||||
|
'font-weight:bold;font-size:15px;color:#3fb950',
|
||||||
|
'font-weight:bold;font-size:14px;color:#d29922',
|
||||||
|
'font-weight:bold;font-size:13px;color:#58a6ff',
|
||||||
|
'font-weight:bold;font-size:13px;color:#a371f7',
|
||||||
|
'font-weight:bold;font-size:13px;color:#39c5cf'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分段样式打印器。收集 [文本, CSS] 片段,分批 flush 到 console.log,
|
||||||
|
* 既能保证多行样式连续,又避免单条消息过长被浏览器截断。
|
||||||
|
*/
|
||||||
|
function Printer(chunkSize) {
|
||||||
|
this.chunk = chunkSize || 160;
|
||||||
|
this.fmt = [];
|
||||||
|
this.css = [];
|
||||||
|
this.pending = 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 追加一个带样式的片段(不换行)。
|
||||||
|
* @param {string} text 文本内容
|
||||||
|
* @param {string} [css] CSS 样式串,省略则使用默认样式
|
||||||
|
*/
|
||||||
|
Printer.prototype.push = function (text, css) {
|
||||||
|
// 转义 %,防止与 console 的格式化占位符冲突
|
||||||
|
this.fmt.push('%c' + String(text).replace(/%/g, '%%'));
|
||||||
|
this.css.push(css || '');
|
||||||
|
this.pending++;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
/** 追加一行(自动换行),并在片段数超过阈值时 flush。 */
|
||||||
|
Printer.prototype.line = function (text, css) {
|
||||||
|
this.push((text === undefined ? '' : text) + '\n', css);
|
||||||
|
if (this.pending >= this.chunk) this.flush();
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
/** 结束当前行。 */
|
||||||
|
Printer.prototype.br = function () {
|
||||||
|
this.push('\n', '');
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
/** 把已累积的片段输出到控制台。 */
|
||||||
|
Printer.prototype.flush = function () {
|
||||||
|
if (!this.fmt.length) return this;
|
||||||
|
var msg = this.fmt.join('');
|
||||||
|
// 去掉行尾多余换行,避免每次 flush 产生空行
|
||||||
|
msg = msg.replace(/\n$/, '');
|
||||||
|
console.log.apply(console, [msg].concat(this.css));
|
||||||
|
this.fmt = [];
|
||||||
|
this.css = [];
|
||||||
|
this.pending = 0;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 打印一条分隔标题横幅。 */
|
||||||
|
function banner(text) {
|
||||||
|
var p = new Printer();
|
||||||
|
p.line('── ' + text + ' ' + repeat('─', Math.max(2, 46 - strWidth(text))), S.title);
|
||||||
|
p.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
function repeat(ch, n) { return n > 0 ? new Array(n + 1).join(ch) : ''; }
|
||||||
|
|
||||||
|
/** 粗略估算显示宽度(中日韩字符按 2 计)。 */
|
||||||
|
function strWidth(s) {
|
||||||
|
var w = 0;
|
||||||
|
for (var i = 0; i < s.length; i++) {
|
||||||
|
w += /[\u2E80-\uFFFF]/.test(s[i]) ? 2 : 1;
|
||||||
|
}
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打印错误并返回 null,统一失败出口。 */
|
||||||
|
function fail(msg) {
|
||||||
|
console.log('%c⚠️ ' + msg, S.err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
* 2. 宿主环境依赖层
|
||||||
|
* ===================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GitHub Issues 访问用的 Basic 凭据。
|
||||||
|
* @returns {{headers:object, owner:string, repo:string}}
|
||||||
|
*/
|
||||||
|
function githubAuth() {
|
||||||
|
var g = GitalkConfig;
|
||||||
|
return {
|
||||||
|
headers: { Authorization: 'Basic ' + btoa(g.clientID + ':' + g.clientSecret) },
|
||||||
|
owner: g.owner,
|
||||||
|
repo: g.repo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取搜索索引 search.json,复用 main.js 的 getSearchJSON(带 localStorage 缓存)。
|
||||||
|
* @returns {Promise<Array>} search.json 原始数组
|
||||||
|
*/
|
||||||
|
function loadSearchJSON() {
|
||||||
|
return new Promise(function (resolve) {
|
||||||
|
getSearchJSON(resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 懒加载站点自带的 SimpleJekyllSearch。
|
||||||
|
* 该库只在 /search.html 里被引入,其他页面需要时动态注入同一个文件,
|
||||||
|
* 从而保证控制台搜索与页面搜索使用完全一致的匹配/排序逻辑。
|
||||||
|
* @returns {Promise<Function>} SimpleJekyllSearch 工厂函数
|
||||||
|
* @throws {Error} 脚本加载失败时 reject
|
||||||
|
*/
|
||||||
|
function ensureSimpleJekyllSearch() {
|
||||||
|
if (typeof global.SimpleJekyllSearch === 'function') {
|
||||||
|
return Promise.resolve(global.SimpleJekyllSearch);
|
||||||
|
}
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
var s = document.createElement('script');
|
||||||
|
s.src = '/assets/js/simple-jekyll-search.min.js';
|
||||||
|
s.async = true;
|
||||||
|
s.onload = function () {
|
||||||
|
if (typeof global.SimpleJekyllSearch === 'function') resolve(global.SimpleJekyllSearch);
|
||||||
|
};
|
||||||
|
s.onerror = function () {
|
||||||
|
reject(new Error('无法加载 SimpleJekyllSearch'));
|
||||||
|
};
|
||||||
|
document.head.appendChild(s);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
* 3. 通用请求工具
|
||||||
|
* ===================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求 JSON,失败返回 null(不抛异常,便于控制台链式使用)。
|
||||||
|
* @param {string} url
|
||||||
|
* @param {object} [options] fetch 选项
|
||||||
|
* @returns {Promise<any|null>}
|
||||||
|
*/
|
||||||
|
function fetchJSON(url, options) {
|
||||||
|
return fetch(url, options || {})
|
||||||
|
.then(function (r) { return r.ok ? r.json() : null; })
|
||||||
|
.catch(function () { return null; });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求纯文本,失败返回 null。
|
||||||
|
* @param {string} url
|
||||||
|
* @param {object} [options] fetch 选项
|
||||||
|
* @returns {Promise<string|null>}
|
||||||
|
*/
|
||||||
|
function fetchText(url, options) {
|
||||||
|
return fetch(url, options || {})
|
||||||
|
.then(function (r) { return r.ok ? r.text() : null; })
|
||||||
|
.catch(function () { return null; });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解码 HTML 实体(search.json 的 title 经过 Liquid escape 过滤器处理)。 */
|
||||||
|
function unescapeHTML(str) {
|
||||||
|
if (!str || str.indexOf('&') === -1) return str || '';
|
||||||
|
var el = document.createElement('textarea');
|
||||||
|
el.innerHTML = str;
|
||||||
|
return el.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
* 4. 数据层
|
||||||
|
* ===================================================================== */
|
||||||
|
|
||||||
|
var _articles = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规范化 search.json 的一条记录。
|
||||||
|
* @param {object} item search.json 原始项
|
||||||
|
* @param {number} index 从 0 开始的下标
|
||||||
|
* @returns {{num:number,title:string,url:string,date:string,category:string,tags:string[],content:string,excerpt:string,link:string}}
|
||||||
|
*/
|
||||||
|
function normalize(item, index) {
|
||||||
|
var content = item.content || '';
|
||||||
|
var tags = (item.tags || '')
|
||||||
|
.split(',')
|
||||||
|
.map(function (t) { return t.trim(); })
|
||||||
|
.filter(Boolean);
|
||||||
|
return {
|
||||||
|
num: index + 1,
|
||||||
|
title: unescapeHTML(item.title || ''),
|
||||||
|
url: item.url || '',
|
||||||
|
date: item.date || '',
|
||||||
|
category: item.category || '',
|
||||||
|
tags: tags,
|
||||||
|
content: content,
|
||||||
|
excerpt: content.slice(0, config.previewLength) +
|
||||||
|
(content.length > config.previewLength ? '……' : ''),
|
||||||
|
link: item.url || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得全部文章(含缓存)。
|
||||||
|
* 注意:search.json 由 Jekyll 生成时已排除 layout 为 encrypt 的加密文章,
|
||||||
|
* 因此这里的序号 num 是「非加密文章」的序号。
|
||||||
|
* @param {boolean} [force] 传 true 强制重新拉取
|
||||||
|
* @returns {Promise<Array>} 规范化后的文章数组,最新的在前
|
||||||
|
*/
|
||||||
|
function getArticles(force) {
|
||||||
|
if (_articles && !force) return Promise.resolve(_articles);
|
||||||
|
return loadSearchJSON().then(function (data) {
|
||||||
|
_articles = (data || []).map(normalize);
|
||||||
|
return _articles;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把各种形式的标识解析为一篇文章。
|
||||||
|
* @param {number|string} id 序号(1 起) / 文章 URL / 标题(支持模糊包含匹配)
|
||||||
|
* @returns {Promise<object|null>} 命中的文章对象,未命中为 null
|
||||||
|
*/
|
||||||
|
function resolve(id) {
|
||||||
|
return getArticles().then(function (list) {
|
||||||
|
if (!list || !list.length) return null;
|
||||||
|
if (id === undefined || id === null || id === '') {
|
||||||
|
// 无参时默认取当前页面对应的文章
|
||||||
|
return matchByPath(list, global.location.pathname);
|
||||||
|
}
|
||||||
|
// 1) 纯数字序号
|
||||||
|
var n = parseInt(id, 10);
|
||||||
|
if (!isNaN(n) && String(n) === String(id).trim()) {
|
||||||
|
return (n >= 1 && n <= list.length) ? list[n - 1] : null;
|
||||||
|
}
|
||||||
|
var s = String(id).trim();
|
||||||
|
// 2) 精确 URL / 路径
|
||||||
|
var byUrl = matchByPath(list, s);
|
||||||
|
if (byUrl) return byUrl;
|
||||||
|
// 3) 标题包含匹配(大小写不敏感)
|
||||||
|
var low = s.toLowerCase();
|
||||||
|
var hit = list.filter(function (a) {
|
||||||
|
return a.title.toLowerCase().indexOf(low) !== -1;
|
||||||
|
});
|
||||||
|
return hit.length ? hit[0] : null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按路径匹配文章(容忍 URL 编码差异与站点前缀)。 */
|
||||||
|
function matchByPath(list, path) {
|
||||||
|
if (!path) return null;
|
||||||
|
var dec = path, p = path;
|
||||||
|
try { dec = decodeURIComponent(p); } catch (e) { }
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
var u = list[i].url, ud = u;
|
||||||
|
try { ud = decodeURIComponent(u); } catch (e) { }
|
||||||
|
if (u === p || ud === dec || u === dec || ud === p) return list[i];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 由文章对象推导其原始 Markdown 文件的 raw 地址。
|
||||||
|
* @param {object} article
|
||||||
|
* @returns {string} raw.githubusercontent.com 上的 .md 地址
|
||||||
|
*/
|
||||||
|
function rawUrlOf(article) {
|
||||||
|
var dateDash = (article.date || '').replace(/\//g, '-');
|
||||||
|
var last = (article.url || '').split('/').pop();
|
||||||
|
try { last = decodeURIComponent(last); } catch (e) { }
|
||||||
|
var slug = last.replace(/\.html$/, '');
|
||||||
|
return 'https://raw.githubusercontent.com/Mabbs/mabbs.github.io/refs/heads/master/_posts/' + dateDash + '-' + slug + '.md';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
* 5. Markdown → 控制台样式渲染
|
||||||
|
* ===================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行内标记解析:`code`、**bold**、*italic*、~~del~~、[text](url)、
|
||||||
|
* @param {Printer} p
|
||||||
|
* @param {string} text
|
||||||
|
* @param {string} baseCss 该行的基础样式
|
||||||
|
*/
|
||||||
|
function inline(p, text, baseCss) {
|
||||||
|
var re = /(`[^`]+`)|(\*\*[^*]+\*\*|__[^_]+__)|(\*[^*\n]+\*|_[^_\n]+_)|(~~[^~]+~~)|(!?\[[^\]]*\]\([^)]*\))/g;
|
||||||
|
var last = 0, m;
|
||||||
|
while ((m = re.exec(text)) !== null) {
|
||||||
|
if (m.index > last) p.push(text.slice(last, m.index), baseCss);
|
||||||
|
var tok = m[0];
|
||||||
|
if (m[1]) {
|
||||||
|
p.push(tok.slice(1, -1), S.code);
|
||||||
|
} else if (m[2]) {
|
||||||
|
p.push(tok.slice(2, -2), baseCss + ';font-weight:bold');
|
||||||
|
} else if (m[3]) {
|
||||||
|
p.push(tok.slice(1, -1), baseCss + ';font-style:italic');
|
||||||
|
} else if (m[4]) {
|
||||||
|
p.push(tok.slice(2, -2), baseCss + ';text-decoration:line-through;opacity:.7');
|
||||||
|
} else if (m[5]) {
|
||||||
|
var mm = /^(!?)\[([^\]]*)\]\(([^)]*)\)$/.exec(tok);
|
||||||
|
if (mm) {
|
||||||
|
var label = mm[2] || (mm[1] ? '图片' : '链接');
|
||||||
|
p.push((mm[1] ? '🖼 ' : '') + label, S.link);
|
||||||
|
if (mm[3]) p.push(' (' + mm[3] + ')', S.dim);
|
||||||
|
} else {
|
||||||
|
p.push(tok, baseCss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last = m.index + tok.length;
|
||||||
|
}
|
||||||
|
if (last < text.length) p.push(text.slice(last), baseCss);
|
||||||
|
p.push('\n', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染整篇 Markdown 到控制台。
|
||||||
|
* @param {string} md Markdown 原文
|
||||||
|
* @param {object} [opts]
|
||||||
|
* @param {number} [opts.maxLines] 最大渲染行数
|
||||||
|
* @param {boolean} [opts.frontMatter=true] 是否显示 YAML front matter
|
||||||
|
*/
|
||||||
|
function renderMarkdown(md, opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
var maxLines = opts.maxLines || config.maxShowLines;
|
||||||
|
var showFM = opts.frontMatter !== false;
|
||||||
|
|
||||||
|
var lines = md.split('\n');
|
||||||
|
var p = new Printer();
|
||||||
|
var inCode = false, inFM = false, rendered = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < lines.length && rendered < maxLines; i++) {
|
||||||
|
var raw = lines[i];
|
||||||
|
var t = raw.replace(/^\s+/, '');
|
||||||
|
|
||||||
|
// YAML front matter
|
||||||
|
if (i === 0 && /^---\s*$/.test(t)) { inFM = true; if (showFM) p.line(raw, S.dim); rendered++; continue; }
|
||||||
|
if (inFM) {
|
||||||
|
if (/^---\s*$/.test(t)) inFM = false;
|
||||||
|
if (showFM) { p.line(raw, S.dim); rendered++; }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 围栏代码块
|
||||||
|
if (/^```/.test(t) || /^~~~/.test(t)) {
|
||||||
|
inCode = !inCode;
|
||||||
|
p.line(raw, S.dim);
|
||||||
|
rendered++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inCode) { p.line(raw, S.code); rendered++; continue; }
|
||||||
|
|
||||||
|
if (t === '') { p.line(''); rendered++; continue; }
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
var h = t.match(/^(#{1,6})\s+/);
|
||||||
|
if (h) {
|
||||||
|
p.line(raw, S.h[h[1].length] || S.h[6]);
|
||||||
|
rendered++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 水平分割线
|
||||||
|
var hr = t.replace(/\s/g, '');
|
||||||
|
if (/^-{3,}$/.test(hr) || /^\*{3,}$/.test(hr) || /^_{3,}$/.test(hr)) {
|
||||||
|
p.line(raw, S.hr); rendered++; continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 引用
|
||||||
|
if (/^>/.test(t)) { p.line(raw, S.quote); rendered++; continue; }
|
||||||
|
|
||||||
|
// 无序列表
|
||||||
|
if (/^[-*+]\s/.test(t)) {
|
||||||
|
var ind = raw.match(/^(\s*)/)[1];
|
||||||
|
p.push(ind + t[0] + ' ', S.ok);
|
||||||
|
inline(p, t.replace(/^[-*+]\s+/, ''), '');
|
||||||
|
rendered++;
|
||||||
|
if (p.pending >= p.chunk) p.flush();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 有序列表
|
||||||
|
if (/^\d+\.\s/.test(t)) {
|
||||||
|
var ind2 = raw.match(/^(\s*)/)[1];
|
||||||
|
var mk = t.match(/^\d+\./)[0];
|
||||||
|
p.push(ind2 + mk + ' ', S.num);
|
||||||
|
inline(p, t.replace(/^\d+\.\s+/, ''), '');
|
||||||
|
rendered++;
|
||||||
|
if (p.pending >= p.chunk) p.flush();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline(p, raw, '');
|
||||||
|
rendered++;
|
||||||
|
if (p.pending >= p.chunk) p.flush();
|
||||||
|
}
|
||||||
|
p.flush();
|
||||||
|
|
||||||
|
if (lines.length > rendered) {
|
||||||
|
console.log('%c… 已省略 ' + (lines.length - rendered) + ' 行', S.warn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
* 6. 对外 API
|
||||||
|
* ===================================================================== */
|
||||||
|
|
||||||
|
var Blog = {};
|
||||||
|
|
||||||
|
/** 运行期配置对象,可直接修改 @type {object} */
|
||||||
|
Blog.config = config;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- 数据
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按序号 / URL / 标题定位一篇文章并打印其元信息。
|
||||||
|
* @param {number|string} [id] 序号(从 1 开始) / 文章 URL / 标题关键字;
|
||||||
|
* 省略时取当前页面对应的文章
|
||||||
|
* @returns {Promise<object|null>} 文章对象,未找到返回 null
|
||||||
|
* @example await Blog.get(1); await Blog.get('/2015/02/23/diary.html'); await Blog.get('日记')
|
||||||
|
*/
|
||||||
|
Blog.get = function (id) {
|
||||||
|
return resolve(id).then(function (a) {
|
||||||
|
if (!a) return fail('未找到文章: ' + id);
|
||||||
|
banner('文章 #' + a.num);
|
||||||
|
var p = new Printer();
|
||||||
|
p.line(a.title, S.title);
|
||||||
|
p.push('日期 ', S.dim).line(a.date, S.date);
|
||||||
|
p.push('链接 ', S.dim).line(a.link, S.link);
|
||||||
|
if (a.category) p.push('分类 ', S.dim).line(a.category, S.tag);
|
||||||
|
if (a.tags.length) p.push('标签 ', S.dim).line(a.tags.join(' #'), S.tag);
|
||||||
|
p.push('字数 ', S.dim).line(String(a.content.length), S.num);
|
||||||
|
p.line('');
|
||||||
|
p.line(a.excerpt, S.sub);
|
||||||
|
p.flush();
|
||||||
|
return a;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页列出文章(控制台以表格呈现)。
|
||||||
|
* @param {number} [page=1] 页码,从 1 开始
|
||||||
|
* @param {number} [pageSize] 每页条数,默认取 Blog.config.pageSize(10)
|
||||||
|
* @returns {Promise<Array|null>} 当前页的文章数组
|
||||||
|
* @example await Blog.list(2)
|
||||||
|
*/
|
||||||
|
Blog.list = function (page, pageSize) {
|
||||||
|
page = parseInt(page, 10) || 1;
|
||||||
|
pageSize = parseInt(pageSize, 10) || config.pageSize;
|
||||||
|
if (page < 1) return Promise.resolve(fail('页码必须是正整数'));
|
||||||
|
|
||||||
|
return getArticles().then(function (list) {
|
||||||
|
if (!list) return fail('无法获取文章列表');
|
||||||
|
var total = list.length;
|
||||||
|
var totalPages = Math.ceil(total / pageSize);
|
||||||
|
var start = (page - 1) * pageSize;
|
||||||
|
if (start >= total) return fail('第 ' + page + ' 页没有文章(共 ' + totalPages + ' 页)');
|
||||||
|
|
||||||
|
var slice = list.slice(start, Math.min(start + pageSize, total));
|
||||||
|
banner('博客文章列表 · 第 ' + page + '/' + totalPages + ' 页');
|
||||||
|
var obj = {};
|
||||||
|
slice.forEach(function (a) {
|
||||||
|
obj[a.num] = { '日期': a.date, '标题': a.title };
|
||||||
|
});
|
||||||
|
|
||||||
|
console.table(obj);
|
||||||
|
console.log('%c共 ' + total + ' 篇 · Blog.show(id) 读正文 · Blog.open(id) 跳转 · Blog.comment(id) 看评论', S.sub);
|
||||||
|
return slice;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索文章。复用站点自带的 SimpleJekyllSearch,
|
||||||
|
* 与 /search.html 使用完全一致的匹配与排序规则。
|
||||||
|
* @param {string} keyword 关键词
|
||||||
|
* @param {object} [opts]
|
||||||
|
* @param {number} [opts.limit=10] 最大结果数
|
||||||
|
* @param {boolean} [opts.fuzzy=false] 是否启用模糊匹配
|
||||||
|
* @returns {Promise<Array|null>} 命中的文章数组
|
||||||
|
* @example await Blog.search('Jekyll')
|
||||||
|
*/
|
||||||
|
Blog.search = function (keyword, opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
var limit = opts.limit || 10;
|
||||||
|
if (!keyword) return Promise.resolve(fail('请提供关键词,例如 Blog.search("Jekyll")'));
|
||||||
|
|
||||||
|
return getArticles().then(function (list) {
|
||||||
|
if (!list) return fail('无法获取文章列表');
|
||||||
|
return ensureSimpleJekyllSearch().then(function (SJS) {
|
||||||
|
var results = searchWithSJS(SJS, list, keyword, limit, !!opts.fuzzy);
|
||||||
|
render(results);
|
||||||
|
return results;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function render(results) {
|
||||||
|
banner('搜索「' + keyword + '」· ' + results.length + ' 条结果');
|
||||||
|
if (!results.length) {
|
||||||
|
console.log('%c没有匹配的文章。', S.warn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var obj = {};
|
||||||
|
results.forEach(function (a) {
|
||||||
|
obj[a.num] = { '日期': a.date, '标题': a.title, '摘要': a.excerpt.slice(0, 40) };
|
||||||
|
});
|
||||||
|
|
||||||
|
console.table(obj);
|
||||||
|
console.log('%c用 Blog.show(' + results[0].num + ') 查看第一条结果。', S.sub);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 借助 SimpleJekyllSearch 在游离 DOM 节点上取得搜索结果。
|
||||||
|
* 结果模板只输出 url,再用 url 反查完整文章对象。
|
||||||
|
* @param {Function} SJS SimpleJekyllSearch 工厂函数
|
||||||
|
* @param {Array} list 全部文章
|
||||||
|
* @param {string} keyword 关键词
|
||||||
|
* @param {number} limit 最大结果数
|
||||||
|
* @param {boolean} fuzzy 是否模糊匹配
|
||||||
|
* @returns {Array} 命中的文章数组
|
||||||
|
*/
|
||||||
|
function searchWithSJS(SJS, list, keyword, limit, fuzzy) {
|
||||||
|
// SimpleJekyllSearch 会对每个字段调用 String.prototype.trim,
|
||||||
|
// 因此必须喂给它与 search.json 一致的「全字符串」结构,
|
||||||
|
// 而不是规范化后的对象(num 为数字、tags 为数组会直接报错)。
|
||||||
|
var flat = list.map(function (a) {
|
||||||
|
return {
|
||||||
|
title: a.title,
|
||||||
|
category: a.category,
|
||||||
|
tags: a.tags.join(' '),
|
||||||
|
url: a.url,
|
||||||
|
date: a.date,
|
||||||
|
content: a.content
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
var box = document.createElement('div');
|
||||||
|
SJS({
|
||||||
|
searchInput: document.createElement('input'),
|
||||||
|
resultsContainer: box,
|
||||||
|
json: flat,
|
||||||
|
searchResultTemplate: '<i data-u="{url}"></i>',
|
||||||
|
noResultsText: '',
|
||||||
|
limit: limit,
|
||||||
|
fuzzy: fuzzy
|
||||||
|
}).search(keyword);
|
||||||
|
|
||||||
|
var byUrl = {};
|
||||||
|
list.forEach(function (a) { byUrl[a.url] = a; });
|
||||||
|
var out = [];
|
||||||
|
Array.prototype.forEach.call(box.querySelectorAll('i[data-u]'), function (el) {
|
||||||
|
var a = byUrl[el.getAttribute('data-u')];
|
||||||
|
if (a) out.push(a);
|
||||||
|
});
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用正则表达式检索全部文章正文,并打印命中的上下文片段。
|
||||||
|
* (search.json 已内联全文,因此无需额外网络请求)
|
||||||
|
* @param {string|RegExp} pattern 正则或字符串
|
||||||
|
* @param {object} [opts]
|
||||||
|
* @param {number} [opts.context=40] 命中处前后保留的字符数
|
||||||
|
* @param {number} [opts.limit=20] 最多显示的命中条目数
|
||||||
|
* @returns {Promise<Array<{article:object,matches:string[]}>>}
|
||||||
|
* @example await Blog.grep(/Cloudflare\s*Workers?/i)
|
||||||
|
*/
|
||||||
|
Blog.grep = function (pattern, opts) {
|
||||||
|
if (!pattern) return Promise.resolve(fail('请提供关键词,例如 Blog.grep("Jekyll")'));
|
||||||
|
opts = opts || {};
|
||||||
|
var ctx = opts.context || 40;
|
||||||
|
var limit = opts.limit || 20;
|
||||||
|
var re;
|
||||||
|
try {
|
||||||
|
re = pattern instanceof RegExp
|
||||||
|
? new RegExp(pattern.source, pattern.flags.indexOf('g') === -1 ? pattern.flags + 'g' : pattern.flags)
|
||||||
|
: new RegExp(String(pattern).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.resolve(fail('无效的正则表达式: ' + e.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
return getArticles().then(function (list) {
|
||||||
|
if (!list) return fail('无法获取文章列表');
|
||||||
|
var out = [];
|
||||||
|
for (var i = 0; i < list.length && out.length < limit; i++) {
|
||||||
|
var a = list[i], m, hits = [];
|
||||||
|
re.lastIndex = 0;
|
||||||
|
while ((m = re.exec(a.content)) !== null && hits.length < 3) {
|
||||||
|
var s = Math.max(0, m.index - ctx);
|
||||||
|
var e = Math.min(a.content.length, m.index + m[0].length + ctx);
|
||||||
|
hits.push((s > 0 ? '…' : '') + a.content.slice(s, e) + (e < a.content.length ? '…' : ''));
|
||||||
|
if (m[0] === '') re.lastIndex++;
|
||||||
|
}
|
||||||
|
if (hits.length) out.push({ article: a, matches: hits });
|
||||||
|
}
|
||||||
|
|
||||||
|
banner('grep ' + re + ' · ' + out.length + ' 篇命中');
|
||||||
|
if (!out.length) { console.log('%c无命中。', S.warn); return out; }
|
||||||
|
var p = new Printer();
|
||||||
|
out.forEach(function (r) {
|
||||||
|
p.push('#' + r.article.num + ' ', S.num).line(r.article.title, S.strong);
|
||||||
|
r.matches.forEach(function (t) { p.line(' ' + t, S.sub); });
|
||||||
|
});
|
||||||
|
p.flush();
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- 内容
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉取文章的原始 Markdown 并在控制台做语法着色渲染。
|
||||||
|
* @param {number|string} id 序号 / URL / 标题
|
||||||
|
* @param {object} [opts]
|
||||||
|
* @param {number} [opts.maxLines] 最多渲染行数,默认 Blog.config.maxShowLines(600)
|
||||||
|
* @param {boolean} [opts.frontMatter=true] 是否显示 YAML front matter
|
||||||
|
* @param {boolean} [opts.raw=false] 为 true 时跳过渲染,直接返回原文
|
||||||
|
* @returns {Promise<string|null>} Markdown 原文
|
||||||
|
* @example await Blog.show(1); await Blog.show(1, { raw: true })
|
||||||
|
*/
|
||||||
|
Blog.show = function (id, opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
return resolve(id).then(function (a) {
|
||||||
|
if (!a) return fail('未找到文章: ' + id);
|
||||||
|
var url = rawUrlOf(a);
|
||||||
|
return fetchText(url).then(function (md) {
|
||||||
|
if (md === null) return fail('无法获取原始 Markdown:' + url);
|
||||||
|
if (opts.raw) return md;
|
||||||
|
banner(a.title);
|
||||||
|
var p = new Printer();
|
||||||
|
p.push('日期 ', S.dim).push(a.date, S.date)
|
||||||
|
.push(' 来源 ', S.dim).line(url, S.link);
|
||||||
|
p.line('');
|
||||||
|
p.flush();
|
||||||
|
renderMarkdown(md, opts);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文章的 Gitalk 评论(走 GitHub Issues API)。
|
||||||
|
* @param {number|string} id 序号 / URL / 标题
|
||||||
|
* @returns {Promise<Array|null>} 评论数组 [{author, date, body}]
|
||||||
|
* @example await Blog.comment(1)
|
||||||
|
*/
|
||||||
|
Blog.comment = function (id) {
|
||||||
|
return resolve(id).then(function (a) {
|
||||||
|
if (!a) return fail('未找到文章: ' + id);
|
||||||
|
var auth = githubAuth();
|
||||||
|
var label = a.url.replace(/\.html$/, '');
|
||||||
|
var api = 'https://api.github.com/repos/' + auth.owner + '/' + auth.repo +
|
||||||
|
'/issues?labels=' + encodeURIComponent('Gitalk,' + label);
|
||||||
|
|
||||||
|
return fetchJSON(api, { headers: auth.headers }).then(function (issues) {
|
||||||
|
if (!issues || !issues.length) {
|
||||||
|
console.log('%c📭 文章「' + a.title + '」暂无评论(未找到对应 Issue)', S.warn);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return fetchJSON(issues[0].comments_url, { headers: auth.headers }).then(function (cs) {
|
||||||
|
if (!cs || !cs.length) {
|
||||||
|
console.log('%c📭 文章「' + a.title + '」暂无评论', S.warn);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
var out = cs.map(function (c) {
|
||||||
|
return {
|
||||||
|
author: (c.user && c.user.login) || 'unknown',
|
||||||
|
date: c.created_at || '',
|
||||||
|
body: c.body || ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
banner('评论 · ' + a.title + ' · ' + out.length + ' 条');
|
||||||
|
var p = new Printer();
|
||||||
|
out.forEach(function (c, i) {
|
||||||
|
p.push((i + 1) + '. ', S.num)
|
||||||
|
.push(c.author, S.ok)
|
||||||
|
.line(' ' + c.date, S.dim);
|
||||||
|
c.body.split('\n').forEach(function (l) { p.line(' ' + l, ''); });
|
||||||
|
p.line('');
|
||||||
|
});
|
||||||
|
p.flush();
|
||||||
|
console.log('%c原 Issue: ' + issues[0].html_url, S.sub);
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- 导航
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开一篇文章。默认复用 pjax.js 的 window.go() 做站内无刷新跳转。
|
||||||
|
* @param {number|string} id 序号 / URL / 标题
|
||||||
|
* @param {object} [opts]
|
||||||
|
* @param {boolean} [opts.newTab=false] 为 true 时改用新标签页打开
|
||||||
|
* @returns {Promise<object|null>} 被打开的文章对象
|
||||||
|
* @example await Blog.open(1); await Blog.open(1, { newTab: true })
|
||||||
|
*/
|
||||||
|
Blog.open = function (id, opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
return resolve(id).then(function (a) {
|
||||||
|
if (!a) return fail('未找到文章: ' + id);
|
||||||
|
if (opts.newTab) {
|
||||||
|
var w = global.open(a.link, '_blank', 'noopener,noreferrer');
|
||||||
|
if (!w) return fail('浏览器阻止了弹出窗口,请允许后重试');
|
||||||
|
console.log('%c✅ 已在新标签页打开:%c' + a.title, S.ok, S.strong);
|
||||||
|
} else {
|
||||||
|
go(a.url);
|
||||||
|
console.log('%c✅ 正在跳转:%c' + a.title, S.ok, S.strong);
|
||||||
|
}
|
||||||
|
console.log('%c' + a.link, S.link);
|
||||||
|
return a;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机打开一篇文章。等价于首页 "Random" 链接的逻辑
|
||||||
|
* (getSearchJSON + go 的组合),此处复用同一对函数。
|
||||||
|
* @param {object} [opts]
|
||||||
|
* @param {boolean} [opts.open=true] 为 false 时只返回文章不跳转
|
||||||
|
* @returns {Promise<object|null>} 随机选中的文章
|
||||||
|
* @example await Blog.random({ open: false })
|
||||||
|
*/
|
||||||
|
Blog.random = function (opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
return getArticles().then(function (list) {
|
||||||
|
if (!list || !list.length) return fail('无法获取文章列表');
|
||||||
|
var a = list[Math.floor(Math.random() * list.length)];
|
||||||
|
console.log('%c🎲 随机文章 #' + a.num + ':%c' + a.title, S.ok, S.strong);
|
||||||
|
console.log('%c' + a.link, S.link);
|
||||||
|
if (opts.open !== false) go(a.url);
|
||||||
|
return a;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回当前页面对应的文章信息(若当前不是文章页则返回 null)。
|
||||||
|
* @returns {Promise<object|null>}
|
||||||
|
* @example await Blog.current()
|
||||||
|
*/
|
||||||
|
Blog.current = function () {
|
||||||
|
return getArticles().then(function (list) {
|
||||||
|
var a = list ? matchByPath(list, global.location.pathname) : null;
|
||||||
|
if (!a) {
|
||||||
|
console.log('%c当前页面不是文章页:' + global.location.pathname, S.warn);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Blog.get(a.num);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- 其他
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示站点作者信息(读取 /humans.txt)。。
|
||||||
|
* @returns {Promise<string|null>} humans.txt 全文
|
||||||
|
* @example await Blog.about()
|
||||||
|
*/
|
||||||
|
Blog.about = function () {
|
||||||
|
return fetchText('/humans.txt').then(function (t) {
|
||||||
|
if (t === null) return fail('无法获取 humans.txt');
|
||||||
|
banner('关于本站');
|
||||||
|
console.log('%c' + t, 'line-height:1.5');
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印全部可用命令的帮助信息。
|
||||||
|
* @returns {void}
|
||||||
|
* @example Blog.help()
|
||||||
|
*/
|
||||||
|
Blog.help = function () {
|
||||||
|
var groups = [
|
||||||
|
['数据查询', [
|
||||||
|
['Blog.list(page, size)', '分页列出文章,表格输出,页码从 1 开始'],
|
||||||
|
['Blog.get(id)', '查看单篇文章元信息,id 支持 序号/URL/标题'],
|
||||||
|
['Blog.search(kw, opts)', '搜索文章'],
|
||||||
|
['Blog.grep(re, opts)', '用正则检索全文并显示上下文片段']
|
||||||
|
]],
|
||||||
|
['内容读取', [
|
||||||
|
['Blog.show(id, opts)', '阅读文章正文'],
|
||||||
|
['Blog.comment(id)', '获取 Gitalk 评论(GitHub Issues)'],
|
||||||
|
['Blog.current()', '当前页面对应的文章信息']
|
||||||
|
]],
|
||||||
|
['导航跳转', [
|
||||||
|
['Blog.open(id, {newTab})', '打开文章'],
|
||||||
|
['Blog.random({open})', '随机一篇文章']
|
||||||
|
]],
|
||||||
|
['其他', [
|
||||||
|
['Blog.about()', '站点与作者信息']
|
||||||
|
]]
|
||||||
|
];
|
||||||
|
|
||||||
|
var p = new Printer();
|
||||||
|
p.line('');
|
||||||
|
p.line(' Mayx 博客控制台 API', 'font-weight:bold;font-size:15px;color:#3fb950');
|
||||||
|
p.line('');
|
||||||
|
groups.forEach(function (g) {
|
||||||
|
p.line('▍' + g[0], S.h[3]);
|
||||||
|
g[1].forEach(function (row) {
|
||||||
|
var pad = repeat(' ', Math.max(1, 26 - strWidth(row[0])));
|
||||||
|
p.push(' ' + row[0], S.code).line(pad + row[1], S.sub);
|
||||||
|
});
|
||||||
|
p.line('');
|
||||||
|
});
|
||||||
|
p.line('示例:', S.strong);
|
||||||
|
p.line(' await Blog.list(1) 列出第 1 页文章', S.sub);
|
||||||
|
p.line(' await Blog.search("Jekyll") 搜索关键词', S.sub);
|
||||||
|
p.line(' await Blog.show(1) 阅读第 1 篇文章', S.sub);
|
||||||
|
p.line(' await Blog.open(1) 跳转到第 1 篇文章', S.sub);
|
||||||
|
p.line('');
|
||||||
|
p.flush();
|
||||||
|
};
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
* 7. 挂载
|
||||||
|
* ===================================================================== */
|
||||||
|
|
||||||
|
global.Blog = Blog;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'%c Mayx Blog %c 控制台 API 已就绪,输入 %cBlog.help()%c 查看全部命令 ',
|
||||||
|
'background:#3fb950;color:#fff;font-weight:bold;border-radius:3px 0 0 3px;padding:2px 6px',
|
||||||
|
'background:rgba(110,118,129,.2);padding:2px 6px',
|
||||||
|
'font-family:ui-monospace,Consolas,monospace;color:#e3b341;background:rgba(110,118,129,.2)',
|
||||||
|
'background:rgba(110,118,129,.2);padding:2px 6px;border-radius:0 3px 3px 0'
|
||||||
|
);
|
||||||
|
|
||||||
|
})(typeof window !== 'undefined' ? window : this);
|
||||||
10
games.md
Normal file
10
games.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Mayx的游戏列表
|
||||||
|
---
|
||||||
|
|
||||||
|
# Mayx的游戏列表
|
||||||
|
- 凯露&危险生存( [CHS]({% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/?cn) \| [JA]({% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/) \| [KO]({% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/?kr) )
|
||||||
|
- [立直麻将 Wordle]({% unless site.github %}https://mabbs.github.io{% endunless %}/riichi-mahjong-wordle/)
|
||||||
|
- [Brainfuck可视化演示]({% unless site.github %}https://mabbs.github.io{% endunless %}/brainfuck-visualizer/)
|
||||||
|
- [《激情麻将》关服前快照]({% unless site.github %}https://mabbs.github.io{% endunless %}/mahjong-dream-snapshot/) ([国际服]({% unless site.github %}https://mabbs.github.io{% endunless %}/mahjong-dream-snapshot/?intl))
|
||||||
@@ -66,10 +66,8 @@ image: https://screenshot.mayx.eu.org/
|
|||||||
<h2>其他页面</h2>
|
<h2>其他页面</h2>
|
||||||
<p>
|
<p>
|
||||||
<a href="/service.html">Mayx的公开服务</a><br />
|
<a href="/service.html">Mayx的公开服务</a><br />
|
||||||
<a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/linux/">Mayx's Blog Terminal</a><br />
|
<a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/linux/">Mayx's Blog Terminal</a> (<a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/jsnix/">Fake Version</a>)<br />
|
||||||
凯露&危险生存( <a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/?cn">CHS</a> | <a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/">JA</a> | <a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/?kr">KO</a> )<br />
|
<a href="/games.html">Mayx的游戏列表</a><br />
|
||||||
<a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/riichi-mahjong-wordle/">立直麻将 Wordle</a><br />
|
|
||||||
<a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/brainfuck-visualizer/">Brainfuck可视化演示</a><br />
|
|
||||||
<a href="/message.html">留言板</a><br />
|
<a href="/message.html">留言板</a><br />
|
||||||
<a href="/links.html">Links</a><br />
|
<a href="/links.html">Links</a><br />
|
||||||
<a href="/proxylist.html">代理列表</a><br />
|
<a href="/proxylist.html">代理列表</a><br />
|
||||||
|
|||||||
Reference in New Issue
Block a user