hexo-theme-stellar 1.34.0 → 1.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_config.yml +25 -19
- package/layout/_partial/comments/artalk/layout.ejs +2 -2
- package/layout/_partial/comments/artalk/script.ejs +3 -9
- package/layout/_partial/comments/beaudar/script.ejs +2 -8
- package/layout/_partial/comments/giscus/script.ejs +2 -8
- package/layout/_partial/comments/twikoo/layout.ejs +2 -2
- package/layout/_partial/comments/twikoo/script.ejs +3 -9
- package/layout/_partial/comments/utterances/script.ejs +2 -8
- package/layout/_partial/comments/waline/layout.ejs +1 -1
- package/layout/_partial/comments/waline/script.ejs +1 -7
- package/layout/_partial/head.ejs +0 -3
- package/layout/_partial/scripts/defines.ejs +118 -1
- package/layout/_partial/scripts/lazyload.ejs +7 -6
- package/layout/_partial/scripts/services.ejs +11 -13
- package/layout/_partial/scripts/utils.ejs +358 -66
- package/layout/_partial/widgets/tagtree.ejs +1 -1
- package/layout/_partial/widgets/toc.ejs +1 -4
- package/layout/_partial/widgets/tree.ejs +1 -1
- package/layout/_plugins/fancybox.ejs +23 -8
- package/layout/_plugins/scrollreveal.ejs +77 -13
- package/layout/layout.ejs +0 -1
- package/package.json +4 -2
- package/scripts/events/index.js +2 -1
- package/scripts/events/lib/doc_tree.js +7 -4
- package/scripts/events/lib/merge_posts.js +3 -1
- package/scripts/events/lib/notebooks.js +5 -3
- package/scripts/events/lib/path_normalize.js +22 -0
- package/scripts/filters/lib/img_lazyload.js +9 -9
- package/scripts/generators/search.js +3 -1
- package/scripts/helpers/json_ld.js +3 -2
- package/scripts/helpers/pretty_url.js +9 -10
- package/scripts/lib/path_utils.js +24 -0
- package/scripts/tags/lib/emoji.js +17 -2
- package/scripts/tags/lib/image.js +13 -18
- package/source/css/_common/html.styl +0 -2
- package/source/css/_components/widgets/toc.styl +2 -2
- package/source/css/_plugins/index.styl +0 -3
- package/source/css/_plugins/lazyload.styl +40 -26
- package/source/css/_plugins/scrollreveal.styl +5 -1
- package/source/js/main.js +157 -73
- package/source/js/search/algolia-search.js +62 -62
- package/source/js/search/local-search.js +37 -36
- package/source/js/services/artalk_latest_comment.js +5 -7
- package/source/js/services/contributors.js +4 -6
- package/source/js/services/fcircle.js +4 -6
- package/source/js/services/friends.js +4 -6
- package/source/js/services/friends_and_posts.js +4 -6
- package/source/js/services/ghinfo.js +6 -8
- package/source/js/services/giscus_latest_comment.js +4 -6
- package/source/js/services/mdrender.js +2 -2
- package/source/js/services/memos.js +3 -4
- package/source/js/services/rss.js +10 -12
- package/source/js/services/siteinfo.js +24 -26
- package/source/js/services/sites.js +4 -6
- package/source/js/services/timeline.js +4 -6
- package/source/js/services/twikoo_latest_comment.js +5 -7
- package/source/js/services/waline_latest_comment.js +4 -6
- package/source/js/services/weibo.js +4 -6
- package/CLAUDE.md +0 -217
- package/docs/audits/2026-08-08-stellar-analysis.md +0 -267
- package/docs/release-process.md +0 -59
- package/layout/_plugins/pjax.ejs +0 -8
- package/source/css/_plugins/pjax.styl +0 -61
- package/source/js/plugins/pjax.js +0 -646
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
// 已加载脚本缓存
|
|
114
114
|
_loadedScripts: new Set(),
|
|
115
115
|
|
|
116
|
-
// 已加载元素缓存 (使用 WeakSet
|
|
116
|
+
// 已加载元素缓存 (使用 WeakSet 追踪元素实例,避免重复加载)
|
|
117
117
|
_loadedElements: new WeakSet(),
|
|
118
118
|
|
|
119
119
|
js: (src, opt) => new Promise((resolve, reject) => {
|
|
@@ -146,71 +146,355 @@
|
|
|
146
146
|
document.head.appendChild(script)
|
|
147
147
|
}),
|
|
148
148
|
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
149
|
+
// 原生 DOM 工具:querySelector / querySelectorAll 简写
|
|
150
|
+
qs: (sel, ctx) => (ctx || document).querySelector(sel),
|
|
151
|
+
qsa: (sel, ctx) => Array.prototype.slice.call((ctx || document).querySelectorAll(sel)),
|
|
152
|
+
|
|
153
|
+
// 原生 DOM 封装:常用 DOM 操作方法子集(find/append/class/attr/事件等)
|
|
154
|
+
dom: (selector, ctx) => {
|
|
155
|
+
var els = [];
|
|
156
|
+
if (typeof selector === 'string') {
|
|
157
|
+
els = utils.qsa(selector, ctx);
|
|
158
|
+
} else if (selector && selector.nodeType === 1) {
|
|
159
|
+
els = [selector];
|
|
160
|
+
} else if (selector && typeof selector.length === 'number') {
|
|
161
|
+
els = Array.prototype.slice.call(selector);
|
|
162
162
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
163
|
+
var api = {
|
|
164
|
+
length: els.length,
|
|
165
|
+
each: function (fn) {
|
|
166
|
+
els.forEach(function (el, i) {
|
|
167
|
+
fn.call(el, i, el);
|
|
168
|
+
});
|
|
169
|
+
return api;
|
|
170
|
+
},
|
|
171
|
+
find: function (sel) {
|
|
172
|
+
var result = [];
|
|
173
|
+
els.forEach(function (el) {
|
|
174
|
+
Array.prototype.push.apply(result, el.querySelectorAll(sel));
|
|
175
|
+
});
|
|
176
|
+
return utils.dom(result);
|
|
177
|
+
},
|
|
178
|
+
append: function (content) {
|
|
179
|
+
els.forEach(function (el) {
|
|
180
|
+
if (typeof content === 'string') {
|
|
181
|
+
el.insertAdjacentHTML('beforeend', content);
|
|
182
|
+
} else if (content && content.nodeType === 1) {
|
|
183
|
+
el.appendChild(content);
|
|
184
|
+
} else if (content && typeof content.length === 'number') {
|
|
185
|
+
Array.prototype.forEach.call(content, function (child) {
|
|
186
|
+
if (child && child.nodeType === 1) el.appendChild(child);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
return api;
|
|
191
|
+
},
|
|
192
|
+
remove: function () {
|
|
193
|
+
els.forEach(function (el) {
|
|
194
|
+
el.remove();
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
addClass: function (cls) {
|
|
198
|
+
els.forEach(function (el) {
|
|
199
|
+
cls.trim().split(/\s+/).forEach(function (c) {
|
|
200
|
+
if (c) el.classList.add(c);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
return api;
|
|
204
|
+
},
|
|
205
|
+
removeClass: function (cls) {
|
|
206
|
+
els.forEach(function (el) {
|
|
207
|
+
cls.trim().split(/\s+/).forEach(function (c) {
|
|
208
|
+
if (c) el.classList.remove(c);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
return api;
|
|
212
|
+
},
|
|
213
|
+
toggleClass: function (cls, force) {
|
|
214
|
+
els.forEach(function (el) {
|
|
215
|
+
cls.trim().split(/\s+/).forEach(function (c) {
|
|
216
|
+
if (c) el.classList.toggle(c, force);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
return api;
|
|
220
|
+
},
|
|
221
|
+
attr: function (name, value) {
|
|
222
|
+
if (value === undefined) {
|
|
223
|
+
return els[0] ? els[0].getAttribute(name) : undefined;
|
|
224
|
+
}
|
|
225
|
+
els.forEach(function (el) {
|
|
226
|
+
el.setAttribute(name, value);
|
|
227
|
+
});
|
|
228
|
+
return api;
|
|
229
|
+
},
|
|
230
|
+
data: function (name) {
|
|
231
|
+
return els[0] ? els[0].getAttribute('data-' + name) : undefined;
|
|
232
|
+
},
|
|
233
|
+
text: function (value) {
|
|
234
|
+
if (value === undefined) {
|
|
235
|
+
return els[0] ? els[0].textContent : undefined;
|
|
236
|
+
}
|
|
237
|
+
els.forEach(function (el) {
|
|
238
|
+
el.textContent = value;
|
|
239
|
+
});
|
|
240
|
+
return api;
|
|
241
|
+
},
|
|
242
|
+
html: function (content) {
|
|
243
|
+
if (content === undefined) {
|
|
244
|
+
return els[0] ? els[0].innerHTML : undefined;
|
|
245
|
+
}
|
|
246
|
+
els.forEach(function (el) {
|
|
247
|
+
if (typeof content !== 'string' && content && typeof content.length === 'number' && content[0] && content[0].nodeType === 1) {
|
|
248
|
+
var frag = document.createDocumentFragment();
|
|
249
|
+
Array.prototype.forEach.call(content, function (child) {
|
|
250
|
+
frag.appendChild(child);
|
|
251
|
+
});
|
|
252
|
+
el.replaceChildren(frag);
|
|
253
|
+
} else {
|
|
254
|
+
el.innerHTML = content;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
return api;
|
|
258
|
+
},
|
|
259
|
+
val: function (value) {
|
|
260
|
+
if (value === undefined) {
|
|
261
|
+
return els[0] ? els[0].value : undefined;
|
|
262
|
+
}
|
|
263
|
+
els.forEach(function (el) {
|
|
264
|
+
el.value = value;
|
|
265
|
+
});
|
|
266
|
+
return api;
|
|
267
|
+
},
|
|
268
|
+
offset: function () {
|
|
269
|
+
var el = els[0];
|
|
270
|
+
if (!el) return { top: 0, left: 0 };
|
|
271
|
+
var rect = el.getBoundingClientRect();
|
|
272
|
+
return { top: rect.top + window.scrollY, left: rect.left + window.scrollX };
|
|
273
|
+
},
|
|
274
|
+
on: function (event, cb) {
|
|
275
|
+
els.forEach(function (el) {
|
|
276
|
+
el.addEventListener(event, cb);
|
|
277
|
+
});
|
|
278
|
+
return api;
|
|
279
|
+
},
|
|
280
|
+
click: function (cb) { return api.on('click', cb); },
|
|
281
|
+
focus: function (cb) { return api.on('focus', cb); },
|
|
282
|
+
keydown: function (cb) { return api.on('keydown', cb); },
|
|
283
|
+
empty: function () {
|
|
284
|
+
els.forEach(function (el) {
|
|
285
|
+
el.replaceChildren();
|
|
286
|
+
});
|
|
287
|
+
return api;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
els.forEach(function (el, i) {
|
|
291
|
+
api[i] = el;
|
|
170
292
|
});
|
|
171
|
-
|
|
293
|
+
return api;
|
|
172
294
|
},
|
|
173
295
|
|
|
174
296
|
onLoading: (el) => {
|
|
175
297
|
if (el) {
|
|
176
|
-
if (
|
|
177
|
-
|
|
298
|
+
if (el.querySelector('.loading-wrap') === null) {
|
|
299
|
+
el.insertAdjacentHTML('beforeend', '<div class="loading-wrap"><svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="2"><path stroke-dasharray="60" stroke-dashoffset="60" stroke-opacity=".3" d="M12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3Z"><animate fill="freeze" attributeName="stroke-dashoffset" dur="1.3s" values="60;0"/></path><path stroke-dasharray="15" stroke-dashoffset="15" d="M12 3C16.9706 3 21 7.02944 21 12"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.3s" values="15;0"/><animateTransform attributeName="transform" dur="1.5s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path></g></svg></div>');
|
|
178
300
|
}
|
|
179
301
|
}
|
|
180
302
|
},
|
|
181
303
|
onLoadSuccess: (el) => {
|
|
182
304
|
if (el) {
|
|
183
|
-
|
|
305
|
+
var wrap = el.querySelector('.loading-wrap');
|
|
306
|
+
if (wrap) wrap.remove();
|
|
184
307
|
}
|
|
185
308
|
},
|
|
186
309
|
onLoadFailure: (el) => {
|
|
187
310
|
if (el) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
311
|
+
var wrap = el.querySelector('.loading-wrap');
|
|
312
|
+
if (wrap) {
|
|
313
|
+
var svg = wrap.querySelector('svg');
|
|
314
|
+
if (svg) svg.remove();
|
|
315
|
+
wrap.insertAdjacentHTML('beforeend', '<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path stroke-dasharray="60" stroke-dashoffset="60" d="M12 3L21 20H3L12 3Z"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.5s" values="60;0"/></path><path stroke-dasharray="6" stroke-dashoffset="6" d="M12 10V14"><animate fill="freeze" attributeName="stroke-dashoffset" begin="0.6s" dur="0.2s" values="6;0"/></path></g><circle cx="12" cy="17" r="1" fill="currentColor" fill-opacity="0"><animate fill="freeze" attributeName="fill-opacity" begin="0.8s" dur="0.4s" values="0;1"/></circle></svg>');
|
|
316
|
+
wrap.classList.add('error');
|
|
317
|
+
}
|
|
191
318
|
}
|
|
192
319
|
},
|
|
193
|
-
|
|
320
|
+
/********************** data cache ********************************/
|
|
321
|
+
// 动态数据本地缓存:TTL 未过期直接命中;过期后先显示缓存再后台刷新(stale-while-revalidate)
|
|
322
|
+
_cacheEnabled: true,
|
|
323
|
+
_cacheMaxEntryBytes: 200 * 1024,
|
|
324
|
+
cache: {
|
|
325
|
+
prefix: 'Stellar.data_cache.v1.',
|
|
326
|
+
enabled: () => !!def.data_cache?.enable && utils._cacheEnabled,
|
|
327
|
+
serviceId: (el, options) => {
|
|
328
|
+
if (options && options.service) return options.service;
|
|
329
|
+
if (el) {
|
|
330
|
+
const match = String(el.className || '').match(/\bds-([\w-]+)\b/);
|
|
331
|
+
if (match) return match[1];
|
|
332
|
+
}
|
|
333
|
+
return null;
|
|
334
|
+
},
|
|
335
|
+
ttl: (el, options) => {
|
|
336
|
+
const conf = def.data_cache || {};
|
|
337
|
+
const id = utils.cache.serviceId(el, options);
|
|
338
|
+
const value = id && conf.ttl && conf.ttl[id] != null ? conf.ttl[id] : conf.default_ttl;
|
|
339
|
+
return typeof value === 'number' && value > 0 ? value : 0;
|
|
340
|
+
},
|
|
341
|
+
shouldCache: (url, options) => {
|
|
342
|
+
if (!utils.cache.enabled()) return false;
|
|
343
|
+
if (options && (options.cache === false || options.cache === 'no-store')) return false;
|
|
344
|
+
if (options && options.method && options.method !== 'GET') return false;
|
|
345
|
+
// 带时间戳缓存破坏参数的请求不缓存(如 mdrender 的 ?t=)
|
|
346
|
+
if (/[?&]t=\d{10,}/.test(url)) return false;
|
|
347
|
+
return true;
|
|
348
|
+
},
|
|
349
|
+
get: (url) => {
|
|
350
|
+
try {
|
|
351
|
+
const raw = localStorage.getItem(utils.cache.prefix + url);
|
|
352
|
+
if (!raw) return null;
|
|
353
|
+
const entry = JSON.parse(raw);
|
|
354
|
+
if (!entry || typeof entry.text !== 'string' || typeof entry.ts !== 'number') return null;
|
|
355
|
+
return entry;
|
|
356
|
+
} catch (e) {
|
|
357
|
+
console.warn('[cache] 读取失败:', url, e);
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
set: (url, text, contentType, ttl) => {
|
|
362
|
+
if (!(ttl > 0)) return;
|
|
363
|
+
if (text.length > utils._cacheMaxEntryBytes) return;
|
|
364
|
+
const entry = JSON.stringify({
|
|
365
|
+
text: text,
|
|
366
|
+
contentType: contentType || 'application/json',
|
|
367
|
+
ts: Date.now(),
|
|
368
|
+
ttl: ttl
|
|
369
|
+
});
|
|
370
|
+
const trySet = () => {
|
|
371
|
+
try {
|
|
372
|
+
localStorage.setItem(utils.cache.prefix + url, entry);
|
|
373
|
+
utils.cache.trim();
|
|
374
|
+
return true;
|
|
375
|
+
} catch (e) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
if (!trySet()) {
|
|
380
|
+
// 配额不足时淘汰最旧条目后重试一次,仍失败则本会话禁用缓存
|
|
381
|
+
utils.cache.evictOldest(url);
|
|
382
|
+
if (!trySet()) {
|
|
383
|
+
console.warn('[cache] 写入失败,本会话禁用缓存:', url);
|
|
384
|
+
utils._cacheEnabled = false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
trim: () => {
|
|
389
|
+
try {
|
|
390
|
+
const max = (def.data_cache && def.data_cache.max_entries) || 200;
|
|
391
|
+
const keys = [];
|
|
392
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
393
|
+
const key = localStorage.key(i);
|
|
394
|
+
if (key && key.indexOf(utils.cache.prefix) === 0) keys.push(key);
|
|
395
|
+
}
|
|
396
|
+
if (keys.length <= max) return;
|
|
397
|
+
const sorted = keys.map((key) => {
|
|
398
|
+
let ts = 0;
|
|
399
|
+
try {
|
|
400
|
+
const entry = JSON.parse(localStorage.getItem(key) || 'null');
|
|
401
|
+
if (entry && typeof entry.ts === 'number') ts = entry.ts;
|
|
402
|
+
} catch (e) {}
|
|
403
|
+
return { key, ts };
|
|
404
|
+
}).sort((a, b) => a.ts - b.ts);
|
|
405
|
+
for (let i = 0; i < sorted.length - max; i++) {
|
|
406
|
+
localStorage.removeItem(sorted[i].key);
|
|
407
|
+
}
|
|
408
|
+
} catch (e) {
|
|
409
|
+
console.warn('[cache] 清理失败:', e);
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
evictOldest: (exceptUrl) => {
|
|
413
|
+
try {
|
|
414
|
+
let oldestKey = null;
|
|
415
|
+
let oldestTs = Infinity;
|
|
416
|
+
const exceptKey = utils.cache.prefix + exceptUrl;
|
|
417
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
418
|
+
const key = localStorage.key(i);
|
|
419
|
+
if (!key || key.indexOf(utils.cache.prefix) !== 0 || key === exceptKey) continue;
|
|
420
|
+
let ts = 0;
|
|
421
|
+
try {
|
|
422
|
+
const entry = JSON.parse(localStorage.getItem(key) || 'null');
|
|
423
|
+
if (entry && typeof entry.ts === 'number') ts = entry.ts;
|
|
424
|
+
} catch (e) {}
|
|
425
|
+
if (ts < oldestTs) {
|
|
426
|
+
oldestTs = ts;
|
|
427
|
+
oldestKey = key;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (oldestKey) localStorage.removeItem(oldestKey);
|
|
431
|
+
} catch (e) {}
|
|
432
|
+
},
|
|
433
|
+
isFresh: (entry) => {
|
|
434
|
+
if (!entry || typeof entry.ts !== 'number' || typeof entry.ttl !== 'number') return false;
|
|
435
|
+
if (!(entry.ttl > 0)) return false;
|
|
436
|
+
return Date.now() - entry.ts < entry.ttl * 1000;
|
|
437
|
+
},
|
|
438
|
+
// 用缓存文本重建 Response,兼容回调中的 resp.json() / resp.text()
|
|
439
|
+
toResponse: (entry) => new Response(entry.text, {
|
|
440
|
+
status: 200,
|
|
441
|
+
statusText: 'OK',
|
|
442
|
+
headers: { 'Content-Type': entry.contentType }
|
|
443
|
+
})
|
|
444
|
+
},
|
|
445
|
+
request: (el, url, callback, onFailure, options) => {
|
|
194
446
|
// 检查元素实例是否已加载 (而不是检查属性)
|
|
195
447
|
if (el && utils._loadedElements.has(el)) {
|
|
196
448
|
return;
|
|
197
449
|
}
|
|
198
450
|
const maxRetry = 3;
|
|
199
451
|
let retryCount = 0;
|
|
452
|
+
const ttl = utils.cache.ttl(el, options);
|
|
453
|
+
const cacheable = utils.cache.shouldCache(url, options) && ttl > 0;
|
|
454
|
+
const cached = cacheable ? utils.cache.get(url) : null;
|
|
455
|
+
// 缓存渲染前的初始结构,后台刷新渲染前恢复,避免重复渲染
|
|
456
|
+
let initialHTML = null;
|
|
457
|
+
// 缓存渲染完成后(或超时兜底)再启动后台刷新,避免异步渲染未结束就清空重绘
|
|
458
|
+
let cacheRenderDone = null;
|
|
459
|
+
|
|
460
|
+
if (cached) {
|
|
461
|
+
if (el) {
|
|
462
|
+
// 已有缓存时不显示加载动画
|
|
463
|
+
utils.onLoadSuccess?.(el);
|
|
464
|
+
initialHTML = el.innerHTML;
|
|
465
|
+
}
|
|
466
|
+
try {
|
|
467
|
+
cacheRenderDone = Promise.resolve(callback(utils.cache.toResponse(cached))).catch(e => {
|
|
468
|
+
console.warn('[request] 缓存渲染失败:', url, e);
|
|
469
|
+
});
|
|
470
|
+
} catch (e) {
|
|
471
|
+
console.warn('[request] 缓存渲染失败:', url, e);
|
|
472
|
+
}
|
|
473
|
+
// 缓存未过期:直接完成,不发请求
|
|
474
|
+
if (utils.cache.isFresh(cached)) {
|
|
475
|
+
if (el) utils._loadedElements.add(el);
|
|
476
|
+
return Promise.resolve(cached.text);
|
|
477
|
+
}
|
|
478
|
+
} else {
|
|
479
|
+
utils.onLoading?.(el);
|
|
480
|
+
}
|
|
200
481
|
|
|
201
482
|
return new Promise((resolve, reject) => {
|
|
202
483
|
const load = () => {
|
|
203
|
-
utils.onLoading?.(el);
|
|
204
|
-
|
|
205
484
|
let timedOut = false;
|
|
206
485
|
const timeout = setTimeout(() => {
|
|
207
486
|
timedOut = true;
|
|
208
487
|
console.warn('[request] 超时:', url);
|
|
209
488
|
|
|
210
489
|
if (++retryCount >= maxRetry) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
490
|
+
if (cached) {
|
|
491
|
+
// 已有缓存渲染,保留缓存内容,不显示失败
|
|
492
|
+
resolve(cached.text);
|
|
493
|
+
} else {
|
|
494
|
+
utils.onLoadFailure?.(el);
|
|
495
|
+
onFailure?.();
|
|
496
|
+
reject('请求超时');
|
|
497
|
+
}
|
|
214
498
|
} else {
|
|
215
499
|
setTimeout(load, 1000);
|
|
216
500
|
}
|
|
@@ -226,6 +510,16 @@
|
|
|
226
510
|
if (timedOut) return;
|
|
227
511
|
// 标记元素实例为已加载
|
|
228
512
|
if (el) utils._loadedElements.add(el);
|
|
513
|
+
// 写入缓存(克隆响应,避免影响回调读取)
|
|
514
|
+
if (cacheable && data.ok) {
|
|
515
|
+
data.clone().text().then(text => {
|
|
516
|
+
utils.cache.set(url, text, data.headers.get('Content-Type') || 'application/json', ttl);
|
|
517
|
+
}).catch(() => {});
|
|
518
|
+
}
|
|
519
|
+
// 后台刷新渲染前恢复初始结构,避免缓存渲染的内容重复
|
|
520
|
+
if (el && initialHTML !== null) {
|
|
521
|
+
el.innerHTML = initialHTML;
|
|
522
|
+
}
|
|
229
523
|
utils.onLoadSuccess?.(el);
|
|
230
524
|
callback(data);
|
|
231
525
|
resolve(data);
|
|
@@ -234,19 +528,38 @@
|
|
|
234
528
|
console.warn('[request] 错误:', err);
|
|
235
529
|
|
|
236
530
|
if (++retryCount >= maxRetry) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
531
|
+
if (cached) {
|
|
532
|
+
resolve(cached.text);
|
|
533
|
+
} else {
|
|
534
|
+
utils.onLoadFailure?.(el);
|
|
535
|
+
onFailure?.();
|
|
536
|
+
reject(err);
|
|
537
|
+
}
|
|
240
538
|
} else {
|
|
241
539
|
setTimeout(load, 1000);
|
|
242
540
|
}
|
|
243
541
|
});
|
|
244
542
|
};
|
|
245
543
|
|
|
246
|
-
|
|
544
|
+
if (cacheRenderDone) {
|
|
545
|
+
Promise.race([
|
|
546
|
+
cacheRenderDone,
|
|
547
|
+
new Promise(resolve => setTimeout(resolve, 5000))
|
|
548
|
+
]).then(load);
|
|
549
|
+
} else {
|
|
550
|
+
load();
|
|
551
|
+
}
|
|
247
552
|
});
|
|
248
553
|
},
|
|
249
554
|
requestWithoutLoading: (url, options = {}, maxRetry = 2, timeout = 5000) => {
|
|
555
|
+
const ttl = utils.cache.ttl(null, options);
|
|
556
|
+
const cacheable = utils.cache.shouldCache(url, options) && ttl > 0;
|
|
557
|
+
const cached = cacheable ? utils.cache.get(url) : null;
|
|
558
|
+
|
|
559
|
+
if (cached && utils.cache.isFresh(cached)) {
|
|
560
|
+
return Promise.resolve(utils.cache.toResponse(cached));
|
|
561
|
+
}
|
|
562
|
+
|
|
250
563
|
return new Promise((resolve, reject) => {
|
|
251
564
|
let retryCount = 0;
|
|
252
565
|
|
|
@@ -262,6 +575,11 @@
|
|
|
262
575
|
.then(resp => {
|
|
263
576
|
clearTimeout(timer);
|
|
264
577
|
if (!resp.ok) throw new Error('bad response');
|
|
578
|
+
if (cacheable) {
|
|
579
|
+
resp.clone().text().then(text => {
|
|
580
|
+
utils.cache.set(url, text, resp.headers.get('Content-Type') || 'application/json', ttl);
|
|
581
|
+
}).catch(() => {});
|
|
582
|
+
}
|
|
265
583
|
resolve(resp);
|
|
266
584
|
})
|
|
267
585
|
.catch(err => {
|
|
@@ -285,7 +603,7 @@
|
|
|
285
603
|
},
|
|
286
604
|
dark: {},
|
|
287
605
|
|
|
288
|
-
// 插件初始化管理器 - 统一处理 DOMContentLoaded
|
|
606
|
+
// 插件初始化管理器 - 统一处理 DOMContentLoaded 事件
|
|
289
607
|
_pluginInitializers: [],
|
|
290
608
|
_pluginCleanups: new Map(), // 存储每个插件的清理函数
|
|
291
609
|
|
|
@@ -334,20 +652,6 @@
|
|
|
334
652
|
}
|
|
335
653
|
},
|
|
336
654
|
|
|
337
|
-
// 重新初始化所有插件(用于 PJAX 等场景)
|
|
338
|
-
reinitPlugins: () => {
|
|
339
|
-
utils._pluginInitializers.forEach(plugin => {
|
|
340
|
-
// 跳过标记为不需要重新初始化的插件
|
|
341
|
-
if (plugin.options.skipReinit) return;
|
|
342
|
-
|
|
343
|
-
try {
|
|
344
|
-
plugin.fn();
|
|
345
|
-
} catch (error) {
|
|
346
|
-
console.error(`[Plugin ${plugin.name}] 重新初始化失败:`, error);
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
},
|
|
350
|
-
|
|
351
655
|
// 清理所有插件资源
|
|
352
656
|
cleanupPlugins: () => {
|
|
353
657
|
utils._pluginCleanups.forEach((cleanup, name) => {
|
|
@@ -363,21 +667,9 @@
|
|
|
363
667
|
// 清理所有资源(用于页面卸载或重置)
|
|
364
668
|
cleanupAll: () => {
|
|
365
669
|
utils.cleanupPlugins();
|
|
366
|
-
utils.cleanupPjaxListeners();
|
|
367
670
|
},
|
|
368
671
|
};
|
|
369
672
|
|
|
370
|
-
// 自动处理 PJAX 事件
|
|
371
|
-
document.addEventListener('pjax:before', () => {
|
|
372
|
-
// 在导航前清理旧的 PJAX 监听器(如果需要重新注册)
|
|
373
|
-
// 注意:这里不清理,因为监听器应该是持久的
|
|
374
|
-
// utils.cleanupPjaxListeners();
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
document.addEventListener('pjax:complete', () => {
|
|
378
|
-
utils.reinitPlugins();
|
|
379
|
-
});
|
|
380
|
-
|
|
381
673
|
// utils.dark.mode 当前模式 dark or light
|
|
382
674
|
// utils.dark.toggle() 暗黑模式触发器
|
|
383
675
|
// utils.dark.push(callBack[,"callBackName"]) 传入触发器回调函数
|
|
@@ -387,4 +679,4 @@
|
|
|
387
679
|
utils.dark = Object.assign(utils.dark, {
|
|
388
680
|
push: utils.dark.method.toggle.push,
|
|
389
681
|
});
|
|
390
|
-
</script>
|
|
682
|
+
</script>
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
<% } %>
|
|
46
46
|
|
|
47
47
|
<% if (tagTree) { %>
|
|
48
|
-
<widget class="widget-wrapper<%= scrollreveal(' ') %> post-list">
|
|
48
|
+
<widget class="widget-wrapper<%= scrollreveal(' ') %> post-list" data-notebook="<%= page.notebook %>">
|
|
49
49
|
<div class="widget-header dis-select">
|
|
50
50
|
<span class="name"><%= __('meta.tag_tree') %></span>
|
|
51
51
|
</div>
|
|
@@ -37,12 +37,9 @@ function layoutToc(fallback) {
|
|
|
37
37
|
|
|
38
38
|
function layoutDiv(fallback) {
|
|
39
39
|
const tocBody = layoutTocBody()
|
|
40
|
-
if (tocBody.trim().length == 0) {
|
|
41
|
-
return ''
|
|
42
|
-
}
|
|
43
40
|
var el = ''
|
|
44
41
|
el += `<widget class="widget-wrapper${scrollreveal(' ')} toc" id="data-toc" collapse="${item.collapse}">`
|
|
45
|
-
if (tocBody.length > 0) {
|
|
42
|
+
if (tocBody.trim().length > 0) {
|
|
46
43
|
el += layoutTocHeader()
|
|
47
44
|
el += `<div class="widget-body">`
|
|
48
45
|
el += tocBody
|
|
@@ -60,7 +60,7 @@ function layoutDiv(fallback) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
if (el.trim().length > 0) {
|
|
63
|
-
return `<widget class="widget-wrapper doc-tree post-list">${el}</widget>`
|
|
63
|
+
return `<widget class="widget-wrapper doc-tree post-list" data-wiki="${page.wiki}">${el}</widget>`
|
|
64
64
|
} else {
|
|
65
65
|
return ''
|
|
66
66
|
}
|
|
@@ -1,27 +1,42 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
ctx.fancybox = {
|
|
3
|
+
mode: `<%- conf.mode || 'auto' %>`,
|
|
3
4
|
selector: `<%- conf.selector %>`,
|
|
4
5
|
css: `<%- conf.css %>`,
|
|
5
6
|
js: `<%- conf.js %>`
|
|
6
7
|
};
|
|
7
|
-
|
|
8
|
+
// auto=按需:页面存在可弹窗图片才加载;global=全局:所有页面加载
|
|
9
|
+
const isGlobal = ctx.fancybox.mode === 'global';
|
|
10
|
+
// 评论区图片(artalk/twikoo/waline)显示较小,自动支持放大
|
|
11
|
+
var selector = '[data-fancybox]:not(.error), .with-fancybox img:not([atk-emoticon]):not([class*="emo"])';
|
|
8
12
|
if (ctx.fancybox.selector) {
|
|
9
13
|
selector += `, ${ctx.fancybox.selector}`
|
|
10
14
|
}
|
|
11
|
-
var needFancybox =
|
|
15
|
+
var needFancybox = isGlobal;
|
|
12
16
|
if (!needFancybox) {
|
|
13
|
-
|
|
14
|
-
if (
|
|
15
|
-
|
|
17
|
+
needFancybox = document.querySelectorAll(selector).length !== 0;
|
|
18
|
+
if (!needFancybox) {
|
|
19
|
+
// 图片型评论系统容器:评论区图片小图需要 fancybox
|
|
20
|
+
const withFancybox = document.getElementsByClassName('with-fancybox');
|
|
21
|
+
if (withFancybox != undefined && withFancybox.length > 0) {
|
|
22
|
+
needFancybox = true;
|
|
23
|
+
}
|
|
16
24
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
if (!needFancybox) {
|
|
26
|
+
// 动态数据服务:memos 渲染的图片需要 fancybox
|
|
27
|
+
const memos = document.getElementsByClassName('ds-memos');
|
|
28
|
+
if (memos != undefined && memos.length > 0) {
|
|
29
|
+
needFancybox = true;
|
|
30
|
+
}
|
|
20
31
|
}
|
|
21
32
|
}
|
|
22
33
|
if (needFancybox) {
|
|
23
34
|
utils.css(ctx.fancybox.css);
|
|
24
35
|
utils.js(ctx.fancybox.js, { defer: true }).then(function () {
|
|
36
|
+
if (isGlobal) {
|
|
37
|
+
// 全局模式:正文所有非内联图片均可弹窗
|
|
38
|
+
selector += ', .md-text img:not(.inline):not([atk-emoticon])';
|
|
39
|
+
}
|
|
25
40
|
Fancybox.bind(selector, {
|
|
26
41
|
hideScrollbar: false,
|
|
27
42
|
Thumbs: {
|