hexo-theme-stellar 1.35.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 -10
- 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/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 +359 -24
- 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/package.json +1 -1
- package/scripts/filters/lib/img_lazyload.js +9 -9
- 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/lazyload.styl +40 -26
- package/source/css/_plugins/scrollreveal.styl +5 -1
- package/source/js/main.js +157 -69
- 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
|
@@ -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,54 +146,355 @@
|
|
|
146
146
|
document.head.appendChild(script)
|
|
147
147
|
}),
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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);
|
|
154
162
|
}
|
|
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;
|
|
292
|
+
});
|
|
293
|
+
return api;
|
|
155
294
|
},
|
|
156
295
|
|
|
157
296
|
onLoading: (el) => {
|
|
158
297
|
if (el) {
|
|
159
|
-
if (
|
|
160
|
-
|
|
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>');
|
|
161
300
|
}
|
|
162
301
|
}
|
|
163
302
|
},
|
|
164
303
|
onLoadSuccess: (el) => {
|
|
165
304
|
if (el) {
|
|
166
|
-
|
|
305
|
+
var wrap = el.querySelector('.loading-wrap');
|
|
306
|
+
if (wrap) wrap.remove();
|
|
167
307
|
}
|
|
168
308
|
},
|
|
169
309
|
onLoadFailure: (el) => {
|
|
170
310
|
if (el) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
+
}
|
|
174
318
|
}
|
|
175
319
|
},
|
|
176
|
-
|
|
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) => {
|
|
177
446
|
// 检查元素实例是否已加载 (而不是检查属性)
|
|
178
447
|
if (el && utils._loadedElements.has(el)) {
|
|
179
448
|
return;
|
|
180
449
|
}
|
|
181
450
|
const maxRetry = 3;
|
|
182
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
|
+
}
|
|
183
481
|
|
|
184
482
|
return new Promise((resolve, reject) => {
|
|
185
483
|
const load = () => {
|
|
186
|
-
utils.onLoading?.(el);
|
|
187
|
-
|
|
188
484
|
let timedOut = false;
|
|
189
485
|
const timeout = setTimeout(() => {
|
|
190
486
|
timedOut = true;
|
|
191
487
|
console.warn('[request] 超时:', url);
|
|
192
488
|
|
|
193
489
|
if (++retryCount >= maxRetry) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
490
|
+
if (cached) {
|
|
491
|
+
// 已有缓存渲染,保留缓存内容,不显示失败
|
|
492
|
+
resolve(cached.text);
|
|
493
|
+
} else {
|
|
494
|
+
utils.onLoadFailure?.(el);
|
|
495
|
+
onFailure?.();
|
|
496
|
+
reject('请求超时');
|
|
497
|
+
}
|
|
197
498
|
} else {
|
|
198
499
|
setTimeout(load, 1000);
|
|
199
500
|
}
|
|
@@ -209,6 +510,16 @@
|
|
|
209
510
|
if (timedOut) return;
|
|
210
511
|
// 标记元素实例为已加载
|
|
211
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
|
+
}
|
|
212
523
|
utils.onLoadSuccess?.(el);
|
|
213
524
|
callback(data);
|
|
214
525
|
resolve(data);
|
|
@@ -217,19 +528,38 @@
|
|
|
217
528
|
console.warn('[request] 错误:', err);
|
|
218
529
|
|
|
219
530
|
if (++retryCount >= maxRetry) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
531
|
+
if (cached) {
|
|
532
|
+
resolve(cached.text);
|
|
533
|
+
} else {
|
|
534
|
+
utils.onLoadFailure?.(el);
|
|
535
|
+
onFailure?.();
|
|
536
|
+
reject(err);
|
|
537
|
+
}
|
|
223
538
|
} else {
|
|
224
539
|
setTimeout(load, 1000);
|
|
225
540
|
}
|
|
226
541
|
});
|
|
227
542
|
};
|
|
228
543
|
|
|
229
|
-
|
|
544
|
+
if (cacheRenderDone) {
|
|
545
|
+
Promise.race([
|
|
546
|
+
cacheRenderDone,
|
|
547
|
+
new Promise(resolve => setTimeout(resolve, 5000))
|
|
548
|
+
]).then(load);
|
|
549
|
+
} else {
|
|
550
|
+
load();
|
|
551
|
+
}
|
|
230
552
|
});
|
|
231
553
|
},
|
|
232
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
|
+
|
|
233
563
|
return new Promise((resolve, reject) => {
|
|
234
564
|
let retryCount = 0;
|
|
235
565
|
|
|
@@ -245,6 +575,11 @@
|
|
|
245
575
|
.then(resp => {
|
|
246
576
|
clearTimeout(timer);
|
|
247
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
|
+
}
|
|
248
583
|
resolve(resp);
|
|
249
584
|
})
|
|
250
585
|
.catch(err => {
|
|
@@ -268,7 +603,7 @@
|
|
|
268
603
|
},
|
|
269
604
|
dark: {},
|
|
270
605
|
|
|
271
|
-
// 插件初始化管理器 - 统一处理 DOMContentLoaded
|
|
606
|
+
// 插件初始化管理器 - 统一处理 DOMContentLoaded 事件
|
|
272
607
|
_pluginInitializers: [],
|
|
273
608
|
_pluginCleanups: new Map(), // 存储每个插件的清理函数
|
|
274
609
|
|
|
@@ -344,4 +679,4 @@
|
|
|
344
679
|
utils.dark = Object.assign(utils.dark, {
|
|
345
680
|
push: utils.dark.method.toggle.push,
|
|
346
681
|
});
|
|
347
|
-
</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: {
|
|
@@ -1,17 +1,81 @@
|
|
|
1
|
-
<script defer src="<%- conf.js %>"></script>
|
|
2
1
|
<script>
|
|
3
2
|
utils.initPlugin(() => {
|
|
4
|
-
const els = document.querySelectorAll('.slide-up');
|
|
5
|
-
if (els.length
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
3
|
+
const els = Array.prototype.slice.call(document.querySelectorAll('.slide-up'));
|
|
4
|
+
if (els.length === 0) return;
|
|
5
|
+
|
|
6
|
+
// 吸顶/固定定位容器内的元素始终在视口内(或由容器控制显隐),
|
|
7
|
+
// 但 ScrollReveal 按文档坐标判断可见性,会误判这类元素为不可见,
|
|
8
|
+
// 导致带锚点/恢复滚动位置打开页面时组件一直不显示。
|
|
9
|
+
// 解决方式:以最近的吸顶/固定祖先作为该组元素的容器,
|
|
10
|
+
// 使可见性判断恒成立,入场动画保留且不再依赖主文档滚动。
|
|
11
|
+
const findPinnedContainer = (el) => {
|
|
12
|
+
let node = el;
|
|
13
|
+
while (node && node.nodeType === 1) {
|
|
14
|
+
const pos = window.getComputedStyle(node).position;
|
|
15
|
+
if (pos === 'sticky' || pos === 'fixed') return node;
|
|
16
|
+
node = node.parentElement;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const pinnedGroups = new Map(); // 容器 -> 元素列表
|
|
22
|
+
const targets = [];
|
|
23
|
+
els.forEach((el) => {
|
|
24
|
+
const container = findPinnedContainer(el);
|
|
25
|
+
if (container) {
|
|
26
|
+
const group = pinnedGroups.get(container) || [];
|
|
27
|
+
group.push(el);
|
|
28
|
+
pinnedGroups.set(container, group);
|
|
29
|
+
} else {
|
|
30
|
+
targets.push(el);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
if (pinnedGroups.size === 0 && targets.length === 0) return;
|
|
34
|
+
|
|
35
|
+
// 加载/初始化失败时兜底显示内容,避免整页空白
|
|
36
|
+
let fallbackApplied = false;
|
|
37
|
+
const revealFallback = () => {
|
|
38
|
+
if (fallbackApplied) return;
|
|
39
|
+
fallbackApplied = true;
|
|
40
|
+
document.documentElement.classList.add('sr-fallback');
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const slideUp = {
|
|
44
|
+
distance: `<%- conf.distance %>`,
|
|
45
|
+
duration: `<%- conf.duration %>`,
|
|
46
|
+
interval: `<%- conf.interval %>`,
|
|
47
|
+
scale: `<%- conf.scale %>`,
|
|
48
|
+
opacity: 0,
|
|
49
|
+
easing: "ease-out"
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// 看门狗:CDN 长时间无响应时强制显示内容
|
|
53
|
+
const watchdog = setTimeout(() => revealFallback(), 3000);
|
|
54
|
+
|
|
55
|
+
utils.js(`<%- conf.js %>`, { defer: true })
|
|
56
|
+
.then(() => {
|
|
57
|
+
if (fallbackApplied) return;
|
|
58
|
+
if (typeof ScrollReveal !== 'function') {
|
|
59
|
+
revealFallback();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const sr = ScrollReveal();
|
|
64
|
+
if (targets.length > 0) {
|
|
65
|
+
sr.reveal(targets, { ...slideUp });
|
|
66
|
+
}
|
|
67
|
+
pinnedGroups.forEach((group, container) => {
|
|
68
|
+
sr.reveal(group, { ...slideUp, container: container });
|
|
69
|
+
});
|
|
70
|
+
clearTimeout(watchdog);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.error('[Plugin scrollreveal] 初始化失败:', err);
|
|
73
|
+
revealFallback();
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
.catch((err) => {
|
|
77
|
+
console.error('[Plugin scrollreveal] 加载失败:', err);
|
|
78
|
+
revealFallback();
|
|
79
|
+
});
|
|
16
80
|
}, 'scrollreveal');
|
|
17
81
|
</script>
|