@bobfrankston/rmfmail 1.2.261 → 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.
@@ -1339,19 +1339,7 @@ export async function showMessage(accountId, uid, folderId, specialUse, isRetry
1339
1339
  const fromEl = headerEl.querySelector(".mv-from");
1340
1340
  const toEl = headerEl.querySelector(".mv-to");
1341
1341
  fromEl.textContent = formatAddr(msg.from);
1342
- let toLine = `To: ${msg.to.map(formatAddr).join(", ")}`;
1343
- if (msg.cc?.length)
1344
- toLine += ` Cc: ${msg.cc.map(formatAddr).join(", ")}`;
1345
- // Always-visible Delivered-To line — shown when present and not already
1346
- // covered by the To/Cc list. Critical for accounts with multiple aliases
1347
- // where you need to see which one received the message at a glance.
1348
- const toAddrs = (msg.to || []).map((a) => a.address.toLowerCase());
1349
- const ccAddrs = (msg.cc || []).map((a) => a.address.toLowerCase());
1350
- const dt = (msg.deliveredTo || "").toLowerCase();
1351
- if (msg.deliveredTo && !toAddrs.includes(dt) && !ccAddrs.includes(dt)) {
1352
- toLine += ` Delivered-To: ${msg.deliveredTo}`;
1353
- }
1354
- toEl.textContent = toLine;
1342
+ setRecipientLine(toEl, msg.to, msg.cc, msg.deliveredTo);
1355
1343
  headerEl.querySelector(".mv-subject").textContent = msg.subject;
1356
1344
  document.dispatchEvent(new CustomEvent("mailx-message-shown", { detail: { accountId } }));
1357
1345
  // Right-click on email addresses in header: copy name, copy address,
@@ -1361,7 +1349,12 @@ export async function showMessage(accountId, uid, folderId, specialUse, isRetry
1361
1349
  e.preventDefault();
1362
1350
  const me = e;
1363
1351
  const items = [];
1364
- const addrs = el === fromEl ? [msg.from] : [...(msg.to || []), ...(msg.cc || [])];
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;
1365
1358
  for (const addr of addrs) {
1366
1359
  if (!addr?.address)
1367
1360
  continue;
@@ -1452,6 +1445,13 @@ export async function showMessage(accountId, uid, folderId, specialUse, isRetry
1452
1445
  });
1453
1446
  items.push({ label: "", action: () => { }, separator: true });
1454
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
+ }
1455
1455
  items.push({ label: "Reply", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "reply" } })) });
1456
1456
  items.push({ label: "Reply All", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "replyAll" } })) });
1457
1457
  items.push({ label: "Forward", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "forward" } })) });
@@ -2311,6 +2311,63 @@ function formatAddr(addr) {
2311
2311
  return `${addr.name} <${addr.address}>`;
2312
2312
  return addr.address;
2313
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
+ }
2314
2371
  /** Render the viewer header from a list-row envelope (instant — no body
2315
2372
  * fetch awaited). Used to populate the header pane the moment a message
2316
2373
  * is clicked so the user always sees something actionable; getMessage()
@@ -2325,12 +2382,8 @@ function renderHeaderFromEnvelope(headerEl, env) {
2325
2382
  const dateEl = headerEl.querySelector(".mv-date");
2326
2383
  if (fromEl)
2327
2384
  fromEl.textContent = formatAddr(env.from);
2328
- if (toEl) {
2329
- let toLine = `To: ${(env.to || []).map(formatAddr).join(", ")}`;
2330
- if (env.cc?.length)
2331
- toLine += ` Cc: ${env.cc.map(formatAddr).join(", ")}`;
2332
- toEl.textContent = toLine;
2333
- }
2385
+ if (toEl)
2386
+ setRecipientLine(toEl, env.to, env.cc);
2334
2387
  if (subjEl)
2335
2388
  subjEl.textContent = env.subject || "";
2336
2389
  if (dateEl) {