@bobfrankston/rmfmail 1.2.121 → 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
  }
@@ -5158,9 +5158,10 @@ document.getElementById("btn-discard")?.addEventListener("click", async () => {
5158
5158
  }
5159
5159
  closeCompose();
5160
5160
  });
5161
- var composePopoutBtn = document.getElementById("btn-compose-popout");
5162
- if (composePopoutBtn && popoutInitId) composePopoutBtn.style.display = "none";
5163
- composePopoutBtn?.addEventListener("click", async () => {
5161
+ var popoutHandoffInFlight = false;
5162
+ async function popoutHandoff() {
5163
+ if (popoutInitId || popoutHandoffInFlight) return;
5164
+ popoutHandoffInFlight = true;
5164
5165
  if (!draftId) draftId = `mailx-draft-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
5165
5166
  const init = {
5166
5167
  // "draft" mode so applyInit does NOT re-append the signature — the
@@ -5180,7 +5181,6 @@ composePopoutBtn?.addEventListener("click", async () => {
5180
5181
  draftUid: draftUid ?? void 0,
5181
5182
  draftId: draftId ?? void 0
5182
5183
  };
5183
- composePopoutBtn.disabled = true;
5184
5184
  try {
5185
5185
  const r = await popoutCompose(init);
5186
5186
  if (r?.ok) {
@@ -5193,14 +5193,17 @@ composePopoutBtn?.addEventListener("click", async () => {
5193
5193
  draftTimer = null;
5194
5194
  }
5195
5195
  closeCompose();
5196
- } else {
5197
- showDraftStatus(`Popout failed: ${r?.reason || "unknown"}`, true);
5198
- composePopoutBtn.disabled = false;
5196
+ return;
5199
5197
  }
5198
+ showDraftStatus(`Popout failed: ${r?.reason || "unknown"}`, true);
5200
5199
  } catch (e) {
5201
5200
  showDraftStatus(`Popout failed: ${e?.message || e}`, true);
5202
- composePopoutBtn.disabled = false;
5201
+ } finally {
5202
+ popoutHandoffInFlight = false;
5203
5203
  }
5204
+ }
5205
+ window.addEventListener("compose-popout", () => {
5206
+ void popoutHandoff();
5204
5207
  });
5205
5208
  var ccRow = document.getElementById("compose-cc-row");
5206
5209
  var bccRow = document.getElementById("compose-bcc-row");