hexo-theme-gnix 10.0.0 → 12.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 +0 -4
- package/include/hexo/renderer.js +324 -0
- package/package.json +13 -1
- package/source/css/default.css +14 -59
- package/source/js/components/tab.js +242 -0
- package/source/js/main.js +1 -100
package/README.md
CHANGED
package/include/hexo/renderer.js
CHANGED
|
@@ -1,3 +1,322 @@
|
|
|
1
|
+
const path = require("node:path");
|
|
2
|
+
const { createMarkdownExit } = require("markdown-exit");
|
|
3
|
+
const mermaidDiagram = require("markdown-exit-mermaid");
|
|
4
|
+
const ratex = require("markdown-exit-ratex");
|
|
5
|
+
const code = require("markdown-exit-shiki");
|
|
6
|
+
const abbr = require("markdown-it-abbr");
|
|
7
|
+
const anchor = require("markdown-it-anchor");
|
|
8
|
+
const footnote = require("markdown-it-footnote");
|
|
9
|
+
const ins = require("markdown-it-ins");
|
|
10
|
+
const mark = require("markdown-it-mark");
|
|
11
|
+
const sub = require("markdown-it-sub");
|
|
12
|
+
const sup = require("markdown-it-sup");
|
|
13
|
+
const taskLists = require("markdown-it-task-lists");
|
|
14
|
+
|
|
15
|
+
function resolveDefault(module) {
|
|
16
|
+
return module && typeof module === "object" && "default" in module ? module.default : module;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function wrapMarkdownItTable(md, options = {}) {
|
|
20
|
+
const { figureClass = "table-wrapper", tableClass = "" } = options;
|
|
21
|
+
|
|
22
|
+
const defaultTableOpen = md.renderer.rules.table_open || ((tokens, idx, opts, _env, self) => self.renderToken(tokens, idx, opts));
|
|
23
|
+
|
|
24
|
+
const defaultTableClose = md.renderer.rules.table_close || ((tokens, idx, opts, _env, self) => self.renderToken(tokens, idx, opts));
|
|
25
|
+
|
|
26
|
+
md.renderer.rules.table_open = (tokens, idx, opts, env, self) => {
|
|
27
|
+
if (tableClass) {
|
|
28
|
+
tokens[idx].attrJoin("class", tableClass);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return `<figure class="${figureClass}">\n${defaultTableOpen(tokens, idx, opts, env, self)}`;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
md.renderer.rules.table_close = (tokens, idx, opts, env, self) => {
|
|
35
|
+
return `${defaultTableClose(tokens, idx, opts, env, self)}\n</figure>`;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return md;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseTabsMarker(state, line, name) {
|
|
42
|
+
let pos = state.bMarks[line] + state.tShift[line];
|
|
43
|
+
const max = state.eMarks[line];
|
|
44
|
+
|
|
45
|
+
if (state.src.charCodeAt(pos) !== 0x3a /* : */) return false;
|
|
46
|
+
|
|
47
|
+
let markerCount = 0;
|
|
48
|
+
while (pos + markerCount < max && state.src.charCodeAt(pos + markerCount) === 0x3a) markerCount++;
|
|
49
|
+
if (markerCount < 3) return false;
|
|
50
|
+
|
|
51
|
+
pos = state.skipSpaces(pos + markerCount);
|
|
52
|
+
if (state.src.slice(pos, pos + name.length) !== name) return false;
|
|
53
|
+
|
|
54
|
+
pos += name.length;
|
|
55
|
+
if (pos < max && !state.md.utils.isSpace(state.src.charCodeAt(pos))) return false;
|
|
56
|
+
|
|
57
|
+
pos = state.skipSpaces(pos);
|
|
58
|
+
const id = pos < max && state.src.charCodeAt(pos) === 0x23 /* # */ ? state.src.slice(state.skipSpaces(pos + 1), max).trim() : "";
|
|
59
|
+
|
|
60
|
+
return { marker: ":".repeat(markerCount), id };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseTabMarker(state, line) {
|
|
64
|
+
let pos = state.bMarks[line] + state.tShift[line];
|
|
65
|
+
const max = state.eMarks[line];
|
|
66
|
+
const marker = "@tab";
|
|
67
|
+
const activeMarker = "@tab:active";
|
|
68
|
+
|
|
69
|
+
if (state.src.charCodeAt(pos) !== 0x40 /* @ */) return false;
|
|
70
|
+
|
|
71
|
+
let active = false;
|
|
72
|
+
if (state.src.slice(pos, pos + activeMarker.length) === activeMarker) {
|
|
73
|
+
active = true;
|
|
74
|
+
pos += activeMarker.length;
|
|
75
|
+
} else if (state.src.slice(pos, pos + marker.length) === marker) {
|
|
76
|
+
pos += marker.length;
|
|
77
|
+
} else {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const titleStart = state.skipSpaces(pos);
|
|
82
|
+
if (titleStart === pos || titleStart >= max) return false;
|
|
83
|
+
|
|
84
|
+
const rawTitle = state.src.slice(titleStart, max).trim();
|
|
85
|
+
const match = /^(.*?)(?:\s+#([A-Za-z0-9_-]+))?$/.exec(rawTitle);
|
|
86
|
+
const title = (match?.[1] || rawTitle).trim();
|
|
87
|
+
const id = match?.[2] || "";
|
|
88
|
+
|
|
89
|
+
return { active, title, id };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function customTabs(md, options = {}) {
|
|
93
|
+
const name = options.name || "tabs";
|
|
94
|
+
|
|
95
|
+
md.block.ruler.before(
|
|
96
|
+
"fence",
|
|
97
|
+
`${name}_container`,
|
|
98
|
+
(state, startLine, endLine, silent) => {
|
|
99
|
+
const marker = parseTabsMarker(state, startLine, name);
|
|
100
|
+
if (!marker) return false;
|
|
101
|
+
if (silent) return true;
|
|
102
|
+
|
|
103
|
+
const startIndent = state.sCount[startLine];
|
|
104
|
+
let nextLine = startLine + 1;
|
|
105
|
+
let autoClosed = false;
|
|
106
|
+
|
|
107
|
+
for (; nextLine < endLine; nextLine++) {
|
|
108
|
+
const pos = state.bMarks[nextLine] + state.tShift[nextLine];
|
|
109
|
+
const max = state.eMarks[nextLine];
|
|
110
|
+
|
|
111
|
+
if (state.sCount[nextLine] < startIndent) break;
|
|
112
|
+
if (state.sCount[nextLine] === startIndent && state.src.charCodeAt(pos) === 0x3a /* : */) {
|
|
113
|
+
let closePos = pos;
|
|
114
|
+
while (closePos < max && state.src.charCodeAt(closePos) === 0x3a) closePos++;
|
|
115
|
+
if (closePos - pos >= marker.marker.length && state.skipSpaces(closePos) >= max) {
|
|
116
|
+
autoClosed = true;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const oldParent = state.parentType;
|
|
123
|
+
const oldLineMax = state.lineMax;
|
|
124
|
+
const oldBlkIndent = state.blkIndent;
|
|
125
|
+
const oldInTabs = state.env.__gnixInTabs;
|
|
126
|
+
|
|
127
|
+
const open = state.push("gnix_tabs_open", "", 1);
|
|
128
|
+
open.block = true;
|
|
129
|
+
open.markup = marker.marker;
|
|
130
|
+
open.meta = { id: marker.id };
|
|
131
|
+
open.map = [startLine, nextLine];
|
|
132
|
+
|
|
133
|
+
state.parentType = `${name}_container`;
|
|
134
|
+
state.lineMax = nextLine;
|
|
135
|
+
state.blkIndent = startIndent;
|
|
136
|
+
state.env.__gnixInTabs = true;
|
|
137
|
+
state.md.block.tokenize(state, startLine + 1, nextLine);
|
|
138
|
+
state.env.__gnixInTabs = oldInTabs;
|
|
139
|
+
state.parentType = oldParent;
|
|
140
|
+
state.lineMax = oldLineMax;
|
|
141
|
+
state.blkIndent = oldBlkIndent;
|
|
142
|
+
|
|
143
|
+
const close = state.push("gnix_tabs_close", "", -1);
|
|
144
|
+
close.block = true;
|
|
145
|
+
close.markup = marker.marker;
|
|
146
|
+
|
|
147
|
+
state.line = nextLine + (autoClosed ? 1 : 0);
|
|
148
|
+
return true;
|
|
149
|
+
},
|
|
150
|
+
{ alt: ["paragraph", "reference", "blockquote", "list"] },
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
md.block.ruler.before(
|
|
154
|
+
"paragraph",
|
|
155
|
+
`${name}_tab`,
|
|
156
|
+
(state, startLine, endLine, silent) => {
|
|
157
|
+
if (!state.env.__gnixInTabs) return false;
|
|
158
|
+
|
|
159
|
+
const marker = parseTabMarker(state, startLine);
|
|
160
|
+
if (!marker) return false;
|
|
161
|
+
if (silent) return true;
|
|
162
|
+
|
|
163
|
+
const startIndent = state.sCount[startLine];
|
|
164
|
+
let nextLine = startLine + 1;
|
|
165
|
+
|
|
166
|
+
for (; nextLine < endLine; nextLine++) {
|
|
167
|
+
const pos = state.bMarks[nextLine] + state.tShift[nextLine];
|
|
168
|
+
if (state.sCount[nextLine] === startIndent && state.src.charCodeAt(pos) === 0x40 /* @ */ && parseTabMarker(state, nextLine)) break;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const oldParent = state.parentType;
|
|
172
|
+
const oldLineMax = state.lineMax;
|
|
173
|
+
const oldBlkIndent = state.blkIndent;
|
|
174
|
+
|
|
175
|
+
const open = state.push("gnix_tab_open", "", 1);
|
|
176
|
+
open.block = true;
|
|
177
|
+
open.markup = "@tab";
|
|
178
|
+
open.meta = marker;
|
|
179
|
+
open.map = [startLine, nextLine];
|
|
180
|
+
|
|
181
|
+
state.parentType = "tab";
|
|
182
|
+
state.lineMax = nextLine;
|
|
183
|
+
state.blkIndent = startIndent;
|
|
184
|
+
state.md.block.tokenize(state, startLine + 1, nextLine);
|
|
185
|
+
state.parentType = oldParent;
|
|
186
|
+
state.lineMax = oldLineMax;
|
|
187
|
+
state.blkIndent = oldBlkIndent;
|
|
188
|
+
|
|
189
|
+
const close = state.push("gnix_tab_close", "", -1);
|
|
190
|
+
close.block = true;
|
|
191
|
+
|
|
192
|
+
state.line = nextLine;
|
|
193
|
+
return true;
|
|
194
|
+
},
|
|
195
|
+
{ alt: ["paragraph", "reference", "blockquote", "list"] },
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
md.renderer.rules.gnix_tabs_open = (tokens, idx) => {
|
|
199
|
+
const { id } = tokens[idx].meta || {};
|
|
200
|
+
return `<x-tabs${id ? ` group-id="${md.utils.escapeHtml(id)}"` : ""}>\n`;
|
|
201
|
+
};
|
|
202
|
+
md.renderer.rules.gnix_tabs_close = () => "</x-tabs>\n";
|
|
203
|
+
md.renderer.rules.gnix_tab_open = (tokens, idx) => {
|
|
204
|
+
const { title = "Tab", id = "", active = false } = tokens[idx].meta || {};
|
|
205
|
+
return `<x-tab title="${md.utils.escapeHtml(title)}"${id ? ` sync-id="${md.utils.escapeHtml(id)}"` : ""}${active ? " active" : ""}>\n`;
|
|
206
|
+
};
|
|
207
|
+
md.renderer.rules.gnix_tab_close = () => "</x-tab>\n";
|
|
208
|
+
|
|
209
|
+
return md;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
class MarkdownRenderer {
|
|
213
|
+
constructor(hexo) {
|
|
214
|
+
this.hexo = hexo;
|
|
215
|
+
this.config = {
|
|
216
|
+
render_options: {
|
|
217
|
+
breaks: true,
|
|
218
|
+
html: true,
|
|
219
|
+
langPrefix: "language-",
|
|
220
|
+
linkify: true,
|
|
221
|
+
quotes: "“”‘’",
|
|
222
|
+
typographer: true,
|
|
223
|
+
xhtmlOut: false,
|
|
224
|
+
},
|
|
225
|
+
code_options: {
|
|
226
|
+
themes: {
|
|
227
|
+
light: "catppuccin-latte",
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
mermaid_options: {
|
|
231
|
+
theme: "default",
|
|
232
|
+
},
|
|
233
|
+
...(hexo.config.markdown_exit || {}),
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
this.md = createMarkdownExit(this.config.render_options);
|
|
237
|
+
this.initPlugins();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
initPlugins() {
|
|
241
|
+
console.time("MarkdownExit: Load Default Plugins");
|
|
242
|
+
if (this.config.defaultPlugins !== false) {
|
|
243
|
+
this.md
|
|
244
|
+
.use(resolveDefault(footnote))
|
|
245
|
+
.use(resolveDefault(mark))
|
|
246
|
+
.use(resolveDefault(sub))
|
|
247
|
+
.use(resolveDefault(sup))
|
|
248
|
+
.use(resolveDefault(abbr))
|
|
249
|
+
.use(resolveDefault(ins))
|
|
250
|
+
.use(resolveDefault(taskLists))
|
|
251
|
+
.use(resolveDefault(code), this.config.code_options)
|
|
252
|
+
.use(resolveDefault(mermaidDiagram), this.config.mermaid_options)
|
|
253
|
+
.use(resolveDefault(ratex), this.config.ratex_options)
|
|
254
|
+
.use(customTabs)
|
|
255
|
+
.use(wrapMarkdownItTable)
|
|
256
|
+
.use(resolveDefault(anchor), {
|
|
257
|
+
permalink: resolveDefault(anchor).permalink.headerLink(),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
console.timeEnd("MarkdownExit: Load Default Plugins");
|
|
261
|
+
|
|
262
|
+
console.time("MarkdownExit: Load User Plugins");
|
|
263
|
+
this.loadUserPlugins();
|
|
264
|
+
console.timeEnd("MarkdownExit: Load User Plugins");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
resolvePluginFunction(plugin) {
|
|
268
|
+
if (plugin && typeof plugin.default === "function") return plugin.default;
|
|
269
|
+
if (typeof plugin === "function") return plugin;
|
|
270
|
+
|
|
271
|
+
if (plugin && typeof plugin === "object") {
|
|
272
|
+
for (const key in plugin) {
|
|
273
|
+
if (typeof plugin[key] === "function") return plugin[key];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return plugin;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
loadUserPlugins() {
|
|
281
|
+
const plugins = this.config.plugins || [];
|
|
282
|
+
for (const pluginConfig of plugins) {
|
|
283
|
+
const isString = typeof pluginConfig === "string";
|
|
284
|
+
const pluginName = isString ? pluginConfig : pluginConfig.name;
|
|
285
|
+
const pluginOptions = isString ? {} : pluginConfig.options || {};
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
const pluginPath = path.join(this.hexo.base_dir, "node_modules", pluginName);
|
|
289
|
+
const plugin = require(pluginPath);
|
|
290
|
+
const pluginFn = this.resolvePluginFunction(plugin);
|
|
291
|
+
|
|
292
|
+
this.md.use(pluginFn, pluginOptions);
|
|
293
|
+
if (process.env.DEBUG) {
|
|
294
|
+
console.log(`Successfully loaded plugin: ${pluginName}`);
|
|
295
|
+
}
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.warn(`Failed to load plugin: ${pluginName}`);
|
|
298
|
+
if (process.env.DEBUG) {
|
|
299
|
+
console.warn(` Error: ${error}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async render(data) {
|
|
306
|
+
if (!data.text) return "";
|
|
307
|
+
return this.md.renderAsync(data.text);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const markdownRendererInstances = new WeakMap();
|
|
312
|
+
|
|
313
|
+
function getMarkdownRenderer(hexo) {
|
|
314
|
+
if (!markdownRendererInstances.has(hexo)) {
|
|
315
|
+
markdownRendererInstances.set(hexo, new MarkdownRenderer(hexo));
|
|
316
|
+
}
|
|
317
|
+
return markdownRendererInstances.get(hexo);
|
|
318
|
+
}
|
|
319
|
+
|
|
1
320
|
const { transformSync } = require("esbuild");
|
|
2
321
|
const { createElement } = require("inferno-create-element");
|
|
3
322
|
const { renderToStaticMarkup } = require("inferno-server");
|
|
@@ -50,5 +369,10 @@ function renderer(data, locals) {
|
|
|
50
369
|
renderer.compile = compile;
|
|
51
370
|
|
|
52
371
|
module.exports = (hexo) => {
|
|
372
|
+
const markdownConfig = hexo.config.markdown_exit || {};
|
|
373
|
+
const markdownRenderer = async (data) => getMarkdownRenderer(hexo).render(data);
|
|
374
|
+
markdownRenderer.disableNunjucks = Boolean(markdownConfig.disableNunjucks);
|
|
375
|
+
|
|
376
|
+
hexo.extend.renderer.register("md", "html", markdownRenderer);
|
|
53
377
|
hexo.extend.renderer.register("jsx", "html", renderer, true);
|
|
54
378
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hexo-theme-gnix",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "12.0.0",
|
|
4
4
|
"author": "Efterklang <gaojiaxing0220@gmail.com> (https://vluv.space)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Second generation of Hexo theme Icarus, now with Catppuccin flavor and night mode support.",
|
|
@@ -43,6 +43,18 @@
|
|
|
43
43
|
"inferno-create-element": "^9.1.0",
|
|
44
44
|
"inferno-server": "^9.1.0",
|
|
45
45
|
"js-yaml": "^4.1.1",
|
|
46
|
+
"markdown-exit": "^1.0.0-beta.9",
|
|
47
|
+
"markdown-exit-mermaid": "^2.2.3",
|
|
48
|
+
"markdown-exit-ratex": "^0.2.2",
|
|
49
|
+
"markdown-exit-shiki": "^0.3.2",
|
|
50
|
+
"markdown-it-abbr": "^2.0.0",
|
|
51
|
+
"markdown-it-anchor": "^9.2.0",
|
|
52
|
+
"markdown-it-footnote": "^4.0.0",
|
|
53
|
+
"markdown-it-ins": "^4.0.0",
|
|
54
|
+
"markdown-it-mark": "^4.0.0",
|
|
55
|
+
"markdown-it-sub": "^2.0.0",
|
|
56
|
+
"markdown-it-sup": "^2.0.0",
|
|
57
|
+
"markdown-it-task-lists": "^2.1.1",
|
|
46
58
|
"swup": "^4.8.3"
|
|
47
59
|
},
|
|
48
60
|
"peerDependencies": {
|
package/source/css/default.css
CHANGED
|
@@ -688,56 +688,6 @@ html {
|
|
|
688
688
|
|
|
689
689
|
/* #endregion Pagination */
|
|
690
690
|
|
|
691
|
-
/* #region Tabs */
|
|
692
|
-
|
|
693
|
-
.tabs-tabs-wrapper {
|
|
694
|
-
overflow: hidden;
|
|
695
|
-
margin: 10px auto;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
.tabs-tabs-header {
|
|
699
|
-
padding: 0 0.5rem;
|
|
700
|
-
white-space: nowrap;
|
|
701
|
-
overflow-x: auto;
|
|
702
|
-
overflow-y: hidden;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
.tabs-tab-button {
|
|
706
|
-
padding: 0.5em 1rem;
|
|
707
|
-
border: none;
|
|
708
|
-
border-bottom: 2px solid transparent;
|
|
709
|
-
background: 0 0;
|
|
710
|
-
font-size: 1em;
|
|
711
|
-
cursor: pointer;
|
|
712
|
-
position: relative;
|
|
713
|
-
transition: color 0.3s ease;
|
|
714
|
-
outline: 0;
|
|
715
|
-
color: var(--subtext0);
|
|
716
|
-
flex: 0 0 auto;
|
|
717
|
-
white-space: nowrap;
|
|
718
|
-
|
|
719
|
-
&:hover,
|
|
720
|
-
&.active {
|
|
721
|
-
color: var(--text);
|
|
722
|
-
border-color: var(--text);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
.tabs-tabs-container {
|
|
727
|
-
padding: 0.8em 0 10px 0;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
.tabs-tab-content {
|
|
731
|
-
display: none;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
.tabs-tab-content.active,
|
|
735
|
-
.tabs-tab-content[data-active] {
|
|
736
|
-
display: block;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
/* #endregion Tabs */
|
|
740
|
-
|
|
741
691
|
.section {
|
|
742
692
|
padding: 3rem 1.5rem;
|
|
743
693
|
flex-grow: 1;
|
|
@@ -940,8 +890,7 @@ html {
|
|
|
940
890
|
margin-bottom: 0.5rem;
|
|
941
891
|
}
|
|
942
892
|
|
|
943
|
-
p:last-child
|
|
944
|
-
.tabs-tabs-wrapper:last-child {
|
|
893
|
+
p:last-child {
|
|
945
894
|
margin-bottom: 0;
|
|
946
895
|
}
|
|
947
896
|
|
|
@@ -1157,13 +1106,19 @@ article.card-content .content :is(h1, h2, h3, h4, h5, h6, strong, b, th) {
|
|
|
1157
1106
|
font-size: 0.9em;
|
|
1158
1107
|
}
|
|
1159
1108
|
|
|
1160
|
-
table {
|
|
1161
|
-
width: 100%;
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1109
|
+
.table-wrapper {
|
|
1110
|
+
max-width: 100%;
|
|
1111
|
+
margin: 1.5em 0;
|
|
1112
|
+
overflow-x: auto;
|
|
1113
|
+
|
|
1114
|
+
table {
|
|
1115
|
+
min-width: max-content;
|
|
1116
|
+
width: 100%;
|
|
1117
|
+
border-collapse: collapse;
|
|
1118
|
+
border-spacing: 0;
|
|
1119
|
+
overflow: auto;
|
|
1120
|
+
margin: 1rem 0;
|
|
1121
|
+
}
|
|
1167
1122
|
}
|
|
1168
1123
|
|
|
1169
1124
|
td,
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tabs Custom Element
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* <x-tabs>
|
|
6
|
+
* <x-tab title="JavaScript" sync-id="js" active>
|
|
7
|
+
* Content here...
|
|
8
|
+
* </x-tab>
|
|
9
|
+
* <x-tab title="CSS" sync-id="css">
|
|
10
|
+
* More content...
|
|
11
|
+
* </x-tab>
|
|
12
|
+
* </x-tabs>
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
let styleSheetInjected = false;
|
|
16
|
+
let tabsCounter = 0;
|
|
17
|
+
|
|
18
|
+
function escapeHtml(value) {
|
|
19
|
+
const span = document.createElement("span");
|
|
20
|
+
span.textContent = value;
|
|
21
|
+
return span.innerHTML;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class Tabs extends HTMLElement {
|
|
25
|
+
connectedCallback() {
|
|
26
|
+
this.injectStyles();
|
|
27
|
+
this.render();
|
|
28
|
+
if (!this.hasAttribute("data-initialized")) {
|
|
29
|
+
this.setupListeners();
|
|
30
|
+
this.setAttribute("data-initialized", "true");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
injectStyles() {
|
|
35
|
+
if (styleSheetInjected) return;
|
|
36
|
+
|
|
37
|
+
const style = `
|
|
38
|
+
x-tabs {
|
|
39
|
+
display: block;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
margin: var(--tabs-margin, 10px auto);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.x-tabs-header {
|
|
45
|
+
display: flex;
|
|
46
|
+
gap: var(--tabs-header-gap, 0);
|
|
47
|
+
padding: var(--tabs-header-padding, 0 0.5rem);
|
|
48
|
+
white-space: nowrap;
|
|
49
|
+
overflow-x: auto;
|
|
50
|
+
overflow-y: hidden;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.x-tabs-tab {
|
|
54
|
+
flex: 0 0 auto;
|
|
55
|
+
padding: var(--tabs-tab-padding, 0.5em 1rem);
|
|
56
|
+
border: none;
|
|
57
|
+
border-bottom: var(--tabs-tab-border-width, 2px) solid transparent;
|
|
58
|
+
background: transparent;
|
|
59
|
+
color: var(--tabs-tab-color, var(--subtext0));
|
|
60
|
+
font: inherit;
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
position: relative;
|
|
63
|
+
transition:
|
|
64
|
+
color 0.3s ease,
|
|
65
|
+
border-color 0.3s ease;
|
|
66
|
+
outline: 0;
|
|
67
|
+
white-space: nowrap;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.x-tabs-tab:hover,
|
|
71
|
+
.x-tabs-tab[aria-selected="true"] {
|
|
72
|
+
color: var(--tabs-tab-active-color, var(--text));
|
|
73
|
+
border-color: var(--tabs-tab-active-border-color, var(--text));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.x-tabs-tab:focus-visible {
|
|
77
|
+
outline: 2px solid var(--tabs-focus-color, var(--text));
|
|
78
|
+
outline-offset: 2px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.x-tabs-panels {
|
|
82
|
+
padding: var(--tabs-panel-padding, 0.8em 0 10px 0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.x-tabs-panel[hidden] {
|
|
86
|
+
display: none;
|
|
87
|
+
}
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
const styleEl = document.createElement("style");
|
|
91
|
+
styleEl.textContent = style;
|
|
92
|
+
document.head.appendChild(styleEl);
|
|
93
|
+
styleSheetInjected = true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
render() {
|
|
97
|
+
const tabs = Array.from(this.children).filter((child) => child.tagName.toLowerCase() === "x-tab");
|
|
98
|
+
if (tabs.length === 0) return;
|
|
99
|
+
|
|
100
|
+
const instanceId = tabsCounter++;
|
|
101
|
+
const activeIndex = Math.max(
|
|
102
|
+
0,
|
|
103
|
+
tabs.findIndex((tab) => tab.hasAttribute("active")),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const headers = tabs
|
|
107
|
+
.map((tab, index) => {
|
|
108
|
+
const title = tab.getAttribute("title") || `Tab ${index + 1}`;
|
|
109
|
+
const syncId = tab.getAttribute("sync-id") || "";
|
|
110
|
+
const selected = index === activeIndex;
|
|
111
|
+
|
|
112
|
+
return `
|
|
113
|
+
<button
|
|
114
|
+
class="x-tabs-tab"
|
|
115
|
+
type="button"
|
|
116
|
+
role="tab"
|
|
117
|
+
id="x-tabs-${instanceId}-tab-${index}"
|
|
118
|
+
aria-controls="x-tabs-${instanceId}-panel-${index}"
|
|
119
|
+
aria-selected="${selected}"
|
|
120
|
+
tabindex="${selected ? "0" : "-1"}"
|
|
121
|
+
data-index="${index}"
|
|
122
|
+
${syncId ? `data-sync-id="${escapeHtml(syncId)}"` : ""}
|
|
123
|
+
>${escapeHtml(title)}</button>
|
|
124
|
+
`;
|
|
125
|
+
})
|
|
126
|
+
.join("");
|
|
127
|
+
|
|
128
|
+
const panels = tabs
|
|
129
|
+
.map((tab, index) => {
|
|
130
|
+
const content = Array.from(tab.childNodes)
|
|
131
|
+
.map((node) => (node.nodeType === Node.TEXT_NODE ? node.textContent : node.outerHTML))
|
|
132
|
+
.join("");
|
|
133
|
+
const selected = index === activeIndex;
|
|
134
|
+
|
|
135
|
+
return `
|
|
136
|
+
<div
|
|
137
|
+
class="x-tabs-panel content"
|
|
138
|
+
role="tabpanel"
|
|
139
|
+
id="x-tabs-${instanceId}-panel-${index}"
|
|
140
|
+
aria-labelledby="x-tabs-${instanceId}-tab-${index}"
|
|
141
|
+
data-index="${index}"
|
|
142
|
+
${selected ? "" : "hidden"}
|
|
143
|
+
>${content}</div>
|
|
144
|
+
`;
|
|
145
|
+
})
|
|
146
|
+
.join("");
|
|
147
|
+
|
|
148
|
+
this.innerHTML = `
|
|
149
|
+
<div class="x-tabs-header" role="tablist">
|
|
150
|
+
${headers}
|
|
151
|
+
</div>
|
|
152
|
+
<div class="x-tabs-panels">
|
|
153
|
+
${panels}
|
|
154
|
+
</div>
|
|
155
|
+
`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
setupListeners() {
|
|
159
|
+
this.addEventListener("click", (event) => {
|
|
160
|
+
const target = event.target instanceof Element ? event.target : event.target.parentElement;
|
|
161
|
+
const button = target?.closest(".x-tabs-tab");
|
|
162
|
+
if (!button || !this.contains(button)) return;
|
|
163
|
+
|
|
164
|
+
this.activateTab(Number(button.dataset.index));
|
|
165
|
+
this.syncRelatedTabs(button.dataset.syncId);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
this.addEventListener("keydown", (event) => {
|
|
169
|
+
const target = event.target instanceof Element ? event.target : event.target.parentElement;
|
|
170
|
+
const button = target?.closest(".x-tabs-tab");
|
|
171
|
+
if (!button || !this.contains(button)) return;
|
|
172
|
+
|
|
173
|
+
const buttons = this.tabButtons;
|
|
174
|
+
const currentIndex = buttons.indexOf(button);
|
|
175
|
+
let nextIndex = currentIndex;
|
|
176
|
+
|
|
177
|
+
switch (event.key) {
|
|
178
|
+
case "ArrowLeft":
|
|
179
|
+
case "ArrowUp":
|
|
180
|
+
nextIndex = (currentIndex - 1 + buttons.length) % buttons.length;
|
|
181
|
+
break;
|
|
182
|
+
case "ArrowRight":
|
|
183
|
+
case "ArrowDown":
|
|
184
|
+
nextIndex = (currentIndex + 1) % buttons.length;
|
|
185
|
+
break;
|
|
186
|
+
case "Home":
|
|
187
|
+
nextIndex = 0;
|
|
188
|
+
break;
|
|
189
|
+
case "End":
|
|
190
|
+
nextIndex = buttons.length - 1;
|
|
191
|
+
break;
|
|
192
|
+
default:
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
event.preventDefault();
|
|
197
|
+
buttons[nextIndex]?.focus();
|
|
198
|
+
this.activateTab(nextIndex);
|
|
199
|
+
this.syncRelatedTabs(buttons[nextIndex]?.dataset.syncId);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
get tabButtons() {
|
|
204
|
+
return Array.from(this.querySelectorAll(".x-tabs-tab"));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
activateTab(index) {
|
|
208
|
+
if (!Number.isInteger(index)) return;
|
|
209
|
+
|
|
210
|
+
const buttons = this.tabButtons;
|
|
211
|
+
const panels = Array.from(this.querySelectorAll(".x-tabs-panel"));
|
|
212
|
+
|
|
213
|
+
buttons.forEach((button, buttonIndex) => {
|
|
214
|
+
const selected = buttonIndex === index;
|
|
215
|
+
button.setAttribute("aria-selected", String(selected));
|
|
216
|
+
button.tabIndex = selected ? 0 : -1;
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
panels.forEach((panel, panelIndex) => {
|
|
220
|
+
panel.hidden = panelIndex !== index;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
syncRelatedTabs(syncId) {
|
|
225
|
+
if (!syncId) return;
|
|
226
|
+
|
|
227
|
+
document.querySelectorAll(".x-tabs-tab[data-sync-id]").forEach((button) => {
|
|
228
|
+
if (button.dataset.syncId !== syncId) return;
|
|
229
|
+
|
|
230
|
+
const tabs = button.closest("x-tabs");
|
|
231
|
+
if (!tabs || tabs === this) return;
|
|
232
|
+
tabs.activateTab(Number(button.dataset.index));
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
class Tab extends HTMLElement {}
|
|
238
|
+
|
|
239
|
+
if (!customElements.get("x-tabs")) customElements.define("x-tabs", Tabs);
|
|
240
|
+
if (!customElements.get("x-tab")) customElements.define("x-tab", Tab);
|
|
241
|
+
|
|
242
|
+
export { Tab, Tabs };
|
package/source/js/main.js
CHANGED
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
function tableWrapFix() {
|
|
2
|
-
document.querySelectorAll(".content table").forEach((table) => {
|
|
3
|
-
if (table.hasAttribute("data-nowrap") || table.parentElement.classList.contains("table-wrapper")) {
|
|
4
|
-
return;
|
|
5
|
-
}
|
|
6
|
-
// if width exceeds container, wrap it
|
|
7
|
-
const wrapper = document.createElement("div");
|
|
8
|
-
Object.assign(wrapper.style, {
|
|
9
|
-
width: "100%",
|
|
10
|
-
overflowX: "auto",
|
|
11
|
-
});
|
|
12
|
-
table.parentNode.insertBefore(wrapper, table);
|
|
13
|
-
wrapper.appendChild(table);
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
|
|
17
1
|
function twikoo_handler() {
|
|
18
2
|
const el = document.getElementById("tko");
|
|
19
3
|
if (!el) return;
|
|
@@ -42,87 +26,6 @@ function twikoo_handler() {
|
|
|
42
26
|
delete el.dataset.initializing;
|
|
43
27
|
});
|
|
44
28
|
}
|
|
45
|
-
// #region mdit@tab-plugin
|
|
46
|
-
/**
|
|
47
|
-
* 初始化页面上所有的 Tab 组件
|
|
48
|
-
*/
|
|
49
|
-
|
|
50
|
-
function initializeTabs() {
|
|
51
|
-
document.querySelectorAll(".tabs-tabs-wrapper").forEach((container) => {
|
|
52
|
-
const buttons = container.querySelectorAll(".tabs-tab-button");
|
|
53
|
-
buttons.forEach((button) => {
|
|
54
|
-
button.removeEventListener("click", handleTabClick);
|
|
55
|
-
button.addEventListener("click", handleTabClick);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function handleTabClick() {
|
|
61
|
-
const tabContainer = this.closest(".tabs-tabs-wrapper");
|
|
62
|
-
const targetIndex = this.getAttribute("data-tab");
|
|
63
|
-
const syncId = this.getAttribute("data-id");
|
|
64
|
-
activateTab(tabContainer, targetIndex);
|
|
65
|
-
if (syncId) {
|
|
66
|
-
syncRelatedTabs(syncId);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* 激活指定容器中的特定 Tab
|
|
72
|
-
* @param {HTMLElement} container - Tab 容器元素
|
|
73
|
-
* @param {string} targetIndex - 要激活的 Tab 的 data-tab 值
|
|
74
|
-
*/
|
|
75
|
-
function activateTab(container, targetIndex) {
|
|
76
|
-
// 先重置该容器内所有 Tab 的状态
|
|
77
|
-
resetTabsState(container);
|
|
78
|
-
|
|
79
|
-
const buttonToActivate = container.querySelector(`.tabs-tab-button[data-tab="${targetIndex}"]`);
|
|
80
|
-
const contentToActivate = container.querySelector(`.tabs-tab-content[data-index="${targetIndex}"]`);
|
|
81
|
-
|
|
82
|
-
if (buttonToActivate) {
|
|
83
|
-
buttonToActivate.classList.add("active");
|
|
84
|
-
buttonToActivate.setAttribute("data-active", "");
|
|
85
|
-
}
|
|
86
|
-
if (contentToActivate) {
|
|
87
|
-
contentToActivate.classList.add("active");
|
|
88
|
-
contentToActivate.setAttribute("data-active", "");
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* 重置指定容器内所有 Tab 按钮和内容面板的状态
|
|
94
|
-
* @param {HTMLElement} container - Tab 容器元素
|
|
95
|
-
*/
|
|
96
|
-
function resetTabsState(container) {
|
|
97
|
-
const buttons = container.querySelectorAll(".tabs-tab-button");
|
|
98
|
-
const contents = container.querySelectorAll(".tabs-tab-content");
|
|
99
|
-
|
|
100
|
-
buttons.forEach((btn) => {
|
|
101
|
-
btn.classList.remove("active");
|
|
102
|
-
btn.removeAttribute("data-active");
|
|
103
|
-
});
|
|
104
|
-
contents.forEach((content) => {
|
|
105
|
-
content.classList.remove("active");
|
|
106
|
-
content.removeAttribute("data-active");
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* 同步所有具有相同 data-id 的关联 Tab
|
|
112
|
-
* @param {string} syncId - 用于同步的 data-id
|
|
113
|
-
*/
|
|
114
|
-
function syncRelatedTabs(syncId) {
|
|
115
|
-
const relatedButtons = document.querySelectorAll(`.tabs-tab-button[data-id="${syncId}"]`);
|
|
116
|
-
|
|
117
|
-
relatedButtons.forEach((button) => {
|
|
118
|
-
const container = button.closest(".tabs-tabs-wrapper");
|
|
119
|
-
const targetIndex = button.getAttribute("data-tab");
|
|
120
|
-
activateTab(container, targetIndex);
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// #endregion
|
|
125
|
-
|
|
126
29
|
// #region markdown-exit shiki
|
|
127
30
|
const SELECTORS = {
|
|
128
31
|
figure: "figure.shiki",
|
|
@@ -649,7 +552,7 @@ function initArticleCommentPopover() {
|
|
|
649
552
|
|
|
650
553
|
// Preload twikoo JS during idle time so comments render faster on click
|
|
651
554
|
const tko = document.getElementById("tko");
|
|
652
|
-
if (tko
|
|
555
|
+
if (tko?.dataset.jsUrl) {
|
|
653
556
|
const preload = () => loadScriptOnce(tko.dataset.jsUrl, () => {});
|
|
654
557
|
if (typeof requestIdleCallback === "function") {
|
|
655
558
|
requestIdleCallback(preload, { timeout: 3000 });
|
|
@@ -703,8 +606,6 @@ function refreshNavbarIcons() {
|
|
|
703
606
|
}
|
|
704
607
|
|
|
705
608
|
function initPage() {
|
|
706
|
-
tableWrapFix();
|
|
707
|
-
initializeTabs();
|
|
708
609
|
handleMermaid();
|
|
709
610
|
addHighlightTool();
|
|
710
611
|
initArticleSettings();
|