@bobfrankston/rmfmail 1.2.96 → 1.2.98
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/app.bundle.js +116 -20
- package/client/app.bundle.js.map +3 -3
- package/client/components/message-viewer.js +138 -26
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +122 -24
- package/client/compose/compose.bundle.js +4 -0
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/lib/api-client.js +4 -0
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +5 -0
- package/client/lib/mailxapi.js +3 -0
- package/package.json +1 -1
- package/packages/mailx-service/index.d.ts +7 -0
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +40 -0
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +31 -0
- package/packages/mailx-service/jsonrpc.js +2 -0
- package/packages/mailx-service/jsonrpc.js.map +1 -1
- package/packages/mailx-service/jsonrpc.ts +2 -0
package/client/app.bundle.js
CHANGED
|
@@ -55,6 +55,7 @@ __export(api_client_exports, {
|
|
|
55
55
|
getDiagnostics: () => getDiagnostics,
|
|
56
56
|
getFolders: () => getFolders,
|
|
57
57
|
getMessage: () => getMessage,
|
|
58
|
+
getMessageSource: () => getMessageSource,
|
|
58
59
|
getMessages: () => getMessages,
|
|
59
60
|
getOutboxStatus: () => getOutboxStatus,
|
|
60
61
|
getPrimaryAccount: () => getPrimaryAccount,
|
|
@@ -617,6 +618,9 @@ async function openAttachment(accountId, uid, attachmentId, folderId, filename)
|
|
|
617
618
|
const fn = ipc().openAttachment;
|
|
618
619
|
return fn ? fn(accountId, uid, attachmentId, folderId, filename) : void 0;
|
|
619
620
|
}
|
|
621
|
+
async function getMessageSource(accountId, uid, folderId) {
|
|
622
|
+
return ipc().getMessageSource(accountId, uid, folderId);
|
|
623
|
+
}
|
|
620
624
|
async function getDeviceAccounts() {
|
|
621
625
|
return ipc().getDeviceAccounts?.() ?? [];
|
|
622
626
|
}
|
|
@@ -1311,7 +1315,13 @@ function showPreviewBodyMenu(absX, absY, selectedText, sourceWindow, linkUrl, li
|
|
|
1311
1315
|
} },
|
|
1312
1316
|
{ label: "", action: () => {
|
|
1313
1317
|
}, separator: true },
|
|
1314
|
-
{ label: "Print\u2026 (Ctrl+P)", action: () => printCurrentMessage() }
|
|
1318
|
+
{ label: "Print\u2026 (Ctrl+P)", action: () => printCurrentMessage() },
|
|
1319
|
+
{ label: "Save message (.eml)\u2026", action: () => {
|
|
1320
|
+
void saveCurrentMessageAsEml();
|
|
1321
|
+
} },
|
|
1322
|
+
{ label: "Save message as HTML\u2026", action: () => {
|
|
1323
|
+
void saveCurrentMessageAsHtml();
|
|
1324
|
+
} }
|
|
1315
1325
|
);
|
|
1316
1326
|
showContextMenu(absX, absY, items);
|
|
1317
1327
|
}
|
|
@@ -2060,6 +2070,29 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
2060
2070
|
attEl.hidden = true;
|
|
2061
2071
|
if (msg.attachments?.length) {
|
|
2062
2072
|
attEl.hidden = false;
|
|
2073
|
+
for (const u of dragOutUrls) {
|
|
2074
|
+
try {
|
|
2075
|
+
URL.revokeObjectURL(u);
|
|
2076
|
+
} catch {
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
dragOutUrls.length = 0;
|
|
2080
|
+
const dragCache = /* @__PURE__ */ new Map();
|
|
2081
|
+
void (async () => {
|
|
2082
|
+
for (const a1 of msg.attachments) {
|
|
2083
|
+
if ((a1.size || 0) > 50 * 1024 * 1024)
|
|
2084
|
+
continue;
|
|
2085
|
+
try {
|
|
2086
|
+
const data = await getAttachment(accountId, uid, a1.id, msg.folderId);
|
|
2087
|
+
const bytes = Uint8Array.from(atob(data.content), (c) => c.charCodeAt(0));
|
|
2088
|
+
const blob = new Blob([bytes], { type: data.contentType || "application/octet-stream" });
|
|
2089
|
+
const url = URL.createObjectURL(blob);
|
|
2090
|
+
dragOutUrls.push(url);
|
|
2091
|
+
dragCache.set(a1.id, { url, contentType: data.contentType || "application/octet-stream" });
|
|
2092
|
+
} catch {
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
})();
|
|
2063
2096
|
const saveAttachmentAt = async (idx) => {
|
|
2064
2097
|
const a0 = msg.attachments[idx];
|
|
2065
2098
|
const name = a0.filename || "attachment";
|
|
@@ -2158,21 +2191,20 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
2158
2191
|
}
|
|
2159
2192
|
});
|
|
2160
2193
|
chip.draggable = true;
|
|
2161
|
-
chip.addEventListener("dragstart",
|
|
2194
|
+
chip.addEventListener("dragstart", (e) => {
|
|
2162
2195
|
if (!e.dataTransfer)
|
|
2163
2196
|
return;
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
const
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
e.dataTransfer.setData("DownloadURL", downloadUrl);
|
|
2172
|
-
e.dataTransfer.effectAllowed = "copy";
|
|
2173
|
-
} catch (err) {
|
|
2174
|
-
console.error(`Attachment drag-out failed: ${err.message || err}`);
|
|
2197
|
+
const cached2 = dragCache.get(att.id);
|
|
2198
|
+
if (!cached2) {
|
|
2199
|
+
e.preventDefault();
|
|
2200
|
+
const statusEl = document.getElementById("status-sync");
|
|
2201
|
+
if (statusEl)
|
|
2202
|
+
statusEl.textContent = "Attachment still loading \u2014 try dragging again in a moment.";
|
|
2203
|
+
return;
|
|
2175
2204
|
}
|
|
2205
|
+
const safeName = (att.filename || "attachment").replace(/[\r\n"\/\\]/g, "_");
|
|
2206
|
+
e.dataTransfer.setData("DownloadURL", `${cached2.contentType}:${safeName}:${cached2.url}`);
|
|
2207
|
+
e.dataTransfer.effectAllowed = "copy";
|
|
2176
2208
|
});
|
|
2177
2209
|
chip.addEventListener("contextmenu", (e) => {
|
|
2178
2210
|
e.preventDefault();
|
|
@@ -2590,7 +2622,12 @@ ${csp}
|
|
|
2590
2622
|
var k = (e.key || "").toLowerCase();
|
|
2591
2623
|
var isShortcut = e.ctrlKey && !e.altKey && !e.metaKey && (
|
|
2592
2624
|
k === "r" || k === "f" || k === "n" || k === "a" || k === "d" ||
|
|
2593
|
-
k === "z" || k === "y" || k === "k"
|
|
2625
|
+
k === "z" || k === "y" || k === "k" ||
|
|
2626
|
+
// Ctrl+P: without this suppress, WebView2's native print fires on
|
|
2627
|
+
// the iframe's default and prints the WHOLE APP window instead of
|
|
2628
|
+
// the letter (Bob 2026-07-02). The forwarded event reaches app.ts,
|
|
2629
|
+
// which runs printCurrentMessage (letter + header block).
|
|
2630
|
+
k === "p"
|
|
2594
2631
|
);
|
|
2595
2632
|
if (isShortcut) e.preventDefault();
|
|
2596
2633
|
window.parent.postMessage({
|
|
@@ -2663,16 +2700,22 @@ function popOutToWindow() {
|
|
|
2663
2700
|
}
|
|
2664
2701
|
}, 600);
|
|
2665
2702
|
}
|
|
2666
|
-
function
|
|
2667
|
-
if (!
|
|
2668
|
-
return;
|
|
2669
|
-
const m = currentMessage;
|
|
2703
|
+
function buildPrintableDoc(m) {
|
|
2704
|
+
if (!m)
|
|
2705
|
+
return null;
|
|
2670
2706
|
const fmt = (a) => a?.name ? `${a.name} <${a.address || ""}>` : a?.address || "";
|
|
2671
2707
|
const esc = escapeHtmlLocal;
|
|
2672
2708
|
const row = (label, value) => value ? `<div><strong>${label}:</strong> ${esc(value)}</div>` : "";
|
|
2673
2709
|
const headerHtml = `<div style="font-family:system-ui,Segoe UI,sans-serif;font-size:11pt;border-bottom:1px solid #999;padding-bottom:8px;margin-bottom:14px;line-height:1.5;">` + row("From", fmt(m.from || {})) + row("To", (m.to || []).map(fmt).join(", ")) + (m.cc?.length ? row("Cc", m.cc.map(fmt).join(", ")) : "") + row("Subject", m.subject || "") + row("Date", m.date ? new Date(m.date).toLocaleString() : "") + `</div>`;
|
|
2674
2710
|
const bodyHtml = m.bodyHtml ? m.bodyHtml : `<pre style="white-space:pre-wrap;word-break:break-word;font-family:system-ui,sans-serif;">${esc(m.bodyText || "")}</pre>`;
|
|
2675
|
-
|
|
2711
|
+
return `<!DOCTYPE html><html><head><meta charset="utf-8"><title>${esc(m.subject || "Message")}</title><style>@media print{body{margin:0;}}body{margin:16px;}img{max-width:100%;}</style></head><body>${headerHtml}${bodyHtml}</body></html>`;
|
|
2712
|
+
}
|
|
2713
|
+
function printCurrentMessage() {
|
|
2714
|
+
if (!currentMessage)
|
|
2715
|
+
return;
|
|
2716
|
+
const doc = buildPrintableDoc(currentMessage);
|
|
2717
|
+
if (!doc)
|
|
2718
|
+
return;
|
|
2676
2719
|
const iframe = document.createElement("iframe");
|
|
2677
2720
|
iframe.style.cssText = "position:fixed;left:-9999px;width:1px;height:1px;border:0;";
|
|
2678
2721
|
iframe.srcdoc = doc;
|
|
@@ -2701,6 +2744,58 @@ function printCurrentMessage() {
|
|
|
2701
2744
|
}, { once: true });
|
|
2702
2745
|
document.body.appendChild(iframe);
|
|
2703
2746
|
}
|
|
2747
|
+
async function saveBlobAs(suggestedName, blob) {
|
|
2748
|
+
const picker = window.showSaveFilePicker;
|
|
2749
|
+
if (typeof picker === "function") {
|
|
2750
|
+
let handle;
|
|
2751
|
+
try {
|
|
2752
|
+
handle = await picker({ suggestedName });
|
|
2753
|
+
} catch (e) {
|
|
2754
|
+
if (e?.name === "AbortError")
|
|
2755
|
+
return;
|
|
2756
|
+
throw e;
|
|
2757
|
+
}
|
|
2758
|
+
const writable = await handle.createWritable();
|
|
2759
|
+
await writable.write(blob);
|
|
2760
|
+
await writable.close();
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
const url = URL.createObjectURL(blob);
|
|
2764
|
+
const a = document.createElement("a");
|
|
2765
|
+
a.href = url;
|
|
2766
|
+
a.download = suggestedName;
|
|
2767
|
+
a.style.display = "none";
|
|
2768
|
+
document.body.appendChild(a);
|
|
2769
|
+
a.click();
|
|
2770
|
+
setTimeout(() => {
|
|
2771
|
+
a.remove();
|
|
2772
|
+
URL.revokeObjectURL(url);
|
|
2773
|
+
}, 5e3);
|
|
2774
|
+
}
|
|
2775
|
+
async function saveCurrentMessageAsEml() {
|
|
2776
|
+
if (!currentMessage)
|
|
2777
|
+
return;
|
|
2778
|
+
try {
|
|
2779
|
+
const src = await getMessageSource(currentAccountId, currentMessage.uid, currentMessage.folderId);
|
|
2780
|
+
const bytes = Uint8Array.from(atob(src.dataBase64), (c) => c.charCodeAt(0));
|
|
2781
|
+
await saveBlobAs(src.filename || "message.eml", new Blob([bytes], { type: "message/rfc822" }));
|
|
2782
|
+
} catch (e) {
|
|
2783
|
+
window.dispatchEvent(new CustomEvent("mailx-alert", { detail: { message: `Couldn't save message: ${e?.message || e}`, key: "message-save" } }));
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
async function saveCurrentMessageAsHtml() {
|
|
2787
|
+
if (!currentMessage)
|
|
2788
|
+
return;
|
|
2789
|
+
const doc = buildPrintableDoc(currentMessage);
|
|
2790
|
+
if (!doc)
|
|
2791
|
+
return;
|
|
2792
|
+
const base = (currentMessage.subject || "message").replace(/[<>:"/\\|?*\r\n]/g, "_").slice(0, 80).trim() || "message";
|
|
2793
|
+
try {
|
|
2794
|
+
await saveBlobAs(`${base}.html`, new Blob([doc], { type: "text/html" }));
|
|
2795
|
+
} catch (e) {
|
|
2796
|
+
window.dispatchEvent(new CustomEvent("mailx-alert", { detail: { message: `Couldn't save message: ${e?.message || e}`, key: "message-save" } }));
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2704
2799
|
function spawnDesktopPopout(msg, accountId) {
|
|
2705
2800
|
const wrapper = document.createElement("div");
|
|
2706
2801
|
wrapper.className = "compose-overlay viewer-popout";
|
|
@@ -2817,7 +2912,7 @@ function spawnDesktopPopout(msg, accountId) {
|
|
|
2817
2912
|
function escapeHtmlLocal(s) {
|
|
2818
2913
|
return (s || "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2819
2914
|
}
|
|
2820
|
-
var currentMessage, currentAccountId, showMessageGeneration, retryCount, lastEnvelope, PARSED_CACHE_LIMIT, parsedCache, sessionAllowedRemote, recentFetchErrors, ZOOM_KEY, ZOOM_MIN, ZOOM_MAX, ZOOM_STEP, previewZoom;
|
|
2915
|
+
var currentMessage, currentAccountId, dragOutUrls, showMessageGeneration, retryCount, lastEnvelope, PARSED_CACHE_LIMIT, parsedCache, sessionAllowedRemote, recentFetchErrors, ZOOM_KEY, ZOOM_MIN, ZOOM_MAX, ZOOM_STEP, previewZoom;
|
|
2821
2916
|
var init_message_viewer = __esm({
|
|
2822
2917
|
"client/components/message-viewer.js"() {
|
|
2823
2918
|
"use strict";
|
|
@@ -2828,6 +2923,7 @@ var init_message_viewer = __esm({
|
|
|
2828
2923
|
init_mailx_types();
|
|
2829
2924
|
currentMessage = null;
|
|
2830
2925
|
currentAccountId = "";
|
|
2926
|
+
dragOutUrls = [];
|
|
2831
2927
|
showMessageGeneration = 0;
|
|
2832
2928
|
retryCount = 0;
|
|
2833
2929
|
lastEnvelope = null;
|