Path: blob/main/src/format/dashboard/format-dashboard-page.ts
6451 views
/*1* format-dashboard-page.ts2*3* Copyright (C) 2020-2022 Posit Software, PBC4*/56import { Document, Element } from "../../core/deno-dom.ts";7import { recursiveApplyFillClasses } from "./format-dashboard-layout.ts";8import {9DashboardMeta,10kDashboardGridSkip,11makeEl,12} from "./format-dashboard-shared.ts";1314const kPageClass = "dashboard-page";15const kAttrTitle = "data-title";16const kAttrScrolling = "data-scrolling";1718const kDashboardPagesClass = "quarto-dashboard-pages";1920interface NavItem {21id: string;22text: string;23active: boolean;24scrolling: boolean;25}2627export function processPages(doc: Document, dashboardMeta: DashboardMeta) {28// Find the pages, if any29const pageNodes = doc.querySelectorAll(`.${kPageClass}`);30if (pageNodes.length === 0) {31// No pages to process, audi 500032return;33}3435// Decorate the container36const contentEl = doc.querySelector(".quarto-dashboard-content");37if (contentEl !== null) {38contentEl.classList.add(kDashboardPagesClass);39}4041// Find the navbar, which will be using to make navigation42const navbarEl = doc.querySelector("#quarto-dashboard-header .navbar");43if (!navbarEl) {44throw new Error(45"Expected a navbar in the dashboard output since pages are specified.",46);47}4849// The target container50const navbarContainerEl = navbarEl.querySelector(".navbar-container");51if (!navbarContainerEl) {52throw new Error(53"Expected the navbar to have a container marked with `.navbar-container`.",54);55}5657// Mark the toggle button visible58const navbarTogglerEl = navbarEl.querySelector(".navbar-toggler");59if (navbarTogglerEl) {60navbarTogglerEl.classList.remove("hidden");61}6263// Add a dark mode toggle if needed64// If dark and light themes are provided, inject a toggle into the correct spot65if (dashboardMeta.hasDarkMode) {66const toggleEl = makeEl("a", {67classes: ["quarto-color-scheme-toggle"],68attributes: {69href: "",70onclick: "window.quartoToggleColorScheme(); return false;",71},72}, doc);7374const iEl = makeEl("i", { classes: ["bi"] }, doc);75toggleEl.append(iEl);76navbarContainerEl.append(toggleEl);77}7879// Build up the navigation descriptors, marking up the pages as we go80const navItems: NavItem[] = [];81let counter = 1;82for (const pageNode of pageNodes) {83const pageEl = pageNode as Element;8485const scrolling = pageEl.getAttribute(kAttrScrolling) !== null86? pageEl.getAttribute(kAttrScrolling) === "true"87: dashboardMeta.scrolling;88const id = pageEl.id ? pageEl.id : "dashboard-page-" + counter;89const text = pageEl.getAttribute(kAttrTitle);90pageEl.removeAttribute(kAttrTitle);91const active = counter === 1;9293// Set up the page to be collapsible94pageEl.parentElement?.classList.add("tab-content");95pageEl.id = id;96pageEl.classList.add("tab-pane");97pageEl.setAttribute("aria-labelledby", `tab-${id}`);98if (active) {99pageEl.classList.add("show");100pageEl.classList.add("active");101} else {102pageEl.classList.add(kDashboardGridSkip);103}104recursiveApplyFillClasses(pageEl);105106navItems.push({107id,108text: text !== null ? text : "Page " + counter,109active,110scrolling,111});112counter++;113}114115// Generate the navigation116const navUlEl = makeEl("ul", {117classes: ["navbar-nav", "navbar-nav-scroll", "me-auto"],118attributes: { role: "tablist" },119}, doc);120for (const navItem of navItems) {121navUlEl.append(toNav(navItem, doc));122}123124// Generate a collapsible region125const collapseEl = makeEl("div", {126id: "dashboard-collapse",127classes: ["navbar-collapse", "collapse"],128}, doc);129collapseEl.append(navUlEl);130131navbarContainerEl.append(collapseEl);132}133134function toNav(navItem: NavItem, doc: Document) {135const liEl = makeEl("li", {136classes: ["nav-item"],137attributes: { role: "presentation" },138}, doc);139140const classes = ["nav-link"];141if (navItem.active) {142classes.push("active");143}144145const aEl = makeEl("a", {146id: `tab-${navItem.id}`,147classes,148attributes: {149"data-bs-toggle": "tab",150"role": "tab",151"data-bs-target": `#${navItem.id}`,152[kAttrScrolling]: navItem.scrolling.toString(),153"href": `#${navItem.id}`,154"aria-controls": navItem.id,155"aria-selected": navItem.active.toString(),156},157}, doc);158159const spanEl = makeEl("span", {160classes: ["nav-link-text"],161}, doc);162spanEl.innerText = navItem.text;163aEl.append(spanEl);164liEl.append(aEl);165return liEl;166}167168169