feat(desktop): deliver font settings to plugin sandboxes

This commit is contained in:
Jinpy authored and GitHub committed 2026-09-19 19:13:30 +08:00
1 parent ba6b74beea
commit b1b99eccfb
15 files changed
+309 -25

No files matched your search

+51
View File
@@ -175,4 +175,55 @@ describe("useTheme on Linux", () => {
expect(theme.customUiColors.value.background).toBe("#ffffff");
expect(theme.customUiColorsDark.value.background).toBe("#1a1b1e");
});
it("bumps the theme revision so out-of-band token writers (font settings) reach plugin bridges", async () => {
const theme = await loadTheme("light");
const before = theme.themeRevision.value;
theme.bumpThemeRevision();
expect(theme.themeRevision.value).toBe(before + 1);
// No theme classes are touched: this is a pure revision bump.
expect(document.documentElement.classList.contains("disable-transitions")).toBe(false);
});
it("writeRootToken writes the inline override and bumps the revision", async () => {
const theme = await loadTheme("light");
const before = theme.themeRevision.value;
theme.writeRootToken("--dbx-test-token", "1px");
expect(document.documentElement.style.getPropertyValue("--dbx-test-token")).toBe("1px");
expect(theme.themeRevision.value).toBe(before + 1);
document.documentElement.style.removeProperty("--dbx-test-token");
});
it("writeRootToken coalesces rapid writes into one debounced bump", async () => {
const theme = await loadTheme("light");
const before = theme.themeRevision.value;
theme.writeRootToken("--dbx-test-token", "1px", { debounceMs: 30 });
theme.writeRootToken("--dbx-test-token", "2px", { debounceMs: 30 });
// Neither bump has fired yet, but the write itself is immediate.
expect(theme.themeRevision.value).toBe(before);
expect(document.documentElement.style.getPropertyValue("--dbx-test-token")).toBe("2px");
await new Promise((resolve) => setTimeout(resolve, 60));
expect(theme.themeRevision.value).toBe(before + 1);
document.documentElement.style.removeProperty("--dbx-test-token");
});
it("writeRootToken with an immediate write supersedes a pending debounced bump", async () => {
const theme = await loadTheme("light");
const before = theme.themeRevision.value;
theme.writeRootToken("--dbx-test-token", "1px", { debounceMs: 30 });
theme.writeRootToken("--dbx-test-token", "2px");
expect(theme.themeRevision.value).toBe(before + 1);
await new Promise((resolve) => setTimeout(resolve, 60));
// The superseded timer must not fire a second bump.
expect(theme.themeRevision.value).toBe(before + 1);
document.documentElement.style.removeProperty("--dbx-test-token");
});
});
+54 -1
View File
@@ -63,6 +63,39 @@ const isDark = computed(() => resolveAppThemeAppearance(themeMode.value, systemP
// resolved token set (plugin iframe bridge) watch this revision instead.
const themeRevision = ref(0);
// Token mutations on the root that happen outside applyTheme() — e.g. the UI
// and editor font settings write --font-sans / --font-mono inline — bump this
// so open plugin workbench bridges re-read and re-push the resolved tokens.
function bumpThemeRevision() {
themeRevision.value += 1;
}
let pendingBumpTimer: ReturnType<typeof setTimeout> | undefined;
// The one sanctioned way to write a design token onto the root outside
// applyTheme(): writes the inline override AND bumps the theme revision.
// Writing root tokens directly (documentElement.style.setProperty) skips the
// bump and open plugin bridges keep stale tokens — always go through here.
// `debounceMs` coalesces rapid writes (font-family preview keystrokes) into
// one bump; an immediate write afterwards supersedes the pending one.
function writeRootToken(cssVar: string, value: string, options?: { debounceMs?: number }) {
if (typeof document === "undefined") return;
document.documentElement.style.setProperty(cssVar, value);
if (options?.debounceMs) {
if (pendingBumpTimer) clearTimeout(pendingBumpTimer);
pendingBumpTimer = setTimeout(() => {
pendingBumpTimer = undefined;
bumpThemeRevision();
}, options.debounceMs);
return;
}
if (pendingBumpTimer) {
clearTimeout(pendingBumpTimer);
pendingBumpTimer = undefined;
}
bumpThemeRevision();
}
let mediaQuery: MediaQueryList | null = null;
let isListeningForSystemTheme = false;
let cachedTauriWindow: typeof import("@tauri-apps/api/window") | null = null;
@@ -212,5 +245,25 @@ export function useTheme() {
setThemeMode(isDark.value ? "light" : "dark");
}
return { isDark, themeMode, themePalette, customUiColors, customUiColorsDark, activeCustomUiColors, themeRevision, cornerStyle, applyTheme, setThemeMode, setThemePalette, previewThemePalette, clearThemePalettePreview, setCustomUiColors, resetCustomUiColors, setCornerStyle, toggleTheme };
return {
isDark,
themeMode,
themePalette,
customUiColors,
customUiColorsDark,
activeCustomUiColors,
themeRevision,
bumpThemeRevision,
writeRootToken,
cornerStyle,
applyTheme,
setThemeMode,
setThemePalette,
previewThemePalette,
clearThemePalettePreview,
setCustomUiColors,
resetCustomUiColors,
setCornerStyle,
toggleTheme,
};
}