@bobfrankston/rmfmail 1.1.210 → 1.1.212
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 +2 -0
- package/client/app.bundle.js +40 -2
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +1 -1
- package/client/app.js.map +1 -1
- package/client/app.ts +1 -0
- package/client/components/message-viewer.js +47 -1
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +40 -1
- package/package.json +3 -3
- package/packages/mailx-store/db.d.ts.map +1 -1
- package/packages/mailx-store/db.js +18 -4
- package/packages/mailx-store/db.js.map +1 -1
- package/packages/mailx-store/db.ts +18 -4
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-50892 → node_modules.npmglobalize-stash-58140}/.package-lock.json +0 -0
package/TODO.md
CHANGED
|
@@ -236,6 +236,8 @@ Previously shown as showstoppers; moved here because they haven't recurred on re
|
|
|
236
236
|
|
|
237
237
|
Small, self-contained items. Pick them up between higher-priority blocks without asking. Bump version per fix.
|
|
238
238
|
|
|
239
|
+
- **Q153 — Independent pop-out compose window (loopback-HTTP).** [L — feature, ~day+] Bob wants compose/reply as a real OS window (movable to another monitor, main window unobstructed). Plan settled (see `[[project_independent_compose_window]]` memory): in IPC mode also run the Express+WS app on a loopback `127.0.0.1` port sharing the LIVE store+imapManager (same pattern as `--debug-server` at `bin/mailx.ts:1883`, which mounts `createApiRouter(store, imapManager)`); popout button does `window.open("http://127.0.0.1:PORT/compose/compose.html?init=…")`; gate behind the button so a flaky window can't break normal in-window compose. **BLOCKER discovered 2026-06-01:** `client/lib/api-client.ts` is 100% IPC (`ipc().method(...)` via the injected `mailxapi` bridge) — there is NO REST/fetch fallback despite the "auto-detects HTTP" claim in docs. So pop-out FIRST needs a REST+WebSocket transport built into api-client (every method → `fetch('/api/…')`, events → WS), THEN the always-on loopback server (static + /api + ws), THEN cross-origin init via URL params (the popup is origin `127.0.0.1`, can't read the GUI's `sessionStorage`/parent). Sequence the transport layer first; it's the bulk of the work.
|
|
240
|
+
|
|
239
241
|
- ~~**Q152 — "Edit as new message" / resend on a sent (or any) message.**~~ **DONE 2026-05-31.** New `editAsNew` compose mode in `client/app.ts` (`openCompose` branch + `editAsNewBody` helper) clones the selected message into a fresh compose: original To/Cc/Subject verbatim, body via `sanitizeQuotedBody` with no quote/forward wrapper, no In-Reply-To/References (new Message-ID at send). Right-click menu entry "Edit as new message" added after Forward in `message-list.ts`. Replaces the move-to-Drafts kludge. *(Original entry below.)* Bob 2026-05-29: no clean way to take an already-sent message, tweak it, and send it again — today's only path is move-to-Drafts-then-edit (a kludge) or Forward (adds `Fwd:` + quote wrapper, wrong recipients). Add an **"Edit as new message"** affordance — in the viewer toolbar (next to Forward) and/or the message-list right-click menu — that loads the selected message's `.eml` **straight into a fresh compose** as the author: original To/Cc/Subject/body editable, **no `Fwd:`/`Re:` prefix, no quote indent, no In-Reply-To/References threading headers**, a new Message-ID. Effectively "duplicate into compose." Distinct from the existing Drafts `Edit & Send` (that edits the draft in place); this clones any message into a new outgoing draft. Reuse the compose-init plumbing the draft-edit path already uses (`showComposeOverlay` + the init payload built in `app.ts`); the only difference is which headers get stripped vs. carried. Most natural as the message-viewer From/To right-click "duplicate"-style action plus a toolbar button gated on non-draft messages. [S]
|
|
240
242
|
- ~~**Preview CSS leak — `<style>`/`<head>` contents bled into the message-list one-liner.**~~ DONE 2026-05-31. `extractPreview` (`mailx-imap/index.ts`) flattened HTML by stripping tags only, leaving the CSS *between* `<style>…</style>` (and `<head>`/`<script>`) in the preview as `*{box-sizing…}` / `@media…{…}` garbage (Bob's marketing-mail shot). Now strips those block contents + HTML comments before the tag-strip; `[image]` marker preserved. IMAP-path only (Gmail uses Google's clean snippet). Existing rows refresh via `rmfmail -repair`.
|
|
241
243
|
- ~~**Q138 — Quoted-reply image sizes lost.**~~ DONE 2026-05-11 in `client/app.ts:1000` — strip skips `<img>`. Two-pass loop handles multiple stripped attrs per tag.
|
package/client/app.bundle.js
CHANGED
|
@@ -907,7 +907,7 @@ function getCurrentMessage() {
|
|
|
907
907
|
return null;
|
|
908
908
|
return { accountId: currentAccountId, message: currentMessage };
|
|
909
909
|
}
|
|
910
|
-
function showPreviewBodyMenu(absX, absY, selectedText, sourceWindow, linkUrl, linkText) {
|
|
910
|
+
function showPreviewBodyMenu(absX, absY, selectedText, sourceWindow, linkUrl, linkText, imgSrc) {
|
|
911
911
|
const pct = Math.round(previewZoom * 100);
|
|
912
912
|
const runSearch = (query) => {
|
|
913
913
|
const input = document.getElementById("search-input");
|
|
@@ -969,6 +969,41 @@ function showPreviewBodyMenu(absX, absY, selectedText, sourceWindow, linkUrl, li
|
|
|
969
969
|
} }, { label: "", action: () => {
|
|
970
970
|
}, separator: true });
|
|
971
971
|
}
|
|
972
|
+
if (imgSrc) {
|
|
973
|
+
const guessImgName = (() => {
|
|
974
|
+
try {
|
|
975
|
+
const u = new URL(imgSrc);
|
|
976
|
+
const last = u.pathname.split("/").pop() || "";
|
|
977
|
+
return last && last.includes(".") ? last : "image.png";
|
|
978
|
+
} catch {
|
|
979
|
+
return "image.png";
|
|
980
|
+
}
|
|
981
|
+
})();
|
|
982
|
+
items.push({ label: "Copy image", action: async () => {
|
|
983
|
+
try {
|
|
984
|
+
const resp = await fetch(imgSrc);
|
|
985
|
+
const blob = await resp.blob();
|
|
986
|
+
const bmp = await createImageBitmap(blob);
|
|
987
|
+
const canvas = document.createElement("canvas");
|
|
988
|
+
canvas.width = bmp.width;
|
|
989
|
+
canvas.height = bmp.height;
|
|
990
|
+
canvas.getContext("2d").drawImage(bmp, 0, 0);
|
|
991
|
+
const png = await new Promise((res, rej) => canvas.toBlob((b) => b ? res(b) : rej(new Error("toBlob failed")), "image/png"));
|
|
992
|
+
await navigator.clipboard.write([new ClipboardItem({ "image/png": png })]);
|
|
993
|
+
} catch (e) {
|
|
994
|
+
alert(`Couldn't copy image: ${e?.message || e}${imgSrc.startsWith("cid:") ? " (inline cid: image \u2014 try Save instead, or right-click \u2192 save in the message)" : ""}`);
|
|
995
|
+
}
|
|
996
|
+
} }, { label: `Save image as "${guessImgName}"\u2026`, action: () => {
|
|
997
|
+
const a = document.createElement("a");
|
|
998
|
+
a.href = imgSrc;
|
|
999
|
+
a.download = guessImgName;
|
|
1000
|
+
a.style.display = "none";
|
|
1001
|
+
document.body.appendChild(a);
|
|
1002
|
+
a.click();
|
|
1003
|
+
setTimeout(() => a.remove(), 1e3);
|
|
1004
|
+
} }, { label: "", action: () => {
|
|
1005
|
+
}, separator: true });
|
|
1006
|
+
}
|
|
972
1007
|
items.push({ label: "Copy", action: () => tellIframe("copy") }, { label: "Select all", action: () => tellIframe("selectAll") });
|
|
973
1008
|
if (selectedText) {
|
|
974
1009
|
items.push({ label: "", action: () => {
|
|
@@ -2170,12 +2205,14 @@ ${csp}
|
|
|
2170
2205
|
var rect = { left: 0, top: 0 };
|
|
2171
2206
|
try { rect = e.target.getBoundingClientRect ? e.target.getBoundingClientRect() : rect; } catch (_) {}
|
|
2172
2207
|
var a = e.target && e.target.closest ? e.target.closest("a[href]") : null;
|
|
2208
|
+
var img = e.target && e.target.closest ? e.target.closest("img") : null;
|
|
2173
2209
|
var sel = (window.getSelection && window.getSelection()) ? window.getSelection().toString() : "";
|
|
2174
2210
|
window.parent.postMessage({
|
|
2175
2211
|
type: "previewContextMenu",
|
|
2176
2212
|
selectedText: sel,
|
|
2177
2213
|
linkUrl: a ? a.href : "",
|
|
2178
2214
|
linkText: a ? (a.textContent || "").slice(0, 100) : "",
|
|
2215
|
+
imgSrc: img ? (img.currentSrc || img.src || "") : "",
|
|
2179
2216
|
x: e.clientX, y: e.clientY,
|
|
2180
2217
|
iframeLeft: rect.left, iframeTop: rect.top
|
|
2181
2218
|
}, "*");
|
|
@@ -8616,7 +8653,8 @@ window.addEventListener("message", (e) => {
|
|
|
8616
8653
|
String(e.data.selectedText || ""),
|
|
8617
8654
|
e.source,
|
|
8618
8655
|
String(e.data.linkUrl || "") || void 0,
|
|
8619
|
-
String(e.data.linkText || "") || void 0
|
|
8656
|
+
String(e.data.linkText || "") || void 0,
|
|
8657
|
+
String(e.data.imgSrc || "") || void 0
|
|
8620
8658
|
);
|
|
8621
8659
|
return;
|
|
8622
8660
|
}
|