@bobfrankston/rmfmail 1.1.248 → 1.1.250

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 (39) hide show
  1. package/client/app.bundle.js +149 -14
  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/components/message-list.js +13 -5
  10. package/client/components/message-list.js.map +1 -1
  11. package/client/components/message-list.ts +14 -6
  12. package/client/compose/compose.bundle.js +7 -1
  13. package/client/compose/compose.bundle.js.map +2 -2
  14. package/client/lib/api-client.js +6 -0
  15. package/client/lib/api-client.js.map +1 -1
  16. package/client/lib/api-client.ts +7 -0
  17. package/client/lib/message-state.js +23 -8
  18. package/client/lib/message-state.js.map +1 -1
  19. package/client/lib/message-state.ts +24 -10
  20. package/package.json +3 -3
  21. package/packages/mailx-imap/index.d.ts.map +1 -1
  22. package/packages/mailx-imap/index.js +24 -0
  23. package/packages/mailx-imap/index.js.map +1 -1
  24. package/packages/mailx-imap/index.ts +25 -0
  25. package/packages/mailx-imap/package-lock.json +2 -2
  26. package/packages/mailx-imap/package.json +1 -1
  27. package/packages/mailx-service/index.d.ts +7 -0
  28. package/packages/mailx-service/index.d.ts.map +1 -1
  29. package/packages/mailx-service/index.js +15 -0
  30. package/packages/mailx-service/index.js.map +1 -1
  31. package/packages/mailx-service/index.ts +15 -0
  32. package/packages/mailx-service/jsonrpc.js +3 -0
  33. package/packages/mailx-service/jsonrpc.js.map +1 -1
  34. package/packages/mailx-service/jsonrpc.ts +3 -0
  35. package/packages/mailx-service/sync-queue.d.ts +5 -0
  36. package/packages/mailx-service/sync-queue.d.ts.map +1 -1
  37. package/packages/mailx-service/sync-queue.js +8 -0
  38. package/packages/mailx-service/sync-queue.js.map +1 -1
  39. package/packages/mailx-service/sync-queue.ts +9 -0
