@bobfrankston/rmfmail 1.2.223 → 1.2.225

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 (64) hide show
  1. package/client/android-bootstrap.bundle.js +53 -6
  2. package/client/android-bootstrap.bundle.js.map +2 -2
  3. package/client/app.bundle.js +145 -62
  4. package/client/app.bundle.js.map +2 -2
  5. package/client/app.js +24 -10
  6. package/client/app.js.map +1 -1
  7. package/client/app.ts +21 -10
  8. package/client/components/address-book.js +93 -29
  9. package/client/components/address-book.js.map +1 -1
  10. package/client/components/address-book.ts +103 -24
  11. package/client/components/message-list.js +56 -15
  12. package/client/components/message-list.js.map +1 -1
  13. package/client/components/message-list.ts +55 -16
  14. package/client/components/message-viewer.js +2 -2
  15. package/client/components/message-viewer.js.map +1 -1
  16. package/client/components/message-viewer.ts +2 -2
  17. package/client/compose/compose.bundle.js +2 -2
  18. package/client/compose/compose.bundle.js.map +2 -2
  19. package/client/lib/api-client.js +2 -2
  20. package/client/lib/api-client.js.map +1 -1
  21. package/client/lib/api-client.ts +2 -2
  22. package/client/lib/mailxapi.js +2 -2
  23. package/client/lib/message-state.js +15 -4
  24. package/client/lib/message-state.js.map +1 -1
  25. package/client/lib/message-state.ts +12 -3
  26. package/client/styles/components.css +40 -0
  27. package/package.json +5 -5
  28. package/packages/mailx-imap/index.d.ts.map +1 -1
  29. package/packages/mailx-imap/index.js +32 -4
  30. package/packages/mailx-imap/index.js.map +1 -1
  31. package/packages/mailx-imap/index.ts +34 -5
  32. package/packages/mailx-imap/package-lock.json +2 -2
  33. package/packages/mailx-imap/package.json +1 -1
  34. package/packages/mailx-service/index.d.ts +1 -1
  35. package/packages/mailx-service/index.d.ts.map +1 -1
  36. package/packages/mailx-service/index.js +33 -4
  37. package/packages/mailx-service/index.js.map +1 -1
  38. package/packages/mailx-service/index.ts +28 -4
  39. package/packages/mailx-service/jsonrpc.js +1 -1
  40. package/packages/mailx-service/jsonrpc.js.map +1 -1
  41. package/packages/mailx-service/jsonrpc.ts +1 -1
  42. package/packages/mailx-service/package.json +1 -1
  43. package/packages/mailx-store/db.d.ts +30 -10
  44. package/packages/mailx-store/db.d.ts.map +1 -1
  45. package/packages/mailx-store/db.js +108 -10
  46. package/packages/mailx-store/db.js.map +1 -1
  47. package/packages/mailx-store/db.ts +126 -13
  48. package/packages/mailx-store/package.json +1 -1
  49. package/packages/mailx-store-web/android-bootstrap.d.ts.map +1 -1
  50. package/packages/mailx-store-web/android-bootstrap.js +17 -2
  51. package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
  52. package/packages/mailx-store-web/android-bootstrap.ts +17 -2
  53. package/packages/mailx-store-web/db.d.ts +1 -1
  54. package/packages/mailx-store-web/db.d.ts.map +1 -1
  55. package/packages/mailx-store-web/db.js +17 -2
  56. package/packages/mailx-store-web/db.js.map +1 -1
  57. package/packages/mailx-store-web/db.ts +18 -3
  58. package/packages/mailx-store-web/package.json +1 -1
  59. package/packages/mailx-store-web/web-service.d.ts +10 -1
  60. package/packages/mailx-store-web/web-service.d.ts.map +1 -1
  61. package/packages/mailx-store-web/web-service.js +16 -1
  62. package/packages/mailx-store-web/web-service.js.map +1 -1
  63. package/packages/mailx-store-web/web-service.ts +15 -1
  64. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-65000 → node_modules.npmglobalize-stash-67672}/.package-lock.json +0 -0
