Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/resources/formats/dashboard/quarto-dashboard.js
12926 views
1
const fillDivClasseses = ["widget-subarea", "lm-Widget", "leaflet-container"];
2
3
function requiresFill(el) {
4
if (el.tagName === "DIV") {
5
return fillDivClasseses.some((cls) => {
6
return el.classList.contains(cls);
7
});
8
}
9
return false;
10
}
11
12
function ensureWidgetFills(el) {
13
if (!el.classList.contains("html-fill-item")) {
14
el.classList.add("html-fill-item");
15
}
16
17
if (!el.classList.contains("html-fill-container")) {
18
el.classList.add("html-fill-container");
19
}
20
}
21
22
function ensureWidgetsFill() {
23
// Find any jupyter widget containers and keep an eye on them
24
const widgetNodes = document.querySelectorAll(".widget-subarea");
25
for (const widgetEl of widgetNodes) {
26
ensureWidgetFills(widgetEl);
27
}
28
}
29
30
function manageOverflow() {
31
// Don't let vega cells scroll internally
32
const cellOutputs = document.querySelectorAll(".cell-output-display div");
33
for (const cellOutput of cellOutputs) {
34
if (cellOutput.id.startsWith("altair-viz-")) {
35
cellOutput.parentElement.classList.add("no-overflow-x");
36
}
37
}
38
}
39
40
function refreshStickyHeaders() {
41
// Deal with markdown tables
42
const markdownTables = document.querySelectorAll(".card-body > table");
43
for (const markdownTable of markdownTables) {
44
const scrollableArea = markdownTable.parentElement;
45
stickyThead.apply([markdownTable], { scrollableArea: scrollableArea });
46
}
47
48
// Deal with iTables tables
49
const cellOutputNodes = document.querySelectorAll(".card-body .cell-output");
50
for (const cellOutputNode of cellOutputNodes) {
51
const iTable = cellOutputNode.querySelector(".itables table");
52
if (iTable) {
53
stickyThead.apply([iTable], { scrollableArea: cellOutputNode });
54
cellOutputNode.classList.add("dashboard-data-table");
55
}
56
}
57
}
58
59
function updatePageFlow(scrolling) {
60
const dashboardContainerEl = document.querySelector(
61
".quarto-dashboard-content"
62
);
63
const tabContainerEl = document.querySelector(
64
".quarto-dashboard-content > .tab-content"
65
);
66
67
// update the container and body classes
68
if (scrolling) {
69
dashboardContainerEl.classList.add("dashboard-scrolling");
70
document.body.classList.remove("dashboard-fill");
71
dashboardContainerEl.classList.remove("bslib-page-fill");
72
73
if (tabContainerEl !== null && tabContainerEl.gridTemplateRows !== null) {
74
tabContainerEl.style.gridTemplateRows = "minmax(3em, max-content)";
75
}
76
} else {
77
dashboardContainerEl.classList.remove("dashboard-scrolling");
78
document.body.classList.add("dashboard-fill");
79
dashboardContainerEl.classList.add("bslib-page-fill");
80
81
if (tabContainerEl !== null && tabContainerEl.gridTemplateRows !== null) {
82
tabContainerEl.style.gridTemplateRows = "minmax(3em, 1fr)";
83
}
84
}
85
}
86
window.document.documentElement.classList.add("hidden");
87
window.document.addEventListener("DOMContentLoaded", function (_event) {
88
ensureWidgetsFill();
89
90
manageOverflow();
91
refreshStickyHeaders();
92
93
// Fixup any sharing links that require urls
94
// Append url to any sharing urls
95
const sharingLinks = window.document.querySelectorAll(
96
"a.quarto-dashboard-link"
97
);
98
for (let i = 0; i < sharingLinks.length; i++) {
99
const sharingLink = sharingLinks[i];
100
const href = sharingLink.getAttribute("href");
101
if (href) {
102
sharingLink.setAttribute(
103
"href",
104
href.replace("|url|", window.location.href)
105
);
106
}
107
}
108
109
// Try to process the hash and activate a tab
110
const hash = window.decodeURIComponent(window.location.hash);
111
if (hash.length > 0) {
112
QuartoDashboardUtils.showPage(hash, () => {
113
window.document.documentElement.classList.remove("hidden");
114
});
115
} else {
116
window.document.documentElement.classList.remove("hidden");
117
}
118
119
// navigate to a tab when the history changes
120
window.addEventListener("popstate", function (e) {
121
const hash = window.decodeURIComponent(window.location.hash);
122
QuartoDashboardUtils.showPage(hash);
123
});
124
125
// Hook tabs and use that to update history / active tabs
126
const navItems = document.querySelectorAll(".navbar .nav-item .nav-link");
127
for (const navItem of navItems) {
128
const linkHref = navItem.getAttribute("href");
129
navItem.addEventListener("click", () => {
130
const baseUrl = QuartoDashboardUtils.urlWithoutHash(window.location.href);
131
const hash = QuartoDashboardUtils.urlHash(linkHref);
132
const href = baseUrl + hash;
133
QuartoDashboardUtils.setLocation(href);
134
135
const scrolling = navItem.getAttribute("data-scrolling");
136
if (scrolling !== null) {
137
updatePageFlow(scrolling.toLowerCase() === "true");
138
}
139
140
return false;
141
});
142
}
143
144
// Hook links in the body so users can link to pages
145
const linkEls = document.querySelectorAll(
146
".quarto-dashboard-content a:not(.nav-link)"
147
);
148
const currentUrl = new URL(window.location.href);
149
for (const linkEl of linkEls) {
150
const linkHref = linkEl.getAttribute("href");
151
// https://github.com/quarto-dev/quarto-cli/issues/9411
152
// only add the event listener for internal links
153
try {
154
const linkUrl = new URL(linkHref);
155
if (linkUrl.origin !== currentUrl.origin) {
156
continue;
157
}
158
} catch (_e) {
159
// if the link is not a valid URL, skip it
160
continue;
161
}
162
linkEl.addEventListener("click", () => {
163
QuartoDashboardUtils.showPage(linkHref);
164
return false;
165
});
166
}
167
const sidebar = window.document.querySelector(
168
".quarto-dashboard-content .bslib-sidebar-layout"
169
);
170
let prevWidth = window.document.body.clientWidth;
171
const sidebarCollapseClass = "sidebar-collapsed";
172
if (sidebar) {
173
const resizeObserver = new ResizeObserver(
174
throttle(function () {
175
const clientWidth = window.document.body.clientWidth;
176
if (prevWidth !== clientWidth) {
177
if (clientWidth <= 576) {
178
// Hide the sidebar
179
if (!sidebar.classList.contains(sidebarCollapseClass)) {
180
sidebar.classList.add(sidebarCollapseClass);
181
}
182
} else {
183
// Show the sidebar
184
if (sidebar.classList.contains(sidebarCollapseClass)) {
185
sidebar.classList.remove(sidebarCollapseClass);
186
}
187
}
188
prevWidth = clientWidth;
189
}
190
}, 2)
191
);
192
resizeObserver.observe(window.document.body);
193
}
194
195
const observer = new MutationObserver(function (mutations) {
196
mutations.forEach(function (mutation) {
197
mutation.addedNodes.forEach(function (addedNode) {
198
if (requiresFill(addedNode)) {
199
ensureWidgetFills(addedNode);
200
}
201
});
202
});
203
});
204
observer.observe(document.body, { childList: true, subtree: true });
205
});
206
207
// utils
208
window.QuartoDashboardUtils = {
209
setLocation: function (href) {
210
if (history && history.pushState) {
211
history.pushState({}, null, href);
212
// post "hashchange" for tools looking for it
213
if (window.parent?.postMessage) {
214
window.parent.postMessage(
215
{
216
type: "hashchange",
217
href: window.location.href,
218
},
219
"*"
220
);
221
}
222
} else {
223
window.location.replace(href);
224
}
225
setTimeout(function () {
226
window.scrollTo(0, 0);
227
}, 10);
228
},
229
isPage: function (hash) {
230
const tabPaneEl = document.querySelector(`.dashboard-page.tab-pane${hash}`);
231
return tabPaneEl !== null;
232
},
233
showPage: function (hash, fnCallback) {
234
// If the hash is empty, just select the first tab and activate that
235
if (hash === "") {
236
const firstTabPaneEl = document.querySelector(".dashboard-page.tab-pane");
237
if (firstTabPaneEl !== null) {
238
hash = `#${firstTabPaneEl.id}`;
239
}
240
}
241
242
// Find the tab and activate it
243
const tabNodes = document.querySelectorAll(".navbar .nav-item .nav-link");
244
for (const tabEl of tabNodes) {
245
const target = tabEl.getAttribute("data-bs-target");
246
if (target === hash) {
247
const scrolling = tabEl.getAttribute("data-scrolling");
248
if (scrolling !== null) {
249
updatePageFlow(scrolling.toLowerCase() === "true");
250
}
251
252
tabEl.classList.add("active");
253
} else {
254
tabEl.classList.remove("active");
255
}
256
}
257
258
// Find the tabpanes and activate the hash tab
259
const tabPaneNodes = document.querySelectorAll(".dashboard-page.tab-pane");
260
for (const tabPaneEl of tabPaneNodes) {
261
if (`#${tabPaneEl.id}` === hash) {
262
tabPaneEl.classList.add("active");
263
} else {
264
tabPaneEl.classList.remove("active");
265
}
266
}
267
268
if (fnCallback) {
269
fnCallback();
270
}
271
},
272
showLinkedValue: function (href) {
273
// check for a page link
274
if (this.isPage(href)) {
275
this.showPage(href);
276
} else {
277
window.open(href);
278
}
279
},
280
urlWithoutHash: function (url) {
281
const hashLoc = url.indexOf("#");
282
if (hashLoc != -1) return url.substring(0, hashLoc);
283
else return url;
284
},
285
urlHash: function (url) {
286
const hashLoc = url.indexOf("#");
287
if (hashLoc != -1) return url.substring(hashLoc);
288
else return "";
289
},
290
};
291
292
function throttle(func, wait) {
293
let waiting = false;
294
return function () {
295
if (!waiting) {
296
func.apply(this, arguments);
297
waiting = true;
298
setTimeout(function () {
299
waiting = false;
300
}, wait);
301
}
302
};
303
}
304
305