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
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Attributes:
|
|
15
15
|
* - image: Image URL (required)
|
|
16
|
-
* - alt: Image alt text
|
|
16
|
+
* - alt: Image alt text (also used as figcaption)
|
|
17
17
|
* - width: Image width (default: 300px)
|
|
18
18
|
* - left: Put image on left (default: image on right)
|
|
19
19
|
* - font-family: Text font family
|
|
@@ -37,7 +37,7 @@ class TextImageSection extends HTMLElement {
|
|
|
37
37
|
|
|
38
38
|
initZoom() {
|
|
39
39
|
if (typeof mediumZoom !== "function") return;
|
|
40
|
-
const img = this.querySelector(".ti-
|
|
40
|
+
const img = this.querySelector(".ti-figure img");
|
|
41
41
|
if (img) {
|
|
42
42
|
mediumZoom(img, { background: "hsla(from var(--mantle) / 0.9)" });
|
|
43
43
|
}
|
|
@@ -48,71 +48,66 @@ class TextImageSection extends HTMLElement {
|
|
|
48
48
|
|
|
49
49
|
const style = `
|
|
50
50
|
text-image-section {
|
|
51
|
-
|
|
51
|
+
/* flow-root establishes a BFC so floats are contained
|
|
52
|
+
and parent block formatting (e.g. blockquote borders)
|
|
53
|
+
don't visually bleed into the image area */
|
|
54
|
+
display: flow-root;
|
|
52
55
|
margin: 1em 0;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.ti-container {
|
|
56
|
-
overflow: hidden;
|
|
57
|
-
|
|
58
|
-
&::after {
|
|
59
|
-
content: "";
|
|
60
|
-
display: table;
|
|
61
|
-
clear: both;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.ti-text {
|
|
66
|
-
line-height: 1.8;
|
|
67
56
|
font-family: var(--ti-font-family, inherit);
|
|
68
|
-
font-size: var(--ti-font-size
|
|
57
|
+
font-size: var(--ti-font-size);
|
|
69
58
|
color: var(--ti-color, inherit);
|
|
59
|
+
line-height: 1.8;
|
|
70
60
|
}
|
|
71
61
|
|
|
72
|
-
.ti-
|
|
73
|
-
|
|
74
|
-
z-index: 1;
|
|
62
|
+
text-image-section > .ti-figure {
|
|
63
|
+
float: right;
|
|
75
64
|
width: var(--ti-image-width, 300px);
|
|
76
|
-
margin
|
|
77
|
-
|
|
78
|
-
.ti-container > & {
|
|
79
|
-
float: right;
|
|
80
|
-
margin-left: 24px;
|
|
81
|
-
}
|
|
65
|
+
margin: 0 0 12px 24px;
|
|
66
|
+
}
|
|
82
67
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
68
|
+
text-image-section[left] > .ti-figure {
|
|
69
|
+
float: left;
|
|
70
|
+
margin: 0 24px 12px 0;
|
|
71
|
+
}
|
|
88
72
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
73
|
+
text-image-section .ti-figure img {
|
|
74
|
+
display: block;
|
|
75
|
+
width: 100%;
|
|
76
|
+
height: auto;
|
|
77
|
+
border-radius: 8px;
|
|
78
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
79
|
+
}
|
|
95
80
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
81
|
+
text-image-section .ti-figure figcaption {
|
|
82
|
+
font-family: var(--font-serif);
|
|
83
|
+
font-size: 0.875em;
|
|
84
|
+
color: var(--subtext0);
|
|
85
|
+
text-align: center;
|
|
86
|
+
margin-top: 8px;
|
|
87
|
+
font-style: italic;
|
|
88
|
+
}
|
|
99
89
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
text-align: center;
|
|
105
|
-
margin-top: 8px;
|
|
106
|
-
font-style: italic;
|
|
107
|
-
}
|
|
90
|
+
/* Tame nested blockquotes inside the text area so they
|
|
91
|
+
don't fight the image's float/stacked layout. */
|
|
92
|
+
text-image-section > blockquote {
|
|
93
|
+
margin: 0;
|
|
108
94
|
}
|
|
109
95
|
|
|
110
96
|
@media (max-width: 640px) {
|
|
111
|
-
.ti-
|
|
97
|
+
text-image-section > .ti-figure,
|
|
98
|
+
text-image-section[left] > .ti-figure {
|
|
112
99
|
float: none;
|
|
113
|
-
width:
|
|
114
|
-
margin
|
|
115
|
-
|
|
100
|
+
width: auto;
|
|
101
|
+
/* Bottom margin separates the figure from any following
|
|
102
|
+
blockquote so its left border doesn't visually align
|
|
103
|
+
with the image edge */
|
|
104
|
+
margin: 0 0 20px 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* When a blockquote follows the figure on mobile, give it
|
|
108
|
+
a touch more breathing room from the image above */
|
|
109
|
+
text-image-section > .ti-figure + blockquote {
|
|
110
|
+
margin-top: 4px;
|
|
116
111
|
}
|
|
117
112
|
}
|
|
118
113
|
`;
|
|
@@ -130,48 +125,41 @@ class TextImageSection extends HTMLElement {
|
|
|
130
125
|
const image = this.getAttribute("image");
|
|
131
126
|
const alt = this.getAttribute("alt") || "";
|
|
132
127
|
const imageWidth = this.getAttribute("width") || "300px";
|
|
133
|
-
const imageLeft = this.hasAttribute("left");
|
|
134
128
|
const fontFamily = this.getAttribute("font-family");
|
|
135
129
|
const fontSize = this.getAttribute("font-size");
|
|
136
130
|
const color = this.getAttribute("color");
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
131
|
+
|
|
132
|
+
// Apply CSS variables directly to the host element so we no
|
|
133
|
+
// longer need a wrapping <div> just to hold inline styles.
|
|
134
|
+
this.style.setProperty("--ti-image-width", imageWidth);
|
|
135
|
+
if (fontFamily) this.style.setProperty("--ti-font-family", fontFamily);
|
|
136
|
+
if (fontSize) this.style.setProperty("--ti-font-size", fontSize);
|
|
137
|
+
if (color) this.style.setProperty("--ti-color", color);
|
|
138
|
+
|
|
139
|
+
// Without an image we have nothing extra to inject — leave the
|
|
140
|
+
// user's original markdown/HTML content in place.
|
|
141
|
+
if (!image) return;
|
|
142
|
+
|
|
143
|
+
const figure = document.createElement("figure");
|
|
144
|
+
figure.className = "ti-figure";
|
|
145
|
+
|
|
146
|
+
const img = document.createElement("img");
|
|
147
|
+
img.src = image;
|
|
148
|
+
img.alt = alt;
|
|
149
|
+
img.loading = "lazy";
|
|
150
|
+
figure.appendChild(img);
|
|
151
|
+
|
|
152
|
+
if (alt) {
|
|
153
|
+
const figcaption = document.createElement("figcaption");
|
|
154
|
+
figcaption.textContent = alt;
|
|
155
|
+
figure.appendChild(figcaption);
|
|
151
156
|
}
|
|
152
157
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
<figcaption>${alt}</figcaption>
|
|
159
|
-
</figure>`
|
|
160
|
-
: `<img src="${image}" alt="${alt}" loading="lazy">`;
|
|
161
|
-
|
|
162
|
-
const styleAttrs = [];
|
|
163
|
-
styleAttrs.push(`--ti-image-width: ${imageWidth};`);
|
|
164
|
-
if (fontFamily) styleAttrs.push(`--ti-font-family: ${fontFamily};`);
|
|
165
|
-
if (fontSize) styleAttrs.push(`--ti-font-size: ${fontSize};`);
|
|
166
|
-
if (color) styleAttrs.push(`--ti-color: ${color};`);
|
|
167
|
-
this.innerHTML = `
|
|
168
|
-
<div class="${containerClass}" style="${styleAttrs.join(" ")}">
|
|
169
|
-
<div class="ti-image">
|
|
170
|
-
${figureHtml}
|
|
171
|
-
</div>
|
|
172
|
-
<div class="ti-text">${content}</div>
|
|
173
|
-
</div>
|
|
174
|
-
`;
|
|
158
|
+
// Prepend the <figure> so the existing user content (text,
|
|
159
|
+
// blockquotes, paragraphs, etc.) stays as direct children of
|
|
160
|
+
// the host element. This preserves semantic structure and
|
|
161
|
+
// avoids re-parsing innerHTML.
|
|
162
|
+
this.insertBefore(figure, this.firstChild);
|
|
175
163
|
}
|
|
176
164
|
}
|
|
177
165
|
|
|
@@ -29,14 +29,23 @@ const PREVIEW_COLORS = [
|
|
|
29
29
|
"surface0",
|
|
30
30
|
];
|
|
31
31
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
const THEME_DATA_CACHE_PREFIX = "themeDataCache";
|
|
33
|
+
|
|
34
|
+
function getThemeOptions() {
|
|
35
|
+
const config = window.__GNIX_THEME_CONFIG__;
|
|
36
|
+
|
|
37
|
+
if (!Array.isArray(config?.themes)) return [];
|
|
38
|
+
|
|
39
|
+
return config.themes.filter((theme) => theme.value !== config.defaultTheme).map((theme) => ({ id: theme.value, name: theme.name || theme.label }));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getThemeDataCacheKey(themes) {
|
|
43
|
+
return `${THEME_DATA_CACHE_PREFIX}:${themes.map((theme) => theme.id).join(",")}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function hasThemeDataForThemes(themeData, themes) {
|
|
47
|
+
return Boolean(themeData) && themes.every((theme) => themeData[theme.id]);
|
|
48
|
+
}
|
|
40
49
|
|
|
41
50
|
class ThemeStackedElement extends HTMLElement {
|
|
42
51
|
constructor() {
|
|
@@ -45,10 +54,14 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
45
54
|
this._cards = [];
|
|
46
55
|
this._isVisible = false;
|
|
47
56
|
this._themeData = {};
|
|
57
|
+
this._themes = [];
|
|
48
58
|
this.attachShadow({ mode: "open" });
|
|
49
59
|
}
|
|
50
60
|
|
|
51
61
|
async connectedCallback() {
|
|
62
|
+
this._themes = getThemeOptions();
|
|
63
|
+
if (this._themes.length === 0) return;
|
|
64
|
+
|
|
52
65
|
this._observer = new IntersectionObserver((e) => {
|
|
53
66
|
this._isVisible = e[0].isIntersecting;
|
|
54
67
|
});
|
|
@@ -65,15 +78,21 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
65
78
|
}
|
|
66
79
|
|
|
67
80
|
async loadThemeData() {
|
|
68
|
-
if (window.__cachedThemeData) {
|
|
81
|
+
if (hasThemeDataForThemes(window.__cachedThemeData, this._themes)) {
|
|
69
82
|
this._themeData = window.__cachedThemeData;
|
|
70
83
|
return;
|
|
71
84
|
}
|
|
85
|
+
|
|
86
|
+
const cacheKey = getThemeDataCacheKey(this._themes);
|
|
87
|
+
|
|
72
88
|
try {
|
|
73
|
-
const cached = localStorage.getItem(
|
|
89
|
+
const cached = localStorage.getItem(cacheKey);
|
|
74
90
|
if (cached) {
|
|
75
|
-
|
|
76
|
-
|
|
91
|
+
const cachedThemeData = JSON.parse(cached);
|
|
92
|
+
if (hasThemeDataForThemes(cachedThemeData, this._themes)) {
|
|
93
|
+
this._themeData = window.__cachedThemeData = cachedThemeData;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
77
96
|
}
|
|
78
97
|
} catch (_e) {}
|
|
79
98
|
|
|
@@ -81,7 +100,7 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
81
100
|
temp.style.cssText = "position:absolute;left:-9999px;width:0;height:0;overflow:hidden;";
|
|
82
101
|
document.body.appendChild(temp);
|
|
83
102
|
|
|
84
|
-
for (const theme of
|
|
103
|
+
for (const theme of this._themes) {
|
|
85
104
|
temp.setAttribute("data-theme", theme.id);
|
|
86
105
|
const computed = window.getComputedStyle(temp);
|
|
87
106
|
this._themeData[theme.id] = Object.fromEntries(PREVIEW_COLORS.map((c) => [c, computed.getPropertyValue(`--${c}`).trim()]).filter(([, v]) => v));
|
|
@@ -90,7 +109,7 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
90
109
|
temp.remove();
|
|
91
110
|
window.__cachedThemeData = this._themeData;
|
|
92
111
|
try {
|
|
93
|
-
localStorage.setItem(
|
|
112
|
+
localStorage.setItem(cacheKey, JSON.stringify(this._themeData));
|
|
94
113
|
} catch (_e) {}
|
|
95
114
|
}
|
|
96
115
|
|
|
@@ -371,23 +390,24 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
371
390
|
this.renderDots();
|
|
372
391
|
this.attachEvents();
|
|
373
392
|
|
|
374
|
-
const idx =
|
|
393
|
+
const idx = this._themes.findIndex((t) => t.id === this.getCurrentTheme());
|
|
375
394
|
this.goTo(idx !== -1 ? idx : 0, false);
|
|
376
395
|
}
|
|
377
396
|
|
|
378
397
|
renderCards() {
|
|
379
398
|
const current = this.getCurrentTheme();
|
|
380
|
-
this._cardStack.innerHTML =
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
399
|
+
this._cardStack.innerHTML = this._themes
|
|
400
|
+
.map((theme, i) => {
|
|
401
|
+
const colors = this._themeData[theme.id] || {};
|
|
402
|
+
const themeVars = Object.entries(colors)
|
|
403
|
+
.map(([k, v]) => `--${k}: ${v}`)
|
|
404
|
+
.join(";");
|
|
405
|
+
const swatches = PREVIEW_COLORS.map((c) => {
|
|
406
|
+
const v = colors[c] || "transparent";
|
|
407
|
+
return `<div class="color-swatch" data-color="${c}" data-value="${v}" style="--color:${v}"></div>`;
|
|
408
|
+
}).join("");
|
|
409
|
+
const active = current === theme.id;
|
|
410
|
+
return `<div class="theme-card" data-index="${i}" data-theme="${theme.id}" style="${themeVars}">
|
|
391
411
|
<h4 class="card-title">${theme.name}</h4>
|
|
392
412
|
<div class="color-grid">${swatches}</div>
|
|
393
413
|
<div class="card-footer">
|
|
@@ -396,12 +416,13 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
396
416
|
</button>
|
|
397
417
|
</div>
|
|
398
418
|
</div>`;
|
|
399
|
-
|
|
419
|
+
})
|
|
420
|
+
.join("");
|
|
400
421
|
this._cards = [...this._cardStack.querySelectorAll(".theme-card")];
|
|
401
422
|
}
|
|
402
423
|
|
|
403
424
|
renderDots() {
|
|
404
|
-
this._dotsContainer.innerHTML =
|
|
425
|
+
this._dotsContainer.innerHTML = this._themes.map((_, i) => `<button class="dot" data-index="${i}" aria-label="Go to theme ${i + 1}"></button>`).join("");
|
|
405
426
|
this._dotsContainer.querySelectorAll(".dot").forEach((dot, i) => dot.addEventListener("click", () => this.goTo(i)));
|
|
406
427
|
}
|
|
407
428
|
|
|
@@ -421,12 +442,14 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
421
442
|
}
|
|
422
443
|
|
|
423
444
|
goTo(index, animate = true) {
|
|
424
|
-
|
|
445
|
+
if (this._themes.length === 0) return;
|
|
446
|
+
|
|
447
|
+
this._currentIndex = ((index % this._themes.length) + this._themes.length) % this._themes.length;
|
|
425
448
|
this.updateStack();
|
|
426
449
|
if (animate)
|
|
427
450
|
this.dispatchEvent(
|
|
428
451
|
new CustomEvent("themeChange", {
|
|
429
|
-
detail: { index: this._currentIndex, theme:
|
|
452
|
+
detail: { index: this._currentIndex, theme: this._themes[this._currentIndex] },
|
|
430
453
|
}),
|
|
431
454
|
);
|
|
432
455
|
}
|
|
@@ -468,7 +491,7 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
468
491
|
} else if (e.key === "ArrowRight" || e.key === " ") {
|
|
469
492
|
e.preventDefault();
|
|
470
493
|
this.next();
|
|
471
|
-
} else if (e.key === "Enter") this.applyTheme(
|
|
494
|
+
} else if (e.key === "Enter") this.applyTheme(this._themes[this._currentIndex].id);
|
|
472
495
|
};
|
|
473
496
|
document.addEventListener("keydown", this._keyHandler);
|
|
474
497
|
|
|
@@ -501,7 +524,16 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
501
524
|
}
|
|
502
525
|
|
|
503
526
|
getCurrentTheme() {
|
|
504
|
-
|
|
527
|
+
const config = window.__GNIX_THEME_CONFIG__;
|
|
528
|
+
let storedTheme = null;
|
|
529
|
+
|
|
530
|
+
try {
|
|
531
|
+
storedTheme = config?.storageKey ? localStorage.getItem(config.storageKey) : null;
|
|
532
|
+
} catch (_e) {}
|
|
533
|
+
|
|
534
|
+
if (storedTheme && storedTheme !== config?.defaultTheme) return storedTheme;
|
|
535
|
+
|
|
536
|
+
return document.documentElement.getAttribute("data-theme") || config?.systemTheme?.dark || this._themes[0]?.id;
|
|
505
537
|
}
|
|
506
538
|
|
|
507
539
|
applyTheme(themeId) {
|
|
@@ -509,7 +541,7 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
509
541
|
window.applyTheme(themeId, true);
|
|
510
542
|
this._cards.forEach((card, i) => {
|
|
511
543
|
const btn = card.querySelector(".apply-btn");
|
|
512
|
-
const match =
|
|
544
|
+
const match = this._themes[i].id === themeId;
|
|
513
545
|
btn.classList.toggle("applied", match);
|
|
514
546
|
btn.textContent = match ? "Applied ✓" : "Apply Theme";
|
|
515
547
|
});
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - components
|
|
11
11
|
* - accordion.js
|
|
12
12
|
* - tree.js
|
|
13
|
-
* - styles
|
|
13
|
+
* - styles/
|
|
14
14
|
* - main.css
|
|
15
15
|
* - package.json
|
|
16
16
|
* - README.md
|
|
@@ -285,11 +285,7 @@ class XTree extends HTMLElement {
|
|
|
285
285
|
const childUL = li.querySelector(":scope > ul");
|
|
286
286
|
const children = childUL ? this.parseUL(childUL) : [];
|
|
287
287
|
|
|
288
|
-
result.push(
|
|
289
|
-
label: text,
|
|
290
|
-
children,
|
|
291
|
-
hasChildren: children.length > 0,
|
|
292
|
-
});
|
|
288
|
+
result.push(this.createNode(text, children));
|
|
293
289
|
});
|
|
294
290
|
|
|
295
291
|
return result;
|
|
@@ -313,7 +309,7 @@ class XTree extends HTMLElement {
|
|
|
313
309
|
`;
|
|
314
310
|
|
|
315
311
|
this.shadowRoot.querySelectorAll(".tree-children").forEach((el) => {
|
|
316
|
-
el.style.maxHeight = el.scrollHeight
|
|
312
|
+
el.style.maxHeight = `${el.scrollHeight}px`;
|
|
317
313
|
});
|
|
318
314
|
|
|
319
315
|
this.shadowRoot.querySelectorAll(".tree-toggle:not(.collapsed)").forEach((btn) => {
|
|
@@ -333,23 +329,40 @@ class XTree extends HTMLElement {
|
|
|
333
329
|
return ICON_FILE;
|
|
334
330
|
}
|
|
335
331
|
|
|
332
|
+
createNode(label, children = []) {
|
|
333
|
+
const normalizedLabel = label.trim();
|
|
334
|
+
const isExplicitFolder = /\/+$/.test(normalizedLabel);
|
|
335
|
+
const displayLabel = isExplicitFolder ? normalizedLabel.replace(/\/+$/, "") : normalizedLabel;
|
|
336
|
+
const hasChildren = children.length > 0;
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
label: normalizedLabel,
|
|
340
|
+
displayLabel: displayLabel || normalizedLabel,
|
|
341
|
+
children,
|
|
342
|
+
hasChildren,
|
|
343
|
+
isFolder: isExplicitFolder || hasChildren,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
336
347
|
renderTreeItems(items) {
|
|
337
348
|
return items
|
|
338
349
|
.map((item) => {
|
|
339
|
-
const isFolder = item.hasChildren;
|
|
340
|
-
const
|
|
341
|
-
const
|
|
350
|
+
const isFolder = item.isFolder || item.hasChildren;
|
|
351
|
+
const canToggle = item.hasChildren;
|
|
352
|
+
const label = item.displayLabel || item.label;
|
|
353
|
+
const icon = this.getIcon(label, isFolder);
|
|
354
|
+
const extClass = isFolder ? "folder" : this.getExtClass(label);
|
|
342
355
|
|
|
343
|
-
const toggleHTML =
|
|
356
|
+
const toggleHTML = canToggle ? `<button class="tree-toggle" aria-expanded="true">${ICON_CHEVRON}</button>` : `<span class="tree-toggle-placeholder"></span>`;
|
|
344
357
|
|
|
345
|
-
const childrenHTML =
|
|
358
|
+
const childrenHTML = canToggle ? `<ul class="tree-children">${this.renderTreeItems(item.children)}</ul>` : "";
|
|
346
359
|
|
|
347
360
|
return `
|
|
348
361
|
<li class="tree-item">
|
|
349
362
|
<div class="tree-row">
|
|
350
363
|
${toggleHTML}
|
|
351
364
|
<span class="tree-icon ${extClass}">${icon}</span>
|
|
352
|
-
<span class="tree-label">${this.escapeHTML(
|
|
365
|
+
<span class="tree-label">${this.escapeHTML(label)}</span>
|
|
353
366
|
</div>
|
|
354
367
|
${childrenHTML}
|
|
355
368
|
</li>
|
|
@@ -374,11 +387,11 @@ class XTree extends HTMLElement {
|
|
|
374
387
|
btn.classList.remove("collapsed");
|
|
375
388
|
btn.setAttribute("aria-expanded", "true");
|
|
376
389
|
children.classList.remove("collapsed");
|
|
377
|
-
children.style.maxHeight = children.scrollHeight
|
|
390
|
+
children.style.maxHeight = `${children.scrollHeight}px`;
|
|
378
391
|
} else {
|
|
379
392
|
btn.classList.add("collapsed");
|
|
380
393
|
btn.setAttribute("aria-expanded", "false");
|
|
381
|
-
children.style.maxHeight = children.scrollHeight
|
|
394
|
+
children.style.maxHeight = `${children.scrollHeight}px`;
|
|
382
395
|
children.offsetHeight;
|
|
383
396
|
children.classList.add("collapsed");
|
|
384
397
|
}
|
|
@@ -413,7 +426,7 @@ class XTree extends HTMLElement {
|
|
|
413
426
|
const indent = match[1].length;
|
|
414
427
|
const label = match[2].trim();
|
|
415
428
|
|
|
416
|
-
const node =
|
|
429
|
+
const node = this.createNode(label);
|
|
417
430
|
|
|
418
431
|
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
419
432
|
stack.pop();
|
|
@@ -422,6 +435,7 @@ class XTree extends HTMLElement {
|
|
|
422
435
|
const parent = stack[stack.length - 1].node;
|
|
423
436
|
parent.children.push(node);
|
|
424
437
|
parent.hasChildren = true;
|
|
438
|
+
parent.isFolder = true;
|
|
425
439
|
|
|
426
440
|
stack.push({ indent, node });
|
|
427
441
|
}
|
package/source/js/decrypt.js
CHANGED
|
@@ -107,6 +107,11 @@ function init() {
|
|
|
107
107
|
|
|
108
108
|
init();
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
function bindSwupDecryptHook(swupInstance) {
|
|
111
|
+
if (!swupInstance || swupInstance.gnixDecryptPageHookBound) return;
|
|
112
|
+
swupInstance.gnixDecryptPageHookBound = true;
|
|
113
|
+
swupInstance.hooks.on("page:view", init);
|
|
112
114
|
}
|
|
115
|
+
|
|
116
|
+
bindSwupDecryptHook(window.swup);
|
|
117
|
+
document.addEventListener("gnix:swup-ready", (event) => bindSwupDecryptHook(event.detail?.swup), { once: true });
|