@bobfrankston/rmfmail 1.1.44 → 1.1.49
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/TODO.md +9 -0
- package/client/app.bundle.js +403 -83
- package/client/app.bundle.js.map +4 -4
- package/client/app.js +190 -35
- package/client/app.js.map +1 -1
- package/client/app.ts +175 -34
- package/client/components/calendar-sidebar.js +221 -88
- package/client/components/calendar-sidebar.js.map +1 -1
- package/client/components/calendar-sidebar.ts +224 -83
- package/client/components/message-viewer.js +24 -1
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +25 -1
- package/client/compose/compose.bundle.js +14 -0
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/compose/spellcheck.js +15 -0
- package/client/compose/spellcheck.js.map +1 -1
- package/client/compose/spellcheck.ts +14 -0
- package/client/help/search-help.js +75 -0
- package/client/help/search-help.js.map +1 -0
- package/client/help/search-help.ts +75 -0
- package/client/index.html +7 -7
- package/client/lib/api-client.js +5 -0
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +5 -0
- package/client/lib/mailxapi.js +1 -0
- package/client/styles/components.css +204 -6
- package/docs/search.md +5 -1
- package/package.json +1 -1
- package/packages/mailx-service/google-sync.d.ts +3 -0
- package/packages/mailx-service/google-sync.d.ts.map +1 -1
- package/packages/mailx-service/google-sync.js +1 -0
- package/packages/mailx-service/google-sync.js.map +1 -1
- package/packages/mailx-service/google-sync.ts +4 -0
- package/packages/mailx-service/index.d.ts +11 -0
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +99 -127
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +91 -122
- package/packages/mailx-service/jsonrpc.js +2 -0
- package/packages/mailx-service/jsonrpc.js.map +1 -1
- package/packages/mailx-service/jsonrpc.ts +2 -0
- package/packages/mailx-settings/index.d.ts.map +1 -1
- package/packages/mailx-settings/index.js +4 -1
- package/packages/mailx-settings/index.js.map +1 -1
- package/packages/mailx-settings/index.ts +4 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-45524 → node_modules.npmglobalize-stash-43988}/.package-lock.json +0 -0
package/client/app.bundle.js
CHANGED
|
@@ -42,6 +42,7 @@ __export(api_client_exports, {
|
|
|
42
42
|
getAttachment: () => getAttachment,
|
|
43
43
|
getAutocompleteSettings: () => getAutocompleteSettings,
|
|
44
44
|
getCalendarEvents: () => getCalendarEvents,
|
|
45
|
+
getCalendars: () => getCalendars,
|
|
45
46
|
getDeviceAccounts: () => getDeviceAccounts,
|
|
46
47
|
getDiagnostics: () => getDiagnostics,
|
|
47
48
|
getFolders: () => getFolders,
|
|
@@ -221,6 +222,9 @@ function getPrimaryAccount(feature) {
|
|
|
221
222
|
function getCalendarEvents(fromMs, toMs) {
|
|
222
223
|
return ipc().getCalendarEvents?.(fromMs, toMs) ?? Promise.resolve([]);
|
|
223
224
|
}
|
|
225
|
+
function getCalendars() {
|
|
226
|
+
return ipc().getCalendars?.() ?? Promise.resolve([]);
|
|
227
|
+
}
|
|
224
228
|
function createCalendarEvent(ev) {
|
|
225
229
|
return ipc().createCalendarEvent?.(ev);
|
|
226
230
|
}
|
|
@@ -2151,9 +2155,29 @@ function spawnDesktopPopout(msg, accountId) {
|
|
|
2151
2155
|
wrapper.style.top = `${60 + existing * 28}px`;
|
|
2152
2156
|
wrapper.style.right = `${20 + existing * 28}px`;
|
|
2153
2157
|
}
|
|
2154
|
-
|
|
2158
|
+
const toolbar = document.createElement("div");
|
|
2159
|
+
toolbar.style.cssText = "display:flex;gap:6px;padding:6px 12px;border-bottom:1px solid var(--color-border, #ddd);flex-shrink:0;";
|
|
2160
|
+
const mkPopoutBtn = (label, title, onClick) => {
|
|
2161
|
+
const b = document.createElement("button");
|
|
2162
|
+
b.textContent = label;
|
|
2163
|
+
b.title = title;
|
|
2164
|
+
b.style.cssText = "padding:4px 10px;font-size:13px;cursor:pointer;border:1px solid var(--color-border, #ccc);border-radius:4px;background:var(--color-bg, #fff);color:var(--color-text, #000);";
|
|
2165
|
+
b.addEventListener("click", onClick);
|
|
2166
|
+
return b;
|
|
2167
|
+
};
|
|
2168
|
+
const firePopoutAction = (action) => {
|
|
2169
|
+
document.dispatchEvent(new CustomEvent("mailx-popout-action", { detail: { action, msg, accountId } }));
|
|
2170
|
+
};
|
|
2171
|
+
toolbar.appendChild(mkPopoutBtn("Reply", "Reply", () => firePopoutAction("reply")));
|
|
2172
|
+
toolbar.appendChild(mkPopoutBtn("Reply All", "Reply to all", () => firePopoutAction("replyAll")));
|
|
2173
|
+
toolbar.appendChild(mkPopoutBtn("Forward", "Forward", () => firePopoutAction("forward")));
|
|
2174
|
+
toolbar.appendChild(mkPopoutBtn("Delete", "Delete this message", () => {
|
|
2175
|
+
firePopoutAction("delete");
|
|
2176
|
+
wrapper.remove();
|
|
2177
|
+
}));
|
|
2155
2178
|
wrapper.appendChild(titleBar);
|
|
2156
2179
|
wrapper.appendChild(headerInfo);
|
|
2180
|
+
wrapper.appendChild(toolbar);
|
|
2157
2181
|
wrapper.appendChild(bodyContainer);
|
|
2158
2182
|
document.body.appendChild(wrapper);
|
|
2159
2183
|
}
|
|
@@ -3773,6 +3797,106 @@ __export(calendar_sidebar_exports, {
|
|
|
3773
3797
|
isCalendarSidebarOn: () => isCalendarSidebarOn,
|
|
3774
3798
|
showCalendarSidebar: () => showCalendarSidebar
|
|
3775
3799
|
});
|
|
3800
|
+
function calendarKind(id, primary) {
|
|
3801
|
+
if (primary)
|
|
3802
|
+
return "personal";
|
|
3803
|
+
const c = (id || "").toLowerCase();
|
|
3804
|
+
if (c.includes("addressbook#contacts"))
|
|
3805
|
+
return "birthday";
|
|
3806
|
+
if (c.includes("#holiday@") || c.includes("holiday@group")) {
|
|
3807
|
+
if (c.includes("usa"))
|
|
3808
|
+
return "usHoliday";
|
|
3809
|
+
if (c.includes("judaism") || c.includes("jewish"))
|
|
3810
|
+
return "jewishHoliday";
|
|
3811
|
+
return "otherHoliday";
|
|
3812
|
+
}
|
|
3813
|
+
return "other";
|
|
3814
|
+
}
|
|
3815
|
+
function calIconHtml(info) {
|
|
3816
|
+
const kind = calendarKind(info.id, info.primary);
|
|
3817
|
+
const t = escapeHtml4(info.name);
|
|
3818
|
+
if (kind === "personal")
|
|
3819
|
+
return `<span class="cal-ico cal-ico-dot" title="${t}"></span>`;
|
|
3820
|
+
if (kind === "usHoliday")
|
|
3821
|
+
return `<span class="cal-ico cal-ico-emoji" title="${t}">\u{1F1FA}\u{1F1F8}</span>`;
|
|
3822
|
+
if (kind === "jewishHoliday")
|
|
3823
|
+
return `<span class="cal-ico cal-ico-emoji" title="${t}">\u2721\uFE0F</span>`;
|
|
3824
|
+
if (kind === "birthday")
|
|
3825
|
+
return `<span class="cal-ico cal-ico-emoji" title="${t}">\u{1F382}</span>`;
|
|
3826
|
+
if (kind === "otherHoliday")
|
|
3827
|
+
return `<span class="cal-ico cal-ico-emoji" title="${t}">\u2726</span>`;
|
|
3828
|
+
const letter = escapeHtml4((info.name.trim()[0] || "?").toUpperCase());
|
|
3829
|
+
const color = info.color || "#7a7a7a";
|
|
3830
|
+
return `<span class="cal-ico cal-ico-mono" style="background:${escapeHtml4(color)}" title="${t}">${letter}</span>`;
|
|
3831
|
+
}
|
|
3832
|
+
function calInfoFor(calendarId) {
|
|
3833
|
+
const id = calendarId || "primary";
|
|
3834
|
+
const found = calById.get(id);
|
|
3835
|
+
if (found)
|
|
3836
|
+
return found;
|
|
3837
|
+
return { id, name: id.split("@")[0] || id, color: "", primary: id === "primary" };
|
|
3838
|
+
}
|
|
3839
|
+
function isCalHidden(calendarId) {
|
|
3840
|
+
return hiddenCalendars.has(calInfoFor(calendarId).id);
|
|
3841
|
+
}
|
|
3842
|
+
async function loadHiddenCalendars() {
|
|
3843
|
+
try {
|
|
3844
|
+
const s = await getSettings();
|
|
3845
|
+
const arr = s?.calendar?.hiddenCalendars;
|
|
3846
|
+
hiddenCalendars = new Set(Array.isArray(arr) ? arr : []);
|
|
3847
|
+
} catch {
|
|
3848
|
+
hiddenCalendars = /* @__PURE__ */ new Set();
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
async function saveHiddenCalendars() {
|
|
3852
|
+
try {
|
|
3853
|
+
const s = await getSettings();
|
|
3854
|
+
s.calendar = { ...s.calendar || {}, hiddenCalendars: [...hiddenCalendars] };
|
|
3855
|
+
await saveSettings(s);
|
|
3856
|
+
} catch (e) {
|
|
3857
|
+
console.error("[cal] save hiddenCalendars failed:", e);
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
async function renderCalendarList() {
|
|
3861
|
+
const host = document.getElementById("cal-side-calendars");
|
|
3862
|
+
let list = [];
|
|
3863
|
+
try {
|
|
3864
|
+
list = await getCalendars();
|
|
3865
|
+
} catch {
|
|
3866
|
+
}
|
|
3867
|
+
calendarList = list;
|
|
3868
|
+
calById.clear();
|
|
3869
|
+
for (const c of list) {
|
|
3870
|
+
calById.set(c.id, c);
|
|
3871
|
+
if (c.primary)
|
|
3872
|
+
calById.set("primary", c);
|
|
3873
|
+
}
|
|
3874
|
+
if (host) {
|
|
3875
|
+
if (list.length === 0) {
|
|
3876
|
+
host.innerHTML = "";
|
|
3877
|
+
} else {
|
|
3878
|
+
const sorted = [...list].sort((a, b) => (a.primary ? 0 : 1) - (b.primary ? 0 : 1) || a.name.localeCompare(b.name));
|
|
3879
|
+
host.innerHTML = sorted.map((c) => `<label class="cal-side-cal-row" title="${escapeHtml4(c.name)}">
|
|
3880
|
+
<input type="checkbox" class="cal-side-cal-check" data-cal-id="${escapeHtml4(c.id)}" ${hiddenCalendars.has(c.id) ? "" : "checked"}>
|
|
3881
|
+
${calIconHtml(c)}
|
|
3882
|
+
<span class="cal-side-cal-name">${escapeHtml4(c.name)}</span>
|
|
3883
|
+
</label>`).join("");
|
|
3884
|
+
host.querySelectorAll(".cal-side-cal-check").forEach((cb) => {
|
|
3885
|
+
cb.addEventListener("change", async () => {
|
|
3886
|
+
const id = cb.dataset.calId || "";
|
|
3887
|
+
if (cb.checked)
|
|
3888
|
+
hiddenCalendars.delete(id);
|
|
3889
|
+
else
|
|
3890
|
+
hiddenCalendars.add(id);
|
|
3891
|
+
await saveHiddenCalendars();
|
|
3892
|
+
renderEvents(lastEvents);
|
|
3893
|
+
});
|
|
3894
|
+
});
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
if (lastEvents.length > 0)
|
|
3898
|
+
renderEvents(lastEvents);
|
|
3899
|
+
}
|
|
3776
3900
|
function getSelectedTaskUuids() {
|
|
3777
3901
|
return [...selectedTaskUuids];
|
|
3778
3902
|
}
|
|
@@ -3849,11 +3973,26 @@ function renderHead() {
|
|
|
3849
3973
|
const d = new Date(viewYear, viewMonth, viewDay);
|
|
3850
3974
|
dateEl.innerHTML = `<strong>${d.getDate()}</strong> ${d.toLocaleDateString(void 0, { weekday: "short" })} <span class="cal-side-date-month">${d.toLocaleDateString(void 0, { month: "short", year: "numeric" })}</span>`;
|
|
3851
3975
|
}
|
|
3976
|
+
function overdueTaskRowHtml(t) {
|
|
3977
|
+
let dueLabel = "";
|
|
3978
|
+
if (t.dueMs) {
|
|
3979
|
+
const d = new Date(t.dueMs);
|
|
3980
|
+
const sameYear = d.getFullYear() === (/* @__PURE__ */ new Date()).getFullYear();
|
|
3981
|
+
dueLabel = sameYear ? `${d.getMonth() + 1}/${d.getDate()}` : d.toISOString().slice(0, 10);
|
|
3982
|
+
}
|
|
3983
|
+
return `<div class="cal-side-task cal-side-task-overdue" data-uuid="${escapeHtml4(t.uuid)}">
|
|
3984
|
+
<input type="checkbox" class="cal-side-overdue-check" title="Mark done">
|
|
3985
|
+
<span class="cal-side-task-title">${escapeHtml4(t.title)}</span>
|
|
3986
|
+
<span class="cal-side-task-due overdue">${escapeHtml4(dueLabel)}</span>
|
|
3987
|
+
</div>`;
|
|
3988
|
+
}
|
|
3852
3989
|
function renderEvents(events) {
|
|
3853
3990
|
const body = document.getElementById("cal-side-body");
|
|
3854
3991
|
if (!body)
|
|
3855
3992
|
return;
|
|
3856
|
-
|
|
3993
|
+
events = events.filter((e) => !isCalHidden(e.calendarId));
|
|
3994
|
+
const overdue = lastOverdueTasks;
|
|
3995
|
+
if (events.length === 0 && overdue.length === 0) {
|
|
3857
3996
|
body.innerHTML = `<div class="cal-side-empty">No upcoming events. Click + New event to add one.</div>`;
|
|
3858
3997
|
return;
|
|
3859
3998
|
}
|
|
@@ -3892,12 +4031,17 @@ function renderEvents(events) {
|
|
|
3892
4031
|
dailyHeads.push(instances.reduce((a, b) => a.start < b.start ? a : b));
|
|
3893
4032
|
}
|
|
3894
4033
|
let html = "";
|
|
4034
|
+
if (overdue.length > 0) {
|
|
4035
|
+
html += `<div class="cal-side-day cal-side-day-overdue">Overdue tasks</div>`;
|
|
4036
|
+
for (const t of overdue)
|
|
4037
|
+
html += overdueTaskRowHtml(t);
|
|
4038
|
+
}
|
|
3895
4039
|
if (dailyHeads.length > 0) {
|
|
3896
4040
|
html += `<div class="cal-side-day cal-side-day-daily">Daily</div>`;
|
|
3897
4041
|
for (const e of dailyHeads) {
|
|
3898
4042
|
const link = e.htmlLink || "";
|
|
3899
4043
|
html += `<div class="cal-side-event" data-id="${e.id}" data-link="${escapeHtml4(link)}" ${link ? 'title="Click to open in Google Calendar"' : ""}>
|
|
3900
|
-
|
|
4044
|
+
${calIconHtml(calInfoFor(e.calendarId))}
|
|
3901
4045
|
<span class="cal-side-event-time">${escapeHtml4(formatTime(e))}</span>
|
|
3902
4046
|
<span class="cal-side-event-title">${escapeHtml4(e.title)}<span class="cal-side-event-recur" title="Daily">\u21BB</span></span>
|
|
3903
4047
|
</div>`;
|
|
@@ -3909,7 +4053,10 @@ function renderEvents(events) {
|
|
|
3909
4053
|
for (const e of events) {
|
|
3910
4054
|
if (e.recurringEventId && dailyKeys.has(e.recurringEventId))
|
|
3911
4055
|
continue;
|
|
3912
|
-
|
|
4056
|
+
const info = calInfoFor(e.calendarId);
|
|
4057
|
+
const kind = calendarKind(info.id, info.primary);
|
|
4058
|
+
const isHolidayKind = kind === "usHoliday" || kind === "jewishHoliday" || kind === "otherHoliday";
|
|
4059
|
+
if (isHolidayKind && e.start > holidayCutoff)
|
|
3913
4060
|
continue;
|
|
3914
4061
|
const d = new Date(e.start);
|
|
3915
4062
|
const dayKey = `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
|
|
@@ -3919,35 +4066,30 @@ function renderEvents(events) {
|
|
|
3919
4066
|
}
|
|
3920
4067
|
const recurMark = e.recurringEventId ? `<span class="cal-side-event-recur" title="Recurring event">\u21BB</span>` : "";
|
|
3921
4068
|
const link = e.htmlLink || "";
|
|
3922
|
-
let holidayKind;
|
|
3923
|
-
if (e.isHoliday) {
|
|
3924
|
-
const cid = (e.calendarId || "").toLowerCase();
|
|
3925
|
-
if (cid.includes("usa"))
|
|
3926
|
-
holidayKind = "us";
|
|
3927
|
-
else if (cid.includes("judaism") || cid.includes("jewish"))
|
|
3928
|
-
holidayKind = "jewish";
|
|
3929
|
-
else
|
|
3930
|
-
holidayKind = "other";
|
|
3931
|
-
}
|
|
3932
|
-
const holidayAttr = e.isHoliday ? ` data-holiday="1" data-holiday-kind="${holidayKind}"` : "";
|
|
3933
4069
|
const recurAttr = e.recurringEventId ? ' data-recurring="1"' : "";
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
if (e.isHoliday) {
|
|
3938
|
-
const symbol = holidayKind === "us" ? "\u{1F1FA}\u{1F1F8}" : holidayKind === "jewish" ? "\u2721\uFE0F" : "\u2726";
|
|
3939
|
-
html += `<div class="cal-side-event"${holidayAttr} data-id="${e.id}">
|
|
3940
|
-
<span class="cal-side-event-title cal-side-event-holiday-title"><span class="cal-side-event-holiday-symbol">${symbol}</span> ${escapeHtml4(e.title)}</span>
|
|
4070
|
+
if (isHolidayKind || kind === "birthday") {
|
|
4071
|
+
html += `<div class="cal-side-event" data-holiday="1" data-holiday-kind="${kind}" data-id="${e.id}">
|
|
4072
|
+
<span class="cal-side-event-title cal-side-event-holiday-title">${calIconHtml(info)} ${escapeHtml4(e.title)}</span>
|
|
3941
4073
|
</div>`;
|
|
3942
4074
|
} else {
|
|
3943
|
-
|
|
3944
|
-
|
|
4075
|
+
const titleAttr = link ? 'title="Click to open in Google Calendar"' : "";
|
|
4076
|
+
html += `<div class="cal-side-event" data-id="${e.id}"${recurAttr} data-link="${escapeHtml4(link)}" ${titleAttr}>
|
|
4077
|
+
${calIconHtml(info)}
|
|
3945
4078
|
<span class="cal-side-event-time">${escapeHtml4(formatTime(e))}</span>
|
|
3946
4079
|
<span class="cal-side-event-title">${escapeHtml4(e.title)}${recurMark}</span>
|
|
3947
4080
|
</div>`;
|
|
3948
4081
|
}
|
|
3949
4082
|
}
|
|
3950
4083
|
body.innerHTML = html;
|
|
4084
|
+
body.querySelectorAll(".cal-side-task-overdue").forEach((row) => {
|
|
4085
|
+
const uuid = row.dataset.uuid;
|
|
4086
|
+
row.querySelector(".cal-side-overdue-check")?.addEventListener("change", async () => {
|
|
4087
|
+
await updateTask(uuid, { completedMs: Date.now() });
|
|
4088
|
+
lastOverdueTasks = lastOverdueTasks.filter((t) => t.uuid !== uuid);
|
|
4089
|
+
renderEvents(lastEvents);
|
|
4090
|
+
renderTasks();
|
|
4091
|
+
});
|
|
4092
|
+
});
|
|
3951
4093
|
const openInCalendar = (url) => {
|
|
3952
4094
|
const api = window.mailxapi;
|
|
3953
4095
|
if (api?.openCalendarEvent) {
|
|
@@ -3990,10 +4132,10 @@ function renderEvents(events) {
|
|
|
3990
4132
|
});
|
|
3991
4133
|
});
|
|
3992
4134
|
}
|
|
3993
|
-
async function renderTasks() {
|
|
4135
|
+
async function renderTasks(prefetched) {
|
|
3994
4136
|
const cb = document.getElementById("cal-side-show-done");
|
|
3995
4137
|
const showDone = cb?.checked ?? false;
|
|
3996
|
-
const tasks = await getTasks(showDone);
|
|
4138
|
+
const tasks = prefetched ?? await getTasks(showDone);
|
|
3997
4139
|
const host = document.getElementById("cal-side-tasks");
|
|
3998
4140
|
if (!host)
|
|
3999
4141
|
return;
|
|
@@ -4082,15 +4224,25 @@ async function renderTasks() {
|
|
|
4082
4224
|
async function refresh() {
|
|
4083
4225
|
renderHead();
|
|
4084
4226
|
const from = new Date(viewYear, viewMonth, viewDay);
|
|
4227
|
+
let prefetchedTasks;
|
|
4085
4228
|
try {
|
|
4086
|
-
|
|
4229
|
+
const startOfToday = /* @__PURE__ */ new Date();
|
|
4230
|
+
startOfToday.setHours(0, 0, 0, 0);
|
|
4231
|
+
const showDone = document.getElementById("cal-side-show-done")?.checked ?? false;
|
|
4232
|
+
const [events, tasks] = await Promise.all([
|
|
4233
|
+
fetchUpcoming(from),
|
|
4234
|
+
getTasks(showDone).catch(() => [])
|
|
4235
|
+
]);
|
|
4236
|
+
lastEvents = events;
|
|
4237
|
+
prefetchedTasks = tasks;
|
|
4238
|
+
lastOverdueTasks = prefetchedTasks.filter((t) => t.dueMs && !t.completedMs && t.dueMs < startOfToday.getTime()).map((t) => ({ uuid: t.uuid, title: t.title, dueMs: t.dueMs }));
|
|
4087
4239
|
renderEvents(lastEvents);
|
|
4088
4240
|
} catch (e) {
|
|
4089
4241
|
const body = document.getElementById("cal-side-body");
|
|
4090
4242
|
if (body)
|
|
4091
4243
|
body.innerHTML = `<div class="cal-side-empty cal-side-quota-error">Couldn't load calendar: ${escapeHtml4(e?.message || String(e))}</div>`;
|
|
4092
4244
|
}
|
|
4093
|
-
renderTasks();
|
|
4245
|
+
renderTasks(prefetchedTasks);
|
|
4094
4246
|
}
|
|
4095
4247
|
function openNewEventDialog() {
|
|
4096
4248
|
const backdrop = document.createElement("div");
|
|
@@ -4217,6 +4369,8 @@ async function showCalendarSidebar() {
|
|
|
4217
4369
|
localStorage.setItem(SIDEBAR_PREF, "true");
|
|
4218
4370
|
} catch {
|
|
4219
4371
|
}
|
|
4372
|
+
await loadHiddenCalendars();
|
|
4373
|
+
void renderCalendarList();
|
|
4220
4374
|
await refresh();
|
|
4221
4375
|
}
|
|
4222
4376
|
function hideCalendarSidebar() {
|
|
@@ -4301,7 +4455,7 @@ function initCalendarSidebar() {
|
|
|
4301
4455
|
if (btn)
|
|
4302
4456
|
btn.disabled = true;
|
|
4303
4457
|
try {
|
|
4304
|
-
await refresh();
|
|
4458
|
+
await Promise.all([renderCalendarList(), refresh()]);
|
|
4305
4459
|
} finally {
|
|
4306
4460
|
setTimeout(() => {
|
|
4307
4461
|
btn?.classList.remove("cal-side-refreshing");
|
|
@@ -4337,32 +4491,6 @@ function initCalendarSidebar() {
|
|
|
4337
4491
|
refresh();
|
|
4338
4492
|
});
|
|
4339
4493
|
}
|
|
4340
|
-
const wireHolidayCheckbox = (cbId, settingsKey) => {
|
|
4341
|
-
const cb = document.getElementById(cbId);
|
|
4342
|
-
if (!cb || cb.__wired)
|
|
4343
|
-
return;
|
|
4344
|
-
cb.__wired = true;
|
|
4345
|
-
let userTouched = false;
|
|
4346
|
-
getSettings().then((s) => {
|
|
4347
|
-
if (userTouched)
|
|
4348
|
-
return;
|
|
4349
|
-
cb.checked = !!s?.calendar?.[settingsKey];
|
|
4350
|
-
}).catch(() => {
|
|
4351
|
-
});
|
|
4352
|
-
cb.addEventListener("change", async () => {
|
|
4353
|
-
userTouched = true;
|
|
4354
|
-
try {
|
|
4355
|
-
const s = await getSettings();
|
|
4356
|
-
s.calendar = { ...s.calendar || {}, [settingsKey]: cb.checked };
|
|
4357
|
-
await saveSettings(s);
|
|
4358
|
-
} catch (e) {
|
|
4359
|
-
console.error(`[cal] ${settingsKey} save failed:`, e);
|
|
4360
|
-
}
|
|
4361
|
-
refresh();
|
|
4362
|
-
});
|
|
4363
|
-
};
|
|
4364
|
-
wireHolidayCheckbox("cal-side-show-holidays", "showHolidays");
|
|
4365
|
-
wireHolidayCheckbox("cal-side-show-jewish-holidays", "showJewishHolidays");
|
|
4366
4494
|
const horizonInput = document.getElementById("cal-side-horizon");
|
|
4367
4495
|
if (horizonInput && !horizonInput.__wired) {
|
|
4368
4496
|
horizonInput.__wired = true;
|
|
@@ -4421,7 +4549,7 @@ function initCalendarSidebar() {
|
|
|
4421
4549
|
}
|
|
4422
4550
|
}
|
|
4423
4551
|
}
|
|
4424
|
-
var SIDEBAR_PREF, SHOW_RECURRING_PREF, SHOW_DONE_PREF, HORIZON_DAYS_PREF, HORIZON_DEFAULT_DAYS, HOLIDAY_HORIZON_MS, viewYear, viewMonth, viewDay, lastEvents, selectedTaskUuids;
|
|
4552
|
+
var SIDEBAR_PREF, SHOW_RECURRING_PREF, SHOW_DONE_PREF, HORIZON_DAYS_PREF, HORIZON_DEFAULT_DAYS, HOLIDAY_HORIZON_MS, viewYear, viewMonth, viewDay, lastEvents, lastOverdueTasks, calById, calendarList, hiddenCalendars, selectedTaskUuids;
|
|
4425
4553
|
var init_calendar_sidebar = __esm({
|
|
4426
4554
|
"client/components/calendar-sidebar.js"() {
|
|
4427
4555
|
"use strict";
|
|
@@ -4437,6 +4565,10 @@ var init_calendar_sidebar = __esm({
|
|
|
4437
4565
|
viewMonth = (/* @__PURE__ */ new Date()).getMonth();
|
|
4438
4566
|
viewDay = (/* @__PURE__ */ new Date()).getDate();
|
|
4439
4567
|
lastEvents = [];
|
|
4568
|
+
lastOverdueTasks = [];
|
|
4569
|
+
calById = /* @__PURE__ */ new Map();
|
|
4570
|
+
calendarList = [];
|
|
4571
|
+
hiddenCalendars = /* @__PURE__ */ new Set();
|
|
4440
4572
|
selectedTaskUuids = /* @__PURE__ */ new Set();
|
|
4441
4573
|
}
|
|
4442
4574
|
});
|
|
@@ -5073,6 +5205,81 @@ var init_alarms = __esm({
|
|
|
5073
5205
|
}
|
|
5074
5206
|
});
|
|
5075
5207
|
|
|
5208
|
+
// client/help/search-help.js
|
|
5209
|
+
var search_help_exports = {};
|
|
5210
|
+
__export(search_help_exports, {
|
|
5211
|
+
SEARCH_HELP_HTML: () => SEARCH_HELP_HTML
|
|
5212
|
+
});
|
|
5213
|
+
var SEARCH_HELP_HTML;
|
|
5214
|
+
var init_search_help = __esm({
|
|
5215
|
+
"client/help/search-help.js"() {
|
|
5216
|
+
"use strict";
|
|
5217
|
+
SEARCH_HELP_HTML = `
|
|
5218
|
+
<p>Search runs in one of three modes depending on the scope you pick. The mode
|
|
5219
|
+
decides which operators work.</p>
|
|
5220
|
+
<ul>
|
|
5221
|
+
<li><strong>All folders</strong> (default) \u2014 searches the <em>local cache</em> with SQLite FTS5.</li>
|
|
5222
|
+
<li><strong>This folder</strong> \u2014 instant client-side filter on the visible rows; full FTS5 search on Enter.</li>
|
|
5223
|
+
<li><strong>Server</strong> (checkbox) \u2014 sends the query to the mail server (IMAP SEARCH; not yet wired for Gmail accounts).</li>
|
|
5224
|
+
</ul>
|
|
5225
|
+
|
|
5226
|
+
<h3>Qualifiers \u2014 work in every mode</h3>
|
|
5227
|
+
<table>
|
|
5228
|
+
<tr><th>Form</th><th>Effect</th></tr>
|
|
5229
|
+
<tr><td><code>from:bob</code></td><td>Sender contains</td></tr>
|
|
5230
|
+
<tr><td><code>to:eleanor</code></td><td>Recipient contains</td></tr>
|
|
5231
|
+
<tr><td><code>subject:lunch</code></td><td>Subject contains</td></tr>
|
|
5232
|
+
<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>
|
|
5233
|
+
<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>
|
|
5234
|
+
<tr><td><code>has:attachment</code></td><td>Has an attachment</td></tr>
|
|
5235
|
+
<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>
|
|
5236
|
+
<tr><td><code>folder:sent</code></td><td>Restrict to folders whose name contains the term</td></tr>
|
|
5237
|
+
<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>
|
|
5238
|
+
</table>
|
|
5239
|
+
<p>Any remaining unqualified text is the free-text term.</p>
|
|
5240
|
+
|
|
5241
|
+
<h3>Local search (FTS5) \u2014 default</h3>
|
|
5242
|
+
<p>Full-text index over envelopes plus locally-cached bodies. Fast and indexed.</p>
|
|
5243
|
+
<table>
|
|
5244
|
+
<tr><th>Operator</th><th>Example</th><th>Meaning</th></tr>
|
|
5245
|
+
<tr><td>implicit AND</td><td><code>bob lunch</code></td><td>Both terms must appear</td></tr>
|
|
5246
|
+
<tr><td><code>OR</code></td><td><code>bob OR eleanor</code></td><td>Either term</td></tr>
|
|
5247
|
+
<tr><td><code>NOT</code></td><td><code>bob NOT spam</code></td><td>First without the second</td></tr>
|
|
5248
|
+
<tr><td><code>"phrase"</code></td><td><code>"happy birthday"</code></td><td>Exact phrase</td></tr>
|
|
5249
|
+
<tr><td><code>term*</code></td><td><code>lunch*</code></td><td>Prefix</td></tr>
|
|
5250
|
+
<tr><td><code>NEAR(a b, 5)</code></td><td><code>NEAR(bob lunch, 5)</code></td><td>Both within 5 tokens</td></tr>
|
|
5251
|
+
</table>
|
|
5252
|
+
<p>Indexed: subject, from, to, cc, a body snippet, and the full body <em>if</em> it
|
|
5253
|
+
has been downloaded locally (the blue dot in the list). Bodies not yet
|
|
5254
|
+
prefetched aren't searchable until they are.</p>
|
|
5255
|
+
|
|
5256
|
+
<h3>This-folder filter</h3>
|
|
5257
|
+
<p>With scope set to <strong>This folder</strong>, typing filters the rendered rows
|
|
5258
|
+
instantly \u2014 no server hit, no FTS5, just substring match. Press <kbd>Enter</kbd>
|
|
5259
|
+
to escalate to a full FTS5 search of the folder.</p>
|
|
5260
|
+
|
|
5261
|
+
<h3>Server search</h3>
|
|
5262
|
+
<p>Tick <strong>Server</strong> and the query goes to the mail server. On Dovecot
|
|
5263
|
+
(IMAP SEARCH) your <code>from:</code> / <code>to:</code> / <code>subject:</code>
|
|
5264
|
+
qualifiers map to IMAP keys; remaining text becomes a body search. IMAP has no
|
|
5265
|
+
literal <code>AND</code> keyword \u2014 keys are implicitly ANDed. Server search on
|
|
5266
|
+
Gmail accounts is not yet wired and currently returns local results only.</p>
|
|
5267
|
+
|
|
5268
|
+
<h3>Case sensitivity</h3>
|
|
5269
|
+
<p>All three modes \u2014 local FTS5, IMAP server SEARCH, and <code>/regex/</code> \u2014
|
|
5270
|
+
are <strong>case-insensitive</strong>. <code>Lunch</code>, <code>lunch</code>, and
|
|
5271
|
+
<code>LUNCH</code> match the same messages. There is no case-sensitive mode.</p>
|
|
5272
|
+
|
|
5273
|
+
<h3>Limitations</h3>
|
|
5274
|
+
<ul>
|
|
5275
|
+
<li>No regex on the server side, any provider \u2014 <code>/pattern/</code> only filters visible local rows.</li>
|
|
5276
|
+
<li>Server search results are stored locally (envelope-only) on first hit, so a server search also backfills those envelopes.</li>
|
|
5277
|
+
<li>Body search on bodies you haven't prefetched only works server-side; the local index can't search what isn't downloaded.</li>
|
|
5278
|
+
</ul>
|
|
5279
|
+
`;
|
|
5280
|
+
}
|
|
5281
|
+
});
|
|
5282
|
+
|
|
5076
5283
|
// client/components/folder-tree.js
|
|
5077
5284
|
init_api_client();
|
|
5078
5285
|
init_context_menu();
|
|
@@ -6519,7 +6726,10 @@ initFolderTree(folderTree, (accountId, folderId, folderName, specialUse) => {
|
|
|
6519
6726
|
currentFolderSpecialUse = specialUse;
|
|
6520
6727
|
currentAccountId3 = accountId;
|
|
6521
6728
|
currentFolderId2 = folderId;
|
|
6522
|
-
if (searchInput)
|
|
6729
|
+
if (searchInput) {
|
|
6730
|
+
searchInput.value = "";
|
|
6731
|
+
updateSearchHighlight();
|
|
6732
|
+
}
|
|
6523
6733
|
clearSearchMode();
|
|
6524
6734
|
markAsSeen();
|
|
6525
6735
|
releaseFocus();
|
|
@@ -6530,7 +6740,10 @@ initFolderTree(folderTree, (accountId, folderId, folderName, specialUse) => {
|
|
|
6530
6740
|
document.dispatchEvent(new CustomEvent("mailx-folder-changed", { detail: { accountId, folderId } }));
|
|
6531
6741
|
}, () => {
|
|
6532
6742
|
currentFolderSpecialUse = "inbox";
|
|
6533
|
-
if (searchInput)
|
|
6743
|
+
if (searchInput) {
|
|
6744
|
+
searchInput.value = "";
|
|
6745
|
+
updateSearchHighlight();
|
|
6746
|
+
}
|
|
6534
6747
|
clearSearchMode();
|
|
6535
6748
|
releaseFocus();
|
|
6536
6749
|
loadUnifiedInbox();
|
|
@@ -6539,7 +6752,10 @@ initFolderTree(folderTree, (accountId, folderId, folderName, specialUse) => {
|
|
|
6539
6752
|
setActiveView({ kind: "unified" }, "All Inboxes");
|
|
6540
6753
|
});
|
|
6541
6754
|
function applyTabView(tab) {
|
|
6542
|
-
if (searchInput)
|
|
6755
|
+
if (searchInput) {
|
|
6756
|
+
searchInput.value = "";
|
|
6757
|
+
updateSearchHighlight();
|
|
6758
|
+
}
|
|
6543
6759
|
clearSearchMode();
|
|
6544
6760
|
releaseFocus();
|
|
6545
6761
|
const v = tab.view;
|
|
@@ -6556,7 +6772,10 @@ function applyTabView(tab) {
|
|
|
6556
6772
|
setTitle(`${APP_NAME} - ${tab.title}`);
|
|
6557
6773
|
setNarrowFolderTitle(tab.title);
|
|
6558
6774
|
} else {
|
|
6559
|
-
if (searchInput)
|
|
6775
|
+
if (searchInput) {
|
|
6776
|
+
searchInput.value = v.query;
|
|
6777
|
+
updateSearchHighlight();
|
|
6778
|
+
}
|
|
6560
6779
|
loadSearchResults(v.query, v.scope, v.accountId, v.folderId, v.includeTrash);
|
|
6561
6780
|
setTitle(`${APP_NAME} - Search`);
|
|
6562
6781
|
setNarrowFolderTitle(`Search: ${v.query}`);
|
|
@@ -6768,9 +6987,9 @@ document.getElementById("btn-factory-reset")?.addEventListener("click", async ()
|
|
|
6768
6987
|
location.reload();
|
|
6769
6988
|
}
|
|
6770
6989
|
});
|
|
6771
|
-
async function openCompose(mode) {
|
|
6990
|
+
async function openCompose(mode, overrideMsg, overrideAccountId) {
|
|
6772
6991
|
logClientEvent("openCompose-entry", { mode });
|
|
6773
|
-
const current = getCurrentMessage();
|
|
6992
|
+
const current = overrideMsg ? { message: overrideMsg, accountId: overrideAccountId || currentAccountId3 } : getCurrentMessage();
|
|
6774
6993
|
if ((mode === "reply" || mode === "replyAll" || mode === "forward") && !current) {
|
|
6775
6994
|
console.warn(`[compose] ${mode} \u2014 no message selected`);
|
|
6776
6995
|
return;
|
|
@@ -7546,6 +7765,64 @@ function recordSearchHistory(query) {
|
|
|
7546
7765
|
refreshSearchHistoryDatalist();
|
|
7547
7766
|
}
|
|
7548
7767
|
refreshSearchHistoryDatalist();
|
|
7768
|
+
var searchHl = document.getElementById("search-hl");
|
|
7769
|
+
var SEARCH_QUALIFIERS = /* @__PURE__ */ new Set(["from", "to", "subject", "date", "after", "before", "has", "is", "folder"]);
|
|
7770
|
+
var SEARCH_REGEX_SETTLE_MS = 500;
|
|
7771
|
+
var searchRegexSettleTimer = null;
|
|
7772
|
+
function escapeHl(s) {
|
|
7773
|
+
return s.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c] || c);
|
|
7774
|
+
}
|
|
7775
|
+
function renderSearchHighlight(settled) {
|
|
7776
|
+
if (!searchHl || !searchInput) return;
|
|
7777
|
+
const text = searchInput.value;
|
|
7778
|
+
const tokens = text.match(/\s+|"[^"]*"?|\S+/g) || [];
|
|
7779
|
+
let html = "";
|
|
7780
|
+
for (const tok of tokens) {
|
|
7781
|
+
if (/^\s+$/.test(tok)) {
|
|
7782
|
+
html += escapeHl(tok);
|
|
7783
|
+
continue;
|
|
7784
|
+
}
|
|
7785
|
+
if (tok.startsWith("/") && tok.length > 1) {
|
|
7786
|
+
let bad = false;
|
|
7787
|
+
if (tok.endsWith("/")) {
|
|
7788
|
+
try {
|
|
7789
|
+
new RegExp(tok.slice(1, -1), "i");
|
|
7790
|
+
} catch {
|
|
7791
|
+
bad = true;
|
|
7792
|
+
}
|
|
7793
|
+
}
|
|
7794
|
+
const cls = bad && settled ? "sh-regex-bad" : "sh-regex";
|
|
7795
|
+
html += `<span class="${cls}">${escapeHl(tok)}</span>`;
|
|
7796
|
+
continue;
|
|
7797
|
+
}
|
|
7798
|
+
const qm = tok.match(/^([a-z]+):(.*)$/i);
|
|
7799
|
+
if (qm && SEARCH_QUALIFIERS.has(qm[1].toLowerCase())) {
|
|
7800
|
+
html += `<span class="sh-key">${escapeHl(qm[1] + ":")}</span>${escapeHl(qm[2])}`;
|
|
7801
|
+
continue;
|
|
7802
|
+
}
|
|
7803
|
+
if (/^(OR|NOT|AND)$/.test(tok)) {
|
|
7804
|
+
html += `<span class="sh-op">${escapeHl(tok)}</span>`;
|
|
7805
|
+
continue;
|
|
7806
|
+
}
|
|
7807
|
+
if (tok.startsWith('"')) {
|
|
7808
|
+
html += `<span class="sh-phrase">${escapeHl(tok)}</span>`;
|
|
7809
|
+
continue;
|
|
7810
|
+
}
|
|
7811
|
+
html += escapeHl(tok);
|
|
7812
|
+
}
|
|
7813
|
+
searchHl.innerHTML = html;
|
|
7814
|
+
searchHl.scrollLeft = searchInput.scrollLeft;
|
|
7815
|
+
}
|
|
7816
|
+
function updateSearchHighlight() {
|
|
7817
|
+
renderSearchHighlight(false);
|
|
7818
|
+
if (searchRegexSettleTimer) clearTimeout(searchRegexSettleTimer);
|
|
7819
|
+
searchRegexSettleTimer = setTimeout(() => renderSearchHighlight(true), SEARCH_REGEX_SETTLE_MS);
|
|
7820
|
+
}
|
|
7821
|
+
searchInput?.addEventListener("scroll", () => {
|
|
7822
|
+
if (searchHl) searchHl.scrollLeft = searchInput.scrollLeft;
|
|
7823
|
+
});
|
|
7824
|
+
searchInput?.addEventListener("change", () => updateSearchHighlight());
|
|
7825
|
+
updateSearchHighlight();
|
|
7549
7826
|
function doSearch(immediate = false) {
|
|
7550
7827
|
const query = searchInput.value.trim();
|
|
7551
7828
|
if (query.length === 0) {
|
|
@@ -7582,6 +7859,7 @@ var currentFolderId2 = 0;
|
|
|
7582
7859
|
var reloadDebounceTimer = null;
|
|
7583
7860
|
searchInput?.addEventListener("input", () => {
|
|
7584
7861
|
clearTimeout(searchTimeout);
|
|
7862
|
+
updateSearchHighlight();
|
|
7585
7863
|
if (searchInput.value.trim() === "") {
|
|
7586
7864
|
clearSearchMode();
|
|
7587
7865
|
const body = document.getElementById("ml-body");
|
|
@@ -7599,6 +7877,7 @@ searchInput?.addEventListener("keydown", (e) => {
|
|
|
7599
7877
|
}
|
|
7600
7878
|
if (e.key === "Escape") {
|
|
7601
7879
|
searchInput.value = "";
|
|
7880
|
+
updateSearchHighlight();
|
|
7602
7881
|
clearSearchMode();
|
|
7603
7882
|
const body = document.getElementById("ml-body");
|
|
7604
7883
|
if (body) body.querySelectorAll(".filter-hidden").forEach((r) => r.classList.remove("filter-hidden"));
|
|
@@ -8757,34 +9036,66 @@ optSnippet?.addEventListener("change", () => {
|
|
|
8757
9036
|
localStorage.setItem("mailx-snippet", String(optSnippet.checked));
|
|
8758
9037
|
});
|
|
8759
9038
|
document.getElementById("search-help")?.addEventListener("click", async () => {
|
|
8760
|
-
const
|
|
8761
|
-
|
|
8762
|
-
const
|
|
8763
|
-
|
|
8764
|
-
|
|
8765
|
-
|
|
9039
|
+
const btn = document.getElementById("search-help");
|
|
9040
|
+
if (!btn) return;
|
|
9041
|
+
const existing = document.getElementById("search-help-panel");
|
|
9042
|
+
if (existing) {
|
|
9043
|
+
existing.remove();
|
|
9044
|
+
btn.setAttribute("aria-expanded", "false");
|
|
9045
|
+
return;
|
|
9046
|
+
}
|
|
9047
|
+
const { SEARCH_HELP_HTML: SEARCH_HELP_HTML2 } = await Promise.resolve().then(() => (init_search_help(), search_help_exports));
|
|
9048
|
+
const searchBar = document.querySelector("search.ml-search");
|
|
8766
9049
|
const panel = document.createElement("div");
|
|
8767
|
-
panel.
|
|
9050
|
+
panel.id = "search-help-panel";
|
|
9051
|
+
panel.className = "search-help-panel";
|
|
8768
9052
|
panel.innerHTML = `
|
|
8769
|
-
<div
|
|
8770
|
-
<span
|
|
8771
|
-
<button type="button" id="search-help-close"
|
|
9053
|
+
<div class="search-help-head">
|
|
9054
|
+
<span>Search syntax</span>
|
|
9055
|
+
<button type="button" id="search-help-close" title="Close (Esc)" aria-label="Close">×</button>
|
|
8772
9056
|
</div>
|
|
8773
|
-
<
|
|
9057
|
+
<div class="search-help-body">${SEARCH_HELP_HTML2}</div>
|
|
8774
9058
|
`;
|
|
8775
|
-
|
|
8776
|
-
|
|
8777
|
-
const
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
9059
|
+
document.body.appendChild(panel);
|
|
9060
|
+
btn.setAttribute("aria-expanded", "true");
|
|
9061
|
+
const position = () => {
|
|
9062
|
+
const anchor = searchBar || btn;
|
|
9063
|
+
const rect = anchor.getBoundingClientRect();
|
|
9064
|
+
const margin = 8;
|
|
9065
|
+
const top = rect.bottom + 2;
|
|
9066
|
+
const width = Math.min(640, Math.max(320, rect.width));
|
|
9067
|
+
let left = rect.left;
|
|
9068
|
+
if (left + width > window.innerWidth - margin) left = window.innerWidth - margin - width;
|
|
9069
|
+
if (left < margin) left = margin;
|
|
9070
|
+
panel.style.top = `${top}px`;
|
|
9071
|
+
panel.style.left = `${left}px`;
|
|
9072
|
+
panel.style.width = `${width}px`;
|
|
9073
|
+
panel.style.maxHeight = `${Math.max(160, window.innerHeight - top - margin)}px`;
|
|
9074
|
+
};
|
|
9075
|
+
position();
|
|
9076
|
+
const close = () => {
|
|
9077
|
+
panel.remove();
|
|
9078
|
+
btn.setAttribute("aria-expanded", "false");
|
|
9079
|
+
window.removeEventListener("resize", position);
|
|
9080
|
+
document.removeEventListener("keydown", onKey, true);
|
|
9081
|
+
document.removeEventListener("mousedown", onOutside);
|
|
9082
|
+
};
|
|
9083
|
+
const onKey = (e) => {
|
|
8783
9084
|
if (e.key === "Escape") {
|
|
9085
|
+
e.preventDefault();
|
|
9086
|
+
e.stopPropagation();
|
|
8784
9087
|
close();
|
|
8785
|
-
document.removeEventListener("keydown", escClose);
|
|
8786
9088
|
}
|
|
8787
|
-
}
|
|
9089
|
+
};
|
|
9090
|
+
const onOutside = (e) => {
|
|
9091
|
+
const t = e.target;
|
|
9092
|
+
if (panel.contains(t) || btn.contains(t) || searchBar?.contains(t)) return;
|
|
9093
|
+
close();
|
|
9094
|
+
};
|
|
9095
|
+
panel.querySelector("#search-help-close")?.addEventListener("click", close);
|
|
9096
|
+
window.addEventListener("resize", position);
|
|
9097
|
+
document.addEventListener("keydown", onKey, true);
|
|
9098
|
+
document.addEventListener("mousedown", onOutside);
|
|
8788
9099
|
});
|
|
8789
9100
|
document.getElementById("btn-edit-jsonc")?.addEventListener("click", async () => {
|
|
8790
9101
|
const settingsDropdown2 = document.getElementById("settings-dropdown");
|
|
@@ -9661,6 +9972,15 @@ function renderDiagnosticsBadge(snapshot) {
|
|
|
9661
9972
|
|
|
9662
9973
|
${detail}`;
|
|
9663
9974
|
}
|
|
9975
|
+
document.addEventListener("mailx-popout-action", ((e) => {
|
|
9976
|
+
const { action, msg, accountId } = e.detail || {};
|
|
9977
|
+
if (!msg) return;
|
|
9978
|
+
if (action === "reply" || action === "replyAll" || action === "forward") {
|
|
9979
|
+
openCompose(action, msg, accountId);
|
|
9980
|
+
} else if (action === "delete") {
|
|
9981
|
+
deleteMessage(accountId, msg.uid).catch((err) => console.error(`[popout] delete failed: ${err?.message || err}`));
|
|
9982
|
+
}
|
|
9983
|
+
}));
|
|
9664
9984
|
document.addEventListener("mailx-popout-message", (async (e) => {
|
|
9665
9985
|
const { accountId, uid, folderId, subject } = e.detail || {};
|
|
9666
9986
|
if (!accountId || !uid) return;
|