@bobfrankston/rmfmail 1.2.274 → 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.
Files changed (33) hide show
  1. package/.commitmsg +26 -0
  2. package/client/android-bootstrap.bundle.js +169 -0
  3. package/client/android-bootstrap.bundle.js.map +2 -2
  4. package/client/app.bundle.js +123 -23
  5. package/client/app.bundle.js.map +4 -4
  6. package/client/app.js +16 -1
  7. package/client/app.js.map +1 -1
  8. package/client/app.ts +16 -1
  9. package/client/components/invite-card.js +107 -0
  10. package/client/components/invite-card.js.map +1 -0
  11. package/client/components/invite-card.ts +119 -0
  12. package/client/components/message-viewer.js +42 -17
  13. package/client/components/message-viewer.js.map +1 -1
  14. package/client/components/message-viewer.ts +42 -16
  15. package/client/package.json +1 -1
  16. package/client/styles/components.css +48 -0
  17. package/package.json +8 -8
  18. package/packages/mailx-imap/package-lock.json +2 -2
  19. package/packages/mailx-imap/package.json +2 -2
  20. package/packages/mailx-settings/package.json +1 -1
  21. package/packages/mailx-store/package.json +1 -1
  22. package/packages/mailx-store/store.d.ts +5 -0
  23. package/packages/mailx-store/store.d.ts.map +1 -1
  24. package/packages/mailx-store/store.js +21 -0
  25. package/packages/mailx-store/store.js.map +1 -1
  26. package/packages/mailx-store/store.ts +27 -0
  27. package/packages/mailx-types/index.d.ts +56 -0
  28. package/packages/mailx-types/index.d.ts.map +1 -1
  29. package/packages/mailx-types/index.js +187 -0
  30. package/packages/mailx-types/index.js.map +1 -1
  31. package/packages/mailx-types/index.ts +192 -0
  32. package/packages/mailx-types/package.json +1 -1
  33. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-31304 → 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"() {