@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.
- package/client/app.bundle.js +29 -11
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +42 -7
- package/client/app.js.map +1 -1
- package/client/app.ts +43 -7
- package/client/compose/compose.bundle.js +27 -15
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/lib/api-client.js +5 -5
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +5 -5
- package/client/lib/mailxapi.js +4 -4
- package/client/lib/rmf-tiny.js +35 -10
- package/client/lib/tinymce/CHANGELOG.md +12 -0
- package/client/lib/tinymce/composer.json +1 -1
- package/client/lib/tinymce/models/dom/model.js +1 -1
- package/client/lib/tinymce/package.json +1 -1
- package/client/lib/tinymce/plugins/accordion/plugin.js +1 -1
- package/client/lib/tinymce/plugins/advlist/plugin.js +1 -1
- package/client/lib/tinymce/plugins/anchor/plugin.js +1 -1
- package/client/lib/tinymce/plugins/autolink/plugin.js +1 -1
- package/client/lib/tinymce/plugins/autoresize/plugin.js +1 -1
- package/client/lib/tinymce/plugins/autosave/plugin.js +1 -1
- package/client/lib/tinymce/plugins/charmap/plugin.js +1 -1
- package/client/lib/tinymce/plugins/code/plugin.js +1 -1
- package/client/lib/tinymce/plugins/codesample/plugin.js +1 -1
- package/client/lib/tinymce/plugins/directionality/plugin.js +1 -1
- package/client/lib/tinymce/plugins/emoticons/plugin.js +1 -1
- package/client/lib/tinymce/plugins/fullscreen/plugin.js +1 -1
- package/client/lib/tinymce/plugins/help/plugin.js +1 -1
- package/client/lib/tinymce/plugins/image/plugin.js +1 -1
- package/client/lib/tinymce/plugins/importcss/plugin.js +1 -1
- package/client/lib/tinymce/plugins/insertdatetime/plugin.js +1 -1
- package/client/lib/tinymce/plugins/link/plugin.js +1 -1
- package/client/lib/tinymce/plugins/lists/plugin.js +1 -1
- package/client/lib/tinymce/plugins/media/plugin.js +66 -41
- package/client/lib/tinymce/plugins/media/plugin.min.js +1 -1
- package/client/lib/tinymce/plugins/nonbreaking/plugin.js +1 -1
- package/client/lib/tinymce/plugins/pagebreak/plugin.js +1 -1
- package/client/lib/tinymce/plugins/preview/plugin.js +1 -1
- package/client/lib/tinymce/plugins/quickbars/plugin.js +1 -1
- package/client/lib/tinymce/plugins/save/plugin.js +1 -1
- package/client/lib/tinymce/plugins/searchreplace/plugin.js +1 -1
- package/client/lib/tinymce/plugins/table/plugin.js +1 -1
- package/client/lib/tinymce/plugins/visualblocks/plugin.js +1 -1
- package/client/lib/tinymce/plugins/visualchars/plugin.js +1 -1
- package/client/lib/tinymce/plugins/wordcount/plugin.js +1 -1
- package/client/lib/tinymce/themes/silver/theme.js +345 -155
- package/client/lib/tinymce/themes/silver/theme.min.js +3 -1
- package/client/lib/tinymce/tinymce.js +393 -171
- package/client/lib/tinymce/tinymce.min.js +4 -2
- package/docs/outlook.md +215 -0
- package/package.json +3 -3
- package/packages/mailx-service/index.d.ts +2 -2
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +56 -24
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +52 -18
- package/packages/mailx-service/jsonrpc.js +2 -2
- package/packages/mailx-service/jsonrpc.js.map +1 -1
- package/packages/mailx-service/jsonrpc.ts +2 -2
- /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
|
-
|
|
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
|
|
1477
|
-
uids.push(msg.uid);
|
|
1478
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
2117
|
-
|
|
2118
|
-
const
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2116
|
+
const lang = data.language || "text";
|
|
2117
|
+
const esc = (s) => String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
},
|