@bobfrankston/rmfmail 1.1.248 → 1.1.249

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 (33) hide show
  1. package/client/app.bundle.js +129 -1
  2. package/client/app.bundle.js.map +3 -3
  3. package/client/app.js +155 -0
  4. package/client/app.js.map +1 -1
  5. package/client/app.ts +139 -0
  6. package/client/components/context-menu.js +3 -1
  7. package/client/components/context-menu.js.map +1 -1
  8. package/client/components/context-menu.ts +5 -1
  9. package/client/compose/compose.bundle.js +7 -1
  10. package/client/compose/compose.bundle.js.map +2 -2
  11. package/client/lib/api-client.js +6 -0
  12. package/client/lib/api-client.js.map +1 -1
  13. package/client/lib/api-client.ts +7 -0
  14. package/package.json +1 -1
  15. package/packages/mailx-imap/index.d.ts.map +1 -1
  16. package/packages/mailx-imap/index.js +24 -0
  17. package/packages/mailx-imap/index.js.map +1 -1
  18. package/packages/mailx-imap/index.ts +25 -0
  19. package/packages/mailx-imap/package-lock.json +2 -2
  20. package/packages/mailx-imap/package.json +1 -1
  21. package/packages/mailx-service/index.d.ts +7 -0
  22. package/packages/mailx-service/index.d.ts.map +1 -1
  23. package/packages/mailx-service/index.js +15 -0
  24. package/packages/mailx-service/index.js.map +1 -1
  25. package/packages/mailx-service/index.ts +15 -0
  26. package/packages/mailx-service/jsonrpc.js +3 -0
  27. package/packages/mailx-service/jsonrpc.js.map +1 -1
  28. package/packages/mailx-service/jsonrpc.ts +3 -0
  29. package/packages/mailx-service/sync-queue.d.ts +5 -0
  30. package/packages/mailx-service/sync-queue.d.ts.map +1 -1
  31. package/packages/mailx-service/sync-queue.js +8 -0
  32. package/packages/mailx-service/sync-queue.js.map +1 -1
  33. package/packages/mailx-service/sync-queue.ts +9 -0
package/client/app.ts CHANGED
@@ -1596,6 +1596,145 @@ document.addEventListener("mailx-moved", (e: any) => {
1596
1596
  pushUndo({ kind: "move", at: Date.now(), payload: e.detail as MovedBatch });
1597
1597
  });
1598
1598
 
