Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/resources/formats/html/quarto.js
12922 views
1
import * as tabsets from "./tabsets/tabsets.js";
2
3
const sectionChanged = new CustomEvent("quarto-sectionChanged", {
4
detail: {},
5
bubbles: true,
6
cancelable: false,
7
composed: false,
8
});
9
10
const layoutMarginEls = () => {
11
// Find any conflicting margin elements and add margins to the
12
// top to prevent overlap
13
const marginChildren = window.document.querySelectorAll(
14
".column-margin.column-container > *, .margin-caption, .aside"
15
);
16
17
let lastBottom = 0;
18
for (const marginChild of marginChildren) {
19
if (marginChild.offsetParent !== null) {
20
// clear the top margin so we recompute it
21
marginChild.style.marginTop = null;
22
const top = marginChild.getBoundingClientRect().top + window.scrollY;
23
if (top < lastBottom) {
24
const marginChildStyle = window.getComputedStyle(marginChild);
25
const marginBottom = parseFloat(marginChildStyle["marginBottom"]);
26
const margin = lastBottom - top + marginBottom;
27
marginChild.style.marginTop = `${margin}px`;
28
}
29
const styles = window.getComputedStyle(marginChild);
30
const marginTop = parseFloat(styles["marginTop"]);
31
lastBottom = top + marginChild.getBoundingClientRect().height + marginTop;
32
}
33
}
34
};
35
36
window.document.addEventListener("DOMContentLoaded", function (_event) {
37
// Recompute the position of margin elements anytime the body size changes
38
if (window.ResizeObserver) {
39
const resizeObserver = new window.ResizeObserver(
40
throttle(() => {
41
layoutMarginEls();
42
if (
43
window.document.body.getBoundingClientRect().width < 990 &&
44
isReaderMode()
45
) {
46
quartoToggleReader();
47
}
48
}, 50)
49
);
50
resizeObserver.observe(window.document.body);
51
}
52
53
const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]');
54
const sidebarEl = window.document.getElementById("quarto-sidebar");
55
const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left");
56
const marginSidebarEl = window.document.getElementById(
57
"quarto-margin-sidebar"
58
);
59
// function to determine whether the element has a previous sibling that is active
60
const prevSiblingIsActiveLink = (el) => {
61
const sibling = el.previousElementSibling;
62
if (sibling && sibling.tagName === "A") {
63
return sibling.classList.contains("active");
64
} else {
65
return false;
66
}
67
};
68
69
// dispatch for htmlwidgets
70
// they use slideenter event to trigger resize
71
function fireSlideEnter() {
72
const event = window.document.createEvent("Event");
73
event.initEvent("slideenter", true, true);
74
window.document.dispatchEvent(event);
75
}
76
77
const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]');
78
tabs.forEach((tab) => {
79
tab.addEventListener("shown.bs.tab", fireSlideEnter);
80
});
81
82
// dispatch for shiny
83
// they use BS shown and hidden events to trigger rendering
84
function distpatchShinyEvents(previous, current) {
85
if (window.jQuery) {
86
if (previous) {
87
window.jQuery(previous).trigger("hidden");
88
}
89
if (current) {
90
window.jQuery(current).trigger("shown");
91
}
92
}
93
}
94
95
// tabby.js listener: Trigger event for htmlwidget and shiny
96
document.addEventListener(
97
"tabby",
98
function (event) {
99
fireSlideEnter();
100
distpatchShinyEvents(event.detail.previousTab, event.detail.tab);
101
},
102
false
103
);
104
105
// Track scrolling and mark TOC links as active
106
// get table of contents and sidebar (bail if we don't have at least one)
107
const tocLinks = tocEl
108
? [...tocEl.querySelectorAll("a[data-scroll-target]")]
109
: [];
110
const makeActive = (link) => tocLinks[link].classList.add("active");
111
const removeActive = (link) => tocLinks[link].classList.remove("active");
112
const removeAllActive = () =>
113
[...Array(tocLinks.length).keys()].forEach((link) => removeActive(link));
114
115
// activate the anchor for a section associated with this TOC entry
116
tocLinks.forEach((link) => {
117
link.addEventListener("click", () => {
118
if (link.href.indexOf("#") !== -1) {
119
const anchor = link.href.split("#")[1];
120
const heading = window.document.querySelector(
121
`[data-anchor-id="${anchor}"]`
122
);
123
if (heading) {
124
// Add the class
125
heading.classList.add("reveal-anchorjs-link");
126
127
// function to show the anchor
128
const handleMouseout = () => {
129
heading.classList.remove("reveal-anchorjs-link");
130
heading.removeEventListener("mouseout", handleMouseout);
131
};
132
133
// add a function to clear the anchor when the user mouses out of it
134
heading.addEventListener("mouseout", handleMouseout);
135
}
136
}
137
});
138
});
139
140
const sections = tocLinks.map((link) => {
141
const target = link.getAttribute("data-scroll-target");
142
if (target.startsWith("#")) {
143
return window.document.getElementById(decodeURI(`${target.slice(1)}`));
144
} else {
145
return window.document.querySelector(decodeURI(`${target}`));
146
}
147
});
148
149
const sectionMargin = 200;
150
let currentActive = 0;
151
// track whether we've initialized state the first time
152
let init = false;
153
154
const updateActiveLink = () => {
155
// The index from bottom to top (e.g. reversed list)
156
let sectionIndex = -1;
157
if (
158
window.innerHeight + window.pageYOffset >=
159
window.document.body.offsetHeight
160
) {
161
// This is the no-scroll case where last section should be the active one
162
sectionIndex = 0;
163
} else {
164
// This finds the last section visible on screen that should be made active
165
sectionIndex = [...sections].reverse().findIndex((section) => {
166
if (section) {
167
return window.pageYOffset >= section.offsetTop - sectionMargin;
168
} else {
169
return false;
170
}
171
});
172
}
173
if (sectionIndex > -1) {
174
const current = sections.length - sectionIndex - 1;
175
if (current !== currentActive) {
176
removeAllActive();
177
currentActive = current;
178
makeActive(current);
179
if (init) {
180
window.dispatchEvent(sectionChanged);
181
}
182
init = true;
183
}
184
}
185
};
186
187
const inHiddenRegion = (top, bottom, hiddenRegions) => {
188
for (const region of hiddenRegions) {
189
if (top <= region.bottom && bottom >= region.top) {
190
return true;
191
}
192
}
193
return false;
194
};
195
196
const categorySelector = "header.quarto-title-block .quarto-category";
197
const activateCategories = (href) => {
198
// Find any categories
199
// Surround them with a link pointing back to:
200
// #category=Authoring
201
try {
202
const categoryEls = window.document.querySelectorAll(categorySelector);
203
for (const categoryEl of categoryEls) {
204
const categoryText = categoryEl.textContent;
205
if (categoryText) {
206
const link = `${href}#category=${encodeURIComponent(categoryText)}`;
207
const linkEl = window.document.createElement("a");
208
linkEl.setAttribute("href", link);
209
for (const child of categoryEl.childNodes) {
210
linkEl.append(child);
211
}
212
categoryEl.appendChild(linkEl);
213
}
214
}
215
} catch {
216
// Ignore errors
217
}
218
};
219
function hasTitleCategories() {
220
return window.document.querySelector(categorySelector) !== null;
221
}
222
223
function offsetRelativeUrl(url) {
224
const offset = getMeta("quarto:offset");
225
return offset ? offset + url : url;
226
}
227
228
function offsetAbsoluteUrl(url) {
229
const offset = getMeta("quarto:offset");
230
const baseUrl = new URL(offset, window.location);
231
232
const projRelativeUrl = url.replace(baseUrl, "");
233
if (projRelativeUrl.startsWith("/")) {
234
return projRelativeUrl;
235
} else {
236
return "/" + projRelativeUrl;
237
}
238
}
239
240
// read a meta tag value
241
function getMeta(metaName) {
242
const metas = window.document.getElementsByTagName("meta");
243
for (let i = 0; i < metas.length; i++) {
244
if (metas[i].getAttribute("name") === metaName) {
245
return metas[i].getAttribute("content");
246
}
247
}
248
return "";
249
}
250
251
async function findAndActivateCategories() {
252
// Categories search with listing only use path without query
253
const currentPagePath = offsetAbsoluteUrl(
254
window.location.origin + window.location.pathname
255
);
256
const response = await fetch(offsetRelativeUrl("listings.json"));
257
if (response.status == 200) {
258
return response.json().then(function (listingPaths) {
259
const listingHrefs = [];
260
for (const listingPath of listingPaths) {
261
const pathWithoutLeadingSlash = listingPath.listing.substring(1);
262
for (const item of listingPath.items) {
263
const encodedItem = encodeURI(item);
264
if (
265
encodedItem === currentPagePath ||
266
encodedItem === currentPagePath + "index.html"
267
) {
268
// Resolve this path against the offset to be sure
269
// we already are using the correct path to the listing
270
// (this adjusts the listing urls to be rooted against
271
// whatever root the page is actually running against)
272
const relative = offsetRelativeUrl(pathWithoutLeadingSlash);
273
const baseUrl = window.location;
274
const resolvedPath = new URL(relative, baseUrl);
275
listingHrefs.push(resolvedPath.pathname);
276
break;
277
}
278
}
279
}
280
281
// Look up the tree for a nearby linting and use that if we find one
282
const nearestListing = findNearestParentListing(
283
offsetAbsoluteUrl(window.location.pathname),
284
listingHrefs
285
);
286
if (nearestListing) {
287
activateCategories(nearestListing);
288
} else {
289
// See if the referrer is a listing page for this item
290
const referredRelativePath = offsetAbsoluteUrl(document.referrer);
291
const referrerListing = listingHrefs.find((listingHref) => {
292
const isListingReferrer =
293
listingHref === referredRelativePath ||
294
listingHref === referredRelativePath + "index.html";
295
return isListingReferrer;
296
});
297
298
if (referrerListing) {
299
// Try to use the referrer if possible
300
activateCategories(referrerListing);
301
} else if (listingHrefs.length > 0) {
302
// Otherwise, just fall back to the first listing
303
activateCategories(listingHrefs[0]);
304
}
305
}
306
});
307
}
308
}
309
if (hasTitleCategories()) {
310
findAndActivateCategories();
311
}
312
313
const findNearestParentListing = (href, listingHrefs) => {
314
if (!href || !listingHrefs) {
315
return undefined;
316
}
317
// Look up the tree for a nearby linting and use that if we find one
318
const relativeParts = href.substring(1).split("/");
319
while (relativeParts.length > 0) {
320
const path = relativeParts.join("/");
321
for (const listingHref of listingHrefs) {
322
if (listingHref.startsWith(path)) {
323
return listingHref;
324
}
325
}
326
relativeParts.pop();
327
}
328
329
return undefined;
330
};
331
332
const manageSidebarVisiblity = (el, placeholderDescriptor) => {
333
let isVisible = true;
334
let elRect;
335
336
return (hiddenRegions) => {
337
if (el === null) {
338
return;
339
}
340
341
// Find the last element of the TOC
342
const lastChildEl = el.lastElementChild;
343
344
if (lastChildEl) {
345
// Converts the sidebar to a menu
346
const convertToMenu = () => {
347
for (const child of el.children) {
348
child.style.opacity = 0;
349
child.style.overflow = "hidden";
350
child.style.pointerEvents = "none";
351
}
352
353
nexttick(() => {
354
const toggleContainer = window.document.createElement("div");
355
toggleContainer.style.width = "100%";
356
toggleContainer.classList.add("zindex-over-content");
357
toggleContainer.classList.add("quarto-sidebar-toggle");
358
toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom
359
toggleContainer.id = placeholderDescriptor.id;
360
toggleContainer.style.position = "fixed";
361
362
const toggleIcon = window.document.createElement("i");
363
toggleIcon.classList.add("quarto-sidebar-toggle-icon");
364
toggleIcon.classList.add("bi");
365
toggleIcon.classList.add("bi-caret-down-fill");
366
367
const toggleTitle = window.document.createElement("div");
368
const titleEl = window.document.body.querySelector(
369
placeholderDescriptor.titleSelector
370
);
371
if (titleEl) {
372
toggleTitle.append(
373
titleEl.textContent || titleEl.innerText,
374
toggleIcon
375
);
376
}
377
toggleTitle.classList.add("zindex-over-content");
378
toggleTitle.classList.add("quarto-sidebar-toggle-title");
379
toggleContainer.append(toggleTitle);
380
381
const toggleContents = window.document.createElement("div");
382
toggleContents.classList = el.classList;
383
toggleContents.classList.add("zindex-over-content");
384
toggleContents.classList.add("quarto-sidebar-toggle-contents");
385
for (const child of el.children) {
386
if (child.id === "toc-title") {
387
continue;
388
}
389
390
const clone = child.cloneNode(true);
391
clone.style.opacity = 1;
392
clone.style.pointerEvents = null;
393
clone.style.display = null;
394
toggleContents.append(clone);
395
}
396
toggleContents.style.height = "0px";
397
const positionToggle = () => {
398
// position the element (top left of parent, same width as parent)
399
if (!elRect) {
400
elRect = el.getBoundingClientRect();
401
}
402
toggleContainer.style.left = `${elRect.left}px`;
403
toggleContainer.style.top = `${elRect.top}px`;
404
toggleContainer.style.width = `${elRect.width}px`;
405
};
406
positionToggle();
407
408
toggleContainer.append(toggleContents);
409
el.parentElement.prepend(toggleContainer);
410
411
// Process clicks
412
let tocShowing = false;
413
// Allow the caller to control whether this is dismissed
414
// when it is clicked (e.g. sidebar navigation supports
415
// opening and closing the nav tree, so don't dismiss on click)
416
const clickEl = placeholderDescriptor.dismissOnClick
417
? toggleContainer
418
: toggleTitle;
419
420
const closeToggle = () => {
421
if (tocShowing) {
422
toggleContainer.classList.remove("expanded");
423
toggleContents.style.height = "0px";
424
tocShowing = false;
425
}
426
};
427
428
// Get rid of any expanded toggle if the user scrolls
429
window.document.addEventListener(
430
"scroll",
431
throttle(() => {
432
closeToggle();
433
}, 50)
434
);
435
436
// Handle positioning of the toggle
437
window.addEventListener(
438
"resize",
439
throttle(() => {
440
elRect = undefined;
441
positionToggle();
442
}, 50)
443
);
444
445
window.addEventListener("quarto-hrChanged", () => {
446
elRect = undefined;
447
});
448
449
// Process the click
450
clickEl.onclick = () => {
451
if (!tocShowing) {
452
toggleContainer.classList.add("expanded");
453
toggleContents.style.height = null;
454
tocShowing = true;
455
} else {
456
closeToggle();
457
}
458
};
459
});
460
};
461
462
// Converts a sidebar from a menu back to a sidebar
463
const convertToSidebar = () => {
464
for (const child of el.children) {
465
child.style.opacity = 1;
466
child.style.overflow = null;
467
child.style.pointerEvents = null;
468
}
469
470
const placeholderEl = window.document.getElementById(
471
placeholderDescriptor.id
472
);
473
if (placeholderEl) {
474
placeholderEl.remove();
475
}
476
477
el.classList.remove("rollup");
478
};
479
480
if (isReaderMode()) {
481
convertToMenu();
482
isVisible = false;
483
} else {
484
// Find the top and bottom o the element that is being managed
485
const elTop = el.offsetTop;
486
const elBottom =
487
elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight;
488
489
if (!isVisible) {
490
// If the element is current not visible reveal if there are
491
// no conflicts with overlay regions
492
if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) {
493
convertToSidebar();
494
isVisible = true;
495
}
496
} else {
497
// If the element is visible, hide it if it conflicts with overlay regions
498
// and insert a placeholder toggle (or if we're in reader mode)
499
if (inHiddenRegion(elTop, elBottom, hiddenRegions)) {
500
convertToMenu();
501
isVisible = false;
502
}
503
}
504
}
505
}
506
};
507
};
508
509
const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]');
510
for (const tabEl of tabEls) {
511
const id = tabEl.getAttribute("data-bs-target");
512
if (id) {
513
const columnEl = document.querySelector(
514
`${id} .column-margin, .tabset-margin-content`
515
);
516
if (columnEl)
517
tabEl.addEventListener("shown.bs.tab", function (event) {
518
const el = event.srcElement;
519
if (el) {
520
const visibleCls = `${el.id}-margin-content`;
521
// walk up until we find a parent tabset
522
let panelTabsetEl = el.parentElement;
523
while (panelTabsetEl) {
524
if (panelTabsetEl.classList.contains("panel-tabset")) {
525
break;
526
}
527
panelTabsetEl = panelTabsetEl.parentElement;
528
}
529
530
if (panelTabsetEl) {
531
const prevSib = panelTabsetEl.previousElementSibling;
532
if (
533
prevSib &&
534
prevSib.classList.contains("tabset-margin-container")
535
) {
536
const childNodes = prevSib.querySelectorAll(
537
".tabset-margin-content"
538
);
539
for (const childEl of childNodes) {
540
if (childEl.classList.contains(visibleCls)) {
541
childEl.classList.remove("collapse");
542
} else {
543
childEl.classList.add("collapse");
544
}
545
}
546
}
547
}
548
}
549
550
layoutMarginEls();
551
});
552
}
553
}
554
555
// Manage the visibility of the toc and the sidebar
556
const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, {
557
id: "quarto-toc-toggle",
558
titleSelector: "#toc-title",
559
dismissOnClick: true,
560
});
561
const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, {
562
id: "quarto-sidebarnav-toggle",
563
titleSelector: ".title",
564
dismissOnClick: false,
565
});
566
let tocLeftScrollVisibility;
567
if (leftTocEl) {
568
tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, {
569
id: "quarto-lefttoc-toggle",
570
titleSelector: "#toc-title",
571
dismissOnClick: true,
572
});
573
}
574
575
// Find the first element that uses formatting in special columns
576
const conflictingEls = window.document.body.querySelectorAll(
577
'[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]'
578
);
579
580
// Filter all the possibly conflicting elements into ones
581
// the do conflict on the left or ride side
582
const arrConflictingEls = Array.from(conflictingEls);
583
const leftSideConflictEls = arrConflictingEls.filter((el) => {
584
if (el.tagName === "ASIDE") {
585
return false;
586
}
587
return Array.from(el.classList).find((className) => {
588
return (
589
className !== "column-body" &&
590
className.startsWith("column-") &&
591
!className.endsWith("right") &&
592
!className.endsWith("container") &&
593
className !== "column-margin"
594
);
595
});
596
});
597
const rightSideConflictEls = arrConflictingEls.filter((el) => {
598
if (el.tagName === "ASIDE") {
599
return true;
600
}
601
602
const hasMarginCaption = Array.from(el.classList).find((className) => {
603
return className == "margin-caption";
604
});
605
if (hasMarginCaption) {
606
return true;
607
}
608
609
return Array.from(el.classList).find((className) => {
610
return (
611
className !== "column-body" &&
612
!className.endsWith("container") &&
613
className.startsWith("column-") &&
614
!className.endsWith("left")
615
);
616
});
617
});
618
619
const kOverlapPaddingSize = 10;
620
function toRegions(els) {
621
return els.map((el) => {
622
const boundRect = el.getBoundingClientRect();
623
const top =
624
boundRect.top +
625
document.documentElement.scrollTop -
626
kOverlapPaddingSize;
627
return {
628
top,
629
bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize,
630
};
631
});
632
}
633
634
let hasObserved = false;
635
const visibleItemObserver = (els) => {
636
let visibleElements = [...els];
637
const intersectionObserver = new IntersectionObserver(
638
(entries, _observer) => {
639
entries.forEach((entry) => {
640
if (entry.isIntersecting) {
641
if (visibleElements.indexOf(entry.target) === -1) {
642
visibleElements.push(entry.target);
643
}
644
} else {
645
visibleElements = visibleElements.filter((visibleEntry) => {
646
return visibleEntry !== entry;
647
});
648
}
649
});
650
651
if (!hasObserved) {
652
hideOverlappedSidebars();
653
}
654
hasObserved = true;
655
},
656
{}
657
);
658
els.forEach((el) => {
659
intersectionObserver.observe(el);
660
});
661
662
return {
663
getVisibleEntries: () => {
664
return visibleElements;
665
},
666
};
667
};
668
669
const rightElementObserver = visibleItemObserver(rightSideConflictEls);
670
const leftElementObserver = visibleItemObserver(leftSideConflictEls);
671
672
const hideOverlappedSidebars = () => {
673
marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries()));
674
sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries()));
675
if (tocLeftScrollVisibility) {
676
tocLeftScrollVisibility(
677
toRegions(leftElementObserver.getVisibleEntries())
678
);
679
}
680
};
681
682
window.quartoToggleReader = () => {
683
// Applies a slow class (or removes it)
684
// to update the transition speed
685
const slowTransition = (slow) => {
686
const manageTransition = (id, slow) => {
687
const el = document.getElementById(id);
688
if (el) {
689
if (slow) {
690
el.classList.add("slow");
691
} else {
692
el.classList.remove("slow");
693
}
694
}
695
};
696
697
manageTransition("TOC", slow);
698
manageTransition("quarto-sidebar", slow);
699
};
700
const readerMode = !isReaderMode();
701
setReaderModeValue(readerMode);
702
703
// If we're entering reader mode, slow the transition
704
if (readerMode) {
705
slowTransition(readerMode);
706
}
707
highlightReaderToggle(readerMode);
708
hideOverlappedSidebars();
709
710
// If we're exiting reader mode, restore the non-slow transition
711
if (!readerMode) {
712
slowTransition(!readerMode);
713
}
714
};
715
716
const highlightReaderToggle = (readerMode) => {
717
const els = document.querySelectorAll(".quarto-reader-toggle");
718
if (els) {
719
els.forEach((el) => {
720
if (readerMode) {
721
el.classList.add("reader");
722
} else {
723
el.classList.remove("reader");
724
}
725
});
726
}
727
};
728
729
const setReaderModeValue = (val) => {
730
if (window.location.protocol !== "file:") {
731
window.localStorage.setItem("quarto-reader-mode", val);
732
} else {
733
localReaderMode = val;
734
}
735
};
736
737
const isReaderMode = () => {
738
if (window.location.protocol !== "file:") {
739
return window.localStorage.getItem("quarto-reader-mode") === "true";
740
} else {
741
return localReaderMode;
742
}
743
};
744
let localReaderMode = null;
745
746
const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded");
747
const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1;
748
749
// Walk the TOC and collapse/expand nodes
750
// Nodes are expanded if:
751
// - they are top level
752
// - they have children that are 'active' links
753
// - they are directly below an link that is 'active'
754
const walk = (el, depth) => {
755
// Tick depth when we enter a UL
756
if (el.tagName === "UL") {
757
depth = depth + 1;
758
}
759
760
// It this is active link
761
let isActiveNode = false;
762
if (el.tagName === "A" && el.classList.contains("active")) {
763
isActiveNode = true;
764
}
765
766
// See if there is an active child to this element
767
let hasActiveChild = false;
768
for (const child of el.children) {
769
hasActiveChild = walk(child, depth) || hasActiveChild;
770
}
771
772
// Process the collapse state if this is an UL
773
if (el.tagName === "UL") {
774
if (tocOpenDepth === -1 && depth > 1) {
775
// toc-expand: false
776
el.classList.add("collapse");
777
} else if (
778
depth <= tocOpenDepth ||
779
hasActiveChild ||
780
prevSiblingIsActiveLink(el)
781
) {
782
el.classList.remove("collapse");
783
} else {
784
el.classList.add("collapse");
785
}
786
787
// untick depth when we leave a UL
788
depth = depth - 1;
789
}
790
return hasActiveChild || isActiveNode;
791
};
792
793
// walk the TOC and expand / collapse any items that should be shown
794
if (tocEl) {
795
updateActiveLink();
796
walk(tocEl, 0);
797
}
798
799
// Throttle the scroll event and walk peridiocally
800
window.document.addEventListener(
801
"scroll",
802
throttle(() => {
803
if (tocEl) {
804
updateActiveLink();
805
walk(tocEl, 0);
806
}
807
if (!isReaderMode()) {
808
hideOverlappedSidebars();
809
}
810
}, 5)
811
);
812
window.addEventListener(
813
"resize",
814
throttle(() => {
815
if (tocEl) {
816
updateActiveLink();
817
walk(tocEl, 0);
818
}
819
if (!isReaderMode()) {
820
hideOverlappedSidebars();
821
}
822
}, 10)
823
);
824
hideOverlappedSidebars();
825
highlightReaderToggle(isReaderMode());
826
});
827
828
tabsets.init();
829
830
function throttle(func, wait) {
831
let waiting = false;
832
return function () {
833
if (!waiting) {
834
func.apply(this, arguments);
835
waiting = true;
836
setTimeout(function () {
837
waiting = false;
838
}, wait);
839
}
840
};
841
}
842
843
function nexttick(func) {
844
return setTimeout(func, 0);
845
}
846
847