@bobfrankston/rmfmail 1.0.700 → 1.0.702

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/client/app.js CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { initFolderTree, refreshFolderTree, updateFolderCounts, setFolderSynced, getFolderSynced, setOutboxTotal } from "./components/folder-tree.js";
6
6
  import { initMessageList, loadMessages, loadUnifiedInbox, loadSearchResults, reloadCurrentFolder, clearSearchMode, getSelectedMessages, markBodiesCached, getCurrentFocused, releaseFocus, removeMessagesAndReconcile, setRowFlagged, scrollFocusedIntoView, refreshPriorityIndex } from "./components/message-list.js";
7
+ import { seenOf, flaggedOf, draftOf, setSeen, setFlagged } from "@bobfrankston/mailx-types";
7
8
  import { getCurrentMessage, initViewer, popOutCurrentMessage, toggleFullscreenPreview, showPreviewBodyMenu, wrapHtmlBody } from "./components/message-viewer.js";
8
9
  import { connectWebSocket, onWsEvent, triggerSync, syncAccount, reauthenticate, getAccounts, getFolders, deleteMessages, undeleteMessage, restartServer, getSyncPending, getVersion, getSettings, saveSettings, getAutocompleteSettings, saveAutocompleteSettings, repairAccounts, updateFlags, markAsSpamMessages, logClientEvent, sendMessage as apiSendMessage } from "./lib/api-client.js";
9
10
  import * as messageState from "./lib/message-state.js";
@@ -1324,19 +1325,18 @@ document.getElementById("btn-flag")?.addEventListener("click", async () => {
1324
1325
  const sel = getCurrentFocused();
1325
1326
  if (!sel)
1326
1327
  return;
1327
- const isFlagged = sel.flags.includes("\\Flagged");
1328
- const newFlags = isFlagged
1329
- ? sel.flags.filter((f) => f !== "\\Flagged")
1330
- : [...sel.flags, "\\Flagged"];
1328
+ const wasFlagged = flaggedOf(sel);
1329
+ setFlagged(sel, !wasFlagged);
1331
1330
  try {
1332
- await updateFlags(sel.accountId, sel.uid, newFlags);
1333
- sel.flags = newFlags;
1334
- messageState.updateMessageFlags(sel.accountId, sel.uid, newFlags);
1331
+ await updateFlags(sel.accountId, sel.uid, sel.flags);
1332
+ messageState.updateMessageFlags(sel.accountId, sel.uid, sel.flags);
1335
1333
  // Row owns its own DOM \u2014 go through the row object so class + star
1336
1334
  // update atomically and the list/preview stay in sync.
1337
- setRowFlagged(sel.accountId, sel.uid, newFlags.includes("\\Flagged"));
1335
+ setRowFlagged(sel.accountId, sel.uid, !wasFlagged);
1338
1336
  }
1339
1337
  catch (e) {
1338
+ // Revert local state on failure so visual + data stay consistent.
1339
+ setFlagged(sel, wasFlagged);
1340
1340
  console.error(`Flag toggle failed: ${e.message}`);
1341
1341
  }
1342
1342
  });
@@ -1447,17 +1447,14 @@ document.getElementById("btn-mark-unread")?.addEventListener("click", () => {
1447
1447
  const sel = getCurrentFocused();
1448
1448
  if (!sel)
1449
1449
  return;
1450
- const isSeen = sel.flags.includes("\\Seen");
1451
- const newFlags = isSeen
1452
- ? sel.flags.filter((f) => f !== "\\Seen")
1453
- : [...sel.flags, "\\Seen"];
1454
- updateFlags(sel.accountId, sel.uid, newFlags).then(() => {
1455
- sel.flags = newFlags;
1456
- messageState.updateMessageFlags(sel.accountId, sel.uid, newFlags);
1450
+ const wasSeen = seenOf(sel);
1451
+ setSeen(sel, !wasSeen);
1452
+ updateFlags(sel.accountId, sel.uid, sel.flags).then(() => {
1453
+ messageState.updateMessageFlags(sel.accountId, sel.uid, sel.flags);
1457
1454
  const row = document.querySelector(`.ml-row[data-uid="${sel.uid}"][data-account-id="${sel.accountId}"]`);
1458
1455
  if (row)
1459
- row.classList.toggle("unread", !newFlags.includes("\\Seen"));
1460
- }).catch(() => { });
1456
+ row.classList.toggle("unread", wasSeen);
1457
+ }).catch(() => { setSeen(sel, wasSeen); });
1461
1458
  });
1462
1459
  document.getElementById("btn-reply")?.addEventListener("click", () => openCompose("reply"));
1463
1460
  document.getElementById("btn-reply-all")?.addEventListener("click", () => openCompose("replyAll"));
@@ -2845,17 +2842,14 @@ document.addEventListener("keydown", (e) => {
2845
2842
  if (!sel)
2846
2843
  return;
2847
2844
  e.preventDefault();
2848
- const isSeen = sel.flags.includes("\\Seen");
2849
- const newFlags = isSeen
2850
- ? sel.flags.filter((f) => f !== "\\Seen")
2851
- : [...sel.flags, "\\Seen"];
2852
- updateFlags(sel.accountId, sel.uid, newFlags).then(() => {
2853
- sel.flags = newFlags;
2854
- messageState.updateMessageFlags(sel.accountId, sel.uid, newFlags);
2845
+ const wasSeen = seenOf(sel);
2846
+ setSeen(sel, !wasSeen);
2847
+ updateFlags(sel.accountId, sel.uid, sel.flags).then(() => {
2848
+ messageState.updateMessageFlags(sel.accountId, sel.uid, sel.flags);
2855
2849
  const row = document.querySelector(`.ml-row[data-uid="${sel.uid}"][data-account-id="${sel.accountId}"]`);
2856
2850
  if (row)
2857
- row.classList.toggle("unread", !newFlags.includes("\\Seen"));
2858
- }).catch(() => { });
2851
+ row.classList.toggle("unread", wasSeen);
2852
+ }).catch(() => { setSeen(sel, wasSeen); });
2859
2853
  }
2860
2854
  // Z = locate the focused row in the list (scroll-to-selected). After
2861
2855
  // scrolling the list out of sync with the preview, this snaps back.
@@ -4515,7 +4509,7 @@ document.addEventListener("mailx-popout-message", (async (e) => {
4515
4509
  // currently sitting in the user's Drafts folder. The read-only popout
4516
4510
  // surface is missing every action button (Reply, Forward, Edit Draft,
4517
4511
  // …) so dumping a draft into it is a worst-case dead-end.
4518
- const isDraft = Array.isArray(msg?.flags) && msg.flags.includes("\\Draft");
4512
+ const isDraft = !!msg && draftOf(msg);
4519
4513
  if (isDraft) {
4520
4514
  const accts = await getAccounts();
4521
4515
  const init = {