diff --git a/src/core/sass.ts b/src/core/sass.ts index 0de99a872a7..2d5bab5aa36 100644 --- a/src/core/sass.ts +++ b/src/core/sass.ts @@ -20,6 +20,7 @@ import { cssVarsBlock } from "./sass/add-css-vars.ts"; import { md5HashBytes } from "./hash.ts"; import { kSourceMappingRegexes } from "../config/constants.ts"; import { writeTextFileSyncPreserveMode } from "./write.ts"; +import { quartoConfig } from "../core/quarto.ts"; export interface SassVariable { name: string; @@ -107,35 +108,36 @@ export async function compileSass( // * Rules may use functions, variables, and mixins // (theme follows framework so it can override the framework rules) let scssInput = [ - '// quarto-scss-analysis-annotation { "origin": null }', + `// quarto-scss-analysis-annotation { "quarto-version": "${quartoConfig.version()}" }`, + '// quarto-scss-analysis-annotation { "origin": "\'use\' section from format" }', ...frameWorkUses, - '// quarto-scss-analysis-annotation { "origin": null }', + '// quarto-scss-analysis-annotation { "origin": "\'use\' section from Quarto" }', ...quartoUses, - '// quarto-scss-analysis-annotation { "origin": null }', + '// quarto-scss-analysis-annotation { "origin": "\'use\' section from user-defined SCSS" }', ...userUses, - '// quarto-scss-analysis-annotation { "origin": null }', + '// quarto-scss-analysis-annotation { "origin": "\'functions\' section from format" }', ...frameworkFunctions, - '// quarto-scss-analysis-annotation { "origin": null }', + '// quarto-scss-analysis-annotation { "origin": "\'functions\' section from Quarto" }', ...quartoFunctions, - '// quarto-scss-analysis-annotation { "origin": null }', + '// quarto-scss-analysis-annotation { "origin": "\'functions\' section from user-defined SCSS" }', ...userFunctions, - '// quarto-scss-analysis-annotation { "origin": null }', + '// quarto-scss-analysis-annotation { "origin": "Defaults from user-defined SCSS" }', ...userDefaults.reverse(), - '// quarto-scss-analysis-annotation { "origin": null }', + '// quarto-scss-analysis-annotation { "origin": "Defaults from Quarto\'s SCSS" }', ...quartoDefaults.reverse(), - '// quarto-scss-analysis-annotation { "origin": null }', + '// quarto-scss-analysis-annotation { "origin": "Defaults from the format SCSS" }', ...frameworkDefaults.reverse(), - '// quarto-scss-analysis-annotation { "origin": null }', + '// quarto-scss-analysis-annotation { "origin": "\'mixins\' section from format" }', ...frameworkMixins, - '// quarto-scss-analysis-annotation { "origin": null }', + '// quarto-scss-analysis-annotation { "origin": "\'mixins\' section from Quarto" }', ...quartoMixins, - '// quarto-scss-analysis-annotation { "origin": null }', + '// quarto-scss-analysis-annotation { "origin": "\'mixins\' section from user-defined SCSS" }', ...userMixins, - '// quarto-scss-analysis-annotation { "origin": null }', + '// quarto-scss-analysis-annotation { "origin": "\'rules\' section from format" }', ...frameworkRules, - '// quarto-scss-analysis-annotation { "origin": null }', + '// quarto-scss-analysis-annotation { "origin": "\'rules\' section from Quarto" }', ...quartoRules, - '// quarto-scss-analysis-annotation { "origin": null }', + '// quarto-scss-analysis-annotation { "origin": "\'rules\' section from user-defined SCSS" }', ...userRules, '// quarto-scss-analysis-annotation { "origin": null }', ].join("\n\n"); diff --git a/src/core/sass/analyzer/forward-annotations.ts b/src/core/sass/analyzer/forward-annotations.ts index ae6ad584480..f9c882ebc92 100644 --- a/src/core/sass/analyzer/forward-annotations.ts +++ b/src/core/sass/analyzer/forward-annotations.ts @@ -1,9 +1,10 @@ -import { annotateNode } from './ast-utils.ts'; +import { annotateNode } from "./ast-utils.ts"; export const forwardAnnotations = (ast: any) => { const pragmaAnnotation = "quarto-scss-analysis-annotation"; - const currentAnnotation: Record = {}; - let hasAnnotations: boolean = false + const annotationStack: Record[] = [{}]; + let currentAnnotation: Record = annotationStack[0]; + let hasAnnotations: boolean = false; for (const node of ast.children) { if (node.type === "comment_singleline") { const value = node?.value?.trim(); @@ -15,7 +16,18 @@ export const forwardAnnotations = (ast: any) => { console.error("Could not parse annotation payload", e); continue; } + if (payload.action === "push") { + annotationStack.push(JSON.parse(JSON.stringify(currentAnnotation))); + currentAnnotation = annotationStack[annotationStack.length - 1]; + } else if (payload.action === "pop" && annotationStack.length) { + annotationStack.pop(); + currentAnnotation = annotationStack[annotationStack.length - 1]; + } + for (const [key, value] of Object.entries(payload)) { + if (key === "action") { + continue; + } if (value === null) { delete currentAnnotation[key]; } else { @@ -30,4 +42,4 @@ export const forwardAnnotations = (ast: any) => { } } return ast; -} +}; diff --git a/src/core/sass/brand.ts b/src/core/sass/brand.ts index 34e268d643f..eeac5df274f 100644 --- a/src/core/sass/brand.ts +++ b/src/core/sass/brand.ts @@ -180,7 +180,10 @@ const brandColorBundle = ( key: string, nameMap: Record, ): SassBundleLayers => { - const colorVariables: string[] = ["/* color variables from _brand.yml */"]; + const colorVariables: string[] = [ + "/* color variables from _brand.yml */", + '// quarto-scss-analysis-annotation { "action": "push", "origin": "_brand.yml color" }', + ]; for (const colorKey of Object.keys(brand.data?.color?.palette ?? {})) { colorVariables.push( `$${colorKey}: ${brand.getColor(colorKey)} !default;`, @@ -204,6 +207,7 @@ const brandColorBundle = ( } } // const colorEntries = Object.keys(brand.color); + colorVariables.push('// quarto-scss-analysis-annotation { "action": "pop" }'); const colorBundle: SassBundleLayers = { key, // dependency: "bootstrap", @@ -224,6 +228,7 @@ const brandTypographyBundle = ( ): SassBundleLayers => { const typographyVariables: string[] = [ "/* typography variables from _brand.yml */", + '// quarto-scss-analysis-annotation { "action": "push", "origin": "_brand.yml typography" }', ]; const typographyImports: string[] = []; const fonts = brand.data?.typography?.fonts ?? []; @@ -266,7 +271,7 @@ const brandTypographyBundle = ( `Inconsisent Google font families found: ${googleFamily} and ${thisFamily}`, ); } - typographyVariables.push(googleFontImportString(resolvedFont)); + typographyImports.push(googleFontImportString(resolvedFont)); } if (googleFamily === "") { return undefined; @@ -321,12 +326,14 @@ const brandTypographyBundle = ( ["family", "font-family-base"], ["size", "font-size-base"], ["line-height", "line-height-base"], + ["weight", "font-weight-base"], // revealjs ["family", "mainFont"], ["size", "presentation-font-size-root"], ["line-height", "presentation-line-height"], // TBD? + // ["style", "font-style-base"], // ["weight", "font-weight-base"], ], @@ -351,8 +358,16 @@ const brandTypographyBundle = ( ["family", "font-family-monospace"], // bootstrap ["size", "code-font-size"], + // forward explicitly to both `code` and `pre` + // because that interacts less with the default bootstrap styles + ["color", "code-color"], // this is also revealjs + ["color", "pre-color"], + + ["weight", "font-weight-monospace"], + // revealjs ["size", "code-block-font-size"], + ["color", "code-block-color"], ], "monospace-block": [ // bootstrap + revealjs @@ -361,6 +376,8 @@ const brandTypographyBundle = ( ["line-height", "pre-line-height"], ["color", "pre-color"], ["background-color", "pre-bg"], + ["size", "code-block-font-size"], + ["weight", "font-weight-monospace-block"], // revealjs ["line-height", "code-block-line-height"], ["color", "code-block-color"], @@ -373,18 +390,20 @@ const brandTypographyBundle = ( ["background-color", "code-bg"], // bootstrap ["size", "code-inline-font-size"], + ["weight", "font-weight-monospace-inline"], // revealjs - ["size", "code-block-font-size"], + // ["size", "code-block-font-size"], ], }; for ( const kind of [ - "base", - "headings", - "monospace", + // more specific entries go first "monospace-block", "monospace-inline", + "monospace", + "headings", + "base", ] ) { const fontInformation = resolveHTMLFontInformation( @@ -408,6 +427,9 @@ const brandTypographyBundle = ( } } + typographyVariables.push( + '// quarto-scss-analysis-annotation { "action": "pop" }', + ); const typographyBundle: SassBundleLayers = { key, // dependency: "bootstrap", diff --git a/src/format/reveal/format-reveal-theme.ts b/src/format/reveal/format-reveal-theme.ts index 79c63534397..2b1b6fcd21c 100644 --- a/src/format/reveal/format-reveal-theme.ts +++ b/src/format/reveal/format-reveal-theme.ts @@ -18,7 +18,7 @@ import { import { isFileRef } from "../../core/http.ts"; import { pathWithForwardSlashes } from "../../core/path.ts"; -import { formatResourcePath } from "../../core/resources.ts"; +import { formatResourcePath, resourcePath } from "../../core/resources.ts"; import { cleanSourceMappingUrl, compileSass, @@ -164,6 +164,7 @@ export async function revealTheme( const quartoLayers = [ quartoBaseLayer(format, true, true, false, true), quartoLayer(), + quartoRevealBrandLayer(), ]; const titleSlideLayer = titleSlideScss(format); if (titleSlideLayer) { @@ -284,3 +285,9 @@ function quartoLayer(): SassLayer { function themeLayer(theme: string): SassLayer { return sassLayerFile(theme); } + +function quartoRevealBrandLayer(): SassLayer { + return sassLayerFile( + resourcePath(join("formats", "revealjs", "brand", "brand.scss")), + ); +} diff --git a/src/resources/formats/html/bootstrap/_bootstrap-rules.scss b/src/resources/formats/html/bootstrap/_bootstrap-rules.scss index 181f47d5095..b1332b5d71d 100644 --- a/src/resources/formats/html/bootstrap/_bootstrap-rules.scss +++ b/src/resources/formats/html/bootstrap/_bootstrap-rules.scss @@ -2144,3 +2144,20 @@ code.sourceCode a.code-annotation-anchor { #quarto-back-to-top { z-index: 1000; } + +// override _reboot.scss + +// code blocks +pre code { + font-family: $font-family-monospace-block; + // I'm really not confident that this is correct + @include font-size($code-block-font-size); + font-weight: $font-weight-monospace-block; +} + +// code inlines +p code { + font-family: $font-family-monospace-inline; + @include font-size($code-inline-font-size); + font-weight: $font-weight-monospace-inline; +} diff --git a/src/resources/formats/html/bootstrap/_bootstrap-variables.scss b/src/resources/formats/html/bootstrap/_bootstrap-variables.scss index f387a71c133..57713008985 100644 --- a/src/resources/formats/html/bootstrap/_bootstrap-variables.scss +++ b/src/resources/formats/html/bootstrap/_bootstrap-variables.scss @@ -244,4 +244,23 @@ $table-group-separator-color-lighter: lighten( $bootstrap-version: 5; -$h1h2h3-font-weight: 600 !default; \ No newline at end of file +$h1h2h3-font-weight: 600 !default; + +// variables required by _brand.yml + +// these variables need to have been defined here already +// and are repeated in the framework's own _variables.scss +// This will require us to monitor framework changes +// to avoid drift +$font-weight-base: 400 !default; +$small-font-size: 0.875em !default; +$code-font-size: $small-font-size !default; +$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default; + +$font-family-monospace-block: $font-family-monospace !default; +$font-family-monospace-inline: $font-family-monospace !default; +$font-weight-monospace: $font-weight-base !default; +$font-weight-monospace-block: $font-weight-monospace !default; +$font-weight-monospace-inline: $font-weight-monospace !default; +$code-block-font-size: $code-font-size !default; +$code-inline-font-size: $code-font-size !default; diff --git a/src/resources/formats/revealjs/brand/brand.scss b/src/resources/formats/revealjs/brand/brand.scss new file mode 100644 index 00000000000..496ad11aca2 --- /dev/null +++ b/src/resources/formats/revealjs/brand/brand.scss @@ -0,0 +1,11 @@ +/*-- scss:uses --*/ + +// this file is currently intentionally empty + +/*-- scss:mixins --*/ + +/*-- scss:functions --*/ + +/*-- scss:defaults --*/ + +/*-- scss:rules --*/ diff --git a/src/resources/formats/revealjs/quarto.scss b/src/resources/formats/revealjs/quarto.scss index 1a3b6bddc67..4f315074d3c 100644 --- a/src/resources/formats/revealjs/quarto.scss +++ b/src/resources/formats/revealjs/quarto.scss @@ -13,6 +13,14 @@ $presentation-font-size-root: 40px !default; $presentation-font-smaller: 0.7 !default; $presentation-line-height: 1.3 !default; +// Default variables which exist in bootstrap themes +// and are here to simplify the implementation of _brand.yml and +// user theming customization in general +$font-weight-base: 400 !default; +$code-font-size: $presentation-font-size-root !default; +$font-family-monospace-block: $font-family-monospace !default; +$font-family-monospace-inline: $font-family-monospace !default; + // main colors $body-bg: #fff !default; $body-color: #222 !default; @@ -66,10 +74,11 @@ $presentation-list-bullet-color: $body-color !default; // code blocks $code-block-bg: $body-bg !default; $code-block-border-color: lighten($body-color, 60%) !default; -$code-block-font-size: 0.55em !default; +$code-block-font-size: ($code-font-size * 0.55) !default; $code-block-height: 500px !default; $code-block-theme-dark-threshhold: 40% !default; $code-block-line-height: $presentation-line-height !default; +$code-block-color: $body-color !default; // inline code $code-color: var(--quarto-hl-fu-color) !default; @@ -146,6 +155,8 @@ $heading3Size: $revealjs-h3-font-size !default; $heading4Size: $revealjs-h4-font-size !default; $codeFont: $font-family-monospace !default; +$inlineCodeFont: $font-family-monospace-inline !default; +$blockCodeFont: $font-family-monospace-block !default; // Links and actions $linkColor: $link-color !default; @@ -169,6 +180,14 @@ $kbd-font-size: $presentation-font-size-root !default; $kbd-color: $body-color !default; $kbd-bg: $gray-100 !default; // like in bootstrap style +// variables required by _brand.yml +$font-family-monospace-block: $font-family-monospace !default; +$font-family-monospace-inline: $font-family-monospace !default; +$font-weight-monospace: $font-weight-base !default; +$font-weight-monospace-block: $font-weight-monospace !default; +$font-weight-monospace-inline: $font-weight-monospace !default; +$code-inline-font-size: $code-font-size !default; + /*-- scss:functions --*/ @function colorToRGB($color) { @@ -303,6 +322,7 @@ div.reveal div.slides section.quarto-title-block { .reveal code { color: $code-color; + font-size: $code-inline-font-size; background-color: $code-bg; white-space: pre-wrap; } @@ -319,7 +339,8 @@ div.reveal div.slides section.quarto-title-block { .reveal pre code { background-color: $body-bg; - color: $body-color; + font-size: $code-block-font-size; + color: $code-block-color; } .reveal .column-output-location { @@ -791,3 +812,16 @@ kbd { border-radius: 5px; padding: $kbd-padding-y $kbd-padding-x; } + +:root { + --r-inline-code-font: #{$inlineCodeFont}; + --r-block-code-font: #{$blockCodeFont}; +} + +.reveal code { + font-family: var(--r-inline-code-font); +} + +.reveal pre code { + font-family: var(--r-block-code-font); +} diff --git a/tools/sass-variable-explainer/forward-annotations.ts b/tools/sass-variable-explainer/forward-annotations.ts index ae6ad584480..90fa83e2ce6 100644 --- a/tools/sass-variable-explainer/forward-annotations.ts +++ b/tools/sass-variable-explainer/forward-annotations.ts @@ -2,8 +2,9 @@ import { annotateNode } from './ast-utils.ts'; export const forwardAnnotations = (ast: any) => { const pragmaAnnotation = "quarto-scss-analysis-annotation"; - const currentAnnotation: Record = {}; - let hasAnnotations: boolean = false + const annotationStack: Record[] = [{}]; + let currentAnnotation: Record = annotationStack[0]; + let hasAnnotations: boolean = false; for (const node of ast.children) { if (node.type === "comment_singleline") { const value = node?.value?.trim(); @@ -15,7 +16,17 @@ export const forwardAnnotations = (ast: any) => { console.error("Could not parse annotation payload", e); continue; } + if (payload.action === "push") { + annotationStack.push(JSON.parse(JSON.stringify(currentAnnotation))); + currentAnnotation = annotationStack[annotationStack.length - 1]; + } else if (payload.action === "pop" && annotationStack.length) { + annotationStack.pop(); + currentAnnotation = annotationStack[annotationStack.length - 1]; + } for (const [key, value] of Object.entries(payload)) { + if (key === "action") { + continue; + } if (value === null) { delete currentAnnotation[key]; } else { diff --git a/tools/sass-variable-explainer/sass-ui.qmd b/tools/sass-variable-explainer/sass-ui.qmd index ea08b0baf08..eb80145841c 100644 --- a/tools/sass-variable-explainer/sass-ui.qmd +++ b/tools/sass-variable-explainer/sass-ui.qmd @@ -105,13 +105,13 @@ selectedDependencyTitle = { if (selectedDependency === undefined) { return htl.html`
Select a single variable to analyze
`; } else { - return htl.html`
Variable analysis for ${selectedDependency?.node?.property?.variable?.value ?? "unknown"}
`; + return htl.html`
Variable analysis for ${selectedDependency?.node?.property?.variable?.value ?? "unknown"}
`; } } renderedAnalysis = { deps; debugger; - const result = htl.html`
`; + const result = htl.html`
`; if (!selectedDependencyChains.length) { return result; } @@ -124,7 +124,7 @@ renderedAnalysis = { } if (selectedDependency?.node?.annotation?.origin) { - result.appendChild(htl.html`
Origin: ${selectedDependency?.node?.annotation?.origin}
`); + result.appendChild(htl.html`
Origin: ${selectedDependency?.node?.annotation?.origin}
`); } const lineEnd = Math.max( @@ -133,14 +133,14 @@ renderedAnalysis = { selectedDependency?.node?.value?.lineEnd ?? -Infinity, ); if (selectedDependency?.node?.line !== undefined && lineEnd !== -Infinity) { - result.appendChild(htl.html`
SCSS source
`); + result.appendChild(htl.html`
SCSS source:
`); const snippet = sourceLines.slice(selectedDependency?.node?.line - 1, lineEnd).join("\n"); result.appendChild(htl.html`
${snippet}
`) } // Dependencies - result.appendChild(htl.html`
Dependencies:
`); + result.appendChild(htl.html`
Dependencies:
`); const depsDiv = htl.html`
`; result.appendChild(depsDiv); const seenDependencies = new Set([]);