@bobfrankston/rmfmail 1.0.681 → 1.0.686

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 (76) hide show
  1. package/bin/lean-accounts.js +0 -1
  2. package/client/app.bundle.js +138 -38
  3. package/client/app.bundle.js.map +2 -2
  4. package/client/app.js +12 -1
  5. package/client/app.js.map +1 -1
  6. package/client/app.ts +12 -1
  7. package/client/components/context-menu.js +2 -0
  8. package/client/components/context-menu.js.map +1 -1
  9. package/client/components/context-menu.ts +6 -0
  10. package/client/components/folder-tree.js +26 -4
  11. package/client/components/folder-tree.js.map +1 -1
  12. package/client/components/folder-tree.ts +21 -4
  13. package/client/components/message-list.js +108 -40
  14. package/client/components/message-list.js.map +1 -1
  15. package/client/components/message-list.ts +103 -38
  16. package/client/compose/compose.bundle.js +148 -15
  17. package/client/compose/compose.bundle.js.map +3 -3
  18. package/client/compose/spellcheck.js +178 -12
  19. package/client/compose/spellcheck.js.map +1 -1
  20. package/client/compose/spellcheck.ts +168 -8
  21. package/client/lib/api-client.js +3 -0
  22. package/client/lib/api-client.js.map +1 -1
  23. package/client/lib/api-client.ts +4 -0
  24. package/client/lib/mailxapi.js +3 -0
  25. package/client/lib/rmf-tiny.js +25 -6
  26. package/package.json +7 -7
  27. package/packages/mailx-core/index.d.ts.map +1 -1
  28. package/packages/mailx-core/index.js +2 -12
  29. package/packages/mailx-core/index.js.map +1 -1
  30. package/packages/mailx-core/index.ts +2 -12
  31. package/packages/mailx-imap/index.d.ts.map +1 -1
  32. package/packages/mailx-imap/index.js +31 -6
  33. package/packages/mailx-imap/index.js.map +1 -1
  34. package/packages/mailx-imap/index.ts +32 -6
  35. package/packages/mailx-imap/node_modules.npmglobalize-stash-11884/.package-lock.json +116 -0
  36. package/packages/mailx-imap/package-lock.json +2 -2
  37. package/packages/mailx-imap/package.json +1 -1
  38. package/packages/mailx-service/index.d.ts +22 -0
  39. package/packages/mailx-service/index.d.ts.map +1 -1
  40. package/packages/mailx-service/index.js +123 -0
  41. package/packages/mailx-service/index.js.map +1 -1
  42. package/packages/mailx-service/index.ts +109 -0
  43. package/packages/mailx-service/jsonrpc.js +3 -0
  44. package/packages/mailx-service/jsonrpc.js.map +1 -1
  45. package/packages/mailx-service/jsonrpc.ts +3 -0
  46. package/packages/mailx-service/local-store.d.ts.map +1 -1
  47. package/packages/mailx-service/local-store.js +6 -12
  48. package/packages/mailx-service/local-store.js.map +1 -1
  49. package/packages/mailx-service/local-store.ts +6 -12
  50. package/packages/mailx-settings/docs/accounts.md +14 -1
  51. package/packages/mailx-settings/docs/npmglobalize-disttag.md +90 -0
  52. package/packages/mailx-settings/docs/prod-android.md +88 -0
  53. package/packages/mailx-settings/docs/prod.md +224 -0
  54. package/packages/mailx-settings/docs/push-relay.md +141 -0
  55. package/packages/mailx-settings/docs/rmf-tiny.md +156 -0
  56. package/packages/mailx-settings/index.d.ts +1 -1
  57. package/packages/mailx-settings/index.d.ts.map +1 -1
  58. package/packages/mailx-settings/index.js +1 -4
  59. package/packages/mailx-settings/index.js.map +1 -1
  60. package/packages/mailx-settings/index.ts +1 -3
  61. package/packages/mailx-settings/package.json +1 -1
  62. package/packages/mailx-store/db.d.ts.map +1 -1
  63. package/packages/mailx-store/db.js +44 -6
  64. package/packages/mailx-store/db.js.map +1 -1
  65. package/packages/mailx-store/db.ts +47 -6
  66. package/packages/mailx-store/package.json +1 -1
  67. package/packages/mailx-store-web/package.json +4 -1
  68. package/packages/mailx-store-web/web-settings.d.ts.map +1 -1
  69. package/packages/mailx-store-web/web-settings.js +0 -1
  70. package/packages/mailx-store-web/web-settings.js.map +1 -1
  71. package/packages/mailx-store-web/web-settings.ts +0 -1
  72. package/packages/mailx-types/index.d.ts +1 -2
  73. package/packages/mailx-types/index.d.ts.map +1 -1
  74. package/packages/mailx-types/index.js.map +1 -1
  75. package/packages/mailx-types/index.ts +1 -2
  76. package/packages/mailx-types/package.json +1 -1
