@bobfrankston/rmfmail 1.1.36 → 1.1.38

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.
@@ -2298,7 +2298,7 @@ function pickFolder(accountId, opts) {
2298
2298
  }
2299
2299
  const excluded = new Set(opts?.excludeFolderIds || []);
2300
2300
  const targets = folders.filter((f) => !excluded.has(f.id)).filter((f) => f.specialUse !== "outbox").sort((a, b) => a.path.localeCompare(b.path));
2301
- function render(filter) {
2301
+ function render2(filter) {
2302
2302
  listEl.innerHTML = "";
2303
2303
  const lc = filter.toLowerCase().trim();
2304
2304
  let firstMatchSet = false;
@@ -2331,8 +2331,8 @@ function pickFolder(accountId, opts) {
2331
2331
  listEl.appendChild(row);
2332
2332
  }
2333
2333
  }
2334
- render("");
2335
- search.addEventListener("input", () => render(search.value));
2334
+ render2("");
2335
+ search.addEventListener("input", () => render2(search.value));
2336
2336
  setTimeout(() => search.focus(), 0);
2337
2337
  });
2338
2338
  }
@@ -2400,24 +2400,27 @@ function rememberPosition() {
2400
2400
  const uid = Number(sel.dataset.uid);
2401
2401
  if (!Number.isFinite(uid))
2402
2402
  return;
2403
- positionMemory.set(key, { uid, scroll: body.scrollTop });
2403
+ positionMemory.set(key, { uid, uuid: sel.dataset.uuid || "", scroll: body.scrollTop });
2404
2404
  persistPositions();
2405
2405
  }
2406
2406
  function pickRestoreUid(items, saved) {
2407
2407
  if (!items.length)
2408
2408
  return null;
2409
- if (items.some((m) => m.uid === saved))
2410
- return saved;
2411
- let best = -1;
2409
+ if (saved.uuid) {
2410
+ const exact = items.find((m) => m.uuid && m.uuid === saved.uuid);
2411
+ if (exact?.uuid)
2412
+ return exact.uuid;
2413
+ }
2414
+ let best = null;
2412
2415
  for (const m of items) {
2413
- if (typeof m.uid !== "number")
2416
+ if (typeof m.uid !== "number" || !m.uuid)
2414
2417
  continue;
2415
- if (m.uid < saved && m.uid > best)
2416
- best = m.uid;
2418
+ if (m.uid < saved.uid && (!best || m.uid > best.uid))
2419
+ best = m;
2417
2420
  }
2418
- if (best >= 0)
2419
- return best;
2420
- return typeof items[0]?.uid === "number" ? items[0].uid : null;
2421
+ if (best)
2422
+ return best.uuid;
2423
+ return items[0]?.uuid || null;
2421
2424
  }