1599
+ // ── Right-button drag → Move / Copy menu ───────────────────────────────
1600
+ // Browser drag-and-drop is left-button only, so a RIGHT-button drag is a
1601
+ // custom mouse-tracked gesture: right-press a message row, drag onto a folder
1602
+ // in the tree, release → a menu offers Move (default, bold) or Copy. A plain
1603
+ // left drag still moves directly; a plain right-CLICK (no movement) still opens
1604
+ // the normal message menu — only a right-press-AND-drag triggers this.
1605
+ (() => {
1606
+ type RDMsg = { accountId: string; uid: number; folderId: number };
1607
+ const SLOP = 6;
1608
+ let cand: { msgs: RDMsg[]; x: number; y: number } | null = null;
1609
+ let active = false;
1610
+ let ghost: HTMLElement | null = null;
1611
+ let suppressContext = false;
1612
+
1613
+ // Cursor + ghost + drop-highlight styles (self-contained).
1614
+ const style = document.createElement("style");
1615
+ style.textContent =
1616
+ "body.rmf-rdrag, body.rmf-rdrag * { cursor: grabbing !important; }" +
1617
+ ".rmf-rdrag-ghost { position: fixed; z-index: 100000; pointer-events: none;" +
1618
+ " background: var(--color-accent, #1a6dd4); color: #fff; padding: 2px 9px;" +
1619
+ " border-radius: 4px; font: 12px system-ui; box-shadow: 0 2px 8px rgba(0,0,0,.3); }" +
1620
+ ".ft-folder.rmf-rdrag-over { outline: 2px solid var(--color-accent, #1a6dd4); outline-offset: -2px; border-radius: 4px; }";
1621
+ document.head.appendChild(style);
1622
+
1623
+ const folderAt = (x: number, y: number): HTMLElement | null =>
1624
+ ((document.elementFromPoint(x, y) as HTMLElement | null)?.closest?.(".ft-folder") as HTMLElement | null) || null;
1625
+ const clearOver = (): void =>
1626
+ document.querySelectorAll(".ft-folder.rmf-rdrag-over").forEach(el => el.classList.remove("rmf-rdrag-over"));
1627
+ const cleanup = (): void => {
1628
+ active = false; cand = null;
1629
+ document.body.classList.remove("rmf-rdrag");
1630
+ ghost?.remove(); ghost = null;
1631
+ clearOver();
1632
+ };
1633
+
1634
+ document.addEventListener("mousedown", (e) => {
1635
+ if (e.button !== 2) return;
1636
+ const row = (e.target as HTMLElement)?.closest?.(".ml-row") as HTMLElement | null;
1637
+ if (!row) return;
1638
+ const pressed: RDMsg = {
1639
+ accountId: row.dataset.accountId || "",
1640
+ uid: Number(row.dataset.uid),
1641
+ folderId: Number(row.dataset.folderId),
1642
+ };
1643
+ if (!pressed.uid) return;
1644
+ // Drag the current multi-selection if the pressed row is in it;
1645
+ // otherwise just the pressed row.
1646
+ let msgs = getSelectedMessages();
1647
+ if (!msgs.some(m => m.accountId === pressed.accountId && m.uid === pressed.uid)) msgs = [pressed];
1648
+ cand = { msgs, x: e.clientX, y: e.clientY };
1649
+ active = false;
1650
+ }, true);
1651
+
1652
+ document.addEventListener("mousemove", (e) => {
1653
+ if (!cand) return;
1654
+ if (!active) {
1655
+ if (Math.abs(e.clientX - cand.x) < SLOP && Math.abs(e.clientY - cand.y) < SLOP) return;
1656
+ active = true;
1657
+ document.body.classList.add("rmf-rdrag");
1658
+ ghost = document.createElement("div");
1659
+ ghost.className = "rmf-rdrag-ghost";
1660
+ ghost.textContent = cand.msgs.length === 1 ? "1 message" : `${cand.msgs.length} messages`;
1661
+ document.body.appendChild(ghost);
1662
+ }
1663
+ if (ghost) { ghost.style.left = `${e.clientX + 14}px`; ghost.style.top = `${e.clientY + 8}px`; }
1664
+ clearOver();
1665
+ folderAt(e.clientX, e.clientY)?.classList.add("rmf-rdrag-over");
1666
+ }, true);
1667
+
1668
+ document.addEventListener("mouseup", (e) => {
1669
+ if (!cand) return;
1670
+ const wasActive = active;
1671
+ const msgs = cand.msgs;
1672
+ const folder = wasActive ? folderAt(e.clientX, e.clientY) : null;
1673
+ const mx = e.clientX, my = e.clientY;
1674
+ cleanup();
1675
+ if (!wasActive) return; // a right-click, not a drag
1676
+ suppressContext = true; // eat the contextmenu that fires next
1677
+ if (!folder) return; // released off any folder
1678
+ e.preventDefault(); e.stopPropagation();
1679
+ const targetAccount = folder.dataset.accountId || "";
1680
+ const targetFolderId = Number(folder.dataset.folderId);
1681
+ const targetName = folder.querySelector(".ft-folder-name")?.textContent?.trim() || "folder";
1682
+ void showRightDragMenu(mx, my, msgs, targetAccount, targetFolderId, targetName);
1683
+ }, true);
1684
+
1685
+ // Suppress the folder/message context menu that would otherwise pop on the
1686
+ // right-button release after a drag.
1687
+ document.addEventListener("contextmenu", (e) => {
1688
+ if (suppressContext) { e.preventDefault(); e.stopPropagation(); suppressContext = false; }
1689
+ }, true);
1690
+
1691
+ async function showRightDragMenu(x: number, y: number, msgs: RDMsg[], targetAccount: string, targetFolderId: number, targetName: string): Promise<void> {
1692
+ const { showContextMenu } = await import("./components/context-menu.js");
1693
+ const n = msgs.length;
1694
+ const label = n === 1 ? "message" : `${n} messages`;
1695
+ showContextMenu(x, y, [
1696
+ { label: `Move ${label} here`, emphasized: true, action: () => void runRightDragOp(msgs, targetAccount, targetFolderId, targetName, "move") },
1697
+ { label: `Copy ${label} here`, action: () => void runRightDragOp(msgs, targetAccount, targetFolderId, targetName, "copy") },
1698
+ ]);
1699
+ }
1700
+
1701
+ async function runRightDragOp(msgs: RDMsg[], targetAccount: string, targetFolderId: number, targetName: string, op: "move" | "copy"): Promise<void> {
1702
+ const status = document.getElementById("status-sync");
1703
+ const api = await import("./lib/api-client.js");
1704
+ const byAccount = new Map<string, { uids: number[]; folderIds: number[] }>();
1705
+ for (const m of msgs) {
1706
+ const g = byAccount.get(m.accountId) || { uids: [], folderIds: [] };
1707
+ g.uids.push(m.uid); g.folderIds.push(m.folderId);
1708
+ byAccount.set(m.accountId, g);
1709
+ }
1710
+ if (op === "move") {
1711
+ // Optimistic remove + undo, mirroring the left-drag drop handler.
1712
+ removeMessagesAndReconcile(msgs);
1713
+ document.dispatchEvent(new CustomEvent("mailx-moved", {
1714
+ detail: { messages: msgs.map(m => ({ accountId: m.accountId, uid: m.uid, sourceFolderId: m.folderId })) },
1715
+ }));
1716
+ for (const [src, g] of byAccount) {
1717
+ const targetAcct = src === targetAccount ? undefined : targetAccount;
1718
+ api.moveMessages(src, g.uids, targetFolderId, targetAcct)?.catch?.((err: any) => {
1719
+ if (status) status.textContent = `Move failed: ${err?.message || err}`;
1720
+ });
1721
+ }
1722
+ if (status) status.textContent = `Moved ${msgs.length} to ${targetName} — Ctrl+Z to undo`;
1723
+ } else {
1724
+ let skippedXacct = false;
1725
+ for (const [src, g] of byAccount) {
1726
+ if (src !== targetAccount) { skippedXacct = true; continue; } // copy is same-account for now
1727
+ api.copyMessages(src, g.uids, g.folderIds, targetFolderId)?.catch?.((err: any) => {
1728
+ if (status) status.textContent = `Copy failed: ${err?.message || err}`;
1729
+ });
1730
+ }
1731
+ if (status) status.textContent = skippedXacct
1732
+ ? `Copy across accounts isn't supported yet`
1733
+ : `Copying ${msgs.length} to ${targetName}…`;
1734
+ }
1735
+ }
1736
+ })();
1737
+
1599
1738
  document.getElementById("btn-delete")?.addEventListener("click", deleteSelection);