@@ -306,8 +306,8 @@ function hasBccHistoryTo(email) {
306
306
  function listContacts(query, page = 1, pageSize = 100) {
307
307
  return ipc().listContacts(query, page, pageSize);
308
308
  }
309
- function upsertContact(name, email) {
310
- return ipc().upsertContact(name, email);
309
+ function upsertContact(name, email, fields) {
310
+ return ipc().upsertContact(name, email, fields);
311
311
  }
312
312
  function deleteContact(email) {
313
313
  return ipc().deleteContact(email);
@@ -970,10 +970,15 @@ function removeMessages(uids, currentlyFocused) {
970
970
  nextSurvivor: nextSurvivor ? { accountId: nextSurvivor.accountId, uid: nextSurvivor.uid, folderId: nextSurvivor.folderId } : null
971
971
  };
972
972
  }
973
- function updateMessageFlags(accountId, uid, flags) {
974
- const msg = messages.find((m) => m.uid === uid && m.accountId === accountId);
975
- if (msg)
976
- msg.flags = flags;
973
+ function updateMessageFlags(accountId, uid, flags, folderId) {
974
+ const candidates = messages.filter((m) => m.uid === uid && m.accountId === accountId && (folderId === void 0 || m.folderId === folderId));
975
+ if (candidates.length === 1) {
976
+ candidates[0].flags = flags;
977
+ return;
978
+ }
979
+ if (candidates.length > 1) {
980
+ console.warn(`[message-state] ambiguous flag update ${accountId}/uid=${uid} \u2014 ${candidates.length} folders match; caller must pass folderId`);
981
+ }
977
982
  }
978
983
  var messages, listeners;
979
984
  var init_message_state = __esm({
@@ -1127,13 +1132,13 @@ async function openAddressBook(prefillSearch) {
1127
1132
  <span class="ab-actions"></span>
1128
1133
  </div>` + items.map((c) => `
1129
1134
  <div class="ab-row" data-email="${escapeAttr(c.email)}">
1130
- <span class="ab-name">${escapeHtml(c.name || "")}</span>
1135
+ <span class="ab-name" title="${escapeAttr(cardSummary(c))}">${escapeHtml(c.name || "")}${cardSummary(c) ? ' <span class="ab-hascard" aria-hidden="true">\u2022</span>' : ""}</span>
1131
1136
  <span class="ab-email">${escapeHtml(c.email)}</span>
1132
1137
  <span class="ab-source">${escapeHtml(c.source)}</span>
1133
1138
  <span class="ab-count-cell">${c.useCount || 0}</span>
1134
1139
  <span class="ab-last">${fmtDate(c.lastUsed)}</span>
1135
1140
  <span class="ab-actions">
1136
- <button type="button" class="ab-edit" title="Edit name">\u270E</button>
1141
+ <button type="button" class="ab-edit" title="Edit contact">\u270E</button>
1137
1142
  <button type="button" class="ab-del" title="Delete">\u{1F5D1}</button>
1138
1143
  <button type="button" class="ab-mail" title="Compose to">\u2709</button>
1139
1144
  </span>
@@ -1146,38 +1151,76 @@ async function openAddressBook(prefillSearch) {
1146
1151
  row.querySelector(".ab-mail")?.addEventListener("click", () => composeTo(c));
1147
1152
  });
1148
1153
  };
1149
- const beginEdit = (row, c) => {
1154
+ const beginEdit = (_row, c) => {
1150
1155
  if (editingEmail === c.email)
1151
1156
  return;
1152
1157
  editingEmail = c.email;
1153
- const nameSpan = row.querySelector(".ab-name");
1154
- const original = c.name || "";
1155
- nameSpan.innerHTML = `<input type="text" value="${escapeAttr(original)}" class="ab-name-input">`;
1156
- const inp = nameSpan.querySelector("input");
1157
- inp.focus();
1158
- inp.select();
1159
- const commit = async () => {
1160
- const newName = inp.value.trim();
1161
- if (newName !== original) {
1162
- try {
1163
- await upsertContact(newName, c.email);
1164
- c.name = newName;
1165
- } catch (e) {
1166
- alert(`Update failed: ${e?.message || e}`);
1167
- }
1168
- }
1169
- nameSpan.textContent = c.name || "";
1158
+ const back = document.createElement("div");
1159
+ back.className = "mailx-modal-backdrop";
1160
+ const card = document.createElement("div");
1161
+ card.className = "mailx-modal";
1162
+ card.innerHTML = `
1163
+ <div class="mailx-modal-title">
1164
+ <span class="mailx-modal-title-text">Edit contact</span>
1165
+ <button type="button" class="mailx-modal-close" data-role="x" title="Close (Esc)" aria-label="Close">&times;</button>
1166
+ </div>
1167
+ <div class="ab-card">
1168
+ <label class="ab-field"><span>Name</span>
1169
+ <input type="text" class="mailx-modal-input" data-field="name" value="${escapeAttr(c.name || "")}"></label>
1170
+ <label class="ab-field"><span>Email</span>
1171
+ <input type="email" class="mailx-modal-input" value="${escapeAttr(c.email)}" disabled
1172
+ title="The address identifies the contact \u2014 delete and re-add to change it"></label>
1173
+ ${CONTACT_FIELDS.map((f) => `
1174
+ <label class="ab-field"><span>${escapeHtml(f.label)}</span>
1175
+ ${f.multiline ? `<textarea class="mailx-modal-input" rows="2" data-field="${f.key}">${escapeHtml(c[f.key] || "")}</textarea>` : `<input type="${f.type || "text"}" class="mailx-modal-input" data-field="${f.key}"
1176
+ value="${escapeAttr(c[f.key] || "")}"
1177
+ ${f.placeholder ? `placeholder="${escapeAttr(f.placeholder)}"` : ""}>`}
1178
+ </label>`).join("")}
1179
+ </div>
1180
+ <div class="mailx-modal-buttons">
1181
+ <span class="mailx-modal-spacer"></span>
1182
+ <button type="button" class="mailx-modal-btn" data-role="cancel">Cancel</button>
1183
+ <button type="button" class="mailx-modal-btn mailx-modal-btn-primary" data-role="save">Save</button>
1184
+ </div>`;
1185
+ back.appendChild(card);
1186
+ document.body.appendChild(back);
1187
+ card.querySelector('[data-field="name"]')?.focus();
1188
+ const shut = () => {
1189
+ back.remove();
1190
+ document.removeEventListener("keydown", onCardKey, true);
1170
1191
  editingEmail = null;
1171
1192
  };
1172
- inp.addEventListener("blur", commit);
1173
- inp.addEventListener("keydown", (e) => {
1174
- if (e.key === "Enter") {
1175
- e.preventDefault();
1176
- inp.blur();
1177
- } else if (e.key === "Escape") {
1193
+ const onCardKey = (e) => {
1194
+ if (e.key === "Escape") {
1195
+ e.stopPropagation();
1178
1196
  e.preventDefault();
1179
- inp.value = original;
1180
- inp.blur();
1197
+ shut();
1198
+ }
1199
+ };
1200
+ document.addEventListener("keydown", onCardKey, true);
1201
+ card.querySelector('[data-role="x"]')?.addEventListener("click", shut);
1202
+ card.querySelector('[data-role="cancel"]')?.addEventListener("click", shut);
1203
+ back.addEventListener("mousedown", (e) => {
1204
+ if (e.target === back)
1205
+ shut();
1206
+ });
1207
+ card.querySelector('[data-role="save"]')?.addEventListener("click", async () => {
1208
+ const val = (k) => card.querySelector(`[data-field="${k}"]`)?.value.trim() ?? "";
1209
+ const fields = {};
1210
+ for (const f of CONTACT_FIELDS)
1211
+ fields[f.key] = val(f.key);
1212
+ const newName = val("name");
1213
+ const bd = fields.birthday;
1214
+ if (bd && !/^(\d{4}|-)-\d{2}-\d{2}$/.test(bd)) {
1215
+ alert(`Birthday must be YYYY-MM-DD, or --MM-DD when the year is unknown. Got "${bd}".`);
1216
+ return;
1217
+ }
1218
+ try {
1219
+ await upsertContact(newName, c.email, fields);
1220
+ shut();
1221
+ await reload();
1222
+ } catch (e) {
1223
+ alert(`Update failed: ${e?.message || e}`);
1181
1224
  }
1182
1225
  });
1183
1226
  };
@@ -1262,17 +1305,29 @@ async function openAddressBook(prefillSearch) {
1262
1305
  });
1263
1306
  await reload();
1264
1307
  }
1308
+ function cardSummary(c) {
1309
+ return CONTACT_FIELDS.map((f) => ({ label: f.label, value: (c[f.key] || "").trim() })).filter((x) => x.value).map((x) => `${x.label}: ${x.value.replace(/\s+/g, " ")}`).join("\n");
1310
+ }
1265
1311
  function escapeHtml(s) {
1266
1312
  return s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
1267
1313
  }
1268
1314
  function escapeAttr(s) {
1269
1315
  return escapeHtml(s);
1270
1316
  }
1271
- var isOpen;
1317
+ var CONTACT_FIELDS, isOpen;
1272
1318
  var init_address_book = __esm({
1273
1319
  "client/components/address-book.js"() {
1274
1320
  "use strict";
1275
1321
  init_api_client();
1322
+ CONTACT_FIELDS = [
1323
+ { key: "organization", label: "Company" },
1324
+ { key: "title", label: "Job title" },
1325
+ { key: "phone", label: "Phone", type: "tel" },
1326
+ { key: "website", label: "Website", type: "url", placeholder: "https://" },
1327
+ { key: "birthday", label: "Birthday", placeholder: "YYYY-MM-DD or --MM-DD" },
1328
+ { key: "address", label: "Address", multiline: true },
1329
+ { key: "notes", label: "Notes", multiline: true }
1330
+ ];
1276
1331
  isOpen = false;
1277
1332
  }
1278
1333
  });
@@ -1889,11 +1944,11 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
1889
1944
  setSeen(msg, true);
1890
1945
  updateFlags(accountId, uid, msg.flags, msg.folderId);
1891
1946
  try {
1892
- updateMessageFlags(accountId, uid, msg.flags);
1947
+ updateMessageFlags(accountId, uid, msg.flags, msg.folderId);
1893
1948
  } catch {
1894
1949
  }
1895
1950
  try {
1896
- setRowSeen(accountId, uid, true);
1951
+ setRowSeen(accountId, uid, true, msg.folderId);
1897
1952
  } catch {
1898
1953
  }
1899
1954
  };
@@ -3635,8 +3690,26 @@ async function refreshPriorityIndex() {
3635
3690
  } catch {
3636
3691
  }
3637
3692
  }
3638
- function rowKey(accountId, uid) {
3639
- return `${accountId}:${uid}`;
3693
+ function rowKey(accountId, uid, folderId) {
3694
+ return `${accountId}:${folderId ?? ""}:${uid}`;
3695
+ }
3696
+ function findRow(accountId, uid, folderId) {
3697
+ if (folderId !== void 0 && folderId !== null) {
3698
+ const exact = rowByKey.get(rowKey(accountId, uid, folderId));
3699
+ if (exact)
3700
+ return exact;
3701
+ }
3702
+ const hits = [];
3703
+ for (const row of rowByKey.values()) {
3704
+ if (row.accountId === accountId && row.msg.uid === uid)
3705
+ hits.push(row);
3706
+ }
3707
+ if (hits.length === 1)
3708
+ return hits[0];
3709
+ if (hits.length > 1) {
3710
+ console.warn(`[message-list] ambiguous row lookup ${accountId}/uid=${uid} \u2014 ${hits.length} folders match; caller must pass folderId`);
3711
+ }
3712
+ return void 0;
3640
3713
  }
3641
3714
  function focusRow(row, opts = {}) {
3642
3715
  const body = row.el.parentElement;
@@ -3670,26 +3743,26 @@ function getCurrentFocused() {
3670
3743
  return focusedRow ? focusedRow.msg : null;
3671
3744
  }
3672
3745
  function focusByIdentity(accountId, uid, opts = {}) {
3673
- const row = rowByKey.get(rowKey(accountId, uid));
3746
+ const row = findRow(accountId, uid, opts.folderId);
3674
3747
  if (!row)
3675
3748
  return false;
3676
3749
  focusRow(row, opts);
3677
3750
  return true;
3678
3751
  }
3679
- function setRowFlagged(accountId, uid, yes) {
3680
- const row = rowByKey.get(rowKey(accountId, uid));
3752
+ function setRowFlagged(accountId, uid, yes, folderId) {
3753
+ const row = findRow(accountId, uid, folderId);
3681
3754
  if (row)
3682
3755
  row.setFlaggedClass(yes);
3683
3756
  }
3684
- function setRowSeen(accountId, uid, yes) {
3685
- const row = rowByKey.get(rowKey(accountId, uid));
3757
+ function setRowSeen(accountId, uid, yes, folderId) {
3758
+ const row = findRow(accountId, uid, folderId);
3686
3759
  if (row)
3687
3760
  row.setUnreadClass(!yes);
3688
3761
  }
3689
3762
  function selectedRows() {
3690
3763
  const out = [];
3691
3764
  for (const s of getSelectedMessages()) {
3692
- const row = rowByKey.get(rowKey(s.accountId, s.uid));
3765
+ const row = findRow(s.accountId, s.uid, s.folderId);
3693
3766
  if (row)
3694
3767
  out.push(row);
3695
3768
  }
@@ -3699,7 +3772,7 @@ function visibleRowObjs() {
3699
3772
  const body = document.getElementById("ml-body");
3700
3773
  if (!body)
3701
3774
  return [];
3702
- return Array.from(body.querySelectorAll(".ml-row:not(.filter-hidden)")).map((el) => rowByKey.get(rowKey(el.dataset.accountId || "", Number(el.dataset.uid)))).filter((r) => !!r);
3775
+ return Array.from(body.querySelectorAll(".ml-row:not(.filter-hidden)")).map((el) => rowByKey.get(rowKey(el.dataset.accountId || "", Number(el.dataset.uid), el.dataset.folderId))).filter((r) => !!r);
3703
3776
  }
3704
3777
  function bulkTargets() {
3705
3778
  const sel = selectedRows();
@@ -3865,6 +3938,9 @@ function releaseFocus() {
3865
3938
  const body = document.getElementById("ml-body");
3866
3939
  body?.querySelectorAll(".ml-row.selected").forEach((r) => r.classList.remove("selected"));
3867
3940
  clearViewer();
3941
+ document.body.classList.remove("viewer-fullscreen");
3942
+ document.getElementById("message-viewer")?.classList.remove("narrow-active");
3943
+ document.getElementById("message-list")?.classList.remove("narrow-hidden");
3868
3944
  document.dispatchEvent(new CustomEvent("mailx-focus-changed", { detail: null }));
3869
3945
  }
3870
3946
  function markBodiesCached(items) {
@@ -4092,7 +4168,7 @@ function removeMessagesAndReconcile(uids) {
4092
4168
  const el = row;
4093
4169
  const key = `${el.dataset.accountId}:${el.dataset.folderId}:${el.dataset.uid}`;
4094
4170
  if (!stateUids.has(key)) {
4095
- const dead = rowByKey.get(rowKey(el.dataset.accountId || "", Number(el.dataset.uid)));
4171
+ const dead = rowByKey.get(rowKey(el.dataset.accountId || "", Number(el.dataset.uid), el.dataset.folderId));
4096
4172
  if (dead && dead.el === el)
4097
4173
  dead.detach();
4098
4174
  else
@@ -4105,12 +4181,12 @@ function removeMessagesAndReconcile(uids) {
4105
4181
  }
4106
4182
  if (outcome.focusedWasRemoved) {
4107
4183
  const survivor = outcome.nextSurvivor;
4108
- if (survivor && focusByIdentity(survivor.accountId, survivor.uid)) {
4184
+ if (survivor && focusByIdentity(survivor.accountId, survivor.uid, { folderId: survivor.folderId })) {
4109
4185
  return;
4110
4186
  }
4111
4187
  const remaining = getMessages2();
4112
4188
  for (const m of remaining) {
4113
- if (focusByIdentity(m.accountId, m.uid))
4189
+ if (focusByIdentity(m.accountId, m.uid, { folderId: m.folderId }))
4114
4190
  return;
4115
4191
  }
4116
4192
  releaseFocus();
@@ -4860,7 +4936,7 @@ var init_message_list = __esm({
4860
4936
  /** Insert this row into a list body and register it in the lookup map. */
4861
4937
  attach(body) {
4862
4938
  body.appendChild(this.el);
4863
- rowByKey.set(rowKey(this.accountId, this.msg.uid), this);
4939
+ rowByKey.set(rowKey(this.accountId, this.msg.uid, this.msg.folderId), this);
4864
4940
  }
4865
4941
  /** Remove this row from the DOM and the lookup map. If it was the
4866
4942
  * focused row, the controller is responsible for releasing focus
@@ -4868,7 +4944,7 @@ var init_message_list = __esm({
4868
4944
  * may want to hand focus to a sibling instead). */
4869
4945
  detach() {
4870
4946
  this.el.remove();
4871
- rowByKey.delete(rowKey(this.accountId, this.msg.uid));
4947
+ rowByKey.delete(rowKey(this.accountId, this.msg.uid, this.msg.folderId));
4872
4948
  }
4873
4949
  setSelected(yes) {
4874
4950
  this.el.classList.toggle("selected", yes);
@@ -5239,7 +5315,7 @@ var init_message_list = __esm({
5239
5315
  return;
5240
5316
  if (!ev.accountId || ev.previousDraftUid == null)
5241
5317
  return;
5242
- const row = rowByKey.get(rowKey(ev.accountId, ev.previousDraftUid));
5318
+ const row = findRow(ev.accountId, Number(ev.previousDraftUid), ev.folderId);
5243
5319
  if (row)
5244
5320
  row.setDate(typeof ev.savedAt === "number" ? ev.savedAt : Date.now());
5245
5321
  });
@@ -9408,8 +9484,8 @@ async function performUndo() {
9408
9484
  const flag = "\\Flagged";
9409
9485
  const flagsAfter = entry.prevFlagged ? [flag] : [];
9410
9486
  await updateFlags2(entry.accountId, entry.uid, flagsAfter, entry.folderId);
9411
- setRowFlagged(entry.accountId, entry.uid, entry.prevFlagged);
9412
- updateMessageFlags(entry.accountId, entry.uid, flagsAfter);
9487
+ setRowFlagged(entry.accountId, entry.uid, entry.prevFlagged, entry.folderId);
9488
+ updateMessageFlags(entry.accountId, entry.uid, flagsAfter, entry.folderId);
9413
9489
  }
9414
9490
  if (statusSync) statusSync.textContent = `Undid flag change on ${op.payload.length} message${op.payload.length !== 1 ? "s" : ""}${remaining}`;
9415
9491
  }
@@ -9587,8 +9663,8 @@ async function bulkFlagSelected() {
9587
9663
  for (const r of rows) {
9588
9664
  if (flaggedOf(r) === targetFlagged) continue;
9589
9665
  setFlagged(r, targetFlagged);
9590
- setRowFlagged(r.accountId, r.uid, targetFlagged);
9591
- updateMessageFlags(r.accountId, r.uid, r.flags);
9666
+ setRowFlagged(r.accountId, r.uid, targetFlagged, r.folderId);
9667
+ updateMessageFlags(r.accountId, r.uid, r.flags, r.folderId);
9592
9668
  try {
9593
9669
  await updateFlags2(r.accountId, r.uid, r.flags, r.folderId);
9594
9670
  } catch (e) {
@@ -9611,8 +9687,8 @@ document.getElementById("btn-flag")?.addEventListener("click", async () => {
9611
9687
  updateFlagButton();
9612
9688
  try {
9613
9689
  await updateFlags(sel.accountId, sel.uid, sel.flags, sel.folderId);
9614
- updateMessageFlags(sel.accountId, sel.uid, sel.flags);
9615
- setRowFlagged(sel.accountId, sel.uid, !wasFlagged);
9690
+ updateMessageFlags(sel.accountId, sel.uid, sel.flags, sel.folderId);
9691
+ setRowFlagged(sel.accountId, sel.uid, !wasFlagged, sel.folderId);
9616
9692
  } catch (e) {
9617
9693
  setFlagged(sel, wasFlagged);
9618
9694
  updateFlagButton();
@@ -9654,9 +9730,16 @@ async function spamSelectedMessages() {
9654
9730
  markAsSpamMessages(accountId, uids, folderIds).then((result) => {
9655
9731
  console.log(`[spam] ${accountId}: moved ${result?.moved ?? uids.length} to folderId=${result?.targetFolderId}`);
9656
9732
  if (result?.targetFolderId) spamUndo.targetFolderId = result.targetFolderId;
9657
- }).catch((e) => {
9733
+ }).catch(async (e) => {
9658
9734
  console.error(`[spam] ${accountId} failed:`, e);
9659
- if (statusSync) statusSync.textContent = `Spam sync issue (${accountId}): ${e?.message || e}`;
9735
+ if (statusSync) statusSync.textContent = `Spam FAILED (${accountId}): ${e?.message || e}`;
9736
+ try {
9737
+ await reloadCurrentFolder();
9738
+ } catch {
9739
+ }
9740
+ window.dispatchEvent(new CustomEvent("mailx-alert", {
9741
+ detail: { message: `Couldn't move ${uids.length} message(s) to spam: ${e?.message || e}`, key: "spam-move" }
9742
+ }));
9660
9743
  });
9661
9744
  }
9662
9745
  }
@@ -9716,7 +9799,7 @@ document.getElementById("btn-mark-unread")?.addEventListener("click", () => {
9716
9799
  const wasSeen = seenOf(sel);
9717
9800
  setSeen(sel, !wasSeen);
9718
9801
  updateFlags(sel.accountId, sel.uid, sel.flags, sel.folderId).then(() => {
9719
- updateMessageFlags(sel.accountId, sel.uid, sel.flags);
9802
+ updateMessageFlags(sel.accountId, sel.uid, sel.flags, sel.folderId);
9720
9803
  const row = document.querySelector(`.ml-row[data-uid="${sel.uid}"][data-account-id="${sel.accountId}"]`);
9721
9804
  if (row) row.classList.toggle("unread", wasSeen);
9722
9805
  }).catch(() => {
@@ -12758,7 +12841,7 @@ document.addEventListener("mailx-popout-action", ((e) => {
12758
12841
  const wasFlagged = flaggedOf(msg);
12759
12842
  setFlagged(msg, !wasFlagged);
12760
12843
  updateFlags(accountId, msg.uid, msg.flags, msg.folderId).catch((err) => console.error(`[popout] flag failed: ${err?.message || err}`));
12761
- setRowFlagged(accountId, msg.uid, !wasFlagged);
12844
+ setRowFlagged(accountId, msg.uid, !wasFlagged, msg.folderId);
12762
12845
  }
12763
12846
  }));
12764
12847
  document.addEventListener("mailx-popout-message", (async (e) => {