@bobfrankston/rmfmail 1.1.107 → 1.1.108

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.ts CHANGED
@@ -8,7 +8,7 @@ import { initMessageList, loadMessages, loadUnifiedInbox, loadSearchResults, rel
8
8
  import { seenOf, flaggedOf, draftOf, setSeen, setFlagged } from "@bobfrankston/mailx-types";
9
9
  import { initTabs, setActiveView as setActiveTabView, openTab, type ViewTab } from "./components/tabs.js";
10
10
  import { showMessage, getCurrentMessage, initViewer, popOutCurrentMessage, toggleFullscreenPreview, showPreviewBodyMenu, wrapHtmlBody } from "./components/message-viewer.js";
11
- import { connectWebSocket, onWsEvent, triggerSync, syncAccount, reauthenticate, getAccounts, getFolders, deleteMessage, deleteMessages, undeleteMessage, restartServer, getSyncPending, getVersion, getSettings, saveSettings, getAutocompleteSettings, saveAutocompleteSettings, repairAccounts, updateFlags, markAsSpamMessages, logClientEvent, sendMessage as apiSendMessage, subscribeStore } from "./lib/api-client.js";
11
+ import { connectWebSocket, onWsEvent, triggerSync, syncAccount, reauthenticate, getAccounts, getFolders, deleteMessage, deleteMessages, undeleteMessage, restartServer, getSyncPending, getVersion, getSettings, saveSettings, getAutocompleteSettings, saveAutocompleteSettings, repairAccounts, updateFlags, markAsSpamMessages, logClientEvent, sendMessage as apiSendMessage, subscribeStore, cancelServerSearch } from "./lib/api-client.js";
12
12
  import * as messageState from "./lib/message-state.js";
13
13
 
14
14
  // ── New message badge (favicon + title) ──
@@ -1237,12 +1237,12 @@ function quoteBody(msg: any): string {
1237
1237
  const date = new Date(msg.date).toLocaleString();
1238
1238
  const from = msg.from.name ? `${msg.from.name} <${msg.from.address}>` : msg.from.address;
1239
1239
  const body = sanitizeQuotedBody(msg);
1240
- // Lead with a real empty paragraph (not bare <br><br>) so every editor
1241
- // has a proper block for the caret to land in and for the user's reply
1242
- // to flow into. Bare <br>s aren't a block container TinyMCE's caret
1243
- // fell through to the quote and typing landed unpredictably (Bob
1244
- // 2026-05-21). `<p><br></p>` is the standard "empty editable paragraph".
1245
- return `<p><br></p><div class="reply"><p>On ${date}, ${from} wrote:</p><blockquote>${body}</blockquote></div>`;
1240
+ // Lead with an empty paragraph so every editor has a real block for the
1241
+ // caret to land in and the user's reply to flow into — bare <br>s aren't
1242
+ // a block container, so TinyMCE's caret fell through to the quote (Bob
1243
+ // 2026-05-21). The blank-line spacing lives in <br>s AFTER the </p>, not
1244
+ // inside it.
1245
+ return `<p></p><br><br><div class="reply"><p>On ${date}, ${from} wrote:</p><blockquote>${body}</blockquote></div>`;
1246
1246
  }
1247
1247
 
1248
1248
  function forwardBody(msg: any): string {
@@ -1250,7 +1250,7 @@ function forwardBody(msg: any): string {
1250
1250
  const from = msg.from.name ? `${msg.from.name} &lt;${msg.from.address}&gt;` : msg.from.address;
1251
1251
  const to = msg.to.map((a: any) => a.name ? `${a.name} &lt;${a.address}&gt;` : a.address).join(", ");
1252
1252
  const body = sanitizeQuotedBody(msg);
1253
- return `<p><br></p><div class="reply"><p>---------- Forwarded message ----------<br>From: ${from}<br>Date: ${date}<br>Subject: ${msg.subject}<br>To: ${to}</p>${body}</div>`;
1253
+ return `<p></p><br><br><div class="reply"><p>---------- Forwarded message ----------<br>From: ${from}<br>Date: ${date}<br>Subject: ${msg.subject}<br>To: ${to}</p>${body}</div>`;
1254
1254
  }
1255
1255
 
1256
1256
  // ── Delete with undo ──
@@ -1950,8 +1950,11 @@ function doSearch(immediate = false): void {
1950
1950
 
1951
1951
  // Any re-run aborts a pending server pass — it'll be rescheduled below
1952
1952
  // if still wanted. This is the "editing the search aborts + restarts"
1953
- // behavior: each keystroke cancels the prior server search.
1953
+ // behavior: each keystroke cancels the prior server search. clearTimeout
1954
+ // kills a not-yet-fired pass; cancelServerSearch() aborts one that's
1955
+ // already mid-sweep on the daemon (generation bump → loop bails).
1954
1956
  if (serverSearchTimer) { clearTimeout(serverSearchTimer); serverSearchTimer = null; }
1957
+ cancelServerSearch();
1955
1958
 
1956
1959
  // "This folder" scope: instant client-side filter of the visible rows.
1957
1960
  // Only when the server checkbox is OFF — with it on we want the real
@@ -2003,7 +2006,7 @@ let reloadDebounceTimer: ReturnType<typeof setTimeout> | null = null;
2003
2006
 
2004
2007
  searchInput?.addEventListener("input", () => {
2005
2008
  clearTimeout(searchTimeout);
2006
- if (serverSearchTimer) { clearTimeout(serverSearchTimer); serverSearchTimer = null; }
2009
+ if (serverSearchTimer) { clearTimeout(serverSearchTimer); serverSearchTimer = null; cancelServerSearch(); }
2007
2010
  updateSearchHighlight();
2008
2011
  if (searchInput.value.trim() === "") {
2009
2012
  // Cleared — reset immediately, no debounce. Must exit search mode
@@ -2029,6 +2032,7 @@ searchInput?.addEventListener("keydown", (e) => {
2029
2032
  if (e.key === "Escape") {
2030
2033
  searchInput.value = "";
2031
2034
  if (serverSearchTimer) { clearTimeout(serverSearchTimer); serverSearchTimer = null; }
2035
+ cancelServerSearch();
2032
2036
  updateSearchHighlight();
2033
2037
  clearSearchMode();
2034
2038
  // Clear any client-side filters
@@ -4219,6 +4223,14 @@ getSettings().then((s: any) => {
4219
4223
 
4220
4224
  // Save editor choice to server settings
4221
4225
  function saveEditorSetting(editor: string): void {
4226
+ // Update the localStorage cache SYNCHRONOUSLY. compose.ts reads
4227
+ // `mailx-editor-type` from localStorage at module-load to pick the
4228
+ // editor — its async getSettings() refresh only runs when a compose
4229
+ // window opens, and reads localStorage FIRST. Without this write the
4230
+ // cache stays stale and the next compose keeps the old editor until a
4231
+ // full app restart (Bob 2026-05-21: "changed to quill but got tinymce
4232
+ // until I restarted"). With it, the very next compose-open is correct.
4233
+ try { localStorage.setItem("mailx-editor-type", editor); } catch { /* private mode */ }
4222
4234
  getSettings().then((settings: any) => {
4223
4235
  settings.ui = { ...settings.ui, editor };
4224
4236
  saveSettings(settings);
@@ -44,6 +44,7 @@ __export(api_client_exports, {
44
44
  allowRemoteContent: () => allowRemoteContent,
45
45
  autocomplete: () => autocomplete,
46
46
  cancelQueuedOutgoing: () => cancelQueuedOutgoing,
47
+ cancelServerSearch: () => cancelServerSearch,
47
48
  closeWordEdit: () => closeWordEdit,
48
49
  connectEvents: () => connectEvents,
49
50
  connectWebSocket: () => connectWebSocket,
@@ -217,6 +218,9 @@ function getUnifiedInbox(page = 1, pageSize = 50) {
217
218
  function searchMessages(query, page = 1, pageSize = 50, scope = "all", accountId = "", folderId = 0, includeTrashSpam = false) {
218
219
  return ipc().searchMessages(query, page, pageSize, scope, accountId, folderId, includeTrashSpam);
219
220
  }
221
+ function cancelServerSearch() {
222
+ return ipc().cancelServerSearch?.();
223
+ }
220
224
  function getMessage(accountId, uid, allowRemote = false, folderId) {
221
225
  return ipc().getMessage(accountId, uid, allowRemote, folderId);
222
226
  }