@bobfrankston/rmfmail 1.0.703 → 1.0.705

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 (35) hide show
  1. package/bin/build-bundles.mjs +52 -24
  2. package/client/android-bootstrap.bundle.js +8188 -0
  3. package/client/android-bootstrap.bundle.js.map +7 -0
  4. package/client/app.bundle.js +122 -10
  5. package/client/app.bundle.js.map +2 -2
  6. package/client/app.js +46 -2
  7. package/client/app.js.map +1 -1
  8. package/client/app.ts +46 -3
  9. package/client/components/alarms.js +37 -7
  10. package/client/components/alarms.js.map +1 -1
  11. package/client/components/alarms.ts +32 -7
  12. package/client/components/message-list.js +48 -2
  13. package/client/components/message-list.js.map +1 -1
  14. package/client/components/message-list.ts +44 -2
  15. package/client/components/message-viewer.js +27 -0
  16. package/client/components/message-viewer.js.map +1 -1
  17. package/client/components/message-viewer.ts +23 -0
  18. package/client/compose/compose.bundle.js +16 -0
  19. package/client/compose/compose.bundle.js.map +2 -2
  20. package/client/compose/compose.js +37 -0
  21. package/client/compose/compose.js.map +1 -1
  22. package/client/compose/compose.ts +42 -0
  23. package/client/index.html +9 -1
  24. package/docs/accounts.md +1 -1
  25. package/package.json +1 -1
  26. package/packages/mailx-service/index.d.ts.map +1 -1
  27. package/packages/mailx-service/index.js +14 -0
  28. package/packages/mailx-service/index.js.map +1 -1
  29. package/packages/mailx-service/index.ts +15 -0
  30. package/packages/mailx-service/local-store.d.ts.map +1 -1
  31. package/packages/mailx-service/local-store.js +9 -22
  32. package/packages/mailx-service/local-store.js.map +1 -1
  33. package/packages/mailx-service/local-store.ts +9 -23
  34. package/packages/mailx-settings/docs/accounts.md +1 -1
  35. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-4104 → node_modules.npmglobalize-stash-34984}/.package-lock.json +0 -0
@@ -2113,6 +2113,26 @@ var init_message_viewer = __esm({
2113
2113
  else
2114
2114
  bodyEl.prepend(banner);
2115
2115
  });
2116
+ onEvent((ev) => {
2117
+ if (!ev || ev.type !== "draftSaved")
2118
+ return;
2119
+ if (!currentMessage || currentAccountId !== ev.accountId)
2120
+ return;
2121
+ if (currentMessage.uid !== ev.previousDraftUid)
2122
+ return;
2123
+ const newBody = String(ev.bodyHtml || "");
2124
+ currentMessage.bodyHtml = newBody;
2125
+ if (ev.subject)
2126
+ currentMessage.subject = ev.subject;
2127
+ const bodyEl = document.getElementById("mv-body");
2128
+ const iframe = bodyEl?.querySelector("iframe");
2129
+ if (iframe) {
2130
+ iframe.srcdoc = wrapHtmlBody(newBody, !!currentMessage.remoteAllowed);
2131
+ }
2132
+ const subjEl = document.querySelector(".mv-subject");
2133
+ if (subjEl && ev.subject)
2134
+ subjEl.textContent = String(ev.subject);
2135
+ });
2116
2136
  }
2117
2137
  });
2118
2138
 
@@ -3151,6 +3171,15 @@ var init_message_list = __esm({
3151
3171
  markBodyCached() {
3152
3172
  this.el.classList.remove("not-downloaded");
3153
3173
  }
3174
+ /** Update the row's date column. Used by the draft-saved listener so
3175
+ * Ctrl+S in compose bumps the time in the Drafts list immediately
3176
+ * rather than waiting for the next IMAP APPEND + sync round-trip. */
3177
+ setDate(epochMs) {
3178
+ this.msg.date = epochMs;
3179
+ const el = this.el.querySelector(".ml-date");
3180
+ if (el)
3181
+ el.textContent = formatDate(epochMs);
3182
+ }
3154
3183
  // Visual-state accessors for flag toggles. Read the *visual* state
3155
3184
  // (CSS class), not `this.msg.flags`, because the flag array can lag
3156
3185
  // the rendered class — auto-mark-as-read removes the `unread` class
@@ -3394,6 +3423,27 @@ var init_message_list = __esm({
3394
3423
  },
3395
3424
  { label: "", action: () => {
3396
3425
  }, separator: true },
3426
+ // Drafts get an explicit "Edit draft" entry as the primary action —
3427
+ // double-click already opens the row in compose, but the context
3428
+ // menu had no equivalent (Bob 2026-05-13). Detect via \Draft flag
3429
+ // or the Drafts folder context. Edit triggers the same path as
3430
+ // double-click: dispatching popout-message lets app.ts's draft
3431
+ // detection route through to compose.
3432
+ ...(() => {
3433
+ const isDraftRow = draftOf(msg) || currentSpecialUse.toLowerCase() === "drafts";
3434
+ if (!isDraftRow)
3435
+ return [];
3436
+ return [
3437
+ {
3438
+ label: "Edit draft",
3439
+ action: () => document.dispatchEvent(new CustomEvent("mailx-popout-message", {
3440
+ detail: { accountId, uid: msg.uid, folderId: msg.folderId, subject: msg.subject }
3441
+ }))
3442
+ },
3443
+ { label: "", action: () => {
3444
+ }, separator: true }
3445
+ ];
3446
+ })(),
3397
3447
  { label: "Reply", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "reply" } })) },
