@bobfrankston/rmfmail 1.2.87 → 1.2.96
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 +65 -15
- package/client/android-bootstrap.bundle.js.map +2 -2
- package/client/app.bundle.js +378 -286
- package/client/app.bundle.js.map +4 -4
- package/client/app.js +23 -1
- package/client/app.js.map +1 -1
- package/client/app.ts +20 -1
- package/client/components/address-book.js +5 -1
- package/client/components/address-book.js.map +1 -1
- package/client/components/address-book.ts +5 -1
- package/client/components/folder-tree.js +31 -6
- package/client/components/folder-tree.js.map +1 -1
- package/client/components/folder-tree.ts +29 -5
- package/client/components/message-list.js +164 -54
- package/client/components/message-list.js.map +1 -1
- package/client/components/message-list.ts +159 -51
- package/client/components/message-viewer.js +7 -0
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +7 -0
- package/client/compose/compose.bundle.js +4 -4
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/index.html +4 -0
- package/client/lib/api-client.js +4 -4
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +4 -4
- package/client/lib/mailxapi.js +4 -4
- package/client/styles/components.css +20 -0
- package/package.json +9 -9
- package/packages/mailx-imap/index.d.ts +17 -0
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +86 -4
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +85 -4
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-service/index.d.ts +2 -2
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +6 -4
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +7 -5
- package/packages/mailx-service/jsonrpc.js +2 -2
- package/packages/mailx-service/jsonrpc.js.map +1 -1
- package/packages/mailx-service/jsonrpc.ts +2 -2
- package/packages/mailx-service/sync-queue.d.ts +3 -0
- package/packages/mailx-service/sync-queue.d.ts.map +1 -1
- package/packages/mailx-service/sync-queue.js +30 -11
- package/packages/mailx-service/sync-queue.js.map +1 -1
- package/packages/mailx-service/sync-queue.ts +34 -9
- package/packages/mailx-service/sync-worker.js +51 -0
- package/packages/mailx-service/sync-worker.js.map +1 -1
- package/packages/mailx-service/sync-worker.ts +45 -0
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/db.d.ts +10 -1
- package/packages/mailx-store/db.d.ts.map +1 -1
- package/packages/mailx-store/db.js +68 -7
- package/packages/mailx-store/db.js.map +1 -1
- package/packages/mailx-store/db.ts +70 -8
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store/store.d.ts +1 -1
- package/packages/mailx-store/store.d.ts.map +1 -1
- package/packages/mailx-store/store.js +2 -2
- package/packages/mailx-store/store.js.map +1 -1
- package/packages/mailx-store/store.ts +2 -2
- package/packages/mailx-store-web/imap-web-provider.d.ts +7 -0
- package/packages/mailx-store-web/imap-web-provider.d.ts.map +1 -1
- package/packages/mailx-store-web/imap-web-provider.js +12 -0
- package/packages/mailx-store-web/imap-web-provider.js.map +1 -1
- package/packages/mailx-store-web/imap-web-provider.ts +15 -0
- package/packages/mailx-store-web/package.json +1 -1
- package/packages/mailx-store-web/sync-manager.d.ts.map +1 -1
- package/packages/mailx-store-web/sync-manager.js +12 -0
- package/packages/mailx-store-web/sync-manager.js.map +1 -1
- package/packages/mailx-store-web/sync-manager.ts +12 -0
- package/packages/mailx-store-web/web-service.d.ts.map +1 -1
- package/packages/mailx-store-web/web-service.js +53 -19
- package/packages/mailx-store-web/web-service.js.map +1 -1
- package/packages/mailx-store-web/web-service.ts +60 -20
- package/packages/mailx-types/index.d.ts +5 -1
- 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 +5 -1
- package/packages/mailx-types/package.json +1 -1
package/client/app.bundle.js
CHANGED
|
@@ -193,13 +193,13 @@ function getAccounts() {
|
|
|
193
193
|
function getFolders(accountId) {
|
|
194
194
|
return ipc().getFolders(accountId);
|
|
195
195
|
}
|
|
196
|
-
function getMessages(accountId, folderId, page = 1, pageSize = 50, flaggedOnly = false, sort, sortDir) {
|
|
196
|
+
function getMessages(accountId, folderId, page = 1, pageSize = 50, flaggedOnly = false, sort, sortDir, dateBasis2) {
|
|
197
197
|
abortMessageListRequests();
|
|
198
|
-
return ipc().getMessages(accountId, folderId, page, pageSize, sort, sortDir, void 0, flaggedOnly);
|
|
198
|
+
return ipc().getMessages(accountId, folderId, page, pageSize, sort, sortDir, void 0, flaggedOnly, dateBasis2);
|
|
199
199
|
}
|
|
200
|
-
function getUnifiedInbox(page = 1, pageSize = 50, flaggedOnly = false) {
|
|
200
|
+
function getUnifiedInbox(page = 1, pageSize = 50, flaggedOnly = false, dateBasis2) {
|
|
201
201
|
abortMessageListRequests();
|
|
202
|
-
return ipc().getUnifiedInbox(page, pageSize, flaggedOnly);
|
|
202
|
+
return ipc().getUnifiedInbox(page, pageSize, flaggedOnly, dateBasis2);
|
|
203
203
|
}
|
|
204
204
|
function searchMessages(query, page = 1, pageSize = 50, scope = "all", accountId = "", folderId = 0, includeTrashSpam = false) {
|
|
205
205
|
return ipc().searchMessages(query, page, pageSize, scope, accountId, folderId, includeTrashSpam);
|
|
@@ -888,6 +888,213 @@ var init_mailx_types = __esm({
|
|
|
888
888
|
}
|
|
889
889
|
});
|
|
890
890
|
|
|
891
|
+
// client/components/address-book.js
|
|
892
|
+
var address_book_exports = {};
|
|
893
|
+
__export(address_book_exports, {
|
|
894
|
+
openAddressBook: () => openAddressBook
|
|
895
|
+
});
|
|
896
|
+
async function openAddressBook(prefillSearch) {
|
|
897
|
+
if (isOpen)
|
|
898
|
+
return;
|
|
899
|
+
isOpen = true;
|
|
900
|
+
const backdrop = document.createElement("div");
|
|
901
|
+
backdrop.className = "mailx-modal-backdrop";
|
|
902
|
+
const panel = document.createElement("div");
|
|
903
|
+
panel.className = "mailx-modal mailx-modal-wide";
|
|
904
|
+
panel.innerHTML = `
|
|
905
|
+
<div class="mailx-modal-title">
|
|
906
|
+
<span class="mailx-modal-title-text">Address Book</span>
|
|
907
|
+
<button type="button" class="mailx-modal-close" id="ab-close" title="Close (Esc)" aria-label="Close">×</button>
|
|
908
|
+
</div>
|
|
909
|
+
<div class="ab-toolbar">
|
|
910
|
+
<input type="search" id="ab-search" class="mailx-modal-input" placeholder="Search name or email\u2026" autocomplete="off">
|
|
911
|
+
<button type="button" class="mailx-modal-btn mailx-modal-btn-primary" id="ab-new">+ New contact</button>
|
|
912
|
+
</div>
|
|
913
|
+
<div class="ab-count" id="ab-count"></div>
|
|
914
|
+
<div class="ab-list" id="ab-list">Loading\u2026</div>
|
|
915
|
+
<div class="mailx-modal-buttons">
|
|
916
|
+
<span class="mailx-modal-spacer"></span>
|
|
917
|
+
<button type="button" class="mailx-modal-btn" data-action="close">Close</button>
|
|
918
|
+
</div>`;
|
|
919
|
+
backdrop.appendChild(panel);
|
|
920
|
+
document.body.appendChild(backdrop);
|
|
921
|
+
const searchInput2 = panel.querySelector("#ab-search");
|
|
922
|
+
const listEl = panel.querySelector("#ab-list");
|
|
923
|
+
const countEl = panel.querySelector("#ab-count");
|
|
924
|
+
let editingEmail = null;
|
|
925
|
+
const render2 = (items, total) => {
|
|
926
|
+
countEl.textContent = total === items.length ? `${total} contact${total === 1 ? "" : "s"}` : `Showing ${items.length} of ${total}`;
|
|
927
|
+
if (items.length === 0) {
|
|
928
|
+
listEl.innerHTML = `<div class="ab-empty">No contacts match.</div>`;
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
const fmtDate = (ms) => {
|
|
932
|
+
if (!ms)
|
|
933
|
+
return "";
|
|
934
|
+
const d = new Date(ms);
|
|
935
|
+
const now = /* @__PURE__ */ new Date();
|
|
936
|
+
return d.getFullYear() === now.getFullYear() ? d.toLocaleDateString(void 0, { month: "short", day: "numeric" }) : d.toLocaleDateString();
|
|
937
|
+
};
|
|
938
|
+
listEl.innerHTML = `
|
|
939
|
+
<div class="ab-row ab-header">
|
|
940
|
+
<span class="ab-name">Name</span>
|
|
941
|
+
<span class="ab-email">Email</span>
|
|
942
|
+
<span class="ab-source">Source</span>
|
|
943
|
+
<span class="ab-count-cell" title="Times sent to">\xD7</span>
|
|
944
|
+
<span class="ab-last">Last</span>
|
|
945
|
+
<span class="ab-actions"></span>
|
|
946
|
+
</div>` + items.map((c) => `
|
|
947
|
+
<div class="ab-row" data-email="${escapeAttr(c.email)}">
|
|
948
|
+
<span class="ab-name">${escapeHtml(c.name || "")}</span>
|
|
949
|
+
<span class="ab-email">${escapeHtml(c.email)}</span>
|
|
950
|
+
<span class="ab-source">${escapeHtml(c.source)}</span>
|
|
951
|
+
<span class="ab-count-cell">${c.useCount || 0}</span>
|
|
952
|
+
<span class="ab-last">${fmtDate(c.lastUsed)}</span>
|
|
953
|
+
<span class="ab-actions">
|
|
954
|
+
<button type="button" class="ab-edit" title="Edit name">\u270E</button>
|
|
955
|
+
<button type="button" class="ab-del" title="Delete">\u{1F5D1}</button>
|
|
956
|
+
<button type="button" class="ab-mail" title="Compose to">\u2709</button>
|
|
957
|
+
</span>
|
|
958
|
+
</div>`).join("");
|
|
959
|
+
listEl.querySelectorAll(".ab-row[data-email]").forEach((row) => {
|
|
960
|
+
const email = row.dataset.email;
|
|
961
|
+
const c = items.find((x) => x.email === email);
|
|
962
|
+
row.querySelector(".ab-edit")?.addEventListener("click", () => beginEdit(row, c));
|
|
963
|
+
row.querySelector(".ab-del")?.addEventListener("click", () => doDelete(c));
|
|
964
|
+
row.querySelector(".ab-mail")?.addEventListener("click", () => composeTo(c));
|
|
965
|
+
});
|
|
966
|
+
};
|
|
967
|
+
const beginEdit = (row, c) => {
|
|
968
|
+
if (editingEmail === c.email)
|
|
969
|
+
return;
|
|
970
|
+
editingEmail = c.email;
|
|
971
|
+
const nameSpan = row.querySelector(".ab-name");
|
|
972
|
+
const original = c.name || "";
|
|
973
|
+
nameSpan.innerHTML = `<input type="text" value="${escapeAttr(original)}" class="ab-name-input">`;
|
|
974
|
+
const inp = nameSpan.querySelector("input");
|
|
975
|
+
inp.focus();
|
|
976
|
+
inp.select();
|
|
977
|
+
const commit = async () => {
|
|
978
|
+
const newName = inp.value.trim();
|
|
979
|
+
if (newName !== original) {
|
|
980
|
+
try {
|
|
981
|
+
await upsertContact(newName, c.email);
|
|
982
|
+
c.name = newName;
|
|
983
|
+
} catch (e) {
|
|
984
|
+
alert(`Update failed: ${e?.message || e}`);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
nameSpan.textContent = c.name || "";
|
|
988
|
+
editingEmail = null;
|
|
989
|
+
};
|
|
990
|
+
inp.addEventListener("blur", commit);
|
|
991
|
+
inp.addEventListener("keydown", (e) => {
|
|
992
|
+
if (e.key === "Enter") {
|
|
993
|
+
e.preventDefault();
|
|
994
|
+
inp.blur();
|
|
995
|
+
} else if (e.key === "Escape") {
|
|
996
|
+
e.preventDefault();
|
|
997
|
+
inp.value = original;
|
|
998
|
+
inp.blur();
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
};
|
|
1002
|
+
const doDelete = async (c) => {
|
|
1003
|
+
if (!confirm(`Delete contact "${c.name || c.email}"?`))
|
|
1004
|
+
return;
|
|
1005
|
+
try {
|
|
1006
|
+
await deleteContact(c.email);
|
|
1007
|
+
await reload();
|
|
1008
|
+
} catch (e) {
|
|
1009
|
+
alert(`Delete failed: ${e?.message || e}`);
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
const composeTo = (c) => {
|
|
1013
|
+
const init = {
|
|
1014
|
+
mode: "new",
|
|
1015
|
+
to: [{ name: c.name || "", address: c.email }],
|
|
1016
|
+
cc: [],
|
|
1017
|
+
bcc: [],
|
|
1018
|
+
subject: "",
|
|
1019
|
+
bodyHtml: "",
|
|
1020
|
+
inReplyTo: "",
|
|
1021
|
+
references: [],
|
|
1022
|
+
accounts: []
|
|
1023
|
+
};
|
|
1024
|
+
try {
|
|
1025
|
+
sessionStorage.setItem("composeInit", JSON.stringify(init));
|
|
1026
|
+
} catch {
|
|
1027
|
+
}
|
|
1028
|
+
document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "new" } }));
|
|
1029
|
+
close();
|
|
1030
|
+
};
|
|
1031
|
+
let reloadDebounce;
|
|
1032
|
+
const reload = async () => {
|
|
1033
|
+
try {
|
|
1034
|
+
const r = await listContacts(searchInput2.value, 1, 200);
|
|
1035
|
+
render2(r.items, r.total);
|
|
1036
|
+
} catch (e) {
|
|
1037
|
+
listEl.innerHTML = `<div class="ab-empty">Load failed: ${escapeHtml(e?.message || String(e))}</div>`;
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
const scheduleReload = () => {
|
|
1041
|
+
if (reloadDebounce)
|
|
1042
|
+
window.clearTimeout(reloadDebounce);
|
|
1043
|
+
reloadDebounce = window.setTimeout(reload, 200);
|
|
1044
|
+
};
|
|
1045
|
+
panel.querySelector("#ab-new")?.addEventListener("click", async () => {
|
|
1046
|
+
const email = prompt("Email address:");
|
|
1047
|
+
if (!email)
|
|
1048
|
+
return;
|
|
1049
|
+
const name = prompt("Display name (optional):") || "";
|
|
1050
|
+
try {
|
|
1051
|
+
await upsertContact(name, email.trim());
|
|
1052
|
+
await reload();
|
|
1053
|
+
} catch (e) {
|
|
1054
|
+
alert(`Add failed: ${e?.message || e}`);
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
searchInput2.addEventListener("input", scheduleReload);
|
|
1058
|
+
if (prefillSearch)
|
|
1059
|
+
searchInput2.value = prefillSearch;
|
|
1060
|
+
const close = () => {
|
|
1061
|
+
if (reloadDebounce)
|
|
1062
|
+
window.clearTimeout(reloadDebounce);
|
|
1063
|
+
backdrop.remove();
|
|
1064
|
+
document.removeEventListener("keydown", onKey, true);
|
|
1065
|
+
isOpen = false;
|
|
1066
|
+
};
|
|
1067
|
+
const onKey = (e) => {
|
|
1068
|
+
if (e.key === "Escape") {
|
|
1069
|
+
e.stopPropagation();
|
|
1070
|
+
e.preventDefault();
|
|
1071
|
+
close();
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
document.addEventListener("keydown", onKey, true);
|
|
1075
|
+
panel.querySelector("#ab-close").addEventListener("click", close);
|
|
1076
|
+
panel.querySelector('[data-action="close"]').addEventListener("click", close);
|
|
1077
|
+
backdrop.addEventListener("mousedown", (e) => {
|
|
1078
|
+
if (e.target === backdrop)
|
|
1079
|
+
close();
|
|
1080
|
+
});
|
|
1081
|
+
await reload();
|
|
1082
|
+
}
|
|
1083
|
+
function escapeHtml(s) {
|
|
1084
|
+
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
1085
|
+
}
|
|
1086
|
+
function escapeAttr(s) {
|
|
1087
|
+
return escapeHtml(s);
|
|
1088
|
+
}
|
|
1089
|
+
var isOpen;
|
|
1090
|
+
var init_address_book = __esm({
|
|
1091
|
+
"client/components/address-book.js"() {
|
|
1092
|
+
"use strict";
|
|
1093
|
+
init_api_client();
|
|
1094
|
+
isOpen = false;
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
|
|
891
1098
|
// client/components/message-viewer.js
|
|
892
1099
|
function parsedCacheGet(accountId, folderId, uid) {
|
|
893
1100
|
if (folderId == null)
|
|
@@ -1317,7 +1524,7 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
1317
1524
|
}
|
|
1318
1525
|
if (!cachedMsg) {
|
|
1319
1526
|
const previewText = (cached.preview || "").trim();
|
|
1320
|
-
bodyEl.innerHTML = previewText ? `<div class="mv-preview-placeholder">${
|
|
1527
|
+
bodyEl.innerHTML = previewText ? `<div class="mv-preview-placeholder">${escapeHtml2(previewText)}</div>` : `<div class="mv-empty">Loading body\u2026</div>`;
|
|
1321
1528
|
}
|
|
1322
1529
|
} else if (!cachedMsg) {
|
|
1323
1530
|
bodyEl.innerHTML = `<div class="mv-empty">Loading body\u2026</div>`;
|
|
@@ -1346,7 +1553,7 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
1346
1553
|
const previewText = (msg.preview || cached?.preview || "").trim();
|
|
1347
1554
|
const waitStart = Date.now();
|
|
1348
1555
|
const indicatorHtml = `<span class="mv-wait-elapsed" data-start="${waitStart}">(0s)</span>`;
|
|
1349
|
-
bodyEl.innerHTML = previewText ? `<div class="mv-preview-placeholder">${
|
|
1556
|
+
bodyEl.innerHTML = previewText ? `<div class="mv-preview-placeholder">${escapeHtml2(previewText)}<div class="mv-tear-line" aria-label="snippet ends here, full message loading"><span>\u2702 snippet \u2014 fetching full message \u2702</span></div><div class="mv-wait-line">Fetching body from server\u2026 ${indicatorHtml}</div></div>` : `<div class="mv-empty">Fetching body from server\u2026 ${indicatorHtml}</div>`;
|
|
1350
1557
|
const captureGen = gen;
|
|
1351
1558
|
const tick = setInterval(() => {
|
|
1352
1559
|
if (captureGen !== showMessageGeneration) {
|
|
@@ -1450,6 +1657,13 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
1450
1657
|
if (name) {
|
|
1451
1658
|
items.push({ label: `Copy both: ${both}`, action: () => navigator.clipboard.writeText(both) });
|
|
1452
1659
|
}
|
|
1660
|
+
items.push({
|
|
1661
|
+
label: `Open in contacts: ${addr.address}`,
|
|
1662
|
+
action: async () => {
|
|
1663
|
+
const { openAddressBook: openAddressBook2 } = await Promise.resolve().then(() => (init_address_book(), address_book_exports));
|
|
1664
|
+
await openAddressBook2(addr.address);
|
|
1665
|
+
}
|
|
1666
|
+
});
|
|
1453
1667
|
items.push({
|
|
1454
1668
|
label: `Add to contacts: ${addr.address}`,
|
|
1455
1669
|
action: async () => {
|
|
@@ -1828,7 +2042,7 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
1828
2042
|
bodyEl.innerHTML = `<div class="mv-system-message mv-system-error">
|
|
1829
2043
|
<div class="mv-system-tag">mailx</div>
|
|
1830
2044
|
<div class="mv-system-title">Body fetch failed</div>
|
|
1831
|
-
<div class="mv-system-body">${
|
|
2045
|
+
<div class="mv-system-body">${escapeHtml2(fetchErr.error)}<br><span style="color:var(--color-text-muted);font-size:0.9em">Recorded ${Math.round((Date.now() - fetchErr.when) / 1e3)}s ago. ${fetchErr.transient ? "Will retry automatically." : "Permanent \u2014 server-side delete may have raced."}</span></div>
|
|
1832
2046
|
</div>`;
|
|
1833
2047
|
} else {
|
|
1834
2048
|
const emlPath = msg.emlPath || "";
|
|
@@ -1837,8 +2051,8 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
1837
2051
|
const headline = attCount > 0 ? "This message has attachments but no body text." : "This message has no body text \u2014 only a subject.";
|
|
1838
2052
|
const crumbs = `mailx${appVer ? " " + appVer : ""} \xB7 ${accountId}/${uid}${emlPath ? " \xB7 " + emlPath : ""}`;
|
|
1839
2053
|
bodyEl.innerHTML = `<div class="mv-system-message">
|
|
1840
|
-
<div class="mv-system-body">${
|
|
1841
|
-
<div class="mv-system-body" style="color:var(--color-text-muted);font-size:0.8em;margin-top:8px">${
|
|
2054
|
+
<div class="mv-system-body">${escapeHtml2(headline)}</div>
|
|
2055
|
+
<div class="mv-system-body" style="color:var(--color-text-muted);font-size:0.8em;margin-top:8px">${escapeHtml2(crumbs)}</div>
|
|
1842
2056
|
</div>`;
|
|
1843
2057
|
}
|
|
1844
2058
|
}
|
|
@@ -2037,7 +2251,7 @@ function renderHeaderFromEnvelope(headerEl, env) {
|
|
|
2037
2251
|
}
|
|
2038
2252
|
}
|
|
2039
2253
|
}
|
|
2040
|
-
function
|
|
2254
|
+
function escapeHtml2(s) {
|
|
2041
2255
|
return (s || "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
2042
2256
|
}
|
|
2043
2257
|
function linkifyText(text) {
|
|
@@ -2791,6 +3005,7 @@ __export(message_list_exports, {
|
|
|
2791
3005
|
clearSearchMode: () => clearSearchMode,
|
|
2792
3006
|
exitMultiSelect: () => exitMultiSelect,
|
|
2793
3007
|
getCurrentFocused: () => getCurrentFocused,
|
|
3008
|
+
getDateBasis: () => getDateBasis,
|
|
2794
3009
|
getSelectedMessages: () => getSelectedMessages,
|
|
2795
3010
|
initMessageList: () => initMessageList,
|
|
2796
3011
|
loadMessages: () => loadMessages,
|
|
@@ -2804,6 +3019,7 @@ __export(message_list_exports, {
|
|
|
2804
3019
|
revealMessage: () => revealMessage,
|
|
2805
3020
|
scrollFocusedIntoView: () => scrollFocusedIntoView,
|
|
2806
3021
|
selectAllVisible: () => selectAllVisible,
|
|
3022
|
+
setDateBasis: () => setDateBasis,
|
|
2807
3023
|
setLiveFilter: () => setLiveFilter,
|
|
2808
3024
|
setRowFlagged: () => setRowFlagged,
|
|
2809
3025
|
setRowSeen: () => setRowSeen,
|
|
@@ -2834,6 +3050,23 @@ function emptyListMessage() {
|
|
|
2834
3050
|
}
|
|
2835
3051
|
return "No messages";
|
|
2836
3052
|
}
|
|
3053
|
+
function effectiveDate(msg) {
|
|
3054
|
+
return dateBasis === "received" ? msg.date : msg.sentDate ?? msg.date;
|
|
3055
|
+
}
|
|
3056
|
+
function getDateBasis() {
|
|
3057
|
+
return dateBasis;
|
|
3058
|
+
}
|
|
3059
|
+
function setDateBasis(basis) {
|
|
3060
|
+
if (basis === dateBasis)
|
|
3061
|
+
return;
|
|
3062
|
+
dateBasis = basis;
|
|
3063
|
+
try {
|
|
3064
|
+
localStorage.setItem("mailx-date-basis", basis);
|
|
3065
|
+
} catch {
|
|
3066
|
+
}
|
|
3067
|
+
listCache.clear();
|
|
3068
|
+
reloadCurrentFolder();
|
|
3069
|
+
}
|
|
2837
3070
|
function cacheKey(mode, a, f, flagged, q) {
|
|
2838
3071
|
if (mode === "folder")
|
|
2839
3072
|
return `folder:${a}:${f}:${flagged ? "flag" : ""}`;
|
|
@@ -2864,12 +3097,14 @@ function withScrollAnchor(body, doRender) {
|
|
|
2864
3097
|
const wasAtTop = scrollTop < 4;
|
|
2865
3098
|
let anchorUuid = "";
|
|
2866
3099
|
let anchorOffsetWithinViewport = 0;
|
|
3100
|
+
let anchoredOnSelected = false;
|
|
2867
3101
|
const selected = body.querySelector(".ml-row.selected");
|
|
2868
3102
|
if (selected) {
|
|
2869
3103
|
const vt = selected.offsetTop - scrollTop;
|
|
2870
3104
|
if (vt > -selected.offsetHeight && vt < viewportH) {
|
|
2871
3105
|
anchorUuid = selected.dataset.uuid || "";
|
|
2872
3106
|
anchorOffsetWithinViewport = vt;
|
|
3107
|
+
anchoredOnSelected = true;
|
|
2873
3108
|
}
|
|
2874
3109
|
}
|
|
2875
3110
|
if (!anchorUuid) {
|
|
@@ -2892,7 +3127,7 @@ function withScrollAnchor(body, doRender) {
|
|
|
2892
3127
|
return;
|
|
2893
3128
|
const after = body.querySelector(`.ml-row[data-uuid="${CSS.escape(anchorUuid)}"]`);
|
|
2894
3129
|
if (after) {
|
|
2895
|
-
if (wasAtTop && after.offsetTop < viewportH - after.offsetHeight) {
|
|
3130
|
+
if (wasAtTop && !anchoredOnSelected && after.offsetTop < viewportH - after.offsetHeight) {
|
|
2896
3131
|
body.scrollTop = 0;
|
|
2897
3132
|
} else {
|
|
2898
3133
|
body.scrollTop = after.offsetTop - anchorOffsetWithinViewport;
|
|
@@ -2915,7 +3150,7 @@ function rememberPosition() {
|
|
|
2915
3150
|
positionMemory.set(key, { uid, uuid: sel.dataset.uuid || "", scroll: body.scrollTop });
|
|
2916
3151
|
persistPositions();
|
|
2917
3152
|
}
|
|
2918
|
-
function pickRestoreUid(items, saved) {
|
|
3153
|
+
function pickRestoreUid(items, saved, allowNeighbor = true) {
|
|
2919
3154
|
if (!items.length)
|
|
2920
3155
|
return null;
|
|
2921
3156
|
if (saved.uuid) {
|
|
@@ -2923,6 +3158,8 @@ function pickRestoreUid(items, saved) {
|
|
|
2923
3158
|
if (exact?.uuid)
|
|
2924
3159
|
return exact.uuid;
|
|
2925
3160
|
}
|
|
3161
|
+
if (!allowNeighbor)
|
|
3162
|
+
return null;
|
|
2926
3163
|
let best = null;
|
|
2927
3164
|
for (const m of items) {
|
|
2928
3165
|
if (typeof m.uid !== "number" || !m.uuid)
|
|
@@ -3265,6 +3502,24 @@ function exitMultiSelect() {
|
|
|
3265
3502
|
updateBulkBar();
|
|
3266
3503
|
}
|
|
3267
3504
|
function updateBulkBar() {
|
|
3505
|
+
const panel = document.getElementById("message-list");
|
|
3506
|
+
if (!panel)
|
|
3507
|
+
return;
|
|
3508
|
+
const body = document.getElementById("ml-body");
|
|
3509
|
+
const count = body?.classList.contains("multi-select-on") ? body.querySelectorAll(".ml-row.selected").length : 0;
|
|
3510
|
+
let pill = document.getElementById("ml-select-count");
|
|
3511
|
+
if (count >= 2) {
|
|
3512
|
+
if (!pill) {
|
|
3513
|
+
pill = document.createElement("div");
|
|
3514
|
+
pill.id = "ml-select-count";
|
|
3515
|
+
pill.className = "ml-select-count";
|
|
3516
|
+
panel.appendChild(pill);
|
|
3517
|
+
}
|
|
3518
|
+
pill.textContent = `${count} selected`;
|
|
3519
|
+
pill.style.display = "";
|
|
3520
|
+
} else if (pill) {
|
|
3521
|
+
pill.style.display = "none";
|
|
3522
|
+
}
|
|
3268
3523
|
}
|
|
3269
3524
|
function selectAllVisible() {
|
|
3270
3525
|
const body = document.getElementById("ml-body");
|
|
@@ -3325,22 +3580,7 @@ function initMessageList(handler) {
|
|
|
3325
3580
|
}
|
|
3326
3581
|
}, { passive: true });
|
|
3327
3582
|
body.addEventListener("scroll", () => {
|
|
3328
|
-
|
|
3329
|
-
return;
|
|
3330
|
-
const nearBottom = body.scrollHeight - body.scrollTop - body.clientHeight < 1e3;
|
|
3331
|
-
if (nearBottom) {
|
|
3332
|
-
if (currentPage * 50 < totalMessages) {
|
|
3333
|
-
loadMoreMessages();
|
|
3334
|
-
} else {
|
|
3335
|
-
if (!body._mlScrollEndLogged) {
|
|
3336
|
-
body._mlScrollEndLogged = true;
|
|
3337
|
-
const rows = body.querySelectorAll(".ml-row").length;
|
|
3338
|
-
console.log(` [ml-scroll] reached bottom \u2014 currentPage=${currentPage} pageSize=50 loadedRows=${rows} totalMessages=${totalMessages} searchMode=${searchMode} unifiedMode=${unifiedMode} flaggedOnly=${body.classList.contains("flagged-only")}`);
|
|
3339
|
-
}
|
|
3340
|
-
}
|
|
3341
|
-
} else {
|
|
3342
|
-
body._mlScrollEndLogged = false;
|
|
3343
|
-
}
|
|
3583
|
+
maybeLoadMore(body);
|
|
3344
3584
|
});
|
|
3345
3585
|
}
|
|
3346
3586
|
document.addEventListener("mailx-remove-stale", (e) => {
|
|
@@ -3484,7 +3724,7 @@ async function loadUnifiedInbox(autoSelect = true) {
|
|
|
3484
3724
|
withScrollAnchor(body, renderCached);
|
|
3485
3725
|
else
|
|
3486
3726
|
renderCached();
|
|
3487
|
-
const targetUuid = remembered ? pickRestoreUid(cached.items, remembered) : null;
|
|
3727
|
+
const targetUuid = remembered ? pickRestoreUid(cached.items, remembered, autoSelect) : null;
|
|
3488
3728
|
if (targetUuid) {
|
|
3489
3729
|
if (autoSelect)
|
|
3490
3730
|
body.scrollTop = savedScroll;
|
|
@@ -3496,7 +3736,7 @@ async function loadUnifiedInbox(autoSelect = true) {
|
|
|
3496
3736
|
body.innerHTML = `<div class="ml-empty">Loading...</div>`;
|
|
3497
3737
|
}
|
|
3498
3738
|
try {
|
|
3499
|
-
const result = await getUnifiedInbox(1, 50, flaggedOnly);
|
|
3739
|
+
const result = await getUnifiedInbox(1, 50, flaggedOnly, dateBasis);
|
|
3500
3740
|
if (myGen !== loadGen)
|
|
3501
3741
|
return;
|
|
3502
3742
|
totalMessages = result.total;
|
|
@@ -3510,7 +3750,7 @@ async function loadUnifiedInbox(autoSelect = true) {
|
|
|
3510
3750
|
}
|
|
3511
3751
|
setMessages(result.items);
|
|
3512
3752
|
withScrollAnchor(body, () => renderMessages(body, "", result.items, { background: !autoSelect }));
|
|
3513
|
-
const targetUuid = remembered ? pickRestoreUid(result.items, remembered) : null;
|
|
3753
|
+
const targetUuid = remembered ? pickRestoreUid(result.items, remembered, autoSelect) : null;
|
|
3514
3754
|
if (targetUuid) {
|
|
3515
3755
|
if (!cached)
|
|
3516
3756
|
body.scrollTop = savedScroll;
|
|
@@ -3564,7 +3804,7 @@ async function loadSearchResults(query, scope = "all", accountId = "", folderId
|
|
|
3564
3804
|
body.innerHTML = `<div class="ml-empty">Invalid regex</div>`;
|
|
3565
3805
|
return;
|
|
3566
3806
|
}
|
|
3567
|
-
const source = scope === "current" && accountId ? await getMessages(accountId, folderId, 1, 1e4) : await getUnifiedInbox(1, 1e4);
|
|
3807
|
+
const source = scope === "current" && accountId ? await getMessages(accountId, folderId, 1, 1e4, false, currentSort, currentSortDir, dateBasis) : await getUnifiedInbox(1, 1e4, false, dateBasis);
|
|
3568
3808
|
if (myGen !== loadGen)
|
|
3569
3809
|
return;
|
|
3570
3810
|
const matches = source.items.filter((m) => regex.test(m.subject || "") || regex.test(m.from?.name || "") || regex.test(m.from?.address || "") || regex.test(m.preview || ""));
|
|
@@ -3637,7 +3877,7 @@ async function loadMessages(accountId, folderId, page = 1, specialUse = "", auto
|
|
|
3637
3877
|
withScrollAnchor(body, renderCached);
|
|
3638
3878
|
else
|
|
3639
3879
|
renderCached();
|
|
3640
|
-
const targetUuid = remembered ? pickRestoreUid(cached.items, remembered) : null;
|
|
3880
|
+
const targetUuid = remembered ? pickRestoreUid(cached.items, remembered, autoSelect) : null;
|
|
3641
3881
|
if (focusUid != null) {
|
|
3642
3882
|
requestAnimationFrame(() => {
|
|
3643
3883
|
if (!focusByIdentity(accountId, focusUid, { scroll: true }) && autoSelect)
|
|
@@ -3656,7 +3896,7 @@ async function loadMessages(accountId, folderId, page = 1, specialUse = "", auto
|
|
|
3656
3896
|
body.innerHTML = `<div class="ml-empty">Loading...</div>`;
|
|
3657
3897
|
}
|
|
3658
3898
|
try {
|
|
3659
|
-
const result = await getMessages(accountId, folderId, 1, 50, flaggedOnly, currentSort, currentSortDir);
|
|
3899
|
+
const result = await getMessages(accountId, folderId, 1, 50, flaggedOnly, currentSort, currentSortDir, dateBasis);
|
|
3660
3900
|
if (myGen !== loadGen)
|
|
3661
3901
|
return;
|
|
3662
3902
|
totalMessages = result.total;
|
|
@@ -3680,7 +3920,7 @@ async function loadMessages(accountId, folderId, page = 1, specialUse = "", auto
|
|
|
3680
3920
|
focusedRow = null;
|
|
3681
3921
|
}
|
|
3682
3922
|
}
|
|
3683
|
-
const targetUuid = remembered ? pickRestoreUid(result.items, remembered) : null;
|
|
3923
|
+
const targetUuid = remembered ? pickRestoreUid(result.items, remembered, autoSelect) : null;
|
|
3684
3924
|
if (focusUid != null) {
|
|
3685
3925
|
requestAnimationFrame(() => {
|
|
3686
3926
|
if (myGen !== loadGen)
|
|
@@ -3707,6 +3947,22 @@ async function loadMessages(accountId, folderId, page = 1, specialUse = "", auto
|
|
|
3707
3947
|
body.innerHTML = `<div class="ml-empty">Error: ${e.message}</div>`;
|
|
3708
3948
|
}
|
|
3709
3949
|
}
|
|
3950
|
+
function maybeLoadMore(body) {
|
|
3951
|
+
if (loading)
|
|
3952
|
+
return;
|
|
3953
|
+
const nearBottom = body.scrollHeight - body.scrollTop - body.clientHeight < 1e3;
|
|
3954
|
+
if (!nearBottom) {
|
|
3955
|
+
body._mlScrollEndLogged = false;
|
|
3956
|
+
return;
|
|
3957
|
+
}
|
|
3958
|
+
if (currentPage * 50 < totalMessages) {
|
|
3959
|
+
loadMoreMessages();
|
|
3960
|
+
} else if (!body._mlScrollEndLogged) {
|
|
3961
|
+
body._mlScrollEndLogged = true;
|
|
3962
|
+
const rows = body.querySelectorAll(".ml-row").length;
|
|
3963
|
+
console.log(` [ml-scroll] reached bottom \u2014 currentPage=${currentPage} pageSize=50 loadedRows=${rows} totalMessages=${totalMessages} searchMode=${searchMode} unifiedMode=${unifiedMode} flaggedOnly=${body.classList.contains("flagged-only")}`);
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3710
3966
|
async function loadMoreMessages() {
|
|
3711
3967
|
const body = document.getElementById("ml-body");
|
|
3712
3968
|
if (!body)
|
|
@@ -3718,7 +3974,7 @@ async function loadMoreMessages() {
|
|
|
3718
3974
|
const rowsBefore = body.querySelectorAll(".ml-row").length;
|
|
3719
3975
|
try {
|
|
3720
3976
|
const flaggedOnly = body.classList.contains("flagged-only");
|
|
3721
|
-
const result = searchMode ? await searchMessages(currentSearchQuery, currentPage) : unifiedMode ? await getUnifiedInbox(currentPage, 50, flaggedOnly) : await getMessages(currentAccountId2, currentFolderId, currentPage, 50, flaggedOnly);
|
|
3977
|
+
const result = searchMode ? await searchMessages(currentSearchQuery, currentPage) : unifiedMode ? await getUnifiedInbox(currentPage, 50, flaggedOnly, dateBasis) : await getMessages(currentAccountId2, currentFolderId, currentPage, 50, flaggedOnly, currentSort, currentSortDir, dateBasis);
|
|
3722
3978
|
const current = getMessages2();
|
|
3723
3979
|
setMessages([...current, ...result.items]);
|
|
3724
3980
|
appendMessages(body, unifiedMode ? "" : currentAccountId2, result.items);
|
|
@@ -3732,6 +3988,7 @@ async function loadMoreMessages() {
|
|
|
3732
3988
|
} finally {
|
|
3733
3989
|
loading = false;
|
|
3734
3990
|
}
|
|
3991
|
+
requestAnimationFrame(() => maybeLoadMore(body));
|
|
3735
3992
|
}
|
|
3736
3993
|
function renderMessages(body, accountId, items, opts = {}) {
|
|
3737
3994
|
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;
|
|
@@ -3745,6 +4002,7 @@ function renderMessages(body, accountId, items, opts = {}) {
|
|
|
3745
4002
|
for (const uuid of preserveSel) {
|
|
3746
4003
|
body.querySelector(`.ml-row[data-uuid="${CSS.escape(uuid)}"]`)?.classList.add("selected");
|
|
3747
4004
|
}
|
|
4005
|
+
updateBulkBar();
|
|
3748
4006
|
}
|
|
3749
4007
|
try {
|
|
3750
4008
|
maybeShowFilterEmptyState(body);
|
|
@@ -3778,7 +4036,8 @@ function restoreSelection(body, savedUuid) {
|
|
|
3778
4036
|
const accountId = row.dataset.accountId;
|
|
3779
4037
|
const uid = Number(row.dataset.uid);
|
|
3780
4038
|
if (accountId && Number.isFinite(uid)) {
|
|
3781
|
-
|
|
4039
|
+
const switching = focusedRow?.msg?.uuid !== savedUuid;
|
|
4040
|
+
focusByIdentity(accountId, uid, { scroll: switching });
|
|
3782
4041
|
}
|
|
3783
4042
|
}
|
|
3784
4043
|
async function showThreadPopup(pillEl, headMsg) {
|
|
@@ -3803,7 +4062,7 @@ async function showThreadPopup(pillEl, headMsg) {
|
|
|
3803
4062
|
from.textContent = msg.from?.name || msg.from?.address || "?";
|
|
3804
4063
|
const date = document.createElement("span");
|
|
3805
4064
|
date.className = "ml-thread-popup-date";
|
|
3806
|
-
date.textContent = formatDate(msg
|
|
4065
|
+
date.textContent = formatDate(effectiveDate(msg));
|
|
3807
4066
|
const subject = document.createElement("span");
|
|
3808
4067
|
subject.className = "ml-thread-popup-subject";
|
|
3809
4068
|
subject.textContent = msg.subject || "(no subject)";
|
|
@@ -3927,12 +4186,12 @@ function formatDate(epochMs) {
|
|
|
3927
4186
|
return d.toLocaleString(void 0, dateFmtSameYear);
|
|
3928
4187
|
return d.toLocaleString(void 0, dateFmt);
|
|
3929
4188
|
}
|
|
3930
|
-
function
|
|
4189
|
+
function escapeHtml3(s) {
|
|
3931
4190
|
const div = document.createElement("div");
|
|
3932
4191
|
div.textContent = s;
|
|
3933
4192
|
return div.innerHTML;
|
|
3934
4193
|
}
|
|
3935
|
-
var onMessageSelect, currentAccountId2, currentFolderId, currentSpecialUse, lastClickedRow, currentPage, totalMessages, loading, unifiedMode, searchMode, liveFilterText, currentSearchQuery, wasUnifiedBeforeSearch, showToInsteadOfFrom, touchWasScroll, lastPointerWasTouch, currentSort, currentSortDir, loadGen, listCache, CACHE_KEY_UNIFIED, positionMemory, POSITION_STORAGE_KEY, focusedRow, rowByKey, prioritySenders, priorityDomains, timeFmt, dateFmt, dateFmtSameYear, MessageRow;
|
|
4194
|
+
var onMessageSelect, currentAccountId2, currentFolderId, currentSpecialUse, lastClickedRow, currentPage, totalMessages, loading, unifiedMode, searchMode, liveFilterText, currentSearchQuery, wasUnifiedBeforeSearch, showToInsteadOfFrom, touchWasScroll, lastPointerWasTouch, currentSort, currentSortDir, dateBasis, loadGen, listCache, CACHE_KEY_UNIFIED, positionMemory, POSITION_STORAGE_KEY, focusedRow, rowByKey, prioritySenders, priorityDomains, timeFmt, dateFmt, dateFmtSameYear, MessageRow;
|
|
3936
4195
|
var init_message_list = __esm({
|
|
3937
4196
|
"client/components/message-list.js"() {
|
|
3938
4197
|
"use strict";
|
|
@@ -3960,6 +4219,7 @@ var init_message_list = __esm({
|
|
|
3960
4219
|
}
|
|
3961
4220
|
currentSort = "date";
|
|
3962
4221
|
currentSortDir = "desc";
|
|
4222
|
+
dateBasis = localStorage.getItem("mailx-date-basis") || "sent";
|
|
3963
4223
|
loadGen = 0;
|
|
3964
4224
|
listCache = /* @__PURE__ */ new Map();
|
|
3965
4225
|
CACHE_KEY_UNIFIED = "unified";
|
|
@@ -4078,7 +4338,7 @@ var init_message_list = __esm({
|
|
|
4078
4338
|
}
|
|
4079
4339
|
const subject = document.createElement("span");
|
|
4080
4340
|
subject.className = "ml-subject";
|
|
4081
|
-
subject.innerHTML =
|
|
4341
|
+
subject.innerHTML = escapeHtml3(msg.subject);
|
|
4082
4342
|
if (threadHead && threadCount > 1 && msg.threadId) {
|
|
4083
4343
|
const threadPill = document.createElement("span");
|
|
4084
4344
|
threadPill.className = "ml-thread-pill";
|
|
@@ -4104,7 +4364,8 @@ var init_message_list = __esm({
|
|
|
4104
4364
|
renderStatusIcons(icons, msg);
|
|
4105
4365
|
const dateText = document.createElement("span");
|
|
4106
4366
|
dateText.className = "ml-date-text";
|
|
4107
|
-
dateText.textContent = formatDate(msg
|
|
4367
|
+
dateText.textContent = formatDate(effectiveDate(msg));
|
|
4368
|
+
dateText.title = dateBasis === "received" ? "Received date" : "Sent date";
|
|
4108
4369
|
date.appendChild(icons);
|
|
4109
4370
|
date.appendChild(dateText);
|
|
4110
4371
|
row.appendChild(avatar);
|
|
@@ -4304,6 +4565,13 @@ var init_message_list = __esm({
|
|
|
4304
4565
|
if (e.ctrlKey || e.metaKey) {
|
|
4305
4566
|
this.setSelected(!this.isSelected);
|
|
4306
4567
|
lastClickedRow = this.el;
|
|
4568
|
+
if (body) {
|
|
4569
|
+
const n = body.querySelectorAll(".ml-row.selected").length;
|
|
4570
|
+
if (n >= 1)
|
|
4571
|
+
body.classList.add("multi-select-on");
|
|
4572
|
+
else
|
|
4573
|
+
body.classList.remove("multi-select-on");
|
|
4574
|
+
}
|
|
4307
4575
|
} else {
|
|
4308
4576
|
clearSelection();
|
|
4309
4577
|
focusRow(this);
|
|
@@ -4507,9 +4775,9 @@ __export(outbox_view_exports, {
|
|
|
4507
4775
|
openOutboxView: () => openOutboxView
|
|
4508
4776
|
});
|
|
4509
4777
|
async function openOutboxView() {
|
|
4510
|
-
if (
|
|
4778
|
+
if (isOpen2)
|
|
4511
4779
|
return;
|
|
4512
|
-
|
|
4780
|
+
isOpen2 = true;
|
|
4513
4781
|
const backdrop = document.createElement("div");
|
|
4514
4782
|
backdrop.className = "mailx-modal-backdrop";
|
|
4515
4783
|
const panel = document.createElement("div");
|
|
@@ -4550,19 +4818,19 @@ async function openOutboxView() {
|
|
|
4550
4818
|
return `
|
|
4551
4819
|
<div class="ob-row ob-pink" data-idx="${i}">
|
|
4552
4820
|
<div class="ob-row-hdr">
|
|
4553
|
-
<span class="ob-acct">${
|
|
4554
|
-
<span class="ob-subject">${
|
|
4821
|
+
<span class="ob-acct">${escapeHtml4(m.accountId)}</span>
|
|
4822
|
+
<span class="ob-subject">${escapeHtml4(m.subject || "(no subject)")}</span>
|
|
4555
4823
|
<span class="ob-created">${fmtDate(m.createdAt)}</span>
|
|
4556
4824
|
${claimBadge}
|
|
4557
4825
|
${m.attempts > 0 ? `<span class="ob-badge ob-retry" title="Retry attempts made so far">retry \xD7${m.attempts}</span>` : ""}
|
|
4558
4826
|
</div>
|
|
4559
4827
|
<div class="ob-row-meta">
|
|
4560
|
-
<span class="ob-from">${
|
|
4561
|
-
\u2192 <span class="ob-to">${
|
|
4562
|
-
${m.cc ? ` \xB7 Cc: ${
|
|
4828
|
+
<span class="ob-from">${escapeHtml4(m.from || "")}</span>
|
|
4829
|
+
\u2192 <span class="ob-to">${escapeHtml4(m.to || "")}</span>
|
|
4830
|
+
${m.cc ? ` \xB7 Cc: ${escapeHtml4(m.cc)}` : ""}
|
|
4563
4831
|
<span class="ob-size">\xB7 ${(m.sizeBytes / 1024).toFixed(1)}kB</span>
|
|
4564
4832
|
</div>
|
|
4565
|
-
<div class="ob-row-path">${
|
|
4833
|
+
<div class="ob-row-path">${escapeHtml4(m.path)}</div>
|
|
4566
4834
|
<div class="ob-row-actions">
|
|
4567
4835
|
<button type="button" class="ob-cancel">Cancel</button>
|
|
4568
4836
|
</div>
|
|
@@ -4598,13 +4866,13 @@ Subject: ${m.subject}`;
|
|
|
4598
4866
|
const items = await listQueuedOutgoing();
|
|
4599
4867
|
renderList(items || []);
|
|
4600
4868
|
} catch (e) {
|
|
4601
|
-
listEl.innerHTML = `<div class="ob-empty">Load failed: ${
|
|
4869
|
+
listEl.innerHTML = `<div class="ob-empty">Load failed: ${escapeHtml4(e?.message || String(e))}</div>`;
|
|
4602
4870
|
}
|
|
4603
4871
|
};
|
|
4604
4872
|
const close = () => {
|
|
4605
4873
|
backdrop.remove();
|
|
4606
4874
|
document.removeEventListener("keydown", onKey, true);
|
|
4607
|
-
|
|
4875
|
+
isOpen2 = false;
|
|
4608
4876
|
};
|
|
4609
4877
|
const onKey = (e) => {
|
|
4610
4878
|
if (e.key === "Escape") {
|
|
@@ -4623,15 +4891,15 @@ Subject: ${m.subject}`;
|
|
|
4623
4891
|
});
|
|
4624
4892
|
await reload();
|
|
4625
4893
|
}
|
|
4626
|
-
function
|
|
4894
|
+
function escapeHtml4(s) {
|
|
4627
4895
|
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
4628
4896
|
}
|
|
4629
|
-
var
|
|
4897
|
+
var isOpen2;
|
|
4630
4898
|
var init_outbox_view = __esm({
|
|
4631
4899
|
"client/components/outbox-view.js"() {
|
|
4632
4900
|
"use strict";
|
|
4633
4901
|
init_api_client();
|
|
4634
|
-
|
|
4902
|
+
isOpen2 = false;
|
|
4635
4903
|
}
|
|
4636
4904
|
});
|
|
4637
4905
|
|
|
@@ -4662,7 +4930,7 @@ function calendarKind(id, primary) {
|
|
|
4662
4930
|
}
|
|
4663
4931
|
function calIconHtml(info) {
|
|
4664
4932
|
const kind = calendarKind(info.id, info.primary);
|
|
4665
|
-
const t =
|
|
4933
|
+
const t = escapeHtml5(info.name);
|
|
4666
4934
|
if (kind === "personal")
|
|
4667
4935
|
return `<span class="cal-ico cal-ico-dot" title="${t}"></span>`;
|
|
4668
4936
|
if (kind === "usHoliday")
|
|
@@ -4673,9 +4941,9 @@ function calIconHtml(info) {
|
|
|
4673
4941
|
return `<span class="cal-ico cal-ico-emoji" title="${t}">\u{1F382}</span>`;
|
|
4674
4942
|
if (kind === "otherHoliday")
|
|
4675
4943
|
return `<span class="cal-ico cal-ico-emoji" title="${t}">\u2726</span>`;
|
|
4676
|
-
const letter =
|
|
4944
|
+
const letter = escapeHtml5((info.name.trim()[0] || "?").toUpperCase());
|
|
4677
4945
|
const color = info.color || "#7a7a7a";
|
|
4678
|
-
return `<span class="cal-ico cal-ico-mono" style="background:${
|
|
4946
|
+
return `<span class="cal-ico cal-ico-mono" style="background:${escapeHtml5(color)}" title="${t}">${letter}</span>`;
|
|
4679
4947
|
}
|
|
4680
4948
|
function calInfoFor(calendarId) {
|
|
4681
4949
|
const id = calendarId || "primary";
|
|
@@ -4724,10 +4992,10 @@ async function renderCalendarList() {
|
|
|
4724
4992
|
host.innerHTML = "";
|
|
4725
4993
|
} else {
|
|
4726
4994
|
const sorted = [...list].sort((a, b) => (a.primary ? 0 : 1) - (b.primary ? 0 : 1) || a.name.localeCompare(b.name));
|
|
4727
|
-
host.innerHTML = sorted.map((c) => `<label class="cal-side-cal-row" title="${
|
|
4728
|
-
<input type="checkbox" class="cal-side-cal-check" data-cal-id="${
|
|
4995
|
+
host.innerHTML = sorted.map((c) => `<label class="cal-side-cal-row" title="${escapeHtml5(c.name)}">
|
|
4996
|
+
<input type="checkbox" class="cal-side-cal-check" data-cal-id="${escapeHtml5(c.id)}" ${hiddenCalendars.has(c.id) ? "" : "checked"}>
|
|
4729
4997
|
${calIconHtml(c)}
|
|
4730
|
-
<span class="cal-side-cal-name">${
|
|
4998
|
+
<span class="cal-side-cal-name">${escapeHtml5(c.name)}</span>
|
|
4731
4999
|
</label>`).join("");
|
|
4732
5000
|
host.querySelectorAll(".cal-side-cal-check").forEach((cb) => {
|
|
4733
5001
|
cb.addEventListener("change", async () => {
|
|
@@ -4811,7 +5079,7 @@ function formatTime(e) {
|
|
|
4811
5079
|
return "all day";
|
|
4812
5080
|
return new Date(e.start).toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit", hour12: false });
|
|
4813
5081
|
}
|
|
4814
|
-
function
|
|
5082
|
+
function escapeHtml5(s) {
|
|
4815
5083
|
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
4816
5084
|
}
|
|
4817
5085
|
function renderHead() {
|
|
@@ -4828,10 +5096,10 @@ function overdueTaskRowHtml(t) {
|
|
|
4828
5096
|
const sameYear = d.getFullYear() === (/* @__PURE__ */ new Date()).getFullYear();
|
|
4829
5097
|
dueLabel = sameYear ? `${d.getMonth() + 1}/${d.getDate()}` : d.toISOString().slice(0, 10);
|
|
4830
5098
|
}
|
|
4831
|
-
return `<div class="cal-side-task cal-side-task-overdue" data-uuid="${
|
|
5099
|
+
return `<div class="cal-side-task cal-side-task-overdue" data-uuid="${escapeHtml5(t.uuid)}">
|
|
4832
5100
|
<input type="checkbox" class="cal-side-overdue-check" title="Mark done">
|
|
4833
|
-
<span class="cal-side-task-title" title="${
|
|
4834
|
-
<span class="cal-side-task-due overdue">${
|
|
5101
|
+
<span class="cal-side-task-title" title="${escapeHtml5(t.title)}">${escapeHtml5(t.title)}</span>
|
|
5102
|
+
<span class="cal-side-task-due overdue">${escapeHtml5(dueLabel)}</span>
|
|
4835
5103
|
</div>`;
|
|
4836
5104
|
}
|
|
4837
5105
|
function renderEvents(events) {
|
|
@@ -4888,10 +5156,10 @@ function renderEvents(events) {
|
|
|
4888
5156
|
html += `<div class="cal-side-day cal-side-day-daily">Daily</div>`;
|
|
4889
5157
|
for (const e of dailyHeads) {
|
|
4890
5158
|
const link = e.htmlLink || "";
|
|
4891
|
-
html += `<div class="cal-side-event" data-id="${e.id}" data-link="${
|
|
5159
|
+
html += `<div class="cal-side-event" data-id="${e.id}" data-link="${escapeHtml5(link)}" ${link ? 'title="Click to open in Google Calendar"' : ""}>
|
|
4892
5160
|
${calIconHtml(calInfoFor(e.calendarId))}
|
|
4893
|
-
<span class="cal-side-event-time">${
|
|
4894
|
-
<span class="cal-side-event-title" title="${
|
|
5161
|
+
<span class="cal-side-event-time">${escapeHtml5(formatTime(e))}</span>
|
|
5162
|
+
<span class="cal-side-event-title" title="${escapeHtml5(e.title)}">${escapeHtml5(e.title)}<span class="cal-side-event-recur" title="Daily">\u21BB</span></span>
|
|
4895
5163
|
</div>`;
|
|
4896
5164
|
}
|
|
4897
5165
|
}
|
|
@@ -4909,7 +5177,7 @@ function renderEvents(events) {
|
|
|
4909
5177
|
const d = new Date(e.start);
|
|
4910
5178
|
const dayKey = `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
|
|
4911
5179
|
if (dayKey !== lastDayKey) {
|
|
4912
|
-
html += `<div class="cal-side-day">${
|
|
5180
|
+
html += `<div class="cal-side-day">${escapeHtml5(formatDayHeader(d, today, tomorrow))}</div>`;
|
|
4913
5181
|
lastDayKey = dayKey;
|
|
4914
5182
|
}
|
|
4915
5183
|
const recurMark = e.recurringEventId ? `<span class="cal-side-event-recur" title="Recurring event">\u21BB</span>` : "";
|
|
@@ -4917,14 +5185,14 @@ function renderEvents(events) {
|
|
|
4917
5185
|
const recurAttr = e.recurringEventId ? ' data-recurring="1"' : "";
|
|
4918
5186
|
if (isHolidayKind || kind === "birthday") {
|
|
4919
5187
|
html += `<div class="cal-side-event" data-holiday="1" data-holiday-kind="${kind}" data-id="${e.id}">
|
|
4920
|
-
<span class="cal-side-event-title cal-side-event-holiday-title" title="${
|
|
5188
|
+
<span class="cal-side-event-title cal-side-event-holiday-title" title="${escapeHtml5(e.title)}">${calIconHtml(info)} ${escapeHtml5(e.title)}</span>
|
|
4921
5189
|
</div>`;
|
|
4922
5190
|
} else {
|
|
4923
5191
|
const titleAttr = link ? 'title="Click to open in Google Calendar"' : "";
|
|
4924
|
-
html += `<div class="cal-side-event" data-id="${e.id}"${recurAttr} data-link="${
|
|
5192
|
+
html += `<div class="cal-side-event" data-id="${e.id}"${recurAttr} data-link="${escapeHtml5(link)}" ${titleAttr}>
|
|
4925
5193
|
${calIconHtml(info)}
|
|
4926
|
-
<span class="cal-side-event-time">${
|
|
4927
|
-
<span class="cal-side-event-title" title="${
|
|
5194
|
+
<span class="cal-side-event-time">${escapeHtml5(formatTime(e))}</span>
|
|
5195
|
+
<span class="cal-side-event-title" title="${escapeHtml5(e.title)}">${escapeHtml5(e.title)}${recurMark}</span>
|
|
4928
5196
|
</div>`;
|
|
4929
5197
|
}
|
|
4930
5198
|
}
|
|
@@ -5009,7 +5277,7 @@ async function renderTasks(prefetched) {
|
|
|
5009
5277
|
const sel = selectedTaskUuids.has(t.uuid) ? " selected" : "";
|
|
5010
5278
|
html += `<div class="cal-side-task${sel}" data-uuid="${t.uuid}">
|
|
5011
5279
|
<input type="checkbox" ${done ? "checked" : ""} class="cal-side-task-check">
|
|
5012
|
-
<span class="cal-side-task-title${done ? " done" : ""}" title="${
|
|
5280
|
+
<span class="cal-side-task-title${done ? " done" : ""}" title="${escapeHtml5(t.title)}">${escapeHtml5(t.title)}</span>
|
|
5013
5281
|
${dueHtml}
|
|
5014
5282
|
<button class="cal-side-task-delete" title="Delete task" aria-label="Delete task">\xD7</button>
|
|
5015
5283
|
</div>`;
|
|
@@ -5088,7 +5356,7 @@ async function refresh() {
|
|
|
5088
5356
|
} catch (e) {
|
|
5089
5357
|
const body = document.getElementById("cal-side-body");
|
|
5090
5358
|
if (body)
|
|
5091
|
-
body.innerHTML = `<div class="cal-side-empty cal-side-quota-error">Couldn't load calendar: ${
|
|
5359
|
+
body.innerHTML = `<div class="cal-side-empty cal-side-quota-error">Couldn't load calendar: ${escapeHtml5(e?.message || String(e))}</div>`;
|
|
5092
5360
|
}
|
|
5093
5361
|
renderTasks(prefetchedTasks);
|
|
5094
5362
|
}
|
|
@@ -5369,14 +5637,14 @@ function initCalendarSidebar() {
|
|
|
5369
5637
|
const host = event.feature === "tasks" ? document.getElementById("cal-side-tasks") : document.getElementById("cal-side-body");
|
|
5370
5638
|
if (host && !host.querySelector(".cal-side-quota-error")) {
|
|
5371
5639
|
const msg = event.message || `Google ${event.feature} quota exceeded \u2014 try again later.`;
|
|
5372
|
-
host.innerHTML = `<div class="cal-side-empty cal-side-quota-error">${
|
|
5640
|
+
host.innerHTML = `<div class="cal-side-empty cal-side-quota-error">${escapeHtml5(msg)}</div>`;
|
|
5373
5641
|
}
|
|
5374
5642
|
} else if (event?.type === "authScopeError") {
|
|
5375
5643
|
const host = event.feature === "tasks" ? document.getElementById("cal-side-tasks") : document.getElementById("cal-side-body");
|
|
5376
5644
|
if (host && !host.querySelector(".cal-side-auth-error")) {
|
|
5377
5645
|
const msg = event.message || "Google access needs re-consent.";
|
|
5378
5646
|
host.innerHTML = `<div class="cal-side-empty cal-side-auth-error">
|
|
5379
|
-
<div style="margin-bottom:0.6em">${
|
|
5647
|
+
<div style="margin-bottom:0.6em">${escapeHtml5(msg)}</div>
|
|
5380
5648
|
<button type="button" class="cal-side-reauth-btn" style="padding:0.3em 0.8em;border-radius:4px;border:1px solid currentColor;background:transparent;color:inherit;cursor:pointer;font-size:0.9em">Re-authenticate Now</button>
|
|
5381
5649
|
</div>`;
|
|
5382
5650
|
const btn = host.querySelector(".cal-side-reauth-btn");
|
|
@@ -5421,211 +5689,6 @@ var init_calendar_sidebar = __esm({
|
|
|
5421
5689
|
}
|
|
5422
5690
|
});
|
|
5423
5691
|
|
|
5424
|
-
// client/components/address-book.js
|
|
5425
|
-
var address_book_exports = {};
|
|
5426
|
-
__export(address_book_exports, {
|
|
5427
|
-
openAddressBook: () => openAddressBook
|
|
5428
|
-
});
|
|
5429
|
-
async function openAddressBook() {
|
|
5430
|
-
if (isOpen2)
|
|
5431
|
-
return;
|
|
5432
|
-
isOpen2 = true;
|
|
5433
|
-
const backdrop = document.createElement("div");
|
|
5434
|
-
backdrop.className = "mailx-modal-backdrop";
|
|
5435
|
-
const panel = document.createElement("div");
|
|
5436
|
-
panel.className = "mailx-modal mailx-modal-wide";
|
|
5437
|
-
panel.innerHTML = `
|
|
5438
|
-
<div class="mailx-modal-title">
|
|
5439
|
-
<span class="mailx-modal-title-text">Address Book</span>
|
|
5440
|
-
<button type="button" class="mailx-modal-close" id="ab-close" title="Close (Esc)" aria-label="Close">×</button>
|
|
5441
|
-
</div>
|
|
5442
|
-
<div class="ab-toolbar">
|
|
5443
|
-
<input type="search" id="ab-search" class="mailx-modal-input" placeholder="Search name or email\u2026" autocomplete="off">
|
|
5444
|
-
<button type="button" class="mailx-modal-btn mailx-modal-btn-primary" id="ab-new">+ New contact</button>
|
|
5445
|
-
</div>
|
|
5446
|
-
<div class="ab-count" id="ab-count"></div>
|
|
5447
|
-
<div class="ab-list" id="ab-list">Loading\u2026</div>
|
|
5448
|
-
<div class="mailx-modal-buttons">
|
|
5449
|
-
<span class="mailx-modal-spacer"></span>
|
|
5450
|
-
<button type="button" class="mailx-modal-btn" data-action="close">Close</button>
|
|
5451
|
-
</div>`;
|
|
5452
|
-
backdrop.appendChild(panel);
|
|
5453
|
-
document.body.appendChild(backdrop);
|
|
5454
|
-
const searchInput2 = panel.querySelector("#ab-search");
|
|
5455
|
-
const listEl = panel.querySelector("#ab-list");
|
|
5456
|
-
const countEl = panel.querySelector("#ab-count");
|
|
5457
|
-
let editingEmail = null;
|
|
5458
|
-
const render2 = (items, total) => {
|
|
5459
|
-
countEl.textContent = total === items.length ? `${total} contact${total === 1 ? "" : "s"}` : `Showing ${items.length} of ${total}`;
|
|
5460
|
-
if (items.length === 0) {
|
|
5461
|
-
listEl.innerHTML = `<div class="ab-empty">No contacts match.</div>`;
|
|
5462
|
-
return;
|
|
5463
|
-
}
|
|
5464
|
-
const fmtDate = (ms) => {
|
|
5465
|
-
if (!ms)
|
|
5466
|
-
return "";
|
|
5467
|
-
const d = new Date(ms);
|
|
5468
|
-
const now = /* @__PURE__ */ new Date();
|
|
5469
|
-
return d.getFullYear() === now.getFullYear() ? d.toLocaleDateString(void 0, { month: "short", day: "numeric" }) : d.toLocaleDateString();
|
|
5470
|
-
};
|
|
5471
|
-
listEl.innerHTML = `
|
|
5472
|
-
<div class="ab-row ab-header">
|
|
5473
|
-
<span class="ab-name">Name</span>
|
|
5474
|
-
<span class="ab-email">Email</span>
|
|
5475
|
-
<span class="ab-source">Source</span>
|
|
5476
|
-
<span class="ab-count-cell" title="Times sent to">\xD7</span>
|
|
5477
|
-
<span class="ab-last">Last</span>
|
|
5478
|
-
<span class="ab-actions"></span>
|
|
5479
|
-
</div>` + items.map((c) => `
|
|
5480
|
-
<div class="ab-row" data-email="${escapeAttr(c.email)}">
|
|
5481
|
-
<span class="ab-name">${escapeHtml5(c.name || "")}</span>
|
|
5482
|
-
<span class="ab-email">${escapeHtml5(c.email)}</span>
|
|
5483
|
-
<span class="ab-source">${escapeHtml5(c.source)}</span>
|
|
5484
|
-
<span class="ab-count-cell">${c.useCount || 0}</span>
|
|
5485
|
-
<span class="ab-last">${fmtDate(c.lastUsed)}</span>
|
|
5486
|
-
<span class="ab-actions">
|
|
5487
|
-
<button type="button" class="ab-edit" title="Edit name">\u270E</button>
|
|
5488
|
-
<button type="button" class="ab-del" title="Delete">\u{1F5D1}</button>
|
|
5489
|
-
<button type="button" class="ab-mail" title="Compose to">\u2709</button>
|
|
5490
|
-
</span>
|
|
5491
|
-
</div>`).join("");
|
|
5492
|
-
listEl.querySelectorAll(".ab-row[data-email]").forEach((row) => {
|
|
5493
|
-
const email = row.dataset.email;
|
|
5494
|
-
const c = items.find((x) => x.email === email);
|
|
5495
|
-
row.querySelector(".ab-edit")?.addEventListener("click", () => beginEdit(row, c));
|
|
5496
|
-
row.querySelector(".ab-del")?.addEventListener("click", () => doDelete(c));
|
|
5497
|
-
row.querySelector(".ab-mail")?.addEventListener("click", () => composeTo(c));
|
|
5498
|
-
});
|
|
5499
|
-
};
|
|
5500
|
-
const beginEdit = (row, c) => {
|
|
5501
|
-
if (editingEmail === c.email)
|
|
5502
|
-
return;
|
|
5503
|
-
editingEmail = c.email;
|
|
5504
|
-
const nameSpan = row.querySelector(".ab-name");
|
|
5505
|
-
const original = c.name || "";
|
|
5506
|
-
nameSpan.innerHTML = `<input type="text" value="${escapeAttr(original)}" class="ab-name-input">`;
|
|
5507
|
-
const inp = nameSpan.querySelector("input");
|
|
5508
|
-
inp.focus();
|
|
5509
|
-
inp.select();
|
|
5510
|
-
const commit = async () => {
|
|
5511
|
-
const newName = inp.value.trim();
|
|
5512
|
-
if (newName !== original) {
|
|
5513
|
-
try {
|
|
5514
|
-
await upsertContact(newName, c.email);
|
|
5515
|
-
c.name = newName;
|
|
5516
|
-
} catch (e) {
|
|
5517
|
-
alert(`Update failed: ${e?.message || e}`);
|
|
5518
|
-
}
|
|
5519
|
-
}
|
|
5520
|
-
nameSpan.textContent = c.name || "";
|
|
5521
|
-
editingEmail = null;
|
|
5522
|
-
};
|
|
5523
|
-
inp.addEventListener("blur", commit);
|
|
5524
|
-
inp.addEventListener("keydown", (e) => {
|
|
5525
|
-
if (e.key === "Enter") {
|
|
5526
|
-
e.preventDefault();
|
|
5527
|
-
inp.blur();
|
|
5528
|
-
} else if (e.key === "Escape") {
|
|
5529
|
-
e.preventDefault();
|
|
5530
|
-
inp.value = original;
|
|
5531
|
-
inp.blur();
|
|
5532
|
-
}
|
|
5533
|
-
});
|
|
5534
|
-
};
|
|
5535
|
-
const doDelete = async (c) => {
|
|
5536
|
-
if (!confirm(`Delete contact "${c.name || c.email}"?`))
|
|
5537
|
-
return;
|
|
5538
|
-
try {
|
|
5539
|
-
await deleteContact(c.email);
|
|
5540
|
-
await reload();
|
|
5541
|
-
} catch (e) {
|
|
5542
|
-
alert(`Delete failed: ${e?.message || e}`);
|
|
5543
|
-
}
|
|
5544
|
-
};
|
|
5545
|
-
const composeTo = (c) => {
|
|
5546
|
-
const init = {
|
|
5547
|
-
mode: "new",
|
|
5548
|
-
to: [{ name: c.name || "", address: c.email }],
|
|
5549
|
-
cc: [],
|
|
5550
|
-
bcc: [],
|
|
5551
|
-
subject: "",
|
|
5552
|
-
bodyHtml: "",
|
|
5553
|
-
inReplyTo: "",
|
|
5554
|
-
references: [],
|
|
5555
|
-
accounts: []
|
|
5556
|
-
};
|
|
5557
|
-
try {
|
|
5558
|
-
sessionStorage.setItem("composeInit", JSON.stringify(init));
|
|
5559
|
-
} catch {
|
|
5560
|
-
}
|
|
5561
|
-
document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "new" } }));
|
|
5562
|
-
close();
|
|
5563
|
-
};
|
|
5564
|
-
let reloadDebounce;
|
|
5565
|
-
const reload = async () => {
|
|
5566
|
-
try {
|
|
5567
|
-
const r = await listContacts(searchInput2.value, 1, 200);
|
|
5568
|
-
render2(r.items, r.total);
|
|
5569
|
-
} catch (e) {
|
|
5570
|
-
listEl.innerHTML = `<div class="ab-empty">Load failed: ${escapeHtml5(e?.message || String(e))}</div>`;
|
|
5571
|
-
}
|
|
5572
|
-
};
|
|
5573
|
-
const scheduleReload = () => {
|
|
5574
|
-
if (reloadDebounce)
|
|
5575
|
-
window.clearTimeout(reloadDebounce);
|
|
5576
|
-
reloadDebounce = window.setTimeout(reload, 200);
|
|
5577
|
-
};
|
|
5578
|
-
panel.querySelector("#ab-new")?.addEventListener("click", async () => {
|
|
5579
|
-
const email = prompt("Email address:");
|
|
5580
|
-
if (!email)
|
|
5581
|
-
return;
|
|
5582
|
-
const name = prompt("Display name (optional):") || "";
|
|
5583
|
-
try {
|
|
5584
|
-
await upsertContact(name, email.trim());
|
|
5585
|
-
await reload();
|
|
5586
|
-
} catch (e) {
|
|
5587
|
-
alert(`Add failed: ${e?.message || e}`);
|
|
5588
|
-
}
|
|
5589
|
-
});
|
|
5590
|
-
searchInput2.addEventListener("input", scheduleReload);
|
|
5591
|
-
const close = () => {
|
|
5592
|
-
if (reloadDebounce)
|
|
5593
|
-
window.clearTimeout(reloadDebounce);
|
|
5594
|
-
backdrop.remove();
|
|
5595
|
-
document.removeEventListener("keydown", onKey, true);
|
|
5596
|
-
isOpen2 = false;
|
|
5597
|
-
};
|
|
5598
|
-
const onKey = (e) => {
|
|
5599
|
-
if (e.key === "Escape") {
|
|
5600
|
-
e.stopPropagation();
|
|
5601
|
-
e.preventDefault();
|
|
5602
|
-
close();
|
|
5603
|
-
}
|
|
5604
|
-
};
|
|
5605
|
-
document.addEventListener("keydown", onKey, true);
|
|
5606
|
-
panel.querySelector("#ab-close").addEventListener("click", close);
|
|
5607
|
-
panel.querySelector('[data-action="close"]').addEventListener("click", close);
|
|
5608
|
-
backdrop.addEventListener("mousedown", (e) => {
|
|
5609
|
-
if (e.target === backdrop)
|
|
5610
|
-
close();
|
|
5611
|
-
});
|
|
5612
|
-
await reload();
|
|
5613
|
-
}
|
|
5614
|
-
function escapeHtml5(s) {
|
|
5615
|
-
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
5616
|
-
}
|
|
5617
|
-
function escapeAttr(s) {
|
|
5618
|
-
return escapeHtml5(s);
|
|
5619
|
-
}
|
|
5620
|
-
var isOpen2;
|
|
5621
|
-
var init_address_book = __esm({
|
|
5622
|
-
"client/components/address-book.js"() {
|
|
5623
|
-
"use strict";
|
|
5624
|
-
init_api_client();
|
|
5625
|
-
isOpen2 = false;
|
|
5626
|
-
}
|
|
5627
|
-
});
|
|
5628
|
-
|
|
5629
5692
|
// client/components/alarms.js
|
|
5630
5693
|
var alarms_exports = {};
|
|
5631
5694
|
__export(alarms_exports, {
|
|
@@ -6371,6 +6434,14 @@ var refreshDebounceTimer = null;
|
|
|
6371
6434
|
var DRAG_HOVER_EXPAND_MS = 600;
|
|
6372
6435
|
var dragAutoExpanded = /* @__PURE__ */ new Set();
|
|
6373
6436
|
var dragInFlight = false;
|
|
6437
|
+
function collapseDragExpanded() {
|
|
6438
|
+
if (dragAutoExpanded.size === 0)
|
|
6439
|
+
return false;
|
|
6440
|
+
for (const key of dragAutoExpanded)
|
|
6441
|
+
delete expandState[key];
|
|
6442
|
+
dragAutoExpanded.clear();
|
|
6443
|
+
return true;
|
|
6444
|
+
}
|
|
6374
6445
|
var expandState = JSON.parse(localStorage.getItem("mailx-folders-expanded") || "{}");
|
|
6375
6446
|
function saveExpandState() {
|
|
6376
6447
|
localStorage.setItem("mailx-folders-expanded", JSON.stringify(expandState));
|
|
@@ -6749,6 +6820,14 @@ function renderNode(node, container, depth) {
|
|
|
6749
6820
|
clearTimeout(dragExpandTimer);
|
|
6750
6821
|
dragExpandTimer = null;
|
|
6751
6822
|
}
|
|
6823
|
+
const springCollapsed = collapseDragExpanded();
|
|
6824
|
+
const rerenderTree = () => {
|
|
6825
|
+
const tc = document.getElementById("folder-tree");
|
|
6826
|
+
if (tc)
|
|
6827
|
+
loadFolderTree(tc);
|
|
6828
|
+
};
|
|
6829
|
+
if (springCollapsed)
|
|
6830
|
+
rerenderTree();
|
|
6752
6831
|
const folderData = e.dataTransfer.getData("application/x-mailx-folder");
|
|
6753
6832
|
if (folderData) {
|
|
6754
6833
|
let src;
|
|
@@ -6857,11 +6936,8 @@ document.addEventListener("dragend", () => {
|
|
|
6857
6936
|
if (!dragInFlight)
|
|
6858
6937
|
return;
|
|
6859
6938
|
dragInFlight = false;
|
|
6860
|
-
if (
|
|
6939
|
+
if (!collapseDragExpanded())
|
|
6861
6940
|
return;
|
|
6862
|
-
for (const key of dragAutoExpanded)
|
|
6863
|
-
delete expandState[key];
|
|
6864
|
-
dragAutoExpanded.clear();
|
|
6865
6941
|
const treeContainer = document.getElementById("folder-tree");
|
|
6866
6942
|
if (treeContainer)
|
|
6867
6943
|
loadFolderTree(treeContainer);
|
|
@@ -9066,6 +9142,9 @@ document.addEventListener("mailx-share-intent", ((e) => {
|
|
|
9066
9142
|
if (detail.subject) init.subject = detail.subject;
|
|
9067
9143
|
if (detail.text) init.bodyHtml = `<p>${escapeHtml7(String(detail.text)).replace(/\n/g, "<br>")}</p>`;
|
|
9068
9144
|
}
|
|
9145
|
+
if (Array.isArray(detail.attachments) && detail.attachments.length) {
|
|
9146
|
+
init.attachments = detail.attachments.filter((a) => a?.filename && a?.dataBase64);
|
|
9147
|
+
}
|
|
9069
9148
|
sessionStorage.setItem("composeInit", JSON.stringify(init));
|
|
9070
9149
|
showComposeOverlay(init.subject || "Compose");
|
|
9071
9150
|
}));
|
|
@@ -11204,6 +11283,19 @@ optThreaded?.addEventListener("change", () => {
|
|
|
11204
11283
|
localStorage.setItem("mailx-threaded", String(optThreaded.checked));
|
|
11205
11284
|
reloadCurrentFolder();
|
|
11206
11285
|
});
|
|
11286
|
+
var optDateSent = document.getElementById("opt-date-sent");
|
|
11287
|
+
var optDateReceived = document.getElementById("opt-date-received");
|
|
11288
|
+
(function initDateBasisRadios() {
|
|
11289
|
+
const basis = getDateBasis();
|
|
11290
|
+
if (optDateSent) optDateSent.checked = basis === "sent";
|
|
11291
|
+
if (optDateReceived) optDateReceived.checked = basis === "received";
|
|
11292
|
+
})();
|
|
11293
|
+
optDateSent?.addEventListener("change", () => {
|
|
11294
|
+
if (optDateSent.checked) setDateBasis("sent");
|
|
11295
|
+
});
|
|
11296
|
+
optDateReceived?.addEventListener("change", () => {
|
|
11297
|
+
if (optDateReceived.checked) setDateBasis("received");
|
|
11298
|
+
});
|
|
11207
11299
|
function syncFilterFlaggedButton() {
|
|
11208
11300
|
const btn = document.getElementById("btn-filter-flagged");
|
|
11209
11301
|
if (!btn) return;
|