Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/format/dashboard/format-dashboard-page.ts
6451 views
1
/*
2
* format-dashboard-page.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*/
6
7
import { Document, Element } from "../../core/deno-dom.ts";
8
import { recursiveApplyFillClasses } from "./format-dashboard-layout.ts";
9
import {
10
DashboardMeta,
11
kDashboardGridSkip,
12
makeEl,
13
} from "./format-dashboard-shared.ts";
14
15
const kPageClass = "dashboard-page";
16
const kAttrTitle = "data-title";
17
const kAttrScrolling = "data-scrolling";
18
19
const kDashboardPagesClass = "quarto-dashboard-pages";
20
21
interface NavItem {
22
id: string;
23
text: string;
24
active: boolean;
25
scrolling: boolean;
26
}
27
28
export function processPages(doc: Document, dashboardMeta: DashboardMeta) {
29
// Find the pages, if any
30
const pageNodes = doc.querySelectorAll(`.${kPageClass}`);
31
if (pageNodes.length === 0) {
32
// No pages to process, audi 5000
33
return;
34
}
35
36
// Decorate the container
37
const contentEl = doc.querySelector(".quarto-dashboard-content");
38
if (contentEl !== null) {
39
contentEl.classList.add(kDashboardPagesClass);
40
}
41
42
// Find the navbar, which will be using to make navigation
43
const navbarEl = doc.querySelector("#quarto-dashboard-header .navbar");
44
if (!navbarEl) {
45
throw new Error(
46
"Expected a navbar in the dashboard output since pages are specified.",
47
);
48
}
49
50
// The target container
51
const navbarContainerEl = navbarEl.querySelector(".navbar-container");
52
if (!navbarContainerEl) {
53
throw new Error(
54
"Expected the navbar to have a container marked with `.navbar-container`.",
55
);
56
}
57
58
// Mark the toggle button visible
59
const navbarTogglerEl = navbarEl.querySelector(".navbar-toggler");
60
if (navbarTogglerEl) {
61
navbarTogglerEl.classList.remove("hidden");
62
}
63
64
// Add a dark mode toggle if needed
65
// If dark and light themes are provided, inject a toggle into the correct spot
66
if (dashboardMeta.hasDarkMode) {
67
const toggleEl = makeEl("a", {
68
classes: ["quarto-color-scheme-toggle"],
69
attributes: {
70
href: "",
71
onclick: "window.quartoToggleColorScheme(); return false;",
72
},
73
}, doc);
74
75
const iEl = makeEl("i", { classes: ["bi"] }, doc);
76
toggleEl.append(iEl);
77
navbarContainerEl.append(toggleEl);
78
}
79
80
// Build up the navigation descriptors, marking up the pages as we go
81
const navItems: NavItem[] = [];
82
let counter = 1;
83
for (const pageNode of pageNodes) {
84
const pageEl = pageNode as Element;
85
86
const scrolling = pageEl.getAttribute(kAttrScrolling) !== null
87
? pageEl.getAttribute(kAttrScrolling) === "true"
88
: dashboardMeta.scrolling;
89
const id = pageEl.id ? pageEl.id : "dashboard-page-" + counter;
90
const text = pageEl.getAttribute(kAttrTitle);
91
pageEl.removeAttribute(kAttrTitle);
92
const active = counter === 1;
93
94
// Set up the page to be collapsible
95
pageEl.parentElement?.classList.add("tab-content");
96
pageEl.id = id;
97
pageEl.classList.add("tab-pane");
98
pageEl.setAttribute("aria-labelledby", `tab-${id}`);
99
if (active) {
100
pageEl.classList.add("show");
101
pageEl.classList.add("active");
102
} else {
103
pageEl.classList.add(kDashboardGridSkip);
104
}
105
recursiveApplyFillClasses(pageEl);
106
107
navItems.push({
108
id,
109
text: text !== null ? text : "Page " + counter,
110
active,
111
scrolling,
112
});
113
counter++;
114
}
115
116
// Generate the navigation
117
const navUlEl = makeEl("ul", {
118
classes: ["navbar-nav", "navbar-nav-scroll", "me-auto"],
119
attributes: { role: "tablist" },
120
}, doc);
121
for (const navItem of navItems) {
122
navUlEl.append(toNav(navItem, doc));
123
}
124
125
// Generate a collapsible region
126
const collapseEl = makeEl("div", {
127
id: "dashboard-collapse",
128
classes: ["navbar-collapse", "collapse"],
129
}, doc);
130
collapseEl.append(navUlEl);
131
132
navbarContainerEl.append(collapseEl);
133
}
134
135
function toNav(navItem: NavItem, doc: Document) {
136
const liEl = makeEl("li", {
137
classes: ["nav-item"],
138
attributes: { role: "presentation" },
139
}, doc);
140
141
const classes = ["nav-link"];
142
if (navItem.active) {
143
classes.push("active");
144
}
145
146
const aEl = makeEl("a", {
147
id: `tab-${navItem.id}`,
148
classes,
149
attributes: {
150
"data-bs-toggle": "tab",
151
"role": "tab",
152
"data-bs-target": `#${navItem.id}`,
153
[kAttrScrolling]: navItem.scrolling.toString(),
154
"href": `#${navItem.id}`,
155
"aria-controls": navItem.id,
156
"aria-selected": navItem.active.toString(),
157
},
158
}, doc);
159
160
const spanEl = makeEl("span", {
161
classes: ["nav-link-text"],
162
}, doc);
163
spanEl.innerText = navItem.text;
164
aEl.append(spanEl);
165
liEl.append(aEl);
166
return liEl;
167
}
168
169