jekyll-sticky-toc 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +178 -0
- data/assets/jekyll-sticky-toc.css +474 -0
- data/assets/jekyll-sticky-toc.js +768 -0
- data/lib/jekyll/sticky_toc/config.rb +282 -0
- data/lib/jekyll/sticky_toc/heading_extractor.rb +51 -0
- data/lib/jekyll/sticky_toc/heading_sync.rb +39 -0
- data/lib/jekyll/sticky_toc/hook_injector.rb +113 -0
- data/lib/jekyll/sticky_toc/root_style_builder.rb +20 -0
- data/lib/jekyll/sticky_toc/source_heading_extractor.rb +79 -0
- data/lib/jekyll/sticky_toc/templates/toc.html +26 -0
- data/lib/jekyll/sticky_toc/toc_builder.rb +76 -0
- data/lib/jekyll/sticky_toc/toc_list_renderer.rb +149 -0
- data/lib/jekyll/sticky_toc/version.rb +7 -0
- data/lib/jekyll/sticky_toc.rb +12 -0
- data/lib/jekyll-sticky-toc.rb +3 -0
- metadata +103 -0
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
var DEFAULT_BACKGROUND_COLOR = "#ffffff";
|
|
3
|
+
var DEFAULT_TEXT_COLOR = "#111827";
|
|
4
|
+
var DEFAULT_HIGHLIGHT_COLOR = "#2563eb";
|
|
5
|
+
var DEFAULT_BORDER_COLOR = "#e5e7eb";
|
|
6
|
+
var DEFAULT_BORDER_TYPE = "solid";
|
|
7
|
+
var DEFAULT_BORDER_RADIUS = 8;
|
|
8
|
+
var DEFAULT_WIDTH_RATIO = 15;
|
|
9
|
+
var DEFAULT_HEIGHT_RATIO = 50;
|
|
10
|
+
var DEFAULT_VERTICAL_START = 30;
|
|
11
|
+
|
|
12
|
+
function parseConfig() {
|
|
13
|
+
var configNode = document.querySelector("script[data-jtoc-config]");
|
|
14
|
+
if (!configNode) {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(configNode.textContent || "{}");
|
|
20
|
+
} catch (_error) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeHexColor(value, fallback) {
|
|
26
|
+
if (typeof value !== "string") {
|
|
27
|
+
return fallback;
|
|
28
|
+
}
|
|
29
|
+
var raw = value.trim();
|
|
30
|
+
if (!/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(raw)) {
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
|
33
|
+
if (raw.length === 4) {
|
|
34
|
+
return (
|
|
35
|
+
"#" +
|
|
36
|
+
raw.charAt(1) +
|
|
37
|
+
raw.charAt(1) +
|
|
38
|
+
raw.charAt(2) +
|
|
39
|
+
raw.charAt(2) +
|
|
40
|
+
raw.charAt(3) +
|
|
41
|
+
raw.charAt(3)
|
|
42
|
+
).toLowerCase();
|
|
43
|
+
}
|
|
44
|
+
return raw.toLowerCase();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getDirectChildList(item) {
|
|
48
|
+
return item.querySelector(":scope > .jtoc-list");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getDirectToggle(item) {
|
|
52
|
+
return item.querySelector(":scope > .jtoc-link-row > .jtoc-fold-toggle");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getTocScrollContainer(root) {
|
|
56
|
+
return root.querySelector(".jtoc-nav");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeBoolean(value, fallback) {
|
|
60
|
+
if (value === true || value === false) {
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
if (typeof value === "string") {
|
|
64
|
+
if (/^(true|1|yes|on)$/i.test(value)) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
if (/^(false|0|no|off)$/i.test(value)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return fallback;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function normalizeInteger(value, fallback, min, max) {
|
|
75
|
+
var parsed = Number(value);
|
|
76
|
+
if (!Number.isFinite(parsed)) {
|
|
77
|
+
return fallback;
|
|
78
|
+
}
|
|
79
|
+
var rounded = Math.round(parsed);
|
|
80
|
+
if (rounded < min) {
|
|
81
|
+
return min;
|
|
82
|
+
}
|
|
83
|
+
if (rounded > max) {
|
|
84
|
+
return max;
|
|
85
|
+
}
|
|
86
|
+
return rounded;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function normalizeBorderType(value, fallback) {
|
|
90
|
+
if (value === "solid" || value === "none" || value === "dot" || value === "dotted") {
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
return fallback;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resolveCssBorderType(value) {
|
|
97
|
+
if (value === "dot") {
|
|
98
|
+
return "dotted";
|
|
99
|
+
}
|
|
100
|
+
if (value === "none") {
|
|
101
|
+
return "none";
|
|
102
|
+
}
|
|
103
|
+
return "solid";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function hasCssOverride(root, propertyName, defaultValue) {
|
|
107
|
+
var previousInline = root.style.getPropertyValue(propertyName);
|
|
108
|
+
if (previousInline) {
|
|
109
|
+
root.style.removeProperty(propertyName);
|
|
110
|
+
}
|
|
111
|
+
var cssValue = window.getComputedStyle(root).getPropertyValue(propertyName).trim();
|
|
112
|
+
if (previousInline) {
|
|
113
|
+
root.style.setProperty(propertyName, previousInline);
|
|
114
|
+
}
|
|
115
|
+
if (!cssValue) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return cssValue.toLowerCase() !== String(defaultValue).trim().toLowerCase();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function setStyleByPriority(root, propertyName, defaultValue, configValue) {
|
|
122
|
+
if (hasCssOverride(root, propertyName, defaultValue)) {
|
|
123
|
+
root.style.removeProperty(propertyName);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
root.style.setProperty(propertyName, configValue);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function resolveRuntimeStyle(config) {
|
|
130
|
+
var scrollBehavior = config.scroll_behavior;
|
|
131
|
+
if (!scrollBehavior || typeof scrollBehavior !== "object") {
|
|
132
|
+
scrollBehavior = {};
|
|
133
|
+
}
|
|
134
|
+
var borderStyleConfig = config.border_style;
|
|
135
|
+
if (!borderStyleConfig || typeof borderStyleConfig !== "object") {
|
|
136
|
+
borderStyleConfig = {};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
backgroundColor: normalizeHexColor(
|
|
141
|
+
config.background_color,
|
|
142
|
+
DEFAULT_BACKGROUND_COLOR
|
|
143
|
+
),
|
|
144
|
+
textColor: normalizeHexColor(config.text_color, DEFAULT_TEXT_COLOR),
|
|
145
|
+
highlightColor: normalizeHexColor(
|
|
146
|
+
config.highlight_color,
|
|
147
|
+
DEFAULT_HIGHLIGHT_COLOR
|
|
148
|
+
),
|
|
149
|
+
borderColor: normalizeHexColor(borderStyleConfig.color, DEFAULT_BORDER_COLOR),
|
|
150
|
+
borderType: normalizeBorderType(
|
|
151
|
+
borderStyleConfig.type,
|
|
152
|
+
DEFAULT_BORDER_TYPE
|
|
153
|
+
),
|
|
154
|
+
borderRadius: normalizeInteger(borderStyleConfig.radius, DEFAULT_BORDER_RADIUS, 0, 999),
|
|
155
|
+
hideAtBottom: normalizeBoolean(
|
|
156
|
+
config.scroll_behavior !== undefined
|
|
157
|
+
? scrollBehavior.hide_at_bottom
|
|
158
|
+
: config.hide_at_bottom,
|
|
159
|
+
true
|
|
160
|
+
),
|
|
161
|
+
hideAtTop: normalizeBoolean(
|
|
162
|
+
config.scroll_behavior !== undefined
|
|
163
|
+
? scrollBehavior.hide_at_top
|
|
164
|
+
: config.hide_at_top,
|
|
165
|
+
false
|
|
166
|
+
),
|
|
167
|
+
width: normalizeInteger(config.width, DEFAULT_WIDTH_RATIO, 8, 40),
|
|
168
|
+
height: normalizeInteger(config.height, DEFAULT_HEIGHT_RATIO, 20, 90),
|
|
169
|
+
verticalStart: normalizeInteger(
|
|
170
|
+
config.vertical_start,
|
|
171
|
+
DEFAULT_VERTICAL_START,
|
|
172
|
+
0,
|
|
173
|
+
100
|
|
174
|
+
)
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function applyRuntimeStyle(root, runtimeStyle) {
|
|
179
|
+
setStyleByPriority(root, "--jtoc-bg", DEFAULT_BACKGROUND_COLOR, runtimeStyle.backgroundColor);
|
|
180
|
+
setStyleByPriority(root, "--jtoc-text", DEFAULT_TEXT_COLOR, runtimeStyle.textColor);
|
|
181
|
+
setStyleByPriority(root, "--jtoc-highlight", DEFAULT_HIGHLIGHT_COLOR, runtimeStyle.highlightColor);
|
|
182
|
+
setStyleByPriority(root, "--jtoc-border-color", DEFAULT_BORDER_COLOR, runtimeStyle.borderColor);
|
|
183
|
+
setStyleByPriority(
|
|
184
|
+
root,
|
|
185
|
+
"--jtoc-border-style",
|
|
186
|
+
DEFAULT_BORDER_TYPE,
|
|
187
|
+
resolveCssBorderType(runtimeStyle.borderType)
|
|
188
|
+
);
|
|
189
|
+
setStyleByPriority(
|
|
190
|
+
root,
|
|
191
|
+
"--jtoc-radius",
|
|
192
|
+
String(DEFAULT_BORDER_RADIUS) + "px",
|
|
193
|
+
String(runtimeStyle.borderRadius) + "px"
|
|
194
|
+
);
|
|
195
|
+
setStyleByPriority(root, "--jtoc-width-ratio", String(DEFAULT_WIDTH_RATIO), String(runtimeStyle.width));
|
|
196
|
+
setStyleByPriority(root, "--jtoc-height-ratio", String(DEFAULT_HEIGHT_RATIO), String(runtimeStyle.height));
|
|
197
|
+
setStyleByPriority(
|
|
198
|
+
root,
|
|
199
|
+
"--jtoc-panel-vertical-start",
|
|
200
|
+
String(DEFAULT_VERTICAL_START),
|
|
201
|
+
String(runtimeStyle.verticalStart)
|
|
202
|
+
);
|
|
203
|
+
root.dataset.jtocHideAtBottom = runtimeStyle.hideAtBottom ? "true" : "false";
|
|
204
|
+
root.dataset.jtocHideAtTop = runtimeStyle.hideAtTop ? "true" : "false";
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function observeThemeChanges(root, runtimeStyle) {
|
|
208
|
+
function reapply() {
|
|
209
|
+
applyRuntimeStyle(root, runtimeStyle);
|
|
210
|
+
updateEdgeSpacingByScrollbar(root);
|
|
211
|
+
applyPanelMaxHeight(root);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
var observer = new MutationObserver(function () {
|
|
215
|
+
reapply();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
observer.observe(document.documentElement, {
|
|
219
|
+
attributes: true,
|
|
220
|
+
attributeFilter: ["class", "data-theme", "style"]
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (document.body) {
|
|
224
|
+
observer.observe(document.body, {
|
|
225
|
+
attributes: true,
|
|
226
|
+
attributeFilter: ["class", "data-theme", "style"]
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
var darkMedia = window.matchMedia("(prefers-color-scheme: dark)");
|
|
231
|
+
if (darkMedia && typeof darkMedia.addEventListener === "function") {
|
|
232
|
+
darkMedia.addEventListener("change", reapply);
|
|
233
|
+
} else if (darkMedia && typeof darkMedia.addListener === "function") {
|
|
234
|
+
darkMedia.addListener(reapply);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function updateEdgeSpacingByScrollbar(root) {
|
|
239
|
+
var doc = document.documentElement;
|
|
240
|
+
var body = document.body;
|
|
241
|
+
var scrollHeight = Math.max(
|
|
242
|
+
doc ? doc.scrollHeight : 0,
|
|
243
|
+
body ? body.scrollHeight : 0
|
|
244
|
+
);
|
|
245
|
+
var viewportHeight = window.innerHeight || (doc ? doc.clientHeight : 0);
|
|
246
|
+
var hasVerticalScrollbar = scrollHeight > viewportHeight + 1;
|
|
247
|
+
var leftExtra = 0;
|
|
248
|
+
var rightExtra = 0;
|
|
249
|
+
|
|
250
|
+
if (hasVerticalScrollbar && doc) {
|
|
251
|
+
var docRect = doc.getBoundingClientRect();
|
|
252
|
+
var leftInset = Math.max(0, Math.round(docRect.left));
|
|
253
|
+
var rightInset = Math.max(0, Math.round(window.innerWidth - docRect.right));
|
|
254
|
+
var scrollbarOnLeft = leftInset > rightInset;
|
|
255
|
+
if (scrollbarOnLeft) {
|
|
256
|
+
rightExtra = 20;
|
|
257
|
+
} else {
|
|
258
|
+
leftExtra = 20;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
root.style.setProperty("--jtoc-edge-extra-left", leftExtra + "px");
|
|
263
|
+
root.style.setProperty("--jtoc-edge-extra-right", rightExtra + "px");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function readCssCapMaxHeightPx(panel, root) {
|
|
267
|
+
root.style.removeProperty("--jtoc-panel-max-height");
|
|
268
|
+
var v = parseFloat(window.getComputedStyle(panel).maxHeight);
|
|
269
|
+
if (!Number.isFinite(v) || v <= 0) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
return v;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function captureInitialNavContentHeightOnce(root) {
|
|
276
|
+
if (root.dataset.jtocInitialContentPx) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
var scrollEl = getTocScrollContainer(root);
|
|
280
|
+
if (!scrollEl) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
var h = scrollEl.scrollHeight;
|
|
284
|
+
if (h > 0) {
|
|
285
|
+
root.dataset.jtocInitialContentPx = String(Math.ceil(h));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function applyPanelMaxHeight(root) {
|
|
290
|
+
var panel = root.querySelector(".jtoc-panel");
|
|
291
|
+
var scrollEl = getTocScrollContainer(root);
|
|
292
|
+
if (!panel || !scrollEl) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (window.matchMedia("(max-width: 900px)").matches) {
|
|
297
|
+
root.style.removeProperty("--jtoc-panel-max-height");
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
var cssCap = readCssCapMaxHeightPx(panel, root);
|
|
302
|
+
if (!cssCap) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
var initialContentPx = parseFloat(root.dataset.jtocInitialContentPx);
|
|
307
|
+
var hasInitial = Number.isFinite(initialContentPx) && initialContentPx > 0;
|
|
308
|
+
var fixedBase = hasInitial ? Math.min(initialContentPx, cssCap) : cssCap;
|
|
309
|
+
|
|
310
|
+
root.style.setProperty("--jtoc-panel-max-height", fixedBase + "px");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function refreshFoldHeights(root) {
|
|
314
|
+
var items = Array.prototype.slice.call(
|
|
315
|
+
root.querySelectorAll(".jtoc-item.has-children")
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
items.reverse().forEach(function (item) {
|
|
319
|
+
var childList = getDirectChildList(item);
|
|
320
|
+
if (!childList) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
var toggle = getDirectToggle(item);
|
|
325
|
+
var isOpen = item.classList.contains("is-open");
|
|
326
|
+
if (toggle) {
|
|
327
|
+
toggle.setAttribute("aria-expanded", String(isOpen));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (!isOpen) {
|
|
331
|
+
childList.style.maxHeight = "0px";
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
childList.style.maxHeight = "none";
|
|
336
|
+
childList.style.maxHeight = childList.scrollHeight + "px";
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
captureInitialNavContentHeightOnce(root);
|
|
340
|
+
applyPanelMaxHeight(root);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function setupMobileToggle(root) {
|
|
344
|
+
var button = root.querySelector(".jtoc-mobile-toggle");
|
|
345
|
+
if (!button) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
var backdrop = root.querySelector(".jtoc-mobile-backdrop");
|
|
350
|
+
|
|
351
|
+
button.addEventListener("click", function () {
|
|
352
|
+
var isOpen = root.classList.toggle("is-mobile-open");
|
|
353
|
+
button.setAttribute("aria-expanded", String(isOpen));
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
if (backdrop) {
|
|
357
|
+
backdrop.addEventListener("click", function () {
|
|
358
|
+
root.classList.remove("is-mobile-open");
|
|
359
|
+
button.setAttribute("aria-expanded", "false");
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function getWindowScrollTop() {
|
|
365
|
+
if (typeof window.scrollY === "number") {
|
|
366
|
+
return window.scrollY;
|
|
367
|
+
}
|
|
368
|
+
if (typeof window.pageYOffset === "number") {
|
|
369
|
+
return window.pageYOffset;
|
|
370
|
+
}
|
|
371
|
+
var docEl = document.documentElement;
|
|
372
|
+
if (docEl && typeof docEl.scrollTop === "number") {
|
|
373
|
+
return docEl.scrollTop;
|
|
374
|
+
}
|
|
375
|
+
if (document.body && typeof document.body.scrollTop === "number") {
|
|
376
|
+
return document.body.scrollTop;
|
|
377
|
+
}
|
|
378
|
+
return 0;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function setupScrollEdgeVisibility(root) {
|
|
382
|
+
function updateVisibility() {
|
|
383
|
+
var scrollTop = getWindowScrollTop();
|
|
384
|
+
var doc = document.documentElement;
|
|
385
|
+
var body = document.body;
|
|
386
|
+
var scrollHeight = Math.max(
|
|
387
|
+
doc ? doc.scrollHeight : 0,
|
|
388
|
+
body ? body.scrollHeight : 0
|
|
389
|
+
);
|
|
390
|
+
var viewportHeight = window.innerHeight || (doc ? doc.clientHeight : 0);
|
|
391
|
+
var atTop = scrollTop <= 2;
|
|
392
|
+
var atBottom = scrollTop + viewportHeight >= scrollHeight - 1;
|
|
393
|
+
var scrollEdgeBehaviorActive = !window.matchMedia("(max-width: 900px)").matches;
|
|
394
|
+
var hideAtBottom =
|
|
395
|
+
scrollEdgeBehaviorActive && root.dataset.jtocHideAtBottom !== "false";
|
|
396
|
+
var hideAtTop =
|
|
397
|
+
scrollEdgeBehaviorActive && root.dataset.jtocHideAtTop === "true";
|
|
398
|
+
var scrollHidden =
|
|
399
|
+
(hideAtBottom && atBottom) || (hideAtTop && atTop);
|
|
400
|
+
root.classList.toggle("is-scroll-hidden", scrollHidden);
|
|
401
|
+
updateEdgeSpacingByScrollbar(root);
|
|
402
|
+
applyPanelMaxHeight(root);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
updateVisibility();
|
|
406
|
+
window.addEventListener("scroll", updateVisibility, { passive: true });
|
|
407
|
+
window.addEventListener("resize", updateVisibility);
|
|
408
|
+
if (document.readyState === "loading") {
|
|
409
|
+
document.addEventListener("DOMContentLoaded", updateVisibility);
|
|
410
|
+
}
|
|
411
|
+
window.addEventListener("load", updateVisibility);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function openAncestorsFromLink(root, link) {
|
|
415
|
+
var node = link.parentElement;
|
|
416
|
+
var changed = false;
|
|
417
|
+
while (node) {
|
|
418
|
+
if (node.classList && node.classList.contains("jtoc-item")) {
|
|
419
|
+
if (!node.classList.contains("is-open")) {
|
|
420
|
+
node.classList.add("is-open");
|
|
421
|
+
changed = true;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
node = node.parentElement;
|
|
425
|
+
}
|
|
426
|
+
if (changed) {
|
|
427
|
+
refreshFoldHeights(root);
|
|
428
|
+
}
|
|
429
|
+
return changed;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function setupFold(root) {
|
|
433
|
+
if (root.dataset.fold !== "true") {
|
|
434
|
+
captureInitialNavContentHeightOnce(root);
|
|
435
|
+
applyPanelMaxHeight(root);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
var items = root.querySelectorAll(".jtoc-item.has-children");
|
|
440
|
+
var minDisplayLevel = Number.POSITIVE_INFINITY;
|
|
441
|
+
|
|
442
|
+
items.forEach(function (item) {
|
|
443
|
+
var displayLevel = Number(item.dataset.jtocDisplayLevel || 0);
|
|
444
|
+
if (displayLevel >= 0 && displayLevel < minDisplayLevel) {
|
|
445
|
+
minDisplayLevel = displayLevel;
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
if (Number.isFinite(minDisplayLevel)) {
|
|
450
|
+
items.forEach(function (item) {
|
|
451
|
+
var displayLevel = Number(item.dataset.jtocDisplayLevel || 0);
|
|
452
|
+
if (displayLevel === minDisplayLevel) {
|
|
453
|
+
item.classList.add("is-open");
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function ensureActiveVisibleAfterLayoutChange() {
|
|
459
|
+
var scrollEl = getTocScrollContainer(root);
|
|
460
|
+
var activeLink = root.querySelector(".jtoc-link.is-active");
|
|
461
|
+
if (!scrollEl || !activeLink) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
var viewRect = scrollEl.getBoundingClientRect();
|
|
466
|
+
var linkRect = activeLink.getBoundingClientRect();
|
|
467
|
+
var topGuard = viewRect.top + 36;
|
|
468
|
+
var bottomGuard = viewRect.bottom - 12;
|
|
469
|
+
var isOutside = linkRect.top < topGuard || linkRect.bottom > bottomGuard;
|
|
470
|
+
if (!isOutside) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
var relativeTop = linkRect.top - viewRect.top + scrollEl.scrollTop;
|
|
475
|
+
var targetTop = relativeTop - scrollEl.clientHeight * 0.35;
|
|
476
|
+
scrollEl.scrollTo({ top: Math.max(0, targetTop), behavior: "smooth" });
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
items.forEach(function (item) {
|
|
480
|
+
var toggle = getDirectToggle(item);
|
|
481
|
+
if (!toggle) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
toggle.addEventListener("click", function (event) {
|
|
486
|
+
event.preventDefault();
|
|
487
|
+
event.stopPropagation();
|
|
488
|
+
item.classList.toggle("is-open");
|
|
489
|
+
refreshFoldHeights(root);
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
root.classList.add("jtoc-fold-init");
|
|
494
|
+
refreshFoldHeights(root);
|
|
495
|
+
ensureActiveVisibleAfterLayoutChange();
|
|
496
|
+
window.requestAnimationFrame(function () {
|
|
497
|
+
window.requestAnimationFrame(function () {
|
|
498
|
+
root.classList.remove("jtoc-fold-init");
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
window.addEventListener("resize", function () {
|
|
503
|
+
refreshFoldHeights(root);
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function setupScrollSpy(root) {
|
|
508
|
+
var links = Array.prototype.slice.call(root.querySelectorAll(".jtoc-link"));
|
|
509
|
+
var panel = root.querySelector(".jtoc-panel");
|
|
510
|
+
var scrollEl = getTocScrollContainer(root);
|
|
511
|
+
if (!links.length || !panel || !scrollEl) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
var headings = links
|
|
516
|
+
.map(function (link) {
|
|
517
|
+
var id = (link.getAttribute("href") || "").replace(/^#/, "");
|
|
518
|
+
if (!id) {
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
var heading = document.getElementById(id);
|
|
522
|
+
if (!heading) {
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
return { id: id, heading: heading, link: link };
|
|
526
|
+
})
|
|
527
|
+
.filter(Boolean);
|
|
528
|
+
|
|
529
|
+
if (!headings.length) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
var currentActiveId = null;
|
|
534
|
+
|
|
535
|
+
function getLinkById(id) {
|
|
536
|
+
for (var i = 0; i < links.length; i += 1) {
|
|
537
|
+
if (links[i].dataset.jtocLink === id) {
|
|
538
|
+
return links[i];
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function setActive(id) {
|
|
545
|
+
if (!id) {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (id === currentActiveId) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
var activeLink = null;
|
|
554
|
+
links.forEach(function (link) {
|
|
555
|
+
var linkId = link.dataset.jtocLink;
|
|
556
|
+
var isActive = linkId === id;
|
|
557
|
+
link.classList.toggle("is-active", isActive);
|
|
558
|
+
var row = link.closest(".jtoc-link-row");
|
|
559
|
+
if (row) {
|
|
560
|
+
row.classList.toggle("is-active", isActive);
|
|
561
|
+
}
|
|
562
|
+
if (isActive) {
|
|
563
|
+
activeLink = link;
|
|
564
|
+
}
|
|
565
|
+
if (isActive && root.dataset.fold === "true") {
|
|
566
|
+
var changed = openAncestorsFromLink(root, link);
|
|
567
|
+
if (changed) {
|
|
568
|
+
trackActiveLinkDuringUnfold(link);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
currentActiveId = id;
|
|
573
|
+
ensureActiveLinkVisible(activeLink);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function ensureActiveLinkVisible(activeLink) {
|
|
577
|
+
if (!activeLink) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
var viewRect = scrollEl.getBoundingClientRect();
|
|
582
|
+
var linkRect = activeLink.getBoundingClientRect();
|
|
583
|
+
var topGuard = viewRect.top + 36;
|
|
584
|
+
var bottomGuard = viewRect.bottom - 12;
|
|
585
|
+
var isOutside = linkRect.top < topGuard || linkRect.bottom > bottomGuard;
|
|
586
|
+
if (!isOutside) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
var relativeTop = linkRect.top - viewRect.top + scrollEl.scrollTop;
|
|
591
|
+
var targetTop = relativeTop - scrollEl.clientHeight * 0.35;
|
|
592
|
+
if (linkRect.top - viewRect.top <= scrollEl.clientHeight * 0.2) {
|
|
593
|
+
targetTop = 0;
|
|
594
|
+
}
|
|
595
|
+
targetTop = Math.max(0, targetTop);
|
|
596
|
+
scrollEl.scrollTo({ top: targetTop, behavior: "auto" });
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function ensureCurrentActiveVisible() {
|
|
600
|
+
if (!currentActiveId) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
ensureActiveLinkVisible(getLinkById(currentActiveId));
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function trackActiveLinkDuringUnfold(activeLink) {
|
|
607
|
+
if (!activeLink) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
var endAt = Date.now() + 700;
|
|
612
|
+
function frameTrack() {
|
|
613
|
+
ensureCurrentActiveVisible();
|
|
614
|
+
if (Date.now() < endAt) {
|
|
615
|
+
window.requestAnimationFrame(frameTrack);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
window.requestAnimationFrame(frameTrack);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
var observer = new IntersectionObserver(
|
|
622
|
+
function (entries) {
|
|
623
|
+
var visible = entries
|
|
624
|
+
.filter(function (entry) {
|
|
625
|
+
return entry.isIntersecting;
|
|
626
|
+
})
|
|
627
|
+
.sort(function (a, b) {
|
|
628
|
+
return a.boundingClientRect.top - b.boundingClientRect.top;
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
if (visible.length) {
|
|
632
|
+
setActive(visible[0].target.id);
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
rootMargin: "-20% 0px -65% 0px",
|
|
637
|
+
threshold: [0.1, 0.4, 0.8]
|
|
638
|
+
}
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
headings.forEach(function (item) {
|
|
642
|
+
observer.observe(item.heading);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
function setActiveByHash() {
|
|
646
|
+
var hash = (window.location.hash || "").replace(/^#/, "");
|
|
647
|
+
if (!hash) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
setActive(hash);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function setActiveByScrollPosition() {
|
|
654
|
+
var offsetY = 140;
|
|
655
|
+
var current = headings[0];
|
|
656
|
+
headings.forEach(function (item) {
|
|
657
|
+
if (item.heading.getBoundingClientRect().top <= offsetY) {
|
|
658
|
+
current = item;
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
if (current) {
|
|
662
|
+
setActive(current.id);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
links.forEach(function (link) {
|
|
667
|
+
link.addEventListener("click", function () {
|
|
668
|
+
var id = (link.getAttribute("href") || "").replace(/^#/, "");
|
|
669
|
+
if (!id) {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
setActive(id);
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
window.addEventListener("hashchange", setActiveByHash);
|
|
677
|
+
window.addEventListener(
|
|
678
|
+
"scroll",
|
|
679
|
+
function () {
|
|
680
|
+
setActiveByScrollPosition();
|
|
681
|
+
},
|
|
682
|
+
{ passive: true }
|
|
683
|
+
);
|
|
684
|
+
|
|
685
|
+
window.addEventListener("resize", function () {
|
|
686
|
+
ensureCurrentActiveVisible();
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
panel.addEventListener(
|
|
690
|
+
"transitionrun",
|
|
691
|
+
function (event) {
|
|
692
|
+
if (!event.target.classList || !event.target.classList.contains("jtoc-list")) {
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (!currentActiveId) {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
var activeLink = getLinkById(currentActiveId);
|
|
699
|
+
if (!activeLink || !event.target.contains(activeLink)) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
trackActiveLinkDuringUnfold(activeLink);
|
|
703
|
+
},
|
|
704
|
+
true
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
panel.addEventListener(
|
|
708
|
+
"transitionend",
|
|
709
|
+
function (event) {
|
|
710
|
+
if (!event.target.classList || !event.target.classList.contains("jtoc-list")) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
if (!currentActiveId) {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
var activeLink = getLinkById(currentActiveId);
|
|
717
|
+
if (!activeLink || !event.target.contains(activeLink)) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
ensureCurrentActiveVisible();
|
|
721
|
+
},
|
|
722
|
+
true
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
setActiveByHash();
|
|
726
|
+
setActiveByScrollPosition();
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function init() {
|
|
730
|
+
var root = document.querySelector("[data-jtoc-root]");
|
|
731
|
+
if (!root) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
root.classList.add("jtoc-panel-layout-init");
|
|
736
|
+
|
|
737
|
+
var config = parseConfig();
|
|
738
|
+
var runtimeStyle = resolveRuntimeStyle(config);
|
|
739
|
+
applyRuntimeStyle(root, runtimeStyle);
|
|
740
|
+
observeThemeChanges(root, runtimeStyle);
|
|
741
|
+
setupMobileToggle(root);
|
|
742
|
+
setupFold(root);
|
|
743
|
+
setupScrollEdgeVisibility(root);
|
|
744
|
+
|
|
745
|
+
function bindScrollSpyAndUnlock() {
|
|
746
|
+
setupScrollSpy(root);
|
|
747
|
+
window.requestAnimationFrame(function () {
|
|
748
|
+
window.requestAnimationFrame(function () {
|
|
749
|
+
root.classList.remove("jtoc-panel-layout-init");
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (document.readyState === "loading") {
|
|
755
|
+
document.addEventListener("DOMContentLoaded", bindScrollSpyAndUnlock);
|
|
756
|
+
} else {
|
|
757
|
+
bindScrollSpyAndUnlock();
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (document.querySelector("[data-jtoc-root]")) {
|
|
762
|
+
init();
|
|
763
|
+
} else if (document.readyState === "loading") {
|
|
764
|
+
document.addEventListener("DOMContentLoaded", init);
|
|
765
|
+
} else {
|
|
766
|
+
init();
|
|
767
|
+
}
|
|
768
|
+
})();
|