/* RustBox UI module: 00-bootstrap.js */ (() => { "use strict"; const root = document.documentElement; const configuredBase = root.dataset.basePath || "/"; const BASE = configuredBase === "/" ? "" : configuredBase.replace(/\/$/, ""); const API = `${BASE}/api`; const ICONS = `${BASE}/assets/icons.svg`; const state = { accounts: [], accountId: localStorage.getItem("rustbox.activeAccount") || null, folders: [], folder: "INBOX", collapsedFolders: new Set(), page: 0, perPage: 50, total: 0, messages: [], selected: null, selectedMessages: new Set(), selectionMode: false, searchContext: null, searchAllResults: null, currentDetail: null, specialFolders: { sent: null, drafts: null, trash: null, junk: null, archive: null }, tags: [], sieveScripts: [], sieveInfo: null, sieveEditingRuleId: null, identities: [], pgpKeys: [], pgpPrefs: { wkdEnabled: true, hkpEnabled: false, requireTrustedEncryption: false }, pgpRecipientStatus: [], authPolicy: { rememberMeEnabled: true, rememberMeDays: 30, sessionTimeoutHours: 24 }, contacts: [], trustedSenders: new Set(), contactGroups: [], contactPhotoUrl: null, calendarCursor: new Date(), calendarView: localStorage.getItem("rustbox.calendarView") || "month", threadExpanded: localStorage.getItem("rustbox.threadExpanded") !== "false", calendarEvents: [], savedSearches: [], responseTemplates: [], notificationPrefs: { enabled: true, sound: false, folders: ["INBOX"], dnd_enabled: false, dnd_start: "22:00", dnd_end: "07:00" }, mailPrefs: { messages_per_page: 50, 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 }, currentFolderFlaggedOnTop: false, markReadTimer: null, ws: null, wsRetryTimer: null, realtimeRefreshTimer: null, calendarReminderTimer: null, composeDraftId: null, composeAttachments: [], composeDirty: false, composeNeedsRemoteSync: false, composeUploading: false, composeReplyMeta: null, composeInsertRange: null, composeSelectedImage: null, composeSignatureGeneration: 0, identitySignatureRange: null, inlineAttachmentIds: new Set(), inlineImageCleanupTimer: null, imageEditorBitmap: null, imageEditorBlob: null, draftSaveTimer: null, prefs: { theme: root.dataset.defaultTheme || "system", interfaceSkin: "default", presetScaleUnlocked: false, density: "compact", preview: "right", scale: "normal", language: "en", composeFormat: "html", composeAutosaveMs: 3000, defaultPriority: "normal", requestMdnDefault: false, attachmentReminder: true, composeFontFamily: "system", replyPosition: "above", quoteStyle: "standard", forwardMode: "inline", spellcheckEnabled: true, 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: "", }, translations: {}, quotaWarningLevel: null, networkOnline: navigator.onLine, listStateTimer: null, }; const $ = (selector, parent = document) => parent.querySelector(selector); const $$ = (selector, parent = document) => [...parent.querySelectorAll(selector)]; const el = (tag, className, text) => { const node = document.createElement(tag); if (className) node.className = className; if (text !== undefined) node.textContent = text; return node; }; function svgIcon(name) { return ``; } function escapeHtml(value) { return String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function stripRustBoxUiLeak(value) { let text = String(value ?? ""); // Strip only RustBox's own embedded SVG sprite fragments if malformed MIME // content captured adjacent UI markup. Legitimate SVG/code in mail remains. text = text.replace(/]*>\s*]*(?:\/assets\/icons\.svg|assets\/icons\.svg)[^>]*><\/use>\s*<\/svg>/gi, ""); text = text.replace(/<svg\b[\s\S]*?(?:\/assets\/icons\.svg|assets\/icons\.svg)[\s\S]*?<\/svg>/gi, ""); return text; } function cleanPlainMessageText(value) { return stripRustBoxUiLeak(value); } function sanitizeMessageHtml(html) { const doc = new DOMParser().parseFromString(stripRustBoxUiLeak(html), "text/html"); doc.querySelectorAll("script,iframe,frame,frameset,object,embed,applet,form,input,button,select,textarea,base,meta,link,svg,math").forEach((node) => node.remove()); doc.querySelectorAll("*").forEach((node) => { for (const attr of [...node.attributes]) { const name = attr.name.toLowerCase(); const value = attr.value.trim(); if (name.startsWith("on") || ["srcdoc", "formaction"].includes(name)) node.removeAttribute(attr.name); if (["href", "src", "background", "action", "poster"].includes(name) && /^\s*javascript:/i.test(value)) node.removeAttribute(attr.name); } }); return doc.body.innerHTML; } function sanitizeAttachmentHtml(html) { const doc = new DOMParser().parseFromString(sanitizeMessageHtml(html), "text/html"); doc.querySelectorAll("a[href]").forEach((node) => node.removeAttribute("href")); doc.querySelectorAll("[src],[background],[poster]").forEach((node) => { for (const name of ["src", "background", "poster"]) { if (!node.hasAttribute(name)) continue; const value = String(node.getAttribute(name) || "").trim(); if (!/^data:/i.test(value)) node.removeAttribute(name); } }); return doc.body.innerHTML; } function apiPath(path) { return `${API}${path.startsWith("/") ? path : `/${path}`}`; } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }