@bobfrankston/rmfmail 1.1.225 → 1.1.230

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 (61) hide show
  1. package/client/app.bundle.js +29 -11
  2. package/client/app.bundle.js.map +2 -2
  3. package/client/app.js +42 -7
  4. package/client/app.js.map +1 -1
  5. package/client/app.ts +43 -7
  6. package/client/compose/compose.bundle.js +27 -15
  7. package/client/compose/compose.bundle.js.map +2 -2
  8. package/client/lib/api-client.js +5 -5
  9. package/client/lib/api-client.js.map +1 -1
  10. package/client/lib/api-client.ts +5 -5
  11. package/client/lib/mailxapi.js +4 -4
  12. package/client/lib/rmf-tiny.js +35 -10
  13. package/client/lib/tinymce/CHANGELOG.md +12 -0
  14. package/client/lib/tinymce/composer.json +1 -1
  15. package/client/lib/tinymce/models/dom/model.js +1 -1
  16. package/client/lib/tinymce/package.json +1 -1
  17. package/client/lib/tinymce/plugins/accordion/plugin.js +1 -1
  18. package/client/lib/tinymce/plugins/advlist/plugin.js +1 -1
  19. package/client/lib/tinymce/plugins/anchor/plugin.js +1 -1
  20. package/client/lib/tinymce/plugins/autolink/plugin.js +1 -1
  21. package/client/lib/tinymce/plugins/autoresize/plugin.js +1 -1
  22. package/client/lib/tinymce/plugins/autosave/plugin.js +1 -1
  23. package/client/lib/tinymce/plugins/charmap/plugin.js +1 -1
  24. package/client/lib/tinymce/plugins/code/plugin.js +1 -1
  25. package/client/lib/tinymce/plugins/codesample/plugin.js +1 -1
  26. package/client/lib/tinymce/plugins/directionality/plugin.js +1 -1
  27. package/client/lib/tinymce/plugins/emoticons/plugin.js +1 -1
  28. package/client/lib/tinymce/plugins/fullscreen/plugin.js +1 -1
  29. package/client/lib/tinymce/plugins/help/plugin.js +1 -1
  30. package/client/lib/tinymce/plugins/image/plugin.js +1 -1
  31. package/client/lib/tinymce/plugins/importcss/plugin.js +1 -1
  32. package/client/lib/tinymce/plugins/insertdatetime/plugin.js +1 -1
  33. package/client/lib/tinymce/plugins/link/plugin.js +1 -1
  34. package/client/lib/tinymce/plugins/lists/plugin.js +1 -1
  35. package/client/lib/tinymce/plugins/media/plugin.js +66 -41
  36. package/client/lib/tinymce/plugins/media/plugin.min.js +1 -1
  37. package/client/lib/tinymce/plugins/nonbreaking/plugin.js +1 -1
  38. package/client/lib/tinymce/plugins/pagebreak/plugin.js +1 -1
  39. package/client/lib/tinymce/plugins/preview/plugin.js +1 -1
  40. package/client/lib/tinymce/plugins/quickbars/plugin.js +1 -1
  41. package/client/lib/tinymce/plugins/save/plugin.js +1 -1
  42. package/client/lib/tinymce/plugins/searchreplace/plugin.js +1 -1
  43. package/client/lib/tinymce/plugins/table/plugin.js +1 -1
  44. package/client/lib/tinymce/plugins/visualblocks/plugin.js +1 -1
  45. package/client/lib/tinymce/plugins/visualchars/plugin.js +1 -1
  46. package/client/lib/tinymce/plugins/wordcount/plugin.js +1 -1
  47. package/client/lib/tinymce/themes/silver/theme.js +345 -155
  48. package/client/lib/tinymce/themes/silver/theme.min.js +3 -1
  49. package/client/lib/tinymce/tinymce.js +393 -171
  50. package/client/lib/tinymce/tinymce.min.js +4 -2
  51. package/docs/outlook.md +215 -0
  52. package/package.json +3 -3
  53. package/packages/mailx-service/index.d.ts +2 -2
  54. package/packages/mailx-service/index.d.ts.map +1 -1
  55. package/packages/mailx-service/index.js +56 -24
  56. package/packages/mailx-service/index.js.map +1 -1
  57. package/packages/mailx-service/index.ts +52 -18
  58. package/packages/mailx-service/jsonrpc.js +2 -2
  59. package/packages/mailx-service/jsonrpc.js.map +1 -1
  60. package/packages/mailx-service/jsonrpc.ts +2 -2
  61. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-62732 → node_modules.npmglobalize-stash-86824}/.package-lock.json +0 -0