@@ -26,6 +26,7 @@ __export(api_client_exports, {
26
26
  connectEvents: () => connectEvents,
27
27
  connectWebSocket: () => connectWebSocket,
28
28
  consumePendingMailto: () => consumePendingMailto,
29
+ copyMessages: () => copyMessages,
29
30
  createCalendarEvent: () => createCalendarEvent,
30
31
  createFolder: () => createFolder,
31
32
  createTask: () => createTask,
@@ -345,6 +346,9 @@ function moveMessages(accountId, uids, targetFolderId, targetAccountId) {
345
346
  function markAsSpamMessages(accountId, uids) {
346
347
  return ipc().markAsSpamMessages?.(accountId, uids);
347
348
  }
349
+ function copyMessages(accountId, uids, folderIds, targetFolderId) {
350
+ return ipc().copyMessages?.(accountId, uids, folderIds, targetFolderId);
351
+ }
348
352
  function undeleteMessage(accountId, uid, folderId) {
349
353
  return ipc().undeleteMessage?.(accountId, uid, folderId);
350
354
  }
@@ -652,7 +656,9 @@ function showContextMenu(x, y, items) {
652
656
  continue;
653
657
  }
654
658
  const el = document.createElement("div");
655
- el.className = "ctx-item" + (item.disabled ? " ctx-disabled" : "");
659
+ el.className = "ctx-item" + (item.disabled ? " ctx-disabled" : "") + (item.emphasized ? " ctx-emphasized" : "");
660
+ if (item.emphasized)
661
+ el.style.fontWeight = "600";
656
662
  el.textContent = item.label;
657
663
  if (item.tooltip)
658
664
  el.title = item.tooltip;
@@ -740,25 +746,32 @@ function getMessages2() {
740
746
  return messages;
741
747
  }
742
748
  function removeMessages(uids, currentlyFocused) {
743
- const removeSet = new Set(uids.map((u) => `${u.accountId}:${u.uid}`));
744
- const focusedKey = currentlyFocused ? `${currentlyFocused.accountId}:${currentlyFocused.uid}` : null;
745
- const focusedWasRemoved = focusedKey !== null && removeSet.has(focusedKey);
749
+ const fullSet = /* @__PURE__ */ new Set();
750
+ const looseSet = /* @__PURE__ */ new Set();
751
+ for (const u of uids) {
752
+ if (u.folderId != null)
753
+ fullSet.add(`${u.accountId}:${u.folderId}:${u.uid}`);
754
+ else
755
+ looseSet.add(`${u.accountId}:${u.uid}`);
756
+ }
757
+ const isRemoved = (m) => m.folderId != null && fullSet.has(`${m.accountId}:${m.folderId}:${m.uid}`) || looseSet.has(`${m.accountId}:${m.uid}`);
758
+ const focusedWasRemoved = currentlyFocused !== null && isRemoved(currentlyFocused);
746
759
  let nextSurvivor = null;
747
760
  if (focusedWasRemoved) {
748
761
  let lastRemovedIdx = -1;
749
762
  for (let i = 0; i < messages.length; i++) {
750
- if (removeSet.has(`${messages[i].accountId}:${messages[i].uid}`))
763
+ if (isRemoved(messages[i]))
751
764
  lastRemovedIdx = i;
752
765
  }
753
766
  for (let i = lastRemovedIdx + 1; i < messages.length; i++) {
754
- if (!removeSet.has(`${messages[i].accountId}:${messages[i].uid}`)) {
767
+ if (!isRemoved(messages[i])) {
755
768
  nextSurvivor = messages[i];
756
769
  break;
757
770
  }
758
771
  }
759
772
  if (!nextSurvivor) {
760
773
  for (let i = lastRemovedIdx - 1; i >= 0; i--) {
761
- if (!removeSet.has(`${messages[i].accountId}:${messages[i].uid}`)) {
774
+ if (!isRemoved(messages[i])) {
762
775
  nextSurvivor = messages[i];
763
776
  break;
764
777
  }
@@ -767,11 +780,11 @@ function removeMessages(uids, currentlyFocused) {
767
780
  }
768
781
  const removedIds = /* @__PURE__ */ new Set();
769
782
  for (const m of messages) {
770
- if (removeSet.has(`${m.accountId}:${m.uid}`) && m.messageId) {
783
+ if (isRemoved(m) && m.messageId) {
771
784
  removedIds.add(m.messageId);
772
785
  }
773
786
  }
774
- messages = messages.filter((m) => !removeSet.has(`${m.accountId}:${m.uid}`));
787
+ messages = messages.filter((m) => !isRemoved(m));
775
788
  if (removedIds.size > 0) {
776
789
  for (const m of messages) {
777
790
  if (m.messageId && removedIds.has(m.messageId) && typeof m.dupeCount === "number") {
@@ -3155,18 +3168,18 @@ function updateSortIndicators() {
3155
3168
  });
3156
3169
  }
3157
3170
  function removeMessagesAndReconcile(uids) {
3158
- const focusedIdent = focusedRow ? { accountId: focusedRow.accountId, uid: focusedRow.msg.uid } : null;
3171
+ const focusedIdent = focusedRow ? { accountId: focusedRow.accountId, uid: focusedRow.msg.uid, folderId: focusedRow.msg.folderId } : null;
3159
3172
  const outcome = removeMessages(uids, focusedIdent);
3160
3173
  listCache.clear();
3161
3174
  const body = document.getElementById("ml-body");
3162
3175
  if (body) {
3163
- const stateUids = new Set(getMessages2().map((m) => `${m.accountId}:${m.uid}`));
3176
+ const stateUids = new Set(getMessages2().map((m) => `${m.accountId}:${m.folderId}:${m.uid}`));
3164
3177
  for (const row of Array.from(body.querySelectorAll(".ml-row"))) {
3165
3178
  const el = row;
3166
- const key = `${el.dataset.accountId}:${el.dataset.uid}`;
3179
+ const key = `${el.dataset.accountId}:${el.dataset.folderId}:${el.dataset.uid}`;
3167
3180
  if (!stateUids.has(key)) {
3168
- const dead = rowByKey.get(key);
3169
- if (dead)
3181
+ const dead = rowByKey.get(rowKey(el.dataset.accountId || "", Number(el.dataset.uid)));
3182
+ if (dead && dead.el === el)
3170
3183
  dead.detach();
3171
3184
  else
3172
3185
  el.remove();
@@ -8106,6 +8119,128 @@ async function performUndo() {
8106
8119
  document.addEventListener("mailx-moved", (e) => {
8107
8120
  pushUndo({ kind: "move", at: Date.now(), payload: e.detail });
8108
8121
  });
8122
+ (() => {
8123
+ const SLOP = 6;
8124
+ let cand = null;
8125
+ let active = false;
8126
+ let ghost = null;
8127
+ let suppressContext = false;
8128
+ const style = document.createElement("style");
8129
+ style.textContent = "body.rmf-rdrag, body.rmf-rdrag * { cursor: grabbing !important; }.rmf-rdrag-ghost { position: fixed; z-index: 100000; pointer-events: none; background: var(--color-accent, #1a6dd4); color: #fff; padding: 2px 9px; border-radius: 4px; font: 12px system-ui; box-shadow: 0 2px 8px rgba(0,0,0,.3); }.ft-folder.rmf-rdrag-over { outline: 2px solid var(--color-accent, #1a6dd4); outline-offset: -2px; border-radius: 4px; }";
8130
+ document.head.appendChild(style);
8131
+ const folderAt = (x, y) => document.elementFromPoint(x, y)?.closest?.(".ft-folder") || null;
8132
+ const clearOver = () => document.querySelectorAll(".ft-folder.rmf-rdrag-over").forEach((el) => el.classList.remove("rmf-rdrag-over"));
8133
+ const cleanup = () => {
8134
+ active = false;
8135
+ cand = null;
8136
+ document.body.classList.remove("rmf-rdrag");
8137
+ ghost?.remove();
8138
+ ghost = null;
8139
+ clearOver();
8140
+ };
8141
+ document.addEventListener("mousedown", (e) => {
8142
+ if (e.button !== 2) return;
8143
+ const row = e.target?.closest?.(".ml-row");
8144
+ if (!row) return;
8145
+ const pressed = {
8146
+ accountId: row.dataset.accountId || "",
8147
+ uid: Number(row.dataset.uid),
8148
+ folderId: Number(row.dataset.folderId)
8149
+ };
8150
+ if (!pressed.uid) return;
8151
+ let msgs = getSelectedMessages();
8152
+ if (!msgs.some((m) => m.accountId === pressed.accountId && m.uid === pressed.uid)) msgs = [pressed];
8153
+ cand = { msgs, x: e.clientX, y: e.clientY };
8154
+ active = false;
8155
+ }, true);
8156
+ document.addEventListener("mousemove", (e) => {
8157
+ if (!cand) return;
8158
+ if (!active) {
8159
+ if (Math.abs(e.clientX - cand.x) < SLOP && Math.abs(e.clientY - cand.y) < SLOP) return;
8160
+ active = true;
8161
+ document.body.classList.add("rmf-rdrag");
8162
+ ghost = document.createElement("div");
8163
+ ghost.className = "rmf-rdrag-ghost";
8164
+ ghost.textContent = cand.msgs.length === 1 ? "1 message" : `${cand.msgs.length} messages`;
8165
+ document.body.appendChild(ghost);
8166
+ }
8167
+ if (ghost) {
8168
+ ghost.style.left = `${e.clientX + 14}px`;
8169
+ ghost.style.top = `${e.clientY + 8}px`;
8170
+ }
8171
+ clearOver();
8172
+ folderAt(e.clientX, e.clientY)?.classList.add("rmf-rdrag-over");
8173
+ }, true);
8174
+ document.addEventListener("mouseup", (e) => {
8175
+ if (!cand) return;
8176
+ const wasActive = active;
8177
+ const msgs = cand.msgs;
8178
+ const folder = wasActive ? folderAt(e.clientX, e.clientY) : null;
8179
+ const mx = e.clientX, my = e.clientY;
8180
+ cleanup();
8181
+ if (!wasActive) return;
8182
+ suppressContext = true;
8183
+ if (!folder) return;
8184
+ e.preventDefault();
8185
+ e.stopPropagation();
8186
+ const targetAccount = folder.dataset.accountId || "";
8187
+ const targetFolderId = Number(folder.dataset.folderId);
8188
+ const targetName = folder.querySelector(".ft-folder-name")?.textContent?.trim() || "folder";
8189
+ void showRightDragMenu(mx, my, msgs, targetAccount, targetFolderId, targetName);
8190
+ }, true);
8191
+ document.addEventListener("contextmenu", (e) => {
8192
+ if (suppressContext) {
8193
+ e.preventDefault();
8194
+ e.stopPropagation();
8195
+ suppressContext = false;
8196
+ }
8197
+ }, true);
8198
+ async function showRightDragMenu(x, y, msgs, targetAccount, targetFolderId, targetName) {
8199
+ const { showContextMenu: showContextMenu2 } = await Promise.resolve().then(() => (init_context_menu(), context_menu_exports));
8200
+ const n = msgs.length;
8201
+ const label = n === 1 ? "message" : `${n} messages`;
8202
+ showContextMenu2(x, y, [
8203
+ { label: `Move ${label} here`, emphasized: true, action: () => void runRightDragOp(msgs, targetAccount, targetFolderId, targetName, "move") },
8204
+ { label: `Copy ${label} here`, action: () => void runRightDragOp(msgs, targetAccount, targetFolderId, targetName, "copy") }
8205
+ ]);
8206
+ }
8207
+ async function runRightDragOp(msgs, targetAccount, targetFolderId, targetName, op) {
8208
+ const status = document.getElementById("status-sync");
8209
+ const api = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
8210
+ const byAccount = /* @__PURE__ */ new Map();
8211
+ for (const m of msgs) {
8212
+ const g = byAccount.get(m.accountId) || { uids: [], folderIds: [] };
8213
+ g.uids.push(m.uid);
8214
+ g.folderIds.push(m.folderId);
8215
+ byAccount.set(m.accountId, g);
8216
+ }
8217
+ if (op === "move") {
8218
+ removeMessagesAndReconcile(msgs);
8219
+ document.dispatchEvent(new CustomEvent("mailx-moved", {
8220
+ detail: { messages: msgs.map((m) => ({ accountId: m.accountId, uid: m.uid, sourceFolderId: m.folderId })) }
8221
+ }));
8222
+ for (const [src, g] of byAccount) {
8223
+ const targetAcct = src === targetAccount ? void 0 : targetAccount;
8224
+ api.moveMessages(src, g.uids, targetFolderId, targetAcct)?.catch?.((err) => {
8225
+ if (status) status.textContent = `Move failed: ${err?.message || err}`;
8226
+ });
8227
+ }
8228
+ if (status) status.textContent = `Moved ${msgs.length} to ${targetName} \u2014 Ctrl+Z to undo`;
8229
+ } else {
8230
+ let skippedXacct = false;
8231
+ for (const [src, g] of byAccount) {
8232
+ if (src !== targetAccount) {
8233
+ skippedXacct = true;
8234
+ continue;
8235
+ }
8236
+ api.copyMessages(src, g.uids, g.folderIds, targetFolderId)?.catch?.((err) => {
8237
+ if (status) status.textContent = `Copy failed: ${err?.message || err}`;
8238
+ });
8239
+ }
8240
+ if (status) status.textContent = skippedXacct ? `Copy across accounts isn't supported yet` : `Copying ${msgs.length} to ${targetName}\u2026`;
8241
+ }
8242
+ }
8243
+ })();
8109
8244
  document.getElementById("btn-delete")?.addEventListener("click", deleteSelection);
8110
8245
  document.getElementById("btn-tb-delete")?.addEventListener("click", deleteSelection);
8111
8246
  document.getElementById("btn-tb-spam")?.addEventListener("click", spamSelectedMessages);