hexo-theme-gnix 7.0.0 → 9.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.
Files changed (53) hide show
  1. package/README.md +6 -2
  2. package/include/hexo/encrypt.js +42 -0
  3. package/include/hexo/feed.js +330 -0
  4. package/include/util/common.js +7 -16
  5. package/languages/en.yml +5 -2
  6. package/languages/zh-CN.yml +5 -2
  7. package/layout/archive.jsx +8 -204
  8. package/layout/comment/twikoo.jsx +2 -11
  9. package/layout/common/article.jsx +45 -32
  10. package/layout/common/article_cover.jsx +11 -1
  11. package/layout/common/article_media.jsx +2 -4
  12. package/layout/common/footer.jsx +10 -14
  13. package/layout/common/head.jsx +7 -15
  14. package/layout/common/navbar.jsx +3 -18
  15. package/layout/common/scripts.jsx +6 -5
  16. package/layout/common/theme_selector.jsx +5 -6
  17. package/layout/common/toc.jsx +8 -14
  18. package/layout/index.jsx +2 -4
  19. package/layout/misc/open_graph.jsx +4 -4
  20. package/layout/misc/paginator.jsx +10 -4
  21. package/layout/misc/structured_data.jsx +3 -4
  22. package/layout/plugin/busuanzi.jsx +1 -1
  23. package/layout/plugin/cookie_consent.jsx +40 -31
  24. package/layout/plugin/swup.jsx +2 -22
  25. package/layout/search/insight.jsx +16 -3
  26. package/package.json +12 -8
  27. package/scripts/hot-reload.js +92 -0
  28. package/scripts/index.js +2 -0
  29. package/source/css/archive.css +251 -0
  30. package/source/css/default.css +300 -309
  31. package/source/css/encrypt.css +55 -0
  32. package/source/css/responsive/desktop.css +0 -119
  33. package/source/css/responsive/mobile.css +2 -22
  34. package/source/css/responsive/touch.css +9 -103
  35. package/source/css/twikoo.css +265 -249
  36. package/source/img/og_image.webp +0 -0
  37. package/source/js/archive-breadcrumb.js +1 -5
  38. package/source/js/busuanzi.js +1 -12
  39. package/source/js/components/chat.js +239 -0
  40. package/source/js/components/image-carousel.js +410 -0
  41. package/source/js/components/text-image-section.js +180 -0
  42. package/source/js/components/theme-stacked.js +165 -246
  43. package/source/js/components/tree.js +437 -0
  44. package/source/js/decrypt.js +112 -0
  45. package/source/js/insight.js +75 -65
  46. package/source/js/main.js +48 -31
  47. package/source/js/mdit/mermaid.js +12 -4
  48. package/source/js/swup.bundle.js +1 -0
  49. package/source/js/theme-selector.js +94 -113
  50. package/source/img/og_image.png +0 -0
  51. package/source/js/host/swup/Swup.umd.min.js +0 -1
  52. package/source/js/host/swup/head-plugin.umd.min.js +0 -1
  53. package/source/js/host/swup/scripts-plugin.umd.min.js +0 -2
@@ -3,91 +3,95 @@
3
3
  * Displays themes as a stack of interactive cards with circular navigation
4
4
  */
5
5
 
