@bobfrankston/rmfmail 1.2.329 → 1.2.333

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.
@@ -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");
@@ -5560,6 +5580,7 @@ async function tryEditor(type) {
5560
5580
  }
5561
5581
  } catch {
5562
5582
  }
5583
+ if (ed) wirePasteImageInlining(ed, type);
5563
5584
  return ed;
5564
5585
  } catch (e) {
5565
5586
  logClientEvent("compose-editor-create-failed", { type, error: String(e?.message || e) });
@@ -5664,6 +5685,17 @@ if (fromInput) {
5664
5685
  openFromPicker();
5665
5686
  });
5666
5687
  fromInput.addEventListener("click", openFromPicker);
5688
+ const copyFromBtn = document.getElementById("btn-copy-from");
5689
+ copyFromBtn?.addEventListener("click", async () => {
5690
+ const value = getFromAddress();
5691
+ if (!value) return;
5692
+ try {
5693
+ await navigator.clipboard.writeText(value);
5694
+ showDraftStatus(`Copied From: ${value}`, false);
5695
+ } catch (e) {
5696
+ showDraftStatus(`Copy From failed: ${e?.message || e}`, true);
5697
+ }
5698
+ });
5667
5699
  fromInput.addEventListener("blur", () => {
5668
5700
  if (fromInput.value.trim() === "" && fromInput.dataset.prevFrom) {
5669
5701
  fromInput.value = fromInput.dataset.prevFrom;
@@ -5817,7 +5849,9 @@ function getFromAccountId() {
5817
5849
  return def?.id || "";
5818
5850
  }
5819
5851
  function getFromAddress() {
5820
- return fromInput.value.trim();
5852
+ const live = fromInput.value.trim();
5853
+ if (live) return live;
5854
+ return (fromInput.dataset.prevFrom || "").trim();
5821
5855
  }
5822
5856
  function smartTab(current) {
5823
5857
  const fields = [toInput, ccInput, bccInput, subjectInput];
@@ -6388,6 +6422,10 @@ var baselineSnapshot = "";
6388
6422
  function getContentSnapshot() {
6389
6423
  return JSON.stringify({
6390
6424
  body: editor?.getHtml() || "",
6425
+ // 2026-09-15 — Claude Code (Fable 5.1). From was not part of the
6426
+ // snapshot, so picking a different identity never triggered a save;
6427
+ // the draft kept the old From until the body or subject changed.
6428
+ from: fromInput ? getFromAddress() : "",
6391
6429
  to: toInput?.value || "",
6392
6430
  cc: ccInput?.value || "",
6393
6431
  bcc: bccInput?.value || "",
@@ -6478,6 +6516,29 @@ function editorBodyEl() {
6478
6516
  return document.querySelector(".ql-editor") || document.querySelector(".tt-content .tiptap");
6479
6517
  }
6480
6518
  var INLINE_CHECKED_ATTR = "data-mailx-inline-checked";
6519
+ function wirePasteImageInlining(ed, type) {
6520
+ const onPaste = () => {
6521
+ const body2 = editorBodyEl();
6522
+ if (!body2) return;
6523
+ for (const img of Array.from(body2.querySelectorAll(`img:not([${INLINE_CHECKED_ATTR}])`))) {
6524
+ img.setAttribute(INLINE_CHECKED_ATTR, "1");
6525
+ }
6526
+ setTimeout(() => {
6527
+ void inlinePastedRemoteImages();
6528
+ }, 50);
6529
+ setTimeout(() => {
6530
+ void inlinePastedRemoteImages();
6531
+ }, 800);
6532
+ };
6533
+ const native = ed?.nativeEditor;
6534
+ if (type === "tinymce" && native?.on) {
6535
+ native.on("paste", onPaste);
6536
+ return;
6537
+ }
6538
+ const body = editorBodyEl();
6539
+ if (body) body.addEventListener("paste", onPaste);
6540
+ else console.error("[compose] paste-image inlining not wired: no editor body");
6541
+ }
6481
6542
  async function inlinePastedRemoteImages() {
6482
6543
  const body = editorBodyEl();
6483
6544
  if (!body) return;
@@ -6499,7 +6560,6 @@ async function inlinePastedRemoteImages() {
6499
6560
  }
6500
6561
  function scheduleDraftSave() {
6501
6562
  markComposeDirty();
6502
- void inlinePastedRemoteImages();
6503
6563
  if (draftDebounceTimer) clearTimeout(draftDebounceTimer);
6504
6564
  draftDebounceTimer = setTimeout(() => {
6505
6565
  draftDebounceTimer = null;