@bobfrankston/rmfmail 1.2.272 → 1.2.276
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/.commitmsg +26 -0
- package/client/android-bootstrap.bundle.js +184 -0
- package/client/android-bootstrap.bundle.js.map +2 -2
- package/client/app.bundle.js +235 -31
- package/client/app.bundle.js.map +4 -4
- package/client/app.js +35 -1
- package/client/app.js.map +1 -1
- package/client/app.ts +36 -1
- package/client/components/invite-card.js +107 -0
- package/client/components/invite-card.js.map +1 -0
- package/client/components/invite-card.ts +119 -0
- package/client/components/message-list.js +9 -1
- package/client/components/message-list.js.map +1 -1
- package/client/components/message-list.ts +9 -1
- package/client/components/message-viewer.js +82 -19
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +79 -18
- package/client/compose/compose.bundle.js +4 -0
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/lib/api-client.js +6 -0
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +6 -0
- package/client/lib/approved-senders.js +66 -0
- package/client/lib/approved-senders.js.map +1 -0
- package/client/lib/approved-senders.ts +63 -0
- package/client/lib/mailxapi.js +3 -0
- package/client/package.json +1 -1
- package/client/styles/components.css +48 -0
- package/package.json +10 -10
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +2 -2
- package/packages/mailx-service/index.d.ts +15 -0
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +18 -0
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +19 -0
- package/packages/mailx-service/jsonrpc.js +2 -0
- package/packages/mailx-service/jsonrpc.js.map +1 -1
- package/packages/mailx-service/jsonrpc.ts +2 -0
- package/packages/mailx-service/package.json +1 -1
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store/store.d.ts +5 -0
- package/packages/mailx-store/store.d.ts.map +1 -1
- package/packages/mailx-store/store.js +21 -0
- package/packages/mailx-store/store.js.map +1 -1
- package/packages/mailx-store/store.ts +27 -0
- package/packages/mailx-store-web/android-bootstrap.js +1 -0
- package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
- package/packages/mailx-store-web/android-bootstrap.ts +1 -0
- package/packages/mailx-store-web/package.json +1 -1
- package/packages/mailx-store-web/web-service.d.ts +11 -0
- package/packages/mailx-store-web/web-service.d.ts.map +1 -1
- package/packages/mailx-store-web/web-service.js +14 -0
- package/packages/mailx-store-web/web-service.js.map +1 -1
- package/packages/mailx-store-web/web-service.ts +15 -0
- package/packages/mailx-types/index.d.ts +56 -0
- package/packages/mailx-types/index.d.ts.map +1 -1
- package/packages/mailx-types/index.js +187 -0
- package/packages/mailx-types/index.js.map +1 -1
- package/packages/mailx-types/index.ts +192 -0
- package/packages/mailx-types/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-72180 → node_modules.npmglobalize-stash-59216}/.package-lock.json +0 -0
|
@@ -10,12 +10,14 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { getMessage, updateFlags, allowRemoteContent, flagSenderOrDomain, getAttachment, openAttachment, getMessageSource, addContact, listContacts, upsertContact, unsubscribeOneClick, addPreferredContact, onEvent, subscribeStore } from "../lib/api-client.js";
|
|
13
|
+
import { isSenderApproved, noteApprovedLocally, refreshApprovedSenders } from "../lib/approved-senders.js";
|
|
14
|
+
import { buildInviteCard } from "./invite-card.js";
|
|
13
15
|
import { showContextMenu } from "./context-menu.js";
|
|
14
16
|
import type { MenuItem } from "./context-menu.js";
|
|
15
17
|
import type { ListMessage } from "../lib/message-state.js";
|
|
16
18
|
import { updateMessageFlags as stateUpdateFlags } from "../lib/message-state.js";
|
|
17
19
|
import { setRowSeen } from "./message-list.js";
|
|
18
|
-
import { setSeen, seenOf, formatSender } from "@bobfrankston/mailx-types";
|
|
20
|
+
import { setSeen, seenOf, formatSender, displayNameClaimsOtherAddress } from "@bobfrankston/mailx-types";
|
|
19
21
|
|
|
20
22
|
// ── "Copy image" bridge ──
|
|
21
23
|
// The preview iframe encodes the image and posts the bytes back; these track
|
|
@@ -715,22 +717,27 @@ function installPreviewControls(iframe: HTMLIFrameElement): void {
|
|
|
715
717
|
// don't preventDefault — the browser's native select-all inside
|
|
716
718
|
// the iframe selects exactly this document.
|
|
717
719
|
if (e.ctrlKey && !e.altKey && !e.metaKey && (e.key === "a" || e.key === "A")) return;
|
|
718
|
-
//
|
|
719
|
-
//
|
|
720
|
-
//
|
|
721
|
-
//
|
|
722
|
-
//
|
|
723
|
-
//
|
|
724
|
-
//
|
|
725
|
-
//
|
|
726
|
-
//
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
720
|
+
// Everything else is FORWARDED BY THE IFRAME ITSELF — the inline
|
|
721
|
+
// script wrapHtmlBody installs posts every keydown to the parent
|
|
722
|
+
// as `previewKey`, and app.ts re-dispatches it on document. This
|
|
723
|
+
// listener must NOT forward as well.
|
|
724
|
+
//
|
|
725
|
+
// It used to, and both routes are live on desktop WebView2 (the
|
|
726
|
+
// srcdoc document is same-origin, so this contentDocument listener
|
|
727
|
+
// attaches, and the inline script runs too). One Ctrl+F in the
|
|
728
|
+
// preview therefore reached app.ts TWICE and opened TWO compose
|
|
729
|
+
// windows — one of which never finished loading and sat blank and
|
|
730
|
+
// "(Not Responding)" until it was killed (Bob 2026-08-23,
|
|
731
|
+
// "Compose: Fwd: FirstCare Slide Deck (Not Responding)"; daemon
|
|
732
|
+
// log shows openCompose-entry mode=forward twice in the same
|
|
733
|
+
// millisecond and two popoutCompose windows). Ctrl+D / Ctrl+P
|
|
734
|
+
// doubled the same way.
|
|
735
|
+
//
|
|
736
|
+
// The inline forwarder is the one that survives, because it is the
|
|
737
|
+
// only one that works on Android WebView, where parent-side
|
|
738
|
+
// contentDocument listeners never fire. Zoom and Ctrl+A stay here:
|
|
739
|
+
// they act on THIS document and the inline script deliberately
|
|
740
|
+
// skips them. (Claude Code 2026-08-23)
|
|
734
741
|
});
|
|
735
742
|
|
|
736
743
|
doc.addEventListener("wheel", (e) => {
|
|
@@ -788,6 +795,10 @@ export function clearViewer(): void {
|
|
|
788
795
|
if (bodyEl) bodyEl.innerHTML = `<div class="mv-empty">Select a message to read</div>`;
|
|
789
796
|
if (headerEl) headerEl.hidden = true;
|
|
790
797
|
if (attEl) attEl.hidden = true;
|
|
798
|
+
// The invite card is a SIBLING of the body (it sits after the header), so
|
|
799
|
+
// clearing bodyEl leaves it behind — a stale meeting hovering over "Select
|
|
800
|
+
// a message to read" is exactly the viewer-must-clear failure.
|
|
801
|
+
document.getElementById("mv-invite")?.remove();
|
|
791
802
|
// Divergence watchdog: highlight and viewer are supposed to be one
|
|
792
803
|
// thing. If a second after a clear some row still shows .selected while
|
|
793
804
|
// the pane shows the placeholder, that's the "message selected but it's
|
|
@@ -1215,6 +1226,22 @@ export async function showMessage(accountId: string, uid: number, folderId?: num
|
|
|
1215
1226
|
fromEl.textContent = formatAddr(msg.from);
|
|
1216
1227
|
setRecipientLine(toEl, msg.to, msg.cc, msg.deliveredTo);
|
|
1217
1228
|
headerEl.querySelector(".mv-subject")!.textContent = msg.subject;
|
|
1229
|
+
|
|
1230
|
+
// Calendar invite card. Lives BETWEEN the header and the body as its
|
|
1231
|
+
// own element, not inside .mv-body — the body's innerHTML is replaced
|
|
1232
|
+
// wholesale on every render, which would take the card with it.
|
|
1233
|
+
// Removed and rebuilt per message so a non-invite never inherits the
|
|
1234
|
+
// previous one (the "viewer must clear" rule).
|
|
1235
|
+
document.getElementById("mv-invite")?.remove();
|
|
1236
|
+
try {
|
|
1237
|
+
const card = buildInviteCard((msg as any).invite, String(msg.bodyHtml || ""));
|
|
1238
|
+
if (card) {
|
|
1239
|
+
card.id = "mv-invite";
|
|
1240
|
+
headerEl.insertAdjacentElement("afterend", card);
|
|
1241
|
+
}
|
|
1242
|
+
} catch (e: any) {
|
|
1243
|
+
console.warn("[viewer] invite card failed:", e?.message || e);
|
|
1244
|
+
}
|
|
1218
1245
|
document.dispatchEvent(new CustomEvent("mailx-message-shown", { detail: { accountId } }));
|
|
1219
1246
|
|
|
1220
1247
|
// Right-click on email addresses in header: copy name, copy address,
|
|
@@ -1312,6 +1339,39 @@ export async function showMessage(accountId: string, uid: number, folderId?: num
|
|
|
1312
1339
|
});
|
|
1313
1340
|
items.push({ label: "", action: () => {}, separator: true });
|
|
1314
1341
|
}
|
|
1342
|
+
// "Mark as valid" — the display-name warning is only useful if
|
|
1343
|
+
// it can be silenced per sender. Reuses the shared allowlist
|
|
1344
|
+
// (same senders/domains that gate remote images), so one act of
|
|
1345
|
+
// trust covers both (Bob 2026-08-20: "another variation on
|
|
1346
|
+
// approved senders. If a message is flagged I should be able to
|
|
1347
|
+
// mark it as valid").
|
|
1348
|
+
if (el === fromEl && msg.from?.address) {
|
|
1349
|
+
const addr = String(msg.from.address);
|
|
1350
|
+
const dom = addr.split("@").pop() || "";
|
|
1351
|
+
const claimed = displayNameClaimsOtherAddress(String(msg.from.name || ""), addr);
|
|
1352
|
+
if (claimed && !isSenderApproved(addr)) {
|
|
1353
|
+
const approve = async (type: "sender" | "domain", value: string) => {
|
|
1354
|
+
noteApprovedLocally(type, value); // optimistic — row updates now
|
|
1355
|
+
try {
|
|
1356
|
+
await allowRemoteContent(type, value);
|
|
1357
|
+
await refreshApprovedSenders();
|
|
1358
|
+
document.dispatchEvent(new CustomEvent("mailx-allowlist-changed"));
|
|
1359
|
+
const st = document.getElementById("status-sync");
|
|
1360
|
+
if (st) st.textContent = `Marked valid: ${value}`;
|
|
1361
|
+
} catch (e: any) {
|
|
1362
|
+
const st = document.getElementById("status-sync");
|
|
1363
|
+
if (st) {
|
|
1364
|
+
st.textContent = `Couldn't save "${value}" to the allowlist — ${e?.message || e}`;
|
|
1365
|
+
st.style.color = "oklch(0.65 0.2 25)";
|
|
1366
|
+
}
|
|
1367
|
+
console.error("[allowlist] approve failed:", e);
|
|
1368
|
+
}
|
|
1369
|
+
};
|
|
1370
|
+
items.push({ label: `✓ Mark ${addr} as a valid sender`, action: () => void approve("sender", addr) });
|
|
1371
|
+
if (dom) items.push({ label: `✓ Trust all senders at ${dom}`, action: () => void approve("domain", dom) });
|
|
1372
|
+
items.push({ label: "", action: () => {}, separator: true });
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1315
1375
|
if (omitted > 0) {
|
|
1316
1376
|
items.push({
|
|
1317
1377
|
label: `… ${omitted} more recipients — copy the whole list`,
|
|
@@ -2146,7 +2206,8 @@ function formatAddr(addr: { name: string; address: string }): string {
|
|
|
2146
2206
|
// 2026-08-19) and reports when a display name asserts a DIFFERENT address
|
|
2147
2207
|
// than the envelope's, which is the display-name spoof.
|
|
2148
2208
|
const { text, claimed } = formatSender(addr);
|
|
2149
|
-
|
|
2209
|
+
// Approved senders are never called out — see approved-senders.ts.
|
|
2210
|
+
if (!claimed || isSenderApproved(addr.address)) return text;
|
|
2150
2211
|
// Never let the claim stand unqualified: name the real address explicitly.
|
|
2151
2212
|
return `${text} — display name claims ${claimed}`;
|
|
2152
2213
|
}
|
|
@@ -1053,6 +1053,7 @@ __export(api_client_exports, {
|
|
|
1053
1053
|
flagSenderOrDomain: () => flagSenderOrDomain,
|
|
1054
1054
|
formatJsonc: () => formatJsonc,
|
|
1055
1055
|
getAccounts: () => getAccounts,
|
|
1056
|
+
getAllowlist: () => getAllowlist,
|
|
1056
1057
|
getAttachment: () => getAttachment,
|
|
1057
1058
|
getAutocompleteSettings: () => getAutocompleteSettings,
|
|
1058
1059
|
getCalendarEvents: () => getCalendarEvents,
|
|
@@ -1347,6 +1348,9 @@ function addUserDictWords(words) {
|
|
|
1347
1348
|
function removeUserDictWord(word) {
|
|
1348
1349
|
return ipc().removeUserDictWord?.(word) ?? Promise.resolve([]);
|
|
1349
1350
|
}
|
|
1351
|
+
function getAllowlist() {
|
|
1352
|
+
return ipc().getAllowlist?.() ?? Promise.resolve({ senders: [], domains: [], recipients: [], flaggedSenders: [], flaggedDomains: [] });
|
|
1353
|
+
}
|
|
1350
1354
|
function flagSenderOrDomain(type, value) {
|
|
1351
1355
|
return ipc().flagSenderOrDomain?.(type, value) ?? Promise.resolve({ flagged: false });
|
|
1352
1356
|
}
|