@bobfrankston/rmfmail 1.2.331 → 1.2.335

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 (35) hide show
  1. package/client/android-bootstrap.bundle.js +60 -2
  2. package/client/android-bootstrap.bundle.js.map +2 -2
  3. package/client/app.bundle.js +2 -1
  4. package/client/app.bundle.js.map +2 -2
  5. package/client/components/message-viewer.js +6 -1
  6. package/client/components/message-viewer.js.map +1 -1
  7. package/client/components/message-viewer.ts +6 -1
  8. package/client/compose/compose.bundle.js +90 -10
  9. package/client/compose/compose.bundle.js.map +4 -4
  10. package/client/compose/compose.js +49 -5
  11. package/client/compose/compose.js.map +1 -1
  12. package/client/compose/compose.ts +47 -5
  13. package/client/lib/rmf-tiny.js +34 -4
  14. package/client/package.json +1 -1
  15. package/package.json +5 -5
  16. package/packages/mailx-imap/package-lock.json +2 -2
  17. package/packages/mailx-imap/package.json +1 -1
  18. package/packages/mailx-service/index.d.ts.map +1 -1
  19. package/packages/mailx-service/index.js +7 -1
  20. package/packages/mailx-service/index.js.map +1 -1
  21. package/packages/mailx-service/index.ts +7 -1
  22. package/packages/mailx-service/package.json +1 -1
  23. package/packages/mailx-service/sync-queue.d.ts +7 -0
  24. package/packages/mailx-service/sync-queue.d.ts.map +1 -1
  25. package/packages/mailx-service/sync-queue.js +9 -0
  26. package/packages/mailx-service/sync-queue.js.map +1 -1
  27. package/packages/mailx-service/sync-queue.ts +10 -0
  28. package/packages/mailx-settings/package.json +1 -1
  29. package/packages/mailx-store/package.json +1 -1
  30. package/packages/mailx-store-web/package.json +1 -1
  31. package/packages/mailx-types/index.d.ts.map +1 -1
  32. package/packages/mailx-types/index.js +14 -0
  33. package/packages/mailx-types/index.js.map +1 -1
  34. package/packages/mailx-types/index.ts +13 -0
  35. package/packages/mailx-types/package.json +1 -1
@@ -3022,14 +3022,34 @@ async function createTinyMceEditor(container2, opts = {}) {
3022
3022
  };
3023
3023
  reader.readAsDataURL(file);
3024
3024
  });
3025
+ const restoreBlobSrcs = (html) => html.replace(/src="(blob:[^"]+)"/g, (m, uri) => {
3026
+ const info = ed.editorUpload?.blobCache?.getByUri?.(uri);
3027
+ return info ? `src="data:${info.blob().type};base64,${info.base64()}"` : m;
3028
+ });
3025
3029
  ed.on("GetContent", (e) => {
3026
3030
  if (!e?.content || typeof e.content !== "string" || !e.content.includes("blob:"))
3027
3031
  return;
3028
- e.content = e.content.replace(/src="(blob:[^"]+)"/g, (m, uri) => {
3029
- const info = ed.editorUpload?.blobCache?.getByUri?.(uri);
3030
- return info ? `src="data:${info.blob().type};base64,${info.base64()}"` : m;
3031
- });
3032
+ e.content = restoreBlobSrcs(e.content);
3032
3033
  });
