@bobfrankston/rmfmail 1.0.664 → 1.0.666

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.ts CHANGED
@@ -997,7 +997,21 @@ function sanitizeQuotedBody(msg: any): string {
997
997
  body = body.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
998
998
  body = body.replace(/\s+style="[^"]*"/gi, "");
999
999
  body = body.replace(/\s+class="[^"]*"/gi, "");
1000
- body = body.replace(/\s+(width|height|align|valign|bgcolor|cellpadding|cellspacing|border)="[^"]*"/gi, "");
1000
+ // Strip layout-table attrs only off non-<img> tags. Earlier this regex
1001
+ // ate width/height on <img> too, which is what made App Store / Play
1002
+ // Store buttons render huge in quoted replies (the source ad had
1003
+ // explicit width="120" height="40" on each image). Q138.
1004
+ body = body.replace(/<(?!img\b)([^>]*?)\s+(width|height|align|valign|bgcolor|cellpadding|cellspacing|border)="[^"]*"([^>]*)>/gi,
1005
+ (_m: string, before: string, _attr: string, after: string) => `<${before}${after}>`);
1006
+ // Loop until no more matches — a single tag with multiple stripped
1007
+ // attrs needs more than one pass since the regex only handles one
1008
+ // attribute per match.
1009
+ let prev = "";
1010
+ while (prev !== body) {
1011
+ prev = body;
1012
+ body = body.replace(/<(?!img\b)([^>]*?)\s+(width|height|align|valign|bgcolor|cellpadding|cellspacing|border)="[^"]*"([^>]*)>/gi,
1013
+ (_m: string, before: string, _attr: string, after: string) => `<${before}${after}>`);
1014
+ }
1001
1015
  body = body.replace(/<table[^>]*>/gi, "<div>").replace(/<\/table>/gi, "</div>");
1002
1016
  body = body.replace(/<t[rdh][^>]*>/gi, "").replace(/<\/t[rdh]>/gi, " ");
1003
1017
  body = body.replace(/<thead[^>]*>|<\/thead>|<tbody[^>]*>|<\/tbody>/gi, "");
@@ -3762,7 +3776,28 @@ optEditorTiptap?.addEventListener("change", () => {
3762
3776
  if (optEditorTiptap.checked) saveEditorSetting("tiptap");
3763
3777
  });
3764
3778
  optEditorTinymce?.addEventListener("change", () => {
3765
- if (optEditorTinymce.checked) saveEditorSetting("tinymce");
3779
+ if (optEditorTinymce.checked) {
3780
+ saveEditorSetting("tinymce");
3781
+ // Q133 pre-warm — fire the TinyMCE bundle fetch right now so the
3782
+ // next compose-open finds tinymce.min.js already in WebView2's
3783
+ // HTTP cache. Without this, the first compose after picking
3784
+ // TinyMCE in Settings blocks ~1 s while the bundle downloads.
3785
+ try {
3786
+ // Default points at the locally bundled TinyMCE (build-tinymce.js
3787
+ // copies it from node_modules). Path is relative to index.html
3788
+ // which lives at the client/ root; compose.html (in client/compose/)
3789
+ // uses `../lib/tinymce/...` instead.
3790
+ const cdnUrl = localStorage.getItem("mailx-tinymce-cdn") || "lib/tinymce/tinymce.min.js";
3791
+ // Use <link rel="prefetch"> — fires the request as a hint,
3792
+ // doesn't execute the script (we don't want a stray global
3793
+ // tinymce in the main window; it lives in the compose iframe).
3794
+ const link = document.createElement("link");
3795
+ link.rel = "prefetch";
3796
+ link.as = "script";
3797
+ link.href = cdnUrl;
3798
+ document.head.appendChild(link);
3799
+ } catch { /* prefetch is best-effort */ }
3800
+ }
3766
3801
  });
3767
3802
 
3768
3803
  // External editor preference (Edit-in-Word handoff target). Stored under
@@ -4162,3 +4197,35 @@ if (isApp) {
4162
4197
  window.addEventListener("beforeunload", sendGeometry);
4163
4198
  setInterval(sendGeometry, 60_000);
4164
4199
  }
4200
+
4201
+ // Boot-snapshot writer — captures key DOM regions to localStorage so the
4202
+ // next cold start hydrates instantly (see index.html's hydrateFromBootSnapshot).
4203
+ // Saves every 30 s and on beforeunload; pages with no inbox open just save
4204
+ // empty strings, which the loader treats as "skip this region."
4205
+ function saveBootSnapshot(): void {
4206
+ try {
4207
+ const folderTree = document.getElementById("folder-tree");
4208
+ const messageList = document.getElementById("ml-body");
4209
+ const folderTitle = document.getElementById("ml-folder-title");
4210
+ // Only save when at least one region has content. An in-flight render
4211
+ // (e.g. user clicked a folder, list is empty for a tick) would
4212
+ // otherwise clobber the previous good snapshot.
4213
+ const ftHtml = folderTree?.innerHTML?.trim() || "";
4214
+ const mlHtml = messageList?.innerHTML?.trim() || "";
4215
+ if (!ftHtml && !mlHtml) return;
4216
+ const snap = {
4217
+ folderTree: ftHtml,
4218
+ messageList: mlHtml,
4219
+ folderTitle: folderTitle?.innerHTML?.trim() || "",
4220
+ savedAt: Date.now(),
4221
+ };
4222
+ localStorage.setItem("mailx-boot-snapshot", JSON.stringify(snap));
4223
+ } catch { /* best-effort */ }
4224
+ }
4225
+ window.addEventListener("beforeunload", saveBootSnapshot);
4226
+ // Also save when the user tabs away — a Windows kill of an inactive
4227
+ // app would otherwise lose the snapshot up to 30 s old.
4228
+ document.addEventListener("visibilitychange", () => {
4229
+ if (document.visibilityState === "hidden") saveBootSnapshot();
4230
+ });
4231
+ setInterval(saveBootSnapshot, 30_000);