mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 06:00:39 +00:00
feat(editor): add CJK fallback fonts to monospace stacks
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { cssFontFamilyForName, DEFAULT_MONO_FALLBACK_FONTS, DEFAULT_MONO_FONT_FAMILY, FONT_FAMILIES, LEGACY_PRESET_FONT_MAP, normalizeCustomFontFamilyInput, readableFontFamily } from "../appFonts";
|
||||
import { displayFontFamily, isPresetFontFamily } from "../fontFamilyOptions";
|
||||
|
||||
describe("appFonts", () => {
|
||||
it("includes CJK fallbacks in DEFAULT_MONO_FALLBACK_FONTS", () => {
|
||||
expect(DEFAULT_MONO_FALLBACK_FONTS).toContain("PingFang SC");
|
||||
expect(DEFAULT_MONO_FALLBACK_FONTS).toContain("Hiragino Sans GB");
|
||||
expect(DEFAULT_MONO_FALLBACK_FONTS).toContain("Microsoft YaHei");
|
||||
expect(DEFAULT_MONO_FALLBACK_FONTS).toMatch(/monospace$/);
|
||||
});
|
||||
|
||||
it("appends CJK fallbacks to DEFAULT_MONO_FONT_FAMILY", () => {
|
||||
expect(DEFAULT_MONO_FONT_FAMILY).toContain("'Fira Code'");
|
||||
expect(DEFAULT_MONO_FONT_FAMILY).toContain(DEFAULT_MONO_FALLBACK_FONTS);
|
||||
});
|
||||
|
||||
it("appends CJK fallbacks to every preset font family", () => {
|
||||
for (const preset of FONT_FAMILIES) {
|
||||
expect(preset.value).toContain(DEFAULT_MONO_FALLBACK_FONTS);
|
||||
}
|
||||
});
|
||||
|
||||
it("maps legacy preset values to updated stacks with CJK fallbacks", () => {
|
||||
expect(LEGACY_PRESET_FONT_MAP["'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace"]).toBe(DEFAULT_MONO_FONT_FAMILY);
|
||||
expect(LEGACY_PRESET_FONT_MAP["'JetBrains Mono', 'Fira Code', monospace"]).toContain(DEFAULT_MONO_FALLBACK_FONTS);
|
||||
expect(LEGACY_PRESET_FONT_MAP["'Consolas', 'Courier New', monospace"]).toContain(DEFAULT_MONO_FALLBACK_FONTS);
|
||||
expect(LEGACY_PRESET_FONT_MAP["monospace"]).toBe(DEFAULT_MONO_FALLBACK_FONTS);
|
||||
});
|
||||
|
||||
it("constructs CSS font family for system font name with CJK fallbacks", () => {
|
||||
expect(cssFontFamilyForName("Ubuntu Mono")).toBe(`'Ubuntu Mono', ${DEFAULT_MONO_FALLBACK_FONTS}`);
|
||||
expect(cssFontFamilyForName("JetBrains Mono")).toBe(`'JetBrains Mono', ${DEFAULT_MONO_FALLBACK_FONTS}`);
|
||||
expect(cssFontFamilyForName("Font's Name")).toBe(`'Font\\'s Name', ${DEFAULT_MONO_FALLBACK_FONTS}`);
|
||||
});
|
||||
|
||||
it("extracts readable font family name from font stacks", () => {
|
||||
expect(readableFontFamily(DEFAULT_MONO_FONT_FAMILY)).toBe("Fira Code");
|
||||
expect(readableFontFamily(`'JetBrains Mono', ${DEFAULT_MONO_FALLBACK_FONTS}`)).toBe("JetBrains Mono");
|
||||
expect(readableFontFamily("Consolas")).toBe("Consolas");
|
||||
});
|
||||
|
||||
it("normalizes custom font family input", () => {
|
||||
expect(normalizeCustomFontFamilyInput("")).toBe("");
|
||||
expect(normalizeCustomFontFamilyInput(" ")).toBe("");
|
||||
expect(normalizeCustomFontFamilyInput("Hack")).toBe(`'Hack', ${DEFAULT_MONO_FALLBACK_FONTS}`);
|
||||
// Custom font stacks with quotes or commas are preserved verbatim
|
||||
expect(normalizeCustomFontFamilyInput("'My Custom Font', monospace")).toBe("'My Custom Font', monospace");
|
||||
expect(normalizeCustomFontFamilyInput("Fira Code, PingFang SC, monospace")).toBe("Fira Code, PingFang SC, monospace");
|
||||
});
|
||||
|
||||
it("recognizes preset font labels for both current and legacy preset values", () => {
|
||||
expect(displayFontFamily(DEFAULT_MONO_FONT_FAMILY)).toBe("Fira Code");
|
||||
expect(displayFontFamily(DEFAULT_MONO_FALLBACK_FONTS)).toBe("System Monospace");
|
||||
|
||||
// Legacy preset values
|
||||
expect(displayFontFamily("'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace")).toBe("Fira Code");
|
||||
expect(displayFontFamily("'JetBrains Mono', 'Fira Code', monospace")).toBe("JetBrains Mono");
|
||||
expect(displayFontFamily("monospace")).toBe("System Monospace");
|
||||
|
||||
// Custom font fallback
|
||||
expect(displayFontFamily("'My Font', monospace")).toBe("My Font");
|
||||
});
|
||||
|
||||
it("identifies preset font values for both current and legacy presets", () => {
|
||||
expect(isPresetFontFamily(DEFAULT_MONO_FONT_FAMILY)).toBe(true);
|
||||
expect(isPresetFontFamily(DEFAULT_MONO_FALLBACK_FONTS)).toBe(true);
|
||||
expect(isPresetFontFamily("'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace")).toBe(true);
|
||||
expect(isPresetFontFamily("monospace")).toBe(true);
|
||||
expect(isPresetFontFamily("'Unknown Font', monospace")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -3,26 +3,41 @@ export const FONT_MONO_CSS_VAR = "--font-mono";
|
||||
export const DATA_GRID_FONT_FAMILY_CSS_VAR = "--dbx-data-grid-font-family";
|
||||
|
||||
export const DEFAULT_UI_FONT_FAMILY = `"Geist Variable", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Segoe UI", system-ui, sans-serif`;
|
||||
// CJK and system fallback fonts appended to monospace font stacks so Chinese/CJK
|
||||
// glyphs fall back smoothly to sans-serif CJK fonts (e.g. PingFang SC on macOS,
|
||||
// Microsoft YaHei on Windows) instead of ugly SimSun/NSimSun or bitmap fonts (dbx#674, dbx#1510).
|
||||
export const DEFAULT_MONO_FALLBACK_FONTS = `"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", monospace`;
|
||||
|
||||
// Keep in sync with the editor font default in settingsStore; also the baseline
|
||||
// `--font-mono` value in tokens.css, which plugin sandboxes consume via the bridge.
|
||||
export const DEFAULT_MONO_FONT_FAMILY = `'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace`;
|
||||
export const DEFAULT_MONO_FONT_FAMILY = `'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', ${DEFAULT_MONO_FALLBACK_FONTS}`;
|
||||
export const DEFAULT_DATA_GRID_FONT_FAMILY = `"Geist Variable Tabular", "Geist Variable", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif`;
|
||||
|
||||
// Native-feeling UI option without DBX's bundled/brand font at the front of the stack.
|
||||
export const SYSTEM_UI_FONT_FAMILY = `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`;
|
||||
|
||||
export const FONT_FAMILIES: { value: string; label: string }[] = [
|
||||
{ value: "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace", label: "Fira Code" },
|
||||
{ value: "'JetBrains Mono', 'Fira Code', monospace", label: "JetBrains Mono" },
|
||||
{ value: "'Cascadia Code', 'Cascadia Mono', monospace", label: "Cascadia Code" },
|
||||
{ value: "'Source Code Pro', monospace", label: "Source Code Pro" },
|
||||
{ value: "'SF Mono', 'Menlo', monospace", label: "SF Mono / Menlo" },
|
||||
{ value: "'Consolas', 'Courier New', monospace", label: "Consolas" },
|
||||
{ value: "monospace", label: "System Monospace" },
|
||||
{ value: `'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', ${DEFAULT_MONO_FALLBACK_FONTS}`, label: "Fira Code" },
|
||||
{ value: `'JetBrains Mono', 'Fira Code', ${DEFAULT_MONO_FALLBACK_FONTS}`, label: "JetBrains Mono" },
|
||||
{ value: `'Cascadia Code', 'Cascadia Mono', ${DEFAULT_MONO_FALLBACK_FONTS}`, label: "Cascadia Code" },
|
||||
{ value: `'Source Code Pro', ${DEFAULT_MONO_FALLBACK_FONTS}`, label: "Source Code Pro" },
|
||||
{ value: `'SF Mono', 'Menlo', ${DEFAULT_MONO_FALLBACK_FONTS}`, label: "SF Mono / Menlo" },
|
||||
{ value: `'Consolas', 'Courier New', ${DEFAULT_MONO_FALLBACK_FONTS}`, label: "Consolas" },
|
||||
{ value: DEFAULT_MONO_FALLBACK_FONTS, label: "System Monospace" },
|
||||
];
|
||||
|
||||
export const LEGACY_PRESET_FONT_MAP: Record<string, string> = {
|
||||
"'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace": DEFAULT_MONO_FONT_FAMILY,
|
||||
"'JetBrains Mono', 'Fira Code', monospace": `'JetBrains Mono', 'Fira Code', ${DEFAULT_MONO_FALLBACK_FONTS}`,
|
||||
"'Cascadia Code', 'Cascadia Mono', monospace": `'Cascadia Code', 'Cascadia Mono', ${DEFAULT_MONO_FALLBACK_FONTS}`,
|
||||
"'Source Code Pro', monospace": `'Source Code Pro', ${DEFAULT_MONO_FALLBACK_FONTS}`,
|
||||
"'SF Mono', 'Menlo', monospace": `'SF Mono', 'Menlo', ${DEFAULT_MONO_FALLBACK_FONTS}`,
|
||||
"'Consolas', 'Courier New', monospace": `'Consolas', 'Courier New', ${DEFAULT_MONO_FALLBACK_FONTS}`,
|
||||
monospace: DEFAULT_MONO_FALLBACK_FONTS,
|
||||
};
|
||||
|
||||
export function cssFontFamilyForName(name: string): string {
|
||||
return `'${name.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}', monospace`;
|
||||
return `'${name.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}', ${DEFAULT_MONO_FALLBACK_FONTS}`;
|
||||
}
|
||||
|
||||
export function readableFontFamily(value: string): string {
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { cssFontFamilyForName, FONT_FAMILIES, readableFontFamily } from "@/lib/app/appFonts";
|
||||
import { cssFontFamilyForName, FONT_FAMILIES, LEGACY_PRESET_FONT_MAP, readableFontFamily } from "@/lib/app/appFonts";
|
||||
import { listSystemFonts } from "@/lib/backend/api";
|
||||
|
||||
let cachedSystemFontNames: string[] | null = null;
|
||||
let pendingSystemFontNames: Promise<string[]> | null = null;
|
||||
|
||||
const presetFontLabels = new Map(FONT_FAMILIES.map((font) => [font.value, font.label]));
|
||||
const presetFontValues = new Set(FONT_FAMILIES.map((font) => font.value));
|
||||
const presetFontLabels = new Map<string, string>([
|
||||
...FONT_FAMILIES.map((font) => [font.value, font.label] as const),
|
||||
...Object.entries(LEGACY_PRESET_FONT_MAP).map(([legacyVal, newVal]) => {
|
||||
const label = FONT_FAMILIES.find((f) => f.value === newVal)?.label ?? "System Monospace";
|
||||
return [legacyVal, label] as const;
|
||||
}),
|
||||
]);
|
||||
const presetFontValues = new Set<string>([...FONT_FAMILIES.map((font) => font.value), ...Object.keys(LEGACY_PRESET_FONT_MAP)]);
|
||||
|
||||
export function buildFontFamilyOptions(systemFontNames: readonly string[], selectedValues: readonly string[] = [], leadingValues: readonly string[] = []): string[] {
|
||||
return [...new Set([...leadingValues, ...FONT_FAMILIES.map((font) => font.value), ...systemFontNames.map(cssFontFamilyForName), ...selectedValues.filter(Boolean)])];
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from "@/stores/settingsStore";
|
||||
import type { AiConfigItem } from "@/types/ai";
|
||||
import { DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION } from "@/lib/dataGrid/dataGridCopyExtractor";
|
||||
import { DEFAULT_MONO_FONT_FAMILY } from "@/lib/app/appFonts";
|
||||
|
||||
describe("normalizeEditorSettings", () => {
|
||||
it("preserves comment-first naming for existing settings and permits opting out", () => {
|
||||
@@ -647,6 +648,17 @@ describe("normalizeEditorSettings", () => {
|
||||
expect(normalizeEditorSettings({ tableFontFamily: " " }).tableFontFamily).toBe(defaultFontFamily);
|
||||
});
|
||||
|
||||
it("defaults the editor font with CJK fallbacks and migrates legacy default presets", () => {
|
||||
expect(normalizeEditorSettings({}).fontFamily).toBe(DEFAULT_MONO_FONT_FAMILY);
|
||||
expect(
|
||||
normalizeEditorSettings({
|
||||
fontFamily: "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace",
|
||||
}).fontFamily,
|
||||
).toBe(DEFAULT_MONO_FONT_FAMILY);
|
||||
expect(normalizeEditorSettings({ fontFamily: "'Custom Mono', monospace" }).fontFamily).toBe("'Custom Mono', monospace");
|
||||
expect(normalizeEditorSettings({ fontFamily: " " }).fontFamily).toBe(DEFAULT_MONO_FONT_FAMILY);
|
||||
});
|
||||
|
||||
it("shows cell detail metadata by default and preserves collapsed state", () => {
|
||||
expect(normalizeEditorSettings({}).cellDetailMetadataCollapsed).toBe(false);
|
||||
expect(normalizeEditorSettings({ cellDetailMetadataCollapsed: true }).cellDetailMetadataCollapsed).toBe(true);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineStore } from "pinia";
|
||||
import { computed, ref } from "vue";
|
||||
import { aiConfigToItem, generateId, getConfigKey } from "@/lib/ai/aiConfigList";
|
||||
import { AI_CONVERSATION_FONT_FAMILY_DEFAULT, AI_CONVERSATION_FONT_SIZE_DEFAULT, normalizeAiConversationFontFamily, normalizeAiConversationFontSize } from "@/lib/ai/aiTypography";
|
||||
import { DEFAULT_DATA_GRID_FONT_FAMILY, DEFAULT_UI_FONT_FAMILY } from "@/lib/app/appFonts";
|
||||
import { DEFAULT_DATA_GRID_FONT_FAMILY, DEFAULT_MONO_FONT_FAMILY, DEFAULT_UI_FONT_FAMILY } from "@/lib/app/appFonts";
|
||||
import { emitAlwaysOnTopToolbarVisibilityChanged } from "@/lib/app/windowAlwaysOnTop";
|
||||
import { defaultBackgroundImageSettings, normalizeBackgroundImageSettings, type BackgroundImageSettings } from "@/lib/app/appBackgroundImage";
|
||||
import * as api from "@/lib/backend/api";
|
||||
@@ -1176,7 +1176,7 @@ export const EXECUTE_MODE_CURRENT_DEFAULT_VERSION = 1;
|
||||
export const SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION = 1;
|
||||
|
||||
export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
|
||||
fontFamily: "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace",
|
||||
fontFamily: DEFAULT_MONO_FONT_FAMILY,
|
||||
fontSize: 13,
|
||||
uiFontFamily: DEFAULT_UI_FONT_FAMILY,
|
||||
aiFontFamily: AI_CONVERSATION_FONT_FAMILY_DEFAULT,
|
||||
@@ -1392,10 +1392,13 @@ function normalizeUiScale(value: unknown): number {
|
||||
return Math.min(MAX_UI_SCALE, Math.max(MIN_UI_SCALE, Math.round(value * 100) / 100));
|
||||
}
|
||||
|
||||
const LEGACY_DEFAULT_MONO_FONT_FAMILY = "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace";
|
||||
|
||||
function normalizeFontFamily(value: unknown, fallback: string): string {
|
||||
if (typeof value !== "string") return fallback;
|
||||
const trimmed = value.trim();
|
||||
return trimmed || fallback;
|
||||
if (!trimmed || trimmed === LEGACY_DEFAULT_MONO_FONT_FAMILY) return fallback;
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function normalizeDrawerWidth(value: unknown, min: number, fallback: number): number {
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
--font-heading: var(--font-sans);
|
||||
/* Baseline mono token; App.vue overrides it inline with the editor font
|
||||
setting, and plugin sandboxes receive the resolved value via the bridge. */
|
||||
--font-mono: 'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace;
|
||||
--font-mono: 'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", monospace;
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
|
||||
@@ -105,7 +105,7 @@ const themes = () => ({
|
||||
"--color-destructive": "#f3625f",
|
||||
"--color-destructive-foreground": "#18181b",
|
||||
"--font-sans": '-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif',
|
||||
"--font-mono": "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace",
|
||||
"--font-mono": "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", monospace",
|
||||
"--radius-md": "6px",
|
||||
"--radius-lg": "8px",
|
||||
}
|
||||
@@ -124,7 +124,7 @@ const themes = () => ({
|
||||
"--color-destructive": "#e7000b",
|
||||
"--color-destructive-foreground": "#ffffff",
|
||||
"--font-sans": '-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif',
|
||||
"--font-mono": "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace",
|
||||
"--font-mono": "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", monospace",
|
||||
"--radius-md": "6px",
|
||||
"--radius-lg": "8px",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user