@bobfrankston/rmfmail 1.2.224 → 1.2.226

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 (48) hide show
  1. package/client/android-bootstrap.bundle.js +84 -4
  2. package/client/android-bootstrap.bundle.js.map +2 -2
  3. package/client/app.bundle.js +64 -31
  4. package/client/app.bundle.js.map +2 -2
  5. package/client/app.js +24 -10
  6. package/client/app.js.map +1 -1
  7. package/client/app.ts +21 -10
  8. package/client/components/message-list.js +56 -15
  9. package/client/components/message-list.js.map +1 -1
  10. package/client/components/message-list.ts +55 -16
  11. package/client/components/message-viewer.js +2 -2
  12. package/client/components/message-viewer.js.map +1 -1
  13. package/client/components/message-viewer.ts +2 -2
  14. package/client/lib/message-state.js +15 -4
  15. package/client/lib/message-state.js.map +1 -1
  16. package/client/lib/message-state.ts +12 -3
  17. package/package.json +5 -5
  18. package/packages/mailx-core/index.ts +3 -2
  19. package/packages/mailx-core/package.json +1 -1
  20. package/packages/mailx-imap/package-lock.json +2 -2
  21. package/packages/mailx-imap/package.json +1 -1
  22. package/packages/mailx-send/index.d.ts.map +1 -1
  23. package/packages/mailx-send/index.js +10 -5
  24. package/packages/mailx-send/index.js.map +1 -1
  25. package/packages/mailx-send/index.ts +10 -5
  26. package/packages/mailx-send/mailsend/index.ts +8 -5
  27. package/packages/mailx-send/package.json +1 -1
  28. package/packages/mailx-service/index.d.ts.map +1 -1
  29. package/packages/mailx-service/index.js +10 -7
  30. package/packages/mailx-service/index.js.map +1 -1
  31. package/packages/mailx-service/index.ts +10 -7
  32. package/packages/mailx-service/package.json +1 -1
  33. package/packages/mailx-settings/docs/TODO-prereorg-snapshot-2026-07-15.md +1130 -0
  34. package/packages/mailx-settings/package.json +1 -1
  35. package/packages/mailx-store/package.json +1 -1
  36. package/packages/mailx-store-web/package.json +1 -1
  37. package/packages/mailx-store-web/web-service.d.ts +10 -1
  38. package/packages/mailx-store-web/web-service.d.ts.map +1 -1
  39. package/packages/mailx-store-web/web-service.js +19 -4
  40. package/packages/mailx-store-web/web-service.js.map +1 -1
  41. package/packages/mailx-store-web/web-service.ts +18 -4
  42. package/packages/mailx-types/index.d.ts +40 -0
  43. package/packages/mailx-types/index.d.ts.map +1 -1
  44. package/packages/mailx-types/index.js +117 -0
  45. package/packages/mailx-types/index.js.map +1 -1
  46. package/packages/mailx-types/index.ts +103 -0
  47. package/packages/mailx-types/package.json +1 -1
  48. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-142920 → node_modules.npmglobalize-stash-86196}/.package-lock.json +0 -0
