1 parent
fb9eab87eb
commit
d91212d24c
40 files changed
+585
-380
No files matched your search
+37
-3
@@ -244,7 +244,7 @@
|
||||
.message-load-actions {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 6px;
|
||||
padding: 7px;
|
||||
box-sizing: border-box;
|
||||
@@ -254,6 +254,40 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.message-pagination {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 30px auto 30px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 7px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.message-pagination.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pagination-arrow {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-color: var(--line);
|
||||
}
|
||||
|
||||
.pagination-arrow svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.message-page-status {
|
||||
min-width: 88px;
|
||||
text-align: center;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
@@ -1355,6 +1389,7 @@ body.reader-fullscreen-open {
|
||||
min-width: max-content;
|
||||
}
|
||||
}
|
||||
|
||||
/* Optional wide-pane single-line message rows. The preference is on by default,
|
||||
but the layout only activates once the message pane has enough horizontal room. */
|
||||
@container message-pane (min-width: 520px) {
|
||||
@@ -1443,5 +1478,4 @@ body.reader-fullscreen-open {
|
||||
html[data-compact-wide-rows="true"] .message-list.selection-mode .message-row .message-icons {
|
||||
grid-column: 6;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -274,6 +274,22 @@
|
||||
max-width: 760px;
|
||||
}
|
||||
|
||||
.appearance-toggle-described {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.appearance-toggle-copy {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.appearance-toggle-copy small {
|
||||
display: block;
|
||||
line-height: 1.35;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.compose-limit-summary {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"loading": "Loading…",
|
||||
"loading_quota": "Loading quota…",
|
||||
"of": "of",
|
||||
"page": "Page",
|
||||
"used": "used",
|
||||
"server_limit_unavailable": "server limit unavailable",
|
||||
"quota_unavailable": "Quota unavailable",
|
||||
@@ -56,5 +57,7 @@
|
||||
"move_copy": "Move / copy",
|
||||
"move_to": "Move to…",
|
||||
"copy_to": "Copy to…",
|
||||
"login_success": "Successfully logged in"
|
||||
"login_success": "Successfully logged in",
|
||||
"adaptive_email_colors": "Adapt HTML mail colors to current theme",
|
||||
"adaptive_email_colors_note": "Adjusts neutral backgrounds and low-contrast text while preserving layout, images and brand colors."
|
||||
}
|
||||
+12
-1
@@ -40,6 +40,7 @@
|
||||
"loading": "Ładowanie…",
|
||||
"loading_quota": "Ładowanie limitu…",
|
||||
"of": "z",
|
||||
"page": "Strona",
|
||||
"used": "wykorzystano",
|
||||
"server_limit_unavailable": "limit serwera niedostępny",
|
||||
"quota_unavailable": "Limit niedostępny",
|
||||
@@ -189,6 +190,14 @@
|
||||
"Message-list background override": "Nadpisanie tła listy wiadomości",
|
||||
"Reader background override": "Nadpisanie tła czytnika",
|
||||
"Control message-list and reader behavior for this account.": "Ustaw zachowanie listy wiadomości i czytnika dla tego konta.",
|
||||
"Message loading": "Ładowanie wiadomości",
|
||||
"Pagination": "Paginacja",
|
||||
"Smooth loading": "Płynne doładowywanie",
|
||||
"Load all messages": "Załaduj wszystkie wiadomości",
|
||||
"Mailbox pages": "Strony skrzynki",
|
||||
"Previous page": "Poprzednia strona",
|
||||
"Next page": "Następna strona",
|
||||
"Page 1 of 1": "Strona 1 z 1",
|
||||
"Messages per page": "Wiadomości na stronę",
|
||||
"Mark as read": "Oznacz jako przeczytane",
|
||||
"Immediately": "Natychmiast",
|
||||
@@ -655,5 +664,7 @@
|
||||
"move_copy": "Przenieś / kopiuj",
|
||||
"move_to": "Przenieś do…",
|
||||
"copy_to": "Kopiuj do…",
|
||||
"login_success": "Pomyślnie zalogowano"
|
||||
"login_success": "Pomyślnie zalogowano",
|
||||
"adaptive_email_colors": "Dopasuj kolory maili HTML do bieżącego motywu",
|
||||
"adaptive_email_colors_note": "Dostosowuje neutralne tła i tekst o niskim kontraście bez zmiany układu, obrazów i kolorów firmowych."
|
||||
}
|
||||
+30
-9
@@ -116,10 +116,10 @@
|
||||
</svg></button></div>
|
||||
<nav id="tag-list" class="folder-list tag-list" aria-label="Message labels"></nav>
|
||||
<div id="folder-quota" class="folder-quota" aria-live="polite">
|
||||
<div class="folder-quota-label"><span>IMAP storage</span><span id="folder-quota-text">Loading…</span></div>
|
||||
<div class="folder-quota-label"><span>Mailbox storage</span><span id="folder-quota-text">Loading…</span></div>
|
||||
<div class="folder-quota-track"><span id="folder-quota-bar"></span></div>
|
||||
</div>
|
||||
<div class="folder-pane-footer"><span id="mail-server-label">IMAP</span><span>RustBox __RUSTBOX_VERSION__</span>
|
||||
<div class="folder-pane-footer"><span id="mail-server-label">RustBox</span><span>__RUSTBOX_VERSION__</span>
|
||||
</div>
|
||||
<div id="folder-resizer" class="pane-resizer" role="separator" aria-orientation="vertical"
|
||||
aria-label="Resize folder pane"></div>
|
||||
@@ -180,9 +180,19 @@
|
||||
<button id="selection-done" class="button quiet" type="button">Done</button>
|
||||
</div>
|
||||
<div id="message-list" class="message-list" aria-live="polite"></div>
|
||||
<div id="message-pagination" class="message-pagination hidden" aria-label="Mailbox pages">
|
||||
<button id="page-prev" class="icon-button pagination-arrow" type="button" title="Previous page"
|
||||
aria-label="Previous page"><svg aria-hidden="true">
|
||||
<use href="__RUSTBOX_ASSET_PREFIX__/assets/icons.svg#icon-arrow-left"></use>
|
||||
</svg></button>
|
||||
<span id="page-status" class="message-page-status">Page 1 of 1</span>
|
||||
<button id="page-next" class="icon-button pagination-arrow" type="button" title="Next page"
|
||||
aria-label="Next page"><svg aria-hidden="true">
|
||||
<use href="__RUSTBOX_ASSET_PREFIX__/assets/icons.svg#icon-chevron-right"></use>
|
||||
</svg></button>
|
||||
</div>
|
||||
<div id="message-load-actions" class="message-load-actions hidden">
|
||||
<button id="load-more" class="button quiet load-more" type="button">Load more</button>
|
||||
<button id="load-all" class="button quiet load-more" type="button">Load all</button>
|
||||
</div>
|
||||
<div id="message-resizer" class="pane-resizer" role="separator" aria-orientation="vertical"
|
||||
aria-label="Resize message list"></div>
|
||||
@@ -311,11 +321,11 @@
|
||||
<form id="compose-form" class="dialog-card compose-card" method="dialog">
|
||||
<header class="dialog-header"><strong id="compose-title">New message</strong>
|
||||
<div class="compose-header-actions"><button type="button" id="compose-fields-toggle" class="button quiet small"
|
||||
aria-expanded="true" title="Hide addressing fields">Hide fields</button><button type="button" id="compose-recover" class="button quiet small"
|
||||
title="Recover a saved draft">Recover draft</button><button type="button" id="compose-fullscreen"
|
||||
class="icon-button compose-fullscreen-toggle" aria-label="Toggle full screen composer" aria-pressed="false"
|
||||
title="Full screen">⛶</button><button type="button" class="icon-button close-dialog"
|
||||
aria-label="Close"><svg>
|
||||
aria-expanded="true" title="Hide addressing fields">Hide fields</button><button type="button"
|
||||
id="compose-recover" class="button quiet small" title="Recover a saved draft">Recover draft</button><button
|
||||
type="button" id="compose-fullscreen" class="icon-button compose-fullscreen-toggle"
|
||||
aria-label="Toggle full screen composer" aria-pressed="false" title="Full screen">⛶</button><button
|
||||
type="button" class="icon-button close-dialog" aria-label="Close"><svg>
|
||||
<use href="__RUSTBOX_ASSET_PREFIX__/assets/icons.svg#icon-x"></use>
|
||||
</svg></button></div>
|
||||
</header>
|
||||
@@ -444,7 +454,8 @@
|
||||
<use href="__RUSTBOX_ASSET_PREFIX__/assets/icons.svg#icon-file-text"></use>
|
||||
</svg></button>
|
||||
</div>
|
||||
<div id="compose-image-actions" class="compose-image-actions hidden" role="toolbar" aria-label="Inline image actions">
|
||||
<div id="compose-image-actions" class="compose-image-actions hidden" role="toolbar"
|
||||
aria-label="Inline image actions">
|
||||
<span class="compose-image-actions-label">Inline image</span>
|
||||
<button type="button" class="button quiet small" data-image-width="25">25%</button>
|
||||
<button type="button" class="button quiet small" data-image-width="50">50%</button>
|
||||
@@ -740,6 +751,11 @@
|
||||
flags</span></label>
|
||||
<label class="checkbox-row"><input id="pref-reduce-motion" type="checkbox"><span
|
||||
data-i18n="reduce_motion">Reduce motion</span></label>
|
||||
<label class="checkbox-row appearance-toggle-described"><input id="pref-adaptive-email-colors"
|
||||
type="checkbox"><span class="appearance-toggle-copy"><span data-i18n="adaptive_email_colors">Adapt
|
||||
HTML mail colors to current theme</span><small class="muted"
|
||||
data-i18n="adaptive_email_colors_note">Adjusts neutral backgrounds and low-contrast text while
|
||||
preserving layout, images and brand colors.</small></span></label>
|
||||
</div>
|
||||
<details class="appearance-advanced">
|
||||
<summary>Advanced interface styling</summary>
|
||||
@@ -808,6 +824,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-grid">
|
||||
<label><span>Message loading</span><select id="mail-pref-loading-mode">
|
||||
<option value="pagination">Pagination</option>
|
||||
<option value="progressive">Smooth loading</option>
|
||||
<option value="all">Load all messages</option>
|
||||
</select></label>
|
||||
<label><span>Messages per page</span><select id="mail-pref-per-page">
|
||||
<option value="20">20</option>
|
||||
<option value="50">50</option>
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
page: 0,
|
||||
perPage: 300,
|
||||
total: 0,
|
||||
loadingMoreMessages: false,
|
||||
loadingAllMessages: false,
|
||||
messages: [],
|
||||
selected: null,
|
||||
selectedMessages: new Set(),
|
||||
@@ -45,7 +47,7 @@
|
||||
savedSearches: [],
|
||||
responseTemplates: [],
|
||||
notificationPrefs: { enabled: true, sound: false, folders: ["INBOX"], dnd_enabled: false, dnd_start: "22:00", dnd_end: "07:00" },
|
||||
mailPrefs: { messages_per_page: 300, mark_read_delay_ms: 0, remote_images: "ask", preferred_body: "auto", thread_mode: "threaded", collapse_quotes: true, show_snippets: true, default_sort: "date_desc", flagged_on_top: false, sync_imap_keywords: false, compact_wide_rows: true },
|
||||
mailPrefs: { messages_per_page: 300, message_loading_mode: "pagination", mark_read_delay_ms: 0, remote_images: "ask", preferred_body: "auto", thread_mode: "threaded", collapse_quotes: true, show_snippets: true, default_sort: "date_desc", flagged_on_top: false, sync_imap_keywords: false, compact_wide_rows: true },
|
||||
currentFolderFlaggedOnTop: false,
|
||||
markReadTimer: null,
|
||||
ws: null,
|
||||
@@ -98,7 +100,7 @@
|
||||
colorPreset: "sage", accentColor: "#6f9c86", backgroundColor: "#121513", panelColor: "#181c19", textColor: "#edf0ec", fontFamily: "system", fontSize: 13,
|
||||
rowHeight: 58, folderPaneWidth: 228, messagePaneWidth: 420, borderRadius: 4,
|
||||
contrast: "normal", showAvatars: true, showAttachmentIcons: true, showFlags: true, reduceMotion: false, dateFormat: "locale", timeFormat: "locale",
|
||||
iconSize: 18, toolbarDensity: "normal", sidebarItemHeight: 29, readerMaxWidth: 0, composerWidth: 900, selectionColor: "#28352e", unreadFontWeight: 730, unreadBadgeStyle: "dot", compactHeaders: false, showBorders: true, scrollbarDensity: "thin", monochrome: false, folderBackground: "", messageBackground: "", readerBackground: "",
|
||||
iconSize: 18, toolbarDensity: "normal", sidebarItemHeight: 29, readerMaxWidth: 0, composerWidth: 900, selectionColor: "#28352e", unreadFontWeight: 730, unreadBadgeStyle: "dot", compactHeaders: false, showBorders: true, scrollbarDensity: "thin", monochrome: false, folderBackground: "", messageBackground: "", readerBackground: "", adaptiveEmailColors: false,
|
||||
},
|
||||
translations: {},
|
||||
quotaWarningLevel: null,
|
||||
|
||||
@@ -13,7 +13,7 @@ const preferenceMap = {
|
||||
composeAutosaveMs: "compose_autosave_ms", defaultPriority: "default_priority", requestMdnDefault: "request_mdn_default", attachmentReminder: "attachment_reminder", composeFontFamily: "compose_font_family", replyPosition: "reply_position", quoteStyle: "quote_style", forwardMode: "forward_mode", spellcheckEnabled: "spellcheck_enabled",
|
||||
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",
|
||||
compactHeaders: "compact_headers", showBorders: "show_borders", scrollbarDensity: "scrollbar_density", monochrome: "monochrome", folderBackground: "folder_background", messageBackground: "message_background", readerBackground: "reader_background", adaptiveEmailColors: "adaptive_email_colors",
|
||||
};
|
||||
const preferenceTimers = new Map();
|
||||
let presetPromotionTimer = null;
|
||||
@@ -273,7 +273,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-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-preset-scale-unlocked": p.presetScaleUnlocked, "#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; });
|
||||
@@ -314,7 +314,7 @@ function normalizeServerPreferences(data) {
|
||||
rowHeight: data.row_height || 58, folderPaneWidth: data.folder_pane_width || 228, messagePaneWidth: data.message_pane_width || 420,
|
||||
borderRadius: data.border_radius ?? 4, contrast: data.contrast || "normal", showAvatars: data.show_avatars !== false,
|
||||
showAttachmentIcons: data.show_attachment_icons !== false, showFlags: data.show_flags !== false, reduceMotion: Boolean(data.reduce_motion), dateFormat: data.date_format || "locale", timeFormat: data.time_format || "locale",
|
||||
iconSize: Number(data.icon_size || 18), toolbarDensity: data.toolbar_density || "normal", sidebarItemHeight: Number(data.sidebar_item_height || 29), readerMaxWidth: Number(data.reader_max_width || 0), composerWidth: Number(data.composer_width || 900), selectionColor: data.selection_color || "#28352e", unreadFontWeight: Number(data.unread_font_weight || 730), unreadBadgeStyle: data.unread_badge_style || "dot", compactHeaders: Boolean(data.compact_headers), showBorders: data.show_borders !== false, scrollbarDensity: data.scrollbar_density || "thin", monochrome: Boolean(data.monochrome), folderBackground: data.folder_background || "", messageBackground: data.message_background || "", readerBackground: data.reader_background || "",
|
||||
iconSize: Number(data.icon_size || 18), toolbarDensity: data.toolbar_density || "normal", sidebarItemHeight: Number(data.sidebar_item_height || 29), readerMaxWidth: Number(data.reader_max_width || 0), composerWidth: Number(data.composer_width || 900), selectionColor: data.selection_color || "#28352e", unreadFontWeight: Number(data.unread_font_weight || 730), unreadBadgeStyle: data.unread_badge_style || "dot", compactHeaders: Boolean(data.compact_headers), showBorders: data.show_borders !== false, scrollbarDensity: data.scrollbar_density || "thin", monochrome: Boolean(data.monochrome), folderBackground: data.folder_background || "", messageBackground: data.message_background || "", readerBackground: data.reader_background || "", adaptiveEmailColors: Boolean(data.adaptive_email_colors),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -331,6 +331,7 @@ async function loadMailPreferences() {
|
||||
state.mailPrefs = { ...state.mailPrefs, ...prefs };
|
||||
state.perPage = Number(state.mailPrefs.messages_per_page || 300);
|
||||
const values = {
|
||||
"#mail-pref-loading-mode": state.mailPrefs.message_loading_mode || "pagination",
|
||||
"#mail-pref-per-page": String(state.perPage),
|
||||
"#mail-pref-read-delay": String(state.mailPrefs.mark_read_delay_ms || 0),
|
||||
"#mail-pref-remote-images": state.mailPrefs.remote_images || "ask",
|
||||
@@ -374,6 +375,7 @@ async function saveCompactWideRowsAppearance(enabled) {
|
||||
|
||||
async function saveMailPreferences() {
|
||||
const payload = {
|
||||
message_loading_mode: $("#mail-pref-loading-mode").value,
|
||||
messages_per_page: Number($("#mail-pref-per-page").value),
|
||||
mark_read_delay_ms: Number($("#mail-pref-read-delay").value),
|
||||
remote_images: $("#mail-pref-remote-images").value,
|
||||
@@ -564,7 +566,7 @@ function savePreference(key, value) {
|
||||
}
|
||||
|
||||
async function resetAppearance() {
|
||||
const payload = { theme: root.dataset.defaultTheme || "system", interface_skin: "default", 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: "" };
|
||||
const payload = { theme: root.dataset.defaultTheme || "system", interface_skin: "default", 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 }) });
|
||||
|
||||
@@ -1,11 +1,45 @@
|
||||
function mailboxLoadingMode() {
|
||||
const mode = state.mailPrefs.message_loading_mode || "pagination";
|
||||
return ["pagination", "progressive", "all"].includes(mode) ? mode : "pagination";
|
||||
}
|
||||
|
||||
function mailboxPageCount() {
|
||||
return Math.max(1, Math.ceil(Number(state.total || 0) / Math.max(20, Number(state.perPage || 300))));
|
||||
}
|
||||
|
||||
function updateMessageLoadControls() {
|
||||
const pagination = $("#message-pagination");
|
||||
const loadActions = $("#message-load-actions");
|
||||
const mode = mailboxLoadingMode();
|
||||
const searchable = Boolean(state.searchContext);
|
||||
const canLoadMore = !searchable && state.messages.length < state.total;
|
||||
|
||||
if (pagination) {
|
||||
const pages = mailboxPageCount();
|
||||
const showPagination = !searchable && mode === "pagination" && pages > 1;
|
||||
pagination.classList.toggle("hidden", !showPagination);
|
||||
const previous = $("#page-prev");
|
||||
const next = $("#page-next");
|
||||
const status = $("#page-status");
|
||||
if (previous) previous.disabled = state.page <= 0;
|
||||
if (next) next.disabled = state.page >= pages - 1;
|
||||
if (status) status.textContent = `${tr("page", "Page")} ${Math.min(state.page + 1, pages)} ${tr("of", "of")} ${pages}`;
|
||||
}
|
||||
|
||||
if (loadActions) {
|
||||
loadActions.classList.toggle("hidden", mode !== "progressive" || !canLoadMore);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMessages(reset = false, options = {}) {
|
||||
const list = $("#message-list");
|
||||
const mode = mailboxLoadingMode();
|
||||
const restoreState = Boolean(options.restoreState);
|
||||
const savedState = restoreState ? readMessageListState(state.folder) : null;
|
||||
const previousScrollTop = reset && !restoreState && list ? list.scrollTop : null;
|
||||
const pageBeforeReset = state.page;
|
||||
if (reset) {
|
||||
state.page = 0;
|
||||
state.page = options.background && mode === "pagination" ? pageBeforeReset : 0;
|
||||
if (!options.background) {
|
||||
state.messages = [];
|
||||
state.selectedMessages.clear();
|
||||
@@ -17,15 +51,20 @@ async function loadMessages(reset = false, options = {}) {
|
||||
try {
|
||||
const sort = encodeURIComponent(state.mailPrefs.default_sort || "date_desc");
|
||||
const maxRestorePage = Math.max(0, Math.ceil(500 / Math.max(20, state.perPage)) - 1);
|
||||
const targetPage = reset && savedState ? Math.min(savedState.page, maxRestorePage) : state.page;
|
||||
const pages = reset && targetPage > 0 ? Array.from({ length: targetPage + 1 }, (_, index) => index) : [state.page];
|
||||
const savedPage = reset && savedState
|
||||
? (mode === "pagination" ? savedState.page : Math.min(savedState.page, maxRestorePage))
|
||||
: state.page;
|
||||
const targetPage = reset && mode === "all" ? 0 : savedPage;
|
||||
const pages = reset && mode === "progressive" && targetPage > 0
|
||||
? Array.from({ length: targetPage + 1 }, (_, index) => index)
|
||||
: [targetPage];
|
||||
let loaded = [];
|
||||
let totalCount = 0;
|
||||
let flaggedOnTop = state.currentFolderFlaggedOnTop;
|
||||
let folderTotalCount = null;
|
||||
let folderUnreadCount = null;
|
||||
let syncing = false;
|
||||
let lastLoadedPage = state.page;
|
||||
let lastLoadedPage = targetPage;
|
||||
for (const page of pages) {
|
||||
const forceRefresh = options.force ? "&refresh=true" : "";
|
||||
const data = await request(`/folders/${encodeURIComponent(state.folder)}/messages?page=${page}&per_page=${state.perPage}&sort=${sort}${forceRefresh}`, { method: "GET" });
|
||||
@@ -38,10 +77,10 @@ async function loadMessages(reset = false, options = {}) {
|
||||
lastLoadedPage = page;
|
||||
if (loaded.length >= totalCount) break;
|
||||
}
|
||||
state.page = reset ? lastLoadedPage : state.page;
|
||||
state.page = lastLoadedPage;
|
||||
state.total = totalCount;
|
||||
state.currentFolderFlaggedOnTop = flaggedOnTop;
|
||||
state.messages = reset ? loaded : [...state.messages, ...loaded];
|
||||
state.messages = reset || mode === "pagination" ? loaded : [...state.messages, ...loaded];
|
||||
if (state.folders.some((item) => item.name === state.folder)) {
|
||||
setFolderCounts(state.folder, folderTotalCount, folderUnreadCount);
|
||||
}
|
||||
@@ -54,39 +93,87 @@ async function loadMessages(reset = false, options = {}) {
|
||||
$("#connection-label").textContent = "Connected";
|
||||
renderMessages();
|
||||
const desiredScroll = savedState?.scrollTop ?? previousScrollTop;
|
||||
if (Number.isFinite(desiredScroll) && desiredScroll > 0) {
|
||||
if (mode === "pagination") {
|
||||
requestAnimationFrame(() => { list.scrollTop = 0; });
|
||||
} else if (Number.isFinite(desiredScroll) && desiredScroll > 0) {
|
||||
requestAnimationFrame(() => { list.scrollTop = Math.min(desiredScroll, Math.max(0, list.scrollHeight - list.clientHeight)); });
|
||||
}
|
||||
saveMessageListState();
|
||||
if (reset && mode === "all" && !state.searchContext && state.messages.length < state.total && !options.skipAutoAll) {
|
||||
await loadAllMessages();
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
$("#connection-label").textContent = error.network ? (navigator.onLine ? "Reconnecting" : "Offline") : "Connection error";
|
||||
if (!state.messages.length) list.innerHTML = `<div class="empty-list">${escapeHtml(error.message)}</div>`;
|
||||
$("#message-count").textContent = state.messages.length ? `${state.total} messages · cached view` : "Unavailable";
|
||||
updateMessageLoadControls();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function changeMailboxPage(delta) {
|
||||
if (state.searchContext || mailboxLoadingMode() !== "pagination") return false;
|
||||
const pages = mailboxPageCount();
|
||||
const target = Math.max(0, Math.min(pages - 1, state.page + delta));
|
||||
if (target === state.page) return false;
|
||||
const previousPage = state.page;
|
||||
state.page = target;
|
||||
const loaded = await loadMessages(false, { background: true });
|
||||
if (!loaded) {
|
||||
state.page = previousPage;
|
||||
updateMessageLoadControls();
|
||||
return false;
|
||||
}
|
||||
const list = $("#message-list");
|
||||
if (list) list.scrollTop = 0;
|
||||
saveMessageListState();
|
||||
return true;
|
||||
}
|
||||
|
||||
async function loadNextProgressivePage() {
|
||||
if (state.searchContext || mailboxLoadingMode() !== "progressive" || state.loadingMoreMessages || state.messages.length >= state.total) return;
|
||||
state.loadingMoreMessages = true;
|
||||
const before = state.messages.length;
|
||||
const previousPage = state.page;
|
||||
state.page += 1;
|
||||
try {
|
||||
await loadMessages(false, { background: true });
|
||||
if (state.messages.length <= before) state.page = previousPage;
|
||||
} finally {
|
||||
state.loadingMoreMessages = false;
|
||||
updateMessageLoadControls();
|
||||
}
|
||||
}
|
||||
|
||||
function handleMessageListScroll() {
|
||||
scheduleMessageListStateSave();
|
||||
if (mailboxLoadingMode() !== "progressive" || state.loadingMoreMessages || state.searchContext) return;
|
||||
const list = $("#message-list");
|
||||
if (!list || state.messages.length >= state.total) return;
|
||||
if (list.scrollTop + list.clientHeight >= list.scrollHeight - 240) loadNextProgressivePage();
|
||||
}
|
||||
|
||||
async function loadAllMessages() {
|
||||
if (state.searchContext || state.messages.length >= state.total) return;
|
||||
const loadAll = $("#load-all");
|
||||
const loadMore = $("#load-more");
|
||||
setBusy(loadAll, true);
|
||||
if (loadMore) loadMore.disabled = true;
|
||||
if (state.searchContext || mailboxLoadingMode() !== "all" || state.messages.length >= state.total || state.loadingAllMessages) return;
|
||||
state.loadingAllMessages = true;
|
||||
try {
|
||||
let guard = 0;
|
||||
while (state.messages.length < state.total && guard < 10000) {
|
||||
const before = state.messages.length;
|
||||
const previousPage = state.page;
|
||||
state.page += 1;
|
||||
await loadMessages(false, { background: true });
|
||||
await loadMessages(false, { background: true, skipAutoAll: true });
|
||||
guard += 1;
|
||||
if (state.messages.length <= before) {
|
||||
state.page = Math.max(0, state.page - 1);
|
||||
state.page = previousPage;
|
||||
break;
|
||||
}
|
||||
}
|
||||
saveMessageListState();
|
||||
} finally {
|
||||
setBusy(loadAll, false);
|
||||
if (loadMore) loadMore.disabled = false;
|
||||
state.loadingAllMessages = false;
|
||||
updateMessageLoadControls();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,24 +388,30 @@ function renderMessages() {
|
||||
row.addEventListener("contextmenu", (event) => { event.preventDefault(); openMessageContextMenu(event.clientX, event.clientY, message); });
|
||||
list.append(row);
|
||||
}
|
||||
const canLoadMore = !state.searchContext && state.messages.length < state.total;
|
||||
$("#message-load-actions")?.classList.toggle("hidden", !canLoadMore);
|
||||
updateMessageLoadControls();
|
||||
renderBulkToolbar();
|
||||
}
|
||||
|
||||
function navigateMessage(delta) {
|
||||
async function navigateMessage(delta) {
|
||||
if (!state.selected) return;
|
||||
const index = state.messages.findIndex((message) => message.uid === state.selected.uid && (message.folder || state.folder) === state.selected.folder);
|
||||
if (index < 0) return;
|
||||
const next = state.messages[index + delta];
|
||||
if (!next) return toast(delta > 0 ? "No next loaded message" : "No previous loaded message");
|
||||
openMessage(next.folder || state.folder, next.uid);
|
||||
if (next) return openMessage(next.folder || state.folder, next.uid);
|
||||
if (!state.searchContext && mailboxLoadingMode() === "pagination" && await changeMailboxPage(delta > 0 ? 1 : -1)) {
|
||||
const boundary = delta > 0 ? state.messages[0] : state.messages[state.messages.length - 1];
|
||||
if (boundary) return openMessage(boundary.folder || state.folder, boundary.uid);
|
||||
}
|
||||
toast(delta > 0 ? "No next message" : "No previous message");
|
||||
}
|
||||
|
||||
function updateMessageNavigation() {
|
||||
const index = state.selected ? state.messages.findIndex((message) => message.uid === state.selected.uid && (message.folder || state.folder) === state.selected.folder) : -1;
|
||||
$("#reader-prev").disabled = index <= 0;
|
||||
$("#reader-next").disabled = index < 0 || index >= state.messages.length - 1;
|
||||
const paginated = !state.searchContext && mailboxLoadingMode() === "pagination";
|
||||
const hasPreviousPage = paginated && state.page > 0;
|
||||
const hasNextPage = paginated && state.page < mailboxPageCount() - 1;
|
||||
$("#reader-prev").disabled = index < 0 || (index <= 0 && !hasPreviousPage);
|
||||
$("#reader-next").disabled = index < 0 || (index >= state.messages.length - 1 && !hasNextPage);
|
||||
}
|
||||
|
||||
async function senderIsSavedContact(email) {
|
||||
@@ -730,6 +823,162 @@ async function discoverCurrentPgpSigner() {
|
||||
if (key) await processCurrentPgpMessage();
|
||||
}
|
||||
|
||||
function parseAdaptiveEmailColor(value) {
|
||||
const raw = String(value || "").trim().toLowerCase();
|
||||
if (!raw || ["transparent", "inherit", "initial", "unset", "currentcolor"].includes(raw)) return null;
|
||||
const named = {
|
||||
black: [0, 0, 0], white: [255, 255, 255], gray: [128, 128, 128], grey: [128, 128, 128],
|
||||
silver: [192, 192, 192], navy: [0, 0, 128], red: [255, 0, 0], green: [0, 128, 0],
|
||||
blue: [0, 0, 255], yellow: [255, 255, 0], maroon: [128, 0, 0], purple: [128, 0, 128],
|
||||
teal: [0, 128, 128], olive: [128, 128, 0], aqua: [0, 255, 255], fuchsia: [255, 0, 255],
|
||||
lime: [0, 255, 0], whitesmoke: [245, 245, 245], gainsboro: [220, 220, 220],
|
||||
};
|
||||
if (named[raw]) return { r: named[raw][0], g: named[raw][1], b: named[raw][2], a: 1 };
|
||||
let match = raw.match(/^#([0-9a-f]{3,8})$/i);
|
||||
if (match) {
|
||||
let hex = match[1];
|
||||
if (hex.length === 3 || hex.length === 4) hex = [...hex].map((part) => part + part).join("");
|
||||
if (hex.length === 6 || hex.length === 8) {
|
||||
return {
|
||||
r: parseInt(hex.slice(0, 2), 16), g: parseInt(hex.slice(2, 4), 16), b: parseInt(hex.slice(4, 6), 16),
|
||||
a: hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
match = raw.match(/^rgba?\(\s*([0-9.]+)\s*[, ]\s*([0-9.]+)\s*[, ]\s*([0-9.]+)(?:\s*[,/]\s*([0-9.]+%?))?\s*\)$/i);
|
||||
if (!match) return null;
|
||||
const alpha = match[4]?.endsWith("%") ? Number(match[4].slice(0, -1)) / 100 : Number(match[4] ?? 1);
|
||||
const clamp = (value) => Math.max(0, Math.min(255, Math.round(Number(value))));
|
||||
return { r: clamp(match[1]), g: clamp(match[2]), b: clamp(match[3]), a: Math.max(0, Math.min(1, alpha)) };
|
||||
}
|
||||
|
||||
function adaptiveEmailRgbCss(color) {
|
||||
return `rgb(${Math.round(color.r)} ${Math.round(color.g)} ${Math.round(color.b)})`;
|
||||
}
|
||||
|
||||
function adaptiveEmailMix(a, b, amount) {
|
||||
const t = Math.max(0, Math.min(1, Number(amount) || 0));
|
||||
return { r: a.r + (b.r - a.r) * t, g: a.g + (b.g - a.g) * t, b: a.b + (b.b - a.b) * t, a: 1 };
|
||||
}
|
||||
|
||||
function adaptiveEmailComposite(foreground, background) {
|
||||
if (!foreground || foreground.a >= 0.999) return foreground;
|
||||
const alpha = Math.max(0, Math.min(1, foreground.a));
|
||||
return {
|
||||
r: foreground.r * alpha + background.r * (1 - alpha),
|
||||
g: foreground.g * alpha + background.g * (1 - alpha),
|
||||
b: foreground.b * alpha + background.b * (1 - alpha),
|
||||
a: 1,
|
||||
};
|
||||
}
|
||||
|
||||
function adaptiveEmailBrightness(color) {
|
||||
return (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b) / 255;
|
||||
}
|
||||
|
||||
function adaptiveEmailSaturation(color) {
|
||||
const max = Math.max(color.r, color.g, color.b);
|
||||
const min = Math.min(color.r, color.g, color.b);
|
||||
return max <= 0 ? 0 : (max - min) / max;
|
||||
}
|
||||
|
||||
function adaptiveEmailLuminance(color) {
|
||||
const channel = (value) => {
|
||||
const normalized = value / 255;
|
||||
return normalized <= 0.04045 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4;
|
||||
};
|
||||
return 0.2126 * channel(color.r) + 0.7152 * channel(color.g) + 0.0722 * channel(color.b);
|
||||
}
|
||||
|
||||
function adaptiveEmailContrast(a, b) {
|
||||
const one = adaptiveEmailLuminance(a);
|
||||
const two = adaptiveEmailLuminance(b);
|
||||
return (Math.max(one, two) + 0.05) / (Math.min(one, two) + 0.05);
|
||||
}
|
||||
|
||||
function adaptiveEmailReadableColor(color, background, preferred) {
|
||||
if (!color || !background || adaptiveEmailContrast(color, background) >= 3.5) return color;
|
||||
for (let step = 0.18; step <= 1.001; step += 0.14) {
|
||||
const candidate = adaptiveEmailMix(color, preferred, step);
|
||||
if (adaptiveEmailContrast(candidate, background) >= 4.5) return candidate;
|
||||
}
|
||||
const black = { r: 0, g: 0, b: 0, a: 1 };
|
||||
const white = { r: 255, g: 255, b: 255, a: 1 };
|
||||
return adaptiveEmailContrast(white, background) >= adaptiveEmailContrast(black, background) ? white : black;
|
||||
}
|
||||
|
||||
function adaptMessagePreviewColors(html, detail, body) {
|
||||
const targetTheme = resolveTheme(state.prefs.theme);
|
||||
const targetDark = targetTheme === "dark";
|
||||
const bodyStyles = getComputedStyle(body);
|
||||
const surface = parseAdaptiveEmailColor(bodyStyles.backgroundColor) || (targetDark ? { r: 24, g: 28, b: 25, a: 1 } : { r: 255, g: 255, b: 255, a: 1 });
|
||||
const text = parseAdaptiveEmailColor(bodyStyles.color) || (targetDark ? { r: 237, g: 240, b: 236, a: 1 } : { r: 32, g: 38, b: 34, a: 1 });
|
||||
const mailTheme = String(detail?.email_theme || "").toLowerCase();
|
||||
const themeAlreadyMatches = mailTheme === targetTheme;
|
||||
const doc = new DOMParser().parseFromString(`<body>${html}</body>`, "text/html");
|
||||
|
||||
const remapBackground = (color) => {
|
||||
if (!color || color.a < 0.45) return null;
|
||||
const brightness = adaptiveEmailBrightness(color);
|
||||
const saturation = adaptiveEmailSaturation(color);
|
||||
if (saturation > 0.38) return null;
|
||||
if (targetDark && brightness >= 0.62) {
|
||||
return adaptiveEmailMix(surface, text, Math.max(0, Math.min(0.12, (1 - brightness) * 0.26)));
|
||||
}
|
||||
if (!targetDark && brightness <= 0.38) {
|
||||
return adaptiveEmailMix(surface, text, Math.max(0, Math.min(0.10, brightness * 0.24)));
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const remapNeutralText = (color) => {
|
||||
if (!color || color.a < 0.45) return null;
|
||||
const brightness = adaptiveEmailBrightness(color);
|
||||
const saturation = adaptiveEmailSaturation(color);
|
||||
if (saturation > 0.32) return null;
|
||||
if (targetDark && brightness <= 0.45) return adaptiveEmailMix(text, surface, brightness * 0.28);
|
||||
if (!targetDark && brightness >= 0.62) return adaptiveEmailMix(text, surface, (1 - brightness) * 0.28);
|
||||
return null;
|
||||
};
|
||||
|
||||
const walk = (node, inheritedBackground) => {
|
||||
let effectiveBackground = inheritedBackground;
|
||||
const style = node.style;
|
||||
const backgroundAttribute = node.getAttribute("bgcolor");
|
||||
const sourceBackground = parseAdaptiveEmailColor(style?.backgroundColor || backgroundAttribute);
|
||||
if (sourceBackground) {
|
||||
const composited = adaptiveEmailComposite(sourceBackground, inheritedBackground);
|
||||
const mappedBackground = themeAlreadyMatches ? null : remapBackground(composited);
|
||||
if (mappedBackground) {
|
||||
if (style) style.backgroundColor = adaptiveEmailRgbCss(mappedBackground);
|
||||
else node.setAttribute("bgcolor", adaptiveEmailRgbCss(mappedBackground));
|
||||
if (backgroundAttribute) node.setAttribute("bgcolor", adaptiveEmailRgbCss(mappedBackground));
|
||||
effectiveBackground = mappedBackground;
|
||||
} else {
|
||||
effectiveBackground = composited;
|
||||
}
|
||||
}
|
||||
|
||||
const colorAttribute = node.getAttribute("color");
|
||||
const sourceColor = parseAdaptiveEmailColor(style?.color || colorAttribute);
|
||||
if (sourceColor) {
|
||||
const composited = adaptiveEmailComposite(sourceColor, effectiveBackground);
|
||||
const neutralMapped = themeAlreadyMatches ? null : remapNeutralText(composited);
|
||||
const readable = adaptiveEmailReadableColor(neutralMapped || composited, effectiveBackground, text);
|
||||
if (neutralMapped || readable !== composited) {
|
||||
const replacement = adaptiveEmailRgbCss(readable);
|
||||
if (style) style.color = replacement;
|
||||
if (colorAttribute) node.setAttribute("color", replacement);
|
||||
}
|
||||
}
|
||||
|
||||
for (const child of node.children) walk(child, effectiveBackground);
|
||||
};
|
||||
|
||||
for (const child of doc.body.children) walk(child, surface);
|
||||
return { html: doc.body.innerHTML, targetTheme, surface: adaptiveEmailRgbCss(surface), text: adaptiveEmailRgbCss(text) };
|
||||
}
|
||||
|
||||
function renderMessageBody(detail, forceRemote = false) {
|
||||
const body = $("#message-body");
|
||||
const remoteBar = $("#remote-image-bar");
|
||||
@@ -751,9 +1000,18 @@ function renderMessageBody(detail, forceRemote = false) {
|
||||
iframe.setAttribute("referrerpolicy", "no-referrer");
|
||||
const imageSources = allowRemote ? "data: cid: https: http:" : "data: cid:";
|
||||
const csp = `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${imageSources}; style-src 'unsafe-inline'; font-src data:">`;
|
||||
const quoteStyle = state.mailPrefs.collapse_quotes !== false ? "blockquote{max-height:16em;overflow:auto;border-left:2px solid #d9ddda;padding-left:12px;margin-left:0}" : "blockquote{border-left:2px solid #d9ddda;padding-left:12px;margin-left:0}";
|
||||
const safeHtml = sanitizeMessagePreviewHtml(detail.html);
|
||||
iframe.srcdoc = `<!doctype html><html><head><meta charset="utf-8">${csp}<meta name="color-scheme" content="light"><style>body{margin:0;padding:22px;color:#202622;background:#fff;font:14px/1.6 Inter,system-ui,-apple-system,'Segoe UI',sans-serif}img{max-width:100%;height:auto}a{color:#356a56}pre{white-space:pre-wrap}${quoteStyle}</style></head><body>${safeHtml}</body></html>`;
|
||||
let safeHtml = sanitizeMessagePreviewHtml(detail.html);
|
||||
let previewTheme = { targetTheme: "light", surface: "#fff", text: "#202622" };
|
||||
if (state.prefs.adaptiveEmailColors) {
|
||||
previewTheme = adaptMessagePreviewColors(safeHtml, detail, body);
|
||||
safeHtml = previewTheme.html;
|
||||
}
|
||||
const quoteColor = state.prefs.adaptiveEmailColors ? `color-mix(in srgb, ${previewTheme.text} 25%, ${previewTheme.surface})` : "#d9ddda";
|
||||
const quoteStyle = state.mailPrefs.collapse_quotes !== false ? `blockquote{max-height:16em;overflow:auto;border-left:2px solid ${quoteColor};padding-left:12px;margin-left:0}` : `blockquote{border-left:2px solid ${quoteColor};padding-left:12px;margin-left:0}`;
|
||||
const previewStyle = state.prefs.adaptiveEmailColors
|
||||
? `body{margin:0;padding:22px;color:${previewTheme.text};background:${previewTheme.surface};font:14px/1.6 Inter,system-ui,-apple-system,'Segoe UI',sans-serif}img{max-width:100%;height:auto}a{color:inherit;text-decoration:underline}pre{white-space:pre-wrap}${quoteStyle}`
|
||||
: `body{margin:0;padding:22px;color:#202622;background:#fff;font:14px/1.6 Inter,system-ui,-apple-system,'Segoe UI',sans-serif}img{max-width:100%;height:auto}a{color:#356a56}pre{white-space:pre-wrap}${quoteStyle}`;
|
||||
iframe.srcdoc = `<!doctype html><html><head><meta charset="utf-8">${csp}<meta name="color-scheme" content="${previewTheme.targetTheme}"><style>${previewStyle}</style></head><body>${safeHtml}</body></html>`;
|
||||
body.append(iframe);
|
||||
}
|
||||
|
||||
|
||||
@@ -324,9 +324,10 @@ function bindEvents() {
|
||||
toast("Message sort updated", "success");
|
||||
} catch (error) { toast(error.message, "error"); }
|
||||
});
|
||||
$("#load-more").addEventListener("click", async () => { state.page += 1; await loadMessages(false); });
|
||||
$("#load-all").addEventListener("click", loadAllMessages);
|
||||
$("#message-list").addEventListener("scroll", scheduleMessageListStateSave, { passive: true });
|
||||
$("#page-prev").addEventListener("click", () => changeMailboxPage(-1));
|
||||
$("#page-next").addEventListener("click", () => changeMailboxPage(1));
|
||||
$("#load-more").addEventListener("click", loadNextProgressivePage);
|
||||
$("#message-list").addEventListener("scroll", handleMessageListScroll, { passive: true });
|
||||
window.addEventListener("offline", () => setConnectivityState(false));
|
||||
window.addEventListener("online", async () => {
|
||||
setConnectivityState(true);
|
||||
@@ -404,7 +405,7 @@ function bindEvents() {
|
||||
$("#pgp-import-file-input").addEventListener("change", async (event) => { const file = event.target.files?.[0]; if (file) await importPgpFile(file); event.target.value = ""; });
|
||||
$("#pgp-discover").addEventListener("click", () => discoverPgpEmail());
|
||||
$$(".settings-tab").forEach((button) => button.addEventListener("click", () => { selectSettingsTab(button.dataset.tab); if (button.dataset.tab === "sieve") loadSieve(); if (button.dataset.tab === "identities") { loadIdentities(); if (state.authPolicy.pgpEnabled !== false) loadOpenPgp(); } if (button.dataset.tab === "openpgp" && state.authPolicy.pgpEnabled !== false) loadOpenPgp(); if (button.dataset.tab === "notifications") loadNotificationPreferences(); if (button.dataset.tab === "mailbox") loadMailPreferences(); if (button.dataset.tab === "composing") loadResponseTemplates(); if (button.dataset.tab === "diagnostics") loadDiagnostics(); if (button.dataset.tab === "account") { renderAccountSettings(); loadActiveSessions(); } if (button.dataset.tab === "folders") { loadSpecialFolders(); loadQuota(); } }));
|
||||
$("#pref-theme").addEventListener("change", (event) => savePreference("theme", event.target.value));
|
||||
$("#pref-theme").addEventListener("change", (event) => { savePreference("theme", event.target.value); if (state.currentDetail && state.prefs.adaptiveEmailColors) renderMessageDetail(state.currentDetail); });
|
||||
$("#pref-interface-skin").addEventListener("change", (event) => savePreference("interfaceSkin", event.target.value));
|
||||
$("#pref-preset-scale-unlocked").addEventListener("change", (event) => savePreference("presetScaleUnlocked", event.target.checked));
|
||||
$("#pref-density").addEventListener("change", (event) => savePreference("density", event.target.value));
|
||||
@@ -423,6 +424,7 @@ function bindEvents() {
|
||||
$("#pref-attachment-icons").addEventListener("change", (event) => savePreference("showAttachmentIcons", event.target.checked));
|
||||
$("#pref-flags").addEventListener("change", (event) => savePreference("showFlags", event.target.checked));
|
||||
$("#pref-reduce-motion").addEventListener("change", (event) => savePreference("reduceMotion", event.target.checked));
|
||||
$("#pref-adaptive-email-colors").addEventListener("change", (event) => { savePreference("adaptiveEmailColors", event.target.checked); if (state.currentDetail) renderMessageDetail(state.currentDetail); });
|
||||
[["#pref-icon-size", "iconSize"], ["#pref-sidebar-height", "sidebarItemHeight"], ["#pref-composer-width", "composerWidth"], ["#pref-unread-weight", "unreadFontWeight"]].forEach(([selector, key]) => $(selector).addEventListener("input", (event) => savePreference(key, Number(event.target.value))));
|
||||
$("#pref-reader-width").addEventListener("change", (event) => savePreference("readerMaxWidth", Number(event.target.value)));
|
||||
$("#pref-toolbar-density").addEventListener("change", (event) => savePreference("toolbarDensity", event.target.value));
|
||||
@@ -639,7 +641,7 @@ function bindEvents() {
|
||||
});
|
||||
matchMedia("(prefers-color-scheme: light)").addEventListener("change", () => {
|
||||
if (!$("#login-view").classList.contains("hidden")) showLogin();
|
||||
else if (state.prefs.theme === "system") applyPreferences();
|
||||
else if (state.prefs.theme === "system") { applyPreferences(); if (state.currentDetail && state.prefs.adaptiveEmailColors) renderMessageDetail(state.currentDetail); }
|
||||
});
|
||||
window.addEventListener("resize", () => { if (innerWidth > 820) setFolderDrawerOpen(false); });
|
||||
window.addEventListener("popstate", async (event) => {
|
||||
|
||||
Reference in new issue
Block a user