hexo-theme-gnix 11.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.
@@ -1,5 +1,4 @@
1
1
  const path = require("node:path");
2
- const { tab } = require("@mdit/plugin-tab");
3
2
  const { createMarkdownExit } = require("markdown-exit");
4
3
  const mermaidDiagram = require("markdown-exit-mermaid");
5
4
  const ratex = require("markdown-exit-ratex");
@@ -17,6 +16,199 @@ function resolveDefault(module) {
17
16
  return module && typeof module === "object" && "default" in module ? module.default : module;
18
17
  }
19
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
+
20
212
  class MarkdownRenderer {
21
213
  constructor(hexo) {
22
214
  this.hexo = hexo;
@@ -59,7 +251,8 @@ class MarkdownRenderer {
59
251
  .use(resolveDefault(code), this.config.code_options)
60
252
  .use(resolveDefault(mermaidDiagram), this.config.mermaid_options)
61
253
  .use(resolveDefault(ratex), this.config.ratex_options)
62
- .use(tab)
254
+ .use(customTabs)
255
+ .use(wrapMarkdownItTable)
63
256
  .use(resolveDefault(anchor), {
64
257
  permalink: resolveDefault(anchor).permalink.headerLink(),
65
258
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hexo-theme-gnix",
3
- "version": "11.0.0",
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.",
@@ -34,7 +34,6 @@
34
34
  "README.md"
35
35
  ],
36
36
  "dependencies": {
37
- "@mdit/plugin-tab": "^0.24.2",
38
37
  "@swup/head-plugin": "^2.3.1",
39
38
  "@swup/scripts-plugin": "^2.1.0",
40
39
  "esbuild": "^0.28.0",
@@ -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
- border-collapse: collapse;
1163
- border-spacing: 0;
1164
- overflow: auto;
1165
- margin: 1rem 0;
1166
- font-size: 1rem;
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 && tko.dataset.jsUrl) {
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();