Skip to content

Commit 3bc777d

Browse files
project can specify light and dark brand files
1 parent c2d7723 commit 3bc777d

File tree

10 files changed

+227
-30
lines changed

10 files changed

+227
-30
lines changed

src/command/render/pandoc-html.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,13 @@ export async function resolveSassBundles(
101101
const userBrand = bundle.user?.findIndex((layer) => layer === "brand");
102102
let cloned = false;
103103
if (userBrand && userBrand !== -1) {
104-
// console.log('light brand order specified', userBrand, cloned);
105104
bundle = cloneDeep(bundle);
106105
cloned = true;
107106
bundle.user!.splice(userBrand, 1, ...(maybeBrandBundle?.user || []));
108107
foundBrand.light = true;
109108
}
110109
const darkBrand = bundle.dark?.user?.findIndex((layer) => layer === "brand");
111110
if (darkBrand && darkBrand !== -1) {
112-
// console.log('dark brand order specified', darkBrand, cloned);
113111
if (!cloned) {
114112
bundle = cloneDeep(bundle);
115113
}

src/project/project-shared.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -521,12 +521,12 @@ export async function projectResolveBrand(
521521
) as BrandJson;
522522
return new Brand(brand, dirname(brandPath), project.dir);
523523
}
524-
async function loadLocalBrand(brandPath: string) : Promise<Brand> {
524+
async function loadRelativeBrand(brandPath: string, dir: string = dirname(fileName!)) : Promise<Brand> {
525525
let resolved: string = "";
526526
if (brandPath.startsWith("/")) {
527527
resolved = join(project.dir, brandPath);
528528
} else {
529-
resolved = join(dirname(fileName!), brandPath);
529+
resolved = join(dir, brandPath);
530530
}
531531
return await loadBrand(resolved);
532532
}
@@ -538,28 +538,28 @@ export async function projectResolveBrand(
538538
let fileNames = ["_brand.yml", "_brand.yaml"].map((file) =>
539539
join(project.dir, file)
540540
);
541-
if (project?.config?.brand === false) {
541+
let brand = project?.config?.brand as Boolean | string | {light?: string, dark?: string};
542+
if (brand === false) {
542543
project.brandCache.brand = undefined;
543544
return project.brandCache.brand;
544545
}
545-
if (typeof project?.config?.brand === "string") {
546-
fileNames = [join(project.dir, project.config.brand)];
546+
if (typeof brand === "object" && brand &&
547+
("light" in brand || "dark" in brand)) {
548+
project.brandCache.brand = {
549+
light: brand.light ? await loadRelativeBrand(brand.light, project.dir) : undefined,
550+
dark: brand.dark ? await loadRelativeBrand(brand.dark, project.dir) : undefined,
551+
};
552+
return project.brandCache.brand;
553+
}
554+
if (typeof brand === "string") {
555+
fileNames = [join(project.dir, brand)];
547556
}
548557

549558
for (const brandPath of fileNames) {
550559
if (!existsSync(brandPath)) {
551560
continue;
552561
}
553-
const brand = await readAndValidateYamlFromFile(
554-
brandPath,
555-
refSchema("brand", "Format-independent brand configuration."),
556-
"Brand validation failed for " + brandPath + ".",
557-
) as BrandJson;
558-
project.brandCache.brand = {light: new Brand(
559-
brand,
560-
dirname(brandPath),
561-
project.dir,
562-
)};
562+
project.brandCache.brand = {light: await loadBrand(brandPath)};
563563
}
564564
return project.brandCache.brand;
565565
} else {
@@ -576,14 +576,14 @@ export async function projectResolveBrand(
576576
return fileInformation.brand;
577577
}
578578
if (typeof brand === "string") {
579-
fileInformation.brand = {light: await loadLocalBrand(brand)};
579+
fileInformation.brand = {light: await loadRelativeBrand(brand)};
580580
return fileInformation.brand;
581581
} else {
582582
assert(typeof brand === "object");
583583
if ("light" in brand || "dark" in brand) {
584584
let light, dark;
585585
if (typeof brand.light === "string") {
586-
light = await loadLocalBrand(brand.light)
586+
light = await loadRelativeBrand(brand.light)
587587
} else {
588588
light = new Brand(
589589
brand.light!,
@@ -592,7 +592,7 @@ export async function projectResolveBrand(
592592
);
593593
}
594594
if (typeof brand.dark === "string") {
595-
dark = await loadLocalBrand(brand.dark)
595+
dark = await loadRelativeBrand(brand.dark)
596596
} else {
597597
dark = new Brand(
598598
brand.dark!,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
project:
2+
type: default
3+
brand:
4+
dark: red-background.yml
5+
light: blue-background.yml
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
color:
2+
background: "#ccddff"
3+
foreground: "#336644"
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
title: "dark brand - ggplot"
3+
brand: false
4+
execute:
5+
echo: false
6+
warning: false
7+
---
8+
9+
```{r}
10+
#| echo: false
11+
#| warning: false
12+
library(ggplot2)
13+
14+
ggplot_theme <- function(bgcolor, fgcolor) {
15+
theme_minimal(base_size = 11) %+%
16+
theme(
17+
panel.border = element_blank(),
18+
panel.grid.major.y = element_blank(),
19+
panel.grid.minor.y = element_blank(),
20+
panel.grid.major.x = element_blank(),
21+
panel.grid.minor.x = element_blank(),
22+
text = element_text(colour = fgcolor),
23+
axis.text = element_text(colour = fgcolor),
24+
rect = element_rect(colour = bgcolor, fill = bgcolor),
25+
plot.background = element_rect(fill = bgcolor, colour = NA),
26+
axis.line = element_line(colour = fgcolor),
27+
axis.ticks = element_line(colour = fgcolor)
28+
)
29+
}
30+
31+
brand_ggplot <- function(brand_yml) {
32+
brand <- yaml::yaml.load_file(brand_yml)
33+
ggplot_theme(brand$color$background, brand$color$foreground)
34+
}
35+
36+
blue_theme <- brand_ggplot("blue-background.yml")
37+
red_theme <- brand_ggplot("red-background.yml")
38+
39+
colour_scale <- scale_colour_manual(values = c("darkorange", "purple", "cyan4"))
40+
```
41+
42+
43+
```{r}
44+
#| renderings: [light, dark]
45+
ggplot(mtcars, aes(mpg, wt)) +
46+
geom_point(aes(colour = factor(cyl))) + blue_theme + colour_scale
47+
ggplot(mtcars, aes(mpg, wt)) +
48+
geom_point(aes(colour = factor(cyl))) + red_theme + colour_scale
49+
```
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
title: "dark brand - ggplot"
3+
theme:
4+
light: simplex
5+
dark: cyborg # same effect as [brand, cyborg]
6+
execute:
7+
echo: false
8+
warning: false
9+
---
10+
11+
```{r}
12+
#| echo: false
13+
#| warning: false
14+
library(ggplot2)
15+
16+
ggplot_theme <- function(bgcolor, fgcolor) {
17+
theme_minimal(base_size = 11) %+%
18+
theme(
19+
panel.border = element_blank(),
20+
panel.grid.major.y = element_blank(),
21+
panel.grid.minor.y = element_blank(),
22+
panel.grid.major.x = element_blank(),
23+
panel.grid.minor.x = element_blank(),
24+
text = element_text(colour = fgcolor),
25+
axis.text = element_text(colour = fgcolor),
26+
rect = element_rect(colour = bgcolor, fill = bgcolor),
27+
plot.background = element_rect(fill = bgcolor, colour = NA),
28+
axis.line = element_line(colour = fgcolor),
29+
axis.ticks = element_line(colour = fgcolor)
30+
)
31+
}
32+
33+
brand_ggplot <- function(brand_yml) {
34+
brand <- yaml::yaml.load_file(brand_yml)
35+
ggplot_theme(brand$color$background, brand$color$foreground)
36+
}
37+
38+
blue_theme <- brand_ggplot("blue-background.yml")
39+
red_theme <- brand_ggplot("red-background.yml")
40+
41+
colour_scale <- scale_colour_manual(values = c("darkorange", "purple", "cyan4"))
42+
```
43+
44+
45+
```{r}
46+
#| renderings: [light, dark]
47+
ggplot(mtcars, aes(mpg, wt)) +
48+
geom_point(aes(colour = factor(cyl))) + blue_theme + colour_scale
49+
ggplot(mtcars, aes(mpg, wt)) +
50+
geom_point(aes(colour = factor(cyl))) + red_theme + colour_scale
51+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
color:
2+
background: "#42070b"
3+
foreground: "#cceedd"
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
title: "dark brand - ggplot"
3+
execute:
4+
echo: false
5+
warning: false
6+
---
7+
8+
```{r}
9+
#| echo: false
10+
#| warning: false
11+
library(ggplot2)
12+
13+
ggplot_theme <- function(bgcolor, fgcolor) {
14+
theme_minimal(base_size = 11) %+%
15+
theme(
16+
panel.border = element_blank(),
17+
panel.grid.major.y = element_blank(),
18+
panel.grid.minor.y = element_blank(),
19+
panel.grid.major.x = element_blank(),
20+
panel.grid.minor.x = element_blank(),
21+
text = element_text(colour = fgcolor),
22+
axis.text = element_text(colour = fgcolor),
23+
rect = element_rect(colour = bgcolor, fill = bgcolor),
24+
plot.background = element_rect(fill = bgcolor, colour = NA),
25+
axis.line = element_line(colour = fgcolor),
26+
axis.ticks = element_line(colour = fgcolor)
27+
)
28+
}
29+
30+
brand_ggplot <- function(brand_yml) {
31+
brand <- yaml::yaml.load_file(brand_yml)
32+
ggplot_theme(brand$color$background, brand$color$foreground)
33+
}
34+
35+
blue_theme <- brand_ggplot("blue-background.yml")
36+
red_theme <- brand_ggplot("red-background.yml")
37+
38+
colour_scale <- scale_colour_manual(values = c("darkorange", "purple", "cyan4"))
39+
```
40+
41+
42+
```{r}
43+
#| renderings: [light, dark]
44+
ggplot(mtcars, aes(mpg, wt)) +
45+
geom_point(aes(colour = factor(cyl))) + blue_theme + colour_scale
46+
ggplot(mtcars, aes(mpg, wt)) +
47+
geom_point(aes(colour = factor(cyl))) + red_theme + colour_scale
48+
```

tests/integration/playwright/tests/html-dark-mode-nojs.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,14 @@ test('Light brand default, no JS', async ({ page }) => {
2020
await expect(locatr).toHaveClass('fullcontent quarto-light');
2121
await expect(locatr).toHaveCSS('background-color', 'rgb(252, 252, 252)');
2222
});
23+
24+
25+
test('Project dark brand default, no JS', async ({ page }) => {
26+
// This document use a custom theme file that change the background color of the title banner
27+
// Same user defined color should be used in both dark and light theme
28+
await page.goto('./html/dark-brand/project-light-dark/simple.html');
29+
const locatr = await page.locator('body').first();
30+
await expect(locatr).toHaveClass('fullcontent quarto-dark');
31+
await expect(locatr).toHaveCSS('background-color', 'rgb(66, 7, 11)');
32+
});
33+
Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,55 @@
11
import { test, expect } from '@playwright/test';
22

3-
test('Dark and light brand after user themes', async ({ page }) => {
4-
// This document use a custom theme file that change the background color of the title banner
5-
// Same user defined color should be used in both dark and light theme
6-
await page.goto('./html/dark-brand/brand-after-theme.html');
3+
async function check_red_blue(page) {
74
const locatr = await page.locator('body').first();
85
await expect(locatr).toHaveClass('fullcontent quarto-dark');
96
await expect(locatr).toHaveCSS('background-color', 'rgb(66, 7, 11)');
107
await page.locator("a.quarto-color-scheme-toggle").click();
118
const locatr2 = await page.locator('body').first();
129
await expect(locatr2).toHaveCSS('background-color', 'rgb(204, 221, 255)');
13-
});
10+
}
1411

15-
16-
test('Dark and light brand before user themes', async ({ page }) => {
17-
// This document use a custom theme file that change the background color of the title banner
18-
// Same user defined color should be used in both dark and light theme
19-
await page.goto('./html/dark-brand/brand-before-theme.html');
12+
async function check_theme_overrides(page) {
2013
const locatr = await page.locator('body').first();
2114
await expect(locatr).toHaveClass('fullcontent quarto-light');
2215
await expect(locatr).toHaveCSS('background-color', 'rgb(252, 252, 252)');
2316
await page.locator("a.quarto-color-scheme-toggle").click();
2417
const locatr2 = await page.locator('body').first();
2518
await expect(locatr2).toHaveCSS('background-color', 'rgb(6, 6, 6)');
19+
}
20+
// themes used in these documents have background colors
21+
22+
test('Dark and light brand after user themes', async ({ page }) => {
23+
// brand overrides theme background color
24+
await page.goto('./html/dark-brand/brand-after-theme.html');
25+
await check_red_blue(page);
2626
});
27+
28+
test('Dark and light brand before user themes', async ({ page }) => {
29+
// theme will override brand
30+
await page.goto('./html/dark-brand/brand-before-theme.html');
31+
await check_theme_overrides(page);
32+
});
33+
34+
// project tests
35+
36+
test('Project specifies dark and light brands', async ({ page }) => {
37+
await page.goto('./html/dark-brand/project-light-dark/simple.html');
38+
await check_red_blue(page);
39+
});
40+
41+
test('Project brand before user themes', async ({ page }) => {
42+
// theme will override brand
43+
await page.goto('./html/dark-brand/project-light-dark/brand-under-theme.html');
44+
await check_theme_overrides(page);
45+
});
46+
47+
test('Brand false remove project brand', async ({ page }) => {
48+
// theme will override brand
49+
await page.goto('./html/dark-brand/project-light-dark/brand-false.html');
50+
const locatr = await page.locator('body').first();
51+
await expect(locatr).toHaveClass('fullcontent quarto-light');
52+
await expect(locatr).toHaveCSS('background-color', 'rgb(255, 255, 255)');
53+
// no toggle
54+
expect(await page.locator('a.quarto-color-scheme-toggle').count()).toEqual(0);
55+
});

0 commit comments

Comments
 (0)