hexo-theme-linen 1.0.33 → 1.0.35
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/package.json +1 -1
- package/source/linen-theme/js/toc.js +24 -31
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
12
12
|
);
|
|
13
13
|
if (headerElements.length === 0) return;
|
|
14
14
|
|
|
15
|
-
// 1.
|
|
15
|
+
// 1. Ensure every heading has a valid ID, filling in missing IDs in the DOM.
|
|
16
16
|
headerElements.forEach((header, index) => {
|
|
17
17
|
if (!header.id) {
|
|
18
18
|
const text = (header.textContent || "").trim();
|
|
@@ -21,7 +21,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
21
21
|
}
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
// 2.
|
|
24
|
+
// 2. Parse the TOC DOM and build lookup maps.
|
|
25
25
|
const tocItemWraps = Array.from(
|
|
26
26
|
tocContainer.querySelectorAll(".toc-item-wrap"),
|
|
27
27
|
);
|
|
@@ -51,7 +51,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
51
51
|
});
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
// 3.
|
|
54
|
+
// 3. Build the ordered list of content headings.
|
|
55
55
|
let currentH2Id = null;
|
|
56
56
|
const headers = headerElements.map((header) => {
|
|
57
57
|
const isH2 = header.tagName === "H2";
|
|
@@ -66,13 +66,13 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
66
66
|
};
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
//
|
|
69
|
+
// Offset used to account for the fixed top navigation bar.
|
|
70
70
|
const TOP_OFFSET = 80;
|
|
71
71
|
|
|
72
72
|
let lastActiveH2Id = null;
|
|
73
73
|
let lastActiveH3Id = null;
|
|
74
74
|
|
|
75
|
-
//
|
|
75
|
+
// Keep the active TOC item visible without affecting the page scroll position.
|
|
76
76
|
function scrollIntoTocView(element) {
|
|
77
77
|
if (!element || !tocContainer) return;
|
|
78
78
|
const containerRect = tocContainer.getBoundingClientRect();
|
|
@@ -85,30 +85,23 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
// 5.
|
|
88
|
+
// 5. Determine the currently active H2 / H3.
|
|
89
89
|
function determineActive() {
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
return
|
|
99
|
-
h2Id:
|
|
100
|
-
h3Id:
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
if (h3ToH2Map.has(currentAnchoringId)) {
|
|
104
|
-
return {
|
|
105
|
-
h2Id: h3ToH2Map.get(currentAnchoringId),
|
|
106
|
-
h3Id: currentAnchoringId,
|
|
107
|
-
};
|
|
90
|
+
// At the bottom of the page, the last heading may not reach the top trigger
|
|
91
|
+
// line. In that case, the last heading represents the current section.
|
|
92
|
+
const scrollElement = document.scrollingElement || document.documentElement;
|
|
93
|
+
const isAtBottom =
|
|
94
|
+
window.scrollY + window.innerHeight >= scrollElement.scrollHeight - 1;
|
|
95
|
+
if (isAtBottom) {
|
|
96
|
+
const lastHeader = headers[headers.length - 1];
|
|
97
|
+
if (lastHeader) {
|
|
98
|
+
return lastHeader.isH2
|
|
99
|
+
? { h2Id: lastHeader.id, h3Id: null }
|
|
100
|
+
: { h2Id: lastHeader.h2Id, h3Id: lastHeader.id };
|
|
108
101
|
}
|
|
109
102
|
}
|
|
110
103
|
|
|
111
|
-
//
|
|
104
|
+
// Find the last heading above the viewport trigger line (top <= TOP_OFFSET).
|
|
112
105
|
let activeHeading = null;
|
|
113
106
|
for (let i = 0; i < headers.length; i++) {
|
|
114
107
|
const header = headers[i];
|
|
@@ -120,7 +113,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
120
113
|
}
|
|
121
114
|
}
|
|
122
115
|
|
|
123
|
-
//
|
|
116
|
+
// If no heading has reached the trigger line, activate the first H2.
|
|
124
117
|
if (!activeHeading) {
|
|
125
118
|
const firstH2 = headers.find((h) => h.isH2);
|
|
126
119
|
return {
|
|
@@ -142,7 +135,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
142
135
|
};
|
|
143
136
|
}
|
|
144
137
|
|
|
145
|
-
// 6.
|
|
138
|
+
// 6. Apply the active state to the TOC DOM.
|
|
146
139
|
function updateActive() {
|
|
147
140
|
const { h2Id, h3Id } = determineActive();
|
|
148
141
|
|
|
@@ -164,7 +157,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
164
157
|
item.wrap.classList.remove("active");
|
|
165
158
|
}
|
|
166
159
|
|
|
167
|
-
//
|
|
160
|
+
// Match H3 items only within the active H2 to prevent cross-section residue.
|
|
168
161
|
item.subMap.forEach((subLink, currentH3Id) => {
|
|
169
162
|
if (isH2Active && currentH3Id === h3Id) {
|
|
170
163
|
subLink.classList.add("active");
|
|
@@ -180,7 +173,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
180
173
|
}
|
|
181
174
|
}
|
|
182
175
|
|
|
183
|
-
// 7.
|
|
176
|
+
// 7. Throttle scroll updates with requestAnimationFrame.
|
|
184
177
|
let isTicking = false;
|
|
185
178
|
function requestUpdate() {
|
|
186
179
|
if (isTicking) return;
|
|
@@ -191,7 +184,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
191
184
|
});
|
|
192
185
|
}
|
|
193
186
|
|
|
194
|
-
// 8.
|
|
187
|
+
// 8. Listen for viewport changes and scrolling.
|
|
195
188
|
window.addEventListener("scroll", requestUpdate, { passive: true });
|
|
196
189
|
window.addEventListener("resize", requestUpdate, { passive: true });
|
|
197
190
|
|
|
@@ -208,6 +201,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
208
201
|
headerElements.forEach((header) => observer.observe(header));
|
|
209
202
|
}
|
|
210
203
|
|
|
211
|
-
//
|
|
204
|
+
// Run once on initialization.
|
|
212
205
|
updateActive();
|
|
213
206
|
});
|