@@ -85,6 +85,7 @@ __export(api_client_exports, {
85
85
  logClientEvent: () => logClientEvent,
86
86
  markAsSpamMessages: () => markAsSpamMessages,
87
87
  markFolderRead: () => markFolderRead,
88
+ moveFolderToTrash: () => moveFolderToTrash,
88
89
  moveMessage: () => moveMessage,
89
90
  moveMessages: () => moveMessages,
90
91
  onEvent: () => onEvent,
@@ -357,6 +358,9 @@ function renameFolder(accountId, folderId, newName) {
357
358
  function deleteFolder(accountId, folderId) {
358
359
  return ipc().deleteFolder?.(accountId, folderId);
359
360
  }
361
+ function moveFolderToTrash(accountId, folderId) {
362
+ return ipc().moveFolderToTrash?.(accountId, folderId);
363
+ }
360
364
  function emptyFolder(accountId, folderId) {
361
365
  return ipc().emptyFolder?.(accountId, folderId);
362
366
  }
@@ -617,7 +621,23 @@ async function createTinyMceEditor(container2, opts = {}) {
617
621
  return;
618
622
  args.content = args.content.replace(/(^|[\s(\[])((?:https?|ftp):\/\/[^\s<>"']+[^\s<>"'.,;:!?)\]])/gi, (_m, lead, url) => `${lead}<a href="${url}">${url}</a>`);
619
623
  },
620
- content_style: "body { font-family: system-ui, sans-serif; font-size: 14px; }",
624
+ // Body font + quoted-reply styling. Without explicit rules for
625
+ // <blockquote> and div.reply the editor iframe renders the
626
+ // quoted block with no visual distinction from the user's own
627
+ // text — pasted reply quotes vanish into a wall of unformatted
628
+ // paragraphs (Bob 2026-05-12: "the body losing all formatting").
629
+ // The styles below match Thunderbird/Outlook conventions: a
630
+ // muted left-bar + indent on blockquotes, slight color shift on
631
+ // the "On … wrote:" attribution, untouched typography for
632
+ // everything else so genuine HTML formatting (bold / italic /
633
+ // tables / inline color) still comes through verbatim.
634
+ content_style: [
635
+ "body { font-family: system-ui, sans-serif; font-size: 14px; }",
636
+ "blockquote { border-left: 3px solid #c0c8d0; margin: 0 0 0 4px; padding: 2px 0 2px 10px; color: #555; }",
637
+ "div.reply { margin-top: 0.5em; }",
638
+ "div.reply > p:first-child { color: #666; font-size: 0.95em; margin: 0 0 4px 0; }",
639
+ "pre, code { font-family: ui-monospace, Consolas, Menlo, monospace; }"
640
+ ].join(" "),
621
641
  init_instance_callback: (ed) => resolve(ed),
622
642
  setup: (ed) => {
623
643
  if (opts.initialHtml)
@@ -714,10 +734,14 @@ async function createTinyMceEditor(container2, opts = {}) {
714
734
  focus() {
715
735
  editor2.focus();
716
736
  },
717
- setCursor(_pos) {
737
+ setCursor(pos) {
718
738
  try {
719
739
  editor2.selection.select(editor2.getBody(), true);
720
- editor2.selection.collapse(false);
740
+ editor2.selection.collapse(
741
+ pos === 0
742
+ /* true = start */
743
+ );
744
+ editor2.focus();
721
745
  } catch {
722
746
  }
723
747
  },
@@ -1766,7 +1790,7 @@ function decorate(editor2, sp) {
1766
1790
  p = p.parentNode;
1767
1791
  }
1768
1792
  }
1769
- const bookmark = editor2.selection?.getBookmark?.(2);
1793
+ const savedAbs = caretAbsOffsetFromBody(body);
1770
1794
  try {
1771
1795
  editor2.undoManager?.ignore?.(() => {
1772
1796
  const old = body.querySelectorAll(`span[${MARKER_ATTR}]`);
@@ -1791,6 +1815,16 @@ function decorate(editor2, sp) {
1791
1815
  return NodeFilter.FILTER_ACCEPT;
1792
1816
  }
1793
1817
  });
1818
+ let caretNode = null;
1819
+ let caretOffset = 0;
1820
+ const liveSel = doc.getSelection();
1821
+ if (liveSel && liveSel.rangeCount > 0) {
1822
+ const f = liveSel.focusNode;
1823
+ if (f && f.nodeType === Node.TEXT_NODE) {
1824
+ caretNode = f;
1825
+ caretOffset = liveSel.focusOffset;
1826
+ }
1827
+ }
1794
1828
  const hits = [];
1795
1829
  let n = walker.nextNode();
1796
1830
  const WORD_RE = /[\p{L}][\p{L}'’\-]*/gu;
@@ -1803,6 +1837,9 @@ function decorate(editor2, sp) {
1803
1837
  const word = m[0];
1804
1838
  if (word.length < MIN_WORD_LEN)
1805
1839
  continue;
1840
+ if (caretNode === tn && caretOffset >= m.index && caretOffset <= m.index + word.length) {
1841
+ continue;
1842
+ }
1806
1843
  if (sp.correct(word))
1807
1844
  continue;
1808
1845
  hits.push({ node: tn, start: m.index, end: m.index + word.length });
@@ -1823,11 +1860,72 @@ function decorate(editor2, sp) {
1823
1860
  }
1824
1861
  });
1825
1862
  } finally {
1826
- if (bookmark)
1827
- try {
1828
- editor2.selection?.moveToBookmark?.(bookmark);
1829
- } catch {
1830
- }
1863
+ if (savedAbs != null)
1864
+ restoreCaretFromAbsOffset(body, savedAbs);
1865
+ }
1866
+ }
1867
+ function caretAbsOffsetFromBody(body) {
1868
+ const doc = body.ownerDocument;
1869
+ const sel = doc.getSelection();
1870
+ if (!sel || sel.rangeCount === 0)
1871
+ return null;
1872
+ const focusNode = sel.focusNode;
1873
+ const focusOffset = sel.focusOffset;
1874
+ if (!focusNode)
1875
+ return null;
1876
+ if (focusNode.nodeType !== Node.TEXT_NODE) {
1877
+ let abs2 = 0;
1878
+ const walker2 = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT);
1879
+ let n2 = walker2.nextNode();
1880
+ while (n2) {
1881
+ if (focusNode.contains(n2))
1882
+ break;
1883
+ const cmp = focusNode.compareDocumentPosition(n2);
1884
+ if (cmp & Node.DOCUMENT_POSITION_PRECEDING)
1885
+ abs2 += n2.data.length;
1886
+ n2 = walker2.nextNode();
1887
+ }
1888
+ return abs2;
1889
+ }
1890
+ let abs = 0;
1891
+ const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT);
1892
+ let n = walker.nextNode();
1893
+ while (n) {
1894
+ if (n === focusNode)
1895
+ return abs + focusOffset;
1896
+ abs += n.data.length;
1897
+ n = walker.nextNode();
1898
+ }
1899
+ return null;
1900
+ }
1901
+ function restoreCaretFromAbsOffset(body, abs) {
1902
+ const doc = body.ownerDocument;
1903
+ const sel = doc.getSelection();
1904
+ if (!sel)
1905
+ return;
1906
+ const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT);
1907
+ let acc = 0;
1908
+ let n = walker.nextNode();
1909
+ while (n) {
1910
+ const len = n.data.length;
1911
+ if (acc + len >= abs) {
1912
+ const range = doc.createRange();
1913
+ range.setStart(n, Math.max(0, abs - acc));
1914
+ range.collapse(true);
1915
+ sel.removeAllRanges();
1916
+ sel.addRange(range);
1917
+ return;
1918
+ }
1919
+ acc += len;
1920
+ n = walker.nextNode();
1921
+ }
1922
+ const last = walker.previousNode();
1923
+ if (last) {
1924
+ const range = doc.createRange();
1925
+ range.setStart(last, last.data.length);
1926
+ range.collapse(true);
1927
+ sel.removeAllRanges();
1928
+ sel.addRange(range);
1831
1929
  }
1832
1930
  }
1833
1931
  function installDecorationStyle(editor2) {
@@ -1920,18 +2018,37 @@ function showSuggestionsMenu(parentDoc, x, y, items) {
1920
2018
  menu.style.left = `${Math.max(8, window.innerWidth - r.width - 8)}px`;
1921
2019
  if (r.bottom > window.innerHeight)
1922
2020
  menu.style.top = `${Math.max(8, window.innerHeight - r.height - 8)}px`;
2021
+ const docs = [parentDoc];
2022
+ try {
2023
+ const composeWin = parentDoc.defaultView;
2024
+ if (composeWin?.frameElement && composeWin.parent?.document && composeWin.parent.document !== parentDoc) {
2025
+ docs.push(composeWin.parent.document);
2026
+ }
2027
+ } catch {
2028
+ }
2029
+ try {
2030
+ const editorIframe = parentDoc.querySelector("iframe.tox-edit-area__iframe") || parentDoc.querySelector("iframe");
2031
+ const editorDoc = editorIframe?.contentDocument;
2032
+ if (editorDoc && editorDoc !== parentDoc)
2033
+ docs.push(editorDoc);
2034
+ } catch {
2035
+ }
1923
2036
  const dismiss2 = (e) => {
1924
2037
  if (e.type === "keydown" && e.key !== "Escape")
1925
2038
  return;
1926
2039
  if (e.type === "mousedown" && menu.contains(e.target))
1927
2040
  return;
1928
2041
  menu.remove();
1929
- parentDoc.removeEventListener("mousedown", dismiss2, true);
1930
- parentDoc.removeEventListener("keydown", dismiss2, true);
2042
+ for (const d of docs) {
2043
+ d.removeEventListener("mousedown", dismiss2, true);
2044
+ d.removeEventListener("keydown", dismiss2, true);
2045
+ }
1931
2046
  };
1932
2047
  setTimeout(() => {
1933
- parentDoc.addEventListener("mousedown", dismiss2, true);
1934
- parentDoc.addEventListener("keydown", dismiss2, true);
2048
+ for (const d of docs) {
2049
+ d.addEventListener("mousedown", dismiss2, true);
2050
+ d.addEventListener("keydown", dismiss2, true);
2051
+ }
1935
2052
  }, 0);
1936
2053
  }
1937
2054
  function replaceMarker(editor2, marker, replacement) {
@@ -1993,7 +2110,21 @@ function wireSpellcheck(editor2) {
1993
2110
  return;
1994
2111
  e.preventDefault();
1995
2112
  e.stopPropagation();
1996
- const sugs = sp.suggest(word).slice(0, 7);
2113
+ const transposed = [];
2114
+ for (let i = 0; i < word.length - 1; i++) {
2115
+ const swapped = word.slice(0, i) + word[i + 1] + word[i] + word.slice(i + 2);
2116
+ if (swapped !== word && sp.correct(swapped) && !transposed.includes(swapped)) {
2117
+ transposed.push(swapped);
2118
+ }
2119
+ }
2120
+ const nspellSugs = sp.suggest(word);
2121
+ const sugs = [];
2122
+ for (const s of [...transposed, ...nspellSugs]) {
2123
+ if (!sugs.includes(s))
2124
+ sugs.push(s);
2125
+ if (sugs.length >= 7)
2126
+ break;
2127
+ }
1997
2128
  const iframeEl = editor2.iframeElement;
1998
2129
  const iframeRect = iframeEl ? iframeEl.getBoundingClientRect() : { left: 0, top: 0 };
1999
2130
  const items = [];
@@ -2040,7 +2171,7 @@ var init_spellcheck = __esm({
2040
2171
  import_nspell = __toESM(require_lib(), 1);
2041
2172
  USER_DICT_KEY = "mailx-user-dict";
2042
2173
  MARKER_ATTR = "data-mailx-spellerror";
2043
- DECORATE_DEBOUNCE_MS = 500;
2174
+ DECORATE_DEBOUNCE_MS = 1200;
2044
2175
  MIN_WORD_LEN = 3;
2045
2176
  SKIP_TAGS = /* @__PURE__ */ new Set(["BLOCKQUOTE", "CODE", "PRE", "A", "SCRIPT", "STYLE", "KBD", "SAMP", "VAR"]);
2046
2177
  spellPromise = null;
@@ -2922,6 +3053,8 @@ function showContextMenu(x, y, items) {
2922
3053
  const el = document.createElement("div");
2923
3054
  el.className = "ctx-item" + (item.disabled ? " ctx-disabled" : "");
2924
3055
  el.textContent = item.label;
3056
+ if (item.tooltip)
3057
+ el.title = item.tooltip;
2925
3058
  if (!item.disabled) {
2926
3059
  el.addEventListener("click", () => {
2927
3060
  closeContextMenu();