@bobfrankston/rmfmail 1.0.492 → 1.0.493

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 (31) hide show
  1. package/client/app.js +58 -47
  2. package/client/app.js.map +1 -1
  3. package/client/app.ts +55 -45
  4. package/client/components/message-viewer.js +145 -81
  5. package/client/components/message-viewer.js.map +1 -1
  6. package/client/components/message-viewer.ts +132 -85
  7. package/client/index.html +1 -1
  8. package/package.json +3 -3
  9. package/packages/mailx-settings/index.d.ts +1 -0
  10. package/packages/mailx-settings/index.d.ts.map +1 -1
  11. package/packages/mailx-settings/index.js +43 -2
  12. package/packages/mailx-settings/index.js.map +1 -1
  13. package/packages/mailx-settings/index.ts +50 -2
  14. package/packages/mailx-store/db.d.ts.map +1 -1
  15. package/packages/mailx-store/db.js +8 -0
  16. package/packages/mailx-store/db.js.map +1 -1
  17. package/packages/mailx-store/db.ts +6 -0
  18. package/packages/mailx-store-web/db.d.ts.map +1 -1
  19. package/packages/mailx-store-web/db.js +26 -0
  20. package/packages/mailx-store-web/db.js.map +1 -1
  21. package/packages/mailx-store-web/db.ts +20 -0
  22. package/packages/mailx-store-web/web-service.d.ts +4 -0
  23. package/packages/mailx-store-web/web-service.d.ts.map +1 -1
  24. package/packages/mailx-store-web/web-service.js +42 -0
  25. package/packages/mailx-store-web/web-service.js.map +1 -1
  26. package/packages/mailx-store-web/web-service.ts +42 -0
  27. package/packages/mailx-store-web/web-settings.d.ts +2 -0
  28. package/packages/mailx-store-web/web-settings.d.ts.map +1 -1
  29. package/packages/mailx-store-web/web-settings.js +34 -3
  30. package/packages/mailx-store-web/web-settings.js.map +1 -1
  31. package/packages/mailx-store-web/web-settings.ts +41 -3
package/client/app.js CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { initFolderTree, refreshFolderTree, updateFolderCounts, setFolderSynced, getFolderSynced, setOutboxTotal } from "./components/folder-tree.js";
6
6
  import { initMessageList, loadMessages, loadUnifiedInbox, loadSearchResults, reloadCurrentFolder, clearSearchMode, getSelectedMessages, markBodiesCached, getCurrentFocused, releaseFocus, removeMessagesAndReconcile, setRowFlagged, scrollFocusedIntoView } from "./components/message-list.js";
7
- import { showMessage, getCurrentMessage, initViewer, popOutCurrentMessage, toggleFullscreenPreview } from "./components/message-viewer.js";
7
+ import { showMessage, getCurrentMessage, initViewer, popOutCurrentMessage, toggleFullscreenPreview, showPreviewBodyMenu } from "./components/message-viewer.js";
8
8
  import { connectWebSocket, onWsEvent, triggerSync, syncAccount, reauthenticate, getAccounts, getFolders, deleteMessages, undeleteMessage, restartServer, getSyncPending, getVersion, getSettings, saveSettings, getAutocompleteSettings, saveAutocompleteSettings, repairAccounts, updateFlags, markAsSpamMessages, logClientEvent, sendMessage as apiSendMessage } from "./lib/api-client.js";
9
9
  import * as messageState from "./lib/message-state.js";
10
10
  // ── New message badge (favicon + title) ──
