Skip to content

Commit

Permalink
Merge pull request #11027 from quarto-dev/feature/brand-yaml-schema-c…
Browse files Browse the repository at this point in the history
…onsolidation

Feature/brand yaml schema consolidation
  • Loading branch information
cscheid authored Oct 11, 2024
2 parents 52d5a6e + b2d4797 commit aca2088
Show file tree
Hide file tree
Showing 15 changed files with 954 additions and 605 deletions.
152 changes: 85 additions & 67 deletions src/core/brand/brand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
import {
Brand as BrandJson,
BrandFont,
BrandLogoExplicitResource,
BrandNamedLogo,
BrandNamedThemeColor,
BrandStringLightDark,
BrandTypography,
BrandTypographyOptionsBase,
BrandTypographyOptionsHeadings,
} from "../../resources/types/schema-types.ts";
import { InternalError } from "../lib/error.ts";

// we can't programmatically convert typescript types to string arrays,
// so we have to define this manually. They should match `BrandNamedThemeColor` in schema-types.ts
Expand All @@ -32,7 +34,6 @@ export const defaultColorNames: BrandNamedThemeColor[] = [
"danger",
"light",
"dark",
"emphasis",
"link",
];

Expand All @@ -42,10 +43,15 @@ const defaultLogoNames: string[] = [
"large",
];

type CanonicalLogoInfo = {
light: BrandLogoExplicitResource;
dark: BrandLogoExplicitResource;
};

type ProcessedBrandData = {
color: Record<string, string>;
typography: BrandTypography;
logo: Record<string, BrandStringLightDark>;
logo: Record<string, CanonicalLogoInfo>;
};

