@bobfrankston/rmfmail 1.2.49 → 1.2.65
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/client/android-bootstrap.bundle.js +63 -2
- package/client/android-bootstrap.bundle.js.map +4 -4
- package/client/app.bundle.js +417 -63
- package/client/app.bundle.js.map +3 -3
- package/client/app.js +182 -24
- package/client/app.js.map +1 -1
- package/client/app.ts +166 -21
- package/client/components/calendar-sidebar.js +5 -5
- package/client/components/calendar-sidebar.js.map +1 -1
- package/client/components/calendar-sidebar.ts +5 -5
- package/client/components/folder-tree.js +81 -0
- package/client/components/folder-tree.js.map +1 -1
- package/client/components/folder-tree.ts +70 -0
- package/client/components/message-list.js +282 -38
- package/client/components/message-list.js.map +1 -1
- package/client/components/message-list.ts +264 -36
- package/client/compose/compose.bundle.js +70 -14
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/compose/compose.css +28 -7
- package/client/compose/compose.js +64 -4
- package/client/compose/compose.js.map +1 -1
- package/client/compose/compose.ts +66 -4
- package/client/compose/editor.js +23 -7
- package/client/compose/editor.js.map +1 -1
- package/client/compose/editor.ts +23 -7
- package/client/help/search-help.js +4 -3
- package/client/help/search-help.js.map +1 -1
- package/client/help/search-help.ts +4 -3
- package/client/index.html +5 -1
- package/client/lib/api-client.js +4 -2
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +4 -2
- package/client/lib/mailxapi.js +2 -2
- package/client/lib/rmf-tiny.js +39 -6
- package/client/styles/components.css +6 -0
- package/docs/azure.md +58 -0
- package/docs/config-help.md +131 -0
- package/docs/edit-in-word-docx.md +98 -0
- package/docs/host-abstraction-plan.md +169 -0
- package/docs/local-first-plan.md +303 -0
- package/docs/npmglobalize-transitive-workspace-deps.md +107 -0
- package/docs/outlook.md +80 -18
- package/docs/outlook.txt +35 -0
- package/docs/rules-design.md +172 -0
- package/package.json +7 -7
- package/packages/mailx-imap/index.d.ts +48 -1
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +332 -70
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +323 -67
- package/packages/mailx-imap/microsoft-credentials.json +8 -0
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-imap/providers/outlook-api.d.ts +7 -0
- package/packages/mailx-imap/providers/outlook-api.d.ts.map +1 -0
- package/packages/mailx-imap/providers/outlook-api.js +7 -0
- package/packages/mailx-imap/providers/outlook-api.js.map +1 -0
- package/packages/mailx-service/index.d.ts +7 -1
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +8 -8
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +8 -7
- package/packages/mailx-service/jsonrpc.js +1 -1
- package/packages/mailx-service/jsonrpc.js.map +1 -1
- package/packages/mailx-service/jsonrpc.ts +1 -1
- package/packages/mailx-settings/docs/azure.md +58 -0
- package/packages/mailx-settings/docs/config-help.md +131 -0
- package/packages/mailx-settings/docs/edit-in-word-docx.md +98 -0
- package/packages/mailx-settings/docs/host-abstraction-plan.md +169 -0
- package/packages/mailx-settings/docs/local-first-plan.md +303 -0
- package/packages/mailx-settings/docs/npmglobalize-transitive-workspace-deps.md +107 -0
- package/packages/mailx-settings/docs/outlook.md +80 -18
- package/packages/mailx-settings/docs/rules-design.md +172 -0
- package/packages/mailx-settings/index.d.ts.map +1 -1
- package/packages/mailx-settings/index.js +9 -8
- package/packages/mailx-settings/index.js.map +1 -1
- package/packages/mailx-settings/index.ts +9 -8
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/db.d.ts +50 -1
- package/packages/mailx-store/db.d.ts.map +1 -1
- package/packages/mailx-store/db.js +120 -6
- package/packages/mailx-store/db.js.map +1 -1
- package/packages/mailx-store/db.ts +126 -6
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-types/contact-rules.d.ts +1 -1
- package/packages/mailx-types/contact-rules.js +1 -1
- package/packages/mailx-types/contact-rules.js.map +1 -1
- package/packages/mailx-types/contact-rules.jsonc +1 -1
- package/packages/mailx-types/contact-rules.ts +1 -1
- package/packages/mailx-types/package.json +1 -1
package/client/app.bundle.js
CHANGED
|
@@ -370,8 +370,8 @@ function markFolderRead(accountId, folderId) {
|
|
|
370
370
|
function createFolder(accountId, parentPath, name) {
|
|
371
371
|
return ipc().createFolder?.(accountId, parentPath, name);
|
|
372
372
|
}
|
|
373
|
-
function renameFolder(accountId, folderId, newName) {
|
|
374
|
-
return ipc().renameFolder?.(accountId, folderId, newName);
|
|
373
|
+
function renameFolder(accountId, folderId, newName, newParentId) {
|
|
374
|
+
return ipc().renameFolder?.(accountId, folderId, newName, newParentId);
|
|
375
375
|
}
|
|
376
376
|
function deleteFolder(accountId, folderId) {
|
|
377
377
|
return ipc().deleteFolder?.(accountId, folderId);
|
|
@@ -2760,10 +2760,12 @@ __export(message_list_exports, {
|
|
|
2760
2760
|
releaseFocus: () => releaseFocus,
|
|
2761
2761
|
reloadCurrentFolder: () => reloadCurrentFolder,
|
|
2762
2762
|
removeMessagesAndReconcile: () => removeMessagesAndReconcile,
|
|
2763
|
+
revealMessage: () => revealMessage,
|
|
2763
2764
|
scrollFocusedIntoView: () => scrollFocusedIntoView,
|
|
2764
2765
|
setLiveFilter: () => setLiveFilter,
|
|
2765
2766
|
setRowFlagged: () => setRowFlagged,
|
|
2766
2767
|
setRowSeen: () => setRowSeen,
|
|
2768
|
+
showSelectionMenu: () => showSelectionMenu,
|
|
2767
2769
|
showThreadPopup: () => showThreadPopup
|
|
2768
2770
|
});
|
|
2769
2771
|
function setLiveFilter(query) {
|
|
@@ -2784,6 +2786,12 @@ function applyLiveFilter(body) {
|
|
|
2784
2786
|
row.classList.toggle("filter-hidden", !text.includes(liveFilterText));
|
|
2785
2787
|
}
|
|
2786
2788
|
}
|
|
2789
|
+
function emptyListMessage() {
|
|
2790
|
+
if (searchMode && currentSearchQuery) {
|
|
2791
|
+
return `No messages found by "${escapeHtmlText(currentSearchQuery)}"`;
|
|
2792
|
+
}
|
|
2793
|
+
return "No messages";
|
|
2794
|
+
}
|
|
2787
2795
|
function cacheKey(mode, a, f, flagged, q) {
|
|
2788
2796
|
if (mode === "folder")
|
|
2789
2797
|
return `folder:${a}:${f}:${flagged ? "flag" : ""}`;
|
|
@@ -2961,6 +2969,154 @@ function setRowSeen(accountId, uid, yes) {
|
|
|
2961
2969
|
if (row)
|
|
2962
2970
|
row.setUnreadClass(!yes);
|
|
2963
2971
|
}
|
|
2972
|
+
function selectedRows() {
|
|
2973
|
+
const out = [];
|
|
2974
|
+
for (const s of getSelectedMessages()) {
|
|
2975
|
+
const row = rowByKey.get(rowKey(s.accountId, s.uid));
|
|
2976
|
+
if (row)
|
|
2977
|
+
out.push(row);
|
|
2978
|
+
}
|
|
2979
|
+
return out;
|
|
2980
|
+
}
|
|
2981
|
+
function visibleRowObjs() {
|
|
2982
|
+
const body = document.getElementById("ml-body");
|
|
2983
|
+
if (!body)
|
|
2984
|
+
return [];
|
|
2985
|
+
return Array.from(body.querySelectorAll(".ml-row:not(.filter-hidden)")).map((el) => rowByKey.get(rowKey(el.dataset.accountId || "", Number(el.dataset.uid)))).filter((r) => !!r);
|
|
2986
|
+
}
|
|
2987
|
+
function bulkTargets() {
|
|
2988
|
+
const sel = selectedRows();
|
|
2989
|
+
return sel.length > 0 ? sel : visibleRowObjs();
|
|
2990
|
+
}
|
|
2991
|
+
async function bulkSetFlagged(rows, flagged) {
|
|
2992
|
+
const undo = [];
|
|
2993
|
+
await Promise.all(rows.map(async (row) => {
|
|
2994
|
+
const prev = row.isFlagged;
|
|
2995
|
+
if (prev === flagged)
|
|
2996
|
+
return;
|
|
2997
|
+
const prevFlags = [...row.msg.flags || []];
|
|
2998
|
+
row.isFlagged = flagged;
|
|
2999
|
+
try {
|
|
3000
|
+
await updateFlags(row.accountId, row.msg.uid, row.msg.flags);
|
|
3001
|
+
updateMessageFlags(row.accountId, row.msg.uid, row.msg.flags);
|
|
3002
|
+
undo.push({ accountId: row.accountId, uid: row.msg.uid, prevFlagged: prev });
|
|
3003
|
+
} catch {
|
|
3004
|
+
row.msg.flags = prevFlags;
|
|
3005
|
+
row.setFlaggedClass(prev);
|
|
3006
|
+
}
|
|
3007
|
+
}));
|
|
3008
|
+
if (undo.length)
|
|
3009
|
+
document.dispatchEvent(new CustomEvent("mailx-flagged-batch", { detail: undo }));
|
|
3010
|
+
}
|
|
3011
|
+
async function bulkSetSeen(rows, seen) {
|
|
3012
|
+
await Promise.all(rows.map(async (row) => {
|
|
3013
|
+
const prev = row.isSeen;
|
|
3014
|
+
if (prev === seen)
|
|
3015
|
+
return;
|
|
3016
|
+
const prevFlags = [...row.msg.flags || []];
|
|
3017
|
+
row.isSeen = seen;
|
|
3018
|
+
try {
|
|
3019
|
+
await updateFlags(row.accountId, row.msg.uid, row.msg.flags);
|
|
3020
|
+
updateMessageFlags(row.accountId, row.msg.uid, row.msg.flags);
|
|
3021
|
+
} catch {
|
|
3022
|
+
row.msg.flags = prevFlags;
|
|
3023
|
+
row.setUnreadClass(!prev);
|
|
3024
|
+
}
|
|
3025
|
+
}));
|
|
3026
|
+
}
|
|
3027
|
+
async function bulkMoveSelection() {
|
|
3028
|
+
const targets = bulkTargets();
|
|
3029
|
+
if (targets.length === 0)
|
|
3030
|
+
return;
|
|
3031
|
+
const accountId = targets[0].accountId;
|
|
3032
|
+
const same = targets.filter((r) => r.accountId === accountId);
|
|
3033
|
+
const pick = await pickFolder(accountId, { excludeFolderIds: [same[0].msg.folderId] });
|
|
3034
|
+
if (!pick)
|
|
3035
|
+
return;
|
|
3036
|
+
const uids = same.map((r) => r.msg.uid);
|
|
3037
|
+
removeMessagesAndReconcile(uids.map((u) => ({ accountId, uid: u })));
|
|
3038
|
+
const statusSync = document.getElementById("status-sync");
|
|
3039
|
+
if (statusSync)
|
|
3040
|
+
statusSync.textContent = `Moving ${uids.length} message${uids.length !== 1 ? "s" : ""} to ${pick.folderName}\u2026`;
|
|
3041
|
+
moveMessages(accountId, uids, pick.folderId).then(() => {
|
|
3042
|
+
if (statusSync)
|
|
3043
|
+
statusSync.textContent = `Moved ${uids.length} to ${pick.folderName}`;
|
|
3044
|
+
}).catch((err) => {
|
|
3045
|
+
if (statusSync)
|
|
3046
|
+
statusSync.textContent = `Move sync issue: ${err?.message || err}`;
|
|
3047
|
+
});
|
|
3048
|
+
exitMultiSelect();
|
|
3049
|
+
}
|
|
3050
|
+
async function showSelectionMenu(x, y) {
|
|
3051
|
+
const { showContextMenu: showContextMenu2 } = await Promise.resolve().then(() => (init_context_menu(), context_menu_exports));
|
|
3052
|
+
const body = document.getElementById("ml-body");
|
|
3053
|
+
const visibleRows = body ? Array.from(body.querySelectorAll(".ml-row:not(.filter-hidden)")) : [];
|
|
3054
|
+
const enterMulti = () => body?.classList.add("multi-select-on");
|
|
3055
|
+
const shownCount = visibleRows.length;
|
|
3056
|
+
const selCount = getSelectedMessages().length;
|
|
3057
|
+
const hasShown = shownCount > 0;
|
|
3058
|
+
const targetN = selCount > 0 ? selCount : shownCount;
|
|
3059
|
+
const onWhat = selCount > 0 ? `selected (${selCount})` : `all shown (${shownCount})`;
|
|
3060
|
+
const canAct = targetN > 0;
|
|
3061
|
+
const items = [
|
|
3062
|
+
{ label: `Acting on ${onWhat}`, action: () => {
|
|
3063
|
+
}, disabled: true },
|
|
3064
|
+
{ label: "", action: () => {
|
|
3065
|
+
}, separator: true },
|
|
3066
|
+
{
|
|
3067
|
+
label: `Select all shown (${shownCount})`,
|
|
3068
|
+
action: () => {
|
|
3069
|
+
if (!body)
|
|
3070
|
+
return;
|
|
3071
|
+
enterMulti();
|
|
3072
|
+
for (const r of visibleRows)
|
|
3073
|
+
r.classList.add("selected");
|
|
3074
|
+
lastClickedRow = visibleRows[visibleRows.length - 1] || null;
|
|
3075
|
+
updateBulkBar();
|
|
3076
|
+
},
|
|
3077
|
+
disabled: !hasShown
|
|
3078
|
+
},
|
|
3079
|
+
{
|
|
3080
|
+
label: "Select flagged \u2605 among shown",
|
|
3081
|
+
action: () => {
|
|
3082
|
+
if (!body)
|
|
3083
|
+
return;
|
|
3084
|
+
enterMulti();
|
|
3085
|
+
for (const r of visibleRows)
|
|
3086
|
+
r.classList.toggle("selected", r.classList.contains("flagged"));
|
|
3087
|
+
lastClickedRow = null;
|
|
3088
|
+
updateBulkBar();
|
|
3089
|
+
},
|
|
3090
|
+
disabled: !hasShown
|
|
3091
|
+
},
|
|
3092
|
+
{
|
|
3093
|
+
label: "Invert selection among shown",
|
|
3094
|
+
action: () => {
|
|
3095
|
+
if (!body)
|
|
3096
|
+
return;
|
|
3097
|
+
enterMulti();
|
|
3098
|
+
for (const r of visibleRows)
|
|
3099
|
+
r.classList.toggle("selected");
|
|
3100
|
+
updateBulkBar();
|
|
3101
|
+
},
|
|
3102
|
+
disabled: !hasShown
|
|
3103
|
+
},
|
|
3104
|
+
{ label: `Deselect all (${selCount})`, action: () => exitMultiSelect(), disabled: selCount === 0 },
|
|
3105
|
+
{ label: "", action: () => {
|
|
3106
|
+
}, separator: true },
|
|
3107
|
+
// Actions — auto-targeted (selected else all shown). No count on the
|
|
3108
|
+
// verb itself (a number on "Flag" reads as ambiguous, Bob 2026-06-24);
|
|
3109
|
+
// the "Acting on …" header above carries the target count.
|
|
3110
|
+
{ label: "Flag", action: () => bulkSetFlagged(bulkTargets(), true), disabled: !canAct },
|
|
3111
|
+
{ label: "Unflag", action: () => bulkSetFlagged(bulkTargets(), false), disabled: !canAct },
|
|
3112
|
+
{ label: "Mark read", action: () => bulkSetSeen(bulkTargets(), true), disabled: !canAct },
|
|
3113
|
+
{ label: "Mark unread", action: () => bulkSetSeen(bulkTargets(), false), disabled: !canAct },
|
|
3114
|
+
{ label: "", action: () => {
|
|
3115
|
+
}, separator: true },
|
|
3116
|
+
{ label: "Move to folder\u2026", action: () => bulkMoveSelection(), disabled: !canAct }
|
|
3117
|
+
];
|
|
3118
|
+
showContextMenu2(x, y, items);
|
|
3119
|
+
}
|
|
2964
3120
|
function scrollFocusedIntoView() {
|
|
2965
3121
|
if (!focusedRow)
|
|
2966
3122
|
return;
|
|
@@ -3216,7 +3372,7 @@ function removeMessagesAndReconcile(uids) {
|
|
|
3216
3372
|
}
|
|
3217
3373
|
}
|
|
3218
3374
|
if (getMessages2().length === 0) {
|
|
3219
|
-
body.innerHTML = `<div class="ml-empty"
|
|
3375
|
+
body.innerHTML = `<div class="ml-empty">${emptyListMessage()}</div>`;
|
|
3220
3376
|
}
|
|
3221
3377
|
}
|
|
3222
3378
|
if (outcome.focusedWasRemoved) {
|
|
@@ -3273,10 +3429,15 @@ async function loadUnifiedInbox(autoSelect = true) {
|
|
|
3273
3429
|
if (cached) {
|
|
3274
3430
|
totalMessages = cached.total;
|
|
3275
3431
|
setMessages(cached.items);
|
|
3276
|
-
renderMessages(body, "", cached.items, { background: !autoSelect });
|
|
3432
|
+
const renderCached = () => renderMessages(body, "", cached.items, { background: !autoSelect });
|
|
3433
|
+
if (!autoSelect)
|
|
3434
|
+
withScrollAnchor(body, renderCached);
|
|
3435
|
+
else
|
|
3436
|
+
renderCached();
|
|
3277
3437
|
const targetUuid = remembered ? pickRestoreUid(cached.items, remembered) : null;
|
|
3278
3438
|
if (targetUuid) {
|
|
3279
|
-
|
|
3439
|
+
if (autoSelect)
|
|
3440
|
+
body.scrollTop = savedScroll;
|
|
3280
3441
|
restoreSelection(body, targetUuid);
|
|
3281
3442
|
} else if (autoSelect) {
|
|
3282
3443
|
selectFirst(body);
|
|
@@ -3360,7 +3521,7 @@ async function loadSearchResults(query, scope = "all", accountId = "", folderId
|
|
|
3360
3521
|
totalMessages = matches.length;
|
|
3361
3522
|
setMessages(matches);
|
|
3362
3523
|
if (matches.length === 0) {
|
|
3363
|
-
body.innerHTML = `<div class="ml-empty">No
|
|
3524
|
+
body.innerHTML = `<div class="ml-empty">No messages found by "${escapeHtmlText(query)}"</div>`;
|
|
3364
3525
|
return;
|
|
3365
3526
|
}
|
|
3366
3527
|
body.innerHTML = "";
|
|
@@ -3380,7 +3541,7 @@ async function loadSearchResults(query, scope = "all", accountId = "", folderId
|
|
|
3380
3541
|
const partialNote = partial ? `<div class="ml-search-warning">\u26A0 Searched ${foldersSearched} folder(s); ${foldersFailed} could not be searched (timeout / connection). Results may be incomplete \u2014 retry to search them again.</div>` : "";
|
|
3381
3542
|
if (result.items.length === 0) {
|
|
3382
3543
|
setMessages([]);
|
|
3383
|
-
body.innerHTML = partialNote + `<div class="ml-empty">No
|
|
3544
|
+
body.innerHTML = partialNote + `<div class="ml-empty">No messages found by "${escapeHtmlText(query)}"${partial ? " in the folders that responded" : ""}</div>`;
|
|
3384
3545
|
return;
|
|
3385
3546
|
}
|
|
3386
3547
|
setMessages(result.items);
|
|
@@ -3391,7 +3552,10 @@ async function loadSearchResults(query, scope = "all", accountId = "", folderId
|
|
|
3391
3552
|
body.innerHTML = `<div class="ml-empty">Search error: ${e.message}</div>`;
|
|
3392
3553
|
}
|
|
3393
3554
|
}
|
|
3394
|
-
|
|
3555
|
+
function revealMessage(accountId, folderId, uid, specialUse = "") {
|
|
3556
|
+
loadMessages(accountId, folderId, 1, specialUse, true, uid);
|
|
3557
|
+
}
|
|
3558
|
+
async function loadMessages(accountId, folderId, page = 1, specialUse = "", autoSelect = true, focusUid) {
|
|
3395
3559
|
const myGen = ++loadGen;
|
|
3396
3560
|
searchMode = false;
|
|
3397
3561
|
unifiedMode = false;
|
|
@@ -3418,11 +3582,21 @@ async function loadMessages(accountId, folderId, page = 1, specialUse = "", auto
|
|
|
3418
3582
|
if (cached) {
|
|
3419
3583
|
totalMessages = cached.total;
|
|
3420
3584
|
setMessages(cached.items);
|
|
3421
|
-
renderMessages(body, accountId, cached.items, { background: !autoSelect });
|
|
3585
|
+
const renderCached = () => renderMessages(body, accountId, cached.items, { background: !autoSelect });
|
|
3586
|
+
if (!autoSelect)
|
|
3587
|
+
withScrollAnchor(body, renderCached);
|
|
3588
|
+
else
|
|
3589
|
+
renderCached();
|
|
3422
3590
|
const targetUuid = remembered ? pickRestoreUid(cached.items, remembered) : null;
|
|
3423
|
-
if (
|
|
3591
|
+
if (focusUid != null) {
|
|
3424
3592
|
requestAnimationFrame(() => {
|
|
3425
|
-
|
|
3593
|
+
if (!focusByIdentity(accountId, focusUid, { scroll: true }) && autoSelect)
|
|
3594
|
+
selectFirst(body);
|
|
3595
|
+
});
|
|
3596
|
+
} else if (targetUuid) {
|
|
3597
|
+
requestAnimationFrame(() => {
|
|
3598
|
+
if (autoSelect)
|
|
3599
|
+
body.scrollTop = savedScroll;
|
|
3426
3600
|
restoreSelection(body, targetUuid);
|
|
3427
3601
|
});
|
|
3428
3602
|
} else if (autoSelect) {
|
|
@@ -3457,7 +3631,14 @@ async function loadMessages(accountId, folderId, page = 1, specialUse = "", auto
|
|
|
3457
3631
|
}
|
|
3458
3632
|
}
|
|
3459
3633
|
const targetUuid = remembered ? pickRestoreUid(result.items, remembered) : null;
|
|
3460
|
-
if (
|
|
3634
|
+
if (focusUid != null) {
|
|
3635
|
+
requestAnimationFrame(() => {
|
|
3636
|
+
if (myGen !== loadGen)
|
|
3637
|
+
return;
|
|
3638
|
+
if (!focusByIdentity(accountId, focusUid, { scroll: true }) && autoSelect)
|
|
3639
|
+
selectFirst(body);
|
|
3640
|
+
});
|
|
3641
|
+
} else if (targetUuid) {
|
|
3461
3642
|
requestAnimationFrame(() => {
|
|
3462
3643
|
if (myGen !== loadGen)
|
|
3463
3644
|
return;
|
|
@@ -3482,12 +3663,20 @@ async function loadMoreMessages() {
|
|
|
3482
3663
|
return;
|
|
3483
3664
|
loading = true;
|
|
3484
3665
|
currentPage++;
|
|
3666
|
+
const t0 = Date.now();
|
|
3667
|
+
const heightBefore = body.scrollHeight;
|
|
3668
|
+
const rowsBefore = body.querySelectorAll(".ml-row").length;
|
|
3485
3669
|
try {
|
|
3486
3670
|
const flaggedOnly = body.classList.contains("flagged-only");
|
|
3487
3671
|
const result = searchMode ? await searchMessages(currentSearchQuery, currentPage) : unifiedMode ? await getUnifiedInbox(currentPage, 50, flaggedOnly) : await getMessages(currentAccountId2, currentFolderId, currentPage, 50, flaggedOnly);
|
|
3488
3672
|
const current = getMessages2();
|
|
3489
3673
|
setMessages([...current, ...result.items]);
|
|
3490
3674
|
appendMessages(body, unifiedMode ? "" : currentAccountId2, result.items);
|
|
3675
|
+
const rowsAfter = body.querySelectorAll(".ml-row").length;
|
|
3676
|
+
const heightAfter = body.scrollHeight;
|
|
3677
|
+
const elapsed = Date.now() - t0;
|
|
3678
|
+
const grew = heightAfter > heightBefore;
|
|
3679
|
+
console.log(` [ml-loadmore] page=${currentPage} got=${result.items?.length ?? 0} rows ${rowsBefore}->${rowsAfter} height ${heightBefore}->${heightAfter} grew=${grew} total=${totalMessages} ${elapsed}ms${grew ? "" : " \u26A0 NO GROWTH \u2014 scroll will appear stuck"}`);
|
|
3491
3680
|
} catch (e) {
|
|
3492
3681
|
console.error(`Load more error: ${e.message}`);
|
|
3493
3682
|
} finally {
|
|
@@ -3495,12 +3684,18 @@ async function loadMoreMessages() {
|
|
|
3495
3684
|
}
|
|
3496
3685
|
}
|
|
3497
3686
|
function renderMessages(body, accountId, items, opts = {}) {
|
|
3687
|
+
const preserveSel = body.classList.contains("multi-select-on") ? new Set(Array.from(body.querySelectorAll(".ml-row.selected")).map((r) => r.dataset.uuid || "").filter(Boolean)) : null;
|
|
3498
3688
|
const fragment = document.createDocumentFragment();
|
|
3499
3689
|
const tempDiv = document.createElement("div");
|
|
3500
3690
|
appendMessages(tempDiv, accountId, items);
|
|
3501
3691
|
while (tempDiv.firstChild)
|
|
3502
3692
|
fragment.appendChild(tempDiv.firstChild);
|
|
3503
3693
|
body.replaceChildren(fragment);
|
|
3694
|
+
if (preserveSel && preserveSel.size > 0) {
|
|
3695
|
+
for (const uuid of preserveSel) {
|
|
3696
|
+
body.querySelector(`.ml-row[data-uuid="${CSS.escape(uuid)}"]`)?.classList.add("selected");
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3504
3699
|
try {
|
|
3505
3700
|
maybeShowFilterEmptyState(body);
|
|
3506
3701
|
} catch {
|
|
@@ -4002,6 +4197,14 @@ var init_message_list = __esm({
|
|
|
4002
4197
|
}
|
|
4003
4198
|
onFlagClick(e) {
|
|
4004
4199
|
e.stopPropagation();
|
|
4200
|
+
const body = this.el.parentElement;
|
|
4201
|
+
if (body?.classList.contains("multi-select-on") && this.el.classList.contains("selected")) {
|
|
4202
|
+
const sel = selectedRows();
|
|
4203
|
+
if (sel.length > 1) {
|
|
4204
|
+
void bulkSetFlagged(sel, !this.el.classList.contains("flagged"));
|
|
4205
|
+
return;
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4005
4208
|
const newFlaggedState = !this.el.classList.contains("flagged");
|
|
4006
4209
|
const prevFlags = [...this.msg.flags || []];
|
|
4007
4210
|
this.isFlagged = newFlaggedState;
|
|
@@ -4139,33 +4342,23 @@ var init_message_list = __esm({
|
|
|
4139
4342
|
const accountId = this.accountId;
|
|
4140
4343
|
const msg = this.msg;
|
|
4141
4344
|
const self = this;
|
|
4345
|
+
const selCount = getSelectedMessages().length;
|
|
4346
|
+
const countSfx = selCount > 1 ? ` (${selCount})` : "";
|
|
4347
|
+
const rowTargets = () => {
|
|
4348
|
+
const s = selectedRows();
|
|
4349
|
+
return s.length > 0 ? s : [self];
|
|
4350
|
+
};
|
|
4142
4351
|
const items = [
|
|
4143
4352
|
{
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
await updateFlags(accountId, msg.uid, probe.flags);
|
|
4150
|
-
msg.flags = probe.flags;
|
|
4151
|
-
updateMessageFlags(accountId, msg.uid, probe.flags);
|
|
4152
|
-
self.setUnreadClass(isSeen);
|
|
4153
|
-
} catch {
|
|
4154
|
-
}
|
|
4155
|
-
}
|
|
4353
|
+
// Target state = inverse of what the menu showed, matching what
|
|
4354
|
+
// the user clicked. Visual-state-based labels stay coherent with
|
|
4355
|
+
// the auto-mark-as-read debounce. Applies to the whole selection.
|
|
4356
|
+
label: (isSeen ? "Mark unread" : "Mark read") + countSfx,
|
|
4357
|
+
action: () => bulkSetSeen(rowTargets(), !isSeen)
|
|
4156
4358
|
},
|
|
4157
4359
|
{
|
|
4158
|
-
label: isFlagged ? "Unflag" : "Flag",
|
|
4159
|
-
action:
|
|
4160
|
-
const probe = { flags: [...msg.flags || []] };
|
|
4161
|
-
setFlagged(probe, !isFlagged);
|
|
4162
|
-
try {
|
|
4163
|
-
await updateFlags(accountId, msg.uid, probe.flags);
|
|
4164
|
-
msg.flags = probe.flags;
|
|
4165
|
-
self.setFlaggedClass(!isFlagged);
|
|
4166
|
-
} catch {
|
|
4167
|
-
}
|
|
4168
|
-
}
|
|
4360
|
+
label: (isFlagged ? "Unflag" : "Flag") + countSfx,
|
|
4361
|
+
action: () => bulkSetFlagged(rowTargets(), !isFlagged)
|
|
4169
4362
|
},
|
|
4170
4363
|
{ label: "", action: () => {
|
|
4171
4364
|
}, separator: true },
|
|
@@ -4199,8 +4392,8 @@ var init_message_list = __esm({
|
|
|
4199
4392
|
{
|
|
4200
4393
|
label: "Move to folder\u2026",
|
|
4201
4394
|
action: async () => {
|
|
4202
|
-
const
|
|
4203
|
-
const uids =
|
|
4395
|
+
const selectedRows2 = Array.from(document.querySelectorAll(".ml-row.selected"));
|
|
4396
|
+
const uids = selectedRows2.length > 0 ? selectedRows2.map((r) => Number(r.dataset.uid)).filter((u) => !isNaN(u)) : [msg.uid];
|
|
4204
4397
|
const pick = await pickFolder(accountId, { excludeFolderIds: [msg.folderId] });
|
|
4205
4398
|
if (!pick)
|
|
4206
4399
|
return;
|
|
@@ -4582,7 +4775,7 @@ function overdueTaskRowHtml(t) {
|
|
|
4582
4775
|
}
|
|
4583
4776
|
return `<div class="cal-side-task cal-side-task-overdue" data-uuid="${escapeHtml4(t.uuid)}">
|
|
4584
4777
|
<input type="checkbox" class="cal-side-overdue-check" title="Mark done">
|
|
4585
|
-
<span class="cal-side-task-title">${escapeHtml4(t.title)}</span>
|
|
4778
|
+
<span class="cal-side-task-title" title="${escapeHtml4(t.title)}">${escapeHtml4(t.title)}</span>
|
|
4586
4779
|
<span class="cal-side-task-due overdue">${escapeHtml4(dueLabel)}</span>
|
|
4587
4780
|
</div>`;
|
|
4588
4781
|
}
|
|
@@ -4643,7 +4836,7 @@ function renderEvents(events) {
|
|
|
4643
4836
|
html += `<div class="cal-side-event" data-id="${e.id}" data-link="${escapeHtml4(link)}" ${link ? 'title="Click to open in Google Calendar"' : ""}>
|
|
4644
4837
|
${calIconHtml(calInfoFor(e.calendarId))}
|
|
4645
4838
|
<span class="cal-side-event-time">${escapeHtml4(formatTime(e))}</span>
|
|
4646
|
-
<span class="cal-side-event-title">${escapeHtml4(e.title)}<span class="cal-side-event-recur" title="Daily">\u21BB</span></span>
|
|
4839
|
+
<span class="cal-side-event-title" title="${escapeHtml4(e.title)}">${escapeHtml4(e.title)}<span class="cal-side-event-recur" title="Daily">\u21BB</span></span>
|
|
4647
4840
|
</div>`;
|
|
4648
4841
|
}
|
|
4649
4842
|
}
|
|
@@ -4669,14 +4862,14 @@ function renderEvents(events) {
|
|
|
4669
4862
|
const recurAttr = e.recurringEventId ? ' data-recurring="1"' : "";
|
|
4670
4863
|
if (isHolidayKind || kind === "birthday") {
|
|
4671
4864
|
html += `<div class="cal-side-event" data-holiday="1" data-holiday-kind="${kind}" data-id="${e.id}">
|
|
4672
|
-
<span class="cal-side-event-title cal-side-event-holiday-title">${calIconHtml(info)} ${escapeHtml4(e.title)}</span>
|
|
4865
|
+
<span class="cal-side-event-title cal-side-event-holiday-title" title="${escapeHtml4(e.title)}">${calIconHtml(info)} ${escapeHtml4(e.title)}</span>
|
|
4673
4866
|
</div>`;
|
|
4674
4867
|
} else {
|
|
4675
4868
|
const titleAttr = link ? 'title="Click to open in Google Calendar"' : "";
|
|
4676
4869
|
html += `<div class="cal-side-event" data-id="${e.id}"${recurAttr} data-link="${escapeHtml4(link)}" ${titleAttr}>
|
|
4677
4870
|
${calIconHtml(info)}
|
|
4678
4871
|
<span class="cal-side-event-time">${escapeHtml4(formatTime(e))}</span>
|
|
4679
|
-
<span class="cal-side-event-title">${escapeHtml4(e.title)}${recurMark}</span>
|
|
4872
|
+
<span class="cal-side-event-title" title="${escapeHtml4(e.title)}">${escapeHtml4(e.title)}${recurMark}</span>
|
|
4680
4873
|
</div>`;
|
|
4681
4874
|
}
|
|
4682
4875
|
}
|
|
@@ -4761,7 +4954,7 @@ async function renderTasks(prefetched) {
|
|
|
4761
4954
|
const sel = selectedTaskUuids.has(t.uuid) ? " selected" : "";
|
|
4762
4955
|
html += `<div class="cal-side-task${sel}" data-uuid="${t.uuid}">
|
|
4763
4956
|
<input type="checkbox" ${done ? "checked" : ""} class="cal-side-task-check">
|
|
4764
|
-
<span class="cal-side-task-title${done ? " done" : ""}">${escapeHtml4(t.title)}</span>
|
|
4957
|
+
<span class="cal-side-task-title${done ? " done" : ""}" title="${escapeHtml4(t.title)}">${escapeHtml4(t.title)}</span>
|
|
4765
4958
|
${dueHtml}
|
|
4766
4959
|
<button class="cal-side-task-delete" title="Delete task" aria-label="Delete task">\xD7</button>
|
|
4767
4960
|
</div>`;
|
|
@@ -5920,12 +6113,13 @@ decides which operators work.</p>
|
|
|
5920
6113
|
<h3>Qualifiers \u2014 work in every mode</h3>
|
|
5921
6114
|
<table>
|
|
5922
6115
|
<tr><th>Form</th><th>Effect</th></tr>
|
|
5923
|
-
<tr><td><code>from:bob</code></td><td>Sender contains</td></tr>
|
|
5924
|
-
<tr><td><code>to:eleanor</code></td><td>Recipient contains</td></tr>
|
|
6116
|
+
<tr><td><code>from:bob</code></td><td>Sender (name or address) contains</td></tr>
|
|
6117
|
+
<tr><td><code>to:eleanor</code></td><td>Recipient contains \u2014 matches <strong>To <em>or</em> Cc</strong> (anyone the message was addressed to)</td></tr>
|
|
6118
|
+
<tr><td><code>cc:dana</code></td><td>Cc only \u2014 when you want to exclude the main To line</td></tr>
|
|
5925
6119
|
<tr><td><code>subject:lunch</code></td><td>Subject contains</td></tr>
|
|
5926
6120
|
<tr><td><code>date:2026-05-01</code></td><td>On that date \u2014 also <code>date:>1w</code>, <code>date:<=2026-01-15</code></td></tr>
|
|
5927
6121
|
<tr><td><code>after:1w</code> / <code>before:1m</code></td><td>Newer / older than \u2014 <code>d</code>, <code>w</code>, <code>m</code>, <code>y</code>, a date, <code>today</code>, <code>yesterday</code></td></tr>
|
|
5928
|
-
<tr><td><code>has:attachment</code></td><td>
|
|
6122
|
+
<tr><td><code>has:attachment</code></td><td>Only messages that carry a file attachment</td></tr>
|
|
5929
6123
|
<tr><td><code>is:unread</code></td><td>Also <code>is:flagged</code>, <code>is:read</code>, <code>is:answered</code>, <code>is:draft</code></td></tr>
|
|
5930
6124
|
<tr><td><code>folder:sent</code></td><td>Restrict to folders whose name contains the term</td></tr>
|
|
5931
6125
|
<tr><td><code>/regex/</code></td><td>Client-side regex over the currently-visible rows. Local only \u2014 never sent to the server.</td></tr>
|
|
@@ -6252,6 +6446,22 @@ function renderNode(node, container, depth) {
|
|
|
6252
6446
|
folderEl.dataset.folderPath = node.path;
|
|
6253
6447
|
folderEl.dataset.specialUse = node.specialUse || "";
|
|
6254
6448
|
folderEl.style.paddingLeft = `${depth * 16 + 8}px`;
|
|
6449
|
+
if (node.id !== -1 && !node.specialUse) {
|
|
6450
|
+
folderEl.draggable = true;
|
|
6451
|
+
folderEl.addEventListener("dragstart", (e) => {
|
|
6452
|
+
e.dataTransfer.effectAllowed = "move";
|
|
6453
|
+
e.dataTransfer.setData("application/x-mailx-folder", JSON.stringify({
|
|
6454
|
+
accountId: node.accountId,
|
|
6455
|
+
folderId: node.id,
|
|
6456
|
+
name: node.name,
|
|
6457
|
+
path: node.path,
|
|
6458
|
+
// Carry the real hierarchy delimiter so the descendant-drop
|
|
6459
|
+
// guard doesn't have to guess it from the path (a top-level
|
|
6460
|
+
// folder like "Work" has neither "/" nor "." to infer from).
|
|
6461
|
+
delimiter: node.delimiter
|
|
6462
|
+
}));
|
|
6463
|
+
});
|
|
6464
|
+
}
|
|
6255
6465
|
const toggle = document.createElement("span");
|
|
6256
6466
|
toggle.className = "ft-toggle";
|
|
6257
6467
|
if (hasChildren) {
|
|
@@ -6479,6 +6689,48 @@ function renderNode(node, container, depth) {
|
|
|
6479
6689
|
clearTimeout(dragExpandTimer);
|
|
6480
6690
|
dragExpandTimer = null;
|
|
6481
6691
|
}
|
|
6692
|
+
const folderData = e.dataTransfer.getData("application/x-mailx-folder");
|
|
6693
|
+
if (folderData) {
|
|
6694
|
+
let src;
|
|
6695
|
+
try {
|
|
6696
|
+
src = JSON.parse(folderData);
|
|
6697
|
+
} catch {
|
|
6698
|
+
return;
|
|
6699
|
+
}
|
|
6700
|
+
const delim = src.delimiter || (node.path.includes("/") ? "/" : node.path.includes(".") ? "." : "/");
|
|
6701
|
+
if (src.accountId !== node.accountId) {
|
|
6702
|
+
const statusEl3 = document.getElementById("status-sync");
|
|
6703
|
+
if (statusEl3)
|
|
6704
|
+
statusEl3.textContent = "Folders can only be reparented within the same account.";
|
|
6705
|
+
return;
|
|
6706
|
+
}
|
|
6707
|
+
if (src.folderId === node.id)
|
|
6708
|
+
return;
|
|
6709
|
+
if (node.path === src.path || node.path.startsWith(src.path + delim)) {
|
|
6710
|
+
const statusEl3 = document.getElementById("status-sync");
|
|
6711
|
+
if (statusEl3)
|
|
6712
|
+
statusEl3.textContent = "Can't move a folder into one of its own subfolders.";
|
|
6713
|
+
return;
|
|
6714
|
+
}
|
|
6715
|
+
if (!confirm(`Move folder "${src.name}" into "${node.name}"?`))
|
|
6716
|
+
return;
|
|
6717
|
+
const statusEl2 = document.getElementById("status-sync");
|
|
6718
|
+
try {
|
|
6719
|
+
await renameFolder(src.accountId, src.folderId, src.name, node.id);
|
|
6720
|
+
if (statusEl2)
|
|
6721
|
+
statusEl2.textContent = `Moved "${src.name}" into "${node.name}"`;
|
|
6722
|
+
expandState[`${node.accountId}:${node.path}`] = true;
|
|
6723
|
+
saveExpandState();
|
|
6724
|
+
const treeContainer2 = document.getElementById("folder-tree");
|
|
6725
|
+
if (treeContainer2)
|
|
6726
|
+
loadFolderTree(treeContainer2);
|
|
6727
|
+
} catch (err) {
|
|
6728
|
+
if (statusEl2)
|
|
6729
|
+
statusEl2.textContent = `Move failed: ${err?.message || err}`;
|
|
6730
|
+
alert(`Failed: ${err?.message || err}`);
|
|
6731
|
+
}
|
|
6732
|
+
return;
|
|
6733
|
+
}
|
|
6482
6734
|
const multiData = e.dataTransfer.getData("application/x-mailx-messages");
|
|
6483
6735
|
const singleData = e.dataTransfer.getData("application/x-mailx-message");
|
|
6484
6736
|
const messages2 = multiData ? JSON.parse(multiData) : singleData ? [JSON.parse(singleData)] : [];
|
|
@@ -7143,6 +7395,21 @@ init_message_viewer();
|
|
|
7143
7395
|
init_api_client();
|
|
7144
7396
|
init_message_state();
|
|
7145
7397
|
installConsoleCapture();
|
|
7398
|
+
(function installStallWatchdog() {
|
|
7399
|
+
const EXPECTED_MS = 1e3;
|
|
7400
|
+
const STALL_MS = 250;
|
|
7401
|
+
let last = performance.now();
|
|
7402
|
+
const tick = () => {
|
|
7403
|
+
const now = performance.now();
|
|
7404
|
+
const slip = now - last - EXPECTED_MS;
|
|
7405
|
+
if (slip > STALL_MS) {
|
|
7406
|
+
console.warn(` [stall] main thread blocked ~${slip.toFixed(0)}ms (event loop frozen \u2014 UI was unresponsive this long)`);
|
|
7407
|
+
}
|
|
7408
|
+
last = now;
|
|
7409
|
+
setTimeout(tick, EXPECTED_MS);
|
|
7410
|
+
};
|
|
7411
|
+
setTimeout(tick, EXPECTED_MS);
|
|
7412
|
+
})();
|
|
7146
7413
|
var APP_NAME = "rmfmail";
|
|
7147
7414
|
function propagateAppName() {
|
|
7148
7415
|
const set = (id, fn) => {
|
|
@@ -7727,6 +7994,11 @@ document.getElementById("btn-factory-reset")?.addEventListener("click", async ()
|
|
|
7727
7994
|
});
|
|
7728
7995
|
async function openCompose(mode, overrideMsg, overrideAccountId) {
|
|
7729
7996
|
logClientEvent("openCompose-entry", { mode });
|
|
7997
|
+
for (const id of ["settings-dropdown", "view-dropdown", "restart-dropdown"]) {
|
|
7998
|
+
const dd = document.getElementById(id);
|
|
7999
|
+
if (dd && !dd.hidden) hideDropdownHard(dd);
|
|
8000
|
+
}
|
|
8001
|
+
refreshToolbarOverlayShield();
|
|
7730
8002
|
const current = overrideMsg ? { message: overrideMsg, accountId: overrideAccountId || currentAccountId3 } : getCurrentMessage();
|
|
7731
8003
|
if ((mode === "reply" || mode === "replyAll" || mode === "forward" || mode === "editAsNew") && !current) {
|
|
7732
8004
|
console.warn(`[compose] ${mode} \u2014 no message selected`);
|
|
@@ -7738,7 +8010,7 @@ async function openCompose(mode, overrideMsg, overrideAccountId) {
|
|
|
7738
8010
|
const titleSubject = mode === "new" ? "" : msg?.subject || "";
|
|
7739
8011
|
const frame = showComposeOverlay(titleSubject ? `${titlePrefix}: ${titleSubject}` : titlePrefix);
|
|
7740
8012
|
const accounts = await accountsP;
|
|
7741
|
-
const accountId = current?.accountId || accounts[0]?.id || "";
|
|
8013
|
+
const accountId = mode === "new" ? currentAccountId3 || current?.accountId || accounts[0]?.id || "" : current?.accountId || currentAccountId3 || accounts[0]?.id || "";
|
|
7742
8014
|
const rePrefix = /^(re|fwd?):\s*/i;
|
|
7743
8015
|
const cleanSubject = msg ? msg.subject.replace(rePrefix, "") : "";
|
|
7744
8016
|
const init = {
|
|
@@ -7848,6 +8120,25 @@ async function openCompose(mode, overrideMsg, overrideAccountId) {
|
|
|
7848
8120
|
init.subject = msg.subject || "";
|
|
7849
8121
|
init.bodyHtml = editAsNewBody(msg);
|
|
7850
8122
|
}
|
|
8123
|
+
if (msg && (mode === "editAsNew" || mode === "forward") && Array.isArray(msg.attachments) && msg.attachments.length) {
|
|
8124
|
+
const msgAccountId = current?.accountId || accountId;
|
|
8125
|
+
const loaded = [];
|
|
8126
|
+
for (const att of msg.attachments) {
|
|
8127
|
+
try {
|
|
8128
|
+
const data = await getAttachment(msgAccountId, msg.uid, att.id, msg.folderId);
|
|
8129
|
+
if (data?.content) {
|
|
8130
|
+
loaded.push({
|
|
8131
|
+
filename: data.filename || att.filename || "attachment",
|
|
8132
|
+
mimeType: data.contentType || att.contentType || "application/octet-stream",
|
|
8133
|
+
dataBase64: data.content
|
|
8134
|
+
});
|
|
8135
|
+
}
|
|
8136
|
+
} catch (e) {
|
|
8137
|
+
console.error(`[compose] ${mode}: failed to load attachment ${att.id}: ${e?.message || e}`);
|
|
8138
|
+
}
|
|
8139
|
+
}
|
|
8140
|
+
if (loaded.length) init.attachments = loaded;
|
|
8141
|
+
}
|
|
7851
8142
|
const initJson = JSON.stringify(init);
|
|
7852
8143
|
try {
|
|
7853
8144
|
sessionStorage.setItem("composeInit", initJson);
|
|
@@ -8214,6 +8505,12 @@ async function performUndo() {
|
|
|
8214
8505
|
document.addEventListener("mailx-moved", (e) => {
|
|
8215
8506
|
pushUndo({ kind: "move", at: Date.now(), payload: e.detail });
|
|
8216
8507
|
});
|
|
8508
|
+
document.addEventListener("mailx-flagged-batch", (e) => {
|
|
8509
|
+
const payload = e.detail;
|
|
8510
|
+
if (Array.isArray(payload) && payload.length) {
|
|
8511
|
+
pushUndo({ kind: "flag", at: Date.now(), payload });
|
|
8512
|
+
}
|
|
8513
|
+
});
|
|
8217
8514
|
(() => {
|
|
8218
8515
|
const SLOP = 6;
|
|
8219
8516
|
let cand = null;
|
|
@@ -8486,6 +8783,11 @@ document.getElementById("btn-spam-report")?.addEventListener("click", async () =
|
|
|
8486
8783
|
}
|
|
8487
8784
|
});
|
|
8488
8785
|
document.getElementById("btn-compose")?.addEventListener("click", () => openCompose("new"));
|
|
8786
|
+
document.getElementById("btn-selection")?.addEventListener("click", (e) => {
|
|
8787
|
+
e.stopPropagation();
|
|
8788
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
8789
|
+
void showSelectionMenu(r.left, r.bottom + 2);
|
|
8790
|
+
});
|
|
8489
8791
|
document.getElementById("btn-mark-unread")?.addEventListener("click", () => {
|
|
8490
8792
|
const sel = getCurrentFocused();
|
|
8491
8793
|
if (!sel) return;
|
|
@@ -8841,6 +9143,20 @@ var currentAccountId3 = "";
|
|
|
8841
9143
|
var currentFolderId2 = 0;
|
|
8842
9144
|
var reloadDebounceTimer = null;
|
|
8843
9145
|
var lastListReloadAt = 0;
|
|
9146
|
+
function closeSearchAndReveal() {
|
|
9147
|
+
const focused = getCurrentFocused();
|
|
9148
|
+
clearSearchMode();
|
|
9149
|
+
const body = document.getElementById("ml-body");
|
|
9150
|
+
if (body) body.querySelectorAll(".filter-hidden").forEach((r) => r.classList.remove("filter-hidden"));
|
|
9151
|
+
if (focused && focused.folderId) {
|
|
9152
|
+
currentAccountId3 = focused.accountId;
|
|
9153
|
+
currentFolderId2 = focused.folderId;
|
|
9154
|
+
revealMessage(focused.accountId, focused.folderId, focused.uid);
|
|
9155
|
+
} else {
|
|
9156
|
+
reloadCurrentFolder();
|
|
9157
|
+
}
|
|
9158
|
+
setTitle(APP_NAME);
|
|
9159
|
+
}
|
|
8844
9160
|
searchInput?.addEventListener("input", () => {
|
|
8845
9161
|
clearTimeout(searchTimeout);
|
|
8846
9162
|
if (serverSearchTimer) {
|
|
@@ -8850,11 +9166,7 @@ searchInput?.addEventListener("input", () => {
|
|
|
8850
9166
|
}
|
|
8851
9167
|
updateSearchHighlight();
|
|
8852
9168
|
if (searchInput.value.trim() === "") {
|
|
8853
|
-
|
|
8854
|
-
const body = document.getElementById("ml-body");
|
|
8855
|
-
if (body) body.querySelectorAll(".filter-hidden").forEach((r) => r.classList.remove("filter-hidden"));
|
|
8856
|
-
reloadCurrentFolder();
|
|
8857
|
-
setTitle(APP_NAME);
|
|
9169
|
+
closeSearchAndReveal();
|
|
8858
9170
|
} else {
|
|
8859
9171
|
searchTimeout = setTimeout(() => doSearch(false), 180);
|
|
8860
9172
|
}
|
|
@@ -8872,11 +9184,7 @@ searchInput?.addEventListener("keydown", (e) => {
|
|
|
8872
9184
|
}
|
|
8873
9185
|
cancelServerSearch();
|
|
8874
9186
|
updateSearchHighlight();
|
|
8875
|
-
|
|
8876
|
-
const body = document.getElementById("ml-body");
|
|
8877
|
-
if (body) body.querySelectorAll(".filter-hidden").forEach((r) => r.classList.remove("filter-hidden"));
|
|
8878
|
-
reloadCurrentFolder();
|
|
8879
|
-
setTitle(APP_NAME);
|
|
9187
|
+
closeSearchAndReveal();
|
|
8880
9188
|
}
|
|
8881
9189
|
});
|
|
8882
9190
|
function rerunActiveSearch() {
|
|
@@ -9891,6 +10199,7 @@ var viewDropdown = document.getElementById("view-dropdown");
|
|
|
9891
10199
|
var optTwoLine = document.getElementById("opt-two-line");
|
|
9892
10200
|
var optPreview = document.getElementById("opt-preview");
|
|
9893
10201
|
var optSnippet = document.getElementById("opt-snippet");
|
|
10202
|
+
var optFlagStars = document.getElementById("opt-flag-stars");
|
|
9894
10203
|
var optThreaded = document.getElementById("opt-threaded");
|
|
9895
10204
|
var optFlagged = document.getElementById("opt-flagged");
|
|
9896
10205
|
var optFolderCounts = document.getElementById("opt-folder-counts");
|
|
@@ -9925,6 +10234,13 @@ function hideDropdownHard(dd) {
|
|
|
9925
10234
|
dd.hidden = true;
|
|
9926
10235
|
dd.style.cssText = "";
|
|
9927
10236
|
}
|
|
10237
|
+
var tbEscapeCleanup = null;
|
|
10238
|
+
function closeToolbarDropdowns() {
|
|
10239
|
+
for (const id of ["settings-dropdown", "view-dropdown", "restart-dropdown"]) {
|
|
10240
|
+
const dd = document.getElementById(id);
|
|
10241
|
+
if (dd && !dd.hidden) hideDropdownHard(dd);
|
|
10242
|
+
}
|
|
10243
|
+
}
|
|
9928
10244
|
function refreshToolbarOverlayShield() {
|
|
9929
10245
|
const dropdownOpen = ["settings-dropdown", "view-dropdown", "restart-dropdown"].some((id) => {
|
|
9930
10246
|
const el = document.getElementById(id);
|
|
@@ -9940,16 +10256,46 @@ function refreshToolbarOverlayShield() {
|
|
|
9940
10256
|
shield.addEventListener("pointerdown", (ev) => {
|
|
9941
10257
|
ev.preventDefault();
|
|
9942
10258
|
ev.stopPropagation();
|
|
9943
|
-
|
|
9944
|
-
|
|
9945
|
-
if (dd) dd.hidden = true;
|
|
9946
|
-
}
|
|
9947
|
-
shield?.remove();
|
|
10259
|
+
closeToolbarDropdowns();
|
|
10260
|
+
refreshToolbarOverlayShield();
|
|
9948
10261
|
});
|
|
9949
10262
|
document.body.appendChild(shield);
|
|
9950
10263
|
}
|
|
9951
|
-
|
|
9952
|
-
|
|
10264
|
+
if (!tbEscapeCleanup) {
|
|
10265
|
+
const docs = [];
|
|
10266
|
+
document.querySelectorAll(".compose-overlay iframe, .popout-overlay iframe").forEach((ifr) => {
|
|
10267
|
+
try {
|
|
10268
|
+
if (ifr.contentDocument) docs.push(ifr.contentDocument);
|
|
10269
|
+
} catch {
|
|
10270
|
+
}
|
|
10271
|
+
});
|
|
10272
|
+
if (docs.length) {
|
|
10273
|
+
const onEsc = (e) => {
|
|
10274
|
+
if (e.key !== "Escape") return;
|
|
10275
|
+
if (!["settings-dropdown", "view-dropdown", "restart-dropdown"].some((id) => {
|
|
10276
|
+
const el = document.getElementById(id);
|
|
10277
|
+
return el && !el.hidden;
|
|
10278
|
+
})) return;
|
|
10279
|
+
e.preventDefault();
|
|
10280
|
+
e.stopPropagation();
|
|
10281
|
+
closeToolbarDropdowns();
|
|
10282
|
+
refreshToolbarOverlayShield();
|
|
10283
|
+
};
|
|
10284
|
+
for (const d of docs) d.addEventListener("keydown", onEsc, true);
|
|
10285
|
+
tbEscapeCleanup = () => {
|
|
10286
|
+
for (const d of docs) {
|
|
10287
|
+
try {
|
|
10288
|
+
d.removeEventListener("keydown", onEsc, true);
|
|
10289
|
+
} catch {
|
|
10290
|
+
}
|
|
10291
|
+
}
|
|
10292
|
+
tbEscapeCleanup = null;
|
|
10293
|
+
};
|
|
10294
|
+
}
|
|
10295
|
+
}
|
|
10296
|
+
} else {
|
|
10297
|
+
if (shield) shield.remove();
|
|
10298
|
+
if (tbEscapeCleanup) tbEscapeCleanup();
|
|
9953
10299
|
}
|
|
9954
10300
|
}
|
|
9955
10301
|
document.addEventListener("keydown", (e) => {
|
|
@@ -9980,18 +10326,21 @@ document.addEventListener("keydown", (e) => {
|
|
|
9980
10326
|
var savedTwoLine = localStorage.getItem("mailx-two-line") === "true";
|
|
9981
10327
|
var savedPreview = localStorage.getItem("mailx-preview") !== "false";
|
|
9982
10328
|
var savedSnippet = localStorage.getItem("mailx-snippet") !== "false";
|
|
10329
|
+
var savedFlagStars = localStorage.getItem("mailx-flag-stars") !== "false";
|
|
9983
10330
|
var savedThreaded = localStorage.getItem("mailx-threaded") === "true";
|
|
9984
10331
|
var savedFlagged = localStorage.getItem("mailx-flagged") === "true";
|
|
9985
10332
|
var savedFolderCounts = localStorage.getItem("mailx-folder-counts") === "true";
|
|
9986
10333
|
if (optTwoLine) optTwoLine.checked = savedTwoLine;
|
|
9987
10334
|
if (optPreview) optPreview.checked = savedPreview;
|
|
9988
10335
|
if (optSnippet) optSnippet.checked = savedSnippet;
|
|
10336
|
+
if (optFlagStars) optFlagStars.checked = savedFlagStars;
|
|
9989
10337
|
if (optThreaded) optThreaded.checked = savedThreaded;
|
|
9990
10338
|
if (optFlagged) optFlagged.checked = savedFlagged;
|
|
9991
10339
|
if (optFolderCounts) optFolderCounts.checked = savedFolderCounts;
|
|
9992
10340
|
if (savedTwoLine) document.getElementById("message-list")?.classList.add("two-line");
|
|
9993
10341
|
if (!savedPreview) document.querySelector(".main-area")?.classList.add("no-preview");
|
|
9994
10342
|
if (!savedSnippet) document.getElementById("message-list")?.classList.add("no-snippets");
|
|
10343
|
+
if (!savedFlagStars) document.getElementById("message-list")?.classList.add("no-empty-stars");
|
|
9995
10344
|
if (savedThreaded) document.getElementById("ml-body")?.classList.add("threaded");
|
|
9996
10345
|
if (savedFlagged) {
|
|
9997
10346
|
document.getElementById("ml-body")?.classList.add("flagged-only");
|
|
@@ -10161,6 +10510,11 @@ optSnippet?.addEventListener("change", () => {
|
|
|
10161
10510
|
}
|
|
10162
10511
|
localStorage.setItem("mailx-snippet", String(optSnippet.checked));
|
|
10163
10512
|
});
|
|
10513
|
+
optFlagStars?.addEventListener("change", () => {
|
|
10514
|
+
const list = document.getElementById("message-list");
|
|
10515
|
+
list?.classList.toggle("no-empty-stars", !optFlagStars.checked);
|
|
10516
|
+
localStorage.setItem("mailx-flag-stars", String(optFlagStars.checked));
|
|
10517
|
+
});
|
|
10164
10518
|
document.getElementById("search-help")?.addEventListener("click", async () => {
|
|
10165
10519
|
const btn = document.getElementById("search-help");
|
|
10166
10520
|
if (!btn) return;
|