@bobfrankston/rmfmail 1.2.261 → 1.2.268

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.
Files changed (38) hide show
  1. package/TODO.md +1 -0
  2. package/client/android-bootstrap.bundle.js +32 -0
  3. package/client/android-bootstrap.bundle.js.map +2 -2
  4. package/client/app.bundle.js +102 -126
  5. package/client/app.bundle.js.map +3 -3
  6. package/client/app.js +59 -116
  7. package/client/app.js.map +1 -1
  8. package/client/app.ts +64 -93
  9. package/client/components/message-list.js +18 -3
  10. package/client/components/message-list.js.map +1 -1
  11. package/client/components/message-list.ts +18 -3
  12. package/client/components/message-viewer.js +84 -24
  13. package/client/components/message-viewer.js.map +1 -1
  14. package/client/components/message-viewer.ts +90 -21
  15. package/client/compose/compose.bundle.js +16 -5
  16. package/client/compose/compose.bundle.js.map +2 -2
  17. package/client/compose/compose.js +5 -6
  18. package/client/compose/compose.js.map +1 -1
  19. package/client/compose/compose.ts +5 -4
  20. package/client/lib/rmf-tiny.js +15 -1
  21. package/client/package.json +1 -1
  22. package/client/styles/components.css +54 -1
  23. package/docs/thundermail-jmap.md +291 -0
  24. package/package.json +9 -9
  25. package/packages/mailx-imap/index.d.ts.map +1 -1
  26. package/packages/mailx-imap/index.js +17 -1
  27. package/packages/mailx-imap/index.js.map +1 -1
  28. package/packages/mailx-imap/index.ts +17 -1
  29. package/packages/mailx-imap/package-lock.json +2 -2
  30. package/packages/mailx-imap/package.json +1 -1
  31. package/packages/mailx-store-web/package.json +1 -1
  32. package/packages/mailx-types/index.d.ts +51 -0
  33. package/packages/mailx-types/index.d.ts.map +1 -1
  34. package/packages/mailx-types/index.js +72 -0
  35. package/packages/mailx-types/index.js.map +1 -1
  36. package/packages/mailx-types/index.ts +72 -0
  37. package/packages/mailx-types/package.json +1 -1
  38. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-62112 → node_modules.npmglobalize-stash-83744}/.package-lock.json +0 -0
@@ -1368,6 +1368,35 @@ function _set(msg, flag, state) {
1368
1368
  const next = state ? [...msg.flags || [], flag] : (msg.flags || []).filter((f) => f !== flag);
1369
1369
  msg.flags = next;
1370
1370
  }
1371
+ function isJunkPreviewText(s) {
1372
+ const t = (s || "").replace(/\s+/g, " ").trim().toLowerCase();
1373
+ if (!t)
1374
+ return true;
1375
+ return t === "undefined" || t === "null" || t === "nan" || t === "[object object]";
1376
+ }
1377
+ function displayNameClaimsOtherAddress(name, address) {
1378
+ const n = (name || "").trim();
1379
+ if (!n)
1380
+ return null;
1381
+ const real = (address || "").trim().toLowerCase();
1382
+ const m = n.match(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g);
1383
+ if (!m)
1384
+ return null;
1385
+ for (const claimed of m) {
1386
+ if (claimed.toLowerCase() !== real)
1387
+ return claimed;
1388
+ }
1389
+ return null;
1390
+ }
1391
+ function formatSender(addr) {
1392
+ const address = (addr?.address || "").trim();
1393
+ const name = (addr?.name || "").trim();
1394
+ if (!name)
1395
+ return { text: address, claimed: null };
1396
+ if (name.toLowerCase() === address.toLowerCase())
1397
+ return { text: address, claimed: null };
1398
+ return { text: `${name} <${address}>`, claimed: displayNameClaimsOtherAddress(name, address) };
1399
+ }
1371
1400
  var _SEEN, _FLAGGED, _DRAFT, seenOf, flaggedOf, draftOf, setSeen, setFlagged;