6
+ const PREVIEW_COLORS = [
7
+ "rosewater",
8
+ "flamingo",
9
+ "pink",
10
+ "mauve",
11
+ "red",
12
+ "maroon",
13
+ "peach",
14
+ "yellow",
15
+ "green",
16
+ "teal",
17
+ "sky",
18
+ "sapphire",
19
+ "blue",
20
+ "lavender",
21
+ "text",
22
+ "subtext1",
23
+ "subtext0",
24
+ "overlay2",
25
+ "overlay1",
26
+ "overlay0",
27
+ "surface2",
28
+ "surface1",
29
+ "surface0",
30
+ ];
31
+
32
+ const THEMES = [
33
+ { id: "latte", name: "Catppuccin Latte" },
34
+ { id: "nord", name: "Nord Light" },
35
+ { id: "nord_night", name: "Nord Night" },
36
+ { id: "rose_pine", name: "Rosé Pine" },
37
+ { id: "mocha", name: "Catppuccin Mocha" },
38
+ { id: "tokyo_night", name: "Tokyo Night" },
39
+ ];
40
+
6
41
  class ThemeStackedElement extends HTMLElement {
7
42
  constructor() {
8
43
  super();
9
44
  this._currentIndex = 0;
10
- this._isDragging = false;
11
- this._startX = 0;
12
- this._currentX = 0;
13
45
  this._cards = [];
14
-
15
- this.attachShadow({ mode: "open" });
16
-
17
- // All theme color variables to display
18
- this.previewColors = [
19
- "rosewater",
20
- "flamingo",
21
- "pink",
22
- "mauve",
23
- "red",
24
- "maroon",
25
- "peach",
26
- "yellow",
27
- "green",
28
- "teal",
29
- "sky",
30
- "sapphire",
31
- "blue",
32
- "lavender",
33
- "text",
34
- "subtext1",
35
- "subtext0",
36
- "overlay2",
37
- "overlay1",
38
- "overlay0",
39
- "surface2",
40
- "surface1",
41
- "surface0",
42
- ];
43
-
44
- this.themes = [
45
- { id: "latte", name: "Catppuccin Latte" },
46
- { id: "nord", name: "Nord Light" },
47
- { id: "nord_night", name: "Nord Night" },
48
- { id: "rose_pine", name: "Rosé Pine" },
49
- { id: "mocha", name: "Catppuccin Mocha" },
50
- { id: "tokyo_night", name: "Tokyo Night" },
51
- ];
52
-
46
+ this._isVisible = false;
53
47
  this._themeData = {};
48
+ this.attachShadow({ mode: "open" });
54
49
  }
55
50
 
56
- connectedCallback() {
57
- this.loadThemeData().then(() => {
58
- this.render();
59
- this.init();
51
+ async connectedCallback() {
52
+ this._observer = new IntersectionObserver((e) => {
53
+ this._isVisible = e[0].isIntersecting;
60
54
  });
55
+ this._observer.observe(this);
56
+ await this.loadThemeData();
57
+ this.render();
58
+ this.init();
59
+ }
60
+
61
+ disconnectedCallback() {
62
+ this._observer?.disconnect();
63
+ document.removeEventListener("keydown", this._keyHandler);
64
+ document.removeEventListener("mouseup", this._mouseUpHandler);
61
65
  }
62
66
 
63
67
  async loadThemeData() {
64
- // Try to get theme colors from CSS variables of existing themes
65
- // Create a temporary container to extract color values
66
- const tempContainer = document.createElement("div");
67
- tempContainer.style.cssText = "position:absolute;left:-9999px;width:0;height:0;overflow:hidden;";
68
- document.body.appendChild(tempContainer);
69
-
70
- for (const theme of this.themes) {
71
- const themeEl = document.createElement("div");
72
- themeEl.setAttribute("data-theme", theme.id);
73
- themeEl.style.cssText = "position:absolute;";
74
- tempContainer.appendChild(themeEl);
75
-
76
- // Extract computed colors for this theme
77
- const colors = {};
78
- const computedStyle = window.getComputedStyle(themeEl);
79
-
80
- // Get each color variable
81
- for (const colorVar of this.previewColors) {
82
- const varValue = computedStyle.getPropertyValue(`--${colorVar}`).trim();
83
- if (varValue) colors[colorVar] = varValue;
68
+ if (window.__cachedThemeData) {
69
+ this._themeData = window.__cachedThemeData;
70
+ return;
71
+ }
72
+ try {
73
+ const cached = localStorage.getItem("themeDataCache");
74
+ if (cached) {
75
+ this._themeData = window.__cachedThemeData = JSON.parse(cached);
76
+ return;
84
77
  }
85
- this._themeData[theme.id] = colors;
78
+ } catch (_e) {}
79
+
80
+ const temp = document.createElement("div");
81
+ temp.style.cssText = "position:absolute;left:-9999px;width:0;height:0;overflow:hidden;";
82
+ document.body.appendChild(temp);
86
83
 
87
- themeEl.remove();
84
+ for (const theme of THEMES) {
85
+ temp.setAttribute("data-theme", theme.id);
86
+ const computed = window.getComputedStyle(temp);
87
+ this._themeData[theme.id] = Object.fromEntries(PREVIEW_COLORS.map((c) => [c, computed.getPropertyValue(`--${c}`).trim()]).filter(([, v]) => v));
88
88
  }
89
89
 
90
- tempContainer.remove();
90
+ temp.remove();
91
+ window.__cachedThemeData = this._themeData;
92
+ try {
93
+ localStorage.setItem("themeDataCache", JSON.stringify(this._themeData));
94
+ } catch (_e) {}
91
95
  }
92
96
 
93
97
  render() {
@@ -128,17 +132,18 @@ class ThemeStackedElement extends HTMLElement {
128
132
  position: relative;
129
133
  width: min(550px, 90%);
130
134
  background: var(--base);
131
- border: 2px solid var(--surface0);
135
+ border: 2px solid color-mix(in oklch, var(--base) 80%, var(--text));
132
136
  border-radius: 16px;
133
137
  padding: 1.5rem;
134
138
  cursor: grab;
135
- transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
139
+ transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1);
136
140
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
137
141
  user-select: none;
138
142
  touch-action: pan-y;
139
143
  opacity: 0;
140
144
  transform: scale(0.8) translateX(200px);
141
145
  pointer-events: none;
146
+ contain: layout style;
142
147
 
143
148
  &.active {
144
149
  opacity: 1;
@@ -193,7 +198,7 @@ class ThemeStackedElement extends HTMLElement {
193
198
 
194
199
  .color-grid {
195
200
  display: grid;
196
- grid-template-columns: repeat(8, 1fr);
201
+ grid-template-columns: repeat(9, 1fr);
197
202
  gap: 6px;
198
203
  margin-bottom: 1rem;
199
204
 
@@ -302,8 +307,6 @@ class ThemeStackedElement extends HTMLElement {
302
307
  &:active {
303
308
  transform: scale(0.95);
304
309
  }
305
-
306
-
307
310
  }
308
311
 
309
312
  .dots {
@@ -337,9 +340,7 @@ class ThemeStackedElement extends HTMLElement {
337
340
  </style>
338
341
 
339
342
  <div class="stacked-container">
340
- <div class="card-stack" id="card-stack">
341
- <!-- Cards will be generated here -->
342
- </div>
343
+ <div class="card-stack" id="card-stack"></div>
343
344
 
344
345
  <div class="controls">
345
346
  <button class="nav-btn" id="prev-btn" aria-label="Previous theme">
@@ -368,137 +369,66 @@ class ThemeStackedElement extends HTMLElement {
368
369
 
369
370
  this.renderCards();
370
371
  this.renderDots();
371
- this.updateStack();
372
372
  this.attachEvents();
373
373
 
374
- // Set initial index based on current theme
375
- const currentTheme = this.getCurrentTheme();
376
- const initialIndex = this.themes.findIndex((t) => t.id === currentTheme);
377
- if (initialIndex !== -1) {
378
- this.goTo(initialIndex, false);
379
- }
374
+ const idx = THEMES.findIndex((t) => t.id === this.getCurrentTheme());
375
+ this.goTo(idx !== -1 ? idx : 0, false);
380
376
  }
381
377
 
382
378
  renderCards() {
383
- this._cardStack.innerHTML = "";
384
- this._cards = [];
385
-
386
- this.themes.forEach((theme, index) => {
387
- const card = document.createElement("div");
388
- card.className = "theme-card";
389
- card.dataset.index = index;
390
- card.dataset.theme = theme.id;
391
-
379
+ const current = this.getCurrentTheme();
380
+ this._cardStack.innerHTML = THEMES.map((theme, i) => {
392
381
  const colors = this._themeData[theme.id] || {};
393
- const colorSwatches = this.previewColors
394
- .map((color) => {
395
- const colorValue = colors[color] || "transparent";
396
- return `<div class="color-swatch" data-color="${color}" data-value="${colorValue}" style="--color: ${colorValue};"></div>`;
397
- })
398
- .join("");
399
-
400
- const isActive = this.getCurrentTheme() === theme.id;
401
-
402
- card.innerHTML = `
382
+ const themeVars = Object.entries(colors)
383
+ .map(([k, v]) => `--${k}: ${v}`)
384
+ .join(";");
385
+ const swatches = PREVIEW_COLORS.map((c) => {
386
+ const v = colors[c] || "transparent";
387
+ return `<div class="color-swatch" data-color="${c}" data-value="${v}" style="--color:${v}"></div>`;
388
+ }).join("");
389
+ const active = current === theme.id;
390
+ return `<div class="theme-card" data-index="${i}" data-theme="${theme.id}" style="${themeVars}">
403
391
  <h4 class="card-title">${theme.name}</h4>
404
- <div class="color-grid">
405
- ${colorSwatches}
406
- </div>
392
+ <div class="color-grid">${swatches}</div>
407
393
  <div class="card-footer">
408
- <button class="apply-btn ${isActive ? "applied" : ""}" data-theme="${theme.id}">
409
- ${isActive ? "Applied ✓" : "Apply"}
394
+ <button class="apply-btn${active ? " applied" : ""}" data-theme="${theme.id}">
395
+ ${active ? "Applied ✓" : "Apply"}
410
396
  </button>
411
397
  </div>
412
- `;
413
-
414
- // Apply button click
415
- const applyBtn = card.querySelector(".apply-btn");
416
- applyBtn.addEventListener("click", (e) => {
417
- e.stopPropagation();
418
- this.applyTheme(theme.id);
419
- applyBtn.textContent = "Applied";
420
- });
421
-
422
- // Card click to navigate
423
- card.addEventListener("click", () => {
424
- if (!card.classList.contains("active")) {
425
- this.goTo(index);
426
- }
427
- });
428
-
429
- this._cardStack.appendChild(card);
430
- this._cards.push(card);
431
- });
398
+ </div>`;
399
+ }).join("");
400
+ this._cards = [...this._cardStack.querySelectorAll(".theme-card")];
432
401
  }
433
402
 
434
403
  renderDots() {
435
- this._dotsContainer.innerHTML = "";
436
- this.themes.forEach((_, index) => {
437
- const dot = document.createElement("button");
438
- dot.className = "dot";
439
- dot.dataset.index = index;
440
- dot.setAttribute("aria-label", `Go to theme ${index + 1}`);
441
- dot.addEventListener("click", () => this.goTo(index));
442
- this._dotsContainer.appendChild(dot);
443
- });
404
+ this._dotsContainer.innerHTML = THEMES.map((_, i) => `<button class="dot" data-index="${i}" aria-label="Go to theme ${i + 1}"></button>`).join("");
405
+ this._dotsContainer.querySelectorAll(".dot").forEach((dot, i) => dot.addEventListener("click", () => this.goTo(i)));
444
406
  }
445
407
 
446
408
  updateStack() {
447
409
  const total = this._cards.length;
448
-
449
- this._cards.forEach((card, index) => {
450
- card.classList.remove("active", "prev", "next", "hidden");
451
-
452
- const dist = this.getDistance(index, this._currentIndex, total);
453
-
454
- if (dist === 0) {
455
- card.classList.add("active");
456
- } else if (dist === -1 || (this._currentIndex === 0 && index === total - 1)) {
457
- // Previous card (circular)
458
- card.classList.add("prev");
459
- } else if (dist === 1 || (this._currentIndex === total - 1 && index === 0)) {
460
- // Next card (circular)
461
- card.classList.add("next");
462
- } else {
463
- card.classList.add("hidden");
464
- }
465
- });
466
-
467
- // Update dots
468
- this._dotsContainer.querySelectorAll(".dot").forEach((dot, index) => {
469
- dot.classList.toggle("active", index === this._currentIndex);
410
+ const distClass = { 0: "active", "-1": "prev", 1: "next" };
411
+ this._cards.forEach((card, i) => {
412
+ const cls = distClass[this.getDistance(i, this._currentIndex, total)] ?? "hidden";
413
+ card.className = `theme-card ${cls}`;
470
414
  });
415
+ this._dotsContainer.querySelectorAll(".dot").forEach((dot, i) => dot.classList.toggle("active", i === this._currentIndex));
471
416
  }
472
417
 
473
418
  getDistance(index, current, total) {
474
- const diff = index - current;
475
- if (Math.abs(diff) <= 1) return diff;
476
- if (current === 0 && index === total - 1) return -1;
477
- if (current === total - 1 && index === 0) return 1;
478
- return 2;
419
+ const d = (index - current + total) % total;
420
+ return d === 0 ? 0 : d === 1 ? 1 : d === total - 1 ? -1 : 2;
479
421
  }
480
422
 
481
423
  goTo(index, animate = true) {
482
- const total = this.themes.length;
483
-
484
- // Handle circular wrapping for goTo as well
485
- if (index < 0) {
486
- index = total - 1;
487
- } else if (index >= total) {
488
- index = 0;
489
- }
490
-
491
- this._currentIndex = index;
424
+ this._currentIndex = ((index % THEMES.length) + THEMES.length) % THEMES.length;
492
425
  this.updateStack();
493
-
494
- // Trigger animation
495
- if (animate) {
426
+ if (animate)
496
427
  this.dispatchEvent(
497
428
  new CustomEvent("themeChange", {
498
- detail: { index, theme: this.themes[index] },
429
+ detail: { index: this._currentIndex, theme: THEMES[this._currentIndex] },
499
430
  }),
500
431
  );
501
- }
502
432
  }
503
433
 
504
434
  next() {
@@ -509,99 +439,88 @@ class ThemeStackedElement extends HTMLElement {
509
439
  }
510
440
 
511
441
  attachEvents() {
512
- // Navigation buttons
513
442
  this._prevBtn.addEventListener("click", () => this.prev());
514
443
  this._nextBtn.addEventListener("click", () => this.next());
515
444
 
516
- // Keyboard navigation
517
- document.addEventListener("keydown", (e) => {
518
- if (!this.isVisible()) return;
519
-
520
- switch (e.key) {
521
- case "ArrowLeft":
522
- e.preventDefault();
523
- this.prev();
524
- break;
525
- case "ArrowRight":
526
- case " ":
527
- e.preventDefault();
528
- this.next();
529
- break;
530
- case "Enter":
531
- if (this._cards[this._currentIndex].classList.contains("active")) {
532
- this.applyTheme(this.themes[this._currentIndex].id);
533
- }
534
- break;
445
+ this._cardStack.addEventListener("click", (e) => {
446
+ const swatch = e.target.closest(".color-swatch");
447
+ if (swatch) {
448
+ e.stopPropagation();
449
+ const hex = (swatch.dataset.value || "").replace("#", "");
450
+ if (hex && hex !== "transparent") window.open(`https://www.colorhexa.com/${hex}`, "_blank");
451
+ return;
452
+ }
453
+ const applyBtn = e.target.closest(".apply-btn");
454
+ if (applyBtn) {
455
+ e.stopPropagation();
456
+ this.applyTheme(applyBtn.dataset.theme);
457
+ return;
535
458
  }
459
+ const card = e.target.closest(".theme-card");
460
+ if (card && !card.classList.contains("active")) this.goTo(Number(card.dataset.index));
536
461
  });
537
462
 
538
- // Touch/drag support
539
- let startX = 0;
540
- let isDragging = false;
463
+ this._keyHandler = (e) => {
464
+ if (!this._isVisible) return;
465
+ if (e.key === "ArrowLeft") {
466
+ e.preventDefault();
467
+ this.prev();
468
+ } else if (e.key === "ArrowRight" || e.key === " ") {
469
+ e.preventDefault();
470
+ this.next();
471
+ } else if (e.key === "Enter") this.applyTheme(THEMES[this._currentIndex].id);
472
+ };
473
+ document.addEventListener("keydown", this._keyHandler);
541
474
 
542
- const handleStart = (x) => {
475
+ let startX = 0,
476
+ dragging = false;
477
+ const onStart = (x) => {
478
+ if (!this._isVisible) return;
543
479
  startX = x;
544
- isDragging = true;
480
+ dragging = true;
545
481
  };
546
- const handleEnd = (x) => {
547
- if (!isDragging) return;
548
- isDragging = false;
482
+ const onEnd = (x) => {
483
+ if (!dragging) return;
484
+ dragging = false;
549
485
  const diff = startX - x;
550
486
  if (Math.abs(diff) > 50) diff > 0 ? this.next() : this.prev();
551
487
  };
552
488
 
553
- this._cardStack.addEventListener("touchstart", (e) => handleStart(e.touches[0].clientX), { passive: true });
554
- this._cardStack.addEventListener("touchend", (e) => handleEnd(e.changedTouches[0].clientX));
555
-
489
+ this._cardStack.addEventListener("touchstart", (e) => onStart(e.touches[0].clientX), { passive: true });
490
+ this._cardStack.addEventListener("touchend", (e) => onEnd(e.changedTouches[0].clientX));
556
491
  this._cardStack.addEventListener("mousedown", (e) => {
557
- handleStart(e.clientX);
492
+ onStart(e.clientX);
558
493
  this._cardStack.style.cursor = "grabbing";
559
494
  });
560
- document.addEventListener("mouseup", (e) => {
561
- handleEnd(e.clientX);
495
+ this._mouseUpHandler = (e) => {
496
+ if (!this._isVisible) return;
497
+ onEnd(e.clientX);
562
498
  this._cardStack.style.cursor = "";
563
- });
564
- }
565
-
566
- isVisible() {
567
- const rect = this.getBoundingClientRect();
568
- return rect.top < window.innerHeight && rect.bottom > 0;
499
+ };
500
+ document.addEventListener("mouseup", this._mouseUpHandler, { passive: true });
569
501
  }
570
502
 
571
503
  getCurrentTheme() {
572
- if (typeof window !== "undefined") {
573
- const stored = localStorage.getItem("themePreference");
574
- if (stored) return stored;
575
- return document.documentElement.getAttribute("data-theme") || "mocha";
576
- }
577
- return "mocha";
504
+ return localStorage.getItem("themePreference") || document.documentElement.getAttribute("data-theme") || "mocha";
578
505
  }
579
506
 
580
507
  applyTheme(themeId) {
581
- if (typeof window !== "undefined" && window.applyTheme) {
582
- window.applyTheme(themeId, true);
583
-
584
- // Update all buttons
585
- this._cards.forEach((card, idx) => {
586
- const btn = card.querySelector(".apply-btn");
587
- const isThisTheme = this.themes[idx].id === themeId;
588
-
589
- btn.classList.toggle("applied", isThisTheme);
590
- btn.textContent = isThisTheme ? "Applied ✓" : "Apply Theme";
591
- });
592
-
593
- // Visual feedback on active card
594
- const activeCard = this._cards[this._currentIndex];
595
- const activeBtn = activeCard.querySelector(".apply-btn");
596
- activeBtn.style.transform = "scale(0.95)";
597
- setTimeout(() => {
598
- activeBtn.style.transform = "";
599
- }, 150);
600
- }
508
+ if (!window.applyTheme) return;
509
+ window.applyTheme(themeId, true);
510
+ this._cards.forEach((card, i) => {
511
+ const btn = card.querySelector(".apply-btn");
512
+ const match = THEMES[i].id === themeId;
513
+ btn.classList.toggle("applied", match);
514
+ btn.textContent = match ? "Applied ✓" : "Apply Theme";
515
+ });
516
+ const btn = this._cards[this._currentIndex].querySelector(".apply-btn");
517
+ btn.style.transform = "scale(0.95)";
518
+ setTimeout(() => {
519
+ btn.style.transform = "";
520
+ }, 150);
601
521
  }
602
522
  }
603
523
 
604
- // Register the custom element
605
524
  if (!customElements.get("theme-stacked")) {
606
525
  customElements.define("theme-stacked", ThemeStackedElement);
607
526
  }