1600
1739
  // Same handlers also bound to the top-toolbar icons so delete/spam work
1601
1740
  // regardless of whether a message is open in the viewer. Useful for quick
@@ -33,7 +33,9 @@ export function showContextMenu(x, y, items) {
33
33
  continue;
34
34
  }
35
35
  const el = document.createElement("div");
36
- el.className = "ctx-item" + (item.disabled ? " ctx-disabled" : "");
36
+ el.className = "ctx-item" + (item.disabled ? " ctx-disabled" : "") + (item.emphasized ? " ctx-emphasized" : "");
37
+ if (item.emphasized)
38
+ el.style.fontWeight = "600";
37
39
  el.textContent = item.label;
38
40
  if (item.tooltip)
39
41
  el.title = item.tooltip;
@@ -1 +1 @@
1
- {"version":3,"file":"context-menu.js","sourceRoot":"","sources":["context-menu.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"context-menu.js","sourceRoot":"","sources":["context-menu.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,9 @@ export interface MenuItem {
12
12
  action: () => void;
13
13
  disabled?: boolean;
14
14
  separator?: boolean;
15
+ /** Render bold, as the menu's default/primary action (e.g. "Move here"
16
+ * in the right-drag menu, vs the secondary "Copy here"). */
17
+ emphasized?: boolean;
15
18
  /** Native browser tooltip shown on hover (title attribute). Use it
16
19
  * to explain non-obvious side effects of an action — e.g., "skips
17
20
  * Trash, no undo" so the user can tell two near-identical entries
@@ -50,7 +53,8 @@ export function showContextMenu(x: number, y: number, items: MenuItem[]): void {
50
53
  continue;
51
54
  }
52
55
  const el = document.createElement("div");
53
- el.className = "ctx-item" + (item.disabled ? " ctx-disabled" : "");
56
+ el.className = "ctx-item" + (item.disabled ? " ctx-disabled" : "") + (item.emphasized ? " ctx-emphasized" : "");
57
+ if (item.emphasized) el.style.fontWeight = "600";
54
58
  el.textContent = item.label;
55
59
  if (item.tooltip) el.title = item.tooltip;
56
60
  if (!item.disabled) {
@@ -1019,6 +1019,7 @@ __export(api_client_exports, {
1019
1019
  connectEvents: () => connectEvents,
1020
1020
  connectWebSocket: () => connectWebSocket,
1021
1021
  consumePendingMailto: () => consumePendingMailto,
1022
+ copyMessages: () => copyMessages,
1022
1023
  createCalendarEvent: () => createCalendarEvent,
1023
1024
  createFolder: () => createFolder,
1024
1025
  createTask: () => createTask,
@@ -1338,6 +1339,9 @@ function moveMessages(accountId, uids, targetFolderId, targetAccountId) {
1338
1339
  function markAsSpamMessages(accountId, uids) {
1339
1340
  return ipc().markAsSpamMessages?.(accountId, uids);
1340
1341
  }
1342
+ function copyMessages(accountId, uids, folderIds, targetFolderId) {
1343
+ return ipc().copyMessages?.(accountId, uids, folderIds, targetFolderId);
1344
+ }
1341
1345
  function undeleteMessage(accountId, uid, folderId) {
1342
1346
  return ipc().undeleteMessage?.(accountId, uid, folderId);
1343
1347
  }
@@ -3461,7 +3465,9 @@ function showContextMenu(x, y, items) {
3461
3465
  continue;
3462
3466
  }
3463
3467
  const el = document.createElement("div");
3464
- el.className = "ctx-item" + (item.disabled ? " ctx-disabled" : "");
3468
+ el.className = "ctx-item" + (item.disabled ? " ctx-disabled" : "") + (item.emphasized ? " ctx-emphasized" : "");
3469
+ if (item.emphasized)
3470
+ el.style.fontWeight = "600";
3465
3471
  el.textContent = item.label;
3466
3472
  if (item.tooltip)
3467
3473
  el.title = item.tooltip;