@bobfrankston/rmfmail 1.2.112 → 1.2.114
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/TODO.md +5 -5
- package/bin/mailx.js +72 -3
- package/bin/mailx.js.map +1 -1
- package/bin/mailx.ts +62 -3
- package/client/android-bootstrap.bundle.js +60 -3
- package/client/android-bootstrap.bundle.js.map +2 -2
- package/client/app.bundle.js +42 -0
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +58 -0
- package/client/app.js.map +1 -1
- package/client/app.ts +60 -0
- package/client/compose/compose.bundle.js +50 -5
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/compose/compose.ts +13 -5
- package/client/compose/spellcheck-core.js +59 -3
- package/client/compose/spellcheck-core.js.map +1 -1
- package/client/compose/spellcheck-core.ts +58 -3
- package/client/compose/spellcheck.js +15 -6
- package/client/compose/spellcheck.js.map +1 -1
- package/client/compose/spellcheck.ts +15 -6
- package/package.json +1 -1
- package/packages/mailx-imap/index.d.ts +4 -1
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +59 -5
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +55 -5
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-store-web/android-bootstrap.js +48 -3
- package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
- package/packages/mailx-store-web/android-bootstrap.ts +47 -4
- package/packages/mailx-store-web/package.json +1 -1
- package/packages/mailx-store-web/web-service.d.ts +21 -0
- package/packages/mailx-store-web/web-service.d.ts.map +1 -1
- package/packages/mailx-store-web/web-service.js +11 -0
- package/packages/mailx-store-web/web-service.js.map +1 -1
- package/packages/mailx-store-web/web-service.ts +11 -0
- package/packages/mailx-types/mailx-api.d.ts +22 -0
- package/packages/mailx-types/mailx-api.d.ts.map +1 -1
- package/packages/mailx-types/mailx-api.ts +7 -0
- package/packages/mailx-types/package.json +1 -1
package/client/app.ts
CHANGED
|
@@ -634,6 +634,63 @@ function scheduleSyncedStamp(): void {
|
|
|
634
634
|
}, SYNC_SETTLE_MS);
|
|
635
635
|
}
|
|
636
636
|
|
|
637
|
+
// C121: connect-phase ticker. The daemon emits connectProgress per IMAP
|
|
638
|
+
// connection attempt (socket → handshake → done/failed). While any attempt
|
|
639
|
+
// has been in flight longer than CONNECT_SHOW_AFTER_MS, the status bar shows
|
|
640
|
+
// "Connecting to <host> (<phase> <purpose>, 3.2s)" with a live elapsed
|
|
641
|
+
// counter — so a stuck DNS/TLS/greeting/LOGIN is distinguishable at a glance
|
|
642
|
+
// instead of an opaque frozen "Syncing…". Healthy sub-second connects never
|
|
643
|
+
// surface (the delay gate kills the flicker).
|
|
644
|
+
const activeConnects = new Map<string, { host: string; purpose: string; phase: string; startedAt: number }>();
|
|
645
|
+
const CONNECT_SHOW_AFTER_MS = 1500;
|
|
646
|
+
let connectTickTimer: ReturnType<typeof setInterval> | null = null;
|
|
647
|
+
function handleConnectProgress(event: any): void {
|
|
648
|
+
const key = `${event.accountId}|${event.purpose}`;
|
|
649
|
+
if (event.phase === "done" || event.phase === "failed") {
|
|
650
|
+
activeConnects.delete(key);
|
|
651
|
+
if (activeConnects.size === 0 && connectTickTimer) {
|
|
652
|
+
clearInterval(connectTickTimer);
|
|
653
|
+
connectTickTimer = null;
|
|
654
|
+
// Leave the bar to the normal sync handlers; stamp settle so a
|
|
655
|
+
// lingering "Connecting…" flips to Synced when things go quiet.
|
|
656
|
+
scheduleSyncedStamp();
|
|
657
|
+
}
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const existing = activeConnects.get(key);
|
|
661
|
+
if (existing) {
|
|
662
|
+
existing.phase = event.phase;
|
|
663
|
+
} else {
|
|
664
|
+
activeConnects.set(key, {
|
|
665
|
+
host: event.host || event.accountId,
|
|
666
|
+
purpose: event.purpose || "",
|
|
667
|
+
phase: event.phase,
|
|
668
|
+
startedAt: Date.now() - (event.elapsedMs || 0),
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
if (!connectTickTimer) connectTickTimer = setInterval(renderConnectTick, 100);
|
|
672
|
+
}
|
|
673
|
+
function renderConnectTick(): void {
|
|
674
|
+
const el = document.getElementById("status-sync");
|
|
675
|
+
if (!el || activeConnects.size === 0) return;
|
|
676
|
+
// Staleness guard: if a done/failed event got lost (daemon restart,
|
|
677
|
+
// dropped IPC line) an entry would tick forever — expire at 10 min,
|
|
678
|
+
// well past any transport timeout.
|
|
679
|
+
const now = Date.now();
|
|
680
|
+
for (const [k, c] of activeConnects) {
|
|
681
|
+
if (now - c.startedAt > 600_000) activeConnects.delete(k);
|
|
682
|
+
}
|
|
683
|
+
// Show the LONGEST-waiting attempt — that's the one worth staring at.
|
|
684
|
+
let worst: { host: string; purpose: string; phase: string; startedAt: number } | null = null;
|
|
685
|
+
for (const c of activeConnects.values()) {
|
|
686
|
+
if (!worst || c.startedAt < worst.startedAt) worst = c;
|
|
687
|
+
}
|
|
688
|
+
if (!worst) return;
|
|
689
|
+
const elapsed = Date.now() - worst.startedAt;
|
|
690
|
+
if (elapsed < CONNECT_SHOW_AFTER_MS) return;
|
|
691
|
+
el.textContent = `Connecting to ${worst.host} (${worst.phase}${worst.purpose ? " " + worst.purpose : ""}, ${(elapsed / 1000).toFixed(1)}s)`;
|
|
692
|
+
}
|
|
693
|
+
|
|
637
694
|
// ── Auto two-line when message list is narrow ──
|
|
638
695
|
const messageList = document.getElementById("message-list");
|
|
639
696
|
if (messageList) {
|
|
@@ -3011,6 +3068,9 @@ onWsEvent((event) => {
|
|
|
3011
3068
|
if (startupStatus) startupStatus.textContent = "Loading accounts...";
|
|
3012
3069
|
// Don't refresh folder tree on connect — it's already loaded by initFolderTree
|
|
3013
3070
|
break;
|
|
3071
|
+
case "connectProgress":
|
|
3072
|
+
handleConnectProgress(event);
|
|
3073
|
+
break;
|
|
3014
3074
|
case "syncProgress": {
|
|
3015
3075
|
// Aggregate folders phases ("folders:<path>" when starting a folder,
|
|
3016
3076
|
// "folders-done" between folders) print as a proportion so the user
|
|
@@ -1650,8 +1650,11 @@ __export(spellcheck_core_exports, {
|
|
|
1650
1650
|
USER_DICT_KEY: () => USER_DICT_KEY,
|
|
1651
1651
|
addToUserDict: () => addToUserDict,
|
|
1652
1652
|
buildSuggestionList: () => buildSuggestionList,
|
|
1653
|
+
findSkipRanges: () => findSkipRanges,
|
|
1653
1654
|
getSpell: () => getSpell,
|
|
1654
1655
|
getWordAtPoint: () => getWordAtPoint,
|
|
1656
|
+
inSkipRange: () => inSkipRange,
|
|
1657
|
+
isWordCorrect: () => isWordCorrect,
|
|
1655
1658
|
showSuggestionsMenu: () => showSuggestionsMenu
|
|
1656
1659
|
});
|
|
1657
1660
|
async function getSpell() {
|
|
@@ -1714,6 +1717,34 @@ function addToUserDict(word, sp) {
|
|
|
1714
1717
|
sp.add(word);
|
|
1715
1718
|
addUserDictWord(word).catch((e) => console.error("[spell] addUserDictWord:", e));
|
|
1716
1719
|
}
|
|
1720
|
+
function isWordCorrect(word, sp) {
|
|
1721
|
+
let w = word.replace(/['’]+$/u, "");
|
|
1722
|
+
w = w.replace(/['’][sS]$/u, "");
|
|
1723
|
+
if (w.length < MIN_WORD_LEN)
|
|
1724
|
+
return true;
|
|
1725
|
+
return sp.correct(w);
|
|
1726
|
+
}
|
|
1727
|
+
function findSkipRanges(text) {
|
|
1728
|
+
if (!/[.@:]/.test(text))
|
|
1729
|
+
return [];
|
|
1730
|
+
const ranges = [];
|
|
1731
|
+
SKIP_SPAN_RE.lastIndex = 0;
|
|
1732
|
+
let m;
|
|
1733
|
+
while (m = SKIP_SPAN_RE.exec(text)) {
|
|
1734
|
+
let end = m.index + m[0].length;
|
|
1735
|
+
while (end > m.index && /[.,;:!?]/.test(text[end - 1]))
|
|
1736
|
+
end--;
|
|
1737
|
+
if (end > m.index)
|
|
1738
|
+
ranges.push([m.index, end]);
|
|
1739
|
+
}
|
|
1740
|
+
return ranges;
|
|
1741
|
+
}
|
|
1742
|
+
function inSkipRange(ranges, start, end) {
|
|
1743
|
+
for (const [s, e] of ranges)
|
|
1744
|
+
if (start < e && end > s)
|
|
1745
|
+
return true;
|
|
1746
|
+
return false;
|
|
1747
|
+
}
|
|
1717
1748
|
function buildSuggestionList(word, sp) {
|
|
1718
1749
|
const transposed = [];
|
|
1719
1750
|
for (let i = 0; i < word.length - 1; i++) {
|
|
@@ -1840,7 +1871,7 @@ function getWordAtPoint(root, x, y) {
|
|
|
1840
1871
|
const text = node.data;
|
|
1841
1872
|
if (offset > text.length)
|
|
1842
1873
|
offset = text.length;
|
|
1843
|
-
const isWordChar = (c) => /[\p{L}'
|
|
1874
|
+
const isWordChar = (c) => /[\p{L}'’]/u.test(c);
|
|
1844
1875
|
let start = offset;
|
|
1845
1876
|
while (start > 0 && isWordChar(text[start - 1]))
|
|
1846
1877
|
start--;
|
|
@@ -1852,9 +1883,11 @@ function getWordAtPoint(root, x, y) {
|
|
|
1852
1883
|
const word = text.slice(start, end);
|
|
1853
1884
|
if (!/^[\p{L}]/u.test(word))
|
|
1854
1885
|
return null;
|
|
1886
|
+
if (inSkipRange(findSkipRanges(text), start, end))
|
|
1887
|
+
return null;
|
|
1855
1888
|
return { word, node, start, end };
|
|
1856
1889
|
}
|
|
1857
|
-
var import_nspell, USER_DICT_KEY, MARKER_ATTR, MIN_WORD_LEN, SKIP_TAGS, spellPromise;
|
|
1890
|
+
var import_nspell, USER_DICT_KEY, MARKER_ATTR, MIN_WORD_LEN, SKIP_TAGS, spellPromise, URL_BODY, SKIP_SPAN_RE;
|
|
1858
1891
|
var init_spellcheck_core = __esm({
|
|
1859
1892
|
"client/compose/spellcheck-core.js"() {
|
|
1860
1893
|
"use strict";
|
|
@@ -1865,6 +1898,15 @@ var init_spellcheck_core = __esm({
|
|
|
1865
1898
|
MIN_WORD_LEN = 3;
|
|
1866
1899
|
SKIP_TAGS = /* @__PURE__ */ new Set(["BLOCKQUOTE", "CODE", "PRE", "A", "SCRIPT", "STYLE", "KBD", "SAMP", "VAR"]);
|
|
1867
1900
|
spellPromise = null;
|
|
1901
|
+
URL_BODY = String.raw`[\w.\-~:/?#@$&+=%!*]`;
|
|
1902
|
+
SKIP_SPAN_RE = new RegExp(
|
|
1903
|
+
String.raw`(?:(?:[A-Za-z][A-Za-z\d.+-]{1,14}://|mailto:|tel:)${URL_BODY}+` + // scheme'd URL
|
|
1904
|
+
String.raw`|www\.${URL_BODY}+` + // www.…
|
|
1905
|
+
String.raw`|[\w.+\-]+@[\w\-]+(?:\.[\w\-]+)+` + // email
|
|
1906
|
+
String.raw`|[\w\-]+(?:\.[A-Za-z][\w\-]*)+(?:/${URL_BODY}*)?)`,
|
|
1907
|
+
// bare domain [+path]
|
|
1908
|
+
"g"
|
|
1909
|
+
);
|
|
1868
1910
|
}
|
|
1869
1911
|
});
|
|
1870
1912
|
|
|
@@ -2544,18 +2586,21 @@ function wireSpellcheck(editor2) {
|
|
|
2544
2586
|
const sx = body.scrollLeft;
|
|
2545
2587
|
const sy = body.scrollTop;
|
|
2546
2588
|
const frag = doc.createDocumentFragment();
|
|
2547
|
-
const wordRe = /[\p{L}][\p{L}'
|
|
2589
|
+
const wordRe = /[\p{L}][\p{L}'’]*/gu;
|
|
2548
2590
|
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
2549
2591
|
const text = n.data;
|
|
2550
2592
|
if (!text || text.length < MIN_WORD_LEN)
|
|
2551
2593
|
continue;
|
|
2594
|
+
const skip = findSkipRanges(text);
|
|
2552
2595
|
wordRe.lastIndex = 0;
|
|
2553
2596
|
let m;
|
|
2554
2597
|
while (m = wordRe.exec(text)) {
|
|
2555
2598
|
const w = m[0];
|
|
2556
2599
|
if (w.length < MIN_WORD_LEN)
|
|
2557
2600
|
continue;
|
|
2558
|
-
if (
|
|
2601
|
+
if (inSkipRange(skip, m.index, m.index + w.length))
|
|
2602
|
+
continue;
|
|
2603
|
+
if (isWordCorrect(w, sp))
|
|
2559
2604
|
continue;
|
|
2560
2605
|
const r = doc.createRange();
|
|
2561
2606
|
r.setStart(n, m.index);
|
|
@@ -2626,7 +2671,7 @@ function wireSpellcheck(editor2) {
|
|
|
2626
2671
|
const hit = getWordAtPoint(body, e.clientX, e.clientY);
|
|
2627
2672
|
if (!hit)
|
|
2628
2673
|
return;
|
|
2629
|
-
if (
|
|
2674
|
+
if (isWordCorrect(hit.word, sp))
|
|
2630
2675
|
return;
|
|
2631
2676
|
e.preventDefault();
|
|
2632
2677
|
e.stopPropagation();
|