hexo-theme-gnix 9.0.0 → 10.0.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/README.md +4 -2
- package/include/hexo/feed.js +5 -5
- package/include/hexo/filter.js +25 -1
- package/include/hexo/generator/archive.js +116 -0
- package/include/hexo/generator/home.js +64 -0
- package/include/hexo/generator/index.js +82 -0
- package/include/hexo/generator/md_generator.js +87 -0
- package/include/hexo/generator/page.js +55 -0
- package/include/hexo/generator/tag.js +84 -0
- package/include/hexo/helper.js +38 -0
- package/include/hexo/i18n.js +183 -0
- package/include/util/article_font.js +132 -0
- package/include/util/i18n.js +280 -0
- package/include/util/theme.js +84 -0
- package/languages/en.yml +28 -0
- package/languages/zh-CN.yml +28 -0
- package/layout/archive.jsx +131 -127
- package/layout/common/article.jsx +283 -16
- package/layout/common/article_info.jsx +339 -0
- package/layout/common/article_media.jsx +11 -4
- package/layout/common/comment.jsx +15 -7
- package/layout/common/footer.jsx +6 -5
- package/layout/common/head.jsx +121 -32
- package/layout/common/navbar.jsx +195 -65
- package/layout/common/theme_selector.jsx +16 -14
- package/layout/layout.jsx +43 -5
- package/layout/misc/open_graph.jsx +162 -66
- package/layout/misc/paginator.jsx +2 -8
- package/layout/plugin/cookie_consent.jsx +252 -53
- package/layout/plugin/swup.jsx +1 -1
- package/layout/search/insight.jsx +1 -1
- package/layout/tag.jsx +3 -2
- package/layout/tags.jsx +81 -73
- package/package.json +5 -5
- package/scripts/index.js +1 -0
- package/source/css/archive.css +225 -180
- package/source/css/default.css +1162 -98
- package/source/css/responsive.css +426 -0
- package/source/css/shiki/shiki.css +12 -2081
- package/source/css/tags.css +183 -0
- package/source/css/twikoo.css +1049 -1045
- package/source/img/favicon.svg +1 -6
- package/source/img/og_image.webp +0 -0
- package/source/js/article-font-utils.js +99 -0
- package/source/js/busuanzi.js +91 -24
- package/source/js/components/chat.js +169 -50
- package/source/js/components/image-carousel.js +152 -108
- package/source/js/components/sidenote.js +210 -0
- package/source/js/components/text-image-section.js +78 -90
- package/source/js/components/theme-stacked.js +65 -33
- package/source/js/components/tree.js +30 -16
- package/source/js/decrypt.js +7 -2
- package/source/js/main.js +428 -5
- package/source/js/swup.js +39 -0
- package/source/js/theme-selector.js +26 -16
- package/include/hexo/generator.js +0 -53
- package/layout/misc/article_licensing.jsx +0 -99
- package/source/css/responsive/desktop.css +0 -36
- package/source/css/responsive/mobile.css +0 -29
- package/source/css/responsive/tablet.css +0 -43
- package/source/css/responsive/touch.css +0 -155
- package/source/img/logo.svg +0 -9
- package/source/js/archive-breadcrumb.js +0 -132
- package/source/js/host/cookieconsent/3.1.1/build/cookieconsent.min.css +0 -6
- package/source/js/host/cookieconsent/3.1.1/build/cookieconsent.min.js +0 -1
- package/source/js/swup.bundle.js +0 -1
package/source/js/main.js
CHANGED
|
@@ -17,6 +17,7 @@ function tableWrapFix() {
|
|
|
17
17
|
function twikoo_handler() {
|
|
18
18
|
const el = document.getElementById("tko");
|
|
19
19
|
if (!el) return;
|
|
20
|
+
if (el.dataset.initialized === "true" || el.dataset.initializing === "true") return;
|
|
20
21
|
|
|
21
22
|
const { envId, region, lang, jsUrl, cssUrl } = el.dataset;
|
|
22
23
|
|
|
@@ -26,10 +27,20 @@ function twikoo_handler() {
|
|
|
26
27
|
|
|
27
28
|
if (typeof window.twikoo?.init === "function") {
|
|
28
29
|
window.twikoo.init(config);
|
|
30
|
+
el.dataset.initialized = "true";
|
|
29
31
|
return;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
el.dataset.initializing = "true";
|
|
35
|
+
loadScriptOnce(jsUrl, () => {
|
|
36
|
+
if (el.dataset.initialized === "true") {
|
|
37
|
+
delete el.dataset.initializing;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
window.twikoo.init(config);
|
|
41
|
+
el.dataset.initialized = "true";
|
|
42
|
+
delete el.dataset.initializing;
|
|
43
|
+
});
|
|
33
44
|
}
|
|
34
45
|
// #region mdit@tab-plugin
|
|
35
46
|
/**
|
|
@@ -288,25 +299,435 @@ function handleMermaid() {
|
|
|
288
299
|
|
|
289
300
|
// #endregion
|
|
290
301
|
|
|
302
|
+
const articleFontConfig = window.__GNIX_ARTICLE_FONT_CONFIG__ || {};
|
|
303
|
+
const ARTICLE_FONT_STORAGE_KEY = articleFontConfig.storageKey || "gnix-article-font";
|
|
304
|
+
const ARTICLE_FONT_DEFAULT_SETTINGS = articleFontConfig.defaultSettings || { size: "medium", type: "serif", lineHeight: 1.7, weight: "regular" };
|
|
305
|
+
const ARTICLE_SIZE_OPTIONS = new Set(articleFontConfig.sizeOptions || ["small", "medium-small", "medium", "medium-large", "large"]);
|
|
306
|
+
const ARTICLE_FONT_OPTIONS = new Set(articleFontConfig.fontOptions || ["serif", "sans-serif", "mono", "handwriting"]);
|
|
307
|
+
const ARTICLE_WEIGHT_OPTIONS = new Set(articleFontConfig.weightOptions || ["light", "regular", "medium"]);
|
|
308
|
+
const ARTICLE_LINE_HEIGHT_MIN = articleFontConfig.lineHeight?.min ?? 1.45;
|
|
309
|
+
const ARTICLE_LINE_HEIGHT_MAX = articleFontConfig.lineHeight?.max ?? 1.9;
|
|
310
|
+
const ARTICLE_CUSTOM_FONT_OPTIONS = articleFontConfig.customFonts?.familyOptions || {
|
|
311
|
+
serif: "--font-serif",
|
|
312
|
+
"sans-serif": "--font-sans-serif",
|
|
313
|
+
mono: "--font-mono",
|
|
314
|
+
handwriting: "--font-handwriting",
|
|
315
|
+
};
|
|
316
|
+
const ARTICLE_CUSTOM_FONT_IMPORT_LIMIT = articleFontConfig.customFonts?.importLimit ?? 6;
|
|
317
|
+
const ARTICLE_CUSTOM_FONT_LINK_SELECTOR = 'link[data-gnix-custom-font="true"]';
|
|
318
|
+
|
|
319
|
+
function getCssVariableValue(name, fallback = "") {
|
|
320
|
+
if (typeof document === "undefined") return fallback;
|
|
321
|
+
|
|
322
|
+
const value = window.getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
323
|
+
return value || fallback;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Reads `--font-*` from default.css. Custom fonts are applied as inline
|
|
327
|
+
// styles on <html>, which would override the stylesheet value; we strip
|
|
328
|
+
// those overrides while reading and restore them after.
|
|
329
|
+
function getDefaultCustomFontFamilies() {
|
|
330
|
+
if (typeof document === "undefined") return {};
|
|
331
|
+
|
|
332
|
+
const html = document.documentElement;
|
|
333
|
+
const saved = {};
|
|
334
|
+
Object.values(ARTICLE_CUSTOM_FONT_OPTIONS).forEach((cssVar) => {
|
|
335
|
+
const inline = html.style.getPropertyValue(cssVar);
|
|
336
|
+
if (inline) {
|
|
337
|
+
saved[cssVar] = inline;
|
|
338
|
+
html.style.removeProperty(cssVar);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const defaults = {
|
|
343
|
+
serif: getCssVariableValue(ARTICLE_CUSTOM_FONT_OPTIONS.serif),
|
|
344
|
+
"sans-serif": getCssVariableValue(ARTICLE_CUSTOM_FONT_OPTIONS["sans-serif"]),
|
|
345
|
+
mono: getCssVariableValue(ARTICLE_CUSTOM_FONT_OPTIONS.mono),
|
|
346
|
+
handwriting: getCssVariableValue(ARTICLE_CUSTOM_FONT_OPTIONS.handwriting),
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
Object.keys(saved).forEach((cssVar) => {
|
|
350
|
+
html.style.setProperty(cssVar, saved[cssVar]);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
return defaults;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const ARTICLE_FONT_UTILS = window.__GNIX_ARTICLE_FONT_UTILS__ || {};
|
|
357
|
+
|
|
358
|
+
function normalizeCustomFonts(value = {}) {
|
|
359
|
+
const normalized = ARTICLE_FONT_UTILS.normalizeCustomFonts
|
|
360
|
+
? ARTICLE_FONT_UTILS.normalizeCustomFonts(value, ARTICLE_CUSTOM_FONT_OPTIONS, ARTICLE_CUSTOM_FONT_IMPORT_LIMIT)
|
|
361
|
+
: { imports: [], families: {} };
|
|
362
|
+
const defaultFamilies = getDefaultCustomFontFamilies();
|
|
363
|
+
|
|
364
|
+
Object.keys(ARTICLE_CUSTOM_FONT_OPTIONS).forEach((key) => {
|
|
365
|
+
if (!normalized.families[key]) {
|
|
366
|
+
normalized.families[key] = defaultFamilies[key] || "";
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
return normalized;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function applyCustomFonts(customFonts = {}) {
|
|
374
|
+
const normalized = normalizeCustomFonts(customFonts);
|
|
375
|
+
if (ARTICLE_FONT_UTILS.applyCustomFontImports) {
|
|
376
|
+
ARTICLE_FONT_UTILS.applyCustomFontImports(normalized.imports, ARTICLE_CUSTOM_FONT_LINK_SELECTOR);
|
|
377
|
+
}
|
|
378
|
+
if (ARTICLE_FONT_UTILS.applyCustomFontFamilies) {
|
|
379
|
+
ARTICLE_FONT_UTILS.applyCustomFontFamilies(document.documentElement, normalized.families, ARTICLE_CUSTOM_FONT_OPTIONS);
|
|
380
|
+
}
|
|
381
|
+
return normalized;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function initHoverPopover(trigger, popover) {
|
|
385
|
+
if (!trigger || !popover || typeof popover.showPopover !== "function" || typeof popover.hidePopover !== "function") {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
let hideTimer = null;
|
|
390
|
+
|
|
391
|
+
function clearHideTimer() {
|
|
392
|
+
if (hideTimer) {
|
|
393
|
+
clearTimeout(hideTimer);
|
|
394
|
+
hideTimer = null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function openPopover() {
|
|
399
|
+
clearHideTimer();
|
|
400
|
+
if (!popover.matches(":popover-open")) {
|
|
401
|
+
popover.showPopover();
|
|
402
|
+
}
|
|
403
|
+
const rect = trigger.getBoundingClientRect();
|
|
404
|
+
const popoverWidth = popover.offsetWidth || 0;
|
|
405
|
+
const left = Math.min(Math.max(16, rect.left), Math.max(16, window.innerWidth - popoverWidth - 16));
|
|
406
|
+
popover.style.left = `${left}px`;
|
|
407
|
+
popover.style.top = `${rect.bottom + 8}px`;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function scheduleClose() {
|
|
411
|
+
clearHideTimer();
|
|
412
|
+
hideTimer = window.setTimeout(() => {
|
|
413
|
+
const hasPointer = trigger.matches(":hover") || popover.matches(":hover");
|
|
414
|
+
const hasFocus = trigger.matches(":focus-visible") || popover.contains(document.activeElement);
|
|
415
|
+
if (!hasPointer && !hasFocus && popover.matches(":popover-open")) {
|
|
416
|
+
popover.hidePopover();
|
|
417
|
+
}
|
|
418
|
+
}, 80);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
trigger.addEventListener("pointerenter", openPopover);
|
|
422
|
+
trigger.addEventListener("focus", openPopover);
|
|
423
|
+
trigger.addEventListener("pointerleave", scheduleClose);
|
|
424
|
+
trigger.addEventListener("blur", scheduleClose);
|
|
425
|
+
trigger.addEventListener("click", openPopover);
|
|
426
|
+
|
|
427
|
+
popover.addEventListener("pointerenter", openPopover);
|
|
428
|
+
popover.addEventListener("pointerleave", scheduleClose);
|
|
429
|
+
popover.addEventListener("focusin", openPopover);
|
|
430
|
+
popover.addEventListener("focusout", scheduleClose);
|
|
431
|
+
popover.addEventListener("toggle", (event) => {
|
|
432
|
+
if (event.newState === "closed") clearHideTimer();
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function normalizeArticleLineHeight(value) {
|
|
437
|
+
if (value === "compact") return 1.55;
|
|
438
|
+
if (value === "normal") return 1.7;
|
|
439
|
+
if (value === "relaxed") return 1.85;
|
|
440
|
+
|
|
441
|
+
const parsed = Number(value);
|
|
442
|
+
if (!Number.isFinite(parsed)) return ARTICLE_FONT_DEFAULT_SETTINGS.lineHeight;
|
|
443
|
+
return Math.min(ARTICLE_LINE_HEIGHT_MAX, Math.max(ARTICLE_LINE_HEIGHT_MIN, parsed));
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function normalizeArticleFontSettings(value = {}) {
|
|
447
|
+
const candidate = value || {};
|
|
448
|
+
return {
|
|
449
|
+
size: ARTICLE_SIZE_OPTIONS.has(candidate.size) ? candidate.size : ARTICLE_FONT_DEFAULT_SETTINGS.size,
|
|
450
|
+
type: ARTICLE_FONT_OPTIONS.has(candidate.type) ? candidate.type : ARTICLE_FONT_DEFAULT_SETTINGS.type,
|
|
451
|
+
lineHeight: normalizeArticleLineHeight(candidate.lineHeight),
|
|
452
|
+
weight: ARTICLE_WEIGHT_OPTIONS.has(candidate.weight) ? candidate.weight : ARTICLE_FONT_DEFAULT_SETTINGS.weight,
|
|
453
|
+
customFonts: normalizeCustomFonts(candidate.customFonts),
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function getArticleFontSettings() {
|
|
458
|
+
let settings = { ...ARTICLE_FONT_DEFAULT_SETTINGS };
|
|
459
|
+
try {
|
|
460
|
+
const stored = localStorage.getItem(ARTICLE_FONT_STORAGE_KEY);
|
|
461
|
+
if (stored) settings = normalizeArticleFontSettings({ ...ARTICLE_FONT_DEFAULT_SETTINGS, ...JSON.parse(stored) });
|
|
462
|
+
} catch {
|
|
463
|
+
settings = { ...ARTICLE_FONT_DEFAULT_SETTINGS };
|
|
464
|
+
}
|
|
465
|
+
return settings;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function applyArticleFontSettings(settings = getArticleFontSettings()) {
|
|
469
|
+
const normalized = normalizeArticleFontSettings(settings);
|
|
470
|
+
const html = document.documentElement;
|
|
471
|
+
|
|
472
|
+
applyCustomFonts(normalized.customFonts);
|
|
473
|
+
html.dataset.articleFontSize = normalized.size;
|
|
474
|
+
html.dataset.articleFontFamily = normalized.type;
|
|
475
|
+
html.dataset.articleLineHeight = String(normalized.lineHeight);
|
|
476
|
+
html.dataset.articleFontWeight = normalized.weight;
|
|
477
|
+
html.style.setProperty("--article-line-height", String(normalized.lineHeight));
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function saveArticleFontSettings(settings) {
|
|
481
|
+
try {
|
|
482
|
+
localStorage.setItem(ARTICLE_FONT_STORAGE_KEY, JSON.stringify(normalizeArticleFontSettings(settings)));
|
|
483
|
+
} catch {
|
|
484
|
+
// Keep the current page responsive even when storage is unavailable.
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function initArticleSettings() {
|
|
489
|
+
const settings = getArticleFontSettings();
|
|
490
|
+
applyArticleFontSettings(settings);
|
|
491
|
+
|
|
492
|
+
const fontSettingsPopover = document.getElementById("article-font-settings");
|
|
493
|
+
if (!fontSettingsPopover) return;
|
|
494
|
+
|
|
495
|
+
function updateButtonStates(selector, isActive) {
|
|
496
|
+
fontSettingsPopover.querySelectorAll(selector).forEach((btn) => {
|
|
497
|
+
const active = isActive(btn);
|
|
498
|
+
btn.classList.toggle("is-active", active);
|
|
499
|
+
btn.setAttribute("aria-pressed", String(active));
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const lineHeightSlider = fontSettingsPopover.querySelector(".font-line-height-slider");
|
|
504
|
+
const lineHeightValue = fontSettingsPopover.querySelector(".font-line-height-value");
|
|
505
|
+
const customFontForm = fontSettingsPopover.querySelector(".font-custom-form");
|
|
506
|
+
const customFontImportInput = fontSettingsPopover.querySelector(".font-custom-imports");
|
|
507
|
+
const customFontResetButton = fontSettingsPopover.querySelector(".font-custom-reset");
|
|
508
|
+
const customFontFamilyInputs = fontSettingsPopover.querySelectorAll(".font-custom-family-input");
|
|
509
|
+
const customFontHelpButton = fontSettingsPopover.querySelector(".font-custom-help-btn");
|
|
510
|
+
const customFontHelpPopover = document.getElementById("font-custom-help-popover");
|
|
511
|
+
const customFontToggleButton = fontSettingsPopover.querySelector(".font-custom-toggle");
|
|
512
|
+
const customFontPanel = fontSettingsPopover.querySelector(".font-custom-panel");
|
|
513
|
+
|
|
514
|
+
initHoverPopover(customFontHelpButton, customFontHelpPopover);
|
|
515
|
+
|
|
516
|
+
if (customFontToggleButton && customFontPanel) {
|
|
517
|
+
const syncCustomFontPanelState = (expanded) => {
|
|
518
|
+
customFontToggleButton.setAttribute("aria-expanded", String(expanded));
|
|
519
|
+
customFontPanel.dataset.expanded = String(expanded);
|
|
520
|
+
customFontPanel.setAttribute("aria-hidden", String(!expanded));
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
syncCustomFontPanelState(false);
|
|
524
|
+
customFontToggleButton.addEventListener("click", () => {
|
|
525
|
+
const expanded = customFontToggleButton.getAttribute("aria-expanded") === "true";
|
|
526
|
+
syncCustomFontPanelState(!expanded);
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function updateLineHeightUI() {
|
|
531
|
+
if (lineHeightSlider) {
|
|
532
|
+
lineHeightSlider.value = String(settings.lineHeight);
|
|
533
|
+
}
|
|
534
|
+
if (lineHeightValue) {
|
|
535
|
+
lineHeightValue.textContent = settings.lineHeight.toFixed(2);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function updateActiveStates() {
|
|
540
|
+
updateButtonStates(".font-size-btn", (btn) => btn.dataset.size === settings.size);
|
|
541
|
+
updateButtonStates(".font-type-btn", (btn) => btn.dataset.font === settings.type);
|
|
542
|
+
updateButtonStates(".font-weight-btn", (btn) => btn.dataset.weight === settings.weight);
|
|
543
|
+
updateLineHeightUI();
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function updateCustomFontUI() {
|
|
547
|
+
const customFonts = normalizeCustomFonts(settings.customFonts);
|
|
548
|
+
if (customFontImportInput) {
|
|
549
|
+
customFontImportInput.value = customFonts.imports.join("\n");
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
customFontFamilyInputs.forEach((input) => {
|
|
553
|
+
input.value = customFonts.families[input.dataset.fontFamily] || "";
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function readCustomFontsFromUI() {
|
|
558
|
+
const families = {};
|
|
559
|
+
|
|
560
|
+
customFontFamilyInputs.forEach((input) => {
|
|
561
|
+
families[input.dataset.fontFamily] = input.value;
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
return normalizeCustomFonts({
|
|
565
|
+
imports: customFontImportInput?.value || "",
|
|
566
|
+
families,
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
fontSettingsPopover.querySelectorAll(".font-size-btn").forEach((btn) => {
|
|
571
|
+
btn.addEventListener("click", () => {
|
|
572
|
+
if (!ARTICLE_SIZE_OPTIONS.has(btn.dataset.size)) return;
|
|
573
|
+
settings.size = btn.dataset.size;
|
|
574
|
+
saveArticleFontSettings(settings);
|
|
575
|
+
updateActiveStates();
|
|
576
|
+
applyArticleFontSettings(settings);
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
fontSettingsPopover.querySelectorAll(".font-type-btn").forEach((btn) => {
|
|
581
|
+
btn.addEventListener("click", () => {
|
|
582
|
+
if (!ARTICLE_FONT_OPTIONS.has(btn.dataset.font)) return;
|
|
583
|
+
settings.type = btn.dataset.font;
|
|
584
|
+
saveArticleFontSettings(settings);
|
|
585
|
+
updateActiveStates();
|
|
586
|
+
applyArticleFontSettings(settings);
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
if (lineHeightSlider) {
|
|
591
|
+
lineHeightSlider.min = String(ARTICLE_LINE_HEIGHT_MIN);
|
|
592
|
+
lineHeightSlider.max = String(ARTICLE_LINE_HEIGHT_MAX);
|
|
593
|
+
lineHeightSlider.step = "0.05";
|
|
594
|
+
lineHeightSlider.addEventListener("input", () => {
|
|
595
|
+
settings.lineHeight = normalizeArticleLineHeight(lineHeightSlider.value);
|
|
596
|
+
saveArticleFontSettings(settings);
|
|
597
|
+
updateActiveStates();
|
|
598
|
+
applyArticleFontSettings(settings);
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
fontSettingsPopover.querySelectorAll(".font-weight-btn").forEach((btn) => {
|
|
603
|
+
btn.addEventListener("click", () => {
|
|
604
|
+
if (!ARTICLE_WEIGHT_OPTIONS.has(btn.dataset.weight)) return;
|
|
605
|
+
settings.weight = btn.dataset.weight;
|
|
606
|
+
saveArticleFontSettings(settings);
|
|
607
|
+
updateActiveStates();
|
|
608
|
+
applyArticleFontSettings(settings);
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
if (customFontForm) {
|
|
613
|
+
customFontForm.addEventListener("submit", (event) => {
|
|
614
|
+
event.preventDefault();
|
|
615
|
+
settings.customFonts = readCustomFontsFromUI();
|
|
616
|
+
saveArticleFontSettings(settings);
|
|
617
|
+
applyArticleFontSettings(settings);
|
|
618
|
+
updateCustomFontUI();
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (customFontResetButton) {
|
|
623
|
+
customFontResetButton.addEventListener("click", () => {
|
|
624
|
+
settings.customFonts = normalizeCustomFonts();
|
|
625
|
+
saveArticleFontSettings(settings);
|
|
626
|
+
applyArticleFontSettings(settings);
|
|
627
|
+
|
|
628
|
+
const defaults = getDefaultCustomFontFamilies();
|
|
629
|
+
if (customFontImportInput) {
|
|
630
|
+
customFontImportInput.value = "";
|
|
631
|
+
}
|
|
632
|
+
customFontFamilyInputs.forEach((input) => {
|
|
633
|
+
const key = input.dataset.fontFamily;
|
|
634
|
+
input.value = defaults[key] || "";
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
updateActiveStates();
|
|
640
|
+
updateCustomFontUI();
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function initArticleCommentPopover() {
|
|
644
|
+
const commentPopover = document.getElementById("article-comment-popover");
|
|
645
|
+
if (!commentPopover) {
|
|
646
|
+
twikoo_handler();
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Preload twikoo JS during idle time so comments render faster on click
|
|
651
|
+
const tko = document.getElementById("tko");
|
|
652
|
+
if (tko && tko.dataset.jsUrl) {
|
|
653
|
+
const preload = () => loadScriptOnce(tko.dataset.jsUrl, () => {});
|
|
654
|
+
if (typeof requestIdleCallback === "function") {
|
|
655
|
+
requestIdleCallback(preload, { timeout: 3000 });
|
|
656
|
+
} else {
|
|
657
|
+
setTimeout(preload, 200);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const initializeComments = () => twikoo_handler();
|
|
662
|
+
if (commentPopover.matches(":popover-open")) {
|
|
663
|
+
initializeComments();
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (commentPopover.dataset.bound === "true") return;
|
|
667
|
+
commentPopover.dataset.bound = "true";
|
|
668
|
+
commentPopover.addEventListener("toggle", (event) => {
|
|
669
|
+
if (event.newState === "open") initializeComments();
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function getComparablePath(url) {
|
|
674
|
+
const path = new URL(url, window.location.href).pathname.replace(/\/index\.html$/, "/").replace(/\/+$/, "");
|
|
675
|
+
return path || "/";
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function updateNavbarCurrentPage() {
|
|
679
|
+
const currentPath = getComparablePath(window.location.href);
|
|
680
|
+
document.querySelectorAll(".navbar-start .navbar-item").forEach((item) => {
|
|
681
|
+
const itemPath = getComparablePath(item.href);
|
|
682
|
+
const active = itemPath === currentPath || (itemPath !== "/" && currentPath.startsWith(`${itemPath}/`));
|
|
683
|
+
|
|
684
|
+
item.classList.toggle("is-active", active);
|
|
685
|
+
if (active) {
|
|
686
|
+
item.setAttribute("aria-current", "page");
|
|
687
|
+
} else {
|
|
688
|
+
item.removeAttribute("aria-current");
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function refreshNavbarIcons() {
|
|
694
|
+
document.querySelectorAll(".navbar-end iconify-icon").forEach((el) => {
|
|
695
|
+
if (!el.getAttribute("icon")) return;
|
|
696
|
+
if (el.shadowRoot?.querySelector("svg")) return;
|
|
697
|
+
const parent = el.parentNode;
|
|
698
|
+
if (!parent) return;
|
|
699
|
+
const next = el.nextSibling;
|
|
700
|
+
parent.removeChild(el);
|
|
701
|
+
parent.insertBefore(el, next);
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
|
|
291
705
|
function initPage() {
|
|
292
706
|
tableWrapFix();
|
|
293
707
|
initializeTabs();
|
|
294
708
|
handleMermaid();
|
|
295
709
|
addHighlightTool();
|
|
710
|
+
initArticleSettings();
|
|
296
711
|
const zoomOpts = { background: "hsla(from var(--mantle) / 0.9)" };
|
|
297
712
|
const zoomImgs = new Set();
|
|
298
713
|
document.querySelectorAll(".content img").forEach((img) => zoomImgs.add(img));
|
|
299
714
|
mediumZoom([...zoomImgs], zoomOpts);
|
|
300
|
-
|
|
715
|
+
initArticleCommentPopover();
|
|
716
|
+
updateNavbarCurrentPage();
|
|
717
|
+
refreshNavbarIcons();
|
|
301
718
|
}
|
|
302
719
|
|
|
303
720
|
document.addEventListener("DOMContentLoaded", initPage, { once: true });
|
|
304
721
|
|
|
305
|
-
|
|
306
|
-
if (
|
|
307
|
-
|
|
722
|
+
function bindSwupPageHook(swupInstance) {
|
|
723
|
+
if (!swupInstance || swupInstance.gnixMainPageHookBound) return;
|
|
724
|
+
swupInstance.gnixMainPageHookBound = true;
|
|
725
|
+
swupInstance.hooks.on("page:view", initPage);
|
|
308
726
|
}
|
|
309
727
|
|
|
728
|
+
bindSwupPageHook(window.swup);
|
|
729
|
+
document.addEventListener("gnix:swup-ready", (event) => bindSwupPageHook(event.detail?.swup), { once: true });
|
|
730
|
+
|
|
310
731
|
document.addEventListener("keydown", handleKeyDown, {
|
|
311
732
|
capture: true, // 捕获阶段监听,优先于浏览器默认处理
|
|
312
733
|
passive: false, // 允许调用 preventDefault
|
|
@@ -326,3 +747,5 @@ function toggleNav(event) {
|
|
|
326
747
|
menu.classList.remove("is-active");
|
|
327
748
|
}
|
|
328
749
|
}
|
|
750
|
+
|
|
751
|
+
window.toggleNav = toggleNav;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import SwupHeadPlugin from "https://unpkg.com/@swup/head-plugin@2?module";
|
|
2
|
+
import SwupScriptsPlugin from "https://unpkg.com/@swup/scripts-plugin@2?module";
|
|
3
|
+
import Swup from "https://unpkg.com/swup@4?module";
|
|
4
|
+
|
|
5
|
+
const swup = new Swup({
|
|
6
|
+
containers: ["#swup"],
|
|
7
|
+
cache: true,
|
|
8
|
+
native: false,
|
|
9
|
+
animationSelector: false,
|
|
10
|
+
plugins: [
|
|
11
|
+
new SwupHeadPlugin({
|
|
12
|
+
persistTags: true,
|
|
13
|
+
}),
|
|
14
|
+
new SwupScriptsPlugin({
|
|
15
|
+
optin: true,
|
|
16
|
+
}),
|
|
17
|
+
],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
document.addEventListener(
|
|
21
|
+
"click",
|
|
22
|
+
(event) => {
|
|
23
|
+
if (!swup.navigating || event.defaultPrevented) return;
|
|
24
|
+
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
25
|
+
|
|
26
|
+
const link = event.target instanceof Element ? event.target.closest("a[href]") : null;
|
|
27
|
+
if (!link || link.matches('[download], [target="_blank"]')) return;
|
|
28
|
+
|
|
29
|
+
const url = new URL(link.href, window.location.href);
|
|
30
|
+
if (url.origin !== window.location.origin) return;
|
|
31
|
+
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
event.stopImmediatePropagation();
|
|
34
|
+
},
|
|
35
|
+
{ capture: true },
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
window.swup = swup;
|
|
39
|
+
document.dispatchEvent(new CustomEvent("gnix:swup-ready", { detail: { swup } }));
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
((window, document, localStorage) => {
|
|
2
|
-
const
|
|
3
|
-
const colorSchemeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
2
|
+
const themeConfig = window.__GNIX_THEME_CONFIG__;
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
if (!themeConfig) return;
|
|
5
|
+
|
|
6
|
+
const STORAGE_KEY = themeConfig.storageKey;
|
|
7
|
+
const DEFAULT_THEME = themeConfig.defaultTheme;
|
|
8
|
+
const SYSTEM_THEME = themeConfig.systemTheme;
|
|
9
|
+
const THEME_MAP = themeConfig.themeClassMap || {};
|
|
10
|
+
const THEME_CLASSES = [...new Set(Object.values(THEME_MAP))];
|
|
11
|
+
const colorSchemeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
13
12
|
|
|
14
13
|
let currentIndex = 0;
|
|
15
14
|
let previewTheme = null;
|
|
@@ -19,18 +18,28 @@
|
|
|
19
18
|
let previousFocus = null;
|
|
20
19
|
let popoverEl = null;
|
|
21
20
|
|
|
21
|
+
function isValidTheme(theme) {
|
|
22
|
+
return theme === DEFAULT_THEME || Object.hasOwn(THEME_MAP, theme);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveTheme(theme) {
|
|
26
|
+
return theme === DEFAULT_THEME ? (colorSchemeMediaQuery.matches ? SYSTEM_THEME.dark : SYSTEM_THEME.light) : theme;
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
function getThemePreference() {
|
|
23
30
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
24
|
-
return stored
|
|
31
|
+
return isValidTheme(stored) ? stored : DEFAULT_THEME;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
function applyTheme(theme, persist = false) {
|
|
28
|
-
const
|
|
35
|
+
const preference = isValidTheme(theme) ? theme : DEFAULT_THEME;
|
|
36
|
+
const resolved = resolveTheme(preference);
|
|
37
|
+
const themeClass = THEME_MAP[resolved];
|
|
29
38
|
const html = document.documentElement;
|
|
30
39
|
html.setAttribute("data-theme", resolved);
|
|
31
|
-
html.classList.remove(
|
|
32
|
-
html.classList.add(
|
|
33
|
-
if (persist) localStorage.setItem(STORAGE_KEY,
|
|
40
|
+
html.classList.remove(...THEME_CLASSES);
|
|
41
|
+
if (themeClass) html.classList.add(themeClass);
|
|
42
|
+
if (persist) localStorage.setItem(STORAGE_KEY, preference);
|
|
34
43
|
}
|
|
35
44
|
|
|
36
45
|
function updateFocus() {
|
|
@@ -54,6 +63,7 @@
|
|
|
54
63
|
originalTheme = getThemePreference();
|
|
55
64
|
previewTheme = null;
|
|
56
65
|
shouldApply = false;
|
|
66
|
+
currentIndex = 0;
|
|
57
67
|
themeOptions = el.querySelectorAll(".theme-option");
|
|
58
68
|
|
|
59
69
|
themeOptions.forEach((option, index) => {
|
|
@@ -143,7 +153,7 @@
|
|
|
143
153
|
}
|
|
144
154
|
|
|
145
155
|
colorSchemeMediaQuery.addEventListener("change", () => {
|
|
146
|
-
if (getThemePreference() ===
|
|
156
|
+
if (getThemePreference() === DEFAULT_THEME) applyTheme(DEFAULT_THEME, true);
|
|
147
157
|
});
|
|
148
158
|
|
|
149
159
|
window.selectThemeOption = (index) => {
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
const util = require("hexo-util");
|
|
2
|
-
|
|
3
|
-
module.exports = (hexo) => {
|
|
4
|
-
hexo.extend.generator.register("insight", function (locals) {
|
|
5
|
-
const url_for = hexo.extend.helper.get("url_for").bind(this);
|
|
6
|
-
function minify(str) {
|
|
7
|
-
return util
|
|
8
|
-
.stripHTML(str)
|
|
9
|
-
.trim()
|
|
10
|
-
.replace(/\n/g, " ")
|
|
11
|
-
.replace(/\s+/g, " ")
|
|
12
|
-
.replace(/&#x([\da-fA-F]+);/g, (_, hex) => {
|
|
13
|
-
return String.fromCharCode(parseInt(hex, 16));
|
|
14
|
-
})
|
|
15
|
-
.replace(/&#([\d]+);/g, (_, dec) => {
|
|
16
|
-
return String.fromCharCode(dec);
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
function mapPost(post) {
|
|
20
|
-
return {
|
|
21
|
-
title: util.escapeHTML(post.title).trim(),
|
|
22
|
-
text: post.password ? "该文章需要密码" : minify(post.content),
|
|
23
|
-
link: url_for(post.path),
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
function mapTag(tag) {
|
|
27
|
-
return {
|
|
28
|
-
name: util.escapeHTML(tag.name).trim(),
|
|
29
|
-
slug: minify(tag.slug),
|
|
30
|
-
link: url_for(tag.path),
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
const site = {
|
|
34
|
-
posts: locals.posts.map(mapPost),
|
|
35
|
-
tags: locals.tags.map(mapTag),
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
path: "/content.json",
|
|
40
|
-
data: JSON.stringify(site),
|
|
41
|
-
};
|
|
42
|
-
});
|
|
43
|
-
// Generate "<root>/tags/" page
|
|
44
|
-
hexo.extend.generator.register("tags", (locals) => {
|
|
45
|
-
return {
|
|
46
|
-
path: "tags/",
|
|
47
|
-
layout: ["tags"],
|
|
48
|
-
data: Object.assign({}, locals, {
|
|
49
|
-
__tags: true,
|
|
50
|
-
}),
|
|
51
|
-
};
|
|
52
|
-
});
|
|
53
|
-
};
|