@bobfrankston/rmfmail 1.2.147 → 1.2.149
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/compose/compose.bundle.js +70 -7
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/lib/rmf-tiny.js +87 -7
- package/package.json +3 -3
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +71 -26
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +69 -26
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-69320 → node_modules.npmglobalize-stash-41964}/.package-lock.json +0 -0
|
@@ -990,14 +990,38 @@ export class MailxService implements MailxApi {
|
|
|
990
990
|
`;
|
|
991
991
|
try {
|
|
992
992
|
const htmlToDocx = (await import("html-to-docx")).default;
|
|
993
|
-
|
|
994
|
-
|
|
993
|
+
let out: Buffer;
|
|
994
|
+
try {
|
|
995
|
+
const buf = await htmlToDocx(wrapped) as Buffer | ArrayBuffer;
|
|
996
|
+
out = Buffer.isBuffer(buf) ? buf : Buffer.from(buf as ArrayBuffer);
|
|
997
|
+
} catch (first: any) {
|
|
998
|
+
// Two known html-to-docx crashes on real mail bodies
|
|
999
|
+
// (Bob 2026-07-18, reply with a deep quoted chain):
|
|
1000
|
+
// - "Cannot read properties of undefined (reading
|
|
1001
|
+
// 'createFont')" — font-family styles in some nestings
|
|
1002
|
+
// hit an undefined docxDocumentInstance inside the lib;
|
|
1003
|
+
// - "unsupported file type: undefined" — a remote http(s)
|
|
1004
|
+
// <img> the lib tries (and fails) to download.
|
|
1005
|
+
// Retry once with font-family styles stripped and remote
|
|
1006
|
+
// imgs swapped for their alt text — slightly lower fidelity
|
|
1007
|
+
// beats losing the .docx round-trip (the .html fallback
|
|
1008
|
+
// needs Save-As-Web-Page gymnastics in Word).
|
|
1009
|
+
const sanitized = wrapped
|
|
1010
|
+
.replace(/font-family\s*:[^;"']*;?/gi, "")
|
|
1011
|
+
.replace(/<img\b[^>]*\bsrc\s*=\s*["']https?:[^>]*>/gi, (tag) => {
|
|
1012
|
+
const alt = /\balt\s*=\s*["']([^"']*)["']/i.exec(tag)?.[1] || "";
|
|
1013
|
+
return alt ? `[image: ${alt}]` : "[image]";
|
|
1014
|
+
});
|
|
1015
|
+
console.warn(` [word-edit] html-to-docx failed (${first?.message || first}) — retrying with fonts/remote-imgs sanitized`);
|
|
1016
|
+
const buf = await htmlToDocx(sanitized) as Buffer | ArrayBuffer;
|
|
1017
|
+
out = Buffer.isBuffer(buf) ? buf : Buffer.from(buf as ArrayBuffer);
|
|
1018
|
+
}
|
|
995
1019
|
fs.writeFileSync(filePath, out);
|
|
996
1020
|
} catch (e: any) {
|
|
997
|
-
// html-to-docx couldn't produce a docx
|
|
998
|
-
//
|
|
999
|
-
//
|
|
1000
|
-
//
|
|
1021
|
+
// html-to-docx couldn't produce a docx even sanitized. Fall back
|
|
1022
|
+
// to plain .html so the user still gets *something* — they'll
|
|
1023
|
+
// need Save-As-Web-Page in Word but that's better than no edit
|
|
1024
|
+
// path at all.
|
|
1001
1025
|
console.warn(` [word-edit] html-to-docx failed: ${e?.message || e} — falling back to .html`);
|
|
1002
1026
|
const htmlPath = path.join(dir, `${fileBase}.html`);
|
|
1003
1027
|
fs.writeFileSync(htmlPath, wrapped, "utf-8");
|
|
@@ -1082,20 +1106,33 @@ export class MailxService implements MailxApi {
|
|
|
1082
1106
|
// save so the cold-start cost is paid only when the user actually
|
|
1083
1107
|
// edits in Word — most sessions never hit it.
|
|
1084
1108
|
let debounce: ReturnType<typeof setTimeout> | null = null;
|
|
1085
|
-
let
|
|
1109
|
+
let lastSig = "";
|
|
1110
|
+
// Word saves the .docx in place on Ctrl+S, but Save-As-Web-Page
|
|
1111
|
+
// writes a SIBLING .htm/.html (same base name) — catch those too so
|
|
1112
|
+
// a web-page save doesn't vanish into an unwatched file (Bob
|
|
1113
|
+
// 2026-07-18, .html-fallback variant of the same report).
|
|
1114
|
+
const watchNames = new Set([`${fileBase}.docx`, `${fileBase}.htm`, `${fileBase}.html`]);
|
|
1086
1115
|
const watcher = fs.watch(dir, (eventType, name) => {
|
|
1087
|
-
if (name
|
|
1116
|
+
if (!name || !watchNames.has(name)) return;
|
|
1117
|
+
const savedPath = path.join(dir, name);
|
|
1118
|
+
const isDocx = name.endsWith(".docx");
|
|
1088
1119
|
if (debounce) clearTimeout(debounce);
|
|
1089
1120
|
debounce = setTimeout(async () => {
|
|
1090
1121
|
let stat: fs.Stats;
|
|
1091
|
-
try { stat = fs.statSync(
|
|
1092
|
-
|
|
1093
|
-
|
|
1122
|
+
try { stat = fs.statSync(savedPath); } catch { return; }
|
|
1123
|
+
const sig = `${savedPath}:${stat.size}:${stat.mtimeMs}`;
|
|
1124
|
+
if (sig === lastSig) return;
|
|
1125
|
+
lastSig = sig;
|
|
1094
1126
|
try {
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1127
|
+
let inner: string;
|
|
1128
|
+
if (isDocx) {
|
|
1129
|
+
const buf = fs.readFileSync(savedPath);
|
|
1130
|
+
const mammoth = (await import("mammoth")).default;
|
|
1131
|
+
const result = await mammoth.convertToHtml({ buffer: buf });
|
|
1132
|
+
inner = result.value || "";
|
|
1133
|
+
} else {
|
|
1134
|
+
inner = fs.readFileSync(savedPath, "utf-8");
|
|
1135
|
+
}
|
|
1099
1136
|
// mammoth returns body-content HTML (no <html><body> wrapper).
|
|
1100
1137
|
// Defensive: if a wrapper sneaks in, strip it.
|
|
1101
1138
|
const bodyMatch = inner.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
@@ -1148,35 +1185,41 @@ export class MailxService implements MailxApi {
|
|
|
1148
1185
|
if (tryLaunch("xdg-open", [filePath])) opener = "default";
|
|
1149
1186
|
}
|
|
1150
1187
|
let debounce: ReturnType<typeof setTimeout> | null = null;
|
|
1151
|
-
let
|
|
1188
|
+
let lastSig = "";
|
|
1152
1189
|
const htmlName = path.basename(filePath); // <editId>.html
|
|
1153
1190
|
const docxName = htmlName.replace(/\.html$/i, ".docx"); // what Word writes on Ctrl+S
|
|
1191
|
+
const htmName = htmlName.replace(/\.html$/i, ".htm"); // what Word's Save-As-Web-Page writes
|
|
1154
1192
|
const docxPath = path.join(dir, docxName);
|
|
1193
|
+
const htmPath = path.join(dir, htmName);
|
|
1155
1194
|
// CRITICAL: Word, given an .html, converts it to a SIBLING .docx on save
|
|
1156
1195
|
// (Bob 2026-06-06: "Word turns it into a docx you don't see when I do
|
|
1157
|
-
// ^s")
|
|
1158
|
-
//
|
|
1159
|
-
//
|
|
1196
|
+
// ^s") — and Save-As-Web-Page writes a SIBLING .htm (Bob 2026-07-18:
|
|
1197
|
+
// "not seeing the updated .htm when I save it in Word"). The watcher
|
|
1198
|
+
// must cover all three spellings; prefer the .docx (mammoth) as the
|
|
1199
|
+
// authoritative save, then .htm, then the original .html.
|
|
1160
1200
|
const watcher = fs.watch(dir, (_evt, name) => {
|
|
1161
|
-
if (name !== htmlName && name !== docxName) return;
|
|
1201
|
+
if (name !== htmlName && name !== docxName && name !== htmName) return;
|
|
1162
1202
|
if (debounce) clearTimeout(debounce);
|
|
1163
1203
|
debounce = setTimeout(async () => {
|
|
1164
1204
|
try {
|
|
1165
1205
|
let inner: string;
|
|
1166
1206
|
if (fs.existsSync(docxPath)) {
|
|
1167
1207
|
const st = fs.statSync(docxPath);
|
|
1168
|
-
|
|
1169
|
-
|
|
1208
|
+
const sig = `docx:${st.size}:${st.mtimeMs}`;
|
|
1209
|
+
if (sig === lastSig) return;
|
|
1210
|
+
lastSig = sig;
|
|
1170
1211
|
const mammoth = (await import("mammoth")).default;
|
|
1171
1212
|
const result = await mammoth.convertToHtml({ buffer: fs.readFileSync(docxPath) });
|
|
1172
1213
|
inner = result.value || "";
|
|
1173
1214
|
const bm = inner.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
1174
1215
|
if (bm) inner = bm[1];
|
|
1175
1216
|
} else {
|
|
1176
|
-
const
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1217
|
+
const srcPath = fs.existsSync(htmPath) ? htmPath : filePath;
|
|
1218
|
+
const st = fs.statSync(srcPath);
|
|
1219
|
+
const sig = `${srcPath}:${st.size}:${st.mtimeMs}`;
|
|
1220
|
+
if (sig === lastSig) return;
|
|
1221
|
+
lastSig = sig;
|
|
1222
|
+
const updatedHtml = fs.readFileSync(srcPath, "utf-8");
|
|
1180
1223
|
const bm = updatedHtml.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
1181
1224
|
inner = bm ? bm[1] : updatedHtml;
|
|
1182
1225
|
}
|