@@ -2418,6 +2418,8 @@ __export(mailx_types_exports, {
2418
2418
  answeredOf: () => answeredOf,
2419
2419
  deletedOf: () => deletedOf,
2420
2420
  draftOf: () => draftOf,
2421
+ encodeAddressHeader: () => encodeAddressHeader,
2422
+ encodeHeaderWord: () => encodeHeaderWord,
2421
2423
  encodeQuotedPrintable: () => encodeQuotedPrintable,
2422
2424
  expandRecipients: () => expandRecipients,
2423
2425
  extractAddress: () => extractAddress,
@@ -2476,6 +2478,71 @@ function sanitizeHtml(html) {
2476
2478
  clean = clean.replace(/<iframe\b[^>]*>[\s\S]*?<\/iframe>/gi, "");
2477
2479
  return { html: clean, hasRemoteContent };
2478
2480
  }
2481
+ function encodeHeaderWord(text) {
2482
+ if (!text)
2483
+ return "";
2484
+ if (!/[^\x00-\x7F]/.test(text))
2485
+ return text;
2486
+ const encoder = new TextEncoder();
2487
+ const MAX_BYTES_PER_WORD = 45;
2488
+ const words = [];
2489
+ let chunk = [];
2490
+ for (const ch of text) {
2491
+ const bytes = Array.from(encoder.encode(ch));
2492
+ if (chunk.length + bytes.length > MAX_BYTES_PER_WORD) {
2493
+ words.push(chunk);
2494
+ chunk = [];
2495
+ }
2496
+ chunk.push(...bytes);
2497
+ }
2498
+ if (chunk.length)
2499
+ words.push(chunk);
2500
+ return words.map((b) => `=?UTF-8?B?${bytesToBase64(Uint8Array.from(b))}?=`).join("\r\n ");
2501
+ }
2502
+ function bytesToBase64(bytes) {
2503
+ let bin = "";
2504
+ for (const b of bytes)
2505
+ bin += String.fromCharCode(b);
2506
+ return btoa(bin);
2507
+ }
2508
+ function encodeAddressHeader(value) {
2509
+ if (!value)
2510
+ return "";
2511
+ if (!/[^\x00-\x7F]/.test(value))
2512
+ return value;
2513
+ const parts = [];
2514
+ let cur = "";
2515
+ let inAngle = false;
2516
+ let inQuote = false;
2517
+ for (const ch of value) {
2518
+ if (ch === '"')
2519
+ inQuote = !inQuote;
2520
+ else if (ch === "<" && !inQuote)
2521
+ inAngle = true;
2522
+ else if (ch === ">" && !inQuote)
2523
+ inAngle = false;
2524
+ if (ch === "," && !inAngle && !inQuote) {
2525
+ parts.push(cur);
2526
+ cur = "";
2527
+ continue;
2528
+ }
2529
+ cur += ch;
2530
+ }
2531
+ if (cur.trim())
2532
+ parts.push(cur);
2533
+ return parts.map((part) => {
2534
+ const p = part.trim();
2535
+ const m = /^(.*?)\s*(<[^>]*>)\s*$/.exec(p);
2536
+ if (!m)
2537
+ return p;
2538
+ const display = m[1].replace(/^"(.*)"$/s, "$1");
2539
+ if (!display)
2540
+ return m[2];
2541
+ if (!/[^\x00-\x7F]/.test(display))
2542
+ return p;
2543
+ return `${encodeHeaderWord(display)} ${m[2]}`;
2544
+ }).join(", ");
2545
+ }
2479
2546
  function encodeQuotedPrintable(text) {
2480
2547
  const encoder = new TextEncoder();
2481
2548
  const bytes = encoder.encode(text);
@@ -6246,7 +6313,7 @@ var WebMailxService = class _WebMailxService {
6246
6313
  `To: ${to}`,
6247
6314
  cc ? `Cc: ${cc}` : null,
6248
6315
  bcc ? `Bcc: ${bcc}` : null,
6249
- `Subject: ${msg.subject}`,
6316
+ `Subject: ${encodeHeaderWord(msg.subject)}`,
6250
6317
  `Date: ${(/* @__PURE__ */ new Date()).toUTCString()}`,
6251
6318
  `Message-ID: ${messageId}`,
6252
6319
  msg.inReplyTo ? `In-Reply-To: ${msg.inReplyTo}` : null,
@@ -6400,7 +6467,7 @@ ${inner.body}`;
6400
6467
  to ? `To: ${to}` : null,
6401
6468
  cc ? `Cc: ${cc}` : null,
6402
6469
  bcc ? `Bcc: ${bcc}` : null,
6403
- `Subject: ${subject || "(no subject)"}`,
6470
+ `Subject: ${encodeHeaderWord(subject || "(no subject)")}`,
6404
6471
  `Date: ${(/* @__PURE__ */ new Date()).toUTCString()}`,
6405
6472
  `X-Mailx-Draft-ID: ${id}`,
6406
6473
  `MIME-Version: 1.0`,
@@ -6649,8 +6716,21 @@ ${bodyEncoded}`;
6649
6716
  return this.notImpl("openAttachment");
6650
6717
  }
6651
6718
  // Spam / abuse
6652
- async markAsSpamMessages(_accountId, _uids) {
6653
- return this.notImpl("markAsSpamMessages");
6719
+ /** Mark as spam = move to the account's \Junk folder, exactly as desktop's
6720
+ * MailxService.markAsSpamMessages does. This was a notImpl() stub, which
6721
+ * is worse than it sounds: the client optimistically removes the rows
6722
+ * from the list BEFORE calling this, then only writes the rejection to
6723
+ * the status bar. So on Android the spam button looked like it worked,
6724
+ * nothing moved on the server, and the next sync brought every message
6725
+ * straight back (Bob 2026-08-07: "attempts to delete spam but they keep
6726
+ * coming back"). moveMessages was implemented all along — spam just never
6727
+ * got wired to it. */
6728
+ async markAsSpamMessages(accountId, uids) {
6729
+ const target = this.db.getFolders(accountId).find((f) => f.specialUse === "junk");
6730
+ if (!target)
6731
+ throw new Error(`No \\Junk/\\Spam folder found for ${accountId}`);
6732
+ await this.moveMessages(accountId, uids, target.id);
6733
+ return { targetFolderId: target.id, moved: uids.length };
6654
6734
  }
6655
6735
  async recordSpamReport(_accountId, _uid, _folderId) {
6656
6736
  return this.notImpl("recordSpamReport");