3398
3448
  { label: "Reply All", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "replyAll" } })) },
3399
3449
  { label: "Forward", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "forward" } })) },
@@ -3444,6 +3494,15 @@ var init_message_list = __esm({
3444
3494
  showContextMenu(e.clientX, e.clientY, items);
3445
3495
  }
3446
3496
  };
3497
+ onEvent((ev) => {
3498
+ if (!ev || ev.type !== "draftSaved")
3499
+ return;
3500
+ if (!ev.accountId || ev.previousDraftUid == null)
3501
+ return;
3502
+ const row = rowByKey.get(rowKey(ev.accountId, ev.previousDraftUid));
3503
+ if (row)
3504
+ row.setDate(typeof ev.savedAt === "number" ? ev.savedAt : Date.now());
3505
+ });
3447
3506
  }
3448
3507
  });
3449
3508
 
@@ -4519,23 +4578,43 @@ async function collectDueAlarms(now) {
4519
4578
  if (!Array.isArray(ev.reminderMinutes) || ev.reminderMinutes.length === 0)
4520
4579
  continue;
4521
4580
  const offsets = ev.reminderMinutes.map((m) => m * 6e4);
4581
+ const occBaseKey = occKey(ev.uuid, startMs);
4582
+ let pick = null;
4583
+ const eligibleOffsets = [];
4522
4584
  for (const offsetMs of offsets) {
4523
- const occBaseKey = occKey(ev.uuid, startMs);
4524
4585
  const key = `${occBaseKey}@${offsetMs}`;
4525
4586
  if (dismissed[key])
4526
4587
  continue;
4527
4588
  const alarm = startMs - offsetMs;
4528
4589
  const effective = snoozed[key] || alarm;
4529
4590
  if (effective <= now && effective > now - LOOKBACK_MS) {
4530
- items.push({
4531
- uuid: key,
4532
- kind: "calendar",
4533
- title: ev.title || "(no title)",
4534
- alarmMs: effective,
4535
- whenMs: startMs,
4536
- htmlLink: ev.htmlLink
4537
- });
4591
+ eligibleOffsets.push(offsetMs);
4592
+ if (!pick || effective > pick.effective) {
4593
+ pick = { offsetMs, effective, key };
4594
+ }
4595
+ }
4596
+ }
4597
+ if (pick) {
4598
+ items.push({
4599
+ uuid: pick.key,
4600
+ kind: "calendar",
4601
+ title: ev.title || "(no title)",
4602
+ alarmMs: pick.effective,
4603
+ whenMs: startMs,
4604
+ htmlLink: ev.htmlLink
4605
+ });
4606
+ let touched = false;
4607
+ for (const offsetMs of eligibleOffsets) {
4608
+ if (offsetMs === pick.offsetMs)
4609
+ continue;
4610
+ const k = `${occBaseKey}@${offsetMs}`;
4611
+ if (!dismissed[k]) {
4612
+ dismissed[k] = true;
4613
+ touched = true;
4614
+ }
4538
4615
  }
4616
+ if (touched)
4617
+ saveDismissed(dismissed);
4539
4618
  }
4540
4619
  }
4541
4620
  } catch {
@@ -6411,7 +6490,13 @@ async function openCompose(mode) {
6411
6490
  const account = accounts.find((a) => a.id === accountId);
6412
6491
  const explicitDomains = (account?.identityDomains || []).map((d) => d.toLowerCase());
6413
6492
  const accountDomain = (account?.email || "").split("@")[1]?.toLowerCase();
6414
- const identityDomains = explicitDomains.length > 0 ? explicitDomains : accountDomain ? [accountDomain] : [];
6493
+ function foldDomains(list) {
6494
+ const unique = Array.from(new Set(list));
6495
+ return unique.filter((d) => !unique.some((p) => p !== d && d.endsWith(`.${p}`)));
6496
+ }
6497
+ const identityDomains = foldDomains(
6498
+ explicitDomains.length > 0 ? explicitDomains : accountDomain ? [accountDomain] : []
6499
+ );
6415
6500
  function detectReplyFrom() {
6416
6501
  if (!msg) return void 0;
6417
6502
  if (msg.deliveredTo) {
@@ -6433,6 +6518,33 @@ async function openCompose(mode) {
6433
6518
  console.log(`[compose] no identity match`);
6434
6519
  return void 0;
6435
6520
  }
6521
+ if (msg) {
6522
+ try {
6523
+ const dump = {
6524
+ mode,
6525
+ accountId,
6526
+ hasMsg: true,
6527
+ from: msg.from || null,
6528
+ toLen: Array.isArray(msg.to) ? msg.to.length : -1,
6529
+ ccLen: Array.isArray(msg.cc) ? msg.cc.length : -1,
6530
+ deliveredTo: msg.deliveredTo || "",
6531
+ identityDomains,
6532
+ subject: msg.subject || ""
6533
+ };
6534
+ const apiClient = window.mailxapi;
6535
+ if (apiClient?.logClientEvent) {
6536
+ apiClient.logClientEvent("reply-init", dump);
6537
+ } else {
6538
+ console.log("[reply-init]", dump);
6539
+ }
6540
+ } catch {
6541
+ }
6542
+ } else {
6543
+ try {
6544
+ window.mailxapi?.logClientEvent?.("reply-init", { mode, accountId, hasMsg: false });
6545
+ } catch {
6546
+ }
6547
+ }
6436
6548
  if (msg && mode === "reply") {
6437
6549
  init.to = msg.from ? [msg.from] : [];
6438
6550
  init.subject = `Re: ${cleanSubject}`;