@bobfrankston/rmfmail 1.2.182 → 1.2.183

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/bin/mailx.ts CHANGED
@@ -2131,19 +2131,33 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
2131
2131
  }
2132
2132
  }, 5000);
2133
2133
  }
2134
+ // Open reminder popups by occurrence key, so a dismiss/snooze made on
2135
+ // ANOTHER machine (seen via the reminders.jsonc state sync) can retract
2136
+ // a popup that's already on this machine's screen. `popupKey` is
2137
+ // supplied by alarms.ts and stripped before the opts reach msger.
2138
+ const reminderPopups = new Map<string, { close: () => void }>();
2134
2139
  svc.setPopupFn(async (opts: any) => {
2135
2140
  // Own WebView2 profile so a reminder popup can never share (and
2136
2141
  // crash) the main window's browser process — see S67 note on the
2137
2142
  // popout windows below. Service-supplied opts may override.
2138
- const h = showMessageBoxEx({ profile: "popout", ...opts });
2143
+ const { popupKey, ...boxOpts } = opts || {};
2144
+ const h = showMessageBoxEx({ profile: "popout", ...boxOpts });
2139
2145
  if (typeof h.pid === "number") addChildPid(h.pid);
2146
+ if (popupKey) reminderPopups.set(popupKey, { close: () => { try { h.close(); } catch { /* already gone */ } } });
2140
2147
  try {
2141
2148
  const r = await h.result;
2142
2149
  return { button: r.button, closed: r.closed, dismissed: r.dismissed, form: (r as any).form };
2143
2150
  } finally {
2144
2151
  if (typeof h.pid === "number") removeChildPid(h.pid);
2152
+ if (popupKey) reminderPopups.delete(popupKey);
2145
2153
  }
2146
2154
  });
2155
+ svc.setPopupCloserFn((key: string) => {
2156
+ const p = reminderPopups.get(key);
2157
+ if (!p) return false;
2158
+ p.close();
2159
+ return true;
2160
+ });
2147
2161
 
2148
2162
  // Optional debug HTTP server ALONGSIDE the GUI. `--server` mode replaces
2149
2163
  // the WebView with Express; `--debug-server` instead runs a loopback