export class Brand {
Expand Down Expand Up @@ -86,34 +92,65 @@ export class Brand {
if (link) {
typography.link = link;
}
const monospace = this.getFont("monospace");
let monospace = this.getFont("monospace");
let monospaceInline = this.getFont("monospace-inline");
let monospaceBlock = this.getFont("monospace-block");

if (monospace) {
if (typeof monospace === "string") {
monospace = { family: monospace };
}
typography.monospace = monospace;
}
const monospaceInline = this.getFont("monospace-inline");
if (monospaceInline && typeof monospaceInline === "string") {
monospaceInline = { family: monospaceInline };
}
if (monospaceBlock && typeof monospaceBlock === "string") {
monospaceBlock = { family: monospaceBlock };
}

// cut off control flow here so the type checker knows these
// are not strings
if (typeof monospace === "string") {
throw new InternalError("should never happen");
}
if (typeof monospaceInline === "string") {
throw new InternalError("should never happen");
}
if (typeof monospaceBlock === "string") {
throw new InternalError("should never happen");
}

if (monospace || monospaceInline) {
typography["monospace-inline"] = {
...(monospace ?? {}),
...(monospaceInline ?? {}),
};
}
const monospaceBlock = this.getFont("monospace-block");
if (monospaceBlock) {
if (typeof monospaceBlock === "string") {
monospaceBlock = { family: monospaceBlock };
}
}
if (monospace || monospaceBlock) {
typography["monospace-block"] = {
...(monospace ?? {}),
...(monospaceBlock ?? {}),
};
}

const logo: Record<string, BrandStringLightDark> = {};
for (const logoName of Object.keys(data.logo?.images ?? {})) {
logo[logoName] = this.getLogo(logoName);
}
for (const logoName of Object.keys(data.logo ?? {})) {
if (logoName === "images") {
continue;
const logo: Record<string, CanonicalLogoInfo> = {};
for (
const size of [
"small",
"medium",
"large",
] as ("small" | "medium" | "large")[]
) {
const v = this.getLogo(size);
if (v) {
logo[size] = v;
}
logo[logoName] = this.getLogo(logoName);
}

return {
Expand Down Expand Up @@ -195,61 +232,42 @@ export class Brand {
return fonts ?? [];
}

getLogoResource(name: string): BrandLogoExplicitResource {
const entry = this.data.logo?.images?.[name];
if (!entry) {
return { path: name };
}
if (typeof entry === "string") {
return { path: entry };
}
return entry;
}

// the same implementation as getColor except we can also return {light,dark}
// assuming for now that with only contains strings, not {light,dark}
getLogo(name: string): BrandStringLightDark {
const seenValues = new Set<string>();
do {
if (seenValues.has(name)) {
throw new Error(
`Circular reference in _brand.yml color definitions: ${
Array.from(seenValues).join(
" -> ",
)
}`,
);
}
seenValues.add(name);
if (this.data.logo?.images?.[name]) {
name = this.data.logo.images[name] as string;
} else if (
defaultLogoNames.includes(name as BrandNamedLogo) &&
this.data.logo?.[name as BrandNamedLogo]
) {
const brandSLD: BrandStringLightDark = this.data
.logo[name as BrandNamedLogo]!;
if (typeof brandSLD == "string") {
name = brandSLD;
} else {
const ret: BrandStringLightDark = {};
// we need to actually-recurse and not just use the loop
// because two paths light/dark
const light = brandSLD.light;
if (light) {
const brandSLD2 = this.getLogo(light);
if (typeof brandSLD2 == "string") {
ret.light = brandSLD2;
} else {
ret.light = brandSLD2.light;
}
}
const dark = brandSLD.dark;
if (dark) {
const brandSLD2 = this.getLogo(dark);
if (typeof brandSLD2 == "string") {
ret.dark = brandSLD2;
} else {
ret.dark = brandSLD2.light;
}
}
return ret;
}
} else {
return name;
}
} while (seenValues.size < 100); // 100 ought to be enough for anyone, with apologies to Bill Gates
throw new Error(
"Recursion depth exceeded 100 in _brand.yml logo definitions",
);
getLogo(name: "small" | "medium" | "large"): CanonicalLogoInfo | undefined {
const entry = this.data.logo?.[name];
if (!entry) {
return undefined;
}
if (typeof entry === "string") {
const res = this.getLogoResource(entry);
return {
light: res,
dark: res,
};
}
const lightEntry = entry?.light
? this.getLogoResource(entry.light)
: undefined;
const darkEntry = entry?.dark
? this.getLogoResource(entry.dark)
: undefined;
if (lightEntry && darkEntry) {
return {
light: lightEntry,
dark: darkEntry,
};
}
}
}
4 changes: 3 additions & 1 deletion src/core/sass/brand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,11 @@ const brandTypographyBundle = (
const resolveHTMLFontInformation = (
kind: FontKind,
): HTMLFontInformation | undefined => {
const resolvedFontOptions = brand.data.typography?.[kind];
let resolvedFontOptions = brand.data.typography?.[kind];
if (!resolvedFontOptions) {
return undefined;
} else if (typeof resolvedFontOptions === "string") {
resolvedFontOptions = { family: resolvedFontOptions };
}
const family = resolvedFontOptions.family;
const font = getFontFamilies(family);
Expand Down
2 changes: 1 addition & 1 deletion src/format/reveal/format-reveal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ const determineRevealLogo = (format: Format): string | undefined => {
return logoInfo;
} else {
// what to do about light vs dark?
return logoInfo.light ?? logoInfo.dark;
return logoInfo?.light.path ?? logoInfo?.dark.path;
}
}
}
Expand Down
14 changes: 6 additions & 8 deletions src/project/types/website/website-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,9 @@ export async function websiteNavigationConfig(project: ProjectContext) {
const logo = projectBrand.processedData.logo.medium ??
projectBrand.processedData.logo.small ??
projectBrand.processedData.logo.large;
if (typeof logo === "string") {
sidebars[0].logo = logo;
} else if (typeof logo === "object") {
sidebars[0].logo = logo.light; // TODO: This needs smarts to work on light+dark themes
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;
}
}
}
Expand All @@ -186,10 +185,9 @@ export async function websiteNavigationConfig(project: ProjectContext) {
const logo = projectBrand.processedData.logo.small ??
projectBrand.processedData.logo.medium ??
projectBrand.processedData.logo.large;
if (typeof logo === "string") {
navbar.logo = logo;
} else if (typeof logo === "object") {
navbar.logo = logo.light; // TODO: This needs smarts to work on light+dark themes
if (logo) {
navbar.logo = logo.light.path; // TODO: This needs smarts to work on light+dark themes
navbar["logo-alt"] = logo.light.alt;
}
}

Expand Down
Loading

0 comments on commit aca2088

Please sign in to comment.