package/client/app.ts CHANGED
@@ -577,6 +577,29 @@ function refreshSyncTooltip(): void {
577
577
  // no new sync events.
578
578
  setInterval(refreshSyncTooltip, 30_000);
579
579
 
580
+ // Honest "Synced" indicator. "Synced HH:MM" must mean sync is IDLE, not fire
581
+ // on every tick — folderCountsChanged/syncProgress fire repeatedly during a
582
+ // catch-up backfill (e.g. after the daemon was offline) while messages are
583
+ // STILL arriving, so stamping "Synced" each time misled (Bob 2026-06-06: "it
584
+ // says synced but I don't see this afternoon's mail" — they were mid-backfill).
585
+ // Any sync activity (re)arms this settle timer; only when activity goes quiet
586
+ // for SYNC_SETTLE_MS do we stamp the time. While the storm runs, the handlers
587
+ // keep the bar on "Syncing…".
588
+ let syncSettleTimer: ReturnType<typeof setTimeout> | null = null;
589
+ const SYNC_SETTLE_MS = 4000;
590
+ function scheduleSyncedStamp(): void {
591
+ if (syncSettleTimer) clearTimeout(syncSettleTimer);
592
+ syncSettleTimer = setTimeout(() => {
593
+ syncSettleTimer = null;
594
+ const el = document.getElementById("status-sync");
595
+ // Only flip a "Syncing…" display to "Synced" — never clobber an error
596
+ // ("Sync error: …") or a transient action message ("Trashed 1 …").
597
+ if (el && (el.textContent || "").startsWith("Syncing")) {
598
+ el.textContent = `Synced ${new Date().toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", hour12: false })}`;
599
+ }
600
+ }, SYNC_SETTLE_MS);
601
+ }
602
+
580
603
  // ── Auto two-line when message list is narrow ──
581
604
  const messageList = document.getElementById("message-list");
582
605
  if (messageList) {
@@ -1471,14 +1494,19 @@ async function deleteSelectedMessages(): Promise<void> {
1471
1494
  // An IPC 120s timeout doesn't mean the trash failed — surfacing it
1472
1495
  // as a status-bar error would only mislead. Real errors are still
1473
1496
  // reported by next sync's diagnostics.
1474
- const byAccount = new Map<string, number[]>();
1497
+ // Carry folderId per message so the daemon trashes the exact (folder, uid),
1498
+ // not whatever folder a bare (account, uid) lookup happens to resolve to
1499
+ // (Bob 2026-06-06: Del trashed the wrong message). uids/folderIds stay
1500
+ // index-aligned per account.
1501
+ const byAccount = new Map<string, { uids: number[]; folderIds: number[] }>();
1475
1502
  for (const msg of snapshot) {
1476
- const uids = byAccount.get(msg.accountId) || [];
1477
- uids.push(msg.uid);
1478
- byAccount.set(msg.accountId, uids);
1503
+ const g = byAccount.get(msg.accountId) || { uids: [], folderIds: [] };
1504
+ g.uids.push(msg.uid);
1505
+ g.folderIds.push(msg.folderId);
1506
+ byAccount.set(msg.accountId, g);
1479
1507
  }
1480
- for (const [accountId, uids] of byAccount) {
1481
- deleteMessages(accountId, uids).catch((e: any) => {
1508
+ for (const [accountId, g] of byAccount) {
1509
+ deleteMessages(accountId, g.uids, g.folderIds).catch((e: any) => {
1482
1510
  console.error(`Delete failed for ${accountId}: ${e?.message || e}`);
1483
1511
  if (statusSync) statusSync.textContent = `Delete sync issue (${accountId}): ${e?.message || e}`;
1484
1512
  });
@@ -2646,6 +2674,7 @@ onWsEvent((event) => {
2646
2674
  }
2647
2675
  if (statusSync) statusSync.textContent = `Syncing ${event.accountId}: ${label}`;
2648
2676
  if (startupStatus) startupStatus.textContent = `Syncing ${event.accountId}: ${label}`;
2677
+ scheduleSyncedStamp(); // re-arm the settle timer; stamp "Synced" when this quiets
2649
2678
  // Mark syncing folder in tree — bubble up to visible parent if collapsed
2650
2679
  const syncPath = event.phase?.startsWith("sync:") ? event.phase.slice(5) : null;
2651
2680
  // Clear previous syncing markers for this account
@@ -2672,6 +2701,7 @@ onWsEvent((event) => {
2672
2701
  refreshFolderTree();
2673
2702
  // Q53: track per-account last-sync timestamp for the status-bar hover.
2674
2703
  recordAccountSync(event.accountId);
2704
+ scheduleSyncedStamp(); // settle → "Synced HH:MM" once activity stops
2675
2705
  // Earlier I added reloadCurrentFolder() here to fix the
2676
2706
  // "phone INBOX stays on placeholder" report. That broke desktop
2677
2707
  // because syncComplete fires repeatedly on the desktop sync
@@ -2712,7 +2742,13 @@ onWsEvent((event) => {
2712
2742
  hideAlert();
2713
2743
  const syncBtn = document.getElementById("btn-sync") as HTMLButtonElement;
2714
2744
  if (syncBtn) { syncBtn.disabled = false; syncBtn.classList.remove("syncing"); }
2715
- if (statusSync) statusSync.textContent = `Synced ${new Date().toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", hour12: false })}`;
2745
+ // Honest indicator: a count change means activity, not necessarily
2746
+ // "done". Show "Syncing…" and let the settle timer stamp the time
2747
+ // once the backfill goes quiet (see scheduleSyncedStamp).
2748
+ if (statusSync && !(statusSync.textContent || "").startsWith("Sync error")) {
2749
+ statusSync.textContent = "Syncing…";
2750
+ }
2751
+ scheduleSyncedStamp();
2716
2752
  break;
2717
2753
  }
2718
2754
  case "updateAvailable": {
@@ -1322,13 +1322,13 @@ function removeUserDictWord(word) {
1322
1322
  function flagSenderOrDomain(type, value) {
1323
1323
  return ipc().flagSenderOrDomain?.(type, value) ?? Promise.resolve({ flagged: false });
1324
1324
  }
1325
- function deleteMessage(accountId, uid) {
1326
- return ipc().deleteMessage?.(accountId, uid);
1325
+ function deleteMessage(accountId, uid, folderId) {
1326
+ return ipc().deleteMessage?.(accountId, uid, folderId);
1327
1327
  }
1328
- function deleteMessages(accountId, uids) {
1328
+ function deleteMessages(accountId, uids, folderIds) {
1329
1329
  if (uids.length === 1)
1330
- return deleteMessage(accountId, uids[0]);
1331
- return ipc().deleteMessages?.(accountId, uids);
1330
+ return deleteMessage(accountId, uids[0], folderIds?.[0]);
1331
+ return ipc().deleteMessages?.(accountId, uids, folderIds);
1332
1332
  }
1333
1333
  function moveMessages(accountId, uids, targetFolderId, targetAccountId) {
1334
1334
  if (uids.length === 1)
@@ -2084,17 +2084,17 @@ async function createTinyMceEditor(container2, opts = {}) {
2084
2084
  };
2085
2085
  const BORDER_STYLES = { "border": "1px solid #888", "border-radius": "4px", "padding": "10px" };
2086
2086
  const openCodeDialog = () => {
2087
- const cs = ed.plugins.codesample;
2088
2087
  const preEl = ed.dom.getParent(ed.selection.getNode(), "pre");
2089
2088
  let curLang = "text";
2090
2089
  let curBorder = false;
2090
+ let curCode = "";
2091
2091
  if (preEl) {
2092
2092
  const m = (preEl.className || "").match(/language-([\w-]+)/);
2093
2093
  if (m)
2094
2094
  curLang = m[1];
2095
2095
  curBorder = !!(preEl.style && preEl.style.border && preEl.style.border !== "none");
2096
+ curCode = preEl.textContent || "";
2096
2097
  }
2097
- const curCode = cs && cs.getCurrentCode ? cs.getCurrentCode(ed) || "" : "";
2098
2098
  ed.windowManager.open({
2099
2099
  title: "Insert code",
2100
2100
  size: "large",
@@ -2113,15 +2113,16 @@ async function createTinyMceEditor(container2, opts = {}) {
2113
2113
  ],
2114
2114
  onSubmit: (api) => {
2115
2115
  const data = api.getData();
2116
- if (cs && cs.insertCodeSample)
2117
- cs.insertCodeSample(data.language, data.code);
2118
- const pre = ed.dom.getParent(ed.selection.getNode(), "pre");
2119
- if (pre) {
2120
- if (data.border)
2121
- ed.dom.setStyles(pre, BORDER_STYLES);
2122
- else
2123
- ed.dom.setStyles(pre, { "border": "", "border-radius": "", "padding": "" });
2116
+ const lang = data.language || "text";
2117
+ const esc = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2118
+ const borderStyle = data.border ? ` style="${Object.entries(BORDER_STYLES).map(([k, v]) => `${k}:${v}`).join(";")}"` : "";
2119
+ const html = `<pre class="language-${lang}"${borderStyle}><code>${esc(data.code || "")}</code></pre>`;
2120
+ if (preEl && preEl.parentNode) {
2121
+ ed.dom.setOuterHTML(preEl, html);
2122
+ } else {
2123
+ ed.insertContent(html);
2124
2124
  }
2125
+ ed.undoManager.add();
2125
2126
  api.close();
2126
2127
  }
2127
2128
  });
@@ -2260,6 +2261,17 @@ async function createTinyMceEditor(container2, opts = {}) {
2260
2261
  getText() {
2261
2262
  return editor2.getContent({ format: "text" });
2262
2263
  },
2264
+ /** Subscribe to content changes. compose.ts calls this to drive draft
2265
+ * auto-save; it was MISSING from the facade, so the call threw
2266
+ * "editor.onContentChange is not a function" during compose init —
2267
+ * aborting the rest of init and leaving change-tracking unwired, so
2268
+ * edits made via dialogs (Insert code, Source code) were never
2269
+ * captured into the draft and looked "ignored" (Bob 2026-06-06).
2270
+ * `SetContent`/`ExecCommand` are the load-bearing events here: they're
2271
+ * what the codesample + code (source) dialogs fire on apply. */
2272
+ onContentChange(handler) {
2273
+ editor2.on("input change undo redo keyup SetContent ExecCommand AddUndo", () => handler());
2274
+ },
2263
2275
  focus() {
2264
2276
  editor2.focus();
2265
2277
  },