1 parent
96a32ea8fb
commit
a643e7e3ab
106 files changed
+2223
-4669
No files matched your search
+36
-25
@@ -1,4 +1,4 @@
|
||||
/* RustBox UI module: 50-compose.js */
|
||||
|
||||
function parseRecipients(raw) {
|
||||
return raw.split(",").map((value) => value.trim()).filter(Boolean);
|
||||
}
|
||||
@@ -98,7 +98,7 @@ function estimatedMimeAttachmentBytes(rawBytes) {
|
||||
|
||||
function estimateComposeMessageBytes(extraFiles = []) {
|
||||
const editor = $("#compose-editor");
|
||||
let bytes = 4096; // envelope headers, MIME boundaries and normal metadata
|
||||
let bytes = 4096;
|
||||
for (const selector of ["#compose-to", "#compose-cc", "#compose-bcc", "#compose-subject"]) bytes += utf8Bytes($(selector)?.value);
|
||||
if (editor) {
|
||||
const plainBytes = utf8Bytes(editor.innerText);
|
||||
@@ -404,27 +404,37 @@ function setComposeUploadProgress(visible, percent = 0, label = trText("Uploadin
|
||||
}
|
||||
|
||||
async function uploadComposeFiles(files, options = {}) {
|
||||
const list = [...(files || [])];
|
||||
const announce = options.announce !== false;
|
||||
const incoming = [...(files || [])];
|
||||
if (!incoming.length) return [];
|
||||
if (state.composeUploading) return [];
|
||||
const existing = new Set(state.composeAttachments.map((attachment) => `${String(attachment.filename || "")}\u0000${Number(attachment.size || 0)}`));
|
||||
const selected = new Set();
|
||||
const list = incoming.filter((file) => {
|
||||
const key = `${String(file.name || "")}\u0000${Number(file.size || 0)}`;
|
||||
if (existing.has(key) || selected.has(key)) return false;
|
||||
selected.add(key);
|
||||
return true;
|
||||
});
|
||||
if (!list.length) return [];
|
||||
if (state.composeUploading) { if (announce) toast(trText("Another upload is already in progress"), "warning"); return []; }
|
||||
const maxBytes = Math.max(1, Number(root.dataset.maxAttachmentBytes || 10 * 1024 * 1024));
|
||||
const maxMiB = Math.max(1, Number(root.dataset.maxAttachmentMb || Math.round(maxBytes / 1024 / 1024)));
|
||||
const oversized = list.find((file) => Number(file.size || 0) > maxBytes);
|
||||
if (oversized) {
|
||||
toast(`${oversized.name}: ${trText("maximum attachment size is")} ${maxMiB} MiB`, "error");
|
||||
setComposeUploadProgress(true, 0, `${oversized.name}: ${trText("maximum attachment size is")} ${maxMiB} MiB`);
|
||||
setTimeout(() => { if (!state.composeUploading) setComposeUploadProgress(false); }, 4200);
|
||||
return [];
|
||||
}
|
||||
const maxMessageBytes = Math.max(1, Number(root.dataset.maxMessageBytes || 50 * 1024 * 1024));
|
||||
const maxMessageMiB = Math.max(1, Number(root.dataset.maxMessageMb || Math.round(maxMessageBytes / 1024 / 1024)));
|
||||
const estimatedAfterUpload = updateComposeMessageSize(list);
|
||||
if (estimatedAfterUpload > maxMessageBytes) {
|
||||
toast(`${trText("Estimated message size exceeds the configured limit")} (${formatComposeBytes(estimatedAfterUpload)} / ${maxMessageMiB} MiB). ${trText("The exact size will be checked before sending.")}`, "warning");
|
||||
setComposeUploadProgress(true, 0, `${trText("Estimated message size exceeds the configured limit")} (${formatComposeBytes(estimatedAfterUpload)} / ${maxMessageMiB} MiB)`);
|
||||
}
|
||||
state.composeUploading = true;
|
||||
const controller = new AbortController();
|
||||
state.composeUploadController = controller;
|
||||
const uploadLabel = list.length === 1 ? trText("Uploading attachment…") : trText("Uploading files…");
|
||||
setComposeUploadProgress(true, 0, uploadLabel);
|
||||
const progress = progressToast(uploadLabel);
|
||||
await saveCurrentDraft(true, true);
|
||||
const id = ensureDraftId();
|
||||
const added = [];
|
||||
@@ -435,27 +445,28 @@ async function uploadComposeFiles(files, options = {}) {
|
||||
form.append("file", file, file.name);
|
||||
const data = await uploadRequest(`/drafts/${encodeURIComponent(id)}/attachments`, form, (percent) => {
|
||||
const overall = Math.round(((index + (percent / 100)) / list.length) * 100);
|
||||
const label = `${trText("Uploading attachment…")} ${file.name}`;
|
||||
setComposeUploadProgress(true, overall, `${label} · ${overall}%`);
|
||||
progress.update(overall, label);
|
||||
});
|
||||
added.push(...(data.attachments || []));
|
||||
setComposeUploadProgress(true, overall, `${trText("Uploading attachment…")} ${file.name} · ${overall}%`);
|
||||
}, controller.signal);
|
||||
const uploaded = data.attachments || [];
|
||||
added.push(...uploaded);
|
||||
state.composeAttachments.push(...uploaded);
|
||||
renderComposeAttachments();
|
||||
state.composeDirty = true;
|
||||
state.composeNeedsRemoteSync = true;
|
||||
storeLocalDraftSnapshot();
|
||||
}
|
||||
state.composeAttachments.push(...added);
|
||||
renderComposeAttachments();
|
||||
state.composeDirty = true;
|
||||
state.composeNeedsRemoteSync = true;
|
||||
storeLocalDraftSnapshot();
|
||||
setComposeUploadProgress(true, 100, trText("Upload complete"));
|
||||
progress.complete(added.length === 1 ? trText("Attachment uploaded") : `${added.length} ${trText("attachments uploaded")}`);
|
||||
if (announce) toast(`${added.length} attachment${added.length === 1 ? "" : "s"} added`, "success");
|
||||
setTimeout(() => setComposeUploadProgress(false), 450);
|
||||
setTimeout(() => { if (!state.composeUploading) setComposeUploadProgress(false); }, 450);
|
||||
return added;
|
||||
} catch (error) {
|
||||
setComposeUploadProgress(false);
|
||||
progress.fail(error.message);
|
||||
return [];
|
||||
if (error?.name === "AbortError") setComposeUploadProgress(false);
|
||||
else {
|
||||
setComposeUploadProgress(true, 0, error?.message || trText("Upload failed"));
|
||||
setTimeout(() => { if (!state.composeUploading) setComposeUploadProgress(false); }, 4800);
|
||||
}
|
||||
return added;
|
||||
} finally {
|
||||
if (state.composeUploadController === controller) state.composeUploadController = null;
|
||||
state.composeUploading = false;
|
||||
}
|
||||
}
|
||||
@@ -978,8 +989,8 @@ async function recoverDraft() {
|
||||
async function closeComposer() {
|
||||
clearTimeout(state.draftSaveTimer);
|
||||
clearTimeout(state.inlineImageCleanupTimer);
|
||||
if (state.composeUploadController) state.composeUploadController.abort();
|
||||
await cleanupUnusedInlineAttachments();
|
||||
if (state.composeUploading) return toast("Wait for the attachment upload to finish", "warning");
|
||||
if (state.composeNeedsRemoteSync && draftHasContent()) {
|
||||
const saved = await saveCurrentDraft(true, true, true);
|
||||
if (!saved) toast("Draft kept in this browser for recovery", "warning", 5200);
|
||||
|
||||
Reference in new issue
Block a user