1372
1401
  var init_mailx_types = __esm({
1373
1402
  "packages/mailx-types/index.js"() {
@@ -2568,16 +2597,7 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
2568
2597
  const fromEl = headerEl.querySelector(".mv-from");
2569
2598
  const toEl = headerEl.querySelector(".mv-to");
2570
2599
  fromEl.textContent = formatAddr(msg.from);
2571
- let toLine = `To: ${msg.to.map(formatAddr).join(", ")}`;
2572
- if (msg.cc?.length)
2573
- toLine += ` Cc: ${msg.cc.map(formatAddr).join(", ")}`;
2574
- const toAddrs = (msg.to || []).map((a) => a.address.toLowerCase());
2575
- const ccAddrs = (msg.cc || []).map((a) => a.address.toLowerCase());
2576
- const dt = (msg.deliveredTo || "").toLowerCase();
2577
- if (msg.deliveredTo && !toAddrs.includes(dt) && !ccAddrs.includes(dt)) {
2578
- toLine += ` Delivered-To: ${msg.deliveredTo}`;
2579
- }
2580
- toEl.textContent = toLine;
2600
+ setRecipientLine(toEl, msg.to, msg.cc, msg.deliveredTo);
2581
2601
  headerEl.querySelector(".mv-subject").textContent = msg.subject;
2582
2602
  document.dispatchEvent(new CustomEvent("mailx-message-shown", { detail: { accountId } }));
2583
2603
  for (const el of [fromEl, toEl]) {
@@ -2585,7 +2605,9 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
2585
2605
  e.preventDefault();
2586
2606
  const me = e;
2587
2607
  const items = [];
2588
- const addrs = el === fromEl ? [msg.from] : [...msg.to || [], ...msg.cc || []];
2608
+ const allAddrs = el === fromEl ? [msg.from] : [...msg.to || [], ...msg.cc || []];
2609
+ const addrs = allAddrs.slice(0, CONTEXT_MENU_ADDR_CAP);
2610
+ const omitted = allAddrs.length - addrs.length;
2589
2611
  for (const addr of addrs) {
2590
2612
  if (!addr?.address)
2591
2613
  continue;
@@ -2660,6 +2682,14 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
2660
2682
  items.push({ label: "", action: () => {
2661
2683
  }, separator: true });
2662
2684
  }
2685
+ if (omitted > 0) {
2686
+ items.push({
2687
+ label: `\u2026 ${omitted} more recipients \u2014 copy the whole list`,
2688
+ action: () => navigator.clipboard.writeText(allAddrs.map(formatAddr).join(", "))
2689
+ });
2690
+ items.push({ label: "", action: () => {
2691
+ }, separator: true });
2692
+ }
2663
2693
  items.push({ label: "Reply", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "reply" } })) });
2664
2694
  items.push({ label: "Reply All", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "replyAll" } })) });
2665
2695
  items.push({ label: "Forward", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "forward" } })) });
@@ -3240,9 +3270,44 @@ ${escapeText(reputationBlind)}"` : ""}>${reputationText}</div>` : "") + `<div cl
3240
3270
  }
3241
3271
  }
