2 Commits
Author SHA1 Message Date
mayx 987c604dea Update 3 files
- /assets/js/blog-console.js
- /BlogCard.svg
- /_layouts/default.html
2026-08-04 13:49:00 +00:00
mayx 8e0b6e11b3 Update 2 files
- /_data/other_repo_list.csv
- /games.md
2026-08-02 08:32:36 +00:00
5 changed files with 869 additions and 20 deletions
+1 -1
View File
@@ -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>
-18
View File
@@ -24,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
@@ -36,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
@@ -83,7 +81,6 @@ 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://120.210.80.160:3000/mayx/blog http://120.210.80.160:3000/mayx/blog
@@ -96,13 +93,11 @@ 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
@@ -142,7 +137,6 @@ 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://36.133.248.69:3088/mayx/blog_cn http://36.133.248.69:3088/mayx/blog_cn
@@ -156,7 +150,6 @@ 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://git.crystalyx.net/mayx/blog https://git.crystalyx.net/mayx/blog
https://gittea.dev/mayx/blog https://gittea.dev/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
@@ -203,8 +196,6 @@ https://git.thunder-data.cn/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
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
@@ -251,13 +242,11 @@ https://git.liestral.io/mayx/blog
https://gitea.dabit.synology.me/mayx/blog https://gitea.dabit.synology.me/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
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.0bin.in/mayx/blog
https://jsuse.com/mayx/blog https://jsuse.com/mayx/blog
http://23.182.16.150:3000/mayx/blog http://23.182.16.150:3000/mayx/blog
https://git.ritonquilol.fr/mayx/blog https://git.ritonquilol.fr/mayx/blog
@@ -304,10 +293,8 @@ 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
@@ -332,12 +319,10 @@ https://forgejo.wanderingmonster.dev/mayx/blog
http://v.udvip.com/mayx/blog http://v.udvip.com/mayx/blog
https://gitea-fs0kwo8kccc4g88g0kk8k88c.gnextd.io/mayx/blog https://gitea-fs0kwo8kccc4g88g0kk8k88c.gnextd.io/mayx/blog
https://git.edavmig.ru/mayx/blog https://git.edavmig.ru/mayx/blog
https://git.hashdesk.ru/mayx/blog
https://gitea.ns5001k.sigma2.no/mayx/blog https://gitea.ns5001k.sigma2.no/mayx/blog
https://git.dongshan.tech/mayx/blog https://git.dongshan.tech/mayx/blog
https://fzsimo.com/mayx/blog https://fzsimo.com/mayx/blog
https://www.webetter.co.jp/mayx/blog https://www.webetter.co.jp/mayx/blog
https://git.biddydev.com/mayx/blog
https://git.hemangvyas.com/mayx/blog https://git.hemangvyas.com/mayx/blog
https://git.zotadevices.ru/mayx/blog https://git.zotadevices.ru/mayx/blog
https://gitav.ru/mayx/blog https://gitav.ru/mayx/blog
@@ -350,7 +335,6 @@ https://git.jinzhao.me/mayx/blog
http://www.365rise.top/mayx/blog http://www.365rise.top/mayx/blog
https://git.ifuntanhub.dev/mayx/blog https://git.ifuntanhub.dev/mayx/blog
https://mahta-tech.com/mayx/blog https://mahta-tech.com/mayx/blog
https://git.else-if.org/mayx/blog
http://kuiwisen.asia/mayx/blog http://kuiwisen.asia/mayx/blog
https://git.aptcloud.ru/mayx/blog https://git.aptcloud.ru/mayx/blog
https://gitea.simssoftware.in/mayx/blog https://gitea.simssoftware.in/mayx/blog
@@ -366,11 +350,9 @@ https://git.kunstglass.de/mayx/blog
https://git.kry008.xyz/mayx/blog https://git.kry008.xyz/mayx/blog
https://gitea.onfing.ir/mayx/blog https://gitea.onfing.ir/mayx/blog
http://59.57.4.66:3000/mayx/blog_cn http://59.57.4.66:3000/mayx/blog_cn
https://git.tas2580.net/mayx/blog_cn
https://gitea.xala.dev/mayx/blog https://gitea.xala.dev/mayx/blog
https://gitea.augeo.dev/mayx/blog_cn https://gitea.augeo.dev/mayx/blog_cn
http://36.213.200.127:23000/mayx/blog_cn http://36.213.200.127:23000/mayx/blog_cn
http://47.115.221.71:3000/mayx/blog_cn
http://20.219.0.85:3000/mayx/blog_cn http://20.219.0.85:3000/mayx/blog_cn
https://git.niisse.net/mayx/blog_cn https://git.niisse.net/mayx/blog_cn
https://gitea.yfandxt.asia/mayx/blog_cn https://gitea.yfandxt.asia/mayx/blog_cn
1 repo_url
24 https://mygit.iexercice.com/mayx/blog
25 http://162.215.134.149:4000/mayx/blog
26 http://45.33.87.87/mayx/blog
https://gitea.belanjaparts.com/mayx/blog
27 http://185.208.225.190:3000/mayx/blog
28 https://git.Apture.io/mayx/blog
29 http://8.134.61.107:3000/mayx/blog
35 http://gitea.yunshanghub.com:8081/mayx/blog
36 http://113.177.27.200:2033/mayx/blog
37 http://152.69.204.151:3000/mayx/blog
https://gitea.ontoast.uk/mayx/blog
38 http://187.216.152.151:9999/mayx/blog
39 http://116.63.173.179:8001/mayx/blog
40 https://k0ki-dev.com/mayx/blog
81 http://118.178.172.49:3000/mayx/blog
82 https://gitea.lasallesaintdenis.com/mayx/blog
83 http://8.130.135.159:3000/mayx/blog
https://git.suo0.com/mayx/blog
84 https://git.dshkabatur.ru/mayx/blog
85 http://47.103.78.70:3000/mayx/blog
86 http://120.210.80.160:3000/mayx/blog
93 https://dev.kiramtech.com/mayx/blog
94 https://git.f4e.lol/mayx/blog
95 http://72.61.229.93:4000/mayx/blog
https://lishan148.synology.me:3014/mayx/blog_cn
96 http://110.42.101.39:13000/mayx/blog_cn
97 http://39.101.74.135:5000/mayx/blog_cn
98 http://123.57.130.140:3000/mayx/blog_cn
99 http://118.24.46.223:3000/mayx/blog_cn
100 http://s3.v100.vip:31057/mayx/blog_cn
https://www.syq.im:2025/mayx/blog_cn
101 http://101.33.225.95:3000/mayx/blog_cn
102 http://45.55.138.82:3000/mayx/blog_cn
103 http://118.24.129.148:3000/mayx/blog_cn
137 http://43.143.209.246:6300/mayx/blog_cn
138 http://39.100.183.95:88/mayx/blog_cn
139 http://www.xshideserver.com:3000/mayx/blog_cn
http://159.75.131.235:3001/mayx/blog_cn
140 http://1.117.67.95:3000/mayx/blog_cn
141 http://81.70.102.154/mayx/blog_cn
142 http://36.133.248.69:3088/mayx/blog_cn
150 http://36.138.125.206:3000/mayx/blog_cn
151 https://git.crystalyx.net/mayx/blog
152 https://gittea.dev/mayx/blog
https://src.enterpriselinux.social/mayx/blog
153 https://gitea.grxe.io/mayx/blog
154 https://gitea.accept.dev.dbf.nl/mayx/blog
155 https://git.jhossain.online/mayx/blog
196 https://git.ixu.me/mayx/blog_cn
197 http://git.uhfdemo.com/mayx/blog_cn
198 http://43.143.142.38:7001/mayx/blog_cn
http://git.feashow.cn/mayx/blog_cn
http://git.yangben365.com/mayx/blog_cn
199 https://git.wieerwill.dev/mayx/blog
200 https://git.etwo.dev/mayx/blog
201 https://git.panda-number.one/mayx/blog
242 https://gitea.dabit.synology.me/mayx/blog
243 https://git.joinplu.me/mayx/blog
244 http://otter.top/mayx/blog
https://git.xiongyi.xin/mayx/blog
245 http://188.66.0.25:3000/mayx/blog
246 https://gitea.deliverables.io/mayx/blog
247 https://git.sevenvm.de/mayx/blog
248 https://code.letsbe.solutions/mayx/blog
249 https://git.mlhub.it/mayx/blog
https://git.0bin.in/mayx/blog
250 https://jsuse.com/mayx/blog
251 http://23.182.16.150:3000/mayx/blog
252 https://git.ritonquilol.fr/mayx/blog
293 http://gitea.jb1000.com:88/mayx/blog_cn
294 https://gitea.liyongzheng.fun/mayx/blog_cn
295 http://www.22ai.vip/mayx/blog_cn
https://git.tutulab.online/mayx/blog_cn
296 http://voodoo.plus/mayx/blog_cn
297 https://git.extra.eiffel.com/mayx/blog_cn
https://hdtime.space/mayx/blog_cn
298 http://219.151.177.172:3000/mayx/blog_cn
299 http://62.234.20.54:3000/mayx/blog_cn
300 https://git.urnas.cn/mayx/blog_cn
319 http://v.udvip.com/mayx/blog
320 https://gitea-fs0kwo8kccc4g88g0kk8k88c.gnextd.io/mayx/blog
321 https://git.edavmig.ru/mayx/blog
https://git.hashdesk.ru/mayx/blog
322 https://gitea.ns5001k.sigma2.no/mayx/blog
323 https://git.dongshan.tech/mayx/blog
324 https://fzsimo.com/mayx/blog
325 https://www.webetter.co.jp/mayx/blog
https://git.biddydev.com/mayx/blog
326 https://git.hemangvyas.com/mayx/blog
327 https://git.zotadevices.ru/mayx/blog
328 https://gitav.ru/mayx/blog
335 http://www.365rise.top/mayx/blog
336 https://git.ifuntanhub.dev/mayx/blog
337 https://mahta-tech.com/mayx/blog
https://git.else-if.org/mayx/blog
338 http://kuiwisen.asia/mayx/blog
339 https://git.aptcloud.ru/mayx/blog
340 https://gitea.simssoftware.in/mayx/blog
350 https://git.kry008.xyz/mayx/blog
351 https://gitea.onfing.ir/mayx/blog
352 http://59.57.4.66:3000/mayx/blog_cn
https://git.tas2580.net/mayx/blog_cn
353 https://gitea.xala.dev/mayx/blog
354 https://gitea.augeo.dev/mayx/blog_cn
355 http://36.213.200.127:23000/mayx/blog_cn
http://47.115.221.71:3000/mayx/blog_cn
356 http://20.219.0.85:3000/mayx/blog_cn
357 https://git.niisse.net/mayx/blog_cn
358 https://gitea.yfandxt.asia/mayx/blog_cn
+1
View File
@@ -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>
+866
View 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)、![alt](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.pageSize10
* @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.maxShowLines600
* @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);
+1 -1
View File
@@ -7,4 +7,4 @@ title: 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) - 凯露&危险生存( [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/) - [立直麻将 Wordle]({% unless site.github %}https://mabbs.github.io{% endunless %}/riichi-mahjong-wordle/)
- [Brainfuck可视化演示]({% unless site.github %}https://mabbs.github.io{% endunless %}/brainfuck-visualizer/) - [Brainfuck可视化演示]({% unless site.github %}https://mabbs.github.io{% endunless %}/brainfuck-visualizer/)
- [《激情麻将》关服前快照]({% unless site.github %}https://mabbs.github.io{% endunless %}/mahjong-dreamone-snapshot/) - [《激情麻将》关服前快照]({% unless site.github %}https://mabbs.github.io{% endunless %}/mahjong-dream-snapshot/) [国际服]({% unless site.github %}https://mabbs.github.io{% endunless %}/mahjong-dream-snapshot/?intl)