@bobfrankston/rmfmail 1.2.120 → 1.2.122

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/client/app.ts CHANGED
@@ -1266,6 +1266,7 @@ function showComposeOverlay(title = "Compose"): HTMLIFrameElement {
1266
1266
  const SVG_ATTRS = `width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"`;
1267
1267
  const TRASH_SVG = `<svg ${SVG_ATTRS}><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`;
1268
1268
  const POPOUT_SVG = `<svg ${SVG_ATTRS}><path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></svg>`;
1269
+ const MAXIMIZE_SVG = `<svg ${SVG_ATTRS}><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M4 9h16"/></svg>`;
1269
1270
  const RESTORE_SVG = `<svg ${SVG_ATTRS}><path d="M21 9v-6h-6"/><path d="M3 15v6h6"/><path d="M21 3l-7 7"/><path d="M3 21l7-7"/></svg>`;
1270
1271
  const makeIconBtn = (svg: string, titleStr: string, hoverColor: string): HTMLButtonElement => {
1271
1272
  const b = document.createElement("button");
@@ -1286,11 +1287,27 @@ function showComposeOverlay(title = "Compose"): HTMLIFrameElement {
1286
1287
  });
1287
1288
  btnCluster.appendChild(discardBtn);
1288
1289
 
1289
- // Popout = toggle between the floating overlay and a host-window-filling
1290
- // layout. The compose iframe can't open in a real OS window (msger custom
1291
- // protocol doesn't propagate to child windows), so "popout" here means
1292
- // "fill the host window" second click restores the floating geometry.
1293
- const popoutBtn = makeIconBtn(POPOUT_SVG, "Maximize", "#000");
1290
+ // True popout hand the live compose off to its own OS window (native
1291
+ // msger window via the daemon; see compose.ts popoutHandoff). Lives in
1292
+ // the TITLE BAR with the other frame controls because it's about the
1293
+ // frame, not the content (Bob 2026-07-12). The iframe owns the state, so
1294
+ // this just pokes it with an event, same pattern as Discard/Close.
1295
+ // Hidden on small screens where the overlay is full-screen and a separate
1296
+ // OS window makes no sense (and Android has no popout host).
1297
+ if (!isSmall) {
1298
+ const osPopoutBtn = makeIconBtn(POPOUT_SVG, "Open in separate window", "#000");
1299
+ osPopoutBtn.addEventListener("click", () => {
1300
+ try {
1301
+ const win = frame.contentWindow;
1302
+ if (win) win.dispatchEvent(new Event("compose-popout"));
1303
+ } catch { /* */ }
1304
+ });
1305
+ btnCluster.appendChild(osPopoutBtn);
1306
+ }
1307
+
1308
+ // Maximize = toggle between the floating overlay and a host-window-filling
1309
+ // layout — second click restores the floating geometry.
1310
+ const popoutBtn = makeIconBtn(MAXIMIZE_SVG, "Maximize", "#000");
1294
1311
  let maximized = false;
1295
1312
  let savedCss = "";
1296
1313
  popoutBtn.addEventListener("click", () => {
@@ -1302,7 +1319,7 @@ function showComposeOverlay(title = "Compose"): HTMLIFrameElement {
1302
1319
  maximized = true;
1303
1320
  } else {
1304
1321
  wrapper.style.cssText = savedCss;
1305
- popoutBtn.innerHTML = POPOUT_SVG;
1322
+ popoutBtn.innerHTML = MAXIMIZE_SVG;
1306
1323
  popoutBtn.title = "Maximize";
1307
1324
  maximized = false;
1308
1325
  }
@@ -2187,6 +2187,26 @@ async function createTinyMceEditor(container2, opts = {}) {
2187
2187
  setup: (ed) => {
2188
2188
  if (opts.initialHtml)
2189
2189
  ed.on("init", () => ed.setContent(opts.initialHtml));
2190
+ ed.on("GetContent", (e) => {
2191
+ if (!e?.content || typeof e.content !== "string" || !e.content.includes("blob:"))
2192
+ return;
2193
+ e.content = e.content.replace(/src="(blob:[^"]+)"/g, (m, uri) => {
2194
+ const info = ed.editorUpload?.blobCache?.getByUri?.(uri);
2195
+ return info ? `src="data:${info.blob().type};base64,${info.base64()}"` : m;
2196
+ });
2197
+ });
2198
+ ed.on("OpenWindow", () => {
2199
+ requestAnimationFrame(() => requestAnimationFrame(() => {
2200
+ const ta = document.querySelector(".tox-dialog textarea");
2201
+ if (ta && ta.value) {
2202
+ try {
2203
+ ta.setSelectionRange(0, 0);
2204
+ ta.scrollTop = 0;
2205
+ } catch {
2206
+ }
2207
+ }
2208
+ }));
2209
+ });
2190
2210
  const ZOOM_DEFAULT = opts.fontSizePx ?? 14;
2191
2211
  const ZOOM_STEP = 2;
2192
2212
  const ZOOM_MIN = 8;
@@ -5138,9 +5158,10 @@ document.getElementById("btn-discard")?.addEventListener("click", async () => {
5138
5158
  }
5139
5159
  closeCompose();
5140
5160
  });
5141
- var composePopoutBtn = document.getElementById("btn-compose-popout");
5142
- if (composePopoutBtn && popoutInitId) composePopoutBtn.style.display = "none";
5143
- composePopoutBtn?.addEventListener("click", async () => {
5161
+ var popoutHandoffInFlight = false;
5162
+ async function popoutHandoff() {
5163
+ if (popoutInitId || popoutHandoffInFlight) return;
5164
+ popoutHandoffInFlight = true;
5144
5165
  if (!draftId) draftId = `mailx-draft-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
5145
5166
  const init = {
5146
5167
  // "draft" mode so applyInit does NOT re-append the signature — the
@@ -5160,7 +5181,6 @@ composePopoutBtn?.addEventListener("click", async () => {
5160
5181
  draftUid: draftUid ?? void 0,
5161
5182
  draftId: draftId ?? void 0
5162
5183
  };
5163
- composePopoutBtn.disabled = true;
5164
5184
  try {
5165
5185
  const r = await popoutCompose(init);
5166
5186
  if (r?.ok) {
@@ -5173,14 +5193,17 @@ composePopoutBtn?.addEventListener("click", async () => {
5173
5193
  draftTimer = null;
5174
5194
  }
5175
5195
  closeCompose();
5176
- } else {
5177
- showDraftStatus(`Popout failed: ${r?.reason || "unknown"}`, true);
5178
- composePopoutBtn.disabled = false;
5196
+ return;
5179
5197
  }
5198
+ showDraftStatus(`Popout failed: ${r?.reason || "unknown"}`, true);
5180
5199
  } catch (e) {
5181
5200
  showDraftStatus(`Popout failed: ${e?.message || e}`, true);
5182
- composePopoutBtn.disabled = false;
5201
+ } finally {
5202
+ popoutHandoffInFlight = false;
5183
5203
  }
5204
+ }
5205
+ window.addEventListener("compose-popout", () => {
5206
+ void popoutHandoff();
5184
5207
  });
5185
5208
  var ccRow = document.getElementById("compose-cc-row");
5186
5209
  var bccRow = document.getElementById("compose-bcc-row");