@bobfrankston/rmfmail 1.2.272 → 1.2.276

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 (63) hide show
  1. package/.commitmsg +26 -0
  2. package/client/android-bootstrap.bundle.js +184 -0
  3. package/client/android-bootstrap.bundle.js.map +2 -2
  4. package/client/app.bundle.js +235 -31
  5. package/client/app.bundle.js.map +4 -4
  6. package/client/app.js +35 -1
  7. package/client/app.js.map +1 -1
  8. package/client/app.ts +36 -1
  9. package/client/components/invite-card.js +107 -0
  10. package/client/components/invite-card.js.map +1 -0
  11. package/client/components/invite-card.ts +119 -0
  12. package/client/components/message-list.js +9 -1
  13. package/client/components/message-list.js.map +1 -1
  14. package/client/components/message-list.ts +9 -1
  15. package/client/components/message-viewer.js +82 -19
  16. package/client/components/message-viewer.js.map +1 -1
  17. package/client/components/message-viewer.ts +79 -18
  18. package/client/compose/compose.bundle.js +4 -0
  19. package/client/compose/compose.bundle.js.map +2 -2
  20. package/client/lib/api-client.js +6 -0
  21. package/client/lib/api-client.js.map +1 -1
  22. package/client/lib/api-client.ts +6 -0
  23. package/client/lib/approved-senders.js +66 -0
  24. package/client/lib/approved-senders.js.map +1 -0
  25. package/client/lib/approved-senders.ts +63 -0
  26. package/client/lib/mailxapi.js +3 -0
  27. package/client/package.json +1 -1
  28. package/client/styles/components.css +48 -0
  29. package/package.json +10 -10
  30. package/packages/mailx-imap/package-lock.json +2 -2
  31. package/packages/mailx-imap/package.json +2 -2
  32. package/packages/mailx-service/index.d.ts +15 -0
  33. package/packages/mailx-service/index.d.ts.map +1 -1
  34. package/packages/mailx-service/index.js +18 -0
  35. package/packages/mailx-service/index.js.map +1 -1
  36. package/packages/mailx-service/index.ts +19 -0
  37. package/packages/mailx-service/jsonrpc.js +2 -0
  38. package/packages/mailx-service/jsonrpc.js.map +1 -1
  39. package/packages/mailx-service/jsonrpc.ts +2 -0
  40. package/packages/mailx-service/package.json +1 -1
  41. package/packages/mailx-settings/package.json +1 -1
  42. package/packages/mailx-store/package.json +1 -1
  43. package/packages/mailx-store/store.d.ts +5 -0
  44. package/packages/mailx-store/store.d.ts.map +1 -1
  45. package/packages/mailx-store/store.js +21 -0
  46. package/packages/mailx-store/store.js.map +1 -1
  47. package/packages/mailx-store/store.ts +27 -0
  48. package/packages/mailx-store-web/android-bootstrap.js +1 -0
  49. package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
  50. package/packages/mailx-store-web/android-bootstrap.ts +1 -0
  51. package/packages/mailx-store-web/package.json +1 -1
  52. package/packages/mailx-store-web/web-service.d.ts +11 -0
  53. package/packages/mailx-store-web/web-service.d.ts.map +1 -1
  54. package/packages/mailx-store-web/web-service.js +14 -0
  55. package/packages/mailx-store-web/web-service.js.map +1 -1
  56. package/packages/mailx-store-web/web-service.ts +15 -0
  57. package/packages/mailx-types/index.d.ts +56 -0
  58. package/packages/mailx-types/index.d.ts.map +1 -1
  59. package/packages/mailx-types/index.js +187 -0
  60. package/packages/mailx-types/index.js.map +1 -1
  61. package/packages/mailx-types/index.ts +192 -0
  62. package/packages/mailx-types/package.json +1 -1
  63. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-72180 → node_modules.npmglobalize-stash-59216}/.package-lock.json +0 -0
