Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/resources/projects/website/navigation/quarto-nav.js
12923 views
1
const headroomChanged = new CustomEvent("quarto-hrChanged", {
2
detail: {},
3
bubbles: true,
4
cancelable: false,
5
composed: false,
6
});
7
8
const announceDismiss = () => {
9
const annEl = window.document.getElementById("quarto-announcement");
10
if (annEl) {
11
annEl.remove();
12
13
const annId = annEl.getAttribute("data-announcement-id");
14
window.localStorage.setItem(`quarto-announce-${annId}`, "true");
15
}
16
};
17
18
const announceRegister = () => {
19
const annEl = window.document.getElementById("quarto-announcement");
20
if (annEl) {
21
const annId = annEl.getAttribute("data-announcement-id");
22
const isDismissed =
23
window.localStorage.getItem(`quarto-announce-${annId}`) || false;
24
if (isDismissed) {
25
announceDismiss();
26
return;
27
} else {
28
annEl.classList.remove("hidden");
29
}
30
31
const actionEl = annEl.querySelector(".quarto-announcement-action");
32
if (actionEl) {
33
actionEl.addEventListener("click", function (e) {
34
e.preventDefault();
35
// Hide the bar immediately
36
announceDismiss();
37
});
38
}
39
}
40
};
41
42
window.document.addEventListener("DOMContentLoaded", function () {
43
let init = false;
44
45
announceRegister();
46
47
// Manage the back to top button, if one is present.
48
let lastScrollTop = window.pageYOffset || document.documentElement.scrollTop;
49
const scrollDownBuffer = 5;
50
const scrollUpBuffer = 35;
51
const btn = document.getElementById("quarto-back-to-top");
52
const hideBackToTop = () => {
53
btn.style.display = "none";
54
};
55
const showBackToTop = () => {
56
btn.style.display = "inline-block";
57
};
58
if (btn) {
59
window.document.addEventListener(
60
"scroll",
61
function () {
62
const currentScrollTop =
63
window.pageYOffset || document.documentElement.scrollTop;
64
65
// Shows and hides the button 'intelligently' as the user scrolls
66
if (currentScrollTop - scrollDownBuffer > lastScrollTop) {
67
hideBackToTop();
68
lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop;
69
} else if (currentScrollTop < lastScrollTop - scrollUpBuffer) {
70
showBackToTop();
71
lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop;
72
}
73
74
// Show the button at the bottom, hides it at the top
75
if (currentScrollTop <= 0) {
76
hideBackToTop();
77
} else if (
78
window.innerHeight + currentScrollTop >=
79
document.body.offsetHeight
80
) {
81
showBackToTop();
82
}
83
},
84
false
85
);
86
}
87
88
function throttle(func, wait) {
89
var timeout;
90
return function () {
91
const context = this;
92
const args = arguments;
93
const later = function () {
94
clearTimeout(timeout);
95
timeout = null;
96
func.apply(context, args);
97
};
98
99
if (!timeout) {
100
timeout = setTimeout(later, wait);
101
}
102
};
103
}
104
105
function headerOffset() {
106
// Set an offset if there is are fixed top navbar
107
const headerEl = window.document.querySelector("header.fixed-top");
108
if (headerEl) {
109
return headerEl.clientHeight;
110
} else {
111
return 0;
112
}
113
}
114
115
function footerOffset() {
116
const footerEl = window.document.querySelector("footer.footer");
117
if (footerEl) {
118
return footerEl.clientHeight;
119
} else {
120
return 0;
121
}
122
}
123
124
function dashboardOffset() {
125
const dashboardNavEl = window.document.getElementById(
126
"quarto-dashboard-header"
127
);
128
if (dashboardNavEl !== null) {
129
return dashboardNavEl.clientHeight;
130
} else {
131
return 0;
132
}
133
}
134
135
function updateDocumentOffsetWithoutAnimation() {
136
updateDocumentOffset(false);
137
}
138
139
function updateDocumentOffset(animated) {
140
// set body offset
141
const topOffset = headerOffset();
142
const bodyOffset = topOffset + footerOffset() + dashboardOffset();
143
const bodyEl = window.document.body;
144
bodyEl.setAttribute("data-bs-offset", topOffset);
145
bodyEl.style.paddingTop = topOffset + "px";
146
147
// deal with sidebar offsets
148
const sidebars = window.document.querySelectorAll(
149
".sidebar, .headroom-target"
150
);
151
sidebars.forEach((sidebar) => {
152
if (!animated) {
153
sidebar.classList.add("notransition");
154
// Remove the no transition class after the animation has time to complete
155
setTimeout(function () {
156
sidebar.classList.remove("notransition");
157
}, 201);
158
}
159
160
if (window.Headroom && sidebar.classList.contains("sidebar-unpinned")) {
161
sidebar.style.top = "0";
162
sidebar.style.maxHeight = "100vh";
163
} else {
164
sidebar.style.top = topOffset + "px";
165
sidebar.style.maxHeight = "calc(100vh - " + topOffset + "px)";
166
}
167
});
168
169
// allow space for footer
170
const mainContainer = window.document.querySelector(".quarto-container");
171
if (mainContainer) {
172
mainContainer.style.minHeight = "calc(100vh - " + bodyOffset + "px)";
173
}
174
175
// link offset
176
let linkStyle = window.document.querySelector("#quarto-target-style");
177
if (!linkStyle) {
178
linkStyle = window.document.createElement("style");
179
linkStyle.setAttribute("id", "quarto-target-style");
180
window.document.head.appendChild(linkStyle);
181
}
182
while (linkStyle.firstChild) {
183
linkStyle.removeChild(linkStyle.firstChild);
184
}
185
if (topOffset > 0) {
186
linkStyle.appendChild(
187
window.document.createTextNode(`
188
section:target::before {
189
content: "";
190
display: block;
191
height: ${topOffset}px;
192
margin: -${topOffset}px 0 0;
193
}`)
194
);
195
}
196
if (init) {
197
window.dispatchEvent(headroomChanged);
198
}
199
init = true;
200
}
201
202
// initialize headroom
203
var header = window.document.querySelector("#quarto-header");
204
if (header && window.Headroom) {
205
const headroom = new window.Headroom(header, {
206
tolerance: 5,
207
onPin: function () {
208
const sidebars = window.document.querySelectorAll(
209
".sidebar, .headroom-target"
210
);
211
sidebars.forEach((sidebar) => {
212
sidebar.classList.remove("sidebar-unpinned");
213
});
214
updateDocumentOffset();
215
},
216
onUnpin: function () {
217
const sidebars = window.document.querySelectorAll(
218
".sidebar, .headroom-target"
219
);
220
sidebars.forEach((sidebar) => {
221
sidebar.classList.add("sidebar-unpinned");
222
});
223
updateDocumentOffset();
224
},
225
});
226
headroom.init();
227
228
let frozen = false;
229
window.quartoToggleHeadroom = function () {
230
if (frozen) {
231
headroom.unfreeze();
232
frozen = false;
233
} else {
234
headroom.freeze();
235
frozen = true;
236
}
237
};
238
}
239
240
window.addEventListener(
241
"hashchange",
242
function (e) {
243
if (
244
getComputedStyle(document.documentElement).scrollBehavior !== "smooth"
245
) {
246
window.scrollTo(0, window.pageYOffset - headerOffset());
247
}
248
},
249
false
250
);
251
252
// Observe size changed for the header
253
const headerEl = window.document.querySelector("header.fixed-top");
254
if (headerEl && window.ResizeObserver) {
255
const observer = new window.ResizeObserver(() => {
256
setTimeout(updateDocumentOffsetWithoutAnimation, 0);
257
});
258
observer.observe(headerEl, {
259
attributes: true,
260
childList: true,
261
characterData: true,
262
});
263
} else {
264
window.addEventListener(
265
"resize",
266
throttle(updateDocumentOffsetWithoutAnimation, 50)
267
);
268
}
269
setTimeout(updateDocumentOffsetWithoutAnimation, 250);
270
271
// fixup index.html links if we aren't on the filesystem
272
if (window.location.protocol !== "file:") {
273
const links = window.document.querySelectorAll("a");
274
for (let i = 0; i < links.length; i++) {
275
if (links[i].href) {
276
links[i].dataset.originalHref = links[i].href;
277
links[i].href = links[i].href.replace(/\/index\.html/, "/");
278
}
279
}
280
281
// Fixup any sharing links that require urls
282
// Append url to any sharing urls
283
const sharingLinks = window.document.querySelectorAll(
284
"a.sidebar-tools-main-item, a.quarto-navigation-tool, a.quarto-navbar-tools, a.quarto-navbar-tools-item"
285
);
286
for (let i = 0; i < sharingLinks.length; i++) {
287
const sharingLink = sharingLinks[i];
288
const href = sharingLink.getAttribute("href");
289
if (href) {
290
sharingLink.setAttribute(
291
"href",
292
href.replace("|url|", window.location.href)
293
);
294
}
295
}
296
297
// Scroll the active navigation item into view, if necessary
298
const navSidebar = window.document.querySelector("nav#quarto-sidebar");
299
if (navSidebar) {
300
// Find the active item
301
const activeItem = navSidebar.querySelector("li.sidebar-item a.active");
302
if (activeItem) {
303
// Wait for the scroll height and height to resolve by observing size changes on the
304
// nav element that is scrollable
305
const resizeObserver = new ResizeObserver((_entries) => {
306
// The bottom of the element
307
const elBottom = activeItem.offsetTop;
308
const viewBottom = navSidebar.scrollTop + navSidebar.clientHeight;
309
310
// The element height and scroll height are the same, then we are still loading
311
if (viewBottom !== navSidebar.scrollHeight) {
312
// Determine if the item isn't visible and scroll to it
313
if (elBottom >= viewBottom) {
314
navSidebar.scrollTop = elBottom;
315
}
316
317
// stop observing now since we've completed the scroll
318
resizeObserver.unobserve(navSidebar);
319
}
320
});
321
resizeObserver.observe(navSidebar);
322
}
323
}
324
}
325
});
326
327