@bobfrankston/rmfmail 1.2.259 → 1.2.261
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 +176 -3
- 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 +227 -2
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +204 -1
- 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-39528 → node_modules.npmglobalize-stash-62112}/.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,160 @@ 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;
|
|
2239
|
+
if (doc.__mvControlsBound)
|
|
2240
|
+
return;
|
|
2241
|
+
doc.__mvControlsBound = true;
|
|
2092
2242
|
applyZoom(doc);
|
|
2243
|
+
refreshSearchHighlight(iframe, true);
|
|
2093
2244
|
doc.addEventListener("keydown", (e) => {
|
|
2094
2245
|
const target = e.target;
|
|
2095
2246
|
if (target && (target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName)))
|
|
@@ -2132,10 +2283,19 @@ function installPreviewControls(iframe) {
|
|
|
2132
2283
|
setZoom(previewZoom + (e.deltaY < 0 ? ZOOM_STEP : -ZOOM_STEP), doc);
|
|
2133
2284
|
}, { passive: false });
|
|
2134
2285
|
};
|
|
2286
|
+
iframe.addEventListener("load", attach);
|
|
2135
2287
|
if (iframe.contentDocument?.readyState === "complete")
|
|
2136
2288
|
attach();
|
|
2137
|
-
|
|
2138
|
-
|
|
2289
|
+
queueMicrotask(() => {
|
|
2290
|
+
const doc = iframe.contentDocument;
|
|
2291
|
+
if (!doc)
|
|
2292
|
+
return;
|
|
2293
|
+
if (doc.readyState === "loading") {
|
|
2294
|
+
doc.addEventListener("DOMContentLoaded", () => refreshSearchHighlight(iframe, true), { once: true });
|
|
2295
|
+
} else {
|
|
2296
|
+
refreshSearchHighlight(iframe, true);
|
|
2297
|
+
}
|
|
2298
|
+
});
|
|
2139
2299
|
}
|
|
2140
2300
|
function clearViewer() {
|
|
2141
2301
|
currentMessage = null;
|
|
@@ -3144,6 +3304,8 @@ function appendTextProgressively(iframe, rest, gen) {
|
|
|
3144
3304
|
const span = doc.createElement("span");
|
|
3145
3305
|
span.innerHTML = linkifyText(chunks[i++]);
|
|
3146
3306
|
host.appendChild(span);
|
|
3307
|
+
if (i >= chunks.length)
|
|
3308
|
+
refreshSearchHighlight(iframe);
|
|
3147
3309
|
requestAnimationFrame(step);
|
|
3148
3310
|
};
|
|
3149
3311
|
let waits = 0;
|
|
@@ -3305,6 +3467,12 @@ ${csp}
|
|
|
3305
3467
|
word-break: break-word;
|
|
3306
3468
|
}
|
|
3307
3469
|
blockquote { border-left: 3px solid #ccc; padding-left: 1rem; margin-left: 0; color: #666; }
|
|
3470
|
+
/* Search matches. Painted by the parent through the CSS Custom Highlight
|
|
3471
|
+
API (CSS.highlights) \u2014 no <mark> in the message DOM, so the letter the
|
|
3472
|
+
user copies, quotes or re-renders is byte-identical to what arrived.
|
|
3473
|
+
Both colors are explicit: a highlight that inherits the body color is
|
|
3474
|
+
invisible against its own background in one theme or the other. */
|
|
3475
|
+
::highlight(mailx-find) { background: #ffd54a; color: #1a1a2e; }
|
|
3308
3476
|
@media (prefers-color-scheme: dark) {
|
|
3309
3477
|
body { color: #cdd6f4; background: #282840; }
|
|
3310
3478
|
a { color: #89b4fa; }
|
|
@@ -3859,7 +4027,7 @@ function spawnDesktopPopout(msg, accountId) {
|
|
|
3859
4027
|
function escapeHtmlLocal(s) {
|
|
3860
4028
|
return (s || "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3861
4029
|
}
|
|
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;
|
|
4030
|
+
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
4031
|
var init_message_viewer = __esm({
|
|
3864
4032
|
"client/components/message-viewer.js"() {
|
|
3865
4033
|
"use strict";
|
|
@@ -3897,6 +4065,9 @@ var init_message_viewer = __esm({
|
|
|
3897
4065
|
ZOOM_MAX = 3;
|
|
3898
4066
|
ZOOM_STEP = 0.1;
|
|
3899
4067
|
previewZoom = clampZoom(parseFloat(localStorage.getItem(ZOOM_KEY) || "1.15"));
|
|
4068
|
+
HIGHLIGHT_NAME = "mailx-find";
|
|
4069
|
+
HIGHLIGHT_MAX = 2e3;
|
|
4070
|
+
searchHighlightTerms = [];
|
|
3900
4071
|
subscribeStore("*", (ev) => {
|
|
3901
4072
|
if (ev?.kind !== "messageRemoved")
|
|
3902
4073
|
return;
|
|
@@ -10691,6 +10862,7 @@ function renderSearchHighlight(settled) {
|
|
|
10691
10862
|
}
|
|
10692
10863
|
function updateSearchHighlight() {
|
|
10693
10864
|
renderSearchHighlight(false);
|
|
10865
|
+
setSearchHighlightTerms(parseHighlightTerms(searchInput?.value || ""));
|
|
10694
10866
|
if (searchRegexSettleTimer) clearTimeout(searchRegexSettleTimer);
|
|
10695
10867
|
searchRegexSettleTimer = setTimeout(() => renderSearchHighlight(true), SEARCH_REGEX_SETTLE_MS);
|
|
10696
10868
|
}
|
|
@@ -10703,6 +10875,7 @@ var SERVER_SEARCH_DALLY_MS = 700;
|
|
|
10703
10875
|
var serverSearchTimer = null;
|
|
10704
10876
|
function doSearch(immediate = false) {
|
|
10705
10877
|
const query = searchInput.value.trim();
|
|
10878
|
+
setSearchHighlightTerms(parseHighlightTerms(query));
|
|
10706
10879
|
if (query.length === 0) {
|
|
10707
10880
|
if (serverSearchTimer) {
|
|
10708
10881
|
clearTimeout(serverSearchTimer);
|