@@ -56,6 +56,7 @@ __export(api_client_exports, {
56
56
  flagSenderOrDomain: () => flagSenderOrDomain,
57
57
  formatJsonc: () => formatJsonc,
58
58
  getAccounts: () => getAccounts,
59
+ getAllowlist: () => getAllowlist,
59
60
  getAttachment: () => getAttachment,
60
61
  getAutocompleteSettings: () => getAutocompleteSettings,
61
62
  getCalendarEvents: () => getCalendarEvents,
@@ -350,6 +351,9 @@ function addUserDictWords(words) {
350
351
  function removeUserDictWord(word) {
351
352
  return ipc().removeUserDictWord?.(word) ?? Promise.resolve([]);
352
353
  }
354
+ function getAllowlist() {
355
+ return ipc().getAllowlist?.() ?? Promise.resolve({ senders: [], domains: [], recipients: [], flaggedSenders: [], flaggedDomains: [] });
356
+ }
353
357
  function flagSenderOrDomain(type, value) {
354
358
  return ipc().flagSenderOrDomain?.(type, value) ?? Promise.resolve({ flagged: false });
355
359
  }
@@ -1310,6 +1314,61 @@ var init_message_state = __esm({
1310
1314
  }
1311
1315
  });
1312
1316
 
1317
+ // client/lib/approved-senders.js
1318
+ var approved_senders_exports = {};
1319
+ __export(approved_senders_exports, {
1320
+ approvedCounts: () => approvedCounts,
1321
+ isSenderApproved: () => isSenderApproved,
1322
+ noteApprovedLocally: () => noteApprovedLocally,
1323
+ refreshApprovedSenders: () => refreshApprovedSenders
1324
+ });
1325
+ async function refreshApprovedSenders() {
1326
+ try {
1327
+ const list = await getAllowlist();
1328
+ senders = new Set((list.senders || []).map(norm));
1329
+ domains = new Set((list.domains || []).map(norm));
1330
+ loaded = true;
1331
+ } catch (e) {
1332
+ console.warn("[approved-senders] refresh failed:", e?.message || e);
1333
+ }
1334
+ }
1335
+ function isSenderApproved(address) {
1336
+ if (!loaded)
1337
+ return false;
1338
+ const a = norm(address);
1339
+ if (!a)
1340
+ return false;
1341
+ if (senders.has(a))
1342
+ return true;
1343
+ const d = domainOf(a);
1344
+ return !!d && domains.has(d);
1345
+ }
1346
+ function noteApprovedLocally(type, value) {
1347
+ const v = norm(value);
1348
+ if (!v)
1349
+ return;
1350
+ if (type === "sender")
1351
+ senders.add(v);
1352
+ else
1353
+ domains.add(v);
1354
+ loaded = true;
1355
+ }
1356
+ function approvedCounts() {
1357
+ return { senders: senders.size, domains: domains.size, loaded };
1358
+ }
1359
+ var senders, domains, loaded, norm, domainOf;
1360
+ var init_approved_senders = __esm({
1361
+ "client/lib/approved-senders.js"() {
1362
+ "use strict";
1363
+ init_api_client();
1364
+ senders = /* @__PURE__ */ new Set();
1365
+ domains = /* @__PURE__ */ new Set();
1366
+ loaded = false;
1367
+ norm = (s) => (s || "").trim().toLowerCase();
1368
+ domainOf = (addr) => norm(addr).split("@").pop() || "";
1369
+ }
1370
+ });
1371
+
1313
1372
  // packages/mailx-types/contact-rules.js
1314
1373
  var init_contact_rules = __esm({
1315
1374
  "packages/mailx-types/contact-rules.js"() {
@@ -1408,6 +1467,11 @@ function formatSender(addr) {
1408
1467
  return { text: address, claimed: null };
1409
1468
  return { text: `${name} <${address}>`, claimed: displayNameClaimsOtherAddress(name, address) };
1410
1469
  }
1470
+ function inviteHasGoogleRsvp(bodyHtml) {
1471
+ if (!bodyHtml)
1472
+ return false;
1473
+ return /calendar\.google\.com\/calendar\/event\?action=RESPOND/i.test(bodyHtml);
1474
+ }
1411
1475
  var _SEEN, _FLAGGED, _DRAFT, seenOf, flaggedOf, draftOf, setSeen, setFlagged;
1412
1476
  var init_mailx_types = __esm({
1413
1477
  "packages/mailx-types/index.js"() {
@@ -1429,6 +1493,100 @@ var init_mailx_types = __esm({
1429
1493
  }
1430
1494
  });
1431
1495
 
1496
+ // client/components/invite-card.js
1497
+ function formatWhen(inv) {
1498
+ if (!inv.start)
1499
+ return "";
1500
+ if (inv.allDay) {
1501
+ try {
1502
+ const d = /* @__PURE__ */ new Date(inv.start + "T00:00:00");
1503
+ return d.toLocaleDateString(void 0, { weekday: "long", year: "numeric", month: "long", day: "numeric" }) + " (all day)";
1504
+ } catch {
1505
+ return inv.start + " (all day)";
1506
+ }
1507
+ }
1508
+ const instant = (() => {
1509
+ if (/Z$/.test(inv.start))
1510
+ return new Date(inv.start);
1511
+ if (!inv.tzid)
1512
+ return new Date(inv.start);
1513
+ try {
1514
+ const guess = /* @__PURE__ */ new Date(inv.start + "Z");
1515
+ const asZone = new Date(guess.toLocaleString("en-US", { timeZone: inv.tzid }));
1516
+ const asUtc = new Date(guess.toLocaleString("en-US", { timeZone: "UTC" }));
1517
+ return new Date(guess.getTime() + (asUtc.getTime() - asZone.getTime()));
1518
+ } catch {
1519
+ return new Date(inv.start);
1520
+ }
1521
+ })();
1522
+ if (isNaN(instant.getTime()))
1523
+ return inv.start;
1524
+ const localZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
1525
+ const opts = {
1526
+ weekday: "short",
1527
+ month: "short",
1528
+ day: "numeric",
1529
+ hour: "numeric",
1530
+ minute: "2-digit"
1531
+ };
1532
+ let out = instant.toLocaleString(void 0, opts);
1533
+ if (inv.end && !/Z$/.test(inv.end)) {
1534
+ try {
1535
+ const endInstant = new Date(instant.getTime() + ((/* @__PURE__ */ new Date(inv.end + "Z")).getTime() - (/* @__PURE__ */ new Date(inv.start + "Z")).getTime()));
1536
+ out += " \u2013 " + endInstant.toLocaleString(void 0, { hour: "numeric", minute: "2-digit" });
1537
+ } catch {
1538
+ }
1539
+ }
1540
+ if (inv.tzid && inv.tzid !== localZone) {
1541
+ out += ` (sent as ${inv.start.slice(11, 16)} ${inv.tzid})`;
1542
+ }
1543
+ return out;
1544
+ }
1545
+ function buildInviteCard(inv, bodyHtml) {
1546
+ if (!inv || !inv.summary && !inv.start)
1547
+ return null;
1548
+ const card = document.createElement("div");
1549
+ card.className = "mv-invite";
1550
+ if (inv.method === "CANCEL")
1551
+ card.classList.add("mv-invite-cancelled");
1552
+ const when = formatWhen(inv);
1553
+ const isMeetLink = /^https?:/;
1554
+ const googleHandles = inv.fromGoogle && inviteHasGoogleRsvp(bodyHtml);
1555
+ const rows = [];
1556
+ if (when)
1557
+ rows.push(`<div class="mv-invite-when">${esc(when)}</div>`);
1558
+ if (inv.location) {
1559
+ rows.push(isMeetLink ? `<div class="mv-invite-row"><span class="mv-invite-label">Where</span><a href="${esc(inv.location)}" target="_blank" rel="noopener noreferrer">${esc(inv.location)}</a></div>` : `<div class="mv-invite-row"><span class="mv-invite-label">Where</span>${esc(inv.location)}</div>`);
1560
+ }
1561
+ if (inv.organizer.email) {
1562
+ rows.push(`<div class="mv-invite-row"><span class="mv-invite-label">Organizer</span>${esc(inv.organizer.name)}${inv.organizer.name !== inv.organizer.email ? " &lt;" + esc(inv.organizer.email) + "&gt;" : ""}</div>`);
1563
+ }
1564
+ if (inv.attendees.length) {
1565
+ const who = inv.attendees.map((a) => `${esc(a.name || a.email)} <span class="mv-invite-partstat">(${esc(PARTSTAT_LABEL[a.status] || a.status.toLowerCase())})</span>`).join(", ");
1566
+ rows.push(`<div class="mv-invite-row"><span class="mv-invite-label">Guests</span>${who}</div>`);
1567
+ }
1568
+ if (inv.rrule)
1569
+ rows.push(`<div class="mv-invite-row"><span class="mv-invite-label">Repeats</span><code>${esc(inv.rrule)}</code></div>`);
1570
+ const badge = inv.method === "CANCEL" ? "Cancelled" : inv.method === "REPLY" ? "Reply" : inv.status === "TENTATIVE" ? "Tentative" : "Invitation";
1571
+ card.innerHTML = `<div class="mv-invite-head"><span class="mv-invite-badge">${esc(badge)}</span><span class="mv-invite-summary">${esc(inv.summary || "(no title)")}</span></div>` + rows.join("") + (googleHandles ? `<div class="mv-invite-note">Respond with the Yes / No / Maybe buttons in the message below \u2014 they go straight to Google Calendar.</div>` : "");
1572
+ return card;
1573
+ }
1574
+ var PARTSTAT_LABEL, esc;
1575
+ var init_invite_card = __esm({
1576
+ "client/components/invite-card.js"() {
1577
+ "use strict";
1578
+ init_mailx_types();
1579
+ PARTSTAT_LABEL = {
1580
+ "ACCEPTED": "accepted",
1581
+ "DECLINED": "declined",
1582
+ "TENTATIVE": "maybe",
1583
+ "NEEDS-ACTION": "no reply yet",
1584
+ "DELEGATED": "delegated"
1585
+ };
1586
+ esc = (s) => (s || "").replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
1587
+ }
1588
+ });
1589
+
1432
1590
  // client/components/address-book.js
1433
1591
  var address_book_exports = {};
1434
1592
  __export(address_book_exports, {
@@ -2302,19 +2460,6 @@ function installPreviewControls(iframe) {
2302
2460
  }
2303
2461
  if (e.ctrlKey && !e.altKey && !e.metaKey && (e.key === "a" || e.key === "A"))
2304
2462
  return;
2305
- const synth = new KeyboardEvent("keydown", {
2306
- key: e.key,
2307
- code: e.code,
2308
- ctrlKey: e.ctrlKey,
2309
- shiftKey: e.shiftKey,
2310
- altKey: e.altKey,
2311
- metaKey: e.metaKey,
2312
- bubbles: true,
2313
- cancelable: true
2314
- });
2315
- const allowDefault = document.dispatchEvent(synth);
2316
- if (!allowDefault)
2317
- e.preventDefault();
2318
2463
  });
2319
2464
  doc.addEventListener("wheel", (e) => {
2320
2465
  if (!e.ctrlKey)
@@ -2351,6 +2496,7 @@ function clearViewer() {
2351
2496
  headerEl.hidden = true;
2352
2497
  if (attEl)
2353
2498
  attEl.hidden = true;
2499
+ document.getElementById("mv-invite")?.remove();
2354
2500
  setTimeout(() => {
2355
2501
  try {
2356
2502
  const stillPlaceholder = !!document.querySelector("#mv-body .mv-empty");
@@ -2610,6 +2756,16 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
2610
2756
  fromEl.textContent = formatAddr(msg.from);
2611
2757
  setRecipientLine(toEl, msg.to, msg.cc, msg.deliveredTo);
2612
2758
  headerEl.querySelector(".mv-subject").textContent = msg.subject;
2759
+ document.getElementById("mv-invite")?.remove();
2760
+ try {
2761
+ const card = buildInviteCard(msg.invite, String(msg.bodyHtml || ""));
2762
+ if (card) {
2763
+ card.id = "mv-invite";
2764
+ headerEl.insertAdjacentElement("afterend", card);
2765
+ }
2766
+ } catch (e) {
2767
+ console.warn("[viewer] invite card failed:", e?.message || e);
2768
+ }
2613
2769
  document.dispatchEvent(new CustomEvent("mailx-message-shown", { detail: { accountId } }));
2614
2770
  for (const el of [fromEl, toEl]) {
2615
2771
  el.addEventListener("contextmenu", (e) => {
@@ -2693,6 +2849,36 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
2693
2849
  items.push({ label: "", action: () => {
2694
2850
  }, separator: true });
2695
2851
  }
2852
+ if (el === fromEl && msg.from?.address) {
2853
+ const addr = String(msg.from.address);
2854
+ const dom = addr.split("@").pop() || "";
2855
+ const claimed = displayNameClaimsOtherAddress(String(msg.from.name || ""), addr);
2856
+ if (claimed && !isSenderApproved(addr)) {
2857
+ const approve = async (type, value) => {
2858
+ noteApprovedLocally(type, value);
2859
+ try {
2860
+ await allowRemoteContent(type, value);
2861
+ await refreshApprovedSenders();
2862
+ document.dispatchEvent(new CustomEvent("mailx-allowlist-changed"));
2863
+ const st = document.getElementById("status-sync");
2864
+ if (st)
2865
+ st.textContent = `Marked valid: ${value}`;
2866
+ } catch (e2) {
2867
+ const st = document.getElementById("status-sync");
2868
+ if (st) {
2869
+ st.textContent = `Couldn't save "${value}" to the allowlist \u2014 ${e2?.message || e2}`;
2870
+ st.style.color = "oklch(0.65 0.2 25)";
2871
+ }
2872
+ console.error("[allowlist] approve failed:", e2);
2873
+ }
2874
+ };
2875
+ items.push({ label: `\u2713 Mark ${addr} as a valid sender`, action: () => void approve("sender", addr) });
2876
+ if (dom)
2877
+ items.push({ label: `\u2713 Trust all senders at ${dom}`, action: () => void approve("domain", dom) });
2878
+ items.push({ label: "", action: () => {
2879
+ }, separator: true });
2880
+ }
2881
+ }
2696
2882
  if (omitted > 0) {
2697
2883
  items.push({
2698
2884
  label: `\u2026 ${omitted} more recipients \u2014 copy the whole list`,
@@ -3282,7 +3468,7 @@ ${escapeText(reputationBlind)}"` : ""}>${reputationText}</div>` : "") + `<div cl
3282
3468
  }
3283
3469
  function formatAddr(addr) {
3284
3470
  const { text, claimed } = formatSender(addr);
3285
- if (!claimed)
3471
+ if (!claimed || isSenderApproved(addr.address))
3286
3472
  return text;
3287
3473
  return `${text} \u2014 display name claims ${claimed}`;
3288
3474
  }
@@ -3891,11 +4077,11 @@ function buildPrintableDoc(m) {
3891
4077
  if (!m)
3892
4078
  return null;
3893
4079
  const fmt = (a) => a?.name ? `${a.name} <${a.address || ""}>` : a?.address || "";
3894
- const esc = escapeHtmlLocal;
3895
- const row = (label, value) => value ? `<div><strong>${label}:</strong> ${esc(value)}</div>` : "";
4080
+ const esc2 = escapeHtmlLocal;
4081
+ const row = (label, value) => value ? `<div><strong>${label}:</strong> ${esc2(value)}</div>` : "";
3896
4082
  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>`;
3897
- 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>`;
3898
- 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>`;
4083
+ const bodyHtml = m.bodyHtml ? m.bodyHtml : `<pre style="white-space:pre-wrap;word-break:break-word;font-family:system-ui,sans-serif;">${esc2(m.bodyText || "")}</pre>`;
4084
+ return `<!DOCTYPE html><html><head><meta charset="utf-8"><title>${esc2(m.subject || "Message")}</title><style>@media print{body{margin:0;}}body{margin:16px;}img{max-width:100%;}</style></head><body>${headerHtml}${bodyHtml}</body></html>`;
3899
4085
  }
3900
4086
  function printCurrentMessage() {
3901
4087
  if (!currentMessage)
@@ -4107,6 +4293,8 @@ var init_message_viewer = __esm({
4107
4293
  "client/components/message-viewer.js"() {
4108
4294
  "use strict";
4109
4295
  init_api_client();
4296
+ init_approved_senders();
4297
+ init_invite_card();
4110
4298
  init_context_menu();
4111
4299
  init_message_state();
4112
4300
  init_message_list();
@@ -5530,6 +5718,7 @@ var init_message_list = __esm({
5530
5718
  init_folder_picker();
5531
5719
  init_fold_text();
5532
5720
  init_mailx_types();
5721
+ init_approved_senders();
5533
5722
  currentSpecialUse = "";
5534
5723
  lastClickedRow = null;
5535
5724
  loading = false;
@@ -5644,7 +5833,7 @@ var init_message_list = __esm({
5644
5833
  from.textContent = msg.to.map((a) => a.name || a.address).join(", ");
5645
5834
  } else {
5646
5835
  const fromName2 = msg.from.name || msg.from.address;
5647
- const fromClaim = displayNameClaimsOtherAddress(msg.from.name || "", msg.from.address || "");
5836
+ const fromClaim = isSenderApproved(msg.from.address || "") ? null : displayNameClaimsOtherAddress(msg.from.name || "", msg.from.address || "");
5648
5837
  from.textContent = fromClaim ? `${fromName2} \u2014 actually ${msg.from.address}` : fromName2;
5649
5838
  if (fromClaim) {
5650
5839
  from.classList.add("ml-from-spoofed");
@@ -7295,7 +7484,7 @@ async function collectDueAlarms(now) {
7295
7484
  }
7296
7485
  return items;
7297
7486
  }
7298
- function formatWhen(ms) {
7487
+ function formatWhen2(ms) {
7299
7488
  if (!ms)
7300
7489
  return "";
7301
7490
  const d = new Date(ms);
@@ -7418,7 +7607,7 @@ async function firePopupForItem(item) {
7418
7607
  .actions button.action[data-btn="Delete"]:hover { background: #800; }
7419
7608
  </style></head><body>
7420
7609
  <div class="title"><span class="icon">${icon}</span>${escapeHtml7(item.title)}</div>
7421
- <div class="when">${escapeHtml7(formatWhen(item.whenMs))}</div>
7610
+ <div class="when">${escapeHtml7(formatWhen2(item.whenMs))}</div>
7422
7611
  <div class="kind">${kindLabel}</div>
7423
7612
  <div class="row">
7424
7613
  <span class="row-label">Snooze:</span>
@@ -9170,6 +9359,7 @@ window.__btick && window.__btick("app.ts module body executing");
9170
9359
  var baseTitle = APP_NAME;
9171
9360
  var lastSeenCount = 0;
9172
9361
  var badgeCount = 0;
9362
+ var lastOverlayCount = -1;
9173
9363
  function updateBadge(count) {
9174
9364
  badgeCount = count;
9175
9365
  document.title = count > 0 ? `(${count}) ${baseTitle}` : baseTitle;
@@ -9206,7 +9396,8 @@ function updateBadge(count) {
9206
9396
  link.href = dataUrl;
9207
9397
  try {
9208
9398
  const hostApi = window.mailxapi;
9209
- if (hostApi?.setTaskbarOverlay) {
9399
+ if (hostApi?.setTaskbarOverlay && count !== lastOverlayCount) {
9400
+ lastOverlayCount = count;
9210
9401
  if (count > 0) {
9211
9402
  const bc = document.createElement("canvas");
9212
9403
  bc.width = 32;
@@ -9902,12 +10093,12 @@ async function openCompose(mode, overrideMsg, overrideAccountId) {
9902
10093
  }
9903
10094
  if (msg && (mode === "editAsNew" || mode === "forward") && Array.isArray(msg.attachments) && msg.attachments.length) {
9904
10095
  const msgAccountId = current?.accountId || accountId;
9905
- const loaded = [];
10096
+ const loaded2 = [];
9906
10097
  for (const att of msg.attachments) {
9907
10098
  try {
9908
10099
  const data = await getAttachment(msgAccountId, msg.uid, att.id, msg.folderId);
9909
10100
  if (data?.content) {
9910
- loaded.push({
10101
+ loaded2.push({
9911
10102
  filename: data.filename || att.filename || "attachment",
9912
10103
  mimeType: data.contentType || att.contentType || "application/octet-stream",
9913
10104
  dataBase64: data.content
@@ -9917,7 +10108,7 @@ async function openCompose(mode, overrideMsg, overrideAccountId) {
9917
10108
  console.error(`[compose] ${mode}: failed to load attachment ${att.id}: ${e?.message || e}`);
9918
10109
  }
9919
10110
  }
9920
- if (loaded.length) init.attachments = loaded;
10111
+ if (loaded2.length) init.attachments = loaded2;
9921
10112
  }
9922
10113
  if (preferWindow && !frame) {
9923
10114
  try {
@@ -12379,6 +12570,19 @@ function applyThreadFilter() {
12379
12570
  console.error("pending mailto pickup failed:", e?.message || e);
12380
12571
  }
12381
12572
  })();
12573
+ (async () => {
12574
+ try {
12575
+ const { refreshApprovedSenders: refreshApprovedSenders2 } = await Promise.resolve().then(() => (init_approved_senders(), approved_senders_exports));
12576
+ await refreshApprovedSenders2();
12577
+ document.dispatchEvent(new CustomEvent("mailx-allowlist-changed"));
12578
+ } catch (e) {
12579
+ console.warn("approved-sender index load failed:", e?.message || e);
12580
+ }
12581
+ })();
12582
+ document.addEventListener("mailx-allowlist-changed", () => {
12583
+ void Promise.resolve().then(() => (init_message_list(), message_list_exports)).then((m) => m.reloadCurrentFolder?.()).catch(() => {
12584
+ });
12585
+ });
12382
12586
  (async () => {
12383
12587
  try {
12384
12588
  const { consumePendingShare: consumePendingShare2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
@@ -12902,11 +13106,11 @@ function offsetToLineCol(src, pos) {
12902
13106
  return { pos, line, col };
12903
13107
  }
12904
13108
  function renderMarkdown(md) {
12905
- const esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
13109
+ const esc2 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
12906
13110
  const blocks = [];
12907
13111
  let src = md.replace(/```(\w*)\n([\s\S]*?)```/g, (_m, _lang, code) => {
12908
13112
  const i = blocks.length;
12909
- blocks.push(`<pre class="mailx-help-code"><code>${esc(code)}</code></pre>`);
13113
+ blocks.push(`<pre class="mailx-help-code"><code>${esc2(code)}</code></pre>`);
12910
13114
  return `\0BLOCK${i}\0`;
12911
13115
  });
12912
13116
  const lines = src.split(/\r?\n/);
@@ -12926,7 +13130,7 @@ function renderMarkdown(md) {
12926
13130
  }
12927
13131
  };
12928
13132
  function inline(s) {
12929
- s = esc(s);
13133
+ s = esc2(s);
12930
13134
  s = s.replace(/`([^`]+)`/g, (_m, c) => `<code>${c}</code>`);
12931
13135
  s = s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
12932
13136
  s = s.replace(/(^|[^*])\*([^*\n]+)\*/g, "$1<em>$2</em>");
@@ -13630,12 +13834,12 @@ document.addEventListener("mailx-popout-message", (async (e) => {
13630
13834
  draftId: msg.mailxDraftId || ""
13631
13835
  };
13632
13836
  if (Array.isArray(msg.attachments) && msg.attachments.length) {
13633
- const loaded = [];
13837
+ const loaded2 = [];
13634
13838
  for (const att of msg.attachments) {
13635
13839
  try {
13636
13840
  const data = await getAttachment(accountId, msg.uid, att.id, msg.folderId);
13637
13841
  if (data?.content) {
13638
- loaded.push({
13842
+ loaded2.push({
13639
13843
  filename: data.filename || att.filename || "attachment",
13640
13844
  mimeType: data.contentType || att.contentType || "application/octet-stream",
13641
13845
  dataBase64: data.content
@@ -13645,7 +13849,7 @@ document.addEventListener("mailx-popout-message", (async (e) => {
13645
13849
  console.error(`[compose] edit draft: failed to load attachment ${att.id} ("${att.filename}"): ${e2?.message || e2}`);
13646
13850
  }
13647
13851
  }
13648
- if (loaded.length) init.attachments = loaded;
13852
+ if (loaded2.length) init.attachments = loaded2;
13649
13853
  }
13650
13854
  handOffComposeInit(showComposeOverlay(msg.subject ? `Edit: ${msg.subject}` : "Edit draft"), init);
13651
13855
  return;