@bobfrankston/rmfmail 1.2.237 → 1.2.239
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 +6 -0
- package/client/app.bundle.js +60 -17
- package/client/app.bundle.js.map +2 -2
- package/client/components/message-viewer.js +98 -58
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +93 -57
- package/client/compose/compose.bundle.js +2 -2
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/compose/compose.js +5 -2
- package/client/compose/compose.js.map +1 -1
- package/client/compose/compose.ts +5 -2
- package/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-40840 → node_modules.npmglobalize-stash-57024}/.package-lock.json +0 -0
package/TODO.md
CHANGED
|
@@ -693,6 +693,12 @@ This is not tidiness. The RFC 2047 header fix on 2026-08-08 was hand-applied to
|
|
|
693
693
|
- ~~**Context menus** — three clipboard implementations (rich editor, compose recipient field, nothing at all elsewhere)~~ — **FIXED v1.2.230**, see DONE.md.
|
|
694
694
|
- **`escapeHtml`** — 15 separate definitions across client + packages. [S]
|
|
695
695
|
|
|
696
|
+
<a id="ext163"></a>
|
|
697
|
+
|
|
698
|
+
### C163 — Edit-in-Word documents open in Compatibility Mode [↑ summary](#sum)
|
|
699
|
+
|
|
700
|
+
Word's title bar reads "<file> — **Compatibility Mode** — Word" for every Edit-in-Word hand-off: `html-to-docx` emits a document Word treats as an older format. Measured 2026-08-09 while chasing the round-trip bug; it does NOT disable proofing (Word reported `NoProofing=0`, `LanguageID=1033` and flagged 3/3 planted misspellings through COM), so it is not the cause of "it didn't do a spell check" — but it does disable newer Word features and is a plausible source of round-trip fidelity loss through mammoth. Worth either configuring html-to-docx to emit a current-format docx or checking whether a `w:compat` block / missing `docProps/app.xml` is what trips it. [S/M]
|
|
701
|
+
|
|
696
702
|
## Priority 1 — Get basics working [↑ top](#top)
|
|
697
703
|
|
|
698
704
|
### External content security
|
package/client/app.bundle.js
CHANGED
|
@@ -2701,13 +2701,6 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
2701
2701
|
_ptick("iframe srcdoc set");
|
|
2702
2702
|
installPreviewControls(iframe);
|
|
2703
2703
|
} else if (msg.bodyText) {
|
|
2704
|
-
const pre = document.createElement("pre");
|
|
2705
|
-
pre.style.cssText = "padding: 1rem; white-space: pre-wrap; word-break: break-word; font-family: system-ui, sans-serif; font-size: 17.5px; line-height: 1.5; color: #1a1a2e; background: #fff; margin: 0; height: 100%; overflow: auto;";
|
|
2706
|
-
if (msg.bodyText.length > PROGRESSIVE_TEXT_THRESHOLD) {
|
|
2707
|
-
renderTextProgressively(pre, msg.bodyText, gen);
|
|
2708
|
-
} else {
|
|
2709
|
-
pre.innerHTML = linkifyText(msg.bodyText);
|
|
2710
|
-
}
|
|
2711
2704
|
const trunc = msg.bodyTruncated;
|
|
2712
2705
|
if (trunc) {
|
|
2713
2706
|
const mb = (n) => `${(n / 1048576).toFixed(1)} MB`;
|
|
@@ -2721,7 +2714,21 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
2721
2714
|
<code style="user-select:all;font-size:0.9em">${escapeHtml3(trunc.emlPath || "(path unavailable)")}</code></div>`;
|
|
2722
2715
|
bodyEl.appendChild(note);
|
|
2723
2716
|
}
|
|
2724
|
-
|
|
2717
|
+
const iframe = document.createElement("iframe");
|
|
2718
|
+
iframe.sandbox.add("allow-same-origin");
|
|
2719
|
+
iframe.sandbox.add("allow-popups");
|
|
2720
|
+
iframe.sandbox.add("allow-popups-to-escape-sandbox");
|
|
2721
|
+
iframe.sandbox.add("allow-top-navigation-by-user-activation");
|
|
2722
|
+
iframe.sandbox.add("allow-scripts");
|
|
2723
|
+
const head = msg.bodyText.length > PROGRESSIVE_TEXT_THRESHOLD ? msg.bodyText.slice(0, firstChunkEnd(msg.bodyText)) : msg.bodyText;
|
|
2724
|
+
iframe.srcdoc = wrapHtmlBody(`<pre class="rmf-plain" id="rmf-plain">${linkifyText(head)}</pre>`, false);
|
|
2725
|
+
iframe.addEventListener("load", () => _ptick("iframe load (all resources)"), { once: true });
|
|
2726
|
+
bodyEl.appendChild(iframe);
|
|
2727
|
+
_ptick("iframe srcdoc set (plain text)");
|
|
2728
|
+
installPreviewControls(iframe);
|
|
2729
|
+
if (msg.bodyText.length > head.length) {
|
|
2730
|
+
appendTextProgressively(iframe, msg.bodyText.slice(head.length), gen);
|
|
2731
|
+
}
|
|
2725
2732
|
} else {
|
|
2726
2733
|
const fetchErr = recentFetchErrors.get(`${accountId}:${uid}`);
|
|
2727
2734
|
if (fetchErr) {
|
|
@@ -2973,31 +2980,54 @@ function renderHeaderFromEnvelope(headerEl, env) {
|
|
|
2973
2980
|
function escapeHtml3(s) {
|
|
2974
2981
|
return (s || "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
2975
2982
|
}
|
|
2976
|
-
function
|
|
2983
|
+
function firstChunkEnd(text) {
|
|
2984
|
+
const end = Math.min(TEXT_CHUNK_BYTES, text.length);
|
|
2985
|
+
if (end >= text.length)
|
|
2986
|
+
return text.length;
|
|
2987
|
+
const nl = text.lastIndexOf("\n", end);
|
|
2988
|
+
return nl > 0 ? nl + 1 : end;
|
|
2989
|
+
}
|
|
2990
|
+
function appendTextProgressively(iframe, rest, gen) {
|
|
2977
2991
|
const chunks = [];
|
|
2978
2992
|
let at = 0;
|
|
2979
|
-
while (at <
|
|
2980
|
-
let end = Math.min(at + TEXT_CHUNK_BYTES,
|
|
2981
|
-
if (end <
|
|
2982
|
-
const nl =
|
|
2993
|
+
while (at < rest.length) {
|
|
2994
|
+
let end = Math.min(at + TEXT_CHUNK_BYTES, rest.length);
|
|
2995
|
+
if (end < rest.length) {
|
|
2996
|
+
const nl = rest.lastIndexOf("\n", end);
|
|
2983
2997
|
if (nl > at)
|
|
2984
2998
|
end = nl + 1;
|
|
2985
2999
|
}
|
|
2986
|
-
chunks.push(
|
|
3000
|
+
chunks.push(rest.slice(at, end));
|
|
2987
3001
|
at = end;
|
|
2988
3002
|
}
|
|
2989
3003
|
let i = 0;
|
|
2990
3004
|
const step = () => {
|
|
2991
3005
|
if (gen !== showMessageGeneration)
|
|
2992
3006
|
return;
|
|
3007
|
+
const doc = iframe.contentDocument;
|
|
3008
|
+
const host = doc?.getElementById("rmf-plain");
|
|
3009
|
+
if (!doc || !host)
|
|
3010
|
+
return;
|
|
2993
3011
|
if (i >= chunks.length)
|
|
2994
3012
|
return;
|
|
2995
|
-
const span =
|
|
3013
|
+
const span = doc.createElement("span");
|
|
2996
3014
|
span.innerHTML = linkifyText(chunks[i++]);
|
|
2997
|
-
|
|
3015
|
+
host.appendChild(span);
|
|
2998
3016
|
requestAnimationFrame(step);
|
|
2999
3017
|
};
|
|
3000
|
-
|
|
3018
|
+
let waits = 0;
|
|
3019
|
+
const begin = () => {
|
|
3020
|
+
if (gen !== showMessageGeneration)
|
|
3021
|
+
return;
|
|
3022
|
+
if (iframe.contentDocument?.getElementById("rmf-plain")) {
|
|
3023
|
+
step();
|
|
3024
|
+
return;
|
|
3025
|
+
}
|
|
3026
|
+
if (waits++ > 120)
|
|
3027
|
+
return;
|
|
3028
|
+
requestAnimationFrame(begin);
|
|
3029
|
+
};
|
|
3030
|
+
requestAnimationFrame(begin);
|
|
3001
3031
|
}
|
|
3002
3032
|
function linkifyText(text) {
|
|
3003
3033
|
const escaped = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -3130,6 +3160,19 @@ ${csp}
|
|
|
3130
3160
|
img { max-width: 100%; }
|
|
3131
3161
|
a { color: #1a6dd4; }
|
|
3132
3162
|
pre, code { white-space: pre-wrap; }
|
|
3163
|
+
/* Plain-text bodies render as this <pre>. Sans-serif at the same size and
|
|
3164
|
+
line-height as an HTML body, so two messages in one thread don't look
|
|
3165
|
+
like they came from different apps (Bob 2026-05-12: "why the font
|
|
3166
|
+
change"). */
|
|
3167
|
+
.rmf-plain {
|
|
3168
|
+
margin: 0;
|
|
3169
|
+
padding: 0;
|
|
3170
|
+
font-family: system-ui, sans-serif;
|
|
3171
|
+
font-size: 17.5px;
|
|
3172
|
+
line-height: 1.5;
|
|
3173
|
+
white-space: pre-wrap;
|
|
3174
|
+
word-break: break-word;
|
|
3175
|
+
}
|
|
3133
3176
|
blockquote { border-left: 3px solid #ccc; padding-left: 1rem; margin-left: 0; color: #666; }
|
|
3134
3177
|
@media (prefers-color-scheme: dark) {
|
|
3135
3178
|
body { color: #cdd6f4; background: #282840; }
|