@@ -516,6 +516,7 @@ document.getElementById("btn-sync")?.addEventListener("click", async () => {
516
516
  const restartBtn = document.getElementById("btn-restart");
517
517
  const restartDropdown = document.getElementById("restart-dropdown");
518
518
  restartBtn?.addEventListener("click", () => {
519
+ restoreToolbarDropdown("restart-dropdown", "restart-menu");
519
520
  if (restartDropdown)
520
521
  restartDropdown.hidden = !restartDropdown.hidden;
521
522
  });
@@ -1177,7 +1178,7 @@ document.getElementById("rail-tasks")?.addEventListener("click", async () => {
1177
1178
  * content. We never put the dropdown back: the toolbar handler also
1178
1179
  * works on a body-attached dropdown (it's just toggling .hidden), and
1179
1180
  * re-parenting on every toggle would defeat any focus state inside. */
1180
- function openMenuFromRail(dropdownId, anchor) {
1181
+ function openMenuFromRail(dropdownId, _anchor) {
1181
1182
  const dd = document.getElementById(dropdownId);
1182
1183
  if (!dd)
1183
1184
  return;
@@ -1194,54 +1195,39 @@ function openMenuFromRail(dropdownId, anchor) {
1194
1195
  if (dd.parentElement?.classList.contains("tb-menu")) {
1195
1196
  document.body.appendChild(dd);
1196
1197
  }
1197
- // Reset all positional inline styles each open so a previous configuration
1198
- // (e.g. wide-tier anchor narrow-tier modal) doesn't leak through.
1198
+ // Uniform behavior across desktop and Android: render rail-opened menus
1199
+ // as a centered modal with a dimmed backdrop. Anchor-based positioning
1200
+ // was inconsistent (worked on Android narrow but not Mac/desktop wide;
1201
+ // bottom-rail icons sit at the viewport floor and the menu spilled off
1202
+ // or landed under chrome). A centered card is always visible, always
1203
+ // dismissible by tapping the backdrop, and the same on every platform.
1199
1204
  dd.style.cssText = "";
1200
1205
  dd.style.position = "fixed";
1206
+ dd.style.top = "50%";
1207
+ dd.style.left = "50%";
1208
+ dd.style.transform = "translate(-50%, -50%)";
1209
+ dd.style.maxHeight = "85vh";
1210
+ dd.style.maxWidth = "92vw";
1211
+ dd.style.overflowY = "auto";
1212
+ dd.style.boxShadow = "0 8px 32px rgba(0,0,0,0.4)";
1201
1213
  dd.style.zIndex = "10000";
1202
- if (window.innerWidth <= 768) {
1203
- // Narrow tier: render as a centered modal-style overlay. Anchor-based
1204
- // positioning was unreliable on phones because bottom-rail icons sit
1205
- // at the viewport floor the menu either spilled off-screen or
1206
- // landed where the rail itself overlapped it. A centered card is
1207
- // always visible, always reachable, and matches the keyboard-shortcut
1208
- // dialog pattern (click-outside / Esc to close).
1209
- dd.style.top = "50%";
1210
- dd.style.left = "50%";
1211
- dd.style.transform = "translate(-50%, -50%)";
1212
- dd.style.maxHeight = "85vh";
1213
- dd.style.maxWidth = "92vw";
1214
- dd.style.overflowY = "auto";
1215
- dd.style.boxShadow = "0 8px 32px rgba(0,0,0,0.4)";
1216
- dd.hidden = false;
1217
- ensureRailMenuBackdrop();
1218
- }
1219
- else {
1220
- // Wide tier: anchor to the right of the rail icon, growing whichever
1221
- // way has more room. (Bottom-rail icons sit near the viewport floor —
1222
- // anchoring `top: rect.top` makes the menu spill off the bottom.)
1223
- const rect = anchor.getBoundingClientRect();
1224
- const minWidth = 220;
1225
- const left = Math.min(rect.right + 6, window.innerWidth - minWidth - 8);
1226
- dd.style.left = `${Math.max(8, left)}px`;
1227
- dd.hidden = false;
1228
- const ddHeight = dd.getBoundingClientRect().height;
1229
- const spaceBelow = window.innerHeight - rect.top - 8;
1230
- const spaceAbove = rect.bottom - 8;
1231
- if (ddHeight > spaceBelow && spaceAbove > spaceBelow) {
1232
- dd.style.bottom = `${Math.max(8, window.innerHeight - rect.bottom)}px`;
1233
- if (ddHeight > spaceAbove) {
1234
- dd.style.maxHeight = `${spaceAbove}px`;
1235
- dd.style.overflowY = "auto";
1236
- }
1237
- }
1238
- else {
1239
- dd.style.top = `${Math.max(8, rect.top)}px`;
1240
- if (ddHeight > spaceBelow) {
1241
- dd.style.maxHeight = `${spaceBelow}px`;
1242
- dd.style.overflowY = "auto";
1243
- }
1244
- }
1214
+ dd.hidden = false;
1215
+ ensureRailMenuBackdrop();
1216
+ }
1217
+ /** Restore a body-parented rail-opened dropdown back to its toolbar parent
1218
+ * and clear inline positioning, so the toolbar button's plain
1219
+ * `dd.hidden = !dd.hidden` toggle renders the dropdown in its usual
1220
+ * position-absolute place. Without this, the toolbar dropdown opens at
1221
+ * the rail-modal's centered position (or somewhere else stale) the first
1222
+ * time a user mixes rail-open and toolbar-open in the same session. */
1223
+ function restoreToolbarDropdown(ddId, tbMenuId) {
1224
+ const dd = document.getElementById(ddId);
1225
+ const tb = document.getElementById(tbMenuId);
1226
+ if (!dd || !tb)
1227
+ return;
1228
+ if (dd.parentElement === document.body) {
1229
+ tb.appendChild(dd);
1230
+ dd.style.cssText = "";
1245
1231
  }
1246
1232
  }
1247
1233
  /** Narrow-tier rail menus need a tap-anywhere-to-close backdrop because the
@@ -1570,6 +1556,26 @@ window.addEventListener("message", (e) => {
1570
1556
  window.open(url, "_blank", "noopener,noreferrer");
1571
1557
  }
1572
1558
  }
1559
+ if (e.data?.type === "previewToggleFullscreen") {
1560
+ toggleFullscreenPreview();
1561
+ return;
1562
+ }
1563
+ if (e.data?.type === "previewContextMenu") {
1564
+ // Translate iframe-relative coords to viewport. Find the iframe whose
1565
+ // contentWindow matches the source so we map correctly even when
1566
+ // multiple iframes are present (compose iframe + viewer iframe).
1567
+ let iframeRect = null;
1568
+ for (const f of Array.from(document.querySelectorAll("iframe"))) {
1569
+ if (f.contentWindow === e.source) {
1570
+ iframeRect = f.getBoundingClientRect();
1571
+ break;
1572
+ }
1573
+ }
1574
+ const x = (iframeRect?.left || 0) + (e.data.x || 0);
1575
+ const y = (iframeRect?.top || 0) + (e.data.y || 0);
1576
+ showPreviewBodyMenu(x, y, String(e.data.selectedText || ""), e.source);
1577
+ return;
1578
+ }
1573
1579
  if (e.data?.type === "linkContextMenu") {
1574
1580
  // C29: right-click in body iframe → Open / Save / Copy URL menu.
1575
1581
  // Iframe's clientX/Y is relative to the iframe; translate to viewport.
@@ -2380,6 +2386,10 @@ viewBtn?.addEventListener("click", (e) => {
2380
2386
  const restartDd = document.getElementById("restart-dropdown");
2381
2387
  if (restartDd)
2382
2388
  restartDd.hidden = true;
2389
+ // If the dropdown was previously opened via the rail (re-parented to
2390
+ // body with inline modal styles), restore it to its toolbar parent so
2391
+ // it renders in the expected position-absolute location.
2392
+ restoreToolbarDropdown("view-dropdown", "view-menu");
2383
2393
  if (viewDropdown)
2384
2394
  viewDropdown.hidden = !viewDropdown.hidden;
2385
2395
  });
@@ -3195,6 +3205,7 @@ settingsBtn?.addEventListener("click", (e) => {
3195
3205
  const restartDd = document.getElementById("restart-dropdown");
3196
3206
  if (restartDd)
3197
3207
  restartDd.hidden = true;
3208
+ restoreToolbarDropdown("settings-dropdown", "settings-menu");
3198
3209
  if (settingsDropdown)
3199
3210
  settingsDropdown.hidden = !settingsDropdown.hidden;
3200
3211
  });