3034
+ const copyWithImageData = (e) => {
3035
+ if (!e?.clipboardData)
3036
+ return;
3037
+ let html = "";
3038
+ try {
3039
+ html = ed.selection.getContent({ format: "html" }) || "";
3040
+ } catch {
3041
+ return;
3042
+ }
3043
+ if (!html.includes("blob:"))
3044
+ return;
3045
+ e.preventDefault();
3046
+ e.clipboardData.setData("text/html", restoreBlobSrcs(html));
3047
+ e.clipboardData.setData("text/plain", ed.selection.getContent({ format: "text" }) || "");
3048
+ if (e.type === "cut")
3049
+ ed.execCommand("Delete");
3050
+ };
3051
+ ed.on("copy", copyWithImageData);
3052
+ ed.on("cut", copyWithImageData);
3033
3053
  ed.on("OpenWindow", () => {
3034
3054
  requestAnimationFrame(() => requestAnimationFrame(() => {
3035
3055
  const ta = document.querySelector(".tox-dialog textarea");
@@ -5367,6 +5387,43 @@ function keepTypingOutsideLinks(native) {
5367
5387
  // client/compose/compose.ts
5368
5388
  init_api_client();
5369
5389
  init_edit_commands();
5390
+
5391
+ // packages/mailx-types/log-changes.js
5392
+ var RESTATE_AFTER_MS = 60 * 60 * 1e3;
5393
+
5394
+ // packages/mailx-types/reminder-state.js
5395
+ var DISMISSED_RETENTION_MS = 30 * 864e5;
5396
+ var SNOOZED_RETENTION_MS = 7 * 864e5;
5397
+
5398
+ // packages/mailx-types/index.js
5399
+ function htmlToPlainText(html) {
5400
+ if (!html)
5401
+ return "";
5402
+ let s = html;
5403
+ s = s.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "");
5404
+ s = s.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "");
5405
+ s = s.replace(/<br\s*\/?\s*>/gi, "\n");
5406
+ s = s.replace(/<\/(p|div|li|tr|h[1-6]|blockquote|pre|section|article)\s*>/gi, "\n");
5407
+ s = s.replace(/<li\b[^>]*>/gi, " \u2022 ");
5408
+ s = s.replace(/<a\b[^>]*href\s*=\s*(['"])([^'"]*)\1[^>]*>([\s\S]*?)<\/a>/gi, (_m, _q, href, text) => {
5409
+ const t = text.replace(/<[^>]+>/g, "").trim();
5410
+ return t && t !== href ? `${t} (${href})` : href;
5411
+ });
5412
+ s = s.replace(/<img\b[^>]*>/gi, (tag) => {
5413
+ const src = (tag.match(/\bsrc\s*=\s*(['"])([^'"]*)\1/i) || [])[2]?.trim() || "";
5414
+ const alt = (tag.match(/\balt\s*=\s*(['"])([^'"]*)\1/i) || [])[2]?.trim() || "";
5415
+ const remote = /^https?:\/\//i.test(src);
5416
+ if (remote)
5417
+ return alt ? `[image: ${alt}] (${src})` : `(${src})`;
5418
+ return alt ? `[image: ${alt}]` : "[image]";
5419
+ });
5420
+ s = s.replace(/<[^>]+>/g, "");
5421
+ s = s.replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&#39;/gi, "'").replace(/&apos;/gi, "'").replace(/&mdash;/gi, "\u2014").replace(/&ndash;/gi, "\u2013").replace(/&hellip;/gi, "\u2026").replace(/&#(\d+);/g, (_m, n) => String.fromCodePoint(parseInt(n, 10))).replace(/&#x([0-9a-f]+);/gi, (_m, h) => String.fromCodePoint(parseInt(h, 16)));
5422
+ s = s.replace(/[ \t]+/g, " ").split("\n").map((l) => l.replace(/^[ \t]+|[ \t]+$/g, "")).join("\n").replace(/\n{3,}/g, "\n\n").trim();
5423
+ return s;
5424
+ }
5425
+
5426
+ // client/compose/compose.ts
5370
5427
  init_context_menu();
5371
5428
  installConsoleCapture();
5372
5429
  connectEvents();
@@ -5560,6 +5617,7 @@ async function tryEditor(type) {
5560
5617
  }
5561
5618
  } catch {
5562
5619
  }
5620
+ if (ed) wirePasteImageInlining(ed, type);
5563
5621
  return ed;
5564
5622
  } catch (e) {
5565
5623
  logClientEvent("compose-editor-create-failed", { type, error: String(e?.message || e) });
@@ -5943,7 +6001,7 @@ function setupAutocomplete(input) {
5943
6001
  before = before.replace(/[ \t,]+$/, "");
5944
6002
  if (contact) {
5945
6003
  const haystack = `${contact.name} ${contact.email}`.toLowerCase();
5946
- const stripStranded = (s) => splitRecipients(s).map((p) => p.trim()).filter((p) => p.length > 0 && (p.includes("@") || !haystack.includes(p.toLowerCase()))).join(", ");
6004
+ const stripStranded = (s) => splitRecipients2(s).map((p) => p.trim()).filter((p) => p.length > 0 && (p.includes("@") || !haystack.includes(p.toLowerCase()))).join(", ");
5947
6005
  if (before) before = stripStranded(before);
5948
6006
  const afterCore = after.replace(/^[\s,]+/, "").replace(/[\s,]+$/, "");
5949
6007
  if (afterCore) {
@@ -6097,7 +6155,7 @@ for (const field of [toInput, ccInput, bccInput, subjectInput]) {
6097
6155
  smartTab(field);
6098
6156
  });
6099
6157
  }
6100
- function splitRecipients(s) {
6158
+ function splitRecipients2(s) {
6101
6159
  const raw = [];
6102
6160
  let cur = "", inQuote = false;
6103
6161
  for (const c of s) {
@@ -6145,7 +6203,7 @@ function formatAddrs(addrs) {
6145
6203
  }
6146
6204
  function parseAddrs(s) {
6147
6205
  if (!s.trim()) return [];
6148
- return splitRecipients(s).map((p) => p.trim()).filter((p) => p.length > 0).map((part) => {
6206
+ return splitRecipients2(s).map((p) => p.trim()).filter((p) => p.length > 0).map((part) => {
6149
6207
  const match = part.match(/^(.+?)\s*<(.+?)>$/);
6150
6208
  if (match) {
6151
6209
  let name = match[1].trim();
@@ -6457,7 +6515,7 @@ async function saveDraft2() {
6457
6515
  from: getFromAddress(),
6458
6516
  subject: subjectInput.value,
6459
6517
  bodyHtml: editor.getHtml(),
6460
- bodyText: editor.getText(),
6518
+ bodyText: htmlToPlainText(editor.getHtml()),
6461
6519
  to: toInput.value,
6462
6520
  cc: ccInput.value,
6463
6521
  bcc: bccInput.value,
@@ -6495,6 +6553,29 @@ function editorBodyEl() {
6495
6553
  return document.querySelector(".ql-editor") || document.querySelector(".tt-content .tiptap");
6496
6554
  }
6497
6555
  var INLINE_CHECKED_ATTR = "data-mailx-inline-checked";
6556
+ function wirePasteImageInlining(ed, type) {
6557
+ const onPaste = () => {
6558
+ const body2 = editorBodyEl();
6559
+ if (!body2) return;
6560
+ for (const img of Array.from(body2.querySelectorAll(`img:not([${INLINE_CHECKED_ATTR}])`))) {
6561
+ img.setAttribute(INLINE_CHECKED_ATTR, "1");
6562
+ }
6563
+ setTimeout(() => {
6564
+ void inlinePastedRemoteImages();
6565
+ }, 50);
6566
+ setTimeout(() => {
6567
+ void inlinePastedRemoteImages();
6568
+ }, 800);
6569
+ };
6570
+ const native = ed?.nativeEditor;
6571
+ if (type === "tinymce" && native?.on) {
6572
+ native.on("paste", onPaste);
6573
+ return;
6574
+ }
6575
+ const body = editorBodyEl();
6576
+ if (body) body.addEventListener("paste", onPaste);
6577
+ else console.error("[compose] paste-image inlining not wired: no editor body");
6578
+ }
6498
6579
  async function inlinePastedRemoteImages() {
6499
6580
  const body = editorBodyEl();
6500
6581
  if (!body) return;
@@ -6516,7 +6597,6 @@ async function inlinePastedRemoteImages() {
6516
6597
  }
6517
6598
  function scheduleDraftSave() {
6518
6599
  markComposeDirty();
6519
- void inlinePastedRemoteImages();
6520
6600
  if (draftDebounceTimer) clearTimeout(draftDebounceTimer);
6521
6601
  draftDebounceTimer = setTimeout(() => {
6522
6602
  draftDebounceTimer = null;
@@ -6706,7 +6786,7 @@ document.getElementById("btn-send")?.addEventListener("click", async () => {
6706
6786
  bcc: parseAddrs(bccExpanded),
6707
6787
  subject: subjectInput.value,
6708
6788
  bodyHtml: editor.getHtml(),
6709
- bodyText: editor.getText(),
6789
+ bodyText: htmlToPlainText(editor.getHtml()),
6710
6790
  attachments: attachments.map((a) => ({ filename: a.filename, mimeType: a.mimeType, dataBase64: a.dataBase64 })),
6711
6791
  // Threading headers — daemon writes In-Reply-To and References into
6712
6792
  // the outgoing MIME from these. Without them every reply lands as a