@bobfrankston/rmfmail 1.2.260 → 1.2.263
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 +55 -20
- package/client/app.bundle.js.map +2 -2
- package/client/components/message-viewer.js +89 -22
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +95 -19
- package/client/package.json +1 -1
- package/client/styles/components.css +45 -1
- package/package.json +3 -3
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-41916 → node_modules.npmglobalize-stash-88864}/.package-lock.json +0 -0
|
@@ -746,6 +746,15 @@ function installPreviewControls(iframe) {
|
|
|
746
746
|
const doc = iframe.contentDocument;
|
|
747
747
|
if (!doc)
|
|
748
748
|
return;
|
|
749
|
+
// Idempotent per DOCUMENT. A freshly-appended srcdoc iframe first
|
|
750
|
+
// exposes a throwaway `about:blank` document whose readyState is
|
|
751
|
+
// already "complete" — so binding to it is binding to something that
|
|
752
|
+
// is discarded a frame later, and every path below has to run again
|
|
753
|
+
// against the real one. Marking the document (not the iframe) lets us
|
|
754
|
+
// safely attach from both the immediate and the load path.
|
|
755
|
+
if (doc.__mvControlsBound)
|
|
756
|
+
return;
|
|
757
|
+
doc.__mvControlsBound = true;
|
|
749
758
|
applyZoom(doc);
|
|
750
759
|
// Paint search marks as soon as the text exists — `attach` also runs
|
|
751
760
|
// on `load`, which on a remote-image newsletter is many seconds after
|
|
@@ -816,10 +825,15 @@ function installPreviewControls(iframe) {
|
|
|
816
825
|
// host; the doc-level handler missed cases where WebView2's native
|
|
817
826
|
// menu fired before our parent listener got installed.
|
|
818
827
|
};
|
|
828
|
+
// Bind on BOTH paths, not one or the other: `load` is when the real
|
|
829
|
+
// srcdoc document exists, and the immediate call covers an iframe that
|
|
830
|
+
// is genuinely already loaded (re-render of a live preview). The
|
|
831
|
+
// per-document guard in attach() makes the overlap a no-op — where the
|
|
832
|
+
// old `complete ? attach() : onload` choice could bind the whole set to
|
|
833
|
+
// the discarded about:blank document and never run against the letter.
|
|
834
|
+
iframe.addEventListener("load", attach);
|
|
819
835
|
if (iframe.contentDocument?.readyState === "complete")
|
|
820
836
|
attach();
|
|
821
|
-
else
|
|
822
|
-
iframe.addEventListener("load", attach, { once: true });
|
|
823
837
|
// DOMContentLoaded is the "text is painted" mark (see the _ptick pair at
|
|
824
838
|
// the render site); highlight there too so marks appear with the words
|
|
825
839
|
// rather than after the last tracking pixel resolves.
|
|
@@ -1325,19 +1339,7 @@ export async function showMessage(accountId, uid, folderId, specialUse, isRetry
|
|
|
1325
1339
|
const fromEl = headerEl.querySelector(".mv-from");
|
|
1326
1340
|
const toEl = headerEl.querySelector(".mv-to");
|
|
1327
1341
|
fromEl.textContent = formatAddr(msg.from);
|
|
1328
|
-
|
|
1329
|
-
if (msg.cc?.length)
|
|
1330
|
-
toLine += ` Cc: ${msg.cc.map(formatAddr).join(", ")}`;
|
|
1331
|
-
// Always-visible Delivered-To line — shown when present and not already
|
|
1332
|
-
// covered by the To/Cc list. Critical for accounts with multiple aliases
|
|
1333
|
-
// where you need to see which one received the message at a glance.
|
|
1334
|
-
const toAddrs = (msg.to || []).map((a) => a.address.toLowerCase());
|
|
1335
|
-
const ccAddrs = (msg.cc || []).map((a) => a.address.toLowerCase());
|
|
1336
|
-
const dt = (msg.deliveredTo || "").toLowerCase();
|
|
1337
|
-
if (msg.deliveredTo && !toAddrs.includes(dt) && !ccAddrs.includes(dt)) {
|
|
1338
|
-
toLine += ` Delivered-To: ${msg.deliveredTo}`;
|
|
1339
|
-
}
|
|
1340
|
-
toEl.textContent = toLine;
|
|
1342
|
+
setRecipientLine(toEl, msg.to, msg.cc, msg.deliveredTo);
|
|
1341
1343
|
headerEl.querySelector(".mv-subject").textContent = msg.subject;
|
|
1342
1344
|
document.dispatchEvent(new CustomEvent("mailx-message-shown", { detail: { accountId } }));
|
|
1343
1345
|
// Right-click on email addresses in header: copy name, copy address,
|
|
@@ -1347,7 +1349,12 @@ export async function showMessage(accountId, uid, folderId, specialUse, isRetry
|
|
|
1347
1349
|
e.preventDefault();
|
|
1348
1350
|
const me = e;
|
|
1349
1351
|
const items = [];
|
|
1350
|
-
const
|
|
1352
|
+
const allAddrs = el === fromEl ? [msg.from] : [...(msg.to || []), ...(msg.cc || [])];
|
|
1353
|
+
// Bulk mail can address hundreds of people; enumerating every
|
|
1354
|
+
// one builds a menu thousands of items tall. Show the first
|
|
1355
|
+
// few and say how many were left out.
|
|
1356
|
+
const addrs = allAddrs.slice(0, CONTEXT_MENU_ADDR_CAP);
|
|
1357
|
+
const omitted = allAddrs.length - addrs.length;
|
|
1351
1358
|
for (const addr of addrs) {
|
|
1352
1359
|
if (!addr?.address)
|
|
1353
1360
|
continue;
|
|
@@ -1438,6 +1445,13 @@ export async function showMessage(accountId, uid, folderId, specialUse, isRetry
|
|
|
1438
1445
|
});
|
|
1439
1446
|
items.push({ label: "", action: () => { }, separator: true });
|
|
1440
1447
|
}
|
|
1448
|
+
if (omitted > 0) {
|
|
1449
|
+
items.push({
|
|
1450
|
+
label: `… ${omitted} more recipients — copy the whole list`,
|
|
1451
|
+
action: () => navigator.clipboard.writeText(allAddrs.map(formatAddr).join(", ")),
|
|
1452
|
+
});
|
|
1453
|
+
items.push({ label: "", action: () => { }, separator: true });
|
|
1454
|
+
}
|
|
1441
1455
|
items.push({ label: "Reply", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "reply" } })) });
|
|
1442
1456
|
items.push({ label: "Reply All", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "replyAll" } })) });
|
|
1443
1457
|
items.push({ label: "Forward", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "forward" } })) });
|
|
@@ -2297,6 +2311,63 @@ function formatAddr(addr) {
|
|
|
2297
2311
|
return `${addr.name} <${addr.address}>`;
|
|
2298
2312
|
return addr.address;
|
|
2299
2313
|
}
|
|
2314
|
+
/** Above this many To+Cc addresses the recipient line is offered collapsed
|
|
2315
|
+
* with a "▸ N recipients" expander. Anything under it reads fine inline. */
|
|
2316
|
+
const MANY_RECIPIENTS = 6;
|
|
2317
|
+
/** Ceiling on how many addresses the header's right-click menu enumerates.
|
|
2318
|
+
* Each address contributes ~7 entries (copy name / copy address / contacts /
|
|
2319
|
+
* preferred / denylist / priority), so a 444-recipient bulk mail would build
|
|
2320
|
+
* a 3000-item menu taller than the screen and slow to open. */
|
|
2321
|
+
const CONTEXT_MENU_ADDR_CAP = 12;
|
|
2322
|
+
/**
|
|
2323
|
+
* Fill the viewer's To/Cc line.
|
|
2324
|
+
*
|
|
2325
|
+
* Written as elements rather than a bare `textContent` because bulk mail can
|
|
2326
|
+
* carry hundreds of recipients: the full text stays in the DOM (so find,
|
|
2327
|
+
* select and copy still see every address) but `.mv-to-text` is line-clamped
|
|
2328
|
+
* to two rows, with a toggle to expand. Unclamped, that one line grew the
|
|
2329
|
+
* header past the whole viewer height and starved `.mv-body` to zero — see
|
|
2330
|
+
* the `.mv-header` comment in components.css.
|
|
2331
|
+
*/
|
|
2332
|
+
function setRecipientLine(toEl, to, cc, deliveredTo) {
|
|
2333
|
+
const toList = to || [];
|
|
2334
|
+
const ccList = cc || [];
|
|
2335
|
+
let line = `To: ${toList.map(formatAddr).join(", ")}`;
|
|
2336
|
+
if (ccList.length)
|
|
2337
|
+
line += ` Cc: ${ccList.map(formatAddr).join(", ")}`;
|
|
2338
|
+
// Always-visible Delivered-To — shown when present and not already covered
|
|
2339
|
+
// by the To/Cc list. Critical for accounts with multiple aliases where you
|
|
2340
|
+
// need to see which one received the message at a glance.
|
|
2341
|
+
if (deliveredTo) {
|
|
2342
|
+
const dt = deliveredTo.toLowerCase();
|
|
2343
|
+
const covered = [...toList, ...ccList].some(a => a.address.toLowerCase() === dt);
|
|
2344
|
+
if (!covered)
|
|
2345
|
+
line += ` Delivered-To: ${deliveredTo}`;
|
|
2346
|
+
}
|
|
2347
|
+
toEl.textContent = "";
|
|
2348
|
+
toEl.classList.remove("mv-to-expanded");
|
|
2349
|
+
const text = document.createElement("span");
|
|
2350
|
+
text.className = "mv-to-text";
|
|
2351
|
+
text.textContent = line;
|
|
2352
|
+
toEl.append(text);
|
|
2353
|
+
const count = toList.length + ccList.length;
|
|
2354
|
+
// Offer the expander when the list is long by count, or when the clamp
|
|
2355
|
+
// actually cut something off (a handful of very long display names).
|
|
2356
|
+
const clamped = text.scrollHeight > text.clientHeight + 1;
|
|
2357
|
+
if (count <= MANY_RECIPIENTS && !clamped)
|
|
2358
|
+
return;
|
|
2359
|
+
const toggle = document.createElement("button");
|
|
2360
|
+
toggle.className = "mv-to-toggle";
|
|
2361
|
+
toggle.type = "button";
|
|
2362
|
+
const label = count === 1 ? "1 recipient" : `${count} recipients`;
|
|
2363
|
+
const paint = (open) => {
|
|
2364
|
+
toggle.textContent = `${open ? "▾" : "▸"} ${label}`;
|
|
2365
|
+
toggle.title = open ? "Collapse the recipient list" : "Show the full recipient list";
|
|
2366
|
+
};
|
|
2367
|
+
paint(false);
|
|
2368
|
+
toggle.addEventListener("click", () => paint(toEl.classList.toggle("mv-to-expanded")));
|
|
2369
|
+
toEl.append(toggle);
|
|
2370
|
+
}
|
|
2300
2371
|
/** Render the viewer header from a list-row envelope (instant — no body
|
|
2301
2372
|
* fetch awaited). Used to populate the header pane the moment a message
|
|
2302
2373
|
* is clicked so the user always sees something actionable; getMessage()
|
|
@@ -2311,12 +2382,8 @@ function renderHeaderFromEnvelope(headerEl, env) {
|
|
|
2311
2382
|
const dateEl = headerEl.querySelector(".mv-date");
|
|
2312
2383
|
if (fromEl)
|
|
2313
2384
|
fromEl.textContent = formatAddr(env.from);
|
|
2314
|
-
if (toEl)
|
|
2315
|
-
|
|
2316
|
-
if (env.cc?.length)
|
|
2317
|
-
toLine += ` Cc: ${env.cc.map(formatAddr).join(", ")}`;
|
|
2318
|
-
toEl.textContent = toLine;
|
|
2319
|
-
}
|
|
2385
|
+
if (toEl)
|
|
2386
|
+
setRecipientLine(toEl, env.to, env.cc);
|
|
2320
2387
|
if (subjEl)
|
|
2321
2388
|
subjEl.textContent = env.subject || "";
|
|
2322
2389
|
if (dateEl) {
|