@bobfrankston/rmfmail 1.0.701 → 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,7 +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 { FLAG, flagOps } from "@bobfrankston/mailx-types";
7
+ import { seenOf, flaggedOf, draftOf, setSeen, setFlagged } from "@bobfrankston/mailx-types";
8
8
  import { getCurrentMessage, initViewer, popOutCurrentMessage, toggleFullscreenPreview, showPreviewBodyMenu, wrapHtmlBody } from "./components/message-viewer.js";
9
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";
10
10
  import * as messageState from "./lib/message-state.js";
@@ -1325,19 +1325,18 @@ document.getElementById("btn-flag")?.addEventListener("click", async () => {
1325
1325
  const sel = getCurrentFocused();
1326
1326
  if (!sel)
1327
1327
  return;
1328
- const isFlagged = flagOps.isFlagged(sel.flags);
1329
- const newFlags = isFlagged
1330
- ? flagOps.remove(sel.flags, FLAG.FLAGGED)
1331
- : flagOps.add(sel.flags, FLAG.FLAGGED);
1328
+ const wasFlagged = flaggedOf(sel);
1329
+ setFlagged(sel, !wasFlagged);
1332
1330
  try {
1333
- await updateFlags(sel.accountId, sel.uid, newFlags);
1334
- sel.flags = newFlags;
1335
- 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);
1336
1333
  // Row owns its own DOM \u2014 go through the row object so class + star
1337
1334
  // update atomically and the list/preview stay in sync.
1338
- setRowFlagged(sel.accountId, sel.uid, flagOps.isFlagged(newFlags));
1335
+ setRowFlagged(sel.accountId, sel.uid, !wasFlagged);
1339
1336
  }
1340
1337
  catch (e) {
1338
+ // Revert local state on failure so visual + data stay consistent.
1339
+ setFlagged(sel, wasFlagged);
1341
1340
  console.error(`Flag toggle failed: ${e.message}`);
1342
1341
  }
1343
1342
  });
@@ -1448,15 +1447,14 @@ document.getElementById("btn-mark-unread")?.addEventListener("click", () => {
1448
1447
  const sel = getCurrentFocused();
1449
1448
  if (!sel)
1450
1449
  return;
1451
- const isSeen = flagOps.isSeen(sel.flags);
1452
- const newFlags = flagOps.set(sel.flags, FLAG.SEEN, !isSeen);
1453
- updateFlags(sel.accountId, sel.uid, newFlags).then(() => {
1454
- sel.flags = newFlags;
1455
- 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);
1456
1454
  const row = document.querySelector(`.ml-row[data-uid="${sel.uid}"][data-account-id="${sel.accountId}"]`);
1457
1455
  if (row)
1458
- row.classList.toggle("unread", !flagOps.isSeen(newFlags));
1459
- }).catch(() => { });
1456
+ row.classList.toggle("unread", wasSeen);
1457
+ }).catch(() => { setSeen(sel, wasSeen); });
1460
1458
  });
1461
1459
  document.getElementById("btn-reply")?.addEventListener("click", () => openCompose("reply"));
1462
1460
  document.getElementById("btn-reply-all")?.addEventListener("click", () => openCompose("replyAll"));
@@ -2844,15 +2842,14 @@ document.addEventListener("keydown", (e) => {
2844
2842
  if (!sel)
2845
2843
  return;
2846
2844
  e.preventDefault();
2847
- const isSeen = flagOps.isSeen(sel.flags);
2848
- const newFlags = flagOps.set(sel.flags, FLAG.SEEN, !isSeen);
2849
- updateFlags(sel.accountId, sel.uid, newFlags).then(() => {
2850
- sel.flags = newFlags;
2851
- 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);
2852
2849
  const row = document.querySelector(`.ml-row[data-uid="${sel.uid}"][data-account-id="${sel.accountId}"]`);
2853
2850
  if (row)
2854
- row.classList.toggle("unread", !flagOps.isSeen(newFlags));
2855
- }).catch(() => { });
2851
+ row.classList.toggle("unread", wasSeen);
2852
+ }).catch(() => { setSeen(sel, wasSeen); });
2856
2853
  }
2857
2854
  // Z = locate the focused row in the list (scroll-to-selected). After
2858
2855
  // scrolling the list out of sync with the preview, this snaps back.
@@ -4512,7 +4509,7 @@ document.addEventListener("mailx-popout-message", (async (e) => {
4512
4509
  // currently sitting in the user's Drafts folder. The read-only popout
4513
4510
  // surface is missing every action button (Reply, Forward, Edit Draft,
4514
4511
  // …) so dumping a draft into it is a worst-case dead-end.
4515
- const isDraft = Array.isArray(msg?.flags) && flagOps.isDraft(msg.flags);
4512
+ const isDraft = !!msg && draftOf(msg);
4516
4513
  if (isDraft) {
4517
4514
  const accts = await getAccounts();
4518
4515
  const init = {