diff --git a/src/command/render/pandoc-html.ts b/src/command/render/pandoc-html.ts index 14434980f45..36e7294f78b 100644 --- a/src/command/render/pandoc-html.ts +++ b/src/command/render/pandoc-html.ts @@ -88,28 +88,45 @@ export async function resolveSassBundles( // the brand bundle itself doesn't have any 'brand' entries; // those are used to specify where the brand-specific layers should be inserted // in the final bundle. - const brandLayersMaybeBrand = bundlesWithBrand.find((bundle) => + const maybeBrandBundle = bundlesWithBrand.find((bundle) => bundle.key === "brand" - )?.user || []; - assert(!brandLayersMaybeBrand.find((v) => v === "brand")); - const brandLayers = brandLayersMaybeBrand as SassLayer[]; - let foundBrand = false; + ); + assert(!maybeBrandBundle || + !maybeBrandBundle.user?.find((v) => v === "brand") && + !maybeBrandBundle.dark?.user?.find((v) => v === "brand")); + let foundBrand = {light: false, dark: false}; const bundles: SassBundle[] = bundlesWithBrand.filter((bundle) => bundle.key !== "brand" ).map((bundle) => { const userBrand = bundle.user?.findIndex((layer) => layer === "brand"); + let cloned = false; if (userBrand && userBrand !== -1) { + // console.log('light brand order specified', userBrand, cloned); bundle = cloneDeep(bundle); - bundle.user!.splice(userBrand, 1, ...brandLayers); - foundBrand = true; + cloned = true; + bundle.user!.splice(userBrand, 1, ...(maybeBrandBundle?.user || [])); + foundBrand.light = true; + } + const darkBrand = bundle.dark?.user?.findIndex((layer) => layer === "brand"); + if (darkBrand && darkBrand !== -1) { + // console.log('dark brand order specified', darkBrand, cloned); + if (!cloned) { + bundle = cloneDeep(bundle); + } + bundle.dark!.user!.splice(darkBrand, 1, ...(maybeBrandBundle?.dark?.user || [])) + foundBrand.dark = true; } return bundle as SassBundle; }); - if (!foundBrand) { + if (!foundBrand.light || !foundBrand.dark) { bundles.unshift({ dependency, key: "brand", - user: brandLayers, + user: !foundBrand.light && maybeBrandBundle?.user as SassLayer[] || [], + dark: !foundBrand.dark && maybeBrandBundle?.dark?.user && { + user: maybeBrandBundle.dark.user as SassLayer[], + default: maybeBrandBundle.dark.default + } || undefined }); } diff --git a/src/command/render/pandoc.ts b/src/command/render/pandoc.ts index 8cb237ab6ea..158e271402d 100644 --- a/src/command/render/pandoc.ts +++ b/src/command/render/pandoc.ts @@ -1467,7 +1467,7 @@ async function resolveExtras( // perform typst-specific merging if (isTypstOutput(format.pandoc)) { - const brand = await project.resolveBrand(input); + const brand = (await project.resolveBrand(input))?.light; const fontdirs: Set = new Set(); const base_urls = { google: "https://fonts.googleapis.com/css", diff --git a/src/command/render/render-contexts.ts b/src/command/render/render-contexts.ts index e280f459af1..8c7ebb6dbb5 100644 --- a/src/command/render/render-contexts.ts +++ b/src/command/render/render-contexts.ts @@ -614,11 +614,11 @@ async function resolveFormats( // resolve brand in project and forward it to format const brand = await project.resolveBrand(target.source); - mergedFormats[format].render.brand = brand; + mergedFormats[format].render.brand = brand?.light; // apply defaults from brand yaml under the metadata of the current format const brandFormatDefaults: Metadata = - (brand?.data?.defaults?.quarto as unknown as Record< + (brand?.light?.data?.defaults?.quarto as unknown as Record< string, Record >)?.format diff --git a/src/core/brand/brand.ts b/src/core/brand/brand.ts index b6292c40f7a..529ba982bee 100644 --- a/src/core/brand/brand.ts +++ b/src/core/brand/brand.ts @@ -284,6 +284,11 @@ export class Brand { } } +export type LightDarkBrand = { + light?: Brand; + dark?: Brand; +}; + export const getFavicon = (brand: Brand): string | undefined => { const logoInfo = brand.getLogo("small"); if (!logoInfo) { diff --git a/src/core/sass/brand.ts b/src/core/sass/brand.ts index 3ade1aefd33..2cd027b5185 100644 --- a/src/core/sass/brand.ts +++ b/src/core/sass/brand.ts @@ -22,6 +22,10 @@ import { BrandFontWeight, } from "../../resources/types/schema-types.ts"; import { Brand } from "../brand/brand.ts"; +import { + darkModeDefault, +} from "../../format/html/format-html-info.ts"; + const defaultColorNameMap: Record = { "link-color": "link", @@ -75,7 +79,10 @@ export async function brandBootstrapSassBundles( return [{ key, dependency: "bootstrap", - user: layers, + user: layers.light, + dark: { + user: layers.dark + } }]; } @@ -559,16 +566,24 @@ const brandTypographyLayer = ( }; }; +export interface LightDarkSassLayers { + light: SassLayer[]; + dark: SassLayer[]; +} + export async function brandSassLayers( fileName: string | undefined, project: ProjectContext, nameMap: Record = {}, -): Promise { +): Promise { const brand = await project.resolveBrand(fileName); - const sassLayers: SassLayer[] = []; + const sassLayers: LightDarkSassLayers = { + light: [], + dark: [] + }; - if (brand) { - sassLayers.push({ + for (const layer of [sassLayers.light, sassLayers.dark]) { + layer.push({ defaults: '$theme: "brand" !default;', uses: "", functions: "", @@ -576,12 +591,17 @@ export async function brandSassLayers( rules: "", }); } - if (brand?.data.color) { - sassLayers.push(brandColorLayer(brand, nameMap)); + if (brand?.light?.data.color) { + sassLayers.light.push(brandColorLayer(brand?.light, nameMap)); } - - if (brand?.data.typography) { - sassLayers.push(brandTypographyLayer(brand)); + if (brand?.dark?.data.color) { + sassLayers.dark.push(brandColorLayer(brand?.dark, nameMap)); + } + if (brand?.light?.data.typography) { + sassLayers.light.push(brandTypographyLayer(brand?.light)); + } + if (brand?.dark?.data.typography) { + sassLayers.dark.push(brandTypographyLayer(brand?.dark)); } return sassLayers; @@ -591,7 +611,7 @@ export async function brandBootstrapSassLayers( fileName: string | undefined, project: ProjectContext, nameMap: Record = {}, -): Promise { +): Promise { const layers = await brandSassLayers( fileName, project, @@ -599,8 +619,11 @@ export async function brandBootstrapSassLayers( ); const brand = await project.resolveBrand(fileName); - if (brand?.data?.defaults?.bootstrap) { - layers.unshift(brandDefaultsBootstrapLayer(brand)); + if (brand?.light?.data?.defaults?.bootstrap) { + layers.light.unshift(brandDefaultsBootstrapLayer(brand.light)); + } + if (brand?.dark?.data?.defaults?.bootstrap) { + layers.dark.unshift(brandDefaultsBootstrapLayer(brand.dark)); } return layers; @@ -611,16 +634,16 @@ export async function brandRevealSassLayers( _format: Format, project: ProjectContext, ): Promise { - return brandSassLayers( + return (await brandSassLayers( input, project, defaultColorNameMap, - ); + )).light; } export async function brandSassFormatExtras( input: string | undefined, - _format: Format, + format: Format, project: ProjectContext, ): Promise { const htmlSassBundleLayers = await brandBootstrapSassLayers( @@ -634,7 +657,11 @@ export async function brandSassFormatExtras( { key: "brand", dependency: "bootstrap", - user: htmlSassBundleLayers, + user: htmlSassBundleLayers.light, + dark: { + user: htmlSassBundleLayers.dark, + default: darkModeDefault(format.metadata) + } }, ], }, diff --git a/src/format/html/format-html-bootstrap.ts b/src/format/html/format-html-bootstrap.ts index 7db03717544..23303d4a15a 100644 --- a/src/format/html/format-html-bootstrap.ts +++ b/src/format/html/format-html-bootstrap.ts @@ -78,6 +78,10 @@ import { documentTitleScssLayer, processDocumentTitle, } from "./format-html-title.ts"; +import { + darkModeDefault, +} from "./format-html-info.ts"; + import { kTemplatePartials } from "../../command/render/template.ts"; import { isHtmlOutput } from "../../config/format.ts"; import { emplaceNotebookPreviews } from "./format-html-notebook.ts"; @@ -1062,13 +1066,8 @@ function bootstrapHtmlFinalizer(format: Format, flags: PandocFlags) { // start body with light or dark class for proper display when JS is disabled let initialLightDarkClass = "quarto-light"; - // some logic duplicated from resolveThemeLayer - const theme = format.metadata.theme; - if (theme && !Array.isArray(theme) && typeof theme === "object") { - const keys = Object.keys(theme); - if(keys.length > 1 && keys[0] === "dark") { - initialLightDarkClass = "quarto-dark"; - } + if (darkModeDefault(format.metadata)) { + initialLightDarkClass = "quarto-dark"; } doc.body.classList.add(initialLightDarkClass); diff --git a/src/format/html/format-html-info.ts b/src/format/html/format-html-info.ts index 50928a6c3ce..d5923c3bbb2 100644 --- a/src/format/html/format-html-info.ts +++ b/src/format/html/format-html-info.ts @@ -6,7 +6,7 @@ * Copyright (C) 2020-2022 Posit Software, PBC */ -import { kTheme } from "../../config/constants.ts"; +import { kTheme, kBrand } from "../../config/constants.ts"; import { isHtmlDashboardOutput, isHtmlOutput } from "../../config/format.ts"; import { Format, Metadata } from "../../config/types.ts"; @@ -40,14 +40,15 @@ export function formatDarkMode(format: Format): boolean | undefined { export function darkModeDefault(metadata?: Metadata): boolean | undefined { if (metadata !== undefined) { - const theme = metadata[kTheme]; - if (theme && typeof (theme) === "object") { - const keys = Object.keys(theme); - if (keys.includes("dark")) { - if (keys[0] === "dark") { - return true; - } else { - return false; + for (const darkable of [metadata[kTheme], metadata[kBrand]]) { + if (darkable && typeof (darkable) === "object") { + const keys = Object.keys(darkable); + if (keys.includes("dark")) { + if (keys[0] === "dark") { + return true; + } else { + return false; + } } } } diff --git a/src/project/project-shared.ts b/src/project/project-shared.ts index caf3be2147a..2d061836b87 100644 --- a/src/project/project-shared.ts +++ b/src/project/project-shared.ts @@ -47,7 +47,7 @@ import { normalizeNewlines } from "../core/lib/text.ts"; import { DirectiveCell } from "../core/lib/break-quarto-md-types.ts"; import { QuartoJSONSchema, readYamlFromMarkdown } from "../core/yaml.ts"; import { refSchema } from "../core/lib/yaml-schema/common.ts"; -import { Brand as BrandJson } from "../resources/types/schema-types.ts"; +import { Brand as BrandJson, BrandPathBoolLightDark } from "../resources/types/schema-types.ts"; import { Brand } from "../core/brand/brand.ts"; import { warnOnce } from "../core/log.ts"; import { assert } from "testing/asserts"; @@ -512,7 +512,24 @@ export const ensureFileInformationCache = ( export async function projectResolveBrand( project: ProjectContext, fileName?: string, -) { +) : Promise<{light?: Brand, dark?: Brand} | undefined> { + async function loadBrand(brandPath: string) : Promise { + const brand = await readAndValidateYamlFromFile( + brandPath, + refSchema("brand", "Format-independent brand configuration."), + "Brand validation failed for " + brandPath + ".", + ) as BrandJson; + return new Brand(brand, dirname(brandPath), project.dir); + } + async function loadLocalBrand(brandPath: string) : Promise { + let resolved: string = ""; + if (brandPath.startsWith("/")) { + resolved = join(project.dir, brandPath); + } else { + resolved = join(dirname(fileName!), brandPath); + } + return await loadBrand(resolved); + } if (fileName === undefined) { if (project.brandCache) { return project.brandCache.brand; @@ -538,46 +555,59 @@ export async function projectResolveBrand( refSchema("brand", "Format-independent brand configuration."), "Brand validation failed for " + brandPath + ".", ) as BrandJson; - project.brandCache.brand = new Brand( + project.brandCache.brand = {light: new Brand( brand, dirname(brandPath), project.dir, - ); + )}; } return project.brandCache.brand; } else { const metadata = await project.fileMetadata(fileName); - if (metadata.brand === false) { + const brand = metadata.brand as BrandPathBoolLightDark; + if (brand === false) { return undefined; } - if (metadata.brand === true || metadata.brand === undefined) { + if (brand === true || brand === undefined) { return project.resolveBrand(); } const fileInformation = ensureFileInformationCache(project, fileName); if (fileInformation.brand) { return fileInformation.brand; } - if (typeof metadata.brand === "string") { - let brandPath: string = ""; - if (brandPath.startsWith("/")) { - brandPath = join(project.dir, metadata.brand); - } else { - brandPath = join(dirname(fileName), metadata.brand); - } - const brand = await readAndValidateYamlFromFile( - brandPath, - refSchema("brand", "Format-independent brand configuration."), - "Brand validation failed for " + brandPath + ".", - ) as BrandJson; - fileInformation.brand = new Brand(brand, dirname(brandPath), project.dir); + if (typeof brand === "string") { + fileInformation.brand = {light: await loadLocalBrand(brand)}; return fileInformation.brand; } else { - assert(typeof metadata.brand === "object"); - fileInformation.brand = new Brand( - metadata.brand as BrandJson, - dirname(fileName), - project.dir, - ); + assert(typeof brand === "object"); + if ("light" in brand || "dark" in brand) { + let light, dark; + if (typeof brand.light === "string") { + light = await loadLocalBrand(brand.light) + } else { + light = new Brand( + brand.light!, + dirname(fileName), + project.dir + ); + } + if (typeof brand.dark === "string") { + dark = await loadLocalBrand(brand.dark) + } else { + dark = new Brand( + brand.dark!, + dirname(fileName), + project.dir + ); + } + fileInformation.brand = {light, dark}; + } else { + fileInformation.brand = {light: new Brand( + brand as BrandJson, + dirname(fileName), + project.dir, + )}; + } return fileInformation.brand; } } diff --git a/src/project/types.ts b/src/project/types.ts index 0b95f5cdf1c..3bc67290dab 100644 --- a/src/project/types.ts +++ b/src/project/types.ts @@ -7,7 +7,7 @@ import { RenderServices } from "../command/render/types.ts"; import { Metadata, PandocFlags } from "../config/types.ts"; import { Format, FormatExtras } from "../config/types.ts"; -import { Brand } from "../core/brand/brand.ts"; +import { Brand, LightDarkBrand } from "../core/brand/brand.ts"; import { MappedString } from "../core/mapped-text.ts"; import { PartitionedMarkdown } from "../core/pandoc/types.ts"; import { ExecutionEngine, ExecutionTarget } from "../execute/types.ts"; @@ -56,7 +56,7 @@ export type FileInformation = { engine?: ExecutionEngine; target?: ExecutionTarget; metadata?: Metadata; - brand?: Brand; + brand?: LightDarkBrand; }; export interface ProjectContext { @@ -70,8 +70,8 @@ export interface ProjectContext { fileInformationCache: Map; // This is a cache of _brand.yml for a project - brandCache?: { brand?: Brand }; - resolveBrand: (fileName?: string) => Promise; + brandCache?: { brand?: LightDarkBrand}; + resolveBrand: (fileName?: string) => Promise; // expands markdown for a file // input file doesn't have to be markdown; it can be, for example, a knitr spin file diff --git a/src/project/types/book/book-config.ts b/src/project/types/book/book-config.ts index 05847a80ab2..8e4a4d14c85 100644 --- a/src/project/types/book/book-config.ts +++ b/src/project/types/book/book-config.ts @@ -144,8 +144,8 @@ export async function bookProjectConfig( site[kSiteFavicon] = book[kSiteFavicon]; if (!site[kSiteFavicon]) { const brand = await project.resolveBrand(); - if (brand) { - site[kSiteFavicon] = getFavicon(brand); + if (brand?.light) { + site[kSiteFavicon] = getFavicon(brand.light); // } } site[kSiteUrl] = book[kSiteUrl]; diff --git a/src/project/types/website/website-shared.ts b/src/project/types/website/website-shared.ts index 80147c0d8af..195b196bbc9 100644 --- a/src/project/types/website/website-shared.ts +++ b/src/project/types/website/website-shared.ts @@ -166,12 +166,12 @@ export async function websiteNavigationConfig(project: ProjectContext) { const projectBrand = await project.resolveBrand(); if ( - projectBrand?.processedData.logo && sidebars?.[0] + projectBrand?.light?.processedData.logo && sidebars?.[0] ) { if (sidebars[0].logo === undefined) { - const logo = projectBrand.processedData.logo.medium ?? - projectBrand.processedData.logo.small ?? - projectBrand.processedData.logo.large; + const logo = projectBrand.light.processedData.logo.medium ?? + projectBrand.light.processedData.logo.small ?? + projectBrand.light.processedData.logo.large; if (logo) { sidebars[0].logo = logo.light.path; // TODO: This needs smarts to work on light+dark themes sidebars[0]["logo-alt"] = logo.light.alt; @@ -180,11 +180,11 @@ export async function websiteNavigationConfig(project: ProjectContext) { } if ( - projectBrand?.processedData && navbar + projectBrand?.light?.processedData && navbar ) { - const logo = projectBrand.processedData.logo.small ?? - projectBrand.processedData.logo.medium ?? - projectBrand.processedData.logo.large; + const logo = projectBrand.light.processedData.logo.small ?? + projectBrand.light.processedData.logo.medium ?? + projectBrand.light.processedData.logo.large; if (logo) { navbar.logo = logo.light.path; // TODO: This needs smarts to work on light+dark themes navbar["logo-alt"] = logo.light.alt; diff --git a/src/project/types/website/website.ts b/src/project/types/website/website.ts index 07ecb45aa8b..3a924975abf 100644 --- a/src/project/types/website/website.ts +++ b/src/project/types/website/website.ts @@ -182,8 +182,8 @@ export const websiteProjectType: ProjectType = { let favicon = websiteConfigString(kSiteFavicon, project.config); if (!favicon) { const brand = await project.resolveBrand(); - if (brand) { - favicon = getFavicon(brand); + if (brand?.light) { + favicon = getFavicon(brand.light); } } if (favicon) { diff --git a/src/resources/editor/tools/vs-code.mjs b/src/resources/editor/tools/vs-code.mjs index 597c7397424..e1b259a61c0 100644 --- a/src/resources/editor/tools/vs-code.mjs +++ b/src/resources/editor/tools/vs-code.mjs @@ -12544,6 +12544,42 @@ var require_yaml_intelligence_resources = __commonJS({ } } }, + { + id: "brand-path-bool-light-dark", + anyOf: [ + "string", + "boolean", + { + object: { + closed: true, + properties: { + light: { + anyOf: [ + "string", + { + ref: "brand" + } + ], + description: "The path to a light brand file or an inline light brand definition.\n" + }, + dark: { + anyOf: [ + "string", + { + ref: "brand" + } + ], + description: "The path to a dark brand file or an inline dark brand definition.\n" + } + } + } + }, + { + ref: "brand" + } + ], + description: "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition, or an object with light and dark brand paths or definitions.\n" + }, { id: "brand-defaults", object: { @@ -16689,15 +16725,9 @@ var require_yaml_intelligence_resources = __commonJS({ { name: "brand", schema: { - anyOf: [ - "string", - "boolean", - { - ref: "brand" - } - ] + ref: "brand-path-bool-light-dark" }, - description: "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition.\n" + description: "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition, or an object with light and dark brand paths or definitions.\n" }, { name: "theme", @@ -21910,11 +21940,15 @@ var require_yaml_intelligence_resources = __commonJS({ "The font files to include. These can be local or online. Local file\npaths should be relative to the brand.yml file. Online\npaths should be complete URLs.", "The path to the font file. This can be a local path or a URL.", "A locally-installed font family name. When used, the end-user is\nresponsible for ensuring that the font is installed on their system.", + "Branding information to use for this document. If a string, the path\nto a brand file. If false, don\u2019t use branding on this document. If an\nobject, an inline brand definition, or an object with light and dark\nbrand paths or definitions.", + "The path to a light brand file or an inline light brand\ndefinition.", + "The path to a dark brand file or an inline dark brand definition.", { short: "Unique label for code cell", long: "Unique label for code cell. Used when other code needs to refer to\nthe cell (e.g. for cross references fig-samples or\ntbl-summary)" }, "Classes to apply to cell container", + "Array of rendering names", "Array of tags for notebook cell", { short: "Notebook cell identifier", @@ -22879,7 +22913,9 @@ var require_yaml_intelligence_resources = __commonJS({ }, "If true, force the presence of the OJS runtime. If\nfalse, force the absence instead. If unset, the OJS runtime\nis included only if OJS cells are present in the document.", "Use the specified file as a style reference in producing a docx,\npptx, or odt file.", - "Branding information to use for this document. If a string, the path\nto a brand file. If false, don\u2019t use branding on this document. If an\nobject, an inline brand definition.", + "Branding information to use for this document. If a string, the path\nto a brand file. If false, don\u2019t use branding on this document. If an\nobject, an inline brand definition, or an object with light and dark\nbrand paths or definitions.", + "The path to a light brand file or an inline light brand\ndefinition.", + "The path to a dark brand file or an inline dark brand definition.", "Theme name, theme scss file, or a mix of both.", "The light theme name, theme scss file, or a mix of both.", "The light theme name, theme scss file, or a mix of both.", @@ -23630,6 +23666,7 @@ var require_yaml_intelligence_resources = __commonJS({ "Disambiguating year suffix in author-date styles (e.g. \u201Ca\u201D in \u201CDoe,\n1999a\u201D).", "Manuscript configuration", "internal-schema-hack", + "List execution engines you want to give priority when determining\nwhich engine should render a notebook. If two engines have support for a\nnotebook, the one listed earlier will be chosen. Quarto\u2019s default order\nis \u2018knitr\u2019, \u2018jupyter\u2019, \u2018markdown\u2019, \u2018julia\u2019.", { short: "Include an automatically generated table of contents", long: "" @@ -23982,7 +24019,6 @@ var require_yaml_intelligence_resources = __commonJS({ "Disambiguating year suffix in author-date styles (e.g. \u201Ca\u201D in \u201CDoe,\n1999a\u201D).", "Manuscript configuration", "internal-schema-hack", - "Array of rendering names", "List execution engines you want to give priority when determining\nwhich engine should render a notebook. If two engines have support for a\nnotebook, the one listed earlier will be chosen. Quarto\u2019s default order\nis \u2018knitr\u2019, \u2018jupyter\u2019, \u2018markdown\u2019, \u2018julia\u2019." ], "schema/external-schemas.yml": [ @@ -24212,12 +24248,12 @@ var require_yaml_intelligence_resources = __commonJS({ mermaid: "%%" }, "handlers/mermaid/schema.yml": { - _internalId: 194327, + _internalId: 196120, type: "object", description: "be an object", properties: { "mermaid-format": { - _internalId: 194319, + _internalId: 196112, type: "enum", enum: [ "png", @@ -24233,7 +24269,7 @@ var require_yaml_intelligence_resources = __commonJS({ exhaustiveCompletions: true }, theme: { - _internalId: 194326, + _internalId: 196119, type: "anyOf", anyOf: [ { diff --git a/src/resources/editor/tools/yaml/web-worker.js b/src/resources/editor/tools/yaml/web-worker.js index 62b7d2ba481..d666a0a15db 100644 --- a/src/resources/editor/tools/yaml/web-worker.js +++ b/src/resources/editor/tools/yaml/web-worker.js @@ -12545,6 +12545,42 @@ try { } } }, + { + id: "brand-path-bool-light-dark", + anyOf: [ + "string", + "boolean", + { + object: { + closed: true, + properties: { + light: { + anyOf: [ + "string", + { + ref: "brand" + } + ], + description: "The path to a light brand file or an inline light brand definition.\n" + }, + dark: { + anyOf: [ + "string", + { + ref: "brand" + } + ], + description: "The path to a dark brand file or an inline dark brand definition.\n" + } + } + } + }, + { + ref: "brand" + } + ], + description: "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition, or an object with light and dark brand paths or definitions.\n" + }, { id: "brand-defaults", object: { @@ -16690,15 +16726,9 @@ try { { name: "brand", schema: { - anyOf: [ - "string", - "boolean", - { - ref: "brand" - } - ] + ref: "brand-path-bool-light-dark" }, - description: "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition.\n" + description: "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition, or an object with light and dark brand paths or definitions.\n" }, { name: "theme", @@ -21911,11 +21941,15 @@ try { "The font files to include. These can be local or online. Local file\npaths should be relative to the brand.yml file. Online\npaths should be complete URLs.", "The path to the font file. This can be a local path or a URL.", "A locally-installed font family name. When used, the end-user is\nresponsible for ensuring that the font is installed on their system.", + "Branding information to use for this document. If a string, the path\nto a brand file. If false, don\u2019t use branding on this document. If an\nobject, an inline brand definition, or an object with light and dark\nbrand paths or definitions.", + "The path to a light brand file or an inline light brand\ndefinition.", + "The path to a dark brand file or an inline dark brand definition.", { short: "Unique label for code cell", long: "Unique label for code cell. Used when other code needs to refer to\nthe cell (e.g. for cross references fig-samples or\ntbl-summary)" }, "Classes to apply to cell container", + "Array of rendering names", "Array of tags for notebook cell", { short: "Notebook cell identifier", @@ -22880,7 +22914,9 @@ try { }, "If true, force the presence of the OJS runtime. If\nfalse, force the absence instead. If unset, the OJS runtime\nis included only if OJS cells are present in the document.", "Use the specified file as a style reference in producing a docx,\npptx, or odt file.", - "Branding information to use for this document. If a string, the path\nto a brand file. If false, don\u2019t use branding on this document. If an\nobject, an inline brand definition.", + "Branding information to use for this document. If a string, the path\nto a brand file. If false, don\u2019t use branding on this document. If an\nobject, an inline brand definition, or an object with light and dark\nbrand paths or definitions.", + "The path to a light brand file or an inline light brand\ndefinition.", + "The path to a dark brand file or an inline dark brand definition.", "Theme name, theme scss file, or a mix of both.", "The light theme name, theme scss file, or a mix of both.", "The light theme name, theme scss file, or a mix of both.", @@ -23631,6 +23667,7 @@ try { "Disambiguating year suffix in author-date styles (e.g. \u201Ca\u201D in \u201CDoe,\n1999a\u201D).", "Manuscript configuration", "internal-schema-hack", + "List execution engines you want to give priority when determining\nwhich engine should render a notebook. If two engines have support for a\nnotebook, the one listed earlier will be chosen. Quarto\u2019s default order\nis \u2018knitr\u2019, \u2018jupyter\u2019, \u2018markdown\u2019, \u2018julia\u2019.", { short: "Include an automatically generated table of contents", long: "" @@ -23983,7 +24020,6 @@ try { "Disambiguating year suffix in author-date styles (e.g. \u201Ca\u201D in \u201CDoe,\n1999a\u201D).", "Manuscript configuration", "internal-schema-hack", - "Array of rendering names", "List execution engines you want to give priority when determining\nwhich engine should render a notebook. If two engines have support for a\nnotebook, the one listed earlier will be chosen. Quarto\u2019s default order\nis \u2018knitr\u2019, \u2018jupyter\u2019, \u2018markdown\u2019, \u2018julia\u2019." ], "schema/external-schemas.yml": [ @@ -24213,12 +24249,12 @@ try { mermaid: "%%" }, "handlers/mermaid/schema.yml": { - _internalId: 194327, + _internalId: 196120, type: "object", description: "be an object", properties: { "mermaid-format": { - _internalId: 194319, + _internalId: 196112, type: "enum", enum: [ "png", @@ -24234,7 +24270,7 @@ try { exhaustiveCompletions: true }, theme: { - _internalId: 194326, + _internalId: 196119, type: "anyOf", anyOf: [ { diff --git a/src/resources/editor/tools/yaml/yaml-intelligence-resources.json b/src/resources/editor/tools/yaml/yaml-intelligence-resources.json index 08d87ba0637..663646e453a 100644 --- a/src/resources/editor/tools/yaml/yaml-intelligence-resources.json +++ b/src/resources/editor/tools/yaml/yaml-intelligence-resources.json @@ -5516,6 +5516,42 @@ } } }, + { + "id": "brand-path-bool-light-dark", + "anyOf": [ + "string", + "boolean", + { + "object": { + "closed": true, + "properties": { + "light": { + "anyOf": [ + "string", + { + "ref": "brand" + } + ], + "description": "The path to a light brand file or an inline light brand definition.\n" + }, + "dark": { + "anyOf": [ + "string", + { + "ref": "brand" + } + ], + "description": "The path to a dark brand file or an inline dark brand definition.\n" + } + } + } + }, + { + "ref": "brand" + } + ], + "description": "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition, or an object with light and dark brand paths or definitions.\n" + }, { "id": "brand-defaults", "object": { @@ -9661,15 +9697,9 @@ { "name": "brand", "schema": { - "anyOf": [ - "string", - "boolean", - { - "ref": "brand" - } - ] + "ref": "brand-path-bool-light-dark" }, - "description": "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition.\n" + "description": "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition, or an object with light and dark brand paths or definitions.\n" }, { "name": "theme", @@ -14882,11 +14912,15 @@ "The font files to include. These can be local or online. Local file\npaths should be relative to the brand.yml file. Online\npaths should be complete URLs.", "The path to the font file. This can be a local path or a URL.", "A locally-installed font family name. When used, the end-user is\nresponsible for ensuring that the font is installed on their system.", + "Branding information to use for this document. If a string, the path\nto a brand file. If false, don’t use branding on this document. If an\nobject, an inline brand definition, or an object with light and dark\nbrand paths or definitions.", + "The path to a light brand file or an inline light brand\ndefinition.", + "The path to a dark brand file or an inline dark brand definition.", { "short": "Unique label for code cell", "long": "Unique label for code cell. Used when other code needs to refer to\nthe cell (e.g. for cross references fig-samples or\ntbl-summary)" }, "Classes to apply to cell container", + "Array of rendering names", "Array of tags for notebook cell", { "short": "Notebook cell identifier", @@ -15851,7 +15885,9 @@ }, "If true, force the presence of the OJS runtime. If\nfalse, force the absence instead. If unset, the OJS runtime\nis included only if OJS cells are present in the document.", "Use the specified file as a style reference in producing a docx,\npptx, or odt file.", - "Branding information to use for this document. If a string, the path\nto a brand file. If false, don’t use branding on this document. If an\nobject, an inline brand definition.", + "Branding information to use for this document. If a string, the path\nto a brand file. If false, don’t use branding on this document. If an\nobject, an inline brand definition, or an object with light and dark\nbrand paths or definitions.", + "The path to a light brand file or an inline light brand\ndefinition.", + "The path to a dark brand file or an inline dark brand definition.", "Theme name, theme scss file, or a mix of both.", "The light theme name, theme scss file, or a mix of both.", "The light theme name, theme scss file, or a mix of both.", @@ -16602,6 +16638,7 @@ "Disambiguating year suffix in author-date styles (e.g. “a” in “Doe,\n1999a”).", "Manuscript configuration", "internal-schema-hack", + "List execution engines you want to give priority when determining\nwhich engine should render a notebook. If two engines have support for a\nnotebook, the one listed earlier will be chosen. Quarto’s default order\nis ‘knitr’, ‘jupyter’, ‘markdown’, ‘julia’.", { "short": "Include an automatically generated table of contents", "long": "" @@ -16954,7 +16991,6 @@ "Disambiguating year suffix in author-date styles (e.g. “a” in “Doe,\n1999a”).", "Manuscript configuration", "internal-schema-hack", - "Array of rendering names", "List execution engines you want to give priority when determining\nwhich engine should render a notebook. If two engines have support for a\nnotebook, the one listed earlier will be chosen. Quarto’s default order\nis ‘knitr’, ‘jupyter’, ‘markdown’, ‘julia’." ], "schema/external-schemas.yml": [ @@ -17184,12 +17220,12 @@ "mermaid": "%%" }, "handlers/mermaid/schema.yml": { - "_internalId": 194327, + "_internalId": 196120, "type": "object", "description": "be an object", "properties": { "mermaid-format": { - "_internalId": 194319, + "_internalId": 196112, "type": "enum", "enum": [ "png", @@ -17205,7 +17241,7 @@ "exhaustiveCompletions": true }, "theme": { - "_internalId": 194326, + "_internalId": 196119, "type": "anyOf", "anyOf": [ { diff --git a/src/resources/formats/html/esbuild-analysis-cache.json b/src/resources/formats/html/esbuild-analysis-cache.json index 110a06d005f..16f8062bbc3 100644 --- a/src/resources/formats/html/esbuild-analysis-cache.json +++ b/src/resources/formats/html/esbuild-analysis-cache.json @@ -2,7 +2,7 @@ "quarto.js": { "inputs": { "quarto.js": { - "bytes": 26396, + "bytes": 26830, "imports": [], "format": "esm" } @@ -20,10 +20,10 @@ "entryPoint": "quarto.js", "inputs": { "quarto.js": { - "bytesInOutput": 21946 + "bytesInOutput": 22313 } }, - "bytes": 21946 + "bytes": 22313 } } } diff --git a/src/resources/schema/definitions.yml b/src/resources/schema/definitions.yml index 69615106c34..6fe40176b5c 100644 --- a/src/resources/schema/definitions.yml +++ b/src/resources/schema/definitions.yml @@ -2980,6 +2980,31 @@ defaults: ref: brand-defaults +- id: brand-path-bool-light-dark + anyOf: + - string # a file path + - boolean # if false, don't use branding on this document + - object: + closed: true + properties: + light: + anyOf: + - string + - ref: brand + description: > + The path to a light brand file or an inline light brand definition. + dark: + anyOf: + - string + - ref: brand + description: > + The path to a dark brand file or an inline dark brand definition. + - ref: brand + description: | + Branding information to use for this document. If a string, the path to a brand file. + If false, don't use branding on this document. If an object, an inline brand + definition, or an object with light and dark brand paths or definitions. + - id: brand-defaults object: properties: diff --git a/src/resources/schema/document-options.yml b/src/resources/schema/document-options.yml index 1407ceb29e9..0b7be354fb5 100644 --- a/src/resources/schema/document-options.yml +++ b/src/resources/schema/document-options.yml @@ -8,14 +8,11 @@ - name: brand schema: - anyOf: - - string # a file path - - boolean # if false, don't use branding on this document - - ref: brand # an inline brand object definition + ref: brand-path-bool-light-dark description: | Branding information to use for this document. If a string, the path to a brand file. If false, don't use branding on this document. If an object, an inline brand - definition. + definition, or an object with light and dark brand paths or definitions. - name: theme tags: diff --git a/src/resources/schema/json-schemas.json b/src/resources/schema/json-schemas.json index 1898e0c768b..06040e4dcaf 100644 --- a/src/resources/schema/json-schemas.json +++ b/src/resources/schema/json-schemas.json @@ -3776,6 +3776,45 @@ } } }, + "BrandPathBoolLightDark": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "boolean" + }, + { + "object": { + "properties": { + "light": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/Brand" + } + ] + }, + "dark": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/Brand" + } + ] + } + } + } + }, + { + "$ref": "#/$defs/Brand" + } + ] + }, "BrandDefaults": { "object": { "properties": { diff --git a/src/resources/types/schema-types.ts b/src/resources/types/schema-types.ts index c755cbb615d..1d95382ed38 100644 --- a/src/resources/types/schema-types.ts +++ b/src/resources/types/schema-types.ts @@ -1483,6 +1483,21 @@ export type Brand = { typography?: BrandTypography; }; +export type BrandPathBoolLightDark = + | string + | boolean + | { + dark?: + | string + | Brand /* The path to a dark brand file or an inline dark brand definition. */; + light?: + | string + | Brand; /* The path to a light brand file or an inline light brand definition. */ + } + | Brand; /* Branding information to use for this document. If a string, the path to a brand file. +If false, don't use branding on this document. If an object, an inline brand +definition, or an object with light and dark brand paths or definitions. */ + export type BrandDefaults = { bootstrap?: BrandDefaultsBootstrap; quarto?: JsonObject; diff --git a/tests/docs/smoke-all/dark-mode/ggplot-brandless.qmd b/tests/docs/smoke-all/dark-mode/ggplot-brandless.qmd index 9ba0fcab178..0437a6dc3fa 100644 --- a/tests/docs/smoke-all/dark-mode/ggplot-brandless.qmd +++ b/tests/docs/smoke-all/dark-mode/ggplot-brandless.qmd @@ -1,5 +1,5 @@ --- -title: "knitr dark mode - thematic" +title: "knitr dark mode - ggplot" format: html: theme: diff --git a/tests/docs/smoke-all/dark-mode/ggplot-duobrand.qmd b/tests/docs/smoke-all/dark-mode/ggplot-duobrand.qmd new file mode 100644 index 00000000000..2ba8106d939 --- /dev/null +++ b/tests/docs/smoke-all/dark-mode/ggplot-duobrand.qmd @@ -0,0 +1,113 @@ +--- +title: "knitr dark mode - ggplot" +brand: + light: united-brand.yml + dark: slate-brand.yml +execute: + echo: false + warning: false +_quarto: + tests: + html: + ensureHtmlElements: + - + - 'body.quarto-light' + - 'div.cell div.light-content' + - 'div.cell div.dark-content' + - 'div.cell div.cell-code pre.code-with-copy' + - [] +--- + +```{r} +#| echo: false +#| warning: false +library(ggplot2) + +ggplot_theme <- function(bgcolor, fgcolor) { + theme_minimal(base_size = 11) %+% + theme( + panel.border = element_blank(), + panel.grid.major.y = element_blank(), + panel.grid.minor.y = element_blank(), + panel.grid.major.x = element_blank(), + panel.grid.minor.x = element_blank(), + text = element_text(colour = fgcolor), + axis.text = element_text(colour = fgcolor), + rect = element_rect(colour = bgcolor, fill = bgcolor), + plot.background = element_rect(fill = bgcolor, colour = NA), + axis.line = element_line(colour = fgcolor), + axis.ticks = element_line(colour = fgcolor) + ) +} + +brand_ggplot <- function(brand_yml) { + brand <- yaml::yaml.load_file(brand_yml) + ggplot_theme(brand$color$background, brand$color$foreground) +} + +united_theme <- brand_ggplot("united-brand.yml") +slate_theme <- brand_ggplot("slate-brand.yml") + +colour_scale <- scale_colour_manual(values = c("darkorange", "purple", "cyan4")) +``` + +### no crossref, no caption + +```{r} +#| renderings: [light, dark] +ggplot(mtcars, aes(mpg, wt)) + + geom_point(aes(colour = factor(cyl))) + united_theme + colour_scale +ggplot(mtcars, aes(mpg, wt)) + + geom_point(aes(colour = factor(cyl))) + slate_theme + colour_scale +``` + +### with crossref but no caption + +::: {#fig-thematic-ggplot} +```{r} +#| echo: true +#| renderings: +#| - dark +#| - light +ggplot(mtcars, aes(mpg, disp)) + + geom_point(aes(colour = factor(cyl))) + slate_theme + colour_scale +ggplot(mtcars, aes(mpg, disp)) + + geom_point(aes(colour = factor(cyl))) + united_theme + colour_scale +``` +::: + +### with caption but no crossref + +dark rendering only + +
+ +```{r} +#| renderings: [dark] +ggplot(mtcars, aes(mpg, disp)) + + geom_point(aes(colour = factor(cyl))) + slate_theme + colour_scale +``` + +thematic - base r graphics + +
+ +## patchwork + +### with crossref and caption + +::: {#fig-thematic-patchwork} +```{r} +#| renderings: [light, dark] +ggplot(mtcars, aes(mpg, hp)) + + geom_point(aes(colour = factor(cyl))) + united_theme + colour_scale +ggplot(mtcars, aes(mpg, hp)) + + geom_point(aes(colour = factor(cyl))) + slate_theme + colour_scale +``` + +mtcars - mpg vs hp +::: + +Here's a [link](https://example.com). + +{{< lipsum 3 >}} \ No newline at end of file diff --git a/tests/docs/smoke-all/dark-mode/matplotlib-duobrand-defaultdark.qmd b/tests/docs/smoke-all/dark-mode/matplotlib-duobrand-defaultdark.qmd new file mode 100644 index 00000000000..a5f1e43daad --- /dev/null +++ b/tests/docs/smoke-all/dark-mode/matplotlib-duobrand-defaultdark.qmd @@ -0,0 +1,201 @@ +--- +title: "jupyter dark mode - matplotlib" +engine: jupyter +brand: + dark: slate-brand.yml + light: united-brand.yml +keep-md: true +_quarto: + tests: + html: + ensureHtmlElements: + - + - 'body.quarto-dark' + - 'div.cell div.light-content' + - 'div.cell div.dark-content' + - 'div.cell div.cell-code pre.code-with-copy' + - [] +--- + +```{python} +#| echo: false +import yaml +import tempfile +import os + +def apply_mpl_colors(bgcolor, fgcolor, primarycolor): + fd, name = tempfile.mkstemp("mplstyle") + os.close(fd) + with open(name, "w") as out: + out.write("axes.facecolor: \"%s\"\n" % bgcolor) + out.write("axes.edgecolor: \"%s\"\n" % fgcolor) + out.write("axes.labelcolor: \"%s\"\n" % fgcolor) + out.write("axes.titlecolor: \"%s\"\n" % fgcolor) + out.write("figure.facecolor: \"%s\"\n" % bgcolor) + out.write("figure.edgecolor: \"%s\"\n" % fgcolor) + out.write("text.color: \"%s\"\n" % fgcolor) + out.write("xtick.color: \"%s\"\n" % fgcolor) + out.write("ytick.color: \"%s\"\n" % fgcolor) + # seems to require named color, is there a better way? + out.write("axes.prop_cycle: cycler('color', ['%s'])" % primarycolor) + plt.style.use(name) + os.unlink(name) + +def apply_brand_colors(filename): + with open(filename) as brand_file: + brand = yaml.safe_load(brand_file.read()) + def apply(): + apply_mpl_colors(brand["color"]["background"], brand["color"]["foreground"], brand["color"]["primary"]) + return apply + +united_colors = apply_brand_colors("united-brand.yml") +slate_colors = apply_brand_colors("slate-brand.yml") +``` + +### No crossref or caption +```{python} +#| echo: false +#| renderings: [light, dark] +import numpy as np +import matplotlib.pyplot as plt + +# Parameters for the normal distribution +mean = 0 +std_dev = 1 + +# Generate data +x = np.linspace(mean - 4*std_dev, mean + 4*std_dev, 1000) +y = (1/(std_dev * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x - mean) / std_dev)**2) + +# Plotting +united_colors() +plt.figure(figsize=(8, 5)) +plt.plot(x, y, label='Normal Distribution') +plt.title('Normal Distribution Curve') +plt.xlabel('X-axis') +plt.ylabel('Probability Density') +plt.legend() +plt.grid(True) +plt.show() + +slate_colors() +plt.figure(figsize=(8, 5)) +plt.plot(x, y, label='Normal Distribution') +plt.title('Normal Distribution Curve') +plt.xlabel('X-axis') +plt.ylabel('Probability Density') +plt.legend() +plt.grid(True) +plt.show() +``` + +### With crossref but no caption + +And `echo: true` + +::: {#fig-matplotlib-line} +```{python} +#| echo: true +#| renderings: [light, dark] +import matplotlib.pyplot as plt + +united_colors() +plt.title("Hello") +plt.plot([1,2,3]) +plt.grid(True) +plt.show(block=False) + +slate_colors() +plt.figure() +plt.title("Hello") +plt.plot([1,2,3]) +plt.grid(True) +plt.show(block=False) +``` +::: + +### With caption but no crossref + +::: {} +```{python} +#| echo: false +#| renderings: [light, dark] + +# author: "anthropic claude-3-5-sonnet-20240620" +import numpy as np +import matplotlib.pyplot as plt + +# Generate data points +x = np.linspace(0, 2 * np.pi, 100) +y = np.sin(x) + +united_colors() +plt.figure(figsize=(10, 6)) +plt.plot(x, y) +plt.title('Sine Wave') +plt.xlabel('x') +plt.ylabel('sin(x)') +plt.grid(True) +plt.axhline(y=0, color='k', linestyle='--') +plt.axvline(x=0, color='k', linestyle='--') +plt.show() + +slate_colors() +plt.figure(figsize=(10, 6)) +plt.plot(x, y) +plt.title('Sine Wave') +plt.xlabel('x') +plt.ylabel('sin(x)') +plt.grid(True) +plt.axhline(y=0, color='k', linestyle='--') +plt.axvline(x=0, color='k', linestyle='--') +plt.show() +``` +matplotlib sine wave + +::: + +### With crossref and caption + +::: {#fig-matplotlib-cosine} +```{python} +#| echo: false +#| renderings: [dark, light] +import numpy as np +import matplotlib.pyplot as plt + +# Generate data points +x = np.linspace(0, 2 * np.pi, 100) +y = np.cos(x) + +# Create the plot +slate_colors() +plt.figure(figsize=(10, 6)) +plt.plot(x, y) +plt.title('Cosine Wave') +plt.xlabel('x') +plt.ylabel('cos(x)') +plt.grid(True) +plt.axhline(y=0, color='k', linestyle='--') +plt.axvline(x=0, color='k', linestyle='--') +plt.show() + +united_colors() +plt.figure(figsize=(10, 6)) +plt.plot(x, y) +plt.title('Cosine Wave') +plt.xlabel('x') +plt.ylabel('cos(x)') +plt.grid(True) +plt.axhline(y=0, color='k', linestyle='--') +plt.axvline(x=0, color='k', linestyle='--') +plt.show() +``` + +matplotlib cosine wave +::: + +Here's a [link](https://example.com). + + +{{< lipsum 3 >}} \ No newline at end of file diff --git a/tests/docs/smoke-all/dark-mode/matplotlib-duobrand.qmd b/tests/docs/smoke-all/dark-mode/matplotlib-duobrand.qmd new file mode 100644 index 00000000000..b65998d28c1 --- /dev/null +++ b/tests/docs/smoke-all/dark-mode/matplotlib-duobrand.qmd @@ -0,0 +1,201 @@ +--- +title: "jupyter dark mode - matplotlib" +engine: jupyter +brand: + light: united-brand.yml + dark: slate-brand.yml +keep-md: true +_quarto: + tests: + html: + ensureHtmlElements: + - + - 'body.quarto-light' + - 'div.cell div.light-content' + - 'div.cell div.dark-content' + - 'div.cell div.cell-code pre.code-with-copy' + - [] +--- + +```{python} +#| echo: false +import yaml +import tempfile +import os + +def apply_mpl_colors(bgcolor, fgcolor, primarycolor): + fd, name = tempfile.mkstemp("mplstyle") + os.close(fd) + with open(name, "w") as out: + out.write("axes.facecolor: \"%s\"\n" % bgcolor) + out.write("axes.edgecolor: \"%s\"\n" % fgcolor) + out.write("axes.labelcolor: \"%s\"\n" % fgcolor) + out.write("axes.titlecolor: \"%s\"\n" % fgcolor) + out.write("figure.facecolor: \"%s\"\n" % bgcolor) + out.write("figure.edgecolor: \"%s\"\n" % fgcolor) + out.write("text.color: \"%s\"\n" % fgcolor) + out.write("xtick.color: \"%s\"\n" % fgcolor) + out.write("ytick.color: \"%s\"\n" % fgcolor) + # seems to require named color, is there a better way? + out.write("axes.prop_cycle: cycler('color', ['%s'])" % primarycolor) + plt.style.use(name) + os.unlink(name) + +def apply_brand_colors(filename): + with open(filename) as brand_file: + brand = yaml.safe_load(brand_file.read()) + def apply(): + apply_mpl_colors(brand["color"]["background"], brand["color"]["foreground"], brand["color"]["primary"]) + return apply + +united_colors = apply_brand_colors("united-brand.yml") +slate_colors = apply_brand_colors("slate-brand.yml") +``` + +### No crossref or caption +```{python} +#| echo: false +#| renderings: [light, dark] +import numpy as np +import matplotlib.pyplot as plt + +# Parameters for the normal distribution +mean = 0 +std_dev = 1 + +# Generate data +x = np.linspace(mean - 4*std_dev, mean + 4*std_dev, 1000) +y = (1/(std_dev * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x - mean) / std_dev)**2) + +# Plotting +united_colors() +plt.figure(figsize=(8, 5)) +plt.plot(x, y, label='Normal Distribution') +plt.title('Normal Distribution Curve') +plt.xlabel('X-axis') +plt.ylabel('Probability Density') +plt.legend() +plt.grid(True) +plt.show() + +slate_colors() +plt.figure(figsize=(8, 5)) +plt.plot(x, y, label='Normal Distribution') +plt.title('Normal Distribution Curve') +plt.xlabel('X-axis') +plt.ylabel('Probability Density') +plt.legend() +plt.grid(True) +plt.show() +``` + +### With crossref but no caption + +And `echo: true` + +::: {#fig-matplotlib-line} +```{python} +#| echo: true +#| renderings: [light, dark] +import matplotlib.pyplot as plt + +united_colors() +plt.title("Hello") +plt.plot([1,2,3]) +plt.grid(True) +plt.show(block=False) + +slate_colors() +plt.figure() +plt.title("Hello") +plt.plot([1,2,3]) +plt.grid(True) +plt.show(block=False) +``` +::: + +### With caption but no crossref + +::: {} +```{python} +#| echo: false +#| renderings: [light, dark] + +# author: "anthropic claude-3-5-sonnet-20240620" +import numpy as np +import matplotlib.pyplot as plt + +# Generate data points +x = np.linspace(0, 2 * np.pi, 100) +y = np.sin(x) + +united_colors() +plt.figure(figsize=(10, 6)) +plt.plot(x, y) +plt.title('Sine Wave') +plt.xlabel('x') +plt.ylabel('sin(x)') +plt.grid(True) +plt.axhline(y=0, color='k', linestyle='--') +plt.axvline(x=0, color='k', linestyle='--') +plt.show() + +slate_colors() +plt.figure(figsize=(10, 6)) +plt.plot(x, y) +plt.title('Sine Wave') +plt.xlabel('x') +plt.ylabel('sin(x)') +plt.grid(True) +plt.axhline(y=0, color='k', linestyle='--') +plt.axvline(x=0, color='k', linestyle='--') +plt.show() +``` +matplotlib sine wave + +::: + +### With crossref and caption + +::: {#fig-matplotlib-cosine} +```{python} +#| echo: false +#| renderings: [dark, light] +import numpy as np +import matplotlib.pyplot as plt + +# Generate data points +x = np.linspace(0, 2 * np.pi, 100) +y = np.cos(x) + +# Create the plot +slate_colors() +plt.figure(figsize=(10, 6)) +plt.plot(x, y) +plt.title('Cosine Wave') +plt.xlabel('x') +plt.ylabel('cos(x)') +plt.grid(True) +plt.axhline(y=0, color='k', linestyle='--') +plt.axvline(x=0, color='k', linestyle='--') +plt.show() + +united_colors() +plt.figure(figsize=(10, 6)) +plt.plot(x, y) +plt.title('Cosine Wave') +plt.xlabel('x') +plt.ylabel('cos(x)') +plt.grid(True) +plt.axhline(y=0, color='k', linestyle='--') +plt.axvline(x=0, color='k', linestyle='--') +plt.show() +``` + +matplotlib cosine wave +::: + +Here's a [link](https://example.com). + + +{{< lipsum 3 >}} \ No newline at end of file diff --git a/tests/docs/smoke-all/dark-mode/slate-brand.yml b/tests/docs/smoke-all/dark-mode/slate-brand.yml new file mode 100644 index 00000000000..8b640748b2e --- /dev/null +++ b/tests/docs/smoke-all/dark-mode/slate-brand.yml @@ -0,0 +1,12 @@ +color: + background: "#282B30" + foreground: "#aaaaaa" + primary: white +typography: + fonts: + - family: "Montserrat" + source: google + base: + family: "Montserrat" + monospace-block: + background-color: "#435" diff --git a/tests/docs/smoke-all/dark-mode/united-brand.yml b/tests/docs/smoke-all/dark-mode/united-brand.yml new file mode 100644 index 00000000000..1f1a4c50144 --- /dev/null +++ b/tests/docs/smoke-all/dark-mode/united-brand.yml @@ -0,0 +1,10 @@ +color: + background: "#ffffff" + foreground: "#333333" + primary: red +typography: + fonts: + - family: "Montserrat" + source: google + base: + family: "Montserrat"