Skip to content

Commit 7fb39fe

Browse files
enable dark brand in brand sass bundles
do not exempt brand when creating dark sass bundles enable toggling when brand is multi and includes dark (as previously for theme)
1 parent 64e9791 commit 7fb39fe

File tree

17 files changed

+493
-79
lines changed

17 files changed

+493
-79
lines changed

src/command/render/pandoc-html.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@ export async function resolveSassBundles(
114114
}
115115

116116
// See if any bundles are providing dark specific css
117-
const hasDark = bundles.some((bundle) => bundle.dark !== undefined);
117+
const hasDark = bundlesWithBrand.some((bundle) => bundle.dark !== undefined);
118118
defaultStyle =
119-
bundles.some((bundle) => bundle.dark !== undefined && bundle.dark.default)
119+
bundlesWithBrand.some((bundle) => bundle.dark !== undefined && bundle.dark.default)
120120
? "dark"
121121
: "light";
122122
const targets: SassTarget[] = [{
@@ -134,7 +134,7 @@ export async function resolveSassBundles(
134134
};
135135

136136
// Provide a dark bundle for this
137-
const darkBundles = bundles.map((bundle) => {
137+
const darkBundles = bundlesWithBrand.map((bundle) => {
138138
bundle = cloneDeep(bundle);
139139
bundle.user = bundle.dark?.user || bundle.user;
140140
bundle.quarto = bundle.dark?.quarto || bundle.quarto;

src/command/render/pandoc.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1467,7 +1467,7 @@ async function resolveExtras(
14671467

14681468
// perform typst-specific merging
14691469
if (isTypstOutput(format.pandoc)) {
1470-
const brand = await project.resolveBrand(input);
1470+
const brand = (await project.resolveBrand(input))?.light;
14711471
const fontdirs: Set<string> = new Set();
14721472
const base_urls = {
14731473
google: "https://fonts.googleapis.com/css",

src/command/render/render-contexts.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -614,11 +614,11 @@ async function resolveFormats(
614614

615615
// resolve brand in project and forward it to format
616616
const brand = await project.resolveBrand(target.source);
617-
mergedFormats[format].render.brand = brand;
617+
mergedFormats[format].render.brand = brand?.light;
618618

619619
// apply defaults from brand yaml under the metadata of the current format
620620
const brandFormatDefaults: Metadata =
621-
(brand?.data?.defaults?.quarto as unknown as Record<
621+
(brand?.light?.data?.defaults?.quarto as unknown as Record<
622622
string,
623623
Record<string, Metadata>
624624
>)?.format

src/core/brand/brand.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,11 @@ export class Brand {
284284
}
285285
}
286286

287+
export type LightDarkBrand = {
288+
light?: Brand;
289+
dark?: Brand;
290+
};
291+
287292
export const getFavicon = (brand: Brand): string | undefined => {
288293
const logoInfo = brand.getLogo("small");
289294
if (!logoInfo) {

src/core/sass/brand.ts

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ import {
2222
BrandFontWeight,
2323
} from "../../resources/types/schema-types.ts";
2424
import { Brand } from "../brand/brand.ts";
25+
import {
26+
darkModeDefault,
27+
} from "../../format/html/format-html-info.ts";
28+
2529

2630
const defaultColorNameMap: Record<string, string> = {
2731
"link-color": "link",
@@ -75,7 +79,10 @@ export async function brandBootstrapSassBundles(
7579
return [{
7680
key,
7781
dependency: "bootstrap",
78-
user: layers,
82+
user: layers.light,
83+
dark: {
84+
user: layers.dark
85+
}
7986
}];
8087
}
8188

@@ -559,29 +566,42 @@ const brandTypographyLayer = (
559566
};
560567
};
561568

569+
export interface LightDarkSassLayers {
570+
light: SassLayer[];
571+
dark: SassLayer[];
572+
}
573+
562574
export async function brandSassLayers(
563575
fileName: string | undefined,
564576
project: ProjectContext,
565577
nameMap: Record<string, string> = {},
566-
): Promise<SassLayer[]> {
578+
): Promise<LightDarkSassLayers> {
567579
const brand = await project.resolveBrand(fileName);
568-
const sassLayers: SassLayer[] = [];
580+
const sassLayers: LightDarkSassLayers = {
581+
light: [],
582+
dark: []
583+
};
569584

570-
if (brand) {
571-
sassLayers.push({
585+
for (const layer of [sassLayers.light, sassLayers.dark]) {
586+
layer.push({
572587
defaults: '$theme: "brand" !default;',
573588
uses: "",
574589
functions: "",
575590
mixins: "",
576591
rules: "",
577592
});
578593
}
579-
if (brand?.data.color) {
580-
sassLayers.push(brandColorLayer(brand, nameMap));
594+
if (brand?.light?.data.color) {
595+
sassLayers.light.push(brandColorLayer(brand?.light, nameMap));
581596
}
582-
583-
if (brand?.data.typography) {
584-
sassLayers.push(brandTypographyLayer(brand));
597+
if (brand?.dark?.data.color) {
598+
sassLayers.dark.push(brandColorLayer(brand?.dark, nameMap));
599+
}
600+
if (brand?.light?.data.typography) {
601+
sassLayers.light.push(brandTypographyLayer(brand?.light));
602+
}
603+
if (brand?.dark?.data.typography) {
604+
sassLayers.dark.push(brandTypographyLayer(brand?.dark));
585605
}
586606

587607
return sassLayers;
@@ -591,16 +611,19 @@ export async function brandBootstrapSassLayers(
591611
fileName: string | undefined,
592612
project: ProjectContext,
593613
nameMap: Record<string, string> = {},
594-
): Promise<SassLayer[]> {
614+
): Promise<LightDarkSassLayers> {
595615
const layers = await brandSassLayers(
596616
fileName,
597617
project,
598618
nameMap,
599619
);
600620

601621
const brand = await project.resolveBrand(fileName);
602-
if (brand?.data?.defaults?.bootstrap) {
603-
layers.unshift(brandDefaultsBootstrapLayer(brand));
622+
if (brand?.light?.data?.defaults?.bootstrap) {
623+
layers.light.unshift(brandDefaultsBootstrapLayer(brand.light));
624+
}
625+
if (brand?.dark?.data?.defaults?.bootstrap) {
626+
layers.dark.unshift(brandDefaultsBootstrapLayer(brand.dark));
604627
}
605628

606629
return layers;
@@ -611,16 +634,16 @@ export async function brandRevealSassLayers(
611634
_format: Format,
612635
project: ProjectContext,
613636
): Promise<SassLayer[]> {
614-
return brandSassLayers(
637+
return (await brandSassLayers(
615638
input,
616639
project,
617640
defaultColorNameMap,
618-
);
641+
)).light;
619642
}
620643

621644
export async function brandSassFormatExtras(
622645
input: string | undefined,
623-
_format: Format,
646+
format: Format,
624647
project: ProjectContext,
625648
): Promise<FormatExtras> {
626649
const htmlSassBundleLayers = await brandBootstrapSassLayers(
@@ -634,7 +657,11 @@ export async function brandSassFormatExtras(
634657
{
635658
key: "brand",
636659
dependency: "bootstrap",
637-
user: htmlSassBundleLayers,
660+
user: htmlSassBundleLayers.light,
661+
dark: {
662+
user: htmlSassBundleLayers.dark,
663+
default: darkModeDefault(format.metadata)
664+
}
638665
},
639666
],
640667
},

src/format/html/format-html-bootstrap.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ import {
7878
documentTitleScssLayer,
7979
processDocumentTitle,
8080
} from "./format-html-title.ts";
81+
import {
82+
darkModeDefault,
83+
} from "./format-html-info.ts";
84+
8185
import { kTemplatePartials } from "../../command/render/template.ts";
8286
import { isHtmlOutput } from "../../config/format.ts";
8387
import { emplaceNotebookPreviews } from "./format-html-notebook.ts";
@@ -1062,13 +1066,8 @@ function bootstrapHtmlFinalizer(format: Format, flags: PandocFlags) {
10621066

10631067
// start body with light or dark class for proper display when JS is disabled
10641068
let initialLightDarkClass = "quarto-light";
1065-
// some logic duplicated from resolveThemeLayer
1066-
const theme = format.metadata.theme;
1067-
if (theme && !Array.isArray(theme) && typeof theme === "object") {
1068-
const keys = Object.keys(theme);
1069-
if(keys.length > 1 && keys[0] === "dark") {
1070-
initialLightDarkClass = "quarto-dark";
1071-
}
1069+
if (darkModeDefault(format.metadata)) {
1070+
initialLightDarkClass = "quarto-dark";
10721071
}
10731072
doc.body.classList.add(initialLightDarkClass);
10741073

src/format/html/format-html-info.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* Copyright (C) 2020-2022 Posit Software, PBC
77
*/
88

9-
import { kTheme } from "../../config/constants.ts";
9+
import { kTheme, kBrand } from "../../config/constants.ts";
1010
import { isHtmlDashboardOutput, isHtmlOutput } from "../../config/format.ts";
1111
import { Format, Metadata } from "../../config/types.ts";
1212

@@ -40,14 +40,15 @@ export function formatDarkMode(format: Format): boolean | undefined {
4040

4141
export function darkModeDefault(metadata?: Metadata): boolean | undefined {
4242
if (metadata !== undefined) {
43-
const theme = metadata[kTheme];
44-
if (theme && typeof (theme) === "object") {
45-
const keys = Object.keys(theme);
46-
if (keys.includes("dark")) {
47-
if (keys[0] === "dark") {
48-
return true;
49-
} else {
50-
return false;
43+
for (const darkable of [metadata[kTheme], metadata[kBrand]]) {
44+
if (darkable && typeof (darkable) === "object") {
45+
const keys = Object.keys(darkable);
46+
if (keys.includes("dark")) {
47+
if (keys[0] === "dark") {
48+
return true;
49+
} else {
50+
return false;
51+
}
5152
}
5253
}
5354
}

src/project/project-shared.ts

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import { DirectiveCell } from "../core/lib/break-quarto-md-types.ts";
4848
import { QuartoJSONSchema, readYamlFromMarkdown } from "../core/yaml.ts";
4949
import { refSchema } from "../core/lib/yaml-schema/common.ts";
5050
import { Brand as BrandJson } from "../resources/types/schema-types.ts";
51-
import { Brand } from "../core/brand/brand.ts";
51+
import { Brand, LightDarkBrand } from "../core/brand/brand.ts";
5252
import { warnOnce } from "../core/log.ts";
5353
import { assert } from "testing/asserts";
5454

@@ -512,7 +512,24 @@ export const ensureFileInformationCache = (
512512
export async function projectResolveBrand(
513513
project: ProjectContext,
514514
fileName?: string,
515-
) {
515+
) : Promise<{light?: Brand, dark?: Brand} | undefined> {
516+
async function loadBrand(brandPath: string) : Promise<Brand> {
517+
const brand = await readAndValidateYamlFromFile(
518+
brandPath,
519+
refSchema("brand", "Format-independent brand configuration."),
520+
"Brand validation failed for " + brandPath + ".",
521+
) as BrandJson;
522+
return new Brand(brand, dirname(brandPath), project.dir);
523+
}
524+
async function loadLocalBrand(brandPath: string) : Promise<Brand> {
525+
let resolved: string = "";
526+
if (brandPath.startsWith("/")) {
527+
resolved = join(project.dir, brandPath);
528+
} else {
529+
resolved = join(dirname(fileName!), brandPath);
530+
}
531+
return await loadBrand(resolved);
532+
}
516533
if (fileName === undefined) {
517534
if (project.brandCache) {
518535
return project.brandCache.brand;
@@ -538,46 +555,60 @@ export async function projectResolveBrand(
538555
refSchema("brand", "Format-independent brand configuration."),
539556
"Brand validation failed for " + brandPath + ".",
540557
) as BrandJson;
541-
project.brandCache.brand = new Brand(
558+
project.brandCache.brand = {light: new Brand(
542559
brand,
543560
dirname(brandPath),
544561
project.dir,
545-
);
562+
)};
546563
}
547564
return project.brandCache.brand;
548565
} else {
549566
const metadata = await project.fileMetadata(fileName);
550567
if (metadata.brand === false) {
551568
return undefined;
552569
}
553-
if (metadata.brand === true || metadata.brand === undefined) {
570+
if (metadata.brand === true || metadata.brand === undefined || metadata.brand === null) {
554571
return project.resolveBrand();
555572
}
556573
const fileInformation = ensureFileInformationCache(project, fileName);
557574
if (fileInformation.brand) {
558575
return fileInformation.brand;
559576
}
560577
if (typeof metadata.brand === "string") {
561-
let brandPath: string = "";
562-
if (brandPath.startsWith("/")) {
563-
brandPath = join(project.dir, metadata.brand);
564-
} else {
565-
brandPath = join(dirname(fileName), metadata.brand);
566-
}
567-
const brand = await readAndValidateYamlFromFile(
568-
brandPath,
569-
refSchema("brand", "Format-independent brand configuration."),
570-
"Brand validation failed for " + brandPath + ".",
571-
) as BrandJson;
572-
fileInformation.brand = new Brand(brand, dirname(brandPath), project.dir);
578+
fileInformation.brand = {light: await loadLocalBrand(metadata.brand)};
573579
return fileInformation.brand;
574580
} else {
575581
assert(typeof metadata.brand === "object");
576-
fileInformation.brand = new Brand(
577-
metadata.brand as BrandJson,
578-
dirname(fileName),
579-
project.dir,
580-
);
582+
if ((metadata.brand as LightDarkBrand).light || (metadata.brand as LightDarkBrand).dark) {
583+
let light;
584+
let ldb = metadata.brand as LightDarkBrand;
585+
if (typeof ldb.light === "string") {
586+
light = await loadLocalBrand(ldb.light)
587+
} else {
588+
light = new Brand(
589+
ldb.light as BrandJson,
590+
dirname(fileName),
591+
project.dir
592+
);
593+
}
594+
let dark;
595+
if (typeof ldb.dark === "string") {
596+
dark = await loadLocalBrand(ldb.dark)
597+
} else {
598+
dark = new Brand(
599+
ldb.dark as BrandJson,
600+
dirname(fileName),
601+
project.dir
602+
);
603+
}
604+
fileInformation.brand = {light, dark};
605+
} else {
606+
fileInformation.brand = {light: new Brand(
607+
metadata.brand as BrandJson,
608+
dirname(fileName),
609+
project.dir,
610+
)};
611+
}
581612
return fileInformation.brand;
582613
}
583614
}

src/project/types.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { RenderServices } from "../command/render/types.ts";
88
import { Metadata, PandocFlags } from "../config/types.ts";
99
import { Format, FormatExtras } from "../config/types.ts";
10-
import { Brand } from "../core/brand/brand.ts";
10+
import { Brand, LightDarkBrand } from "../core/brand/brand.ts";
1111
import { MappedString } from "../core/mapped-text.ts";
1212
import { PartitionedMarkdown } from "../core/pandoc/types.ts";
1313
import { ExecutionEngine, ExecutionTarget } from "../execute/types.ts";
@@ -56,7 +56,7 @@ export type FileInformation = {
5656
engine?: ExecutionEngine;
5757
target?: ExecutionTarget;
5858
metadata?: Metadata;
59-
brand?: Brand;
59+
brand?: LightDarkBrand;
6060
};
6161

6262
export interface ProjectContext {
@@ -70,8 +70,8 @@ export interface ProjectContext {
7070
fileInformationCache: Map<string, FileInformation>;
7171

7272
// This is a cache of _brand.yml for a project
73-
brandCache?: { brand?: Brand };
74-
resolveBrand: (fileName?: string) => Promise<Brand | undefined>;
73+
brandCache?: { brand?: LightDarkBrand};
74+
resolveBrand: (fileName?: string) => Promise<undefined | {light?: Brand | undefined, dark?: Brand | undefined}>;
7575

7676
// expands markdown for a file
7777
// input file doesn't have to be markdown; it can be, for example, a knitr spin file

0 commit comments

Comments
 (0)