3242
3272
  function formatAddr(addr) {
3243
- if (addr.name)
3244
- return `${addr.name} <${addr.address}>`;
3245
- return addr.address;
3273
+ const { text, claimed } = formatSender(addr);
3274
+ if (!claimed)
3275
+ return text;
3276
+ return `${text} \u2014 display name claims ${claimed}`;
3277
+ }
3278
+ function setRecipientLine(toEl, to, cc, deliveredTo) {
3279
+ const toList = to || [];
3280
+ const ccList = cc || [];
3281
+ let line = `To: ${toList.map(formatAddr).join(", ")}`;
3282
+ if (ccList.length)
3283
+ line += ` Cc: ${ccList.map(formatAddr).join(", ")}`;
3284
+ if (deliveredTo) {
3285
+ const dt = deliveredTo.toLowerCase();
3286
+ const covered = [...toList, ...ccList].some((a) => a.address.toLowerCase() === dt);
3287
+ if (!covered)
3288
+ line += ` Delivered-To: ${deliveredTo}`;
3289
+ }
3290
+ toEl.textContent = "";
3291
+ toEl.classList.remove("mv-to-expanded");
3292
+ const text = document.createElement("span");
3293
+ text.className = "mv-to-text";
3294
+ text.textContent = line;
3295
+ toEl.append(text);
3296
+ const count = toList.length + ccList.length;
3297
+ const clamped = text.scrollHeight > text.clientHeight + 1;
3298
+ if (count <= MANY_RECIPIENTS && !clamped)
3299
+ return;
3300
+ const toggle = document.createElement("button");
3301
+ toggle.className = "mv-to-toggle";
3302
+ toggle.type = "button";
3303
+ const label = count === 1 ? "1 recipient" : `${count} recipients`;
3304
+ const paint = (open) => {
3305
+ toggle.textContent = `${open ? "\u25BE" : "\u25B8"} ${label}`;
3306
+ toggle.title = open ? "Collapse the recipient list" : "Show the full recipient list";
3307
+ };
3308
+ paint(false);
3309
+ toggle.addEventListener("click", () => paint(toEl.classList.toggle("mv-to-expanded")));
3310
+ toEl.append(toggle);
3246
3311
  }