2422
2425
  function listResultsEqual(a, b) {
2423
2426
  if (!a || a.length !== b.length)
@@ -2748,10 +2751,10 @@ async function loadUnifiedInbox(autoSelect = true) {
2748
2751
  totalMessages = cached.total;
2749
2752
  setMessages(cached.items);
2750
2753
  renderMessages(body, "", cached.items);
2751
- const targetUid = remembered ? pickRestoreUid(cached.items, remembered.uid) : null;
2752
- if (targetUid != null) {
2754
+ const targetUuid = remembered ? pickRestoreUid(cached.items, remembered) : null;
2755
+ if (targetUuid) {
2753
2756
  body.scrollTop = savedScroll;
2754
- restoreSelection(body, String(targetUid));
2757
+ restoreSelection(body, targetUuid);
2755
2758
  } else if (autoSelect) {
2756
2759
  selectFirst(body);
2757
2760
  }
@@ -2773,10 +2776,10 @@ async function loadUnifiedInbox(autoSelect = true) {
2773
2776
  }
2774
2777
  setMessages(result.items);
2775
2778
  renderMessages(body, "", result.items);
2776
- const targetUid = remembered ? pickRestoreUid(result.items, remembered.uid) : null;
2777
- if (targetUid != null) {
2779
+ const targetUuid = remembered ? pickRestoreUid(result.items, remembered) : null;
2780
+ if (targetUuid) {
2778
2781
  body.scrollTop = savedScroll;
2779
- restoreSelection(body, String(targetUid));
2782
+ restoreSelection(body, targetUuid);
2780
2783
  } else if (autoSelect) {
2781
2784
  selectFirst(body);
2782
2785
  }
@@ -2887,11 +2890,11 @@ async function loadMessages(accountId, folderId, page = 1, specialUse = "", auto
2887
2890
  totalMessages = cached.total;
2888
2891
  setMessages(cached.items);
2889
2892
  renderMessages(body, accountId, cached.items);
2890
- const targetUid = remembered ? pickRestoreUid(cached.items, remembered.uid) : null;
2891
- if (targetUid != null) {
2893
+ const targetUuid = remembered ? pickRestoreUid(cached.items, remembered) : null;
2894
+ if (targetUuid) {
2892
2895
  requestAnimationFrame(() => {
2893
2896
  body.scrollTop = savedScroll;
2894
- restoreSelection(body, String(targetUid));
2897
+ restoreSelection(body, targetUuid);
2895
2898
  });
2896
2899
  } else if (autoSelect) {
2897
2900
  selectFirst(body);
@@ -2915,13 +2918,13 @@ async function loadMessages(accountId, folderId, page = 1, specialUse = "", auto
2915
2918
  }
2916
2919
  setMessages(result.items);
2917
2920
  renderMessages(body, accountId, result.items);
2918
- const targetUid = remembered ? pickRestoreUid(result.items, remembered.uid) : null;
2919
- if (targetUid != null) {
2921
+ const targetUuid = remembered ? pickRestoreUid(result.items, remembered) : null;
2922
+ if (targetUuid) {
2920
2923
  requestAnimationFrame(() => {
2921
2924
  if (myGen !== loadGen)
2922
2925
  return;
2923
2926
  body.scrollTop = savedScroll;
2924
- restoreSelection(body, String(targetUid));
2927
+ restoreSelection(body, targetUuid);
2925
2928
  });
2926
2929
  } else if (autoSelect) {
2927
2930
  selectFirst(body);
@@ -2967,12 +2970,16 @@ function selectFirst(body) {
2967
2970
  if (firstRow)
2968
2971
  firstRow.click();
2969
2972
  }
2970
- function restoreSelection(body, savedUid) {
2971
- if (!savedUid)
2973
+ function restoreSelection(body, savedUuid) {
2974
+ if (!savedUuid)
2975
+ return;
2976
+ const row = body.querySelector(`.ml-row[data-uuid="${CSS.escape(savedUuid)}"]`);
2977
+ if (!row)
2972
2978
  return;
2973
- const accountId = body.querySelector(`.ml-row[data-uid="${savedUid}"]`)?.dataset.accountId;
2974
- if (accountId) {
2975
- focusByIdentity(accountId, Number(savedUid), { scroll: false });
2979
+ const accountId = row.dataset.accountId;
2980
+ const uid = Number(row.dataset.uid);
2981
+ if (accountId && Number.isFinite(uid)) {
2982
+ focusByIdentity(accountId, uid, { scroll: false });
2976
2983
  }
2977
2984
  }
2978
2985
  async function showThreadPopup(pillEl, headMsg) {
@@ -3173,6 +3180,8 @@ var init_message_list = __esm({
3173
3180
  row.dataset.uid = String(msg.uid);
3174
3181
  row.dataset.accountId = accountId;
3175
3182
  row.dataset.folderId = String(msg.folderId);
3183
+ if (msg.uuid)
3184
+ row.dataset.uuid = msg.uuid;
3176
3185
  if (msg.threadId)
3177
3186
  row.dataset.threadId = msg.threadId;
3178
3187
  if (threadHead)
@@ -4466,7 +4475,7 @@ async function openAddressBook() {
4466
4475
  const listEl = panel.querySelector("#ab-list");
4467
4476
  const countEl = panel.querySelector("#ab-count");
4468
4477
  let editingEmail = null;
4469
- const render = (items, total) => {
4478
+ const render2 = (items, total) => {
4470
4479
  countEl.textContent = total === items.length ? `${total} contact${total === 1 ? "" : "s"}` : `Showing ${items.length} of ${total}`;
4471
4480
  if (items.length === 0) {
4472
4481
  listEl.innerHTML = `<div class="ab-empty">No contacts match.</div>`;
@@ -4576,7 +4585,7 @@ async function openAddressBook() {
4576
4585
  const reload = async () => {
4577
4586
  try {
4578
4587
  const r = await listContacts(searchInput2.value, 1, 200);
4579
- render(r.items, r.total);
4588
+ render2(r.items, r.total);
4580
4589
  } catch (e) {
4581
4590
  listEl.innerHTML = `<div class="ab-empty">Load failed: ${escapeHtml5(e?.message || String(e))}</div>`;
4582
4591
  }
@@ -6090,6 +6099,135 @@ async function updateFolderCounts() {
6090
6099
  // client/app.ts
6091
6100
  init_message_list();
6092
6101
  init_mailx_types();
6102
+
6103
+ // client/components/tabs.js
6104
+ var STORAGE_KEY = "mailx-view-tabs";
6105
+ var tabs = [];
6106
+ var activeId = "";
6107
+ var stripEl = null;
6108
+ var applyView = () => {
6109
+ };
6110
+ var _nextId = 1;
6111
+ function newId() {
6112
+ return `t${_nextId++}`;
6113
+ }
6114
+ function persist() {
6115
+ try {
6116
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ tabs, activeId }));
6117
+ } catch {
6118
+ }
6119
+ }
6120
+ function render() {
6121
+ if (!stripEl)
6122
+ return;
6123
+ stripEl.innerHTML = "";
6124
+ for (const tab of tabs) {
6125
+ const chip = document.createElement("div");
6126
+ chip.className = "view-tab" + (tab.id === activeId ? " active" : "");
6127
+ chip.dataset.tabId = tab.id;
6128
+ const label = document.createElement("span");
6129
+ label.className = "view-tab-label";
6130
+ label.textContent = tab.title || "(view)";
6131
+ chip.appendChild(label);
6132
+ if (tabs.length > 1) {
6133
+ const close = document.createElement("button");
6134
+ close.className = "view-tab-close";
6135
+ close.textContent = "\xD7";
6136
+ close.title = "Close tab";
6137
+ close.addEventListener("click", (e) => {
6138
+ e.stopPropagation();
6139
+ closeTab(tab.id);
6140
+ });
6141
+ chip.appendChild(close);
6142
+ }
6143
+ chip.addEventListener("click", () => activate(tab.id));
6144
+ stripEl.appendChild(chip);
6145
+ }
6146
+ const plus = document.createElement("button");
6147
+ plus.className = "view-tab-new";
6148
+ plus.textContent = "+";
6149
+ plus.title = "New tab (All Inboxes)";
6150
+ plus.addEventListener("click", () => openTab({ kind: "unified" }, "All Inboxes", true));
6151
+ stripEl.appendChild(plus);
6152
+ stripEl.hidden = tabs.length < 1;
6153
+ }
6154
+ function initTabs(strip, apply) {
6155
+ stripEl = strip;
6156
+ applyView = apply;
6157
+ try {
6158
+ const raw = sessionStorage.getItem(STORAGE_KEY);
6159
+ if (raw) {
6160
+ const parsed = JSON.parse(raw);
6161
+ if (Array.isArray(parsed?.tabs) && parsed.tabs.length) {
6162
+ tabs = parsed.tabs;
6163
+ activeId = parsed.activeId || tabs[0].id;
6164
+ for (const t of tabs) {
6165
+ const n = Number(String(t.id).replace(/^t/, ""));
6166
+ if (Number.isFinite(n) && n >= _nextId)
6167
+ _nextId = n + 1;
6168
+ }
6169
+ }
6170
+ }
6171
+ } catch {
6172
+ }
6173
+ render();
6174
+ }
6175
+ function activeTab() {
6176
+ return tabs.find((t) => t.id === activeId) || null;
6177
+ }
6178
+ function openTab(view, title, activateIt = true) {
6179
+ const tab = { id: newId(), title, view };
6180
+ tabs.push(tab);
6181
+ if (activateIt)
6182
+ activeId = tab.id;
6183
+ persist();
6184
+ render();
6185
+ if (activateIt)
6186
+ applyView(tab);
6187
+ }
6188
+ function activate(id) {
6189
+ const tab = tabs.find((t) => t.id === id);
6190
+ if (!tab || id === activeId)
6191
+ return;
6192
+ activeId = id;
6193
+ persist();
6194
+ render();
6195
+ applyView(tab);
6196
+ }
6197
+ function closeTab(id) {
6198
+ if (tabs.length < 2)
6199
+ return;
6200
+ const idx = tabs.findIndex((t) => t.id === id);
6201
+ if (idx < 0)
6202
+ return;
6203
+ const wasActive = id === activeId;
6204
+ tabs.splice(idx, 1);
6205
+ if (wasActive) {
6206
+ const next = tabs[Math.max(0, idx - 1)];
6207
+ activeId = next.id;
6208
+ persist();
6209
+ render();
6210
+ applyView(next);
6211
+ } else {
6212
+ persist();
6213
+ render();
6214
+ }
6215
+ }
6216
+ function setActiveView(view, title) {
6217
+ let tab = activeTab();
6218
+ if (!tab) {
6219
+ tab = { id: newId(), title, view };
6220
+ tabs.push(tab);
6221
+ activeId = tab.id;
6222
+ } else {
6223
+ tab.view = view;
6224
+ tab.title = title;
6225
+ }
6226
+ persist();
6227
+ render();
6228
+ }
6229
+
6230
+ // client/app.ts
6093
6231
  init_message_viewer();
6094
6232
  init_api_client();
6095
6233
  init_message_state();
@@ -6383,6 +6521,7 @@ initFolderTree(folderTree, (accountId, folderId, folderName, specialUse) => {
6383
6521
  loadMessages(accountId, folderId, 1, specialUse);
6384
6522
  setTitle(`${APP_NAME} - ${folderName}`);
6385
6523
  setNarrowFolderTitle(folderName);
6524
+ setActiveView({ kind: "folder", accountId, folderId, specialUse }, folderName);
6386
6525
  document.dispatchEvent(new CustomEvent("mailx-folder-changed", { detail: { accountId, folderId } }));
6387
6526
  }, () => {
6388
6527
  currentFolderSpecialUse = "inbox";
@@ -6392,7 +6531,34 @@ initFolderTree(folderTree, (accountId, folderId, folderName, specialUse) => {
6392
6531
  loadUnifiedInbox();
6393
6532
  setTitle(`${APP_NAME} - All Inboxes`);
6394
6533
  setNarrowFolderTitle("All Inboxes");
6534
+ setActiveView({ kind: "unified" }, "All Inboxes");
6395
6535
  });
6536
+ function applyTabView(tab) {
6537
+ if (searchInput) searchInput.value = "";
6538
+ clearSearchMode();
6539
+ releaseFocus();
6540
+ const v = tab.view;
6541
+ if (v.kind === "unified") {
6542
+ currentFolderSpecialUse = "inbox";
6543
+ loadUnifiedInbox();
6544
+ setTitle(`${APP_NAME} - All Inboxes`);
6545
+ setNarrowFolderTitle("All Inboxes");
6546
+ } else if (v.kind === "folder") {
6547
+ currentFolderSpecialUse = v.specialUse;
6548
+ currentAccountId3 = v.accountId;
6549
+ currentFolderId2 = v.folderId;
6550
+ loadMessages(v.accountId, v.folderId, 1, v.specialUse);
6551
+ setTitle(`${APP_NAME} - ${tab.title}`);
6552
+ setNarrowFolderTitle(tab.title);
6553
+ } else {
6554
+ if (searchInput) searchInput.value = v.query;
6555
+ loadSearchResults(v.query, v.scope, v.accountId, v.folderId, v.includeTrash);
6556
+ setTitle(`${APP_NAME} - Search`);
6557
+ setNarrowFolderTitle(`Search: ${v.query}`);
6558
+ }
6559
+ }
6560
+ var tabStripEl = document.getElementById("view-tab-strip");
6561
+ if (tabStripEl) initTabs(tabStripEl, applyTabView);
6396
6562
  initMessageList((_accountId, _uid, _folderId) => {
6397
6563
  if (window.innerWidth <= 768) {
6398
6564
  document.getElementById("message-viewer")?.classList.add("narrow-active");
@@ -7400,6 +7566,10 @@ function doSearch(immediate = false) {
7400
7566
  }
7401
7567
  loadSearchResults(query, effectiveScope, currentAccountId3, currentFolderId2, includeTrash);
7402
7568
  setTitle(`${APP_NAME} - Search: ${query}`);
7569
+ setActiveView(
7570
+ { kind: "search", query, scope: effectiveScope, accountId: currentAccountId3, folderId: currentFolderId2, includeTrash },
7571
+ `Search: ${query}`
7572
+ );
7403
7573
  if (immediate) recordSearchHistory(query);
7404
7574
  }
7405
7575
  var currentAccountId3 = "";
@@ -8239,7 +8409,7 @@ function openShortcutsDialog() {
8239
8409
  const panel = document.createElement("div");
8240
8410
  panel.className = "mailx-modal";
8241
8411
  panel.style.maxWidth = "640px";
8242
- const tabs = [
8412
+ const tabs2 = [
8243
8413
  {
8244
8414
  id: "app",
8245
8415
  label: "Application",
@@ -8298,10 +8468,10 @@ function openShortcutsDialog() {
8298
8468
  <button type="button" class="mailx-modal-close" id="sc-x" title="Close (Esc)" aria-label="Close">&times;</button>
8299
8469
  </div>
8300
8470
  <div class="mailx-shortcuts-tabs" role="tablist" style="display:flex;gap:0;border-bottom:1px solid var(--color-border, #ddd);margin:0 -16px 12px -16px;padding:0 16px;">
8301
- ${tabs.map((t, i) => `<button type="button" role="tab" class="mailx-shortcuts-tab" data-tab="${t.id}" aria-selected="${i === 0}" style="background:none;border:0;padding:8px 14px;cursor:pointer;font:inherit;border-bottom:2px solid ${i === 0 ? "var(--color-accent, #3b82f6)" : "transparent"};color:${i === 0 ? "var(--color-text)" : "var(--color-text-muted, #888)"};">${t.label}</button>`).join("")}
8471
+ ${tabs2.map((t, i) => `<button type="button" role="tab" class="mailx-shortcuts-tab" data-tab="${t.id}" aria-selected="${i === 0}" style="background:none;border:0;padding:8px 14px;cursor:pointer;font:inherit;border-bottom:2px solid ${i === 0 ? "var(--color-accent, #3b82f6)" : "transparent"};color:${i === 0 ? "var(--color-text)" : "var(--color-text-muted, #888)"};">${t.label}</button>`).join("")}
8302
8472
  </div>
8303
8473
  <div class="mailx-about">
8304
- ${tabs.map((t, i) => `<div class="mailx-shortcuts-pane" data-pane="${t.id}" ${i === 0 ? "" : "hidden"}>
8474
+ ${tabs2.map((t, i) => `<div class="mailx-shortcuts-pane" data-pane="${t.id}" ${i === 0 ? "" : "hidden"}>
8305
8475
  <dl class="mailx-about-dl">
8306
8476
  ${t.rows.map(([k, v]) => `<dt>${k}</dt><dd>${v}</dd>`).join("")}
8307
8477
  </dl>