@bobfrankston/rmfmail 1.1.161 → 1.1.163

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
@@ -3371,6 +3371,7 @@ viewBtn?.addEventListener("click", (e) => {
3371
3371
  // it renders in the expected position-absolute location.
3372
3372
  restoreToolbarDropdown("view-dropdown", "view-menu");
3373
3373
  if (viewDropdown) viewDropdown.hidden = !viewDropdown.hidden;
3374
+ refreshToolbarOverlayShield();
3374
3375
  });
3375
3376
  // Capture-phase pointerdown so we run BEFORE any handler that calls
3376
3377
  // stopPropagation. The earlier bubble-phase click listener missed clicks
@@ -3391,7 +3392,45 @@ document.addEventListener("pointerdown", (e) => {
3391
3392
  if (settingsDropdown && !settingsDropdown.hidden && !target.closest("#settings-menu") && !target.closest("#settings-dropdown")) {
3392
3393
  settingsDropdown.hidden = true;
3393
3394
  }
3395
+ refreshToolbarOverlayShield();
3394
3396
  }, true);
3397
+
3398
+ // Click-to-dismiss for the toolbar dropdowns relies on `pointerdown` firing
3399
+ // on the parent document. Clicks inside the compose iframe stay in the
3400
+ // iframe's document and never bubble out, so a Settings/View menu opened
3401
+ // over compose used to stay stuck until the user hit Escape or the toolbar
3402
+ // button again. Shield the overlay with a transparent fullscreen catcher
3403
+ // while any toolbar dropdown is open — its pointerdown lands here in the
3404
+ // parent doc, triggering the same outside-click close. The shield sits
3405
+ // above compose (z 1600) but below the dropdown itself (z 2000).
3406
+ function refreshToolbarOverlayShield(): void {
3407
+ const dropdownOpen = ["settings-dropdown", "view-dropdown", "restart-dropdown"]
3408
+ .some(id => {
3409
+ const el = document.getElementById(id);
3410
+ return el && !el.hidden;
3411
+ });
3412
+ const hasOverlay = !!document.querySelector(".compose-overlay, .popout-overlay");
3413
+ let shield = document.getElementById("tb-overlay-shield");
3414
+ if (dropdownOpen && hasOverlay) {
3415
+ if (!shield) {
3416
+ shield = document.createElement("div");
3417
+ shield.id = "tb-overlay-shield";
3418
+ shield.style.cssText = "position:fixed;inset:0;z-index:1999;background:transparent;";
3419
+ shield.addEventListener("pointerdown", (ev) => {
3420
+ ev.preventDefault();
3421
+ ev.stopPropagation();
3422
+ for (const id of ["settings-dropdown", "view-dropdown", "restart-dropdown"]) {
3423
+ const dd = document.getElementById(id);
3424
+ if (dd) dd.hidden = true;
3425
+ }
3426
+ shield?.remove();
3427
+ });
3428
+ document.body.appendChild(shield);
3429
+ }
3430
+ } else if (shield) {
3431
+ shield.remove();
3432
+ }
3433
+ }
3395
3434
  // Escape always closes any open dropdown/backdrop, regardless of how it was
3396
3435
  // opened or whether an outside-click handler missed firing. Last-resort
3397
3436
  // escape hatch for the "stuck modal" case.
@@ -3406,7 +3445,10 @@ document.addEventListener("keydown", (e) => {
3406
3445
  if (bd) { bd.remove(); closed = true; }
3407
3446
  const themeSub = document.getElementById("theme-submenu");
3408
3447
  if (themeSub && !themeSub.hidden) { themeSub.hidden = true; closed = true; }
3409
- if (closed) e.preventDefault();
3448
+ if (closed) {
3449
+ e.preventDefault();
3450
+ refreshToolbarOverlayShield();
3451
+ }
3410
3452
  });
3411
3453
 
3412
3454
  // Restore saved view settings
@@ -4385,6 +4427,7 @@ settingsBtn?.addEventListener("click", (e) => {
4385
4427
  if (restartDd) restartDd.hidden = true;
4386
4428
  restoreToolbarDropdown("settings-dropdown", "settings-menu");
4387
4429
  if (settingsDropdown) settingsDropdown.hidden = !settingsDropdown.hidden;
4430
+ refreshToolbarOverlayShield();
4388
4431
  });
4389
4432
  // Close handled by the shared document click handler above
4390
4433
 
@@ -746,7 +746,21 @@ async function createTinyMceEditor(container2, opts = {}) {
746
746
  const ZOOM_STEP = 2;
747
747
  const ZOOM_MIN = 8;
748
748
  const ZOOM_MAX = 32;
749
+ const ZOOM_STORAGE_KEY = "rmf-tiny:zoom-px";
749
750
  let zoomPx = ZOOM_DEFAULT;
751
+ try {
752
+ const stored = Number(localStorage.getItem(ZOOM_STORAGE_KEY));
753
+ if (Number.isFinite(stored) && stored >= ZOOM_MIN && stored <= ZOOM_MAX) {
754
+ zoomPx = stored;
755
+ }
756
+ } catch {
757
+ }
758
+ const saveZoom = () => {
759
+ try {
760
+ localStorage.setItem(ZOOM_STORAGE_KEY, String(zoomPx));
761
+ } catch {
762
+ }
763
+ };
750
764
  const applyZoom = () => {
751
765
  try {
752
766
  const body = ed.getBody();
@@ -758,6 +772,7 @@ async function createTinyMceEditor(container2, opts = {}) {
758
772
  const bumpZoom = (delta) => {
759
773
  zoomPx = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, zoomPx + delta));
760
774
  applyZoom();
775
+ saveZoom();
761
776
  };
762
777
  ed.ui.registry.addMenuItem("zoomIn", {
763
778
  text: "Zoom in",
@@ -775,6 +790,7 @@ async function createTinyMceEditor(container2, opts = {}) {
775
790
  onAction: () => {
776
791
  zoomPx = ZOOM_DEFAULT;
777
792
  applyZoom();
793
+ saveZoom();
778
794
  }
779
795
  });
780
796
  const installLinkEscape = () => {
@@ -830,6 +846,8 @@ async function createTinyMceEditor(container2, opts = {}) {
830
846
  doc.documentElement.setAttribute("lang", "en");
831
847
  } catch {
832
848
  }
849
+ if (zoomPx !== ZOOM_DEFAULT)
850
+ applyZoom();
833
851
  try {
834
852
  const doc = ed.getDoc();
835
853
  doc.addEventListener("wheel", (e) => {
@@ -1964,6 +1982,9 @@ function decorate(editor2, sp) {
1964
1982
  const doc = editor2.getDoc?.();
1965
1983
  if (!body || !doc)
1966
1984
  return;
1985
+ const _activeSel = doc.getSelection();
1986
+ if (_activeSel && _activeSel.rangeCount > 0 && !_activeSel.isCollapsed)
1987
+ return;
1967
1988
  let savedFocusNode = null;
1968
1989
  let savedFocusOffset = 0;
1969
1990
  const _preSel = doc.getSelection();
@@ -2289,6 +2310,9 @@ function cleanupCorrected(editor2, sp) {
2289
2310
  const doc = editor2.getDoc?.();
2290
2311
  if (!body || !doc)
2291
2312
  return;
2313
+ const activeSel = doc.getSelection();
2314
+ if (activeSel && activeSel.rangeCount > 0 && !activeSel.isCollapsed)
2315
+ return;
2292
2316
  const markers = body.querySelectorAll(`span[${MARKER_ATTR}]`);
2293
2317
  if (markers.length === 0)
2294
2318
  return;