Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/visual-editor/src/editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { useProgress } from "../internal/hooks/useProgress.ts";
import { i18nPlatformInstance } from "../utils/i18n/platform.ts";
import { StreamDocument } from "../utils/applyTheme.ts";
import {
googleFontLinkTags,
defaultFonts,
loadGoogleFontsIntoDocument,
} from "../utils/visualEditorFonts.ts";

Expand Down Expand Up @@ -105,7 +105,7 @@ export const Editor = ({
if (typeof window !== "undefined") {
loadGoogleFontsIntoDocument(
window.document,
googleFontLinkTags,
defaultFonts,
"visual-editor-default-fonts"
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ReactNode, useEffect } from "react";
import mapboxPackageJson from "mapbox-gl/package.json";
import {
googleFontLinkTags,
defaultFonts,
loadGoogleFontsIntoDocument,
} from "../../utils/visualEditorFonts.ts";

Expand Down Expand Up @@ -40,7 +40,7 @@ export const loadMapboxIntoIframe = ({
// Load default Google Fonts for the font selector dropdown
loadGoogleFontsIntoDocument(
document,
googleFontLinkTags,
defaultFonts,
"visual-editor-iframe-fonts"
);
}, [document]);
Expand Down
2 changes: 1 addition & 1 deletion packages/visual-editor/src/utils/applyTheme.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ describe("buildCssOverridesStyle", () => {

expect(result).toBe(
'<link rel="preconnect" href="https://fonts.googleapis.com">\n' +
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>\n' +
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous">\n' +
'<link href="https://fonts.googleapis.com/css2?family=Adamina:wght@400&display=swap" rel="stylesheet">' +
'<style id="visual-editor-theme" type="text/css">.components{' +
"--colors-palette-text:black !important;" +
Expand Down
40 changes: 21 additions & 19 deletions packages/visual-editor/src/utils/applyTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ import {
} from "../internal/utils/internalThemeResolver.ts";
import { DevLogger } from "./devLogger.ts";
import {
constructGoogleFontLinkTags,
defaultFonts,
extractInUseFontFamilies,
createFontLinkElements,
generateFontLinkData,
fontLinkDataToHTML,
type FontRegistry,
type FontLinkData,
} from "./visualEditorFonts.ts";
import { ThemeConfig } from "./themeResolver.ts";
import { hexToHSL } from "./colors.ts";
Expand Down Expand Up @@ -55,10 +59,10 @@ export const applyTheme = (
}

// Load only fonts that are actually used in the theme
let fontLinkTags: string;
let fontLinkData: FontLinkData[];
if (!overrides) {
// No theme overrides, use only Open Sans (the default font)
fontLinkTags = constructGoogleFontLinkTags({
fontLinkData = generateFontLinkData({
"Open Sans": defaultFonts["Open Sans"],
});
} else {
Expand All @@ -70,14 +74,16 @@ export const applyTheme = (

if (Object.keys(inUseFonts).length === 0) {
// No fonts found in theme data, use only Open Sans
fontLinkTags = constructGoogleFontLinkTags({
fontLinkData = generateFontLinkData({
"Open Sans": defaultFonts["Open Sans"],
});
} else {
fontLinkTags = constructGoogleFontLinkTags(inUseFonts);
fontLinkData = generateFontLinkData(inUseFonts);
}
}

const fontLinkTags = fontLinkDataToHTML(fontLinkData);

if (Object.keys(themeConfig).length > 0) {
return `${base ?? ""}${fontLinkTags}<style id="${THEME_STYLE_TAG_ID}" type="text/css">${internalApplyTheme(overrides ?? {}, themeConfig)}</style>`;
}
Expand Down Expand Up @@ -136,19 +142,15 @@ const generateContrastingColors = (themeData: ThemeData) => {
};

// Helper function to update font links in a document
const updateFontLinksInDocument = (
document: Document,
fontLinkTags: string
) => {
const updateFontLinksInDocument = (document: Document, fonts: FontRegistry) => {
// Remove only theme-specific font links, preserve default fonts
const existingLinks = document.querySelectorAll(
'link[href*="fonts.googleapis.com"]:not([data-visual-editor-font="true"])'
);
existingLinks.forEach((link) => link.remove());

if (fontLinkTags) {
const tempDiv = document.createElement("div");
tempDiv.innerHTML = fontLinkTags;
const links = tempDiv.querySelectorAll("link");
if (Object.keys(fonts).length > 0) {
const links = createFontLinkElements(fonts);
links.forEach((link) => {
document.head.appendChild(link);
});
Expand All @@ -165,13 +167,13 @@ export const updateThemeInEditor = async (
const mergedThemeData = { ...defaultThemeValues, ...newTheme };
const inUseFonts = extractInUseFontFamilies(mergedThemeData, defaultFonts);

let fontLinkTags: string;
let fontsToLoad: FontRegistry;
if (Object.keys(inUseFonts).length === 0) {
fontLinkTags = constructGoogleFontLinkTags({
fontsToLoad = {
"Open Sans": defaultFonts["Open Sans"],
});
};
} else {
fontLinkTags = constructGoogleFontLinkTags(inUseFonts);
fontsToLoad = inUseFonts;
}

const newThemeTag = internalApplyTheme(newTheme, themeConfig);
Expand All @@ -180,7 +182,7 @@ export const updateThemeInEditor = async (
editorStyleTag.innerText = newThemeTag;
}

updateFontLinksInDocument(window.document, fontLinkTags);
updateFontLinksInDocument(window.document, fontsToLoad);

const observer = new MutationObserver(() => {
const iframe = document.getElementById(
Expand All @@ -191,7 +193,7 @@ export const updateThemeInEditor = async (
if (pagePreviewStyleTag) {
observer.disconnect();
pagePreviewStyleTag.innerText = newThemeTag;
updateFontLinksInDocument(iframe.contentDocument!, fontLinkTags);
updateFontLinksInDocument(iframe.contentDocument!, fontsToLoad);
}
});

Expand Down
2 changes: 1 addition & 1 deletion packages/visual-editor/src/utils/visualEditorFonts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe("extractInUseFontFamilies", () => {
describe("constructGoogleFontLinkTags", () => {
const preconnectTags =
'<link rel="preconnect" href="https://fonts.googleapis.com">\n' +
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>\n';
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous">\n';

it("should return an empty string if the font registry is empty", () => {
const fonts: FontRegistry = {};
Expand Down
131 changes: 90 additions & 41 deletions packages/visual-editor/src/utils/visualEditorFonts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,63 +43,112 @@ export const constructFontSelectOptions = (fonts: FontRegistry) => {
* fall outside of the font's supported values
*/
export const constructGoogleFontLinkTags = (fonts: FontRegistry): string => {
const preconnectTags =
'<link rel="preconnect" href="https://fonts.googleapis.com">\n' +
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>\n';
if (Object.keys(fonts).length === 0) {
return "";
}
return fontLinkDataToHTML(generateFontLinkData(fonts));
};

export type FontLinkData = {
href: string;
rel: string;
crossOrigin?: "anonymous" | "use-credentials";
};

// Helper function to generate weight parameter for Google Fonts API
const generateWeightParam = (fontDetails: FontSpecification): string => {
if ("weights" in fontDetails) {
if (fontDetails.italics) {
const normalWeights = fontDetails.weights
.map((w: number) => `0,${w}`)
.join(";");
const italicWeights = fontDetails.weights
.map((w: number) => `1,${w}`)
.join(";");
return `${normalWeights};${italicWeights}`;
}
return fontDetails.weights.join(";");
} else {
const weightRange =
fontDetails.minWeight === fontDetails.maxWeight
? `${fontDetails.minWeight}`
: `${fontDetails.minWeight}..${fontDetails.maxWeight}`;

const fontEntries = Object.entries(fonts);
const linkTags: string[] = [];
return fontDetails.italics
? `0,${weightRange};1,${weightRange}`
: weightRange;
}
};

// Create a separate link tag for each font
for (const [fontName, fontDetails] of fontEntries) {
// Preconnect links for Google Fonts
const PRECONNECT_LINKS: FontLinkData[] = [
{ href: "https://fonts.googleapis.com", rel: "preconnect" },
{
href: "https://fonts.gstatic.com",
rel: "preconnect",
crossOrigin: "anonymous",
},
];

export const generateFontLinkData = (fonts: FontRegistry): FontLinkData[] => {
const fontLinks = Object.entries(fonts).map(([fontName, fontDetails]) => {
const axes = fontDetails.italics ? ":ital,wght@" : ":wght@";
const weightParam = generateWeightParam(fontDetails);
const param = `family=${fontName.replaceAll(" ", "+")}${axes}${weightParam}`;

let weightParam;
if ("weights" in fontDetails) {
// static font, use enumerated weights
if (fontDetails.italics) {
weightParam =
fontDetails.weights.map((w) => `0,${w};`).join("") +
fontDetails.weights.map((w) => `1,${w};`).join("");
// remove trailing semicolon
weightParam = weightParam.slice(0, -1);
return {
href: `https://fonts.googleapis.com/css2?${param}&display=swap`,
rel: "stylesheet",
};
});

return [...PRECONNECT_LINKS, ...fontLinks];
};

// Convert font link data to HTML string
export const fontLinkDataToHTML = (linkData: FontLinkData[]): string => {
return linkData
.map((link) => {
const crossOriginAttr = link.crossOrigin
? ` crossorigin="${link.crossOrigin}"`
: "";
if (link.rel === "preconnect") {
return `<link rel="${link.rel}" href="${link.href}"${crossOriginAttr}>`;
} else {
weightParam = fontDetails.weights.join(";");
return `<link href="${link.href}" rel="${link.rel}"${crossOriginAttr}>`;
}
} else {
// variable font, use range of weights
const weightRange =
fontDetails.minWeight === fontDetails.maxWeight
? `${fontDetails.minWeight}`
: `${fontDetails.minWeight}..${fontDetails.maxWeight}`;
weightParam = fontDetails.italics
? `0,${weightRange};1,${weightRange}`
: weightRange;
}

const param =
"family=" + fontName.replaceAll(" ", "+") + axes + weightParam;
})
.join("\n");
};

const linkTag = `<link href="https://fonts.googleapis.com/css2?${param}&display=swap" rel="stylesheet">`;
linkTags.push(linkTag);
}
export const googleFontLinkTags = fontLinkDataToHTML(
generateFontLinkData(defaultFonts)
);

const result = linkTags.length ? preconnectTags + linkTags.join("\n") : "";
return result;
// Create DOM elements directly from font data
export const createFontLinkElements = (
fonts: FontRegistry
): HTMLLinkElement[] => {
const linkData = generateFontLinkData(fonts);
return linkData.map((link) => {
const element = document.createElement("link");
element.href = link.href;
element.rel = link.rel;
if (link.crossOrigin) {
element.crossOrigin = link.crossOrigin;
}
return element;
});
};

export const googleFontLinkTags = constructGoogleFontLinkTags(defaultFonts);

// Helper function to load Google Font links into a document
export const loadGoogleFontsIntoDocument = (
document: Document,
fontLinkTags: string,
fonts: FontRegistry,
idPrefix: string = "visual-editor-fonts"
) => {
if (!document.getElementById(`${idPrefix}-0`)) {
const tempDiv = document.createElement("div");
tempDiv.innerHTML = fontLinkTags;
const links = tempDiv.querySelectorAll("link");
const links = createFontLinkElements(fonts);
links.forEach((link, index) => {
link.id = `${idPrefix}-${index}`;
link.setAttribute("data-visual-editor-font", "true");
Expand Down
Loading