From 1940d874cedbbc27e92ab3ebe66287dc7645b5a2 Mon Sep 17 00:00:00 2001 From: Charles Teague Date: Wed, 12 Jul 2023 19:30:47 -0400 Subject: [PATCH] HTML TOC improvements - Improve the appearance of TOC tools - Properly deal with TOC tools when the TOC is `left-body` or `right-body` --- src/format/html/format-html-bootstrap.ts | 15 +++- .../types/website/website-navigation.ts | 47 ++++++----- .../html/bootstrap/_bootstrap-rules.scss | 68 +++++++++++++-- .../html/bootstrap/_bootstrap-variables.scss | 2 + .../website/navigation/quarto-nav.scss | 82 +++++-------------- 5 files changed, 119 insertions(+), 95 deletions(-) diff --git a/src/format/html/format-html-bootstrap.ts b/src/format/html/format-html-bootstrap.ts index cc599ae74e..17109afd26 100644 --- a/src/format/html/format-html-bootstrap.ts +++ b/src/format/html/format-html-bootstrap.ts @@ -285,7 +285,7 @@ function bootstrapHtmlPostprocessor( } // move the toc if there is a sidebar - let toc = doc.querySelector('nav[role="doc-toc"]'); + const toc = doc.querySelector('nav[role="doc-toc"]'); const tocTarget = doc.getElementById("quarto-toc-target"); @@ -294,9 +294,16 @@ function bootstrapHtmlPostprocessor( if (toc && tocTarget) { if (useDoubleToc) { - const clonedToc = toc.cloneNode(true); - toc.id = "TOC-body"; - toc = clonedToc as Element; + // Clone the TOC + // Leave it where it is in the document, and just mutate it + const clonedToc = toc.cloneNode(true) as Element; + clonedToc.id = "TOC-body"; + const tocActionsEl = clonedToc.querySelector(".toc-actions"); + if (tocActionsEl) { + tocActionsEl.remove(); + } + + toc.parentElement?.insertBefore(clonedToc, toc); } // activate selection behavior for this diff --git a/src/project/types/website/website-navigation.ts b/src/project/types/website/website-navigation.ts index e54a3ffdd1..f425181ce9 100644 --- a/src/project/types/website/website-navigation.ts +++ b/src/project/types/website/website-navigation.ts @@ -4,7 +4,7 @@ * Copyright (C) 2020-2022 Posit Software, PBC */ -import { basename, dirname, extname, join, relative } from "path/mod.ts"; +import { basename, join, relative } from "path/mod.ts"; import { warning } from "log/mod.ts"; import * as ld from "../../../core/lodash.ts"; @@ -14,7 +14,7 @@ import { pathWithForwardSlashes, safeExistsSync } from "../../../core/path.ts"; import { resourcePath } from "../../../core/resources.ts"; import { renderEjs } from "../../../core/ejs.ts"; import { warnOnce } from "../../../core/log.ts"; -import { asHtmlId, getDecodedAttribute } from "../../../core/html.ts"; +import { asHtmlId } from "../../../core/html.ts"; import { sassLayer } from "../../../core/sass.ts"; import { removeChapterNumber } from "./website-utils.ts"; @@ -127,7 +127,6 @@ import { createMarkdownPipeline, MarkdownPipeline, } from "./website-pipeline-md.ts"; -import { engineValidExtensions } from "../../../execute/engine.ts"; import { TempContext } from "../../../core/temp.ts"; import { HtmlPostProcessResult } from "../../../command/render/types.ts"; import { isJupyterNotebook } from "../../../core/jupyter/jupyter.ts"; @@ -674,28 +673,30 @@ function handleRepoLinks( }]; const actionsDiv = doc.createElement("div"); actionsDiv.classList.add("toc-actions"); - if (repoInfo) { - const iconDiv = doc.createElement("div"); - const iconEl = doc.createElement("i"); - iconEl.classList.add("bi"); - iconEl.classList.add("bi-" + repoUrlIcon(repoInfo.baseUrl)); - - iconDiv.appendChild(iconEl); - actionsDiv.appendChild(iconDiv); - } - const linksDiv = doc.createElement("div"); - linksDiv.classList.add("action-links"); + const ulEl = doc.createElement("ul"); links.forEach((link) => { const a = doc.createElement("a"); a.setAttribute("href", link.url); a.classList.add("toc-action"); a.innerHTML = link.text; - const p = doc.createElement("p"); - p.appendChild(a); - linksDiv.appendChild(p); + + const i = doc.createElement("i"); + i.classList.add("bi"); + if (link.icon) { + i.classList.add(`bi-${link.icon}`); + } else { + i.classList.add(`empty`); + } + + a.prepend(i); + + const liEl = doc.createElement("li"); + liEl.appendChild(a); + + ulEl.appendChild(liEl); }); - actionsDiv.appendChild(linksDiv); + actionsDiv.appendChild(ulEl); repoTarget.appendChild(actionsDiv); } } @@ -722,14 +723,15 @@ function repoActionLinks( source: string, language: FormatLanguage, issueUrl?: string, -): Array<{ text: string; url: string }> { - return actions.map((action) => { +): Array<{ text: string; url: string; icon?: string }> { + return actions.map((action, i) => { switch (action) { case "edit": if (!isJupyterNotebook(source)) { return { text: language[kRepoActionLinksEdit], url: `${repoInfo.baseUrl}edit/${branch}/${repoInfo.path}${source}`, + icon: i === 0 ? "github" : undefined, }; } else if (repoInfo.baseUrl.indexOf("github.com") !== -1) { return { @@ -737,6 +739,7 @@ function repoActionLinks( url: `${ repoInfo.baseUrl.replace("github.com", "github.dev") }blob/${branch}/${repoInfo.path}${source}`, + icon: i === 0 ? "github" : undefined, }; } else { return null; @@ -745,11 +748,13 @@ function repoActionLinks( return { text: language[kRepoActionLinksSource], url: `${repoInfo.baseUrl}blob/${branch}/${repoInfo.path}${source}`, + icon: i === 0 ? "github" : undefined, }; case "issue": return { text: language[kRepoActionLinksIssue], url: issueUrl || `${repoInfo.baseUrl}issues/new`, + icon: i === 0 ? "github" : undefined, }; default: { @@ -758,7 +763,7 @@ function repoActionLinks( } } }).filter((action) => action !== null) as Array< - { text: string; url: string } + { text: string; url: string; icon: string } >; } diff --git a/src/resources/formats/html/bootstrap/_bootstrap-rules.scss b/src/resources/formats/html/bootstrap/_bootstrap-rules.scss index e5ad8885ed..647060416c 100644 --- a/src/resources/formats/html/bootstrap/_bootstrap-rules.scss +++ b/src/resources/formats/html/bootstrap/_bootstrap-rules.scss @@ -1071,10 +1071,19 @@ figure .quarto-notebook-link { background-size: 0.75rem 0.75rem; } +.toc-actions i.bi, .quarto-other-links i.bi, .quarto-alternate-notebooks i.bi, .quarto-alternate-formats i.bi { margin-right: 0.4em; + font-size: $toc-tools-font-size; +} + +.toc-actions i.bi.empty, +.quarto-other-links i.bi.empty, +.quarto-alternate-notebooks i.bi.empty, +.quarto-alternate-formats i.bi.empty { + padding-left: 1em; } .quarto-notebook .cell-container { @@ -1099,25 +1108,34 @@ figure .quarto-notebook-link { border-bottom: none; } +.toc-actions, +.quarto-alternate-formats, +.quarto-other-links, +.quarto-alternate-notebooks { + padding-left: 0em; +} +.sidebar .toc-actions a, .sidebar .quarto-alternate-formats a, .sidebar .quarto-other-links a, -.sidebar .quarto-alternate-notebooks a { +.sidebar .quarto-alternate-notebooks a, +.sidebar nav[role="doc-toc"] a { text-decoration: none; } +.sidebar .toc-actions a:hover, .sidebar .quarto-other-links a:hover, .sidebar .quarto-alternate-formats a:hover, .sidebar .quarto-alternate-notebooks a:hover { color: $link-color; } +.sidebar .toc-actions h2, .sidebar .quarto-other-links h2, .sidebar .quarto-alternate-notebooks h2, .sidebar .quarto-alternate-formats h2, .sidebar nav[role="doc-toc"] > h2 { - font-size: $toc-font-size; - font-weight: 400; - margin-bottom: 0.5rem; + font-weight: 500; + margin-bottom: 0.2rem; margin-top: 0.3rem; font-family: inherit; border-bottom: 0; @@ -1125,10 +1143,15 @@ figure .quarto-notebook-link { padding-top: 0px; } -.sidebar .quarto-other-links h2, -.sidebar .quarto-alternate-notebooks h2, -.sidebar .quarto-alternate-formats h2 { - margin-top: 1rem; +.sidebar .toc-actions > h2, +.sidebar .quarto-other-links > h2, +.sidebar .quarto-alternate-notebooks > h2, +.sidebar .quarto-alternate-formats > h2 { + font-size: $toc-tools-font-size; +} + +.sidebar nav[role="doc-toc"] > h2 { + font-size: $toc-font-size; } .sidebar nav[role="doc-toc"] > ul a { @@ -1136,6 +1159,7 @@ figure .quarto-notebook-link { padding-left: 0.6rem; } +.sidebar .toc-actions h2 > ul a, .sidebar .quarto-other-links h2 > ul a, .sidebar .quarto-alternate-notebooks h2 > ul a, .sidebar .quarto-alternate-formats h2 > ul a { @@ -1143,6 +1167,7 @@ figure .quarto-notebook-link { padding-left: 0.6rem; } +.sidebar .toc-actions ul a:empty, .sidebar .quarto-other-links ul a:empty, .sidebar .quarto-alternate-notebooks ul a:empty, .sidebar .quarto-alternate-formats ul a:empty, @@ -1150,15 +1175,40 @@ figure .quarto-notebook-link { display: none; } +.sidebar .toc-actions ul, .sidebar .quarto-other-links ul, .sidebar .quarto-alternate-notebooks ul, -.sidebar .quarto-alternate-formats ul, +.sidebar .quarto-alternate-formats ul { + padding-left: 0; + list-style: none; +} + .sidebar nav[role="doc-toc"] ul { + list-style: none; padding-left: 0; list-style: none; +} + +.sidebar nav[role="doc-toc"] > ul { + margin-left: 0.4em; +} + +.quarto-margin-sidebar nav[role="doc-toc"] { + padding-left: 0.5em; +} + +.sidebar .toc-actions > ul, +.sidebar .quarto-other-links > ul, +.sidebar .quarto-alternate-notebooks > ul, +.sidebar .quarto-alternate-formats > ul { + font-size: $toc-tools-font-size; +} + +.sidebar nav[role="doc-toc"] > ul { font-size: $toc-font-size; } +.sidebar .toc-actions ul li a, .sidebar .quarto-other-links ul li a, .sidebar .quarto-alternate-notebooks ul li a, .sidebar .quarto-alternate-formats ul li a, diff --git a/src/resources/formats/html/bootstrap/_bootstrap-variables.scss b/src/resources/formats/html/bootstrap/_bootstrap-variables.scss index 14318662cf..be97c1639f 100644 --- a/src/resources/formats/html/bootstrap/_bootstrap-variables.scss +++ b/src/resources/formats/html/bootstrap/_bootstrap-variables.scss @@ -30,6 +30,8 @@ $toc-font-size: 0.875rem !default; $toc-active-border: $toc-color !default; $toc-inactive-border: $gray-200 !default; +$toc-tools-font-size: 0.8rem !default; + /* Callout customization */ // Formatting $callout-border-width: 5px !default; diff --git a/src/resources/projects/website/navigation/quarto-nav.scss b/src/resources/projects/website/navigation/quarto-nav.scss index d80866c8dd..e6c7c14be3 100644 --- a/src/resources/projects/website/navigation/quarto-nav.scss +++ b/src/resources/projects/website/navigation/quarto-nav.scss @@ -66,10 +66,6 @@ footer.footer .nav-footer, padding-right: 1em; } -nav[role="doc-toc"] { - padding-left: 0.5em; -} - // content padding #quarto-content > * { padding-top: $content-padding-top; @@ -728,68 +724,32 @@ nav.sidebar.sidebar-navigation:not(.rollup) { color: $link-hover-color; } -.toc-actions { - display: flex; -} - -.toc-actions p { - margin-block-start: 0; - margin-block-end: 0; -} - -.toc-actions a { - text-decoration: none; - color: inherit; - font-weight: 400; -} - -.toc-actions a:hover { - color: $link-hover-color; -} - -.toc-actions .action-links { - margin-left: 4px; -} - -.sidebar nav[role="doc-toc"] .toc-actions .bi { - margin-left: -4px; - font-size: 0.7rem; - color: $text-muted; -} - -.sidebar nav[role="doc-toc"] .toc-actions .bi:before { - padding-top: 3px; -} - -#quarto-margin-sidebar .toc-actions .bi:before { - margin-top: 0.3rem; - font-size: 0.7rem; - color: $text-muted; - vertical-align: top; -} - -.sidebar nav[role="doc-toc"] .toc-actions > div:first-of-type { - margin-top: -3px; -} - -#quarto-margin-sidebar .toc-actions p, -.sidebar nav[role="doc-toc"] .toc-actions p { - font-size: $toc-font-size; -} - .nav-footer .toc-actions { - :first-child { - margin-left: auto; - } - :last-child { - margin-right: auto; + a, + a:hover { + text-decoration: none; } - .action-links { + + ul { display: flex; - p { + list-style: none; + + :first-child { + margin-left: auto; + } + :last-child { + margin-right: auto; + } + + li { padding-right: 1.5em; + + i.bi { + padding-right: 0.4em; + } } - p:last-of-type { + + li:last-of-type { padding-right: 0; } }