1 parent
beae94e932
commit
de4d70970c
51 files changed
+4034
-421
No files matched your search
@@ -1,5 +1,22 @@
|
||||
|
||||
function clockMinutes(value, fallback) {
|
||||
const match = /^(\d{2}):(\d{2})$/.exec(String(value || ""));
|
||||
return match ? Number(match[1]) * 60 + Number(match[2]) : fallback;
|
||||
}
|
||||
|
||||
// "schedule": light between theme_light_from and theme_dark_from (local time),
|
||||
// dark otherwise; the window may wrap past midnight.
|
||||
function scheduledTheme(prefs = state.prefs, now = new Date()) {
|
||||
const lightFrom = clockMinutes(prefs.themeLightFrom, 7 * 60);
|
||||
const darkFrom = clockMinutes(prefs.themeDarkFrom, 19 * 60);
|
||||
const minute = now.getHours() * 60 + now.getMinutes();
|
||||
if (lightFrom === darkFrom) return "light";
|
||||
const light = lightFrom < darkFrom ? minute >= lightFrom && minute < darkFrom : minute >= lightFrom || minute < darkFrom;
|
||||
return light ? "light" : "dark";
|
||||
}
|
||||
|
||||
function resolveTheme(theme) {
|
||||
if (theme === "schedule") return scheduledTheme();
|
||||
if (theme !== "system") return theme;
|
||||
return matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
|
||||
}
|
||||
@@ -14,6 +31,7 @@ const preferenceMap = {
|
||||
dateFormat: "date_format", timeFormat: "time_format", iconSize: "icon_size", toolbarDensity: "toolbar_density", sidebarItemHeight: "sidebar_item_height",
|
||||
readerMaxWidth: "reader_max_width", composerWidth: "composer_width", selectionColor: "selection_color", unreadFontWeight: "unread_font_weight", unreadBadgeStyle: "unread_badge_style",
|
||||
compactHeaders: "compact_headers", showBorders: "show_borders", scrollbarDensity: "scrollbar_density", monochrome: "monochrome", folderBackground: "folder_background", messageBackground: "message_background", readerBackground: "reader_background", adaptiveEmailColors: "adaptive_email_colors", skinDepth: "skin_depth", skinGradients: "skin_gradients",
|
||||
themeLightFrom: "theme_light_from", themeDarkFrom: "theme_dark_from", accountColor: "account_color",
|
||||
};
|
||||
const preferenceTimers = new Map();
|
||||
let presetPromotionTimer = null;
|
||||
@@ -40,19 +58,19 @@ const interfaceStyleProfiles = {
|
||||
preferences: { density: "comfortable", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 58, folderPaneWidth: 236, messagePaneWidth: 430, borderRadius: 8, contrast: "soft", iconSize: 18, toolbarDensity: "normal", sidebarItemHeight: 32, readerMaxWidth: 980, composerWidth: 940, selectionColor: "#dce9ff", unreadFontWeight: 700, unreadBadgeStyle: "dot", compactHeaders: false, showBorders: false, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
gmail: {
|
||||
label: "Gmail inspired",
|
||||
label: "Gmail",
|
||||
preferences: { density: "comfortable", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 56, folderPaneWidth: 248, messagePaneWidth: 440, borderRadius: 10, contrast: "soft", iconSize: 19, toolbarDensity: "normal", sidebarItemHeight: 34, readerMaxWidth: 980, composerWidth: 960, selectionColor: "#d3e3fd", unreadFontWeight: 700, unreadBadgeStyle: "dot", compactHeaders: false, showBorders: false, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
proton: {
|
||||
label: "Proton inspired",
|
||||
label: "Proton",
|
||||
preferences: { density: "comfortable", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 56, folderPaneWidth: 242, messagePaneWidth: 430, borderRadius: 9, contrast: "normal", iconSize: 18, toolbarDensity: "normal", sidebarItemHeight: 33, readerMaxWidth: 980, composerWidth: 940, selectionColor: "#e7e1ff", unreadFontWeight: 720, unreadBadgeStyle: "dot", compactHeaders: false, showBorders: false, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
zoho: {
|
||||
label: "Zoho inspired",
|
||||
label: "Zoho",
|
||||
preferences: { density: "compact", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 52, folderPaneWidth: 236, messagePaneWidth: 420, borderRadius: 6, contrast: "normal", iconSize: 18, toolbarDensity: "compact", sidebarItemHeight: 30, readerMaxWidth: 0, composerWidth: 920, selectionColor: "#e1ebff", unreadFontWeight: 710, unreadBadgeStyle: "dot", compactHeaders: true, showBorders: true, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
outlook: {
|
||||
label: "Outlook inspired",
|
||||
label: "Outlook",
|
||||
preferences: { density: "compact", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 50, folderPaneWidth: 232, messagePaneWidth: 420, borderRadius: 4, contrast: "normal", iconSize: 18, toolbarDensity: "compact", sidebarItemHeight: 30, readerMaxWidth: 0, composerWidth: 940, selectionColor: "#deecf9", unreadFontWeight: 700, unreadBadgeStyle: "dot", compactHeaders: true, showBorders: true, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
minimal: {
|
||||
@@ -64,19 +82,19 @@ const interfaceStyleProfiles = {
|
||||
preferences: { density: "compact", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 50, folderPaneWidth: 220, messagePaneWidth: 400, borderRadius: 2, contrast: "normal", iconSize: 17, toolbarDensity: "compact", sidebarItemHeight: 27, readerMaxWidth: 0, composerWidth: 880, selectionColor: "#32373c", unreadFontWeight: 720, unreadBadgeStyle: "solid", compactHeaders: true, showBorders: true, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
fastmail: {
|
||||
label: "Fastmail inspired",
|
||||
label: "Fastmail",
|
||||
preferences: { density: "comfortable", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 56, folderPaneWidth: 240, messagePaneWidth: 430, borderRadius: 7, contrast: "normal", iconSize: 18, toolbarDensity: "normal", sidebarItemHeight: 32, readerMaxWidth: 980, composerWidth: 940, selectionColor: "#d9e8ff", unreadFontWeight: 710, unreadBadgeStyle: "dot", compactHeaders: false, showBorders: false, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
"apple-mail": {
|
||||
label: "Apple Mail inspired",
|
||||
label: "Apple Mail",
|
||||
preferences: { density: "comfortable", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 54, folderPaneWidth: 238, messagePaneWidth: 430, borderRadius: 9, contrast: "soft", iconSize: 18, toolbarDensity: "normal", sidebarItemHeight: 32, readerMaxWidth: 920, composerWidth: 920, selectionColor: "#dcebff", unreadFontWeight: 700, unreadBadgeStyle: "dot", compactHeaders: false, showBorders: false, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
yahoo: {
|
||||
label: "Yahoo Mail inspired",
|
||||
label: "Yahoo",
|
||||
preferences: { density: "comfortable", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 56, folderPaneWidth: 244, messagePaneWidth: 435, borderRadius: 10, contrast: "normal", iconSize: 19, toolbarDensity: "normal", sidebarItemHeight: 33, readerMaxWidth: 960, composerWidth: 950, selectionColor: "#ede3ff", unreadFontWeight: 720, unreadBadgeStyle: "solid", compactHeaders: false, showBorders: false, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
hey: {
|
||||
label: "HEY inspired",
|
||||
label: "HEY",
|
||||
preferences: { density: "comfortable", preview: "right", scale: "normal", fontFamily: "humanist", fontSize: 14, rowHeight: 62, folderPaneWidth: 250, messagePaneWidth: 450, borderRadius: 12, contrast: "normal", iconSize: 19, toolbarDensity: "spacious", sidebarItemHeight: 36, readerMaxWidth: 900, composerWidth: 930, selectionColor: "#fff0a8", unreadFontWeight: 760, unreadBadgeStyle: "solid", compactHeaders: false, showBorders: false, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
nord: {
|
||||
@@ -88,27 +106,27 @@ const interfaceStyleProfiles = {
|
||||
preferences: { density: "comfortable", preview: "right", scale: "normal", fontFamily: "system", fontSize: 14, rowHeight: 60, folderPaneWidth: 240, messagePaneWidth: 440, borderRadius: 2, contrast: "high", iconSize: 20, toolbarDensity: "normal", sidebarItemHeight: 34, readerMaxWidth: 980, composerWidth: 960, selectionColor: "#ffe500", unreadFontWeight: 800, unreadBadgeStyle: "solid", compactHeaders: false, showBorders: true, scrollbarDensity: "normal", monochrome: false },
|
||||
},
|
||||
github: {
|
||||
label: "GitHub inspired",
|
||||
label: "GitHub",
|
||||
preferences: { density: "compact", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 52, folderPaneWidth: 236, messagePaneWidth: 430, borderRadius: 6, contrast: "normal", iconSize: 18, toolbarDensity: "normal", sidebarItemHeight: 30, readerMaxWidth: 980, composerWidth: 940, selectionColor: "#ddf4ff", unreadFontWeight: 720, unreadBadgeStyle: "dot", compactHeaders: false, showBorders: true, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
gitlab: {
|
||||
label: "GitLab inspired",
|
||||
label: "GitLab",
|
||||
preferences: { density: "compact", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 52, folderPaneWidth: 238, messagePaneWidth: 430, borderRadius: 7, contrast: "normal", iconSize: 18, toolbarDensity: "normal", sidebarItemHeight: 30, readerMaxWidth: 980, composerWidth: 940, selectionColor: "#eee7ff", unreadFontWeight: 720, unreadBadgeStyle: "dot", compactHeaders: false, showBorders: true, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
discord: {
|
||||
label: "Discord inspired",
|
||||
label: "Discord",
|
||||
preferences: { density: "comfortable", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 56, folderPaneWidth: 246, messagePaneWidth: 440, borderRadius: 8, contrast: "normal", iconSize: 19, toolbarDensity: "normal", sidebarItemHeight: 34, readerMaxWidth: 980, composerWidth: 950, selectionColor: "#e0e3ff", unreadFontWeight: 720, unreadBadgeStyle: "solid", compactHeaders: false, showBorders: false, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
slack: {
|
||||
label: "Slack inspired",
|
||||
label: "Slack",
|
||||
preferences: { density: "comfortable", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 56, folderPaneWidth: 244, messagePaneWidth: 435, borderRadius: 8, contrast: "normal", iconSize: 18, toolbarDensity: "normal", sidebarItemHeight: 33, readerMaxWidth: 960, composerWidth: 950, selectionColor: "#f3e6f5", unreadFontWeight: 720, unreadBadgeStyle: "dot", compactHeaders: false, showBorders: false, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
reddit: {
|
||||
label: "Reddit inspired",
|
||||
label: "Reddit",
|
||||
preferences: { density: "compact", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 52, folderPaneWidth: 236, messagePaneWidth: 430, borderRadius: 9, contrast: "normal", iconSize: 18, toolbarDensity: "normal", sidebarItemHeight: 31, readerMaxWidth: 980, composerWidth: 940, selectionColor: "#fff0e8", unreadFontWeight: 720, unreadBadgeStyle: "solid", compactHeaders: false, showBorders: true, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
notion: {
|
||||
label: "Notion inspired",
|
||||
label: "Notion",
|
||||
preferences: { density: "comfortable", preview: "right", scale: "normal", fontFamily: "system", fontSize: 14, rowHeight: 58, folderPaneWidth: 238, messagePaneWidth: 430, borderRadius: 5, contrast: "soft", iconSize: 18, toolbarDensity: "normal", sidebarItemHeight: 32, readerMaxWidth: 900, composerWidth: 920, selectionColor: "#efefef", unreadFontWeight: 700, unreadBadgeStyle: "outline", compactHeaders: false, showBorders: false, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
dracula: {
|
||||
@@ -156,17 +174,41 @@ const interfaceStyleProfiles = {
|
||||
preferences: { density: "comfortable", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 58, folderPaneWidth: 236, messagePaneWidth: 430, borderRadius: 10, contrast: "normal", iconSize: 18, toolbarDensity: "normal", sidebarItemHeight: 33, readerMaxWidth: 960, composerWidth: 940, selectionColor: "#1e3a5f", unreadFontWeight: 720, unreadBadgeStyle: "solid", compactHeaders: false, showBorders: false, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
linear: {
|
||||
label: "Linear inspired",
|
||||
label: "Linear",
|
||||
preferences: { density: "compact", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 46, folderPaneWidth: 232, messagePaneWidth: 440, borderRadius: 6, contrast: "normal", iconSize: 16, toolbarDensity: "compact", sidebarItemHeight: 28, readerMaxWidth: 860, composerWidth: 940, selectionColor: "#1d1f33", unreadFontWeight: 640, unreadBadgeStyle: "dot", compactHeaders: false, showBorders: false, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
material: {
|
||||
label: "Material You inspired",
|
||||
label: "Material You",
|
||||
preferences: { density: "comfortable", preview: "right", scale: "normal", fontFamily: "system", fontSize: 14, rowHeight: 60, folderPaneWidth: 256, messagePaneWidth: 440, borderRadius: 12, contrast: "normal", iconSize: 20, toolbarDensity: "normal", sidebarItemHeight: 40, readerMaxWidth: 960, composerWidth: 940, selectionColor: "#4a4458", unreadFontWeight: 700, unreadBadgeStyle: "solid", compactHeaders: false, showBorders: false, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
retro: {
|
||||
label: "Retro 95",
|
||||
preferences: { density: "compact", preview: "right", scale: "normal", fontFamily: "system", fontSize: 12, rowHeight: 44, folderPaneWidth: 220, messagePaneWidth: 420, borderRadius: 0, contrast: "normal", iconSize: 16, toolbarDensity: "compact", sidebarItemHeight: 24, readerMaxWidth: 0, composerWidth: 880, selectionColor: "#000080", unreadFontWeight: 700, unreadBadgeStyle: "dot", compactHeaders: true, showBorders: true, scrollbarDensity: "normal", monochrome: false },
|
||||
},
|
||||
winxp: {
|
||||
label: "Windows XP",
|
||||
preferences: { density: "compact", preview: "right", scale: "normal", fontFamily: "system", fontSize: 12, rowHeight: 48, folderPaneWidth: 232, messagePaneWidth: 430, borderRadius: 3, contrast: "normal", iconSize: 16, toolbarDensity: "normal", sidebarItemHeight: 26, readerMaxWidth: 0, composerWidth: 900, selectionColor: "#c1d2ee", unreadFontWeight: 700, unreadBadgeStyle: "dot", compactHeaders: false, showBorders: true, scrollbarDensity: "normal", monochrome: false },
|
||||
},
|
||||
osx: {
|
||||
label: "Mac OS X",
|
||||
preferences: { density: "compact", preview: "right", scale: "normal", fontFamily: "system", fontSize: 12, rowHeight: 50, folderPaneWidth: 220, messagePaneWidth: 430, borderRadius: 5, contrast: "normal", iconSize: 16, toolbarDensity: "normal", sidebarItemHeight: 24, readerMaxWidth: 0, composerWidth: 900, selectionColor: "#c5d8f4", unreadFontWeight: 700, unreadBadgeStyle: "solid", compactHeaders: false, showBorders: true, scrollbarDensity: "normal", monochrome: false },
|
||||
},
|
||||
ios6: {
|
||||
label: "iOS 6",
|
||||
preferences: { density: "comfortable", preview: "right", scale: "normal", fontFamily: "system", fontSize: 14, rowHeight: 62, folderPaneWidth: 250, messagePaneWidth: 440, borderRadius: 5, contrast: "normal", iconSize: 20, toolbarDensity: "normal", sidebarItemHeight: 40, readerMaxWidth: 0, composerWidth: 900, selectionColor: "#cfe0f7", unreadFontWeight: 700, unreadBadgeStyle: "solid", compactHeaders: false, showBorders: true, scrollbarDensity: "normal", monochrome: false },
|
||||
},
|
||||
win7: {
|
||||
label: "Windows 7",
|
||||
preferences: { density: "compact", preview: "right", scale: "normal", fontFamily: "system", fontSize: 13, rowHeight: 52, folderPaneWidth: 236, messagePaneWidth: 430, borderRadius: 3, contrast: "normal", iconSize: 18, toolbarDensity: "normal", sidebarItemHeight: 28, readerMaxWidth: 960, composerWidth: 900, selectionColor: "#cce8ff", unreadFontWeight: 700, unreadBadgeStyle: "dot", compactHeaders: false, showBorders: true, scrollbarDensity: "normal", monochrome: false },
|
||||
},
|
||||
"frutiger-aero": {
|
||||
label: "Frutiger Aero",
|
||||
preferences: { density: "comfortable", preview: "right", scale: "normal", fontFamily: "system", fontSize: 14, rowHeight: 60, folderPaneWidth: 244, messagePaneWidth: 440, borderRadius: 12, contrast: "normal", iconSize: 20, toolbarDensity: "normal", sidebarItemHeight: 36, readerMaxWidth: 900, composerWidth: 940, selectionColor: "#c9ecff", unreadFontWeight: 700, unreadBadgeStyle: "solid", compactHeaders: false, showBorders: false, scrollbarDensity: "thin", monochrome: false },
|
||||
},
|
||||
beos: {
|
||||
label: "BeOS",
|
||||
preferences: { density: "compact", preview: "right", scale: "normal", fontFamily: "system", fontSize: 12, rowHeight: 46, folderPaneWidth: 224, messagePaneWidth: 430, borderRadius: 2, contrast: "normal", iconSize: 16, toolbarDensity: "compact", sidebarItemHeight: 26, readerMaxWidth: 0, composerWidth: 900, selectionColor: "#d0dcf2", unreadFontWeight: 700, unreadBadgeStyle: "dot", compactHeaders: true, showBorders: true, scrollbarDensity: "normal", monochrome: false },
|
||||
},
|
||||
};
|
||||
|
||||
function resolvedInterfaceSkin(prefs = state.prefs) {
|
||||
@@ -226,10 +268,10 @@ function translateScopedUiText(locale) {
|
||||
const trailing = original.match(/\s*$/)?.[0] || "";
|
||||
node.nodeValue = `${leading}${translated}${trailing}`;
|
||||
}
|
||||
const nodes = [scope, ...scope.querySelectorAll("[title],[aria-label],[placeholder],[data-placeholder]")];
|
||||
const nodes = [scope, ...scope.querySelectorAll("[title],[aria-label],[placeholder],[data-placeholder],optgroup[label]")];
|
||||
for (const node of nodes) {
|
||||
const originals = localizedAttributes.get(node) || {};
|
||||
for (const attribute of ["title", "aria-label", "placeholder", "data-placeholder"]) {
|
||||
for (const attribute of ["title", "aria-label", "placeholder", "data-placeholder", "label"]) {
|
||||
if (!node.hasAttribute(attribute)) continue;
|
||||
if (!(attribute in originals)) originals[attribute] = node.getAttribute(attribute);
|
||||
const translated = map[originals[attribute]];
|
||||
@@ -267,6 +309,7 @@ function applyPreferences() {
|
||||
const paletteStyle = activeInterfaceStyle(p);
|
||||
const fixedStyle = layoutInterfaceStyle(p);
|
||||
const skinDepth = resolvedSkinDepth(p);
|
||||
const previousLook = `${root.dataset.theme}|${root.dataset.interfaceSkin}|${root.dataset.skinDepth}|${root.dataset.skinGradients}`;
|
||||
const visual = fixedStyle
|
||||
? { ...p, ...fixedStyle.preferences }
|
||||
: p;
|
||||
@@ -274,6 +317,22 @@ function applyPreferences() {
|
||||
root.dataset.interfaceSkin = resolvedInterfaceSkin(p);
|
||||
root.dataset.skinDepth = skinDepth;
|
||||
root.dataset.skinGradients = String(Boolean(p.skinGradients));
|
||||
if (/^#[0-9a-f]{6}$/i.test(p.accountColor || "")) {
|
||||
root.dataset.accountColor = "true";
|
||||
root.style.setProperty("--account-color", p.accountColor);
|
||||
} else {
|
||||
delete root.dataset.accountColor;
|
||||
root.style.removeProperty("--account-color");
|
||||
}
|
||||
const nextLook = `${root.dataset.theme}|${root.dataset.interfaceSkin}|${root.dataset.skinDepth}|${root.dataset.skinGradients}`;
|
||||
if (root.dataset.lookReady === "true" && previousLook !== nextLook && !p.reduceMotion) {
|
||||
root.classList.add("theme-transition");
|
||||
clearTimeout(applyPreferences.transitionTimer);
|
||||
applyPreferences.transitionTimer = setTimeout(() => root.classList.remove("theme-transition"), 360);
|
||||
}
|
||||
root.dataset.lookReady = "true";
|
||||
const scheduleRow = $("#pref-theme-schedule-row");
|
||||
if (scheduleRow) scheduleRow.classList.toggle("hidden", p.theme !== "schedule");
|
||||
root.dataset.density = visual.density;
|
||||
root.dataset.preview = visual.preview;
|
||||
root.dataset.scale = visual.scale;
|
||||
@@ -316,7 +375,11 @@ function applyPreferences() {
|
||||
if (lockedRoundcubeSkin) {
|
||||
["--selected", "--folder-bg", "--message-bg", "--reader-bg"].forEach((name) => root.style.removeProperty(name));
|
||||
} else {
|
||||
root.style.setProperty("--selected", visual.selectionColor || "#28352e");
|
||||
// The stored default (#28352e) was picked for the dark theme; treat it as
|
||||
// "automatic" so the light theme keeps its own readable selection colour.
|
||||
const selection = String(visual.selectionColor || "").toLowerCase();
|
||||
if (!selection || selection === "#28352e") root.style.removeProperty("--selected");
|
||||
else root.style.setProperty("--selected", visual.selectionColor);
|
||||
root.style.setProperty("--folder-bg", p.folderBackground || "var(--sidebar)");
|
||||
root.style.setProperty("--message-bg", p.messageBackground || "var(--panel)");
|
||||
root.style.setProperty("--reader-bg", p.readerBackground || "var(--panel)");
|
||||
@@ -339,7 +402,7 @@ function applyPreferences() {
|
||||
const displayedMessageBackground = presetColor("--message-bg", p.messageBackground || "#181c19");
|
||||
const displayedReaderBackground = presetColor("--reader-bg", p.readerBackground || "#181c19");
|
||||
const values = {
|
||||
"#pref-theme": p.theme, "#pref-interface-skin": p.interfaceSkin, "#pref-skin-depth": skinDepth, "#pref-density": visual.density, "#pref-preview": visual.preview, "#pref-scale": visual.scale,
|
||||
"#pref-theme": p.theme, "#pref-theme-light-from": p.themeLightFrom, "#pref-theme-dark-from": p.themeDarkFrom, "#pref-account-color": p.accountColor || "#4d75e6", "#pref-interface-skin": p.interfaceSkin, "#pref-skin-depth": skinDepth, "#pref-density": visual.density, "#pref-preview": visual.preview, "#pref-scale": visual.scale,
|
||||
"#pref-language": p.language, "#pref-color-preset": paletteStyle ? "custom" : p.colorPreset, "#pref-accent": displayedAccent,
|
||||
"#pref-background": displayedBackground, "#pref-panel": displayedPanel, "#pref-text-color": displayedText,
|
||||
"#pref-font-family": visual.fontFamily, "#pref-font-size": visual.fontSize, "#pref-row-height": visual.rowHeight,
|
||||
@@ -350,7 +413,7 @@ function applyPreferences() {
|
||||
"#pref-selection-color": visual.selectionColor, "#pref-unread-weight": visual.unreadFontWeight, "#pref-unread-badge": visual.unreadBadgeStyle, "#pref-scrollbar-density": visual.scrollbarDensity,
|
||||
};
|
||||
Object.entries(values).forEach(([selector, value]) => { const node = $(selector); if (node) node.value = value; });
|
||||
const checks = { "#pref-preset-scale-unlocked": p.presetScaleUnlocked, "#pref-skin-gradients": p.skinGradients, "#pref-adaptive-email-colors": p.adaptiveEmailColors, "#pref-avatars": p.showAvatars, "#pref-attachment-icons": p.showAttachmentIcons, "#pref-flags": p.showFlags, "#pref-reduce-motion": p.reduceMotion, "#compose-pref-mdn": p.requestMdnDefault, "#compose-pref-attachment-reminder": p.attachmentReminder, "#compose-pref-spellcheck": p.spellcheckEnabled, "#pref-compact-headers": visual.compactHeaders, "#pref-show-borders": visual.showBorders, "#pref-monochrome": visual.monochrome, "#pref-folder-bg-enabled": Boolean(p.folderBackground), "#pref-message-bg-enabled": Boolean(p.messageBackground), "#pref-reader-bg-enabled": Boolean(p.readerBackground) };
|
||||
const checks = { "#pref-account-color-enabled": Boolean(p.accountColor), "#pref-preset-scale-unlocked": p.presetScaleUnlocked, "#pref-skin-gradients": p.skinGradients, "#pref-adaptive-email-colors": p.adaptiveEmailColors, "#pref-avatars": p.showAvatars, "#pref-attachment-icons": p.showAttachmentIcons, "#pref-flags": p.showFlags, "#pref-reduce-motion": p.reduceMotion, "#compose-pref-mdn": p.requestMdnDefault, "#compose-pref-attachment-reminder": p.attachmentReminder, "#compose-pref-spellcheck": p.spellcheckEnabled, "#pref-compact-headers": visual.compactHeaders, "#pref-show-borders": visual.showBorders, "#pref-monochrome": visual.monochrome, "#pref-folder-bg-enabled": Boolean(p.folderBackground), "#pref-message-bg-enabled": Boolean(p.messageBackground), "#pref-reader-bg-enabled": Boolean(p.readerBackground) };
|
||||
Object.entries(checks).forEach(([selector, value]) => { const node = $(selector); if (node) node.checked = Boolean(value); });
|
||||
const outputs = { "#pref-accent-value": displayedAccent, "#pref-background-value": displayedBackground, "#pref-panel-value": displayedPanel, "#pref-text-color-value": displayedText, "#pref-font-size-value": `${visual.fontSize} px`, "#pref-row-height-value": `${visual.rowHeight} px`, "#pref-folder-width-value": `${visual.folderPaneWidth} px`, "#pref-message-width-value": `${visual.messagePaneWidth} px`, "#pref-radius-value": `${visual.borderRadius} px`, "#pref-icon-size-value": `${visual.iconSize} px`, "#pref-sidebar-height-value": `${visual.sidebarItemHeight} px`, "#pref-composer-width-value": `${visual.composerWidth} px`, "#pref-selection-color-value": visual.selectionColor, "#pref-unread-weight-value": String(visual.unreadFontWeight) };
|
||||
Object.entries(outputs).forEach(([selector, value]) => { const node = $(selector); if (node) node.textContent = value; });
|
||||
@@ -382,6 +445,7 @@ function applyPreferences() {
|
||||
immersive: tr("skin_depth_immersive_note", "Everything in Standard plus the style's shapes, typography, floating panes, app bar, selection markers and backdrop."),
|
||||
})[skinDepth];
|
||||
}
|
||||
updateContrastWarning(Boolean(paletteStyle));
|
||||
const skinNote = $("#pref-interface-skin-note");
|
||||
if (skinNote) {
|
||||
const styleLabel = state.prefs.language === "pl" ? (state.translations._text?.[paletteStyle?.label] || paletteStyle?.label) : paletteStyle?.label;
|
||||
@@ -391,11 +455,66 @@ function applyPreferences() {
|
||||
}
|
||||
}
|
||||
|
||||
let contrastProbe = null;
|
||||
|
||||
function resolvedCssColor(expression) {
|
||||
if (!contrastProbe) {
|
||||
contrastProbe = document.createElement("span");
|
||||
contrastProbe.setAttribute("aria-hidden", "true");
|
||||
contrastProbe.style.cssText = "position:absolute;width:0;height:0;overflow:hidden;pointer-events:none";
|
||||
document.body.append(contrastProbe);
|
||||
}
|
||||
contrastProbe.style.color = "";
|
||||
contrastProbe.style.color = expression;
|
||||
const value = getComputedStyle(contrastProbe).color;
|
||||
const rgb = value.match(/rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/i);
|
||||
if (rgb) return rgb.slice(1, 4).map((part) => Number(part) / 255);
|
||||
const srgb = value.match(/color\(srgb\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)/i);
|
||||
return srgb ? srgb.slice(1, 4).map(Number) : null;
|
||||
}
|
||||
|
||||
function contrastRatio(first, second) {
|
||||
const luminance = (rgb) => {
|
||||
const [r, g, b] = rgb.map((c) => (c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4));
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
};
|
||||
const [a, b] = [luminance(first), luminance(second)].sort((x, y) => y - x);
|
||||
return (a + 0.05) / (b + 0.05);
|
||||
}
|
||||
|
||||
// Warns while the user's own colours make text hard to read (WCAG AA, 4.5:1
|
||||
// for text, 3:1 for the accent against panels). Predefined palettes are
|
||||
// checked at release time, so the hint is only shown for custom colours.
|
||||
function updateContrastWarning(usingPredefinedPalette) {
|
||||
const warning = $("#pref-contrast-warning");
|
||||
if (!warning) return;
|
||||
if (usingPredefinedPalette || !document.body) { warning.classList.add("hidden"); return; }
|
||||
const color = (name) => resolvedCssColor(`var(${name})`);
|
||||
const checks = [
|
||||
["text", "panel", 4.5, "Text on panels"],
|
||||
["text", "bg", 4.5, "Text on the background"],
|
||||
["text", "selected", 4.5, "Text on the selection"],
|
||||
["muted", "panel", 3, "Secondary text"],
|
||||
["accent", "panel", 3, "Accent on panels"],
|
||||
["accent-ink", "accent", 4.5, "Button text on the accent"],
|
||||
];
|
||||
const problems = [];
|
||||
for (const [fg, bg, minimum, label] of checks) {
|
||||
const a = color(`--${fg}`);
|
||||
const b = color(`--${bg}`);
|
||||
if (!a || !b) continue;
|
||||
const ratio = contrastRatio(a, b);
|
||||
if (ratio < minimum) problems.push(`${trText(label)} ${ratio.toFixed(1)}:1`);
|
||||
}
|
||||
warning.classList.toggle("hidden", problems.length === 0);
|
||||
warning.textContent = problems.length ? `${tr("contrast_warning", "Low contrast")}: ${problems.join(" · ")}` : "";
|
||||
}
|
||||
|
||||
function normalizeServerPreferences(data) {
|
||||
if (!data) return;
|
||||
state.prefs = {
|
||||
...state.prefs,
|
||||
theme: data.theme || root.dataset.defaultTheme || "system", interfaceSkin: data.interface_skin || "default", skinDepth: skinDepths.includes(data.skin_depth) ? data.skin_depth : "standard", skinGradients: Boolean(data.skin_gradients), presetScaleUnlocked: Boolean(data.preset_scale_unlocked), density: data.density || "compact", preview: data.preview_pane || "right", composeFormat: data.compose_format || "html",
|
||||
theme: data.theme || root.dataset.defaultTheme || "system", interfaceSkin: data.interface_skin || "default", skinDepth: skinDepths.includes(data.skin_depth) ? data.skin_depth : "standard", skinGradients: Boolean(data.skin_gradients), themeLightFrom: data.theme_light_from || "07:00", themeDarkFrom: data.theme_dark_from || "19:00", accountColor: data.account_color || "", presetScaleUnlocked: Boolean(data.preset_scale_unlocked), density: data.density || "compact", preview: data.preview_pane || "right", composeFormat: data.compose_format || "html",
|
||||
composeAutosaveMs: Number(data.compose_autosave_ms || 3000), defaultPriority: data.default_priority || "normal", requestMdnDefault: Boolean(data.request_mdn_default), attachmentReminder: data.attachment_reminder !== false, composeFontFamily: data.compose_font_family || "system", replyPosition: data.reply_position || "above", quoteStyle: data.quote_style || "standard", forwardMode: data.forward_mode || "inline", spellcheckEnabled: data.spellcheck_enabled !== false,
|
||||
scale: data.ui_scale || "normal", language: data.language || "en", colorPreset: data.color_preset || "sage",
|
||||
accentColor: data.accent_color || "#6f9c86", backgroundColor: data.background_color || "#121513", panelColor: data.panel_color || "#181c19", textColor: data.text_color || "#edf0ec", fontFamily: data.font_family || "system", fontSize: data.font_size || 13,
|
||||
@@ -660,7 +779,7 @@ function savePreference(key, value) {
|
||||
}
|
||||
|
||||
async function resetAppearance() {
|
||||
const payload = { theme: root.dataset.defaultTheme || "system", interface_skin: "default", skin_depth: "standard", skin_gradients: false, preset_scale_unlocked: false, density: "compact", preview_pane: "right", ui_scale: "normal", color_preset: "sage", accent_color: "#6f9c86", background_color: "#121513", panel_color: "#181c19", text_color: "#edf0ec", font_family: "system", font_size: 13, row_height: 58, folder_pane_width: 228, message_pane_width: 420, border_radius: 4, contrast: "normal", show_avatars: true, show_attachment_icons: true, show_flags: true, reduce_motion: false, date_format: "locale", time_format: "locale", icon_size: 18, toolbar_density: "normal", sidebar_item_height: 29, reader_max_width: 0, composer_width: 900, selection_color: "#28352e", unread_font_weight: 730, unread_badge_style: "dot", compact_headers: false, show_borders: true, scrollbar_density: "thin", monochrome: false, folder_background: "", message_background: "", reader_background: "", adaptive_email_colors: false };
|
||||
const payload = { theme: root.dataset.defaultTheme || "system", interface_skin: "default", skin_depth: "standard", skin_gradients: false, theme_light_from: "07:00", theme_dark_from: "19:00", account_color: "", preset_scale_unlocked: false, density: "compact", preview_pane: "right", ui_scale: "normal", color_preset: "sage", accent_color: "#6f9c86", background_color: "#121513", panel_color: "#181c19", text_color: "#edf0ec", font_family: "system", font_size: 13, row_height: 58, folder_pane_width: 228, message_pane_width: 420, border_radius: 4, contrast: "normal", show_avatars: true, show_attachment_icons: true, show_flags: true, reduce_motion: false, date_format: "locale", time_format: "locale", icon_size: 18, toolbar_density: "normal", sidebar_item_height: 29, reader_max_width: 0, composer_width: 900, selection_color: "#28352e", unread_font_weight: 730, unread_badge_style: "dot", compact_headers: false, show_borders: true, scrollbar_density: "thin", monochrome: false, folder_background: "", message_background: "", reader_background: "", adaptive_email_colors: false };
|
||||
try {
|
||||
normalizeServerPreferences(await request("/settings/display", { method: "PUT", body: JSON.stringify(payload) }));
|
||||
state.mailPrefs = await request("/settings/mailbox", { method: "PUT", body: JSON.stringify({ compact_wide_rows: true }) });
|
||||
|
||||
Reference in new issue
Block a user