@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
package/.commitmsg
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Preview hotkeys fired twice — Ctrl+F opened two compose windows
|
|
2
|
+
|
|
3
|
+
A keydown in the message preview reached app.ts by TWO routes on desktop
|
|
4
|
+
WebView2: the iframe's own inline script posts every keydown to the parent as
|
|
5
|
+
`previewKey` (app.ts re-dispatches it), and installPreviewControls ALSO attached
|
|
6
|
+
a contentDocument listener that re-dispatched a synthetic keydown of its own.
|
|
7
|
+
The srcdoc document is same-origin, so both are live. One Ctrl+F therefore ran
|
|
8
|
+
openCompose("forward") twice and opened two compose popouts — the second never
|
|
9
|
+
finished loading and sat blank and "(Not Responding)" until it was killed (Bob
|
|
10
|
+
2026-08-23, "Compose: Fwd: FirstCare Slide Deck (Not Responding)"; the log shows
|
|
11
|
+
openCompose-entry mode=forward twice in the same millisecond, two popoutCompose
|
|
12
|
+
windows, and the loaded one exiting while the blank duplicate survived). Ctrl+D
|
|
13
|
+
and Ctrl+P doubled the same way.
|
|
14
|
+
|
|
15
|
+
The inline forwarder is the mechanism that survives — it is the only one that
|
|
16
|
+
works on Android WebView, where parent-side contentDocument listeners never
|
|
17
|
+
fire. installPreviewControls keeps only what is genuinely iframe-local: zoom and
|
|
18
|
+
Ctrl+A.
|
|
19
|
+
|
|
20
|
+
Also: push the Windows taskbar overlay only when the unread count CHANGES.
|
|
21
|
+
Windows holds the overlay icon until the next SetOverlayIcon, so the host cannot
|
|
22
|
+
free the icon it just handed over; re-sending an identical badge every few
|
|
23
|
+
seconds cost GDI objects for nothing and helped drive rmfmail.exe to the
|
|
24
|
+
10,000-object per-process cap (measured at GDI=9995, with 3423
|
|
25
|
+
"CreateIconIndirect failed: Not enough memory resources" in one day's log).
|
|
26
|
+
msger 0.1.429 fixes the leak itself by freeing the retired icon.
|
|
@@ -2583,10 +2583,12 @@ __export(mailx_types_exports, {
|
|
|
2583
2583
|
flaggedOf: () => flaggedOf,
|
|
2584
2584
|
formatSender: () => formatSender,
|
|
2585
2585
|
htmlToPlainText: () => htmlToPlainText,
|
|
2586
|
+
inviteHasGoogleRsvp: () => inviteHasGoogleRsvp,
|
|
2586
2587
|
isAddressToken: () => isAddressToken,
|
|
2587
2588
|
isJunkPreviewText: () => isJunkPreviewText,
|
|
2588
2589
|
mergeReminderStates: () => mergeReminderStates,
|
|
2589
2590
|
normalizeReminderState: () => normalizeReminderState,
|
|
2591
|
+
parseInvite: () => parseInvite,
|
|
2590
2592
|
parseSearchQuery: () => parseSearchQuery,
|
|
2591
2593
|
reminderStatesEqual: () => reminderStatesEqual,
|
|
2592
2594
|
sanitizeHtml: () => sanitizeHtml,
|
|
@@ -2887,6 +2889,173 @@ function formatSender(addr) {
|
|
|
2887
2889
|
return { text: address, claimed: null };
|
|
2888
2890
|
return { text: `${name} <${address}>`, claimed: displayNameClaimsOtherAddress(name, address) };
|
|
2889
2891
|
}
|
|
2892
|
+
function unfoldIcs(text) {
|
|
2893
|
+
const raw = (text || "").replace(/\r\n/g, "\n").split("\n");
|
|
2894
|
+
const out = [];
|
|
2895
|
+
for (const line of raw) {
|
|
2896
|
+
if (/^[ \t]/.test(line) && out.length)
|
|
2897
|
+
out[out.length - 1] += line.slice(1);
|
|
2898
|
+
else
|
|
2899
|
+
out.push(line);
|
|
2900
|
+
}
|
|
2901
|
+
return out;
|
|
2902
|
+
}
|
|
2903
|
+
function unescapeIcs(v) {
|
|
2904
|
+
return (v || "").replace(/\\n/gi, "\n").replace(/\\([;,\\])/g, "$1");
|
|
2905
|
+
}
|
|
2906
|
+
function splitIcsLine(line) {
|
|
2907
|
+
let inQuote = false, colon = -1;
|
|
2908
|
+
for (let i = 0; i < line.length; i++) {
|
|
2909
|
+
const c = line[i];
|
|
2910
|
+
if (c === '"')
|
|
2911
|
+
inQuote = !inQuote;
|
|
2912
|
+
else if (c === ":" && !inQuote) {
|
|
2913
|
+
colon = i;
|
|
2914
|
+
break;
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
if (colon < 0)
|
|
2918
|
+
return null;
|
|
2919
|
+
const namePart = line.slice(0, colon);
|
|
2920
|
+
const value = line.slice(colon + 1);
|
|
2921
|
+
const bits = namePart.split(";");
|
|
2922
|
+
const key = (bits.shift() || "").toUpperCase();
|
|
2923
|
+
const params = {};
|
|
2924
|
+
for (const b of bits) {
|
|
2925
|
+
const eq = b.indexOf("=");
|
|
2926
|
+
if (eq < 0)
|
|
2927
|
+
continue;
|
|
2928
|
+
params[b.slice(0, eq).toUpperCase()] = b.slice(eq + 1).replace(/^"|"$/g, "");
|
|
2929
|
+
}
|
|
2930
|
+
return { key, params, value };
|
|
2931
|
+
}
|
|
2932
|
+
function icsDateToIso(v, isUtc) {
|
|
2933
|
+
const m = (v || "").match(/^(\d{4})(\d{2})(\d{2})(?:T(\d{2})(\d{2})(\d{2})(Z)?)?$/);
|
|
2934
|
+
if (!m)
|
|
2935
|
+
return "";
|
|
2936
|
+
const [, y, mo, d, hh, mm, ss, z] = m;
|
|
2937
|
+
if (!hh)
|
|
2938
|
+
return `${y}-${mo}-${d}`;
|
|
2939
|
+
const base = `${y}-${mo}-${d}T${hh}:${mm}:${ss}`;
|
|
2940
|
+
return z || isUtc ? base + "Z" : base;
|
|
2941
|
+
}
|
|
2942
|
+
function parseCalAddress(value, params) {
|
|
2943
|
+
const email = (value || "").replace(/^mailto:/i, "").trim();
|
|
2944
|
+
return { name: (params.CN || "").trim() || email, email };
|
|
2945
|
+
}
|
|
2946
|
+
function parseInvite(ics) {
|
|
2947
|
+
if (!ics || !/BEGIN:VEVENT/i.test(ics))
|
|
2948
|
+
return null;
|
|
2949
|
+
const lines = unfoldIcs(ics);
|
|
2950
|
+
const out = {
|
|
2951
|
+
method: "",
|
|
2952
|
+
uid: "",
|
|
2953
|
+
sequence: 0,
|
|
2954
|
+
summary: "",
|
|
2955
|
+
description: "",
|
|
2956
|
+
location: "",
|
|
2957
|
+
start: "",
|
|
2958
|
+
end: "",
|
|
2959
|
+
tzid: "",
|
|
2960
|
+
allDay: false,
|
|
2961
|
+
organizer: { name: "", email: "" },
|
|
2962
|
+
attendees: [],
|
|
2963
|
+
rrule: "",
|
|
2964
|
+
status: "",
|
|
2965
|
+
fromGoogle: false
|
|
2966
|
+
};
|
|
2967
|
+
let inEvent = false;
|
|
2968
|
+
let depthNonEvent = 0;
|
|
2969
|
+
for (const line of lines) {
|
|
2970
|
+
const up = line.toUpperCase();
|
|
2971
|
+
if (up.startsWith("BEGIN:VEVENT")) {
|
|
2972
|
+
inEvent = true;
|
|
2973
|
+
continue;
|
|
2974
|
+
}
|
|
2975
|
+
if (up.startsWith("END:VEVENT")) {
|
|
2976
|
+
inEvent = false;
|
|
2977
|
+
continue;
|
|
2978
|
+
}
|
|
2979
|
+
if (!inEvent && (up.startsWith("BEGIN:VTIMEZONE") || up.startsWith("BEGIN:VALARM"))) {
|
|
2980
|
+
depthNonEvent++;
|
|
2981
|
+
continue;
|
|
2982
|
+
}
|
|
2983
|
+
if (up.startsWith("END:VTIMEZONE") || up.startsWith("END:VALARM")) {
|
|
2984
|
+
if (depthNonEvent)
|
|
2985
|
+
depthNonEvent--;
|
|
2986
|
+
continue;
|
|
2987
|
+
}
|
|
2988
|
+
if (inEvent && up.startsWith("BEGIN:VALARM")) {
|
|
2989
|
+
depthNonEvent++;
|
|
2990
|
+
continue;
|
|
2991
|
+
}
|
|
2992
|
+
const p = splitIcsLine(line);
|
|
2993
|
+
if (!p)
|
|
2994
|
+
continue;
|
|
2995
|
+
if (!inEvent) {
|
|
2996
|
+
if (p.key === "METHOD")
|
|
2997
|
+
out.method = p.value.trim().toUpperCase();
|
|
2998
|
+
else if (p.key === "PRODID" && /google/i.test(p.value))
|
|
2999
|
+
out.fromGoogle = true;
|
|
3000
|
+
continue;
|
|
3001
|
+
}
|
|
3002
|
+
if (depthNonEvent)
|
|
3003
|
+
continue;
|
|
3004
|
+
switch (p.key) {
|
|
3005
|
+
case "SUMMARY":
|
|
3006
|
+
out.summary = unescapeIcs(p.value);
|
|
3007
|
+
break;
|
|
3008
|
+
case "DESCRIPTION":
|
|
3009
|
+
out.description = unescapeIcs(p.value);
|
|
3010
|
+
break;
|
|
3011
|
+
case "LOCATION":
|
|
3012
|
+
out.location = unescapeIcs(p.value);
|
|
3013
|
+
break;
|
|
3014
|
+
case "UID":
|
|
3015
|
+
out.uid = p.value.trim();
|
|
3016
|
+
break;
|
|
3017
|
+
case "STATUS":
|
|
3018
|
+
out.status = p.value.trim().toUpperCase();
|
|
3019
|
+
break;
|
|
3020
|
+
case "RRULE":
|
|
3021
|
+
out.rrule = p.value.trim();
|
|
3022
|
+
break;
|
|
3023
|
+
case "SEQUENCE":
|
|
3024
|
+
out.sequence = parseInt(p.value, 10) || 0;
|
|
3025
|
+
break;
|
|
3026
|
+
case "DTSTART":
|
|
3027
|
+
out.start = icsDateToIso(p.value.trim(), false);
|
|
3028
|
+
out.tzid = p.params.TZID || out.tzid;
|
|
3029
|
+
out.allDay = (p.params.VALUE || "").toUpperCase() === "DATE" || !/T/.test(p.value);
|
|
3030
|
+
break;
|
|
3031
|
+
case "DTEND":
|
|
3032
|
+
out.end = icsDateToIso(p.value.trim(), false);
|
|
3033
|
+
out.tzid = out.tzid || p.params.TZID || "";
|
|
3034
|
+
break;
|
|
3035
|
+
case "ORGANIZER":
|
|
3036
|
+
out.organizer = parseCalAddress(p.value, p.params);
|
|
3037
|
+
break;
|
|
3038
|
+
case "ATTENDEE": {
|
|
3039
|
+
const a = parseCalAddress(p.value, p.params);
|
|
3040
|
+
out.attendees.push({
|
|
3041
|
+
name: a.name,
|
|
3042
|
+
email: a.email,
|
|
3043
|
+
status: (p.params.PARTSTAT || "NEEDS-ACTION").toUpperCase(),
|
|
3044
|
+
rsvp: (p.params.RSVP || "").toUpperCase() === "TRUE"
|
|
3045
|
+
});
|
|
3046
|
+
break;
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
if (!out.summary && !out.start)
|
|
3051
|
+
return null;
|
|
3052
|
+
return out;
|
|
3053
|
+
}
|
|
3054
|
+
function inviteHasGoogleRsvp(bodyHtml) {
|
|
3055
|
+
if (!bodyHtml)
|
|
3056
|
+
return false;
|
|
3057
|
+
return /calendar\.google\.com\/calendar\/event\?action=RESPOND/i.test(bodyHtml);
|
|
3058
|
+
}
|
|
2890
3059
|
var _SEEN, _FLAGGED, _ANSWERED, _DRAFT, _DELETED, seenOf, flaggedOf, answeredOf, draftOf, deletedOf, setSeen, setFlagged, setAnswered, setDraft, toggleSeen, toggleFlagged;
|
|
2891
3060
|
var init_mailx_types = __esm({
|
|
2892
3061
|
"packages/mailx-types/index.js"() {
|
|
@@ -6403,6 +6572,20 @@ var WebMailxService = class _WebMailxService {
|
|
|
6403
6572
|
await this.syncManager.updateFlagsLocal(accountId, uid, envelope?.folderId || 0, flags);
|
|
6404
6573
|
}
|
|
6405
6574
|
// ── Remote content allow-list ──
|
|
6575
|
+
/** Read side of the shared allowlist, so the UI can ask whether a sender
|
|
6576
|
+
* has been vouched for. Mirrors MailxService.getAllowlist — the desktop
|
|
6577
|
+
* and Android halves must answer the same question the same way, or the
|
|
6578
|
+
* display-name warning would differ between them. */
|
|
6579
|
+
async getAllowlist() {
|
|
6580
|
+
const list = await loadAllowlist() || {};
|
|
6581
|
+
return {
|
|
6582
|
+
senders: list.senders || [],
|
|
6583
|
+
domains: list.domains || [],
|
|
6584
|
+
recipients: list.recipients || [],
|
|
6585
|
+
flaggedSenders: list.flaggedSenders || [],
|
|
6586
|
+
flaggedDomains: list.flaggedDomains || []
|
|
6587
|
+
};
|
|
6588
|
+
}
|
|
6406
6589
|
async allowRemoteContent(type, value) {
|
|
6407
6590
|
await updateAllowlist((list) => {
|
|
6408
6591
|
if (type === "sender" && !list.senders.includes(value))
|
|
@@ -11868,6 +12051,7 @@ function installBridge() {
|
|
|
11868
12051
|
await service.allowRemoteContent(type, value);
|
|
11869
12052
|
return { ok: true };
|
|
11870
12053
|
},
|
|
12054
|
+
getAllowlist: () => service.getAllowlist(),
|
|
11871
12055
|
flagSenderOrDomain: async (type, value) => {
|
|
11872
12056
|
return await service.flagSenderOrDomain(type, value);
|
|
11873
12057
|
},
|