3247
3312
  function renderHeaderFromEnvelope(headerEl, env) {
3248
3313
  headerEl.hidden = false;
@@ -3252,12 +3317,8 @@ function renderHeaderFromEnvelope(headerEl, env) {
3252
3317
  const dateEl = headerEl.querySelector(".mv-date");
3253
3318
  if (fromEl)
3254
3319
  fromEl.textContent = formatAddr(env.from);
3255
- if (toEl) {
3256
- let toLine = `To: ${(env.to || []).map(formatAddr).join(", ")}`;
3257
- if (env.cc?.length)
3258
- toLine += ` Cc: ${env.cc.map(formatAddr).join(", ")}`;
3259
- toEl.textContent = toLine;
3260
- }
3320
+ if (toEl)
3321
+ setRecipientLine(toEl, env.to, env.cc);
3261
3322
  if (subjEl)
3262
3323
  subjEl.textContent = env.subject || "";
3263
3324
  if (dateEl) {
@@ -4027,7 +4088,7 @@ function spawnDesktopPopout(msg, accountId) {
4027
4088
  function escapeHtmlLocal(s) {
4028
4089
  return (s || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4029
4090
  }
4030
- var pendingImageCopies, imageCopySeq, IMAGE_COPY_TIMEOUT_MS, currentMessage, currentAccountId, dragOutUrls, showMessageGeneration, retryCount, lastEnvelope, PARSED_CACHE_LIMIT, parsedCache, sessionAllowedRemote, recentFetchErrors, WAIT_GIVE_UP_SECS, ZOOM_KEY, ZOOM_MIN, ZOOM_MAX, ZOOM_STEP, previewZoom, HIGHLIGHT_NAME, HIGHLIGHT_MAX, searchHighlightTerms, RENDER_MARK_KEY, RENDER_MARK_MAX_AGE_MS, poisonedMessages, renderKey, PROGRESSIVE_TEXT_THRESHOLD, TEXT_CHUNK_BYTES;
4091
+ var pendingImageCopies, imageCopySeq, IMAGE_COPY_TIMEOUT_MS, currentMessage, currentAccountId, dragOutUrls, showMessageGeneration, retryCount, lastEnvelope, PARSED_CACHE_LIMIT, parsedCache, sessionAllowedRemote, recentFetchErrors, WAIT_GIVE_UP_SECS, ZOOM_KEY, ZOOM_MIN, ZOOM_MAX, ZOOM_STEP, previewZoom, HIGHLIGHT_NAME, HIGHLIGHT_MAX, searchHighlightTerms, RENDER_MARK_KEY, RENDER_MARK_MAX_AGE_MS, poisonedMessages, renderKey, MANY_RECIPIENTS, CONTEXT_MENU_ADDR_CAP, PROGRESSIVE_TEXT_THRESHOLD, TEXT_CHUNK_BYTES;
4031
4092
  var init_message_viewer = __esm({
4032
4093
  "client/components/message-viewer.js"() {
4033
4094
  "use strict";
@@ -4111,6 +4172,8 @@ var init_message_viewer = __esm({
4111
4172
  } catch {
4112
4173
  }
4113
4174
  })();
4175
+ MANY_RECIPIENTS = 6;
4176
+ CONTEXT_MENU_ADDR_CAP = 12;
4114
4177
  PROGRESSIVE_TEXT_THRESHOLD = 200 * 1024;
4115
4178
  TEXT_CHUNK_BYTES = 64 * 1024;
4116
4179
  subscribeStore("*", (ev) => {
@@ -5566,7 +5629,13 @@ var init_message_list = __esm({
5566
5629
  if (showToInsteadOfFrom && msg.to?.length) {
5567
5630
  from.textContent = msg.to.map((a) => a.name || a.address).join(", ");
5568
5631
  } else {
5569
- from.textContent = msg.from.name || msg.from.address;
5632
+ const fromName2 = msg.from.name || msg.from.address;
5633
+ const fromClaim = displayNameClaimsOtherAddress(msg.from.name || "", msg.from.address || "");
5634
+ from.textContent = fromClaim ? `${fromName2} \u2014 actually ${msg.from.address}` : fromName2;
5635
+ if (fromClaim) {
5636
+ from.classList.add("ml-from-spoofed");
5637
+ from.title = `Display name claims ${fromClaim}, but the message is from ${msg.from.address}`;
5638
+ }
5570
5639
  }
5571
5640
  if (showAccountTag && accountId) {
5572
5641
  const tag = document.createElement("span");
@@ -5603,7 +5672,7 @@ var init_message_list = __esm({
5603
5672
  });
5604
5673
  subject.prepend(threadPill);
5605
5674
  }
5606
- if (msg.preview) {
5675
+ if (msg.preview && !isJunkPreviewText(msg.preview)) {
5607
5676
  const cleaned = msg.preview.replace(/data:image\/[a-z0-9.+-]+;base64,[A-Za-z0-9+/=\s]+/gi, "[image]").replace(/[A-Za-z0-9+/=]{200,}/g, "[image]");
5608
5677
  const preview = document.createElement("span");
5609
5678
  preview.className = "ml-preview";
@@ -9847,6 +9916,9 @@ async function openCompose(mode, overrideMsg, overrideAccountId) {
9847
9916
  }
9848
9917
  frame = showComposeOverlay(composeTitle);
9849
9918
  }
9919
+ handOffComposeInit(frame, init);
9920
+ }
9921
+ function handOffComposeInit(frame, init) {
9850
9922
  const initJson = JSON.stringify(init);
9851
9923
  try {
9852
9924
  sessionStorage.setItem("composeInit", initJson);
@@ -10578,7 +10650,7 @@ document.getElementById("rail-inbox")?.addEventListener("click", () => {
10578
10650
  inbox?.click();
10579
10651
  });
10580
10652
  document.getElementById("rail-unified")?.addEventListener("click", () => {
10581
- const unified = document.querySelector(".folder-tree .all-inboxes") || document.getElementById("ft-all-inboxes");
10653
+ const unified = document.querySelector(".folder-tree .all-inboxes");
10582
10654
  unified?.click();
10583
10655
  });
10584
10656
  document.getElementById("rail-contacts")?.addEventListener("click", async () => {
@@ -10774,8 +10846,7 @@ document.addEventListener("mailx-share-intent", ((e) => {
10774
10846
  if (Array.isArray(detail.attachments) && detail.attachments.length) {
10775
10847
  init.attachments = detail.attachments.filter((a) => a?.filename && a?.dataBase64);
10776
10848
  }
10777
- sessionStorage.setItem("composeInit", JSON.stringify(init));
10778
- showComposeOverlay(init.subject || "Compose");
10849
+ handOffComposeInit(showComposeOverlay(init.subject || "Compose"), init);
10779
10850
  }));
10780
10851
  var searchTimeout;
10781
10852
  var searchInput = document.getElementById("search-input");
@@ -11065,11 +11136,6 @@ window.addEventListener("message", (e) => {
11065
11136
  }
11066
11137
  return;
11067
11138
  }
11068
- if (e.data?.type === "openLink" && e.data.url) {
11069
- const api = window.mailxapi;
11070
- if (api?.openExternal) api.openExternal(e.data.url);
11071
- else window.open(e.data.url, "_blank", "noopener,noreferrer");
11072
- }
11073
11139
  if (e.data?.type === "linkClick" && e.data.url) {
11074
11140
  const url = e.data.url;
11075
11141
  console.log(`[android-link] parent got linkClick \u2192 ${url}`);
@@ -11088,10 +11154,6 @@ window.addEventListener("message", (e) => {
11088
11154
  }
11089
11155
  return;
11090
11156
  }
11091
- if (e.data?.type === "previewToggleFullscreen") {
11092
- toggleFullscreenPreview();
11093
- return;
11094
- }
11095
11157
  if (e.data?.type === "previewContextMenu") {
11096
11158
  let iframeRect = null;
11097
11159
  for (const f of Array.from(document.querySelectorAll("iframe"))) {
@@ -11113,84 +11175,6 @@ window.addEventListener("message", (e) => {
11113
11175
  );
11114
11176
  return;
11115
11177
  }
11116
- if (e.data?.type === "linkContextMenu") {
11117
- let iframeRect = null;
11118
- for (const f of Array.from(document.querySelectorAll("iframe"))) {
11119
- if (f.contentWindow === e.source) {
11120
- iframeRect = f.getBoundingClientRect();
11121
- break;
11122
- }
11123
- }
11124
- const x = (iframeRect?.left || 0) + (e.data.x || 0);
11125
- const y = (iframeRect?.top || 0) + (e.data.y || 0);
11126
- const url = e.data.url || "";
11127
- const guessName = (() => {
11128
- try {
11129
- const u = new URL(url);
11130
- const last = u.pathname.split("/").pop() || "";
11131
- return last && last.includes(".") ? last : "";
11132
- } catch {
11133
- return "";
11134
- }
11135
- })();
11136
- const items = [
11137
- { label: "Open in browser", action: () => {
11138
- window.open(url, "_blank", "noopener,noreferrer");
11139
- } },
11140
- { label: guessName ? `Save "${guessName}"\u2026` : "Save link as\u2026", action: () => {
11141
- const a = document.createElement("a");
11142
- a.href = url;
11143
- if (guessName) a.download = guessName;
11144
- else a.download = "";
11145
- a.style.display = "none";
11146
- document.body.appendChild(a);
11147
- a.click();
11148
- setTimeout(() => a.remove(), 1e3);
11149
- } },
11150
- { label: "Copy URL", action: async () => {
11151
- try {
11152
- await navigator.clipboard.writeText(url);
11153
- } catch {
11154
- prompt("URL:", url);
11155
- }
11156
- } },
11157
- { label: "Copy link text", action: async () => {
11158
- try {
11159
- await navigator.clipboard.writeText(e.data.text || url);
11160
- } catch {
11161
- prompt("Text:", e.data.text || url);
11162
- }
11163
- } }
11164
- ];
11165
- const menu = document.createElement("div");
11166
- menu.style.cssText = `position:fixed;z-index:2400;background:var(--color-bg);border:1px solid var(--color-border);border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,0.2);padding:4px 0;font-size:13px;min-width:180px;`;
11167
- menu.style.left = `${Math.min(x, window.innerWidth - 200)}px`;
11168
- menu.style.top = `${Math.min(y, window.innerHeight - 200)}px`;
11169
- menu.addEventListener("mousedown", (ev) => ev.stopPropagation());
11170
- for (const it of items) {
11171
- const row = document.createElement("div");
11172
- row.textContent = it.label;
11173
- row.style.cssText = `padding:6px 12px;cursor:pointer;`;
11174
- row.addEventListener("mouseenter", () => row.style.background = "var(--color-bg-hover)");
11175
- row.addEventListener("mouseleave", () => row.style.background = "");
11176
- row.addEventListener("click", () => {
11177
- menu.remove();
11178
- it.action();
11179
- });
11180
- menu.appendChild(row);
11181
- }
11182
- document.body.appendChild(menu);
11183
- const dismiss = () => {
11184
- menu.remove();
11185
- document.removeEventListener("mousedown", dismiss);
11186
- document.removeEventListener("keydown", dismiss);
11187
- };
11188
- setTimeout(() => {
11189
- document.addEventListener("mousedown", dismiss);
11190
- document.addEventListener("keydown", dismiss);
11191
- }, 0);
11192
- return;
11193
- }
11194
11178
  if (e.data?.type === "mailx-send-error") {
11195
11179
  const statusSync = document.getElementById("status-sync");
11196
11180
  if (statusSync) {
@@ -11697,11 +11681,7 @@ async function openComposeFromMailto(m) {
11697
11681
  references: m.inReplyTo ? [m.inReplyTo] : [],
11698
11682
  accounts: accounts.map((a) => ({ id: a.id, name: a.name, email: a.email, signature: a.signature, sig: a.sig }))
11699
11683
  };
11700
- sessionStorage.setItem("composeInit", JSON.stringify(init));
11701
- try {
11702
- frame?.contentWindow?.postMessage({ type: "compose-init-ready" }, "*");
11703
- } catch {
11704
- }
11684
+ handOffComposeInit(frame, init);
11705
11685
  }
11706
11686
  async function openComposeFromShare(s) {
11707
11687
  const accountsP = getAccounts();
@@ -11734,11 +11714,7 @@ async function openComposeFromShare(s) {
11734
11714
  attachments: s.attachments,
11735
11715
  accounts: accounts.map((a) => ({ id: a.id, name: a.name, email: a.email, signature: a.signature, sig: a.sig }))
11736
11716
  };
11737
- sessionStorage.setItem("composeInit", JSON.stringify(init));
11738
- try {
11739
- frame?.contentWindow?.postMessage({ type: "compose-init-ready" }, "*");
11740
- } catch {
11741
- }
11717
+ handOffComposeInit(frame, init);
11742
11718
  }
11743
11719
  window.addEventListener("keydown", (e) => {
11744
11720
  if (!e.ctrlKey || e.altKey || e.metaKey) return;
@@ -12218,9 +12194,10 @@ document.addEventListener("keydown", (e) => {
12218
12194
  bd.remove();
12219
12195
  closed = true;
12220
12196
  }
12221
- const themeSub = document.getElementById("theme-submenu");
12197
+ const themeSub = document.getElementById("theme-submenu-popout");
12222
12198
  if (themeSub && !themeSub.hidden) {
12223
12199
  themeSub.hidden = true;
12200
+ document.getElementById("theme-submenu-toggle")?.setAttribute("aria-expanded", "false");
12224
12201
  closed = true;
12225
12202
  }
12226
12203
  if (closed) {
@@ -13656,8 +13633,7 @@ document.addEventListener("mailx-popout-message", (async (e) => {
13656
13633
  }
13657
13634
  if (loaded.length) init.attachments = loaded;
13658
13635
  }
13659
- sessionStorage.setItem("composeInit", JSON.stringify(init));
13660
- showComposeOverlay(msg.subject ? `Edit: ${msg.subject}` : "Edit draft");
13636
+ handOffComposeInit(showComposeOverlay(msg.subject ? `Edit: ${msg.subject}` : "Edit draft"), init);
13661
13637
  return;
13662
13638
  }
13663
13639
  const { spawnDesktopPopout: spawnDesktopPopout2 } = await Promise.resolve().then(() => (init_message_viewer(), message_viewer_exports));