@bobfrankston/rmfmail 1.1.131 → 1.1.133

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.
@@ -3057,10 +3057,19 @@ async function loadMessages(accountId, folderId, page = 1, specialUse = "", auto
3057
3057
  if (result.items.length === 0) {
3058
3058
  setMessages([]);
3059
3059
  body.innerHTML = `<div class="ml-empty">${flaggedOnly ? "No flagged messages" : "No messages"}</div>`;
3060
+ clearViewer();
3061
+ focusedRow = null;
3060
3062
  return;
3061
3063
  }
3062
3064
  setMessages(result.items);
3063
3065
  renderMessages(body, accountId, result.items);
3066
+ if (focusedRow) {
3067
+ const stillThere = result.items.some((m) => m.accountId === focusedRow.accountId && m.uid === focusedRow.msg.uid);
3068
+ if (!stillThere) {
3069
+ clearViewer();
3070
+ focusedRow = null;
3071
+ }
3072
+ }
3064
3073
  const targetUuid = remembered ? pickRestoreUid(result.items, remembered) : null;
3065
3074
  if (targetUuid) {
3066
3075
  requestAnimationFrame(() => {
@@ -7487,9 +7496,21 @@ function forwardBody(msg) {
7487
7496
  const body = sanitizeQuotedBody(msg);
7488
7497
  return `<p></p><br><br><div class="reply"><p>---------- Forwarded message ----------<br>From: ${from}<br>Date: ${date}<br>Subject: ${msg.subject}<br>To: ${to}</p>${body}</div>`;
7489
7498
  }
7490
- var lastDeleted = null;
7491
- var lastMoved = null;
7492
- var undoTimeout = null;
7499
+ var undoStack = [];
7500
+ var UNDO_MAX = 50;
7501
+ var UNDO_TTL_MS = 10 * 6e4;
7502
+ function pushUndo(op) {
7503
+ undoStack.push(op);
7504
+ if (undoStack.length > UNDO_MAX) undoStack.shift();
7505
+ }
7506
+ function popUndo() {
7507
+ const now = Date.now();
7508
+ while (undoStack.length > 0) {
7509
+ const op = undoStack.pop();
7510
+ if (now - op.at < UNDO_TTL_MS) return op;
7511
+ }
7512
+ return null;
7513
+ }
7493
7514
  async function deleteSelection() {
7494
7515
  try {
7495
7516
  const sidebar = await Promise.resolve().then(() => (init_calendar_sidebar(), calendar_sidebar_exports));
@@ -7512,17 +7533,11 @@ async function deleteSelectedMessages() {
7512
7533
  const snapshot = [...selected];
7513
7534
  removeMessagesAndReconcile(selected);
7514
7535
  if (snapshot.length === 1) {
7515
- lastDeleted = { ...snapshot[0], subject: "" };
7536
+ pushUndo({ kind: "delete", at: Date.now(), payload: { ...snapshot[0], subject: "" } });
7516
7537
  if (statusSync) statusSync.textContent = `Trashed 1 message (syncing) \u2014 Ctrl+Z to undo`;
7517
7538
  } else {
7518
- lastDeleted = null;
7519
7539
  if (statusSync) statusSync.textContent = `Trashed ${snapshot.length} messages (syncing)`;
7520
7540
  }
7521
- if (undoTimeout) clearTimeout(undoTimeout);
7522
- undoTimeout = setTimeout(() => {
7523
- lastDeleted = null;
7524
- if (statusSync?.textContent?.includes("undo")) statusSync.textContent = "";
7525
- }, 3e4);
7526
7541
  const byAccount = /* @__PURE__ */ new Map();
7527
7542
  for (const msg of snapshot) {
7528
7543
  const uids = byAccount.get(msg.accountId) || [];
@@ -7536,52 +7551,48 @@ async function deleteSelectedMessages() {
7536
7551
  });
7537
7552
  }
7538
7553
  }
7539
- async function undoDelete() {
7540
- if (!lastDeleted) return;
7541
- const { accountId, uid, folderId } = lastDeleted;
7542
- try {
7543
- await undeleteMessage(accountId, uid, folderId);
7544
- const statusSync = document.getElementById("status-sync");
7545
- if (statusSync) statusSync.textContent = "Message restored";
7546
- lastDeleted = null;
7547
- if (undoTimeout) clearTimeout(undoTimeout);
7548
- reloadCurrentFolder();
7549
- } catch (e) {
7550
- console.error(`Undo failed: ${e.message}`);
7551
- }
7552
- }
7553
- async function undoMove() {
7554
- if (!lastMoved) return;
7555
- const { messages: messages2 } = lastMoved;
7554
+ async function performUndo() {
7555
+ const op = popUndo();
7556
+ if (!op) return;
7556
7557
  const statusSync = document.getElementById("status-sync");
7557
7558
  try {
7558
- const byDest = /* @__PURE__ */ new Map();
7559
- for (const m of messages2) {
7560
- const key = `${m.accountId}:${m.sourceFolderId}`;
7561
- if (!byDest.has(key)) byDest.set(key, { accountId: m.accountId, folderId: m.sourceFolderId, uids: [] });
7562
- byDest.get(key).uids.push(m.uid);
7563
- }
7564
- const { moveMessages: moveMessages2, moveMessage: moveMessage2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
7565
- for (const group of byDest.values()) {
7566
- if (group.uids.length === 1) await moveMessage2(group.accountId, group.uids[0], group.folderId);
7567
- else await moveMessages2(group.accountId, group.uids, group.folderId);
7568
- }
7569
- if (statusSync) statusSync.textContent = `Undid move of ${messages2.length} message${messages2.length !== 1 ? "s" : ""}`;
7570
- lastMoved = null;
7571
- if (undoTimeout) clearTimeout(undoTimeout);
7559
+ if (op.kind === "delete") {
7560
+ const { accountId, uid, folderId } = op.payload;
7561
+ await undeleteMessage(accountId, uid, folderId);
7562
+ if (statusSync) statusSync.textContent = "Message restored";
7563
+ } else if (op.kind === "move") {
7564
+ const { messages: messages2 } = op.payload;
7565
+ const byDest = /* @__PURE__ */ new Map();
7566
+ for (const m of messages2) {
7567
+ const key = `${m.accountId}:${m.sourceFolderId}`;
7568
+ if (!byDest.has(key)) byDest.set(key, { accountId: m.accountId, folderId: m.sourceFolderId, uids: [] });
7569
+ byDest.get(key).uids.push(m.uid);
7570
+ }
7571
+ const { moveMessages: moveMessages2, moveMessage: moveMessage2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
7572
+ for (const group of byDest.values()) {
7573
+ if (group.uids.length === 1) await moveMessage2(group.accountId, group.uids[0], group.folderId);
7574
+ else await moveMessages2(group.accountId, group.uids, group.folderId);
7575
+ }
7576
+ if (statusSync) statusSync.textContent = `Undid move of ${messages2.length} message${messages2.length !== 1 ? "s" : ""}`;
7577
+ } else if (op.kind === "flag") {
7578
+ const { updateFlags: updateFlags2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
7579
+ for (const entry of op.payload) {
7580
+ const flag = "\\Flagged";
7581
+ const flagsAfter = entry.prevFlagged ? [flag] : [];
7582
+ await updateFlags2(entry.accountId, entry.uid, flagsAfter);
7583
+ setRowFlagged(entry.accountId, entry.uid, entry.prevFlagged);
7584
+ updateMessageFlags(entry.accountId, entry.uid, flagsAfter);
7585
+ }
7586
+ if (statusSync) statusSync.textContent = `Undid flag change on ${op.payload.length} message${op.payload.length !== 1 ? "s" : ""}`;
7587
+ }
7572
7588
  reloadCurrentFolder();
7573
7589
  } catch (e) {
7574
- console.error(`Undo move failed: ${e.message}`);
7575
- if (statusSync) statusSync.textContent = `Undo move failed: ${e.message}`;
7590
+ console.error(`Undo failed: ${e.message}`);
7591
+ if (statusSync) statusSync.textContent = `Undo failed: ${e.message}`;
7576
7592
  }
7577
7593
  }
7578
7594
  document.addEventListener("mailx-moved", (e) => {
7579
- lastMoved = e.detail;
7580
- lastDeleted = null;
7581
- if (undoTimeout) clearTimeout(undoTimeout);
7582
- undoTimeout = setTimeout(() => {
7583
- lastMoved = null;
7584
- }, 6e4);
7595
+ pushUndo({ kind: "move", at: Date.now(), payload: e.detail });
7585
7596
  });
7586
7597
  document.getElementById("btn-delete")?.addEventListener("click", deleteSelection);
7587
7598
  document.getElementById("btn-tb-delete")?.addEventListener("click", deleteSelection);
@@ -7595,6 +7606,46 @@ function updateFlagButton() {
7595
7606
  btn.textContent = yes ? "\u2605" : "\u2606";
7596
7607
  }
7597
7608
  document.addEventListener("mailx-message-shown", updateFlagButton);
7609
+ async function bulkFlagSelected() {
7610
+ const selected = getSelectedMessages();
7611
+ if (selected.length === 0) {
7612
+ const cur = getCurrentMessage();
7613
+ if (!cur) return;
7614
+ selected.push({ accountId: cur.accountId, uid: cur.message.uid, folderId: cur.message.folderId });
7615
+ }
7616
+ const statusSync = document.getElementById("status-sync");
7617
+ const messages2 = getMessages2();
7618
+ const byKey = /* @__PURE__ */ new Map();
7619
+ for (const m of messages2) byKey.set(`${m.accountId}:${m.uid}`, m);
7620
+ const rows = selected.map((s) => byKey.get(`${s.accountId}:${s.uid}`)).filter(Boolean);
7621
+ if (rows.length === 0) return;
7622
+ const anyUnflagged = rows.some((r) => !flaggedOf(r));
7623
+ const targetFlagged = anyUnflagged;
7624
+ const undoPayload = rows.map((r) => ({
7625
+ accountId: r.accountId,
7626
+ uid: r.uid,
7627
+ prevFlagged: flaggedOf(r)
7628
+ }));
7629
+ const { updateFlags: updateFlags2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
7630
+ for (const r of rows) {
7631
+ if (flaggedOf(r) === targetFlagged) continue;
7632
+ setFlagged(r, targetFlagged);
7633
+ setRowFlagged(r.accountId, r.uid, targetFlagged);
7634
+ updateMessageFlags(r.accountId, r.uid, r.flags);
7635
+ try {
7636
+ await updateFlags2(r.accountId, r.uid, r.flags);
7637
+ } catch (e) {
7638
+ console.error(`Bulk flag failed for ${r.accountId}/${r.uid}: ${e?.message || e}`);
7639
+ }
7640
+ }
7641
+ pushUndo({ kind: "flag", at: Date.now(), payload: undoPayload });
7642
+ updateFlagButton();
7643
+ if (statusSync) {
7644
+ const verb = targetFlagged ? "Flagged" : "Unflagged";
7645
+ statusSync.textContent = `${verb} ${rows.length} message${rows.length !== 1 ? "s" : ""} \u2014 Ctrl+Z to undo`;
7646
+ }
7647
+ }
7648
+ document.getElementById("btn-tb-flag")?.addEventListener("click", bulkFlagSelected);
7598
7649
  document.getElementById("btn-flag")?.addEventListener("click", async () => {
7599
7650
  const sel = getCurrentFocused();
7600
7651
  if (!sel) return;
@@ -8814,12 +8865,9 @@ document.addEventListener("keydown", (e) => {
8814
8865
  deleteSelection();
8815
8866
  }
8816
8867
  if (e.ctrlKey && e.key === "z") {
8817
- if (lastMoved) {
8818
- e.preventDefault();
8819
- undoMove();
8820
- } else if (lastDeleted) {
8868
+ if (undoStack.length > 0) {
8821
8869
  e.preventDefault();
8822
- undoDelete();
8870
+ performUndo();
8823
8871
  }
8824
8872
  }
8825
8873
  if (e.key === "F5") {
@@ -9885,13 +9933,28 @@ optThreaded?.addEventListener("change", () => {
9885
9933
  localStorage.setItem("mailx-threaded", String(optThreaded.checked));
9886
9934
  reloadCurrentFolder();
9887
9935
  });
9888
- optFlagged?.addEventListener("change", () => {
9936
+ function syncFilterFlaggedButton() {
9937
+ const btn = document.getElementById("btn-filter-flagged");
9938
+ if (!btn) return;
9939
+ btn.classList.toggle("active", !!optFlagged?.checked);
9940
+ btn.setAttribute("aria-pressed", optFlagged?.checked ? "true" : "false");
9941
+ }
9942
+ function applyFlaggedFilter(on) {
9943
+ if (optFlagged) optFlagged.checked = on;
9889
9944
  const body = document.getElementById("ml-body");
9890
- if (optFlagged.checked) body?.classList.add("flagged-only");
9945
+ if (on) body?.classList.add("flagged-only");
9891
9946
  else body?.classList.remove("flagged-only");
9892
- localStorage.setItem("mailx-flagged", String(optFlagged.checked));
9947
+ localStorage.setItem("mailx-flagged", String(on));
9948
+ syncFilterFlaggedButton();
9893
9949
  reloadCurrentFolder();
9950
+ }
9951
+ optFlagged?.addEventListener("change", () => {
9952
+ applyFlaggedFilter(!!optFlagged.checked);
9953
+ });
9954
+ document.getElementById("btn-filter-flagged")?.addEventListener("click", () => {
9955
+ applyFlaggedFilter(!optFlagged?.checked);
9894
9956
  });
9957
+ syncFilterFlaggedButton();
9895
9958
  var optPriorityOnly = document.getElementById("opt-priority-only");
9896
9959
  if (optPriorityOnly) {
9897
9960
  optPriorityOnly.checked = localStorage.getItem("mailx-priority-only") === "true";