@@ -29,6 +29,7 @@ __export(api_client_exports, {
29
29
  cancelServerSearch: () => cancelServerSearch,
30
30
  checkDraftNewer: () => checkDraftNewer,
31
31
  closeComposePopout: () => closeComposePopout,
32
+ closeReminderPopup: () => closeReminderPopup,
32
33
  closeWordEdit: () => closeWordEdit,
33
34
  connectEvents: () => connectEvents,
34
35
  connectWebSocket: () => connectWebSocket,
@@ -621,6 +622,9 @@ function closeWordEdit(editId) {
621
622
  function showReminderPopup(opts) {
622
623
  return ipc().showReminderPopup?.(opts) ?? Promise.resolve({ button: "", reason: "no host" });
623
624
  }
625
+ function closeReminderPopup(key) {
626
+ return ipc().closeReminderPopup?.(key) ?? Promise.resolve({ ok: false });
627
+ }
624
628
  function mergeReminderState(patch) {
625
629
  return ipc().mergeReminderState?.(patch) ?? Promise.resolve(null);
626
630
  }
@@ -6153,6 +6157,7 @@ async function syncReminderState(force = false) {
6153
6157
  sn[k] = v;
6154
6158
  }
6155
6159
  saveSnoozed(sn);
6160
+ retractSuppressedPopups();
6156
6161
  return true;
6157
6162
  } catch (e) {
6158
6163
  alog("state sync failed", { err: e?.message || String(e) });
@@ -6249,10 +6254,28 @@ function formatWhen(ms) {
6249
6254
  const d = new Date(ms);
6250
6255
  return d.toLocaleString(void 0, { weekday: "short", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
6251
6256
  }
6257
+ function retractSuppressedPopups() {
6258
+ if (openPopups.size === 0)
6259
+ return;
6260
+ const now = Date.now();
6261
+ const dismissed = loadDismissed();
6262
+ const snoozed = loadSnoozed();
6263
+ for (const [key, entry] of openPopups) {
6264
+ if (entry.retracted)
6265
+ continue;
6266
+ if (dismissed[key] || (snoozed[key] || 0) > now) {
6267
+ alog("retracting popup \u2014 resolved on another machine", { key, dismissed: !!dismissed[key], snoozedUntil: snoozed[key] || 0 });
6268
+ try {
6269
+ entry.retract();
6270
+ } catch {
6271
+ }
6272
+ }
6273
+ }
6274
+ }
6252
6275
  function escapeHtml6(s) {
6253
6276
  return s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
6254
6277
  }
6255
- function showInWebViewPopup(opts) {
6278
+ function showInWebViewPopup(opts, onRegisterClose) {
6256
6279
  return new Promise((resolve) => {
6257
6280
  const overlay = document.createElement("div");
6258
6281
  overlay.className = "alarm-overlay";
@@ -6294,12 +6317,15 @@ function showInWebViewPopup(opts) {
6294
6317
  panel.querySelectorAll("[data-button]").forEach((btn) => {
6295
6318
  btn.addEventListener("click", () => cleanup(btn.dataset.button || ""));
6296
6319
  });
6320
+ onRegisterClose?.(cleanup);
6297
6321
  });
6298
6322
  }
6299
6323
  async function firePopupForItem(item) {
6300
6324
  if (openPopups.has(item.uuid))
6301
6325
  return;
6302
- openPopups.add(item.uuid);
6326
+ const popupEntry = { retracted: false, retract: () => {
6327
+ } };
6328
+ openPopups.set(item.uuid, popupEntry);
6303
6329
  const openUrl = item.kind === "calendar" ? item.htmlLink || "https://calendar.google.com/" : "https://tasks.google.com/";
6304
6330
  const buttons = ["snooze", "Dismiss", "Open"];
6305
6331
  if (item.kind === "calendar")
@@ -6401,17 +6427,28 @@ async function firePopupForItem(item) {
6401
6427
  let r;
6402
6428
  try {
6403
6429
  if (hasHost) {
6430
+ popupEntry.retract = () => {
6431
+ popupEntry.retracted = true;
6432
+ closeReminderPopup(item.uuid).catch(() => {
6433
+ });
6434
+ };
6404
6435
  r = await showReminderPopup({
6405
6436
  title,
6406
6437
  html,
6407
6438
  buttons,
6439
+ popupKey: item.uuid,
6408
6440
  // Sized for the new snooze form (chips row + custom input
6409
6441
  // row + action buttons) plus title/when/kind. Width fits
6410
6442
  // "5m 15m 30m 1h 2h 1d" on one line in Segoe UI.
6411
6443
  size: { width: 560, height: 300 }
6412
6444
  });
6413
6445
  } else {
6414
- r = await showInWebViewPopup({ title, html, buttons });
6446
+ r = await showInWebViewPopup({ title, html, buttons }, (close) => {
6447
+ popupEntry.retract = () => {
6448
+ popupEntry.retracted = true;
6449
+ close("retracted");
6450
+ };
6451
+ });
6415
6452
  }
6416
6453
  } catch (e) {
6417
6454
  alog("popup IPC failed \u2014 treating as Dismiss", { key: item.uuid, err: e?.message || String(e) });
@@ -6419,6 +6456,11 @@ async function firePopupForItem(item) {
6419
6456
  } finally {
6420
6457
  openPopups.delete(item.uuid);
6421
6458
  }
6459
+ if (popupEntry.retracted) {
6460
+ alog("popup closed by retraction \u2014 skipping result handling", { key: item.uuid });
6461
+ firedThisSession.delete(item.uuid);
6462
+ return;
6463
+ }
6422
6464
  if (!r || typeof r !== "object" || typeof r.button !== "string") {
6423
6465
  alog("popup returned empty / malformed \u2014 treating as Dismiss", { key: item.uuid, raw: r });
6424
6466
  r = { button: "Dismiss" };
@@ -6570,7 +6612,7 @@ var init_alarms = __esm({
6570
6612
  _lastStateSync = 0;
6571
6613
  STATE_SYNC_MIN_MS = 6e4;
6572
6614
  firedThisSession = /* @__PURE__ */ new Set();
6573
- openPopups = /* @__PURE__ */ new Set();
6615
+ openPopups = /* @__PURE__ */ new Map();
6574
6616
  }
6575
6617
  });
6576
6618