hexo-theme-gnix 12.0.0 → 14.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 (45) hide show
  1. package/README.md +2 -0
  2. package/include/hexo/generator/archive.js +14 -1
  3. package/include/hexo/generator/index.js +0 -5
  4. package/include/hexo/generator/page.js +18 -4
  5. package/include/hexo/generator/tag.js +1 -1
  6. package/include/hexo/helper.js +0 -4
  7. package/include/hexo/i18n.js +31 -136
  8. package/include/hexo/obsidian-callouts.js +210 -0
  9. package/include/hexo/renderer.js +4 -14
  10. package/include/hexo/shiki.js +191 -0
  11. package/include/hexo/sitemap.js +184 -0
  12. package/include/util/i18n.js +92 -106
  13. package/languages/en.yml +4 -10
  14. package/languages/zh-CN.yml +4 -10
  15. package/layout/archive.jsx +155 -78
  16. package/layout/common/article.jsx +94 -108
  17. package/layout/common/article_cover.jsx +3 -3
  18. package/layout/common/article_info.jsx +11 -48
  19. package/layout/common/article_media.jsx +9 -2
  20. package/layout/common/footer.jsx +17 -106
  21. package/layout/common/head.jsx +3 -15
  22. package/layout/common/navbar.jsx +24 -87
  23. package/layout/common/scripts.jsx +1 -1
  24. package/layout/layout.jsx +37 -19
  25. package/layout/plugin/goatcounter.jsx +25 -0
  26. package/layout/tag.jsx +3 -70
  27. package/layout/tags.jsx +26 -23
  28. package/package.json +7 -13
  29. package/scripts/index.js +1 -0
  30. package/source/css/archive.css +287 -168
  31. package/source/css/callout_blocks.css +41 -21
  32. package/source/css/default.css +154 -132
  33. package/source/css/optional/mermaid.css +12 -6
  34. package/source/css/responsive.css +1 -45
  35. package/source/css/shiki/shiki.css +5 -4
  36. package/source/css/tags.css +53 -59
  37. package/source/js/components/archive-popup.js +313 -0
  38. package/source/js/components/friends-list.js +270 -0
  39. package/source/js/components/x-info-card.js +297 -0
  40. package/source/js/main.js +38 -34
  41. package/source/js/mdit/mermaid.js +10 -0
  42. package/include/hexo/generator/home.js +0 -64
  43. package/layout/index.jsx +0 -19
  44. package/layout/misc/paginator.jsx +0 -69
  45. package/source/js/host/iconify-icon/3.0.2/iconify-icon.min.js +0 -12
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Friends list custom elements.
3
+ *
4
+ * Usage:
5
+ * <friends-list id="friends">
6
+ * <friend-card
7
+ * name="Example"
8
+ * href="https://example.com"
9
+ * display-url="example.com"
10
+ * avatar="https://example.com/avatar.png"
11
+ * description="Personal site"
12
+ * feed="https://example.com/atom.xml"
13
+ * open-label="Open"
14
+ * ></friend-card>
15
+ * </friends-list>
16
+ */
17
+
18
+ let friendsListStyleSheetInjected = false;
19
+
20
+ function escapeHtml(value) {
21
+ const span = document.createElement("span");
22
+ span.textContent = value == null ? "" : String(value);
23
+ return span.innerHTML;
24
+ }
25
+
26
+ function escapeAttribute(value) {
27
+ return escapeHtml(value).replace(/"/g, "&quot;");
28
+ }
29
+
30
+ function displayUrlFromHref(href) {
31
+ try {
32
+ const url = new URL(href);
33
+ return url.hostname.replace(/^www\./, "www.");
34
+ } catch {
35
+ return href.replace(/^https?:\/\//, "").replace(/\/$/, "");
36
+ }
37
+ }
38
+
39
+ function injectFriendsListStyles() {
40
+ if (friendsListStyleSheetInjected) return;
41
+
42
+ const style = `
43
+ friends-list {
44
+ display: grid;
45
+ grid-template-columns: repeat(6, 1fr);
46
+ padding: 0;
47
+ }
48
+
49
+ friend-card {
50
+ position: relative;
51
+ grid-column: span 2;
52
+ padding: 1.5rem;
53
+ background: var(--base);
54
+ border: .5px solid var(--surface0);
55
+ cursor: pointer;
56
+ overflow: hidden;
57
+ min-height: 5.25rem;
58
+ display: flex;
59
+ flex-direction: column;
60
+ justify-content: center;
61
+ }
62
+
63
+ friend-card:focus-within,
64
+ friend-card:hover {
65
+ border-color: var(--lavender);
66
+ box-shadow: 0 0.75rem 2.5rem -0.75rem hsl(from var(--text) h s l / 0.1);
67
+ }
68
+
69
+ friend-card:nth-last-child(1):nth-child(3n+1) {
70
+ grid-column: span 6;
71
+ }
72
+
73
+ friend-card:nth-last-child(1):nth-child(3n+2),
74
+ friend-card:nth-last-child(2):nth-child(3n+1) {
75
+ grid-column: span 3;
76
+ }
77
+
78
+ friend-card::before {
79
+ content: '';
80
+ position: absolute;
81
+ inset: 0;
82
+ background:
83
+ radial-gradient(circle at 100% 100%, hsl(from var(--lavender) h s l / 0.14), transparent 55%);
84
+ opacity: 0;
85
+ transition: opacity 0.3s ease;
86
+ z-index: 0;
87
+ }
88
+
89
+ friend-card:focus-within::before,
90
+ friend-card:hover::before {
91
+ opacity: 1;
92
+ }
93
+
94
+ .friend-hit {
95
+ position: absolute;
96
+ inset: 0;
97
+ z-index: 2;
98
+ border-radius: inherit;
99
+ }
100
+
101
+ .friend-avatar {
102
+ position: absolute;
103
+ bottom: -0.5rem;
104
+ right: -0.5rem;
105
+ width: 6rem;
106
+ height: 6rem;
107
+ border-radius: 50%;
108
+ background-color: var(--surface0);
109
+ object-fit: cover;
110
+ opacity: 0.08;
111
+ filter: grayscale(0.5);
112
+ transform: rotate(-8deg);
113
+ transition: all 0.3s ease;
114
+ z-index: 0;
115
+ pointer-events: none;
116
+ border: none;
117
+ }
118
+
119
+ friend-card:focus-within .friend-avatar,
120
+ friend-card:hover .friend-avatar {
121
+ opacity: 0.15;
122
+ filter: grayscale(0);
123
+ transform: scale(1.1) rotate(-8deg);
124
+ }
125
+
126
+ .friend-detail {
127
+ position: relative;
128
+ z-index: 1;
129
+ display: flex;
130
+ flex-direction: column;
131
+ gap: 0.25rem;
132
+ margin: 0;
133
+ }
134
+
135
+ h3.friend-name {
136
+ font-family: var(--font-serif);
137
+ font-style: italic;
138
+ font-synthesis: none;
139
+ font-size: 0.92rem;
140
+ font-weight: bolder;
141
+ color: var(--text);
142
+ display: flex;
143
+ align-items: center;
144
+ gap: 0.5rem;
145
+ line-height: 1.3;
146
+ margin: 0;
147
+ }
148
+
149
+ .friend-name .rss-link {
150
+ display: inline-flex;
151
+ align-items: center;
152
+ justify-content: center;
153
+ width: 1.25rem;
154
+ height: 1.25rem;
155
+ color: var(--subtext0);
156
+ text-decoration: none;
157
+ transition: all 0.2s ease;
158
+ border: 1px solid var(--surface0);
159
+ border-radius: 50%;
160
+ font-size: 0.75rem;
161
+ position: relative;
162
+ z-index: 3;
163
+ flex: 0 0 auto;
164
+ }
165
+
166
+ .friend-name .rss-link:hover {
167
+ color: var(--lavender);
168
+ border-color: var(--lavender);
169
+ }
170
+
171
+ .friend-url {
172
+ font-family: var(--font-mono);
173
+ font-size: 0.75rem;
174
+ font-weight: 400;
175
+ color: var(--subtext0);
176
+ opacity: 0.7;
177
+ transition: all 0.2s ease;
178
+ white-space: nowrap;
179
+ overflow: hidden;
180
+ text-overflow: ellipsis;
181
+ margin: 0;
182
+ }
183
+
184
+ friend-card:hover .friend-url {
185
+ opacity: 1;
186
+ color: var(--lavender);
187
+ }
188
+
189
+ .friend-desc {
190
+ font-family: var(--font-sans-serif);
191
+ font-size: 0.8rem;
192
+ color: var(--subtext1);
193
+ margin: 0.5rem 0 0;
194
+ padding: 0;
195
+ overflow: hidden;
196
+ }
197
+
198
+ @media (max-width: 768px) {
199
+ friends-list {
200
+ grid-template-columns: 1fr;
201
+ }
202
+
203
+ friend-card,
204
+ friend-card:nth-last-child(1):nth-child(3n+1),
205
+ friend-card:nth-last-child(1):nth-child(3n+2),
206
+ friend-card:nth-last-child(2):nth-child(3n+1) {
207
+ grid-column: span 1;
208
+ }
209
+
210
+ friend-card:nth-child(1) {
211
+ min-height: 6.25rem;
212
+ }
213
+ }
214
+
215
+ @media (max-width: 480px) {
216
+ friend-card {
217
+ padding: 1.1rem;
218
+ }
219
+ }
220
+ `;
221
+
222
+ const styleEl = document.createElement("style");
223
+ styleEl.textContent = style;
224
+ document.head.appendChild(styleEl);
225
+ friendsListStyleSheetInjected = true;
226
+ }
227
+
228
+ class FriendsList extends HTMLElement {
229
+ connectedCallback() {
230
+ injectFriendsListStyles();
231
+ if (!this.hasAttribute("role")) this.setAttribute("role", "list");
232
+ }
233
+ }
234
+
235
+ class FriendCard extends HTMLElement {
236
+ connectedCallback() {
237
+ injectFriendsListStyles();
238
+ this.render();
239
+ }
240
+
241
+ render() {
242
+ const name = this.getAttribute("name") || "";
243
+ const href = this.getAttribute("href") || "";
244
+ const displayUrl = this.getAttribute("display-url") || displayUrlFromHref(href);
245
+ const avatar = this.getAttribute("avatar") || "";
246
+ const description = this.getAttribute("description") || "";
247
+ const feed = this.getAttribute("feed") || "";
248
+ const openLabel = this.getAttribute("open-label") || "Open";
249
+ const ariaLabel = href && name ? `${openLabel} ${name}` : "";
250
+
251
+ this.setAttribute("role", "listitem");
252
+ this.innerHTML = `
253
+ ${href ? `<a class="friend-hit" href="${escapeAttribute(href)}" target="_blank" rel="noopener noreferrer" aria-label="${escapeAttribute(ariaLabel)}"></a>` : ""}
254
+ ${avatar ? `<img class="friend-avatar" src="${escapeAttribute(avatar)}" alt="" width="96" height="96" loading="lazy" decoding="async">` : ""}
255
+ <article class="friend-detail">
256
+ <h3 class="friend-name">
257
+ ${escapeHtml(name)}
258
+ ${feed ? `<a class="rss-link" target="_blank" rel="noopener noreferrer" href="${escapeAttribute(feed)}" title="RSS Feed">◎</a>` : ""}
259
+ </h3>
260
+ ${displayUrl ? `<div class="friend-url">${escapeHtml(displayUrl)}</div>` : ""}
261
+ ${description ? `<div class="friend-desc">${escapeHtml(description)}</div>` : ""}
262
+ </article>
263
+ `;
264
+ }
265
+ }
266
+
267
+ if (!customElements.get("friends-list")) customElements.define("friends-list", FriendsList);
268
+ if (!customElements.get("friend-card")) customElements.define("friend-card", FriendCard);
269
+
270
+ export { FriendCard, FriendsList };
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Info card custom element.
3
+ *
4
+ * Usage:
5
+ * <x-info-card
6
+ * name="GnixAij"
7
+ * description="Life Tracking & Tech Sharing"
8
+ * avatar="https://assets.vluv.space/avatar.webp"
9
+ * website="https://vluv.space"
10
+ * feed="/atom.xml"
11
+ * links='{"Github":{"icon":"mdi:github","url":"https://github.com/Efterklang"}}'
12
+ * quicklinks='{"Now":"now"}'
13
+ * ></x-info-card>
14
+ */
15
+
16
+ let infoCardStyleSheetInjected = false;
17
+
18
+ function escapeHtml(value) {
19
+ const span = document.createElement("span");
20
+ span.textContent = value == null ? "" : String(value);
21
+ return span.innerHTML;
22
+ }
23
+
24
+ function escapeAttribute(value) {
25
+ return escapeHtml(value).replace(/"/g, "&quot;");
26
+ }
27
+
28
+ function parseJsonAttribute(element, name) {
29
+ const value = element.getAttribute(name);
30
+ if (!value) return {};
31
+
32
+ try {
33
+ const parsed = JSON.parse(value);
34
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
35
+ } catch {
36
+ return {};
37
+ }
38
+ }
39
+
40
+ function normalizeLinkEntry(entry) {
41
+ if (typeof entry === "string") return { url: entry };
42
+ if (entry && typeof entry === "object" && typeof entry.url === "string") return entry;
43
+ return null;
44
+ }
45
+
46
+ class InfoCard extends HTMLElement {
47
+ connectedCallback() {
48
+ this.injectStyles();
49
+ this.render();
50
+ }
51
+
52
+ injectStyles() {
53
+ if (infoCardStyleSheetInjected) return;
54
+
55
+ const style = `
56
+ x-info-card {
57
+ display: block;
58
+ margin: 2rem 0;
59
+ }
60
+
61
+ .x-info-card {
62
+ background: var(--base);
63
+ border: 1px solid var(--surface0);
64
+ border-radius: var(--radius);
65
+ padding: clamp(1.25rem, 4vw, 2rem);
66
+ position: relative;
67
+ overflow: hidden;
68
+ }
69
+
70
+ .x-info-card::before {
71
+ content: "";
72
+ position: absolute;
73
+ inset: auto -20% -45% 35%;
74
+ height: 70%;
75
+ pointer-events: none;
76
+ }
77
+
78
+ .x-info-header {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 1rem;
82
+ margin-bottom: 1.25rem;
83
+ position: relative;
84
+ z-index: 1;
85
+ }
86
+
87
+ .x-info-avatar {
88
+ width: 3.5rem;
89
+ height: 3.5rem;
90
+ border-radius: 50%;
91
+ object-fit: cover;
92
+ border: 2px solid var(--surface0);
93
+ flex: 0 0 auto;
94
+ }
95
+
96
+ .x-info-name {
97
+ font-family: var(--font-serif);
98
+ font-style: italic;
99
+ font-size: 1.35rem;
100
+ font-weight: 700;
101
+ color: var(--rosewater);
102
+ margin: 0;
103
+ line-height: 1.2;
104
+ }
105
+
106
+ .x-info-desc {
107
+ font-family: var(--font-sans-serif);
108
+ font-size: 0.8rem;
109
+ font-weight: 300;
110
+ color: var(--subtext0);
111
+ margin: 0.2rem 0 0;
112
+ }
113
+
114
+ .x-info-details {
115
+ display: grid;
116
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
117
+ gap: 0.75rem;
118
+ padding-top: 1.25rem;
119
+ border-top: 1px solid var(--surface0);
120
+ position: relative;
121
+ z-index: 1;
122
+ }
123
+
124
+ .x-info-item {
125
+ display: flex;
126
+ flex-direction: column;
127
+ gap: 0.15rem;
128
+ min-width: 0;
129
+ }
130
+
131
+ .x-info-label {
132
+ font-family: var(--font-mono);
133
+ font-size: 0.65rem;
134
+ font-weight: 500;
135
+ color: var(--subtext0);
136
+ text-transform: uppercase;
137
+ letter-spacing: 0.1em;
138
+ }
139
+
140
+ .x-info-value {
141
+ font-family: var(--font-mono);
142
+ font-size: 0.8125rem;
143
+ color: var(--text);
144
+ text-decoration: none;
145
+ word-break: break-word;
146
+ transition: color 0.2s ease;
147
+ }
148
+
149
+ .x-info-value:hover {
150
+ color: var(--lavender);
151
+ }
152
+
153
+ .x-info-links {
154
+ display: flex;
155
+ flex-wrap: wrap;
156
+ align-items: center;
157
+ gap: 0.6rem 0.9rem;
158
+ padding-top: 1.25rem;
159
+ margin-top: 0.25rem;
160
+ border-top: 1px solid var(--surface0);
161
+ position: relative;
162
+ z-index: 1;
163
+ }
164
+
165
+ .x-info-link {
166
+ font-family: var(--font-mono);
167
+ font-size: 0.8rem;
168
+ color: var(--subtext0);
169
+ text-decoration: none;
170
+ transition: color 0.2s ease;
171
+ }
172
+
173
+ .x-info-link:hover {
174
+ color: var(--lavender);
175
+ }
176
+
177
+ .x-info-link--icon {
178
+ display: inline-flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ width: 1.6rem;
182
+ height: 1.6rem;
183
+ border-radius: 50%;
184
+ transition: color 0.2s ease, background 0.2s ease;
185
+ }
186
+
187
+ .x-info-link--icon:hover {
188
+ color: var(--lavender);
189
+ }
190
+
191
+ .x-info-link--icon svg {
192
+ width: 1.1rem;
193
+ height: 1.1rem;
194
+ }
195
+
196
+ .x-info-sep {
197
+ width: 1px;
198
+ height: 1rem;
199
+ background: var(--surface0);
200
+ margin: 0 0.15rem;
201
+ }
202
+
203
+ @media (max-width: 520px) {
204
+ .x-info-header {
205
+ align-items: flex-start;
206
+ }
207
+
208
+ .x-info-details {
209
+ grid-template-columns: 1fr;
210
+ }
211
+ }
212
+ `;
213
+
214
+ const styleEl = document.createElement("style");
215
+ styleEl.textContent = style;
216
+ document.head.appendChild(styleEl);
217
+ infoCardStyleSheetInjected = true;
218
+ }
219
+
220
+ brandIconSVG(label) {
221
+ const svg = (d) => `<svg aria-hidden="true" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="${d}"/></svg>`;
222
+
223
+ const ICONS = {
224
+ Bluesky: "M12 11.388c-.906-1.761-3.372-5.044-5.665-6.662c-2.197-1.55-3.034-1.283-3.583-1.033C2.116 3.978 2 4.955 2 5.528c0 .575.315 4.709.52 5.4c.68 2.28 3.094 3.05 5.32 2.803c-3.26.483-6.157 1.67-2.36 5.898c4.178 4.325 5.726-.927 6.52-3.59c.794 2.663 1.708 7.726 6.444 3.59c3.556-3.59.977-5.415-2.283-5.898c2.225.247 4.64-.523 5.319-2.803c.205-.69.52-4.825.52-5.399c0-.575-.116-1.55-.752-1.838c-.549-.248-1.386-.517-3.583 1.033c-2.293 1.621-4.76 4.904-5.665 6.664",
225
+ Github: "M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33s1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2",
226
+ Youtube: "M12 4c.855 0 1.732.022 2.582.058l1.004.048l.961.057l.9.061l.822.064a3.8 3.8 0 0 1 3.494 3.423l.04.425l.075.91c.07.943.122 1.971.122 2.954s-.052 2.011-.122 2.954l-.075.91l-.04.425a3.8 3.8 0 0 1-3.495 3.423l-.82.063l-.9.062l-.962.057l-1.004.048A62 62 0 0 1 12 20a62 62 0 0 1-2.582-.058l-1.004-.048l-.961-.057l-.9-.062l-.822-.063a3.8 3.8 0 0 1-3.494-3.423l-.04-.425l-.075-.91A41 41 0 0 1 2 12c0-.983.052-2.011.122-2.954l.075-.91l.04-.425A3.8 3.8 0 0 1 5.73 4.288l.821-.064l.9-.061l.962-.057l1.004-.048A62 62 0 0 1 12 4m-2 5.575v4.85c0 .462.5.75.9.52l4.2-2.425a.6.6 0 0 0 0-1.04l-4.2-2.424a.6.6 0 0 0-.9.52Z",
227
+ Bilibili: "M17.555 3.168a1 1 0 0 1 .277 1.387L16.87 6H18a4 4 0 0 1 4 4v7a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4v-7a4 4 0 0 1 4-4h1.131l-.963-1.445a1 1 0 0 1 1.664-1.11L9.535 6h4.93l1.703-2.555a1 1 0 0 1 1.387-.277M9 11a1 1 0 0 0-.993.883L8 12v2a1 1 0 0 0 1.993.117L10 14v-2a1 1 0 0 0-1-1m6 0a1 1 0 0 0-1 1v2a1 1 0 1 0 2 0v-2a1 1 0 0 0-1-1",
228
+ Threads: "M5.086 4.28c1.65-1.76 4.031-2.78 6.93-2.78c4.792 0 8.017 2.784 9.033 6.65a1.5 1.5 0 0 1-2.902.762C17.48 6.37 15.45 4.5 12.017 4.5c-2.164 0-3.72.742-4.742 1.832C6.239 7.438 5.64 9.02 5.64 10.875v2.25c0 1.855.598 3.437 1.634 4.543c1.022 1.09 2.578 1.832 4.741 1.832c1.576 0 2.795-.365 3.714-.93c.92-.568 1.42-1.285 1.56-2.068c.173-.972-.044-1.579-.364-2.001a2 2 0 0 0-.116-.14a5.3 5.3 0 0 1-.623 1.382c-1.514 2.3-4.369 2.46-6.203 1.728c-1.091-.435-1.972-1.583-2.247-2.788a3.5 3.5 0 0 1 .168-2.147c.312-.75.884-1.37 1.662-1.828c.8-.472 1.927-.665 2.979-.694a11 11 0 0 1 1.254.04c-.09-.2-.187-.343-.274-.425c-.384-.357-1.06-.632-1.746-.628c-.647.005-1.126.247-1.41.7a1.5 1.5 0 1 1-2.544-1.59c.948-1.515 2.507-2.1 3.933-2.11c1.388-.01 2.821.512 3.81 1.432c.954.888 1.373 2.254 1.513 3.485c.836.403 1.63.974 2.234 1.77c.874 1.15 1.233 2.624.927 4.34c-.32 1.793-1.45 3.178-2.94 4.096c-1.457.898-3.239 1.376-5.287 1.376c-2.899 0-5.28-1.02-6.93-2.78c-1.636-1.746-2.445-4.1-2.445-6.595v-2.25c0-2.494.81-4.85 2.445-6.594Zm8.947 8.823a8 8 0 0 0-1.405-.09c-.86.024-1.384.188-1.537.279c-.305.18-.39.333-.417.398a.53.53 0 0 0-.011.327c.036.16.121.333.238.48a.8.8 0 0 0 .194.186q.008.006 0 .002c.985.393 2.105.14 2.586-.592c.137-.207.265-.553.352-.99",
229
+ X: "M19.753 4.659a1 1 0 0 0-1.506-1.317l-5.11 5.84L8.8 3.4A1 1 0 0 0 8 3H4a1 1 0 0 0-.8 1.6l6.437 8.582l-5.39 6.16a1 1 0 0 0 1.506 1.317l5.11-5.841L15.2 20.6a1 1 0 0 0 .8.4h4a1 1 0 0 0 .8-1.6l-6.437-8.582l5.39-6.16ZM16.5 19L6 5h1.5L18 19",
230
+ Steam: "M12 2a10 10 0 0 1 10 10a10 10 0 0 1-10 10c-4.6 0-8.45-3.08-9.64-7.27l3.83 1.58a2.84 2.84 0 0 0 2.78 2.27c1.56 0 2.83-1.27 2.83-2.83v-.13l3.4-2.43h.08c2.08 0 3.77-1.69 3.77-3.77s-1.69-3.77-3.77-3.77s-3.78 1.69-3.78 3.77v.05l-2.37 3.46l-.16-.01c-.59 0-1.14.18-1.59.49L2 11.2C2.43 6.05 6.73 2 12 2M8.28 17.17c.8.33 1.72-.04 2.05-.84s-.05-1.71-.83-2.04l-1.28-.53c.49-.18 1.04-.19 1.56.03c.53.21.94.62 1.15 1.15c.22.52.22 1.1 0 1.62c-.43 1.08-1.7 1.6-2.78 1.15c-.5-.21-.88-.59-1.09-1.04zm9.52-7.75c0 1.39-1.13 2.52-2.52 2.52a2.52 2.52 0 0 1-2.51-2.52a2.5 2.5 0 0 1 2.51-2.51a2.52 2.52 0 0 1 2.52 2.51m-4.4 0c0 1.04.84 1.89 1.89 1.89c1.04 0 1.88-.85 1.88-1.89s-.84-1.89-1.88-1.89c-1.05 0-1.89.85-1.89 1.89",
231
+ RSS: "M5 17a2 2 0 1 1 0 4a2 2 0 0 1 0-4M5 3c8.837 0 16 7.163 16 16q0 .277-.01.55a1.5 1.5 0 1 1-2.997-.1A13 13 0 0 0 18 19c0-7.18-5.82-13-13-13q-.225 0-.45.008a1.5 1.5 0 0 1-.1-2.999Q4.722 3 5 3m0 7a9 9 0 0 1 8.98 9.599a1.5 1.5 0 1 1-2.993-.198a6 6 0 0 0-6.388-6.388a1.5 1.5 0 0 1-.197-2.993Q4.699 10 5 10",
232
+ };
233
+
234
+ return ICONS[label] ? svg(ICONS[label]) : svg("M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14l11-11");
235
+ }
236
+
237
+ render() {
238
+ const name = this.getAttribute("name") || "";
239
+ const description = this.getAttribute("description") || "";
240
+ const avatar = this.getAttribute("avatar") || "";
241
+ const website = this.getAttribute("website") || "";
242
+ const feed = this.getAttribute("feed") || "";
243
+ const avatarLink = this.getAttribute("avatar-link") || avatar;
244
+ const links = parseJsonAttribute(this, "links");
245
+ const quicklinks = parseJsonAttribute(this, "quicklinks");
246
+
247
+ const details = [
248
+ website
249
+ ? `<div class="x-info-item"><span class="x-info-label">Website</span><a href="${escapeAttribute(website)}" class="x-info-value" target="_blank" rel="noopener noreferrer">${escapeHtml(website.replace(/^https?:\/\//, ""))}</a></div>`
250
+ : "",
251
+ feed
252
+ ? `<div class="x-info-item"><span class="x-info-label">Feed</span><a href="${escapeAttribute(feed)}" class="x-info-value" target="_blank" rel="noopener noreferrer">${escapeHtml(feed.replace(/^https?:\/\//, ""))}</a></div>`
253
+ : "",
254
+ avatarLink
255
+ ? `<div class="x-info-item"><span class="x-info-label">Avatar</span><a href="${escapeAttribute(avatarLink)}" class="x-info-value" target="_blank" rel="noopener noreferrer">${escapeHtml(avatarLink.replace(/^https?:\/\//, ""))}</a></div>`
256
+ : "",
257
+ ]
258
+ .filter(Boolean)
259
+ .join("");
260
+
261
+ const quicklinkItems = Object.keys(quicklinks)
262
+ .map((label) => {
263
+ const link = normalizeLinkEntry(quicklinks[label]);
264
+ if (!link) return "";
265
+ return `<a class="x-info-link" href="${escapeAttribute(link.url)}" target="_self" rel="noopener noreferrer">${escapeHtml(label)}</a>`;
266
+ })
267
+ .filter(Boolean);
268
+
269
+ const socialItems = Object.keys(links)
270
+ .map((label) => {
271
+ const link = normalizeLinkEntry(links[label]);
272
+ if (!link) return "";
273
+ const iconSvg = this.brandIconSVG(label);
274
+ return `<a class="x-info-link x-info-link--icon" href="${escapeAttribute(link.url)}" target="_blank" rel="noopener noreferrer" title="${escapeAttribute(label)}">${iconSvg}</a>`;
275
+ })
276
+ .filter(Boolean);
277
+
278
+ const hasDivider = quicklinkItems.length > 0 && socialItems.length > 0;
279
+ const linksHtml = [...quicklinkItems, hasDivider ? `<span class="x-info-sep" aria-hidden="true"></span>` : "", ...socialItems].filter(Boolean).join("");
280
+
281
+ this.innerHTML = `
282
+ <div class="x-info-card">
283
+ <div class="x-info-header">
284
+ ${avatar ? `<img src="${escapeAttribute(avatar)}" alt="${escapeAttribute(name)}" class="x-info-avatar" width="56" height="56" loading="lazy" decoding="async">` : ""}
285
+ <div>
286
+ ${name ? `<h3 class="x-info-name">${escapeHtml(name)}</h3>` : ""}
287
+ ${description ? `<p class="x-info-desc">${escapeHtml(description)}</p>` : ""}
288
+ </div>
289
+ </div>
290
+ ${details ? `<div class="x-info-details">${details}</div>` : ""}
291
+ ${linksHtml ? `<div class="x-info-links">${linksHtml}</div>` : ""}
292
+ </div>
293
+ `;
294
+ }
295
+ }
296
+
297
+ if (!customElements.get("x-info-card")) customElements.define("x-info-card", InfoCard);
package/source/js/main.js CHANGED
@@ -1,3 +1,27 @@
1
+ function showSiteToast(message) {
2
+ if (!message) return;
3
+
4
+ let toast = document.getElementById("site-toast");
5
+ if (!toast) {
6
+ toast = document.createElement("div");
7
+ toast.id = "site-toast";
8
+ toast.setAttribute("role", "status");
9
+ toast.setAttribute("aria-live", "polite");
10
+ toast.className = "site-toast";
11
+ document.body.appendChild(toast);
12
+ }
13
+
14
+ toast.textContent = message;
15
+ toast.classList.add("is-visible");
16
+
17
+ clearTimeout(toast._hideTimer);
18
+ toast._hideTimer = setTimeout(() => {
19
+ toast.classList.remove("is-visible");
20
+ }, 1800);
21
+ }
22
+
23
+ window.showSiteToast = showSiteToast;
24
+
1
25
  function twikoo_handler() {
2
26
  const el = document.getElementById("tko");
3
27
  if (!el) return;
@@ -573,38 +597,6 @@ function initArticleCommentPopover() {
573
597
  });
574
598
  }
575
599
 
576
- function getComparablePath(url) {
577
- const path = new URL(url, window.location.href).pathname.replace(/\/index\.html$/, "/").replace(/\/+$/, "");
578
- return path || "/";
579
- }
580
-
581
- function updateNavbarCurrentPage() {
582
- const currentPath = getComparablePath(window.location.href);
583
- document.querySelectorAll(".navbar-start .navbar-item").forEach((item) => {
584
- const itemPath = getComparablePath(item.href);
585
- const active = itemPath === currentPath || (itemPath !== "/" && currentPath.startsWith(`${itemPath}/`));
586
-
587
- item.classList.toggle("is-active", active);
588
- if (active) {
589
- item.setAttribute("aria-current", "page");
590
- } else {
591
- item.removeAttribute("aria-current");
592
- }
593
- });
594
- }
595
-
596
- function refreshNavbarIcons() {
597
- document.querySelectorAll(".navbar-end iconify-icon").forEach((el) => {
598
- if (!el.getAttribute("icon")) return;
599
- if (el.shadowRoot?.querySelector("svg")) return;
600
- const parent = el.parentNode;
601
- if (!parent) return;
602
- const next = el.nextSibling;
603
- parent.removeChild(el);
604
- parent.insertBefore(el, next);
605
- });
606
- }
607
-
608
600
  function initPage() {
609
601
  handleMermaid();
610
602
  addHighlightTool();
@@ -614,8 +606,7 @@ function initPage() {
614
606
  document.querySelectorAll(".content img").forEach((img) => zoomImgs.add(img));
615
607
  mediumZoom([...zoomImgs], zoomOpts);
616
608
  initArticleCommentPopover();
617
- updateNavbarCurrentPage();
618
- refreshNavbarIcons();
609
+ window.__gnixInitArchivePopup?.();
619
610
  }
620
611
 
621
612
  document.addEventListener("DOMContentLoaded", initPage, { once: true });
@@ -634,6 +625,19 @@ document.addEventListener("keydown", handleKeyDown, {
634
625
  passive: false, // 允许调用 preventDefault
635
626
  });
636
627
 
628
+ document.addEventListener(
629
+ "click",
630
+ (e) => {
631
+ const el = e.target.closest("#language-switch");
632
+ if (el && el.dataset.mode === "missing") {
633
+ e.preventDefault();
634
+ e.stopPropagation();
635
+ window.showSiteToast?.(el.dataset.toastMessage);
636
+ }
637
+ },
638
+ { capture: true },
639
+ );
640
+
637
641
  function toggleNav(event) {
638
642
  const container = event.currentTarget;
639
643
  const burger = container.querySelector(".navbar-burger");
@@ -60,6 +60,7 @@
60
60
  this.isDragging = false;
61
61
  this.startX = 0;
62
62
  this.startY = 0;
63
+ this.wrapper = container.querySelector(".mermaid-wrapper");
63
64
  this.initEvents();
64
65
  }
65
66
 
@@ -70,6 +71,15 @@
70
71
  }
71
72
 
72
73
  initEvents() {
74
+ this.container.addEventListener("pointerdown", () => {
75
+ this.wrapper?.classList.add("is-controls-visible");
76
+ });
77
+
78
+ document.addEventListener("pointerdown", (e) => {
79
+ if (this.container.contains(e.target)) return;
80
+ this.wrapper?.classList.remove("is-controls-visible");
81
+ });
82
+
73
83
  // Toolbar & Grid Panel
74
84
  this.container.addEventListener("click", (e) => {
75
85
  const btn = e.target.closest("button");