@bobfrankston/rmfmail 1.2.258 → 1.2.260
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 +172 -1
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +11 -1
- package/client/app.js.map +1 -1
- package/client/app.ts +11 -1
- package/client/components/message-viewer.js +211 -0
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +189 -0
- package/client/compose/compose.bundle.js +5 -7
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/compose/harper-lint.js +10 -3
- package/client/compose/harper-lint.js.map +1 -1
- package/client/compose/harper-lint.ts +10 -3
- package/client/compose/spellcheck.js +19 -9
- package/client/compose/spellcheck.js.map +1 -1
- package/client/compose/spellcheck.ts +19 -9
- package/client/package.json +1 -1
- package/client/styles/components.css +5 -0
- package/package.json +3 -3
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-37720 → node_modules.npmglobalize-stash-41916}/.package-lock.json +0 -0
package/client/app.bundle.js
CHANGED
|
@@ -1653,12 +1653,15 @@ __export(message_viewer_exports, {
|
|
|
1653
1653
|
getCurrentMessage: () => getCurrentMessage,
|
|
1654
1654
|
initViewer: () => initViewer,
|
|
1655
1655
|
invalidateParsedCache: () => invalidateParsedCache,
|
|
1656
|
+
parseHighlightTerms: () => parseHighlightTerms,
|
|
1656
1657
|
popOutCurrentMessage: () => popOutCurrentMessage,
|
|
1657
1658
|
popOutToWindow: () => popOutToWindow,
|
|
1658
1659
|
printCurrentMessage: () => printCurrentMessage,
|
|
1659
1660
|
printMessage: () => printMessage,
|
|
1661
|
+
refreshSearchHighlight: () => refreshSearchHighlight,
|
|
1660
1662
|
saveCurrentMessageAsEml: () => saveCurrentMessageAsEml,
|
|
1661
1663
|
saveCurrentMessageAsHtml: () => saveCurrentMessageAsHtml,
|
|
1664
|
+
setSearchHighlightTerms: () => setSearchHighlightTerms,
|
|
1662
1665
|
showMessage: () => showMessage,
|
|
1663
1666
|
showPreviewBodyMenu: () => showPreviewBodyMenu,
|
|
1664
1667
|
spawnDesktopPopout: () => spawnDesktopPopout,
|
|
@@ -2084,12 +2087,157 @@ async function translateAndShow(text) {
|
|
|
2084
2087
|
status.textContent = `Translate error: ${err?.message || ""}`;
|
|
2085
2088
|
}
|
|
2086
2089
|
}
|
|
2090
|
+
function parseHighlightTerms(query) {
|
|
2091
|
+
const parts = (query || "").match(/"[^"]*"|\S+/g) || [];
|
|
2092
|
+
const out = [];
|
|
2093
|
+
let negate = false;
|
|
2094
|
+
for (const raw of parts) {
|
|
2095
|
+
const part = raw.replace(/^"|"$/g, "");
|
|
2096
|
+
if (/^(AND|OR)$/i.test(part))
|
|
2097
|
+
continue;
|
|
2098
|
+
if (/^NOT$/i.test(part)) {
|
|
2099
|
+
negate = true;
|
|
2100
|
+
continue;
|
|
2101
|
+
}
|
|
2102
|
+
if (negate) {
|
|
2103
|
+
negate = false;
|
|
2104
|
+
continue;
|
|
2105
|
+
}
|
|
2106
|
+
if (part.startsWith("-"))
|
|
2107
|
+
continue;
|
|
2108
|
+
const q = part.match(/^([a-z]+):(.*)$/i);
|
|
2109
|
+
if (q) {
|
|
2110
|
+
const [, field, value] = q;
|
|
2111
|
+
if (/^subject$/i.test(field) && value)
|
|
2112
|
+
out.push(value.replace(/^"|"$/g, ""));
|
|
2113
|
+
continue;
|
|
2114
|
+
}
|
|
2115
|
+
if (part.length >= 2)
|
|
2116
|
+
out.push(part);
|
|
2117
|
+
}
|
|
2118
|
+
return Array.from(new Set(out.map((t) => t.toLowerCase()))).sort((a, b) => b.length - a.length);
|
|
2119
|
+
}
|
|
2120
|
+
function collectHighlightRanges(doc, terms) {
|
|
2121
|
+
const ranges = [];
|
|
2122
|
+
const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT, {
|
|
2123
|
+
acceptNode(node) {
|
|
2124
|
+
const tag = (node.parentElement?.tagName || "").toUpperCase();
|
|
2125
|
+
if (tag === "SCRIPT" || tag === "STYLE" || tag === "NOSCRIPT")
|
|
2126
|
+
return NodeFilter.FILTER_REJECT;
|
|
2127
|
+
return node.nodeValue && node.nodeValue.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
2128
|
+
}
|
|
2129
|
+
});
|
|
2130
|
+
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
2131
|
+
const hay = n.data.toLowerCase();
|
|
2132
|
+
const taken = [];
|
|
2133
|
+
for (const term of terms) {
|
|
2134
|
+
let from = 0;
|
|
2135
|
+
for (; ; ) {
|
|
2136
|
+
const at = hay.indexOf(term, from);
|
|
2137
|
+
if (at < 0)
|
|
2138
|
+
break;
|
|
2139
|
+
const end = at + term.length;
|
|
2140
|
+
from = end;
|
|
2141
|
+
if (taken.some(([s, e]) => at < e && end > s))
|
|
2142
|
+
continue;
|
|
2143
|
+
taken.push([at, end]);
|
|
2144
|
+
const r = doc.createRange();
|
|
2145
|
+
r.setStart(n, at);
|
|
2146
|
+
r.setEnd(n, end);
|
|
2147
|
+
ranges.push(r);
|
|
2148
|
+
if (ranges.length >= HIGHLIGHT_MAX)
|
|
2149
|
+
return ranges;
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
return ranges;
|
|
2154
|
+
}
|
|
2155
|
+
function applySearchHighlight(iframe, scrollToFirst = false) {
|
|
2156
|
+
const doc = iframe.contentDocument;
|
|
2157
|
+
const win = iframe.contentWindow;
|
|
2158
|
+
if (!doc?.body || !win?.CSS?.highlights || typeof win.Highlight !== "function")
|
|
2159
|
+
return 0;
|
|
2160
|
+
try {
|
|
2161
|
+
win.CSS.highlights.delete(HIGHLIGHT_NAME);
|
|
2162
|
+
if (searchHighlightTerms.length === 0)
|
|
2163
|
+
return 0;
|
|
2164
|
+
const ranges = collectHighlightRanges(doc, searchHighlightTerms);
|
|
2165
|
+
if (ranges.length === 0)
|
|
2166
|
+
return 0;
|
|
2167
|
+
win.CSS.highlights.set(HIGHLIGHT_NAME, new win.Highlight(...ranges));
|
|
2168
|
+
if (scrollToFirst) {
|
|
2169
|
+
const rect = ranges[0].getBoundingClientRect();
|
|
2170
|
+
const h = doc.documentElement.clientHeight || 0;
|
|
2171
|
+
if (rect.height > 0 && (rect.top < 0 || rect.bottom > h)) {
|
|
2172
|
+
const target = (doc.scrollingElement || doc.documentElement).scrollTop + rect.top - h / 3;
|
|
2173
|
+
(doc.scrollingElement || doc.documentElement).scrollTop = Math.max(0, target);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
return ranges.length;
|
|
2177
|
+
} catch {
|
|
2178
|
+
return 0;
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
function applyHeaderHighlight() {
|
|
2182
|
+
const anyWin = window;
|
|
2183
|
+
if (!anyWin.CSS?.highlights || typeof anyWin.Highlight !== "function")
|
|
2184
|
+
return;
|
|
2185
|
+
try {
|
|
2186
|
+
anyWin.CSS.highlights.delete(HIGHLIGHT_NAME);
|
|
2187
|
+
const subj = document.querySelector(".mv-subject");
|
|
2188
|
+
if (!subj || searchHighlightTerms.length === 0)
|
|
2189
|
+
return;
|
|
2190
|
+
const ranges = collectHighlightRangesIn(subj, searchHighlightTerms);
|
|
2191
|
+
if (ranges.length)
|
|
2192
|
+
anyWin.CSS.highlights.set(HIGHLIGHT_NAME, new anyWin.Highlight(...ranges));
|
|
2193
|
+
} catch {
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
function collectHighlightRangesIn(root, terms) {
|
|
2197
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
2198
|
+
const ranges = [];
|
|
2199
|
+
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
2200
|
+
const hay = n.data.toLowerCase();
|
|
2201
|
+
for (const term of terms) {
|
|
2202
|
+
let from = 0;
|
|
2203
|
+
for (; ; ) {
|
|
2204
|
+
const at = hay.indexOf(term, from);
|
|
2205
|
+
if (at < 0)
|
|
2206
|
+
break;
|
|
2207
|
+
from = at + term.length;
|
|
2208
|
+
const r = document.createRange();
|
|
2209
|
+
r.setStart(n, at);
|
|
2210
|
+
r.setEnd(n, from);
|
|
2211
|
+
ranges.push(r);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
return ranges;
|
|
2216
|
+
}
|
|
2217
|
+
function setSearchHighlightTerms(terms) {
|
|
2218
|
+
const next = terms.filter(Boolean).map((t) => t.toLowerCase());
|
|
2219
|
+
const same = next.length === searchHighlightTerms.length && next.every((t, i) => t === searchHighlightTerms[i]);
|
|
2220
|
+
if (same)
|
|
2221
|
+
return;
|
|
2222
|
+
searchHighlightTerms = next;
|
|
2223
|
+
applyHeaderHighlight();
|
|
2224
|
+
for (const f of Array.from(document.querySelectorAll("iframe"))) {
|
|
2225
|
+
applySearchHighlight(f, true);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
function refreshSearchHighlight(iframe, scrollToFirst = false) {
|
|
2229
|
+
if (searchHighlightTerms.length === 0)
|
|
2230
|
+
return;
|
|
2231
|
+
applySearchHighlight(iframe, scrollToFirst);
|
|
2232
|
+
applyHeaderHighlight();
|
|
2233
|
+
}
|
|
2087
2234
|
function installPreviewControls(iframe) {
|
|
2088
2235
|
const attach = () => {
|
|
2089
2236
|
const doc = iframe.contentDocument;
|
|
2090
2237
|
if (!doc)
|
|
2091
2238
|
return;
|
|
2092
2239
|
applyZoom(doc);
|
|
2240
|
+
refreshSearchHighlight(iframe, true);
|
|
2093
2241
|
doc.addEventListener("keydown", (e) => {
|
|
2094
2242
|
const target = e.target;
|
|
2095
2243
|
if (target && (target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName)))
|
|
@@ -2136,6 +2284,16 @@ function installPreviewControls(iframe) {
|
|
|
2136
2284
|
attach();
|
|
2137
2285
|
else
|
|
2138
2286
|
iframe.addEventListener("load", attach, { once: true });
|
|
2287
|
+
queueMicrotask(() => {
|
|
2288
|
+
const doc = iframe.contentDocument;
|
|
2289
|
+
if (!doc)
|
|
2290
|
+
return;
|
|
2291
|
+
if (doc.readyState === "loading") {
|
|
2292
|
+
doc.addEventListener("DOMContentLoaded", () => refreshSearchHighlight(iframe, true), { once: true });
|
|
2293
|
+
} else {
|
|
2294
|
+
refreshSearchHighlight(iframe, true);
|
|
2295
|
+
}
|
|
2296
|
+
});
|
|
2139
2297
|
}
|
|
2140
2298
|
function clearViewer() {
|
|
2141
2299
|
currentMessage = null;
|
|
@@ -3144,6 +3302,8 @@ function appendTextProgressively(iframe, rest, gen) {
|
|
|
3144
3302
|
const span = doc.createElement("span");
|
|
3145
3303
|
span.innerHTML = linkifyText(chunks[i++]);
|
|
3146
3304
|
host.appendChild(span);
|
|
3305
|
+
if (i >= chunks.length)
|
|
3306
|
+
refreshSearchHighlight(iframe);
|
|
3147
3307
|
requestAnimationFrame(step);
|
|
3148
3308
|
};
|
|
3149
3309
|
let waits = 0;
|
|
@@ -3305,6 +3465,12 @@ ${csp}
|
|
|
3305
3465
|
word-break: break-word;
|
|
3306
3466
|
}
|
|
3307
3467
|
blockquote { border-left: 3px solid #ccc; padding-left: 1rem; margin-left: 0; color: #666; }
|
|
3468
|
+
/* Search matches. Painted by the parent through the CSS Custom Highlight
|
|
3469
|
+
API (CSS.highlights) \u2014 no <mark> in the message DOM, so the letter the
|
|
3470
|
+
user copies, quotes or re-renders is byte-identical to what arrived.
|
|
3471
|
+
Both colors are explicit: a highlight that inherits the body color is
|
|
3472
|
+
invisible against its own background in one theme or the other. */
|
|
3473
|
+
::highlight(mailx-find) { background: #ffd54a; color: #1a1a2e; }
|
|
3308
3474
|
@media (prefers-color-scheme: dark) {
|
|
3309
3475
|
body { color: #cdd6f4; background: #282840; }
|
|
3310
3476
|
a { color: #89b4fa; }
|
|
@@ -3859,7 +4025,7 @@ function spawnDesktopPopout(msg, accountId) {
|
|
|
3859
4025
|
function escapeHtmlLocal(s) {
|
|
3860
4026
|
return (s || "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3861
4027
|
}
|
|
3862
|
-
var pendingImageCopies, imageCopySeq, IMAGE_COPY_TIMEOUT_MS, currentMessage, currentAccountId, dragOutUrls, showMessageGeneration, retryCount, lastEnvelope, PARSED_CACHE_LIMIT, parsedCache, sessionAllowedRemote, recentFetchErrors, WAIT_GIVE_UP_SECS, ZOOM_KEY, ZOOM_MIN, ZOOM_MAX, ZOOM_STEP, previewZoom, RENDER_MARK_KEY, RENDER_MARK_MAX_AGE_MS, poisonedMessages, renderKey, PROGRESSIVE_TEXT_THRESHOLD, TEXT_CHUNK_BYTES;
|
|
4028
|
+
var pendingImageCopies, imageCopySeq, IMAGE_COPY_TIMEOUT_MS, currentMessage, currentAccountId, dragOutUrls, showMessageGeneration, retryCount, lastEnvelope, PARSED_CACHE_LIMIT, parsedCache, sessionAllowedRemote, recentFetchErrors, WAIT_GIVE_UP_SECS, ZOOM_KEY, ZOOM_MIN, ZOOM_MAX, ZOOM_STEP, previewZoom, HIGHLIGHT_NAME, HIGHLIGHT_MAX, searchHighlightTerms, RENDER_MARK_KEY, RENDER_MARK_MAX_AGE_MS, poisonedMessages, renderKey, PROGRESSIVE_TEXT_THRESHOLD, TEXT_CHUNK_BYTES;
|
|
3863
4029
|
var init_message_viewer = __esm({
|
|
3864
4030
|
"client/components/message-viewer.js"() {
|
|
3865
4031
|
"use strict";
|
|
@@ -3897,6 +4063,9 @@ var init_message_viewer = __esm({
|
|
|
3897
4063
|
ZOOM_MAX = 3;
|
|
3898
4064
|
ZOOM_STEP = 0.1;
|
|
3899
4065
|
previewZoom = clampZoom(parseFloat(localStorage.getItem(ZOOM_KEY) || "1.15"));
|
|
4066
|
+
HIGHLIGHT_NAME = "mailx-find";
|
|
4067
|
+
HIGHLIGHT_MAX = 2e3;
|
|
4068
|
+
searchHighlightTerms = [];
|
|
3900
4069
|
subscribeStore("*", (ev) => {
|
|
3901
4070
|
if (ev?.kind !== "messageRemoved")
|
|
3902
4071
|
return;
|
|
@@ -10691,6 +10860,7 @@ function renderSearchHighlight(settled) {
|
|
|
10691
10860
|
}
|
|
10692
10861
|
function updateSearchHighlight() {
|
|
10693
10862
|
renderSearchHighlight(false);
|
|
10863
|
+
setSearchHighlightTerms(parseHighlightTerms(searchInput?.value || ""));
|
|
10694
10864
|
if (searchRegexSettleTimer) clearTimeout(searchRegexSettleTimer);
|
|
10695
10865
|
searchRegexSettleTimer = setTimeout(() => renderSearchHighlight(true), SEARCH_REGEX_SETTLE_MS);
|
|
10696
10866
|
}
|
|
@@ -10703,6 +10873,7 @@ var SERVER_SEARCH_DALLY_MS = 700;
|
|
|
10703
10873
|
var serverSearchTimer = null;
|
|
10704
10874
|
function doSearch(immediate = false) {
|
|
10705
10875
|
const query = searchInput.value.trim();
|
|
10876
|
+
setSearchHighlightTerms(parseHighlightTerms(query));
|
|
10706
10877
|
if (query.length === 0) {
|
|
10707
10878
|
if (serverSearchTimer) {
|
|
10708
10879
|
clearTimeout(serverSearchTimer);
|