hexo-theme-gnix 6.2.0 → 8.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 (59) hide show
  1. package/README.md +6 -2
  2. package/include/hexo/encrypt.js +42 -0
  3. package/include/hexo/feed.js +329 -0
  4. package/include/util/common.js +7 -9
  5. package/languages/en.yml +6 -3
  6. package/languages/zh-CN.yml +6 -3
  7. package/layout/archive.jsx +86 -65
  8. package/layout/comment/twikoo.jsx +2 -11
  9. package/layout/comment/waline.jsx +2 -2
  10. package/layout/common/article.jsx +5 -8
  11. package/layout/common/article_cover.jsx +11 -1
  12. package/layout/common/article_media.jsx +2 -4
  13. package/layout/common/footer.jsx +11 -31
  14. package/layout/common/head.jsx +6 -14
  15. package/layout/common/navbar.jsx +4 -4
  16. package/layout/common/scripts.jsx +6 -6
  17. package/layout/common/theme_selector.jsx +5 -6
  18. package/layout/common/toc.jsx +8 -14
  19. package/layout/index.jsx +2 -4
  20. package/layout/misc/article_licensing.jsx +4 -2
  21. package/layout/misc/open_graph.jsx +4 -4
  22. package/layout/misc/paginator.jsx +10 -4
  23. package/layout/misc/structured_data.jsx +3 -4
  24. package/layout/plugin/busuanzi.jsx +1 -1
  25. package/layout/plugin/cookie_consent.jsx +40 -31
  26. package/layout/plugin/swup.jsx +2 -22
  27. package/layout/search/insight.jsx +16 -3
  28. package/package.json +12 -8
  29. package/scripts/hot-reload.js +92 -0
  30. package/scripts/index.js +2 -0
  31. package/source/css/archive.css +251 -0
  32. package/source/css/default.css +250 -284
  33. package/source/css/encrypt.css +55 -0
  34. package/source/css/responsive/desktop.css +0 -119
  35. package/source/css/responsive/mobile.css +7 -23
  36. package/source/css/responsive/touch.css +9 -103
  37. package/source/css/shiki/shiki.css +7 -22
  38. package/source/css/twikoo.css +290 -830
  39. package/source/img/og_image.webp +0 -0
  40. package/source/js/archive-breadcrumb.js +132 -0
  41. package/source/js/busuanzi.js +1 -12
  42. package/source/js/components/accordion.js +192 -0
  43. package/source/js/components/chat.js +239 -0
  44. package/source/js/components/device-carousel.js +260 -0
  45. package/source/js/components/image-carousel.js +410 -0
  46. package/source/js/components/text-image-section.js +180 -0
  47. package/source/js/components/theme-stacked.js +526 -0
  48. package/source/js/components/tree.js +437 -0
  49. package/source/js/decrypt.js +112 -0
  50. package/source/js/insight.js +75 -65
  51. package/source/js/main.js +192 -99
  52. package/source/js/mdit/mermaid.js +12 -4
  53. package/source/js/swup.bundle.js +1 -0
  54. package/source/js/theme-selector.js +94 -113
  55. package/source/img/og_image.png +0 -0
  56. package/source/js/host/swup/Swup.umd.min.js +0 -1
  57. package/source/js/host/swup/head-plugin.umd.min.js +0 -1
  58. package/source/js/host/swup/scripts-plugin.umd.min.js +0 -2
  59. package/source/js/mdit/shiki.js +0 -158
