@bobfrankston/rmfmail 1.0.680 → 1.0.686
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/bin/build-quill.js +35 -0
- package/bin/lean-accounts.js +0 -1
- package/client/app.bundle.js +172 -55
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +73 -27
- package/client/app.js.map +1 -1
- package/client/app.ts +73 -29
- package/client/components/context-menu.js +2 -0
- package/client/components/context-menu.js.map +1 -1
- package/client/components/context-menu.ts +6 -0
- package/client/components/folder-tree.js +26 -4
- package/client/components/folder-tree.js.map +1 -1
- package/client/components/folder-tree.ts +21 -4
- package/client/components/message-list.js +108 -40
- package/client/components/message-list.js.map +1 -1
- package/client/components/message-list.ts +103 -38
- package/client/compose/compose.bundle.js +189 -17
- package/client/compose/compose.bundle.js.map +3 -3
- package/client/compose/compose.js +51 -3
- package/client/compose/compose.js.map +1 -1
- package/client/compose/compose.ts +47 -3
- package/client/compose/spellcheck.js +178 -12
- package/client/compose/spellcheck.js.map +1 -1
- package/client/compose/spellcheck.ts +168 -8
- package/client/lib/api-client.js +3 -0
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +4 -0
- package/client/lib/mailxapi.js +3 -0
- package/client/lib/quill/quill.js +3 -0
- package/client/lib/quill/quill.snow.css +10 -0
- package/client/lib/rmf-tiny.js +25 -6
- package/docs/accounts.md +7 -2
- package/package.json +8 -8
- package/packages/mailx-core/index.d.ts.map +1 -1
- package/packages/mailx-core/index.js +2 -12
- package/packages/mailx-core/index.js.map +1 -1
- package/packages/mailx-core/index.ts +2 -12
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +31 -6
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +32 -6
- package/packages/mailx-imap/node_modules.npmglobalize-stash-11884/.package-lock.json +116 -0
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-service/index.d.ts +22 -0
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +134 -6
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +128 -11
- package/packages/mailx-service/jsonrpc.js +3 -0
- package/packages/mailx-service/jsonrpc.js.map +1 -1
- package/packages/mailx-service/jsonrpc.ts +3 -0
- package/packages/mailx-service/local-store.d.ts.map +1 -1
- package/packages/mailx-service/local-store.js +15 -12
- package/packages/mailx-service/local-store.js.map +1 -1
- package/packages/mailx-service/local-store.ts +15 -12
- package/packages/mailx-settings/docs/accounts.md +14 -1
- package/packages/mailx-settings/docs/npmglobalize-disttag.md +90 -0
- package/packages/mailx-settings/docs/prod-android.md +88 -0
- package/packages/mailx-settings/docs/prod.md +224 -0
- package/packages/mailx-settings/docs/push-relay.md +141 -0
- package/packages/mailx-settings/docs/rmf-tiny.md +156 -0
- package/packages/mailx-settings/index.d.ts +2 -2
- package/packages/mailx-settings/index.d.ts.map +1 -1
- package/packages/mailx-settings/index.js +13 -10
- package/packages/mailx-settings/index.js.map +1 -1
- package/packages/mailx-settings/index.ts +13 -9
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/db.d.ts.map +1 -1
- package/packages/mailx-store/db.js +44 -6
- package/packages/mailx-store/db.js.map +1 -1
- package/packages/mailx-store/db.ts +47 -6
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store-web/package.json +4 -1
- package/packages/mailx-store-web/web-settings.d.ts.map +1 -1
- package/packages/mailx-store-web/web-settings.js +0 -1
- package/packages/mailx-store-web/web-settings.js.map +1 -1
- package/packages/mailx-store-web/web-settings.ts +0 -1
- package/packages/mailx-types/index.d.ts +1 -2
- package/packages/mailx-types/index.d.ts.map +1 -1
- package/packages/mailx-types/index.js.map +1 -1
- package/packages/mailx-types/index.ts +1 -2
- package/packages/mailx-types/package.json +1 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copy node_modules/quill/dist/{quill.js,quill.snow.css} → client/lib/quill/
|
|
3
|
+
// so compose can load Quill from disk instead of jsdelivr CDN. The CDN
|
|
4
|
+
// fetch was a 100-500 ms (network-dependent) tax on every compose open,
|
|
5
|
+
// since iframes ran a fresh `await loadScript(jsdelivr…)` chain even on
|
|
6
|
+
// warm starts. Local copy = single msger-protocol fetch = ~30 ms.
|
|
7
|
+
//
|
|
8
|
+
// Same shape as build-tinymce.js (sibling script). Build runs after
|
|
9
|
+
// `npm install`, before bundling, so the assets are present when the
|
|
10
|
+
// browser first reaches them.
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
|
|
14
|
+
const root = path.resolve(import.meta.dirname, "..");
|
|
15
|
+
const srcDir = path.join(root, "node_modules", "quill", "dist");
|
|
16
|
+
const dstDir = path.join(root, "client", "lib", "quill");
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(srcDir)) {
|
|
19
|
+
console.error(`build-quill: source not found at ${srcDir} — run npm install first`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Only the two files the editor actually loads — Quill's other dist
|
|
24
|
+
// artifacts (bubble theme, core-only build, sourcemaps) aren't on the
|
|
25
|
+
// runtime path and would just bloat the client tree.
|
|
26
|
+
const files = ["quill.js", "quill.snow.css"];
|
|
27
|
+
fs.mkdirSync(dstDir, { recursive: true });
|
|
28
|
+
let totalBytes = 0;
|
|
29
|
+
for (const name of files) {
|
|
30
|
+
const s = path.join(srcDir, name);
|
|
31
|
+
const d = path.join(dstDir, name);
|
|
32
|
+
fs.copyFileSync(s, d);
|
|
33
|
+
totalBytes += fs.statSync(d).size;
|
|
34
|
+
}
|
|
35
|
+
console.log(`build-quill: copied ${files.length} files (${(totalBytes / 1024).toFixed(1)} KB) → client/lib/quill/`);
|
package/bin/lean-accounts.js
CHANGED
|
@@ -98,7 +98,6 @@ function denormalizeAccount(acct, globalName) {
|
|
|
98
98
|
if (acct.defaultSend) out.defaultSend = true;
|
|
99
99
|
if (acct.enabled === false) out.enabled = false;
|
|
100
100
|
if (acct.relayDomains?.length > 0) out.relayDomains = acct.relayDomains;
|
|
101
|
-
if (acct.deliveredToPrefix?.length > 0) out.deliveredToPrefix = acct.deliveredToPrefix;
|
|
102
101
|
if (acct.identityDomains?.length > 0) out.identityDomains = acct.identityDomains;
|
|
103
102
|
|
|
104
103
|
const syncContactsDefault = provider?.imap.auth === "oauth2";
|
package/client/app.bundle.js
CHANGED
|
@@ -62,6 +62,7 @@ __export(api_client_exports, {
|
|
|
62
62
|
logClientEvent: () => logClientEvent,
|
|
63
63
|
markAsSpamMessages: () => markAsSpamMessages,
|
|
64
64
|
markFolderRead: () => markFolderRead,
|
|
65
|
+
moveFolderToTrash: () => moveFolderToTrash,
|
|
65
66
|
moveMessage: () => moveMessage,
|
|
66
67
|
moveMessages: () => moveMessages,
|
|
67
68
|
onEvent: () => onEvent,
|
|
@@ -334,6 +335,9 @@ function renameFolder(accountId, folderId, newName) {
|
|
|
334
335
|
function deleteFolder(accountId, folderId) {
|
|
335
336
|
return ipc().deleteFolder?.(accountId, folderId);
|
|
336
337
|
}
|
|
338
|
+
function moveFolderToTrash(accountId, folderId) {
|
|
339
|
+
return ipc().moveFolderToTrash?.(accountId, folderId);
|
|
340
|
+
}
|
|
337
341
|
function emptyFolder(accountId, folderId) {
|
|
338
342
|
return ipc().emptyFolder?.(accountId, folderId);
|
|
339
343
|
}
|
|
@@ -489,6 +493,8 @@ function showContextMenu(x, y, items) {
|
|
|
489
493
|
const el = document.createElement("div");
|
|
490
494
|
el.className = "ctx-item" + (item.disabled ? " ctx-disabled" : "");
|
|
491
495
|
el.textContent = item.label;
|
|
496
|
+
if (item.tooltip)
|
|
497
|
+
el.title = item.tooltip;
|
|
492
498
|
if (!item.disabled) {
|
|
493
499
|
el.addEventListener("click", () => {
|
|
494
500
|
closeContextMenu();
|
|
@@ -2164,6 +2170,57 @@ function cacheKey(mode, a, f, flagged, q) {
|
|
|
2164
2170
|
return `folder:${a}:${f}:${flagged ? "flag" : ""}`;
|
|
2165
2171
|
return `search:${q}`;
|
|
2166
2172
|
}
|
|
2173
|
+
function persistPositions() {
|
|
2174
|
+
try {
|
|
2175
|
+
const obj = {};
|
|
2176
|
+
for (const [k, v] of positionMemory)
|
|
2177
|
+
obj[k] = v;
|
|
2178
|
+
sessionStorage.setItem(POSITION_STORAGE_KEY, JSON.stringify(obj));
|
|
2179
|
+
} catch {
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
function currentViewKey() {
|
|
2183
|
+
if (searchMode)
|
|
2184
|
+
return cacheKey("search", void 0, void 0, void 0, currentSearchQuery);
|
|
2185
|
+
if (unifiedMode)
|
|
2186
|
+
return CACHE_KEY_UNIFIED;
|
|
2187
|
+
if (!currentAccountId2 || currentFolderId == null)
|
|
2188
|
+
return null;
|
|
2189
|
+
const flaggedOnly = document.getElementById("ml-body")?.classList.contains("flagged-only") || false;
|
|
2190
|
+
return cacheKey("folder", currentAccountId2, currentFolderId, flaggedOnly);
|
|
2191
|
+
}
|
|
2192
|
+
function rememberPosition() {
|
|
2193
|
+
const key = currentViewKey();
|
|
2194
|
+
if (!key)
|
|
2195
|
+
return;
|
|
2196
|
+
const body = document.getElementById("ml-body");
|
|
2197
|
+
if (!body)
|
|
2198
|
+
return;
|
|
2199
|
+
const sel = body.querySelector(".ml-row.selected");
|
|
2200
|
+
if (!sel)
|
|
2201
|
+
return;
|
|
2202
|
+
const uid = Number(sel.dataset.uid);
|
|
2203
|
+
if (!Number.isFinite(uid))
|
|
2204
|
+
return;
|
|
2205
|
+
positionMemory.set(key, { uid, scroll: body.scrollTop });
|
|
2206
|
+
persistPositions();
|
|
2207
|
+
}
|
|
2208
|
+
function pickRestoreUid(items, saved) {
|
|
2209
|
+
if (!items.length)
|
|
2210
|
+
return null;
|
|
2211
|
+
if (items.some((m) => m.uid === saved))
|
|
2212
|
+
return saved;
|
|
2213
|
+
let best = -1;
|
|
2214
|
+
for (const m of items) {
|
|
2215
|
+
if (typeof m.uid !== "number")
|
|
2216
|
+
continue;
|
|
2217
|
+
if (m.uid < saved && m.uid > best)
|
|
2218
|
+
best = m.uid;
|
|
2219
|
+
}
|
|
2220
|
+
if (best >= 0)
|
|
2221
|
+
return best;
|
|
2222
|
+
return typeof items[0]?.uid === "number" ? items[0].uid : null;
|
|
2223
|
+
}
|
|
2167
2224
|
function listResultsEqual(a, b) {
|
|
2168
2225
|
if (!a || a.length !== b.length)
|
|
2169
2226
|
return false;
|
|
@@ -2210,6 +2267,7 @@ function focusRow(row) {
|
|
|
2210
2267
|
showMessage(row.accountId, row.msg.uid, row.msg.folderId, void 0, false, row.msg);
|
|
2211
2268
|
onMessageSelect(row.accountId, row.msg.uid, row.msg.folderId);
|
|
2212
2269
|
document.dispatchEvent(new CustomEvent("mailx-focus-changed", { detail: row.msg }));
|
|
2270
|
+
rememberPosition();
|
|
2213
2271
|
}
|
|
2214
2272
|
function getCurrentFocused() {
|
|
2215
2273
|
return focusedRow ? focusedRow.msg : null;
|
|
@@ -2467,19 +2525,19 @@ async function loadUnifiedInbox(autoSelect = true) {
|
|
|
2467
2525
|
const fromHeader = document.querySelector(".ml-col-from");
|
|
2468
2526
|
if (fromHeader)
|
|
2469
2527
|
fromHeader.textContent = "From";
|
|
2470
|
-
const
|
|
2471
|
-
const
|
|
2528
|
+
const remembered = positionMemory.get(CACHE_KEY_UNIFIED);
|
|
2529
|
+
const savedScroll = remembered?.scroll ?? (!autoSelect ? body.scrollTop : 0);
|
|
2472
2530
|
const cached = listCache.get(CACHE_KEY_UNIFIED);
|
|
2473
2531
|
if (cached) {
|
|
2474
|
-
const preCacheUid = currentSelectedUid();
|
|
2475
2532
|
totalMessages = cached.total;
|
|
2476
2533
|
setMessages(cached.items);
|
|
2477
2534
|
renderMessages(body, "", cached.items);
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
else {
|
|
2535
|
+
const targetUid = remembered ? pickRestoreUid(cached.items, remembered.uid) : null;
|
|
2536
|
+
if (targetUid != null) {
|
|
2481
2537
|
body.scrollTop = savedScroll;
|
|
2482
|
-
restoreSelection(body,
|
|
2538
|
+
restoreSelection(body, String(targetUid));
|
|
2539
|
+
} else if (autoSelect) {
|
|
2540
|
+
selectFirst(body);
|
|
2483
2541
|
}
|
|
2484
2542
|
} else if (autoSelect) {
|
|
2485
2543
|
body.innerHTML = `<div class="ml-empty">Loading...</div>`;
|
|
@@ -2497,14 +2555,14 @@ async function loadUnifiedInbox(autoSelect = true) {
|
|
|
2497
2555
|
body.innerHTML = `<div class="ml-empty">${result.total > 0 ? `${result.total} messages syncing...` : "Syncing \u2014 messages will appear shortly"}</div>`;
|
|
2498
2556
|
return;
|
|
2499
2557
|
}
|
|
2500
|
-
const preRenderUid = currentSelectedUid();
|
|
2501
2558
|
setMessages(result.items);
|
|
2502
2559
|
renderMessages(body, "", result.items);
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
} else {
|
|
2560
|
+
const targetUid = remembered ? pickRestoreUid(result.items, remembered.uid) : null;
|
|
2561
|
+
if (targetUid != null) {
|
|
2506
2562
|
body.scrollTop = savedScroll;
|
|
2507
|
-
restoreSelection(body,
|
|
2563
|
+
restoreSelection(body, String(targetUid));
|
|
2564
|
+
} else if (autoSelect) {
|
|
2565
|
+
selectFirst(body);
|
|
2508
2566
|
}
|
|
2509
2567
|
} catch (e) {
|
|
2510
2568
|
if (e.name === "AbortError")
|
|
@@ -2604,23 +2662,23 @@ async function loadMessages(accountId, folderId, page = 1, specialUse = "", auto
|
|
|
2604
2662
|
const fromHeader = document.querySelector(".ml-col-from");
|
|
2605
2663
|
if (fromHeader)
|
|
2606
2664
|
fromHeader.textContent = showToInsteadOfFrom ? "To" : "From";
|
|
2607
|
-
const savedScroll = !autoSelect ? body.scrollTop : 0;
|
|
2608
|
-
const currentSelectedUid = () => !autoSelect ? body.querySelector(".ml-row.selected")?.getAttribute("data-uid") || null : null;
|
|
2609
2665
|
const flaggedOnly = document.getElementById("ml-body")?.classList.contains("flagged-only") || false;
|
|
2610
2666
|
const cKey = cacheKey("folder", accountId, folderId, flaggedOnly);
|
|
2667
|
+
const remembered = positionMemory.get(cKey);
|
|
2668
|
+
const savedScroll = remembered?.scroll ?? (!autoSelect ? body.scrollTop : 0);
|
|
2611
2669
|
const cached = listCache.get(cKey);
|
|
2612
2670
|
if (cached) {
|
|
2613
|
-
const preCacheUid = currentSelectedUid();
|
|
2614
2671
|
totalMessages = cached.total;
|
|
2615
2672
|
setMessages(cached.items);
|
|
2616
2673
|
renderMessages(body, accountId, cached.items);
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
else {
|
|
2674
|
+
const targetUid = remembered ? pickRestoreUid(cached.items, remembered.uid) : null;
|
|
2675
|
+
if (targetUid != null) {
|
|
2620
2676
|
requestAnimationFrame(() => {
|
|
2621
2677
|
body.scrollTop = savedScroll;
|
|
2622
|
-
restoreSelection(body,
|
|
2678
|
+
restoreSelection(body, String(targetUid));
|
|
2623
2679
|
});
|
|
2680
|
+
} else if (autoSelect) {
|
|
2681
|
+
selectFirst(body);
|
|
2624
2682
|
}
|
|
2625
2683
|
} else if (autoSelect) {
|
|
2626
2684
|
body.innerHTML = `<div class="ml-empty">Loading...</div>`;
|
|
@@ -2639,18 +2697,18 @@ async function loadMessages(accountId, folderId, page = 1, specialUse = "", auto
|
|
|
2639
2697
|
body.innerHTML = `<div class="ml-empty">${flaggedOnly ? "No flagged messages" : "No messages"}</div>`;
|
|
2640
2698
|
return;
|
|
2641
2699
|
}
|
|
2642
|
-
const preRenderUid = currentSelectedUid();
|
|
2643
2700
|
setMessages(result.items);
|
|
2644
2701
|
renderMessages(body, accountId, result.items);
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
} else {
|
|
2702
|
+
const targetUid = remembered ? pickRestoreUid(result.items, remembered.uid) : null;
|
|
2703
|
+
if (targetUid != null) {
|
|
2648
2704
|
requestAnimationFrame(() => {
|
|
2649
2705
|
if (myGen !== loadGen)
|
|
2650
2706
|
return;
|
|
2651
2707
|
body.scrollTop = savedScroll;
|
|
2652
|
-
restoreSelection(body,
|
|
2708
|
+
restoreSelection(body, String(targetUid));
|
|
2653
2709
|
});
|
|
2710
|
+
} else if (autoSelect) {
|
|
2711
|
+
selectFirst(body);
|
|
2654
2712
|
}
|
|
2655
2713
|
} catch (e) {
|
|
2656
2714
|
if (e.name === "AbortError")
|
|
@@ -2812,7 +2870,7 @@ function escapeHtml2(s) {
|
|
|
2812
2870
|
div.textContent = s;
|
|
2813
2871
|
return div.innerHTML;
|
|
2814
2872
|
}
|
|
2815
|
-
var onMessageSelect, currentAccountId2, currentFolderId, currentSpecialUse, lastClickedRow, currentPage, totalMessages, loading, unifiedMode, searchMode, currentSearchQuery, wasUnifiedBeforeSearch, showToInsteadOfFrom, touchWasScroll, currentSort, currentSortDir, loadGen, listCache, CACHE_KEY_UNIFIED, focusedRow, rowByKey, prioritySenders, priorityDomains, timeFmt, dateFmt, dateFmtSameYear, MessageRow;
|
|
2873
|
+
var onMessageSelect, currentAccountId2, currentFolderId, currentSpecialUse, lastClickedRow, currentPage, totalMessages, loading, unifiedMode, searchMode, currentSearchQuery, wasUnifiedBeforeSearch, showToInsteadOfFrom, touchWasScroll, currentSort, currentSortDir, loadGen, listCache, CACHE_KEY_UNIFIED, positionMemory, POSITION_STORAGE_KEY, focusedRow, rowByKey, prioritySenders, priorityDomains, timeFmt, dateFmt, dateFmtSameYear, MessageRow;
|
|
2816
2874
|
var init_message_list = __esm({
|
|
2817
2875
|
"client/components/message-list.js"() {
|
|
2818
2876
|
"use strict";
|
|
@@ -2835,6 +2893,19 @@ var init_message_list = __esm({
|
|
|
2835
2893
|
loadGen = 0;
|
|
2836
2894
|
listCache = /* @__PURE__ */ new Map();
|
|
2837
2895
|
CACHE_KEY_UNIFIED = "unified";
|
|
2896
|
+
positionMemory = /* @__PURE__ */ new Map();
|
|
2897
|
+
POSITION_STORAGE_KEY = "mailx-list-positions";
|
|
2898
|
+
try {
|
|
2899
|
+
const raw = sessionStorage.getItem(POSITION_STORAGE_KEY);
|
|
2900
|
+
if (raw) {
|
|
2901
|
+
const parsed = JSON.parse(raw);
|
|
2902
|
+
for (const [k, v] of Object.entries(parsed || {})) {
|
|
2903
|
+
if (typeof v?.uid === "number")
|
|
2904
|
+
positionMemory.set(k, v);
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
} catch {
|
|
2908
|
+
}
|
|
2838
2909
|
focusedRow = null;
|
|
2839
2910
|
rowByKey = /* @__PURE__ */ new Map();
|
|
2840
2911
|
prioritySenders = /* @__PURE__ */ new Set();
|
|
@@ -4918,18 +4989,47 @@ function renderNode(node, container, depth) {
|
|
|
4918
4989
|
alert(`Failed: ${err.message}`);
|
|
4919
4990
|
}
|
|
4920
4991
|
}, disabled: !!node.specialUse },
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4992
|
+
// Two delete entries. Move-to-Trash is the default; permanent
|
|
4993
|
+
// delete is a second, separated item with a tooltip so it's
|
|
4994
|
+
// visible from the menu rather than requiring a discovery
|
|
4995
|
+
// shortcut. The IMAP RENAME under Trash brings messages +
|
|
4996
|
+
// subfolders along; the server-side fallback (when Trash is
|
|
4997
|
+
// \Noinferiors) moves messages to Trash root and then deletes
|
|
4998
|
+
// the empty folder. Permanent skips Trash entirely.
|
|
4999
|
+
{
|
|
5000
|
+
label: "Move folder to Trash",
|
|
5001
|
+
action: async () => {
|
|
5002
|
+
if (!confirm(`Move folder "${node.name}" to Trash? It can be restored by dragging back out of Trash.`))
|
|
5003
|
+
return;
|
|
5004
|
+
try {
|
|
5005
|
+
await moveFolderToTrash(node.accountId, node.id);
|
|
5006
|
+
const treeContainer = document.getElementById("folder-tree");
|
|
5007
|
+
if (treeContainer)
|
|
5008
|
+
loadFolderTree(treeContainer);
|
|
5009
|
+
} catch (err) {
|
|
5010
|
+
alert(`Failed: ${err.message}`);
|
|
5011
|
+
}
|
|
5012
|
+
},
|
|
5013
|
+
disabled: !!node.specialUse,
|
|
5014
|
+
tooltip: "Renames the folder into Trash (date-suffixed if needed); use Delete permanently below to skip Trash."
|
|
5015
|
+
},
|
|
5016
|
+
{
|
|
5017
|
+
label: "Delete folder permanently",
|
|
5018
|
+
action: async () => {
|
|
5019
|
+
if (!confirm(`Permanently delete folder "${node.name}" and ALL its messages? This cannot be undone.`))
|
|
5020
|
+
return;
|
|
5021
|
+
try {
|
|
5022
|
+
await deleteFolder(node.accountId, node.id);
|
|
5023
|
+
const treeContainer = document.getElementById("folder-tree");
|
|
5024
|
+
if (treeContainer)
|
|
5025
|
+
loadFolderTree(treeContainer);
|
|
5026
|
+
} catch (err) {
|
|
5027
|
+
alert(`Failed: ${err.message}`);
|
|
5028
|
+
}
|
|
5029
|
+
},
|
|
5030
|
+
disabled: !!node.specialUse,
|
|
5031
|
+
tooltip: "Skips Trash. Same as Shift+Delete on a regular file. No undo."
|
|
5032
|
+
},
|
|
4933
5033
|
{ label: "", action: () => {
|
|
4934
5034
|
}, separator: true },
|
|
4935
5035
|
// Q57: copy IMAP path so user can paste into accounts.jsonc as
|
|
@@ -5758,9 +5858,11 @@ function updateBadge(count) {
|
|
|
5758
5858
|
async function updateNewMessageCount() {
|
|
5759
5859
|
try {
|
|
5760
5860
|
const accounts = await getAccounts();
|
|
5861
|
+
const folderLists = await Promise.all(
|
|
5862
|
+
accounts.map((acct) => getFolders(acct.id).catch(() => []))
|
|
5863
|
+
);
|
|
5761
5864
|
let totalUnread = 0;
|
|
5762
|
-
for (const
|
|
5763
|
-
const folders = await getFolders(acct.id);
|
|
5865
|
+
for (const folders of folderLists) {
|
|
5764
5866
|
const inbox = folders.find((f) => f.specialUse === "inbox");
|
|
5765
5867
|
if (inbox) totalUnread += inbox.unreadCount || 0;
|
|
5766
5868
|
}
|
|
@@ -5822,9 +5924,11 @@ document.addEventListener("visibilitychange", () => {
|
|
|
5822
5924
|
window.addEventListener("focus", stopTitleFlash);
|
|
5823
5925
|
function markAsSeen() {
|
|
5824
5926
|
getAccounts().then(async (accounts) => {
|
|
5927
|
+
const folderLists = await Promise.all(
|
|
5928
|
+
accounts.map((acct) => getFolders(acct.id).catch(() => []))
|
|
5929
|
+
);
|
|
5825
5930
|
let total = 0;
|
|
5826
|
-
for (const
|
|
5827
|
-
const folders = await getFolders(acct.id);
|
|
5931
|
+
for (const folders of folderLists) {
|
|
5828
5932
|
const inbox = folders.find((f) => f.specialUse === "inbox");
|
|
5829
5933
|
if (inbox) total += inbox.unreadCount || 0;
|
|
5830
5934
|
}
|
|
@@ -6166,9 +6270,13 @@ async function openCompose(mode) {
|
|
|
6166
6270
|
console.warn(`[compose] ${mode} \u2014 no message selected`);
|
|
6167
6271
|
return;
|
|
6168
6272
|
}
|
|
6169
|
-
const
|
|
6170
|
-
const accountId = current?.accountId || accounts[0]?.id || "";
|
|
6273
|
+
const accountsP = getAccounts();
|
|
6171
6274
|
const msg = current?.message;
|
|
6275
|
+
const titlePrefix = mode === "reply" ? "Reply" : mode === "replyAll" ? "Reply All" : mode === "forward" ? "Forward" : "Compose";
|
|
6276
|
+
const titleSubject = mode === "new" ? "" : msg?.subject || "";
|
|
6277
|
+
const frame = showComposeOverlay(titleSubject ? `${titlePrefix}: ${titleSubject}` : titlePrefix);
|
|
6278
|
+
const accounts = await accountsP;
|
|
6279
|
+
const accountId = current?.accountId || accounts[0]?.id || "";
|
|
6172
6280
|
const rePrefix = /^(re|fwd?):\s*/i;
|
|
6173
6281
|
const cleanSubject = msg ? msg.subject.replace(rePrefix, "") : "";
|
|
6174
6282
|
const init = {
|
|
@@ -6180,7 +6288,7 @@ async function openCompose(mode) {
|
|
|
6180
6288
|
bodyHtml: "",
|
|
6181
6289
|
inReplyTo: "",
|
|
6182
6290
|
references: [],
|
|
6183
|
-
accounts: accounts.map((a) => ({ id: a.id, name: a.name, email: a.email, signature: a.signature }))
|
|
6291
|
+
accounts: accounts.map((a) => ({ id: a.id, name: a.name, email: a.email, signature: a.signature, sig: a.sig }))
|
|
6184
6292
|
};
|
|
6185
6293
|
const account = accounts.find((a) => a.id === accountId);
|
|
6186
6294
|
const explicitDomains = (account?.identityDomains || []).map((d) => d.toLowerCase());
|
|
@@ -6234,9 +6342,10 @@ async function openCompose(mode) {
|
|
|
6234
6342
|
init.fromAddress = detectReplyFrom();
|
|
6235
6343
|
}
|
|
6236
6344
|
sessionStorage.setItem("composeInit", JSON.stringify(init));
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6345
|
+
try {
|
|
6346
|
+
frame?.contentWindow?.postMessage({ type: "compose-init-ready" }, "*");
|
|
6347
|
+
} catch {
|
|
6348
|
+
}
|
|
6240
6349
|
}
|
|
6241
6350
|
function showComposeOverlay(title = "Compose") {
|
|
6242
6351
|
const wrapper = document.createElement("div");
|
|
@@ -6357,6 +6466,7 @@ function showComposeOverlay(title = "Compose") {
|
|
|
6357
6466
|
wrapper.appendChild(frame);
|
|
6358
6467
|
if (!isSmall) addComposeResizeHandles(wrapper, frame);
|
|
6359
6468
|
document.body.appendChild(wrapper);
|
|
6469
|
+
return frame;
|
|
6360
6470
|
}
|
|
6361
6471
|
function installDragShield(cursor) {
|
|
6362
6472
|
const shield = document.createElement("div");
|
|
@@ -6425,7 +6535,7 @@ function addComposeResizeHandles(wrapper, frame) {
|
|
|
6425
6535
|
function sanitizeQuotedBody(msg) {
|
|
6426
6536
|
const isPlainText = !msg.bodyHtml;
|
|
6427
6537
|
if (isPlainText) {
|
|
6428
|
-
const escaped = String(msg.bodyText || "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
6538
|
+
const escaped = String(msg.bodyText || "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\r\n?/g, "\n").replace(/\n/g, "<br>");
|
|
6429
6539
|
return `<div style="white-space:pre-wrap;font-family:inherit;margin:0">${escaped}</div>`;
|
|
6430
6540
|
}
|
|
6431
6541
|
let body = msg.bodyHtml;
|
|
@@ -7574,7 +7684,9 @@ onWsEvent((event) => {
|
|
|
7574
7684
|
}
|
|
7575
7685
|
});
|
|
7576
7686
|
async function openComposeFromMailto(m) {
|
|
7577
|
-
const
|
|
7687
|
+
const accountsP = getAccounts();
|
|
7688
|
+
const frame = showComposeOverlay(m.subject ? `Compose: ${m.subject}` : "Compose");
|
|
7689
|
+
const accounts = await accountsP;
|
|
7578
7690
|
const accountId = accounts[0]?.id || "";
|
|
7579
7691
|
const escape = (s) => s.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]);
|
|
7580
7692
|
const bodyHtml = m.body ? "<p>" + escape(m.body).replace(/\r?\n/g, "</p><p>") + "</p>" : "";
|
|
@@ -7588,10 +7700,13 @@ async function openComposeFromMailto(m) {
|
|
|
7588
7700
|
bodyHtml,
|
|
7589
7701
|
inReplyTo: m.inReplyTo,
|
|
7590
7702
|
references: m.inReplyTo ? [m.inReplyTo] : [],
|
|
7591
|
-
accounts: accounts.map((a) => ({ id: a.id, name: a.name, email: a.email, signature: a.signature }))
|
|
7703
|
+
accounts: accounts.map((a) => ({ id: a.id, name: a.name, email: a.email, signature: a.signature, sig: a.sig }))
|
|
7592
7704
|
};
|
|
7593
7705
|
sessionStorage.setItem("composeInit", JSON.stringify(init));
|
|
7594
|
-
|
|
7706
|
+
try {
|
|
7707
|
+
frame?.contentWindow?.postMessage({ type: "compose-init-ready" }, "*");
|
|
7708
|
+
} catch {
|
|
7709
|
+
}
|
|
7595
7710
|
}
|
|
7596
7711
|
window.addEventListener("keydown", (e) => {
|
|
7597
7712
|
if (!e.ctrlKey || e.altKey || e.metaKey) return;
|
|
@@ -8955,16 +9070,18 @@ function renderOutboxStatus(s) {
|
|
|
8955
9070
|
setInterval(async () => {
|
|
8956
9071
|
try {
|
|
8957
9072
|
const { getOutboxStatus: getOutboxStatus2, getDiagnostics: getDiagnostics2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
8958
|
-
|
|
8959
|
-
|
|
9073
|
+
const [outbox, diag] = await Promise.all([getOutboxStatus2(), getDiagnostics2()]);
|
|
9074
|
+
renderOutboxStatus(outbox);
|
|
9075
|
+
renderDiagnosticsBadge(diag);
|
|
8960
9076
|
} catch {
|
|
8961
9077
|
}
|
|
8962
9078
|
}, 15e3);
|
|
8963
9079
|
(async () => {
|
|
8964
9080
|
try {
|
|
8965
9081
|
const { getOutboxStatus: getOutboxStatus2, getDiagnostics: getDiagnostics2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
8966
|
-
|
|
8967
|
-
|
|
9082
|
+
const [outbox, diag] = await Promise.all([getOutboxStatus2(), getDiagnostics2()]);
|
|
9083
|
+
renderOutboxStatus(outbox);
|
|
9084
|
+
renderDiagnosticsBadge(diag);
|
|
8968
9085
|
} catch {
|
|
8969
9086
|
}
|
|
8970
9087
|
})();
|