1 parent
d55640d37a
commit
fff8521a17
67 files changed
+11016
-16763
No files matched your search
@@ -0,0 +1,153 @@
|
||||
/* 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: "system", interfaceSkin: "rustbox", 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 `<svg aria-hidden="true"><use href="${ICONS}#icon-${name}"></use></svg>`;
|
||||
}
|
||||
|
||||
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(/<svg\b[^>]*>\s*<use\b[^>]*(?:\/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));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user