@@ -0,0 +1,437 @@
1
+ /**
2
+ * File Tree Custom Element
3
+ *
4
+ * Renders markdown-style indented unordered lists as a beautiful folder/file tree.
5
+ *
6
+ * Usage:
7
+ * <x-tree>
8
+ *
9
+ * - src
10
+ * - components
11
+ * - accordion.js
12
+ * - tree.js
13
+ * - styles
14
+ * - main.css
15
+ * - package.json
16
+ * - README.md
17
+ *
18
+ * </x-tree>
19
+ *
20
+ * Note: The markdown content inside is compiled to HTML <ul>/<li> by the markdown renderer.
21
+ * This component transforms that structure into a file tree visualization.
22
+ */
23
+
24
+ const ICON_FOLDER = "\uf07b";
25
+ const ICON_FILE = "\uf15b";
26
+ const ICON_CHEVRON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="14" height="14"><polyline points="6 9 12 15 18 9"/></svg>`;
27
+
28
+ const FILE_ICONS = {
29
+ js: "\ue74e",
30
+ jsx: "\ue7ba",
31
+ ts: "\ue628",
32
+ tsx: "\ue7ba",
33
+ mjs: "\ue74e",
34
+ cjs: "\ue74e",
35
+ py: "\ue73c",
36
+ pyc: "\ue73c",
37
+ css: "\ue749",
38
+ scss: "\ue749",
39
+ less: "\ue758",
40
+ html: "\uf13b",
41
+ htm: "\uf13b",
42
+ md: "\uf48a",
43
+ markdown: "\uf48a",
44
+ yaml: "\uf481",
45
+ yml: "\uf481",
46
+ json: "\ue60b",
47
+ toml: "\ue60b",
48
+ sh: "\uf489",
49
+ bash: "\uf489",
50
+ zsh: "\uf489",
51
+ fish: "\uf489",
52
+ rs: "\ue7a8",
53
+ go: "\ue626",
54
+ vue: "\ufde1",
55
+ svelte: "\ue697",
56
+ png: "\uf1c5",
57
+ jpg: "\uf1c5",
58
+ jpeg: "\uf1c5",
59
+ gif: "\uf1c5",
60
+ svg: "\uf1c5",
61
+ webp: "\uf1c5",
62
+ ico: "\uf1c5",
63
+ avif: "\uf1c5",
64
+ mp4: "\uf03d",
65
+ mp3: "\uf03d",
66
+ wav: "\uf03d",
67
+ pdf: "\uf1c1",
68
+ zip: "\uf410",
69
+ tar: "\uf410",
70
+ gz: "\uf410",
71
+ lock: "\uf023",
72
+ env: "\uf469",
73
+ gitignore: "\uf1d3",
74
+ };
75
+
76
+ const SPECIAL_FILES = {
77
+ "package.json": "\ue71e",
78
+ "package-lock.json": "\ue71e",
79
+ "tsconfig.json": "\ue628",
80
+ "jsconfig.json": "\ue74e",
81
+ "vite.config.js": "\ue74e",
82
+ "vite.config.ts": "\ue628",
83
+ "webpack.config.js": "\ue74e",
84
+ "next.config.js": "\ue74e",
85
+ "next.config.ts": "\ue628",
86
+ "nuxt.config.js": "\ue69f",
87
+ "tailwind.config.js": "\ue69f",
88
+ "tailwind.config.ts": "\ue69f",
89
+ "postcss.config.js": "\ue749",
90
+ "eslint.config.js": "\uf469",
91
+ ".eslintrc": "\uf469",
92
+ ".prettierrc": "\uf469",
93
+ "readme.md": "\uf02d",
94
+ "changelog.md": "\uf02d",
95
+ license: "\uf4d2",
96
+ dockerfile: "\uf308",
97
+ "docker-compose.yml": "\uf308",
98
+ makefile: "\uf469",
99
+ "cargo.toml": "\ue7a8",
100
+ "go.mod": "\ue626",
101
+ "go.sum": "\ue626",
102
+ "requirements.txt": "\ue73c",
103
+ pipfile: "\ue73c",
104
+ "pipfile.lock": "\ue73c",
105
+ };
106
+
107
+ const STYLES = `
108
+ :host {
109
+ display: block;
110
+ margin: 1.5em 0;
111
+ font-family: var(--font-mono, 'SF Mono', 'Fira Code', monospace);
112
+ font-size: 14px;
113
+ line-height: 1.6;
114
+ }
115
+
116
+ .file-tree {
117
+ background: var(--mantle, #1e1e2e);
118
+ border: 1px solid var(--surface0, #313244);
119
+ border-radius: 10px;
120
+ padding: 16px 20px;
121
+ overflow-x: auto;
122
+ position: relative;
123
+ }
124
+
125
+ .file-tree::before {
126
+ content: attr(data-root);
127
+ display: block;
128
+ font-size: 12px;
129
+ font-weight: 600;
130
+ color: var(--subtext0, #7f849c);
131
+ text-transform: uppercase;
132
+ letter-spacing: 0.05em;
133
+ margin-bottom: 12px;
134
+ padding-bottom: 8px;
135
+ border-bottom: 1px solid var(--surface0, #313244);
136
+ }
137
+
138
+ .tree-list {
139
+ list-style: none;
140
+ margin: 0;
141
+ padding: 0;
142
+ }
143
+
144
+ .tree-item {
145
+ position: relative;
146
+ }
147
+
148
+ .tree-row {
149
+ display: flex;
150
+ align-items: center;
151
+ gap: .75rem;
152
+ padding: 3px 0;
153
+ cursor: default;
154
+ border-radius: 4px;
155
+ transition: background 0.15s ease;
156
+ white-space: nowrap;
157
+ }
158
+
159
+ .tree-row:hover {
160
+ background: var(--surface0, #313244);
161
+ }
162
+
163
+ .tree-toggle {
164
+ display: inline-flex;
165
+ align-items: center;
166
+ justify-content: center;
167
+ width: 18px;
168
+ height: 18px;
169
+ border: none;
170
+ background: transparent;
171
+ color: var(--subtext0, #7f849c);
172
+ cursor: pointer;
173
+ padding: 0;
174
+ border-radius: 3px;
175
+ transition: transform 0.2s ease, color 0.15s ease;
176
+ flex-shrink: 0;
177
+ }
178
+
179
+ .tree-toggle:hover {
180
+ color: var(--text, #cdd6f4);
181
+ background: var(--surface1, #45475a);
182
+ }
183
+
184
+ .tree-toggle.collapsed {
185
+ transform: rotate(-90deg);
186
+ }
187
+
188
+ .tree-toggle-placeholder {
189
+ width: 18px;
190
+ flex-shrink: 0;
191
+ }
192
+
193
+ .tree-icon {
194
+ flex-shrink: 0;
195
+ color: var(--blue, #89b4fa);
196
+ display: inline-flex;
197
+ align-items: center;
198
+ font-size: 15px;
199
+ line-height: 1;
200
+ }
201
+
202
+ .tree-icon.folder {
203
+ color: var(--yellow, #f9e2af);
204
+ }
205
+
206
+ .tree-icon.js, .tree-icon.ts, .tree-icon.mjs {
207
+ color: var(--yellow, #f9e2af);
208
+ }
209
+
210
+ .tree-icon.css, .tree-icon.scss, .tree-icon.less {
211
+ color: var(--mauve, #cba6f7);
212
+ }
213
+
214
+ .tree-icon.py {
215
+ color: var(--green, #a6e3a1);
216
+ }
217
+
218
+ .tree-icon.md {
219
+ color: var(--peach, #fab387);
220
+ }
221
+
222
+ .tree-icon.go {
223
+ color: var(--sky, #89dceb);
224
+ }
225
+
226
+ .tree-icon.rs {
227
+ color: var(--red, #f38ba8);
228
+ }
229
+
230
+ .tree-icon.vue {
231
+ color: var(--green, #a6e3a1);
232
+ }
233
+
234
+ .tree-icon.svelte {
235
+ color: var(--red, #f38ba8);
236
+ }
237
+
238
+ .tree-label {
239
+ color: var(--text, #cdd6f4);
240
+ font-size: 13px;
241
+ }
242
+
243
+ .tree-children {
244
+ list-style: none;
245
+ margin: 0;
246
+ padding: 0 0 0 20px;
247
+ overflow: hidden;
248
+ transition: max-height 0.3s ease;
249
+ }
250
+
251
+ .tree-children.collapsed {
252
+ max-height: 0 !important;
253
+ display: none;
254
+ }
255
+
256
+ `;
257
+
258
+ class XTree extends HTMLElement {
259
+ constructor() {
260
+ super();
261
+ this.attachShadow({ mode: "open" });
262
+ }
263
+
264
+ connectedCallback() {
265
+ this.render();
266
+ }
267
+
268
+ render() {
269
+ const ulElements = this.querySelectorAll(":scope > ul");
270
+ if (ulElements.length === 0) {
271
+ this.renderFromText();
272
+ return;
273
+ }
274
+
275
+ const treeData = this.parseUL(ulElements[0]);
276
+ this.buildTree(treeData);
277
+ }
278
+
279
+ parseUL(ul) {
280
+ const items = ul.querySelectorAll(":scope > li");
281
+ const result = [];
282
+
283
+ items.forEach((li) => {
284
+ const text = this.extractText(li);
285
+ const childUL = li.querySelector(":scope > ul");
286
+ const children = childUL ? this.parseUL(childUL) : [];
287
+
288
+ result.push({
289
+ label: text,
290
+ children,
291
+ hasChildren: children.length > 0,
292
+ });
293
+ });
294
+
295
+ return result;
296
+ }
297
+
298
+ extractText(li) {
299
+ const clone = li.cloneNode(true);
300
+ clone.querySelectorAll("ul").forEach((ul) => ul.remove());
301
+ return clone.textContent.trim();
302
+ }
303
+
304
+ buildTree(treeData) {
305
+ const rootName = this.getAttribute("root") || "";
306
+ const treeHTML = this.renderTreeItems(treeData);
307
+
308
+ this.shadowRoot.innerHTML = `
309
+ <style>${STYLES}</style>
310
+ <div class="file-tree"${rootName ? ` data-root="${rootName}"` : ""}>
311
+ <ul class="tree-list">${treeHTML}</ul>
312
+ </div>
313
+ `;
314
+
315
+ this.shadowRoot.querySelectorAll(".tree-children").forEach((el) => {
316
+ el.style.maxHeight = el.scrollHeight + "px";
317
+ });
318
+
319
+ this.shadowRoot.querySelectorAll(".tree-toggle:not(.collapsed)").forEach((btn) => {
320
+ btn.addEventListener("click", () => this.toggleNode(btn));
321
+ });
322
+ }
323
+
324
+ getIcon(label, isFolder) {
325
+ if (isFolder) return ICON_FOLDER;
326
+
327
+ const lower = label.toLowerCase();
328
+ if (SPECIAL_FILES[lower]) return SPECIAL_FILES[lower];
329
+
330
+ const ext = label.includes(".") ? label.split(".").pop().toLowerCase() : "";
331
+ if (ext && FILE_ICONS[ext]) return FILE_ICONS[ext];
332
+
333
+ return ICON_FILE;
334
+ }
335
+
336
+ renderTreeItems(items) {
337
+ return items
338
+ .map((item) => {
339
+ const isFolder = item.hasChildren;
340
+ const icon = this.getIcon(item.label, isFolder);
341
+ const extClass = isFolder ? "folder" : this.getExtClass(item.label);
342
+
343
+ const toggleHTML = isFolder ? `<button class="tree-toggle" aria-expanded="true">${ICON_CHEVRON}</button>` : `<span class="tree-toggle-placeholder"></span>`;
344
+
345
+ const childrenHTML = isFolder ? `<ul class="tree-children">${this.renderTreeItems(item.children)}</ul>` : "";
346
+
347
+ return `
348
+ <li class="tree-item">
349
+ <div class="tree-row">
350
+ ${toggleHTML}
351
+ <span class="tree-icon ${extClass}">${icon}</span>
352
+ <span class="tree-label">${this.escapeHTML(item.label)}</span>
353
+ </div>
354
+ ${childrenHTML}
355
+ </li>
356
+ `;
357
+ })
358
+ .join("");
359
+ }
360
+
361
+ getExtClass(filename) {
362
+ const ext = filename.includes(".") ? filename.split(".").pop().toLowerCase() : "";
363
+ return ext;
364
+ }
365
+
366
+ toggleNode(btn) {
367
+ const treeItem = btn.closest(".tree-item");
368
+ const children = treeItem.querySelector(":scope > .tree-children");
369
+ if (!children) return;
370
+
371
+ const isCollapsed = btn.classList.contains("collapsed");
372
+
373
+ if (isCollapsed) {
374
+ btn.classList.remove("collapsed");
375
+ btn.setAttribute("aria-expanded", "true");
376
+ children.classList.remove("collapsed");
377
+ children.style.maxHeight = children.scrollHeight + "px";
378
+ } else {
379
+ btn.classList.add("collapsed");
380
+ btn.setAttribute("aria-expanded", "false");
381
+ children.style.maxHeight = children.scrollHeight + "px";
382
+ children.offsetHeight;
383
+ children.classList.add("collapsed");
384
+ }
385
+ }
386
+
387
+ escapeHTML(str) {
388
+ const div = document.createElement("div");
389
+ div.textContent = str;
390
+ return div.innerHTML;
391
+ }
392
+
393
+ renderFromText() {
394
+ const rawText = this.textContent.trim();
395
+ if (!rawText) {
396
+ this.shadowRoot.innerHTML = "";
397
+ return;
398
+ }
399
+
400
+ const lines = rawText.split("\n").filter((line) => line.trim());
401
+ const treeData = this.parseIndentedLines(lines);
402
+ this.buildTree(treeData);
403
+ }
404
+
405
+ parseIndentedLines(lines) {
406
+ const root = { children: [] };
407
+ const stack = [{ indent: -1, node: root }];
408
+
409
+ for (const line of lines) {
410
+ const match = line.match(/^(\s*)[-*+]\s+(.*)/);
411
+ if (!match) continue;
412
+
413
+ const indent = match[1].length;
414
+ const label = match[2].trim();
415
+
416
+ const node = { label, children: [], hasChildren: false };
417
+
418
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
419
+ stack.pop();
420
+ }
421
+
422
+ const parent = stack[stack.length - 1].node;
423
+ parent.children.push(node);
424
+ parent.hasChildren = true;
425
+
426
+ stack.push({ indent, node });
427
+ }
428
+
429
+ return root.children;
430
+ }
431
+ }
432
+
433
+ if (!customElements.get("x-tree")) {
434
+ customElements.define("x-tree", XTree);
435
+ }
436
+
437
+ export { XTree };
@@ -0,0 +1,112 @@
1
+ function getCacheKey() {
2
+ return `gnix-encrypt:${location.pathname}`;
3
+ }
4
+
5
+ function b64ToBytes(base64) {
6
+ const bin = atob(base64);
7
+ const bytes = new Uint8Array(bin.length);
8
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
9
+ return bytes;
10
+ }
11
+
12
+ async function deriveKey(password, salt) {
13
+ const material = await crypto.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveKey"]);
14
+ return crypto.subtle.deriveKey({ name: "PBKDF2", salt, iterations: 100000, hash: "SHA-256" }, material, { name: "AES-GCM", length: 256 }, false, ["decrypt"]);
15
+ }
16
+
17
+ async function decryptPayload(password, base64) {
18
+ const data = b64ToBytes(base64);
19
+ const salt = data.slice(0, 16);
20
+ const iv = data.slice(16, 28);
21
+ const ciphertext = data.slice(28);
22
+ const key = await deriveKey(password, salt);
23
+ const plain = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext);
24
+ return new TextDecoder().decode(plain);
25
+ }
26
+
27
+ function buildToc() {
28
+ const tocContainer = document.getElementById("icarus-toc-container");
29
+ if (!tocContainer) return;
30
+ const headings = document.querySelectorAll(".content h1[id], .content h2[id], .content h3[id], .content h4[id], .content h5[id], .content h6[id]");
31
+ if (!headings.length) return;
32
+ const ol = document.createElement("ol");
33
+ ol.className = "toc";
34
+ for (const h of headings) {
35
+ const li = document.createElement("li");
36
+ li.className = `toc-item toc-level-${h.tagName[1]}`;
37
+ const a = document.createElement("a");
38
+ a.className = "toc-link";
39
+ a.href = `#${h.id}`;
40
+ const span = document.createElement("span");
41
+ span.className = "toc-text";
42
+ span.textContent = h.textContent;
43
+ a.appendChild(span);
44
+ li.appendChild(a);
45
+ ol.appendChild(li);
46
+ }
47
+ const insert = tocContainer.querySelector("#toc-insert");
48
+ if (insert) {
49
+ insert.innerHTML = "";
50
+ insert.appendChild(ol);
51
+ }
52
+ tocContainer.style.display = "";
53
+ }
54
+
55
+ async function decrypt(container, password) {
56
+ const base64 = container.querySelector(".encrypted-data").textContent.trim();
57
+ const html = await decryptPayload(password, base64);
58
+ container.outerHTML = html;
59
+ localStorage.setItem(getCacheKey(), password);
60
+ buildToc();
61
+ if (typeof initPage === "function") initPage();
62
+ }
63
+
64
+ function showError(container) {
65
+ const form = container.querySelector("#encrypt-form");
66
+ let el = form.querySelector(".encrypt-error");
67
+ if (!el) {
68
+ el = document.createElement("p");
69
+ el.className = "encrypt-error";
70
+ form.appendChild(el);
71
+ }
72
+ el.textContent = document.documentElement.lang === "zh-CN" ? "密码错误,请重试" : "Wrong password, please try again";
73
+ const input = container.querySelector("#encrypt-pass");
74
+ if (input) {
75
+ input.value = "";
76
+ input.focus();
77
+ }
78
+ }
79
+
80
+ async function tryDecrypt(container, password) {
81
+ try {
82
+ await decrypt(container, password);
83
+ } catch {
84
+ showError(container);
85
+ }
86
+ }
87
+
88
+ function init() {
89
+ const container = document.getElementById("encrypted-article");
90
+ if (!container) return;
91
+ const cached = localStorage.getItem(getCacheKey());
92
+ if (cached) {
93
+ tryDecrypt(container, cached);
94
+ return;
95
+ }
96
+ const form = container.querySelector("#encrypt-form");
97
+ if (form) {
98
+ form.addEventListener("submit", (e) => {
99
+ e.preventDefault();
100
+ const pw = container.querySelector("#encrypt-pass")?.value.trim();
101
+ if (pw) tryDecrypt(container, pw);
102
+ });
103
+ }
104
+ const input = container.querySelector("#encrypt-pass");
105
+ if (input) input.focus();
106
+ }
107
+
108
+ init();
109
+
110
+ if (typeof swup !== "undefined") {
111
+ swup.hooks.on("page:view", init);
112
+ }