@bobfrankston/rmfmail 1.2.316 → 1.2.318
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/.commitmsg +16 -29
- package/.llm/trusted-lists.md +6 -0
- package/client/app.bundle.js +159 -91
- package/client/app.bundle.js.map +2 -2
- package/client/components/calendar-sidebar.js +210 -132
- package/client/components/calendar-sidebar.js.map +1 -1
- package/client/components/calendar-sidebar.ts +193 -117
- package/client/index.html +6 -6
- package/npmchanges.md +75 -0
- package/package.json +5 -5
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-service/google-sync.d.ts +5 -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 +6 -0
- package/packages/mailx-service/index.d.ts +4 -0
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +19 -8
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +23 -8
- package/packages/mailx-service/package.json +1 -1
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store-web/package.json +1 -1
- package/packages/mailx-types/mailx-api.d.ts +2 -0
- package/packages/mailx-types/mailx-api.d.ts.map +1 -1
- package/packages/mailx-types/mailx-api.ts +2 -1
- package/packages/mailx-types/package.json +1 -1
package/.commitmsg
CHANGED
|
@@ -1,32 +1,19 @@
|
|
|
1
|
-
New event:
|
|
1
|
+
New event: choose the calendar; pushes go to the event's own calendar
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Bob 2026-09-10: "when creating an event I should be able to specify
|
|
4
|
+
which calendar. I want to start making more use of shared calendars."
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
text controls (TEXTAREA, text-like INPUT) now keep the native menu, which
|
|
11
|
-
already carries Cut/Copy/Paste/Select all for a text field; everything
|
|
12
|
-
else is unchanged. (msger's compose items — Bold, Italic, View source —
|
|
13
|
-
also appear on those fields via its IsEditable gate and forward to the
|
|
14
|
-
compose frame, which is a no-op outside compose; cosmetic, noted.)
|
|
6
|
+
The New event dialog gains a Calendar picker listing the calendars
|
|
7
|
+
Google says the reader can write to (accessRole owner or writer, now
|
|
8
|
+
carried by listCalendars → getCalendars), personal first, shown only
|
|
9
|
+
when there is more than one; the last choice is remembered per machine.
|
|
10
|
+
The status line names the calendar the event went to.
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
it at the next 5-minute throttled refresh. New refreshCalendarNow IPC
|
|
25
|
-
(service, jsonrpc, mailxapi.js, api-client, MailxApi, Android stub that
|
|
26
|
-
says ok:false) rewinds the throttle once and goes through
|
|
27
|
-
getCalendarEvents, so the in-flight dedup and the calendarUpdated emit
|
|
28
|
-
stay in one place; a 20 s floor of its own keeps it from reopening the
|
|
29
|
-
quota hole the throttle closed. The sidebar arms it after a create: on
|
|
30
|
-
the next window focus, and at 45 s / 2 min / 5 min in case the browser
|
|
31
|
-
sits on another monitor. mailx-types 0.1.93, mailx-service 0.1.36,
|
|
32
|
-
mailx-store-web 0.1.114.
|
|
12
|
+
Underneath, a real bug: the store-sync drain dropped calendarId and sent
|
|
13
|
+
every create, update and delete to "primary". A create could not land
|
|
14
|
+
anywhere else, and an update or delete of an event on a shared calendar
|
|
15
|
+
was a 404 retried on every poll. createCalendarEventLocal takes
|
|
16
|
+
calendarId, the row and the queue payload carry it, update/delete
|
|
17
|
+
payloads carry the row's, and the drain passes it to all three Google
|
|
18
|
+
calls. Rows pulled before today already hold the id the pull recorded.
|
|
19
|
+
mailx-types 0.1.95, mailx-service 0.1.37.
|
package/.llm/trusted-lists.md
CHANGED
|
@@ -57,3 +57,9 @@ tests/trust.test.ts, .commitmsg, TODO.md C165, root version 1.2.311. Then rebuil
|
|
|
57
57
|
- Google account: buildGoogleEventUrl adds authuser=<primary calendar email> (getPrimaryAccount("calendar")).
|
|
58
58
|
- Immediate update: refreshCalendarNow IPC (6 edits) + armPostCreateRefresh() in calendar-sidebar.ts (focus + 45s/2m/5m). Service floor 20s.
|
|
59
59
|
- Status: compiled, bundles built, rebuild.cmd next, then restart.
|
|
60
|
+
- v1.2.316 published (spelling native menu, authuser, refreshCalendarNow). Superseded same morning:
|
|
61
|
+
New event now SAVES locally via createCalendarEvent (existing IPC + store-sync push, never called before);
|
|
62
|
+
buildGoogleEventUrl/authuser/post-create timers removed. viewPinned + followToday + midnight rollover.
|
|
63
|
+
Refresh button + window focus call refreshCalendarNow. Tooltips on all sidebar controls; event rows:
|
|
64
|
+
click = edit in Google, right-click = Delete "<title>" (deleteCalendarEvent). Publishing as v1.2.317.
|
|
65
|
+
- v1.2.318: New event Calendar picker (writable calendars via accessRole from listCalendars). Store-sync drain now passes calendarId to create/update/delete (was always "primary" — shared-calendar edits 404'd forever). Google: create/subscribe/share calendars on calendar.google.com; rmfmail lists the ones selected there.
|
package/client/app.bundle.js
CHANGED
|
@@ -6668,6 +6668,25 @@ __export(calendar_sidebar_exports, {
|
|
|
6668
6668
|
isCalendarSidebarOn: () => isCalendarSidebarOn,
|
|
6669
6669
|
showCalendarSidebar: () => showCalendarSidebar
|
|
6670
6670
|
});
|
|
6671
|
+
function followToday() {
|
|
6672
|
+
if (viewPinned)
|
|
6673
|
+
return;
|
|
6674
|
+
const t = /* @__PURE__ */ new Date();
|
|
6675
|
+
viewYear = t.getFullYear();
|
|
6676
|
+
viewMonth = t.getMonth();
|
|
6677
|
+
viewDay = t.getDate();
|
|
6678
|
+
}
|
|
6679
|
+
function armMidnightRollover() {
|
|
6680
|
+
const now = /* @__PURE__ */ new Date();
|
|
6681
|
+
const next = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 5);
|
|
6682
|
+
window.setTimeout(() => {
|
|
6683
|
+
void refresh();
|
|
6684
|
+
armMidnightRollover();
|
|
6685
|
+
}, next.getTime() - now.getTime());
|
|
6686
|
+
}
|
|
6687
|
+
function writableCalendars(list) {
|
|
6688
|
+
return list.filter((c) => c.accessRole === "owner" || c.accessRole === "writer").sort((a, b) => (a.primary ? 0 : 1) - (b.primary ? 0 : 1) || a.name.localeCompare(b.name));
|
|
6689
|
+
}
|
|
6671
6690
|
function calendarKind(id, primary) {
|
|
6672
6691
|
if (primary)
|
|
6673
6692
|
return "personal";
|
|
@@ -6747,7 +6766,7 @@ async function renderCalendarList() {
|
|
|
6747
6766
|
host.innerHTML = "";
|
|
6748
6767
|
} else {
|
|
6749
6768
|
const sorted = [...list].sort((a, b) => (a.primary ? 0 : 1) - (b.primary ? 0 : 1) || a.name.localeCompare(b.name));
|
|
6750
|
-
host.innerHTML = sorted.map((c) => `<label class="cal-side-cal-row" title="${escapeHtml6(c.name)}">
|
|
6769
|
+
host.innerHTML = sorted.map((c) => `<label class="cal-side-cal-row" title="${escapeHtml6(c.name)} \u2014 untick to hide its events in this list. Which calendars are listed is set in Google Calendar: the ones selected there appear; use Refresh after changing it.">
|
|
6751
6770
|
<input type="checkbox" class="cal-side-cal-check" data-cal-id="${escapeHtml6(c.id)}" ${hiddenCalendars.has(c.id) ? "" : "checked"}>
|
|
6752
6771
|
${calIconHtml(c)}
|
|
6753
6772
|
<span class="cal-side-cal-name">${escapeHtml6(c.name)}</span>
|
|
@@ -6956,7 +6975,7 @@ function renderEvents(events) {
|
|
|
6956
6975
|
<span class="cal-side-event-title cal-side-event-holiday-title" title="${escapeHtml6(e.title)}">${calIconHtml(info)} ${escapeHtml6(e.title)}</span>
|
|
6957
6976
|
</div>`;
|
|
6958
6977
|
} else {
|
|
6959
|
-
const titleAttr = link ? 'title="Click to open in Google Calendar"' : "";
|
|
6978
|
+
const titleAttr = link ? 'title="Click to open in Google Calendar to edit it. Right-click for more."' : 'title="Saved in rmfmail, syncing to Google \u2014 it can be opened there once it has synced. Right-click to delete."';
|
|
6960
6979
|
html += `<div class="cal-side-event" data-id="${e.id}"${recurAttr} data-link="${escapeHtml6(link)}" ${titleAttr}>
|
|
6961
6980
|
${calIconHtml(info)}
|
|
6962
6981
|
<span class="cal-side-event-time">${escapeHtml6(formatTime(e))}</span>
|
|
@@ -7001,18 +7020,43 @@ function renderEvents(events) {
|
|
|
7001
7020
|
el.addEventListener("contextmenu", (e) => {
|
|
7002
7021
|
e.preventDefault();
|
|
7003
7022
|
const link = el.dataset.link;
|
|
7023
|
+
const uuid = el.dataset.id || "";
|
|
7024
|
+
const title = el.querySelector(".cal-side-event-title")?.textContent?.trim() || "this event";
|
|
7004
7025
|
const items = [];
|
|
7005
7026
|
if (link)
|
|
7006
7027
|
items.push({
|
|
7007
|
-
label: "
|
|
7028
|
+
label: `Edit "${title}" in Google Calendar`,
|
|
7008
7029
|
action: () => openInBrowser(link)
|
|
7009
7030
|
});
|
|
7010
7031
|
items.push({
|
|
7011
7032
|
label: "Open Google Calendar",
|
|
7012
7033
|
action: () => openInBrowser("https://calendar.google.com/")
|
|
7013
7034
|
});
|
|
7014
|
-
if (
|
|
7015
|
-
|
|
7035
|
+
if (uuid) {
|
|
7036
|
+
items.push({ label: "", action: () => {
|
|
7037
|
+
}, separator: true });
|
|
7038
|
+
items.push({
|
|
7039
|
+
label: `Delete "${title}"`,
|
|
7040
|
+
tooltip: "Removes it from your calendar in rmfmail now and from Google when it syncs.",
|
|
7041
|
+
action: async () => {
|
|
7042
|
+
if (!await confirmAt(e.clientX, e.clientY, `Delete "${title}"`))
|
|
7043
|
+
return;
|
|
7044
|
+
try {
|
|
7045
|
+
await deleteCalendarEvent(uuid);
|
|
7046
|
+
const st = document.getElementById("status-sync");
|
|
7047
|
+
if (st)
|
|
7048
|
+
st.textContent = `Deleted event: ${title}`;
|
|
7049
|
+
await refresh();
|
|
7050
|
+
} catch (err) {
|
|
7051
|
+
const st = document.getElementById("status-sync");
|
|
7052
|
+
if (st)
|
|
7053
|
+
st.textContent = `Couldn't delete "${title}": ${err?.message || err}`;
|
|
7054
|
+
console.error("[calendar] delete failed:", err);
|
|
7055
|
+
}
|
|
7056
|
+
}
|
|
7057
|
+
});
|
|
7058
|
+
}
|
|
7059
|
+
showContextMenu(e.clientX, e.clientY, items, { editTarget: e.target });
|
|
7016
7060
|
});
|
|
7017
7061
|
});
|
|
7018
7062
|
}
|
|
@@ -7112,6 +7156,7 @@ async function renderTasks(prefetched) {
|
|
|
7112
7156
|
});
|
|
7113
7157
|
}
|
|
7114
7158
|
async function refresh() {
|
|
7159
|
+
followToday();
|
|
7115
7160
|
renderHead();
|
|
7116
7161
|
const from = new Date(viewYear, viewMonth, viewDay);
|
|
7117
7162
|
let prefetchedTasks;
|
|
@@ -7145,12 +7190,44 @@ function openNewEventDialog() {
|
|
|
7145
7190
|
heading.textContent = "New event";
|
|
7146
7191
|
const hint = document.createElement("div");
|
|
7147
7192
|
hint.style.cssText = "font-size:0.9em;color:var(--color-text-muted);";
|
|
7148
|
-
hint.textContent = "Describe the event in plain English \u2014 AI extracts the details and
|
|
7193
|
+
hint.textContent = "Describe the event in plain English \u2014 AI extracts the details and saves it to your calendar. Edit it afterwards if it needs adjusting.";
|
|
7149
7194
|
const ta = document.createElement("textarea");
|
|
7150
7195
|
ta.style.cssText = "width:100%;min-height:120px;font:inherit;padding:8px;border-radius:4px;border:1px solid var(--color-border, #ccc);box-sizing:border-box;resize:vertical;";
|
|
7151
7196
|
ta.placeholder = "Lunch with John tomorrow at noon at Joe's Pizza";
|
|
7152
7197
|
const status = document.createElement("div");
|
|
7153
7198
|
status.style.cssText = "min-height:1.2em;font-size:0.85em;color:var(--color-text-muted);";
|
|
7199
|
+
const calRow = document.createElement("label");
|
|
7200
|
+
calRow.style.cssText = "display:flex;align-items:center;gap:8px;font-size:0.9em;";
|
|
7201
|
+
calRow.title = "The calendar this event is saved to. Only calendars you can write to are listed; to add or share a calendar, do that in Google Calendar and press Refresh.";
|
|
7202
|
+
const calSelect = document.createElement("select");
|
|
7203
|
+
calSelect.style.cssText = "flex:1;font:inherit;padding:4px;";
|
|
7204
|
+
calRow.append("Calendar", calSelect);
|
|
7205
|
+
calRow.hidden = true;
|
|
7206
|
+
const fillCalendars = (list) => {
|
|
7207
|
+
const writable = writableCalendars(list);
|
|
7208
|
+
calSelect.innerHTML = "";
|
|
7209
|
+
let last = "";
|
|
7210
|
+
try {
|
|
7211
|
+
last = localStorage.getItem(LAST_CALENDAR_PREF) || "";
|
|
7212
|
+
} catch {
|
|
7213
|
+
}
|
|
7214
|
+
for (const c of writable) {
|
|
7215
|
+
const opt = document.createElement("option");
|
|
7216
|
+
opt.value = c.id;
|
|
7217
|
+
opt.textContent = c.primary ? `${c.name} (personal)` : c.name;
|
|
7218
|
+
if (c.id === last || !last && c.primary)
|
|
7219
|
+
opt.selected = true;
|
|
7220
|
+
calSelect.append(opt);
|
|
7221
|
+
}
|
|
7222
|
+
calRow.hidden = writable.length < 2;
|
|
7223
|
+
};
|
|
7224
|
+
if (calendarList.length)
|
|
7225
|
+
fillCalendars(calendarList);
|
|
7226
|
+
else
|
|
7227
|
+
getCalendars().then((list) => {
|
|
7228
|
+
calendarList = list;
|
|
7229
|
+
fillCalendars(calendarList);
|
|
7230
|
+
}).catch((e) => console.warn("[calendar] calendar list for the picker failed:", e?.message || e));
|
|
7154
7231
|
const btnRow = document.createElement("div");
|
|
7155
7232
|
btnRow.style.cssText = "display:flex;gap:8px;justify-content:flex-end;";
|
|
7156
7233
|
const cancelBtn = document.createElement("button");
|
|
@@ -7160,7 +7237,7 @@ function openNewEventDialog() {
|
|
|
7160
7237
|
createBtn.textContent = "Create";
|
|
7161
7238
|
createBtn.style.cssText = "padding:6px 14px;font-weight:500;";
|
|
7162
7239
|
btnRow.append(cancelBtn, createBtn);
|
|
7163
|
-
panel.append(heading, hint, ta, status, btnRow);
|
|
7240
|
+
panel.append(heading, hint, ta, calRow, status, btnRow);
|
|
7164
7241
|
backdrop.append(panel);
|
|
7165
7242
|
document.body.append(backdrop);
|
|
7166
7243
|
setTimeout(() => ta.focus(), 0);
|
|
@@ -7216,92 +7293,67 @@ function openNewEventDialog() {
|
|
|
7216
7293
|
return;
|
|
7217
7294
|
}
|
|
7218
7295
|
}
|
|
7219
|
-
const api = window.mailxapi;
|
|
7220
7296
|
const targets = events.length ? events : [null];
|
|
7221
|
-
|
|
7297
|
+
const saved = [];
|
|
7298
|
+
const calendarId = calSelect.value || "primary";
|
|
7299
|
+
const calendarName = calSelect.selectedOptions[0]?.textContent || "";
|
|
7222
7300
|
try {
|
|
7223
|
-
|
|
7224
|
-
} catch
|
|
7225
|
-
console.warn("[calendar] primary account lookup failed, opening without authuser:", e?.message || e);
|
|
7301
|
+
localStorage.setItem(LAST_CALENDAR_PREF, calendarId);
|
|
7302
|
+
} catch {
|
|
7226
7303
|
}
|
|
7227
7304
|
for (const ev of targets) {
|
|
7228
|
-
const
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7305
|
+
const row = { ...eventToLocalRow(ev, text), calendarId };
|
|
7306
|
+
try {
|
|
7307
|
+
await createCalendarEvent(row);
|
|
7308
|
+
saved.push(row.title);
|
|
7309
|
+
} catch (e) {
|
|
7310
|
+
status.textContent = `Couldn't save "${row.title}": ${e?.message || e}`;
|
|
7311
|
+
createBtn.disabled = false;
|
|
7312
|
+
cancelBtn.disabled = false;
|
|
7313
|
+
return;
|
|
7314
|
+
}
|
|
7233
7315
|
}
|
|
7234
|
-
armPostCreateRefresh();
|
|
7235
7316
|
close();
|
|
7317
|
+
const st = document.getElementById("status-sync");
|
|
7318
|
+
const where = calendarName && !calRow.hidden ? ` to ${calendarName}` : "";
|
|
7319
|
+
if (st)
|
|
7320
|
+
st.textContent = saved.length === 1 ? `Saved event${where}: ${saved[0]}` : `Saved ${saved.length} events${where}: ${saved.join("; ")}`;
|
|
7321
|
+
await refresh();
|
|
7236
7322
|
});
|
|
7237
7323
|
}
|
|
7238
|
-
function
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
postCreateFocusHandler = () => {
|
|
7250
|
-
void pull("window focus");
|
|
7251
|
-
};
|
|
7252
|
-
window.addEventListener("focus", postCreateFocusHandler);
|
|
7253
|
-
postCreateTimers = POST_CREATE_REFRESH_DELAYS_MS.map((ms, i) => window.setTimeout(() => {
|
|
7254
|
-
void pull(`${ms / 1e3} s after create`);
|
|
7255
|
-
if (i === POST_CREATE_REFRESH_DELAYS_MS.length - 1)
|
|
7256
|
-
disarmPostCreateRefresh();
|
|
7257
|
-
}, ms));
|
|
7258
|
-
}
|
|
7259
|
-
function disarmPostCreateRefresh() {
|
|
7260
|
-
for (const t of postCreateTimers)
|
|
7261
|
-
window.clearTimeout(t);
|
|
7262
|
-
postCreateTimers = [];
|
|
7263
|
-
if (postCreateFocusHandler)
|
|
7264
|
-
window.removeEventListener("focus", postCreateFocusHandler);
|
|
7265
|
-
postCreateFocusHandler = null;
|
|
7266
|
-
}
|
|
7267
|
-
function buildGoogleEventUrl(event, fallbackText, authuser = "") {
|
|
7268
|
-
const base = "https://calendar.google.com/calendar/render?action=TEMPLATE";
|
|
7269
|
-
const params = [];
|
|
7270
|
-
if (authuser)
|
|
7271
|
-
params.push(`authuser=${encodeURIComponent(authuser)}`);
|
|
7272
|
-
const fmt = (iso) => {
|
|
7273
|
-
const m = iso.match(/(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2}))?)?/);
|
|
7274
|
-
if (!m)
|
|
7275
|
-
return "";
|
|
7276
|
-
const [, y, mo, d, hh, mm, ss] = m;
|
|
7277
|
-
const date = `${y}${mo}${d}`;
|
|
7278
|
-
if (!hh)
|
|
7279
|
-
return date;
|
|
7280
|
-
return `${date}T${hh}${mm}${ss || "00"}`;
|
|
7281
|
-
};
|
|
7282
|
-
if (event?.title)
|
|
7283
|
-
params.push(`text=${encodeURIComponent(event.title)}`);
|
|
7324
|
+
function eventToLocalRow(event, fallbackText) {
|
|
7325
|
+
const startOfToday = /* @__PURE__ */ new Date();
|
|
7326
|
+
startOfToday.setHours(0, 0, 0, 0);
|
|
7327
|
+
if (!event?.startISO) {
|
|
7328
|
+
return { title: (event?.title || fallbackText).slice(0, 200), startMs: startOfToday.getTime(), endMs: startOfToday.getTime() + 864e5, allDay: true };
|
|
7329
|
+
}
|
|
7330
|
+
const allDay = !!event.allDay || !event.startISO.includes("T");
|
|
7331
|
+
const startMs = allDay ? (/* @__PURE__ */ new Date(event.startISO.slice(0, 10) + "T00:00:00")).getTime() : wallClockToMs(event.startISO, event.timeZone);
|
|
7332
|
+
let endMs;
|
|
7333
|
+
if (event.endISO)
|
|
7334
|
+
endMs = allDay ? (/* @__PURE__ */ new Date(event.endISO.slice(0, 10) + "T00:00:00")).getTime() : wallClockToMs(event.endISO, event.timeZone);
|
|
7284
7335
|
else
|
|
7285
|
-
|
|
7286
|
-
if (
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7336
|
+
endMs = startMs + (allDay ? 864e5 : 36e5);
|
|
7337
|
+
if (allDay && endMs <= startMs)
|
|
7338
|
+
endMs = startMs + 864e5;
|
|
7339
|
+
return { title: event.title || fallbackText.slice(0, 200), startMs, endMs, allDay, location: event.location || void 0, notes: event.notes || void 0 };
|
|
7340
|
+
}
|
|
7341
|
+
function wallClockToMs(iso, timeZone) {
|
|
7342
|
+
const local = new Date(iso).getTime();
|
|
7343
|
+
if (!timeZone)
|
|
7344
|
+
return local;
|
|
7345
|
+
try {
|
|
7346
|
+
let ms = local;
|
|
7347
|
+
for (let i = 0; i < 2; i++) {
|
|
7348
|
+
const parts = new Intl.DateTimeFormat("en-US", { timeZone, hourCycle: "h23", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" }).formatToParts(new Date(ms)).reduce((o, p) => (o[p.type] = p.value, o), {});
|
|
7349
|
+
const asIfLocal = (/* @__PURE__ */ new Date(`${parts.year}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}:${parts.second}`)).getTime();
|
|
7350
|
+
ms += local - asIfLocal;
|
|
7296
7351
|
}
|
|
7352
|
+
return ms;
|
|
7353
|
+
} catch (e) {
|
|
7354
|
+
console.warn(`[calendar] unknown time zone "${timeZone}", reading the time as local:`, e?.message || e);
|
|
7355
|
+
return local;
|
|
7297
7356
|
}
|
|
7298
|
-
if (event?.location)
|
|
7299
|
-
params.push(`location=${encodeURIComponent(event.location)}`);
|
|
7300
|
-
if (event?.notes)
|
|
7301
|
-
params.push(`details=${encodeURIComponent(event.notes)}`);
|
|
7302
|
-
if (event?.timeZone)
|
|
7303
|
-
params.push(`ctz=${encodeURIComponent(event.timeZone)}`);
|
|
7304
|
-
return `${base}&${params.join("&")}`;
|
|
7305
7357
|
}
|
|
7306
7358
|
async function showCalendarSidebar() {
|
|
7307
7359
|
const el = document.getElementById("calendar-sidebar");
|
|
@@ -7345,6 +7397,7 @@ function initCalendarSidebar() {
|
|
|
7345
7397
|
el.addEventListener("click", fn);
|
|
7346
7398
|
};
|
|
7347
7399
|
wireOnce("cal-side-prev", () => {
|
|
7400
|
+
viewPinned = true;
|
|
7348
7401
|
const d = new Date(viewYear, viewMonth, viewDay - 1);
|
|
7349
7402
|
viewYear = d.getFullYear();
|
|
7350
7403
|
viewMonth = d.getMonth();
|
|
@@ -7352,6 +7405,7 @@ function initCalendarSidebar() {
|
|
|
7352
7405
|
refresh();
|
|
7353
7406
|
});
|
|
7354
7407
|
wireOnce("cal-side-next", () => {
|
|
7408
|
+
viewPinned = true;
|
|
7355
7409
|
const d = new Date(viewYear, viewMonth, viewDay + 1);
|
|
7356
7410
|
viewYear = d.getFullYear();
|
|
7357
7411
|
viewMonth = d.getMonth();
|
|
@@ -7359,12 +7413,22 @@ function initCalendarSidebar() {
|
|
|
7359
7413
|
refresh();
|
|
7360
7414
|
});
|
|
7361
7415
|
wireOnce("cal-side-today", () => {
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
viewMonth = t.getMonth();
|
|
7365
|
-
viewDay = t.getDate();
|
|
7416
|
+
viewPinned = false;
|
|
7417
|
+
followToday();
|
|
7366
7418
|
refresh();
|
|
7367
7419
|
});
|
|
7420
|
+
if (!window.__mailxCalRolloverArmed) {
|
|
7421
|
+
window.__mailxCalRolloverArmed = true;
|
|
7422
|
+
armMidnightRollover();
|
|
7423
|
+
window.addEventListener("focus", () => {
|
|
7424
|
+
if (!isCalendarSidebarOn())
|
|
7425
|
+
return;
|
|
7426
|
+
void refreshCalendarNow().then((r) => {
|
|
7427
|
+
if (!r?.ok && r?.reason && !/refreshed \d+ s ago/.test(r.reason))
|
|
7428
|
+
console.log(`[calendar] focus refresh not started: ${r.reason}`);
|
|
7429
|
+
}).catch((e) => console.warn("[calendar] focus refresh failed:", e?.message || e));
|
|
7430
|
+
});
|
|
7431
|
+
}
|
|
7368
7432
|
wireOnce("cal-side-new", () => {
|
|
7369
7433
|
openNewEventDialog();
|
|
7370
7434
|
});
|
|
@@ -7399,7 +7463,12 @@ function initCalendarSidebar() {
|
|
|
7399
7463
|
if (btn)
|
|
7400
7464
|
btn.disabled = true;
|
|
7401
7465
|
try {
|
|
7402
|
-
await Promise.all([renderCalendarList(), refresh()]);
|
|
7466
|
+
const [, , pulled] = await Promise.all([renderCalendarList(), refresh(), refreshCalendarNow()]);
|
|
7467
|
+
if (!pulled?.ok) {
|
|
7468
|
+
const st = document.getElementById("status-sync");
|
|
7469
|
+
if (st)
|
|
7470
|
+
st.textContent = `Calendar not pulled from Google: ${pulled?.reason || "unknown"}`;
|
|
7471
|
+
}
|
|
7403
7472
|
} finally {
|
|
7404
7473
|
setTimeout(() => {
|
|
7405
7474
|
btn?.classList.remove("cal-side-refreshing");
|
|
@@ -7493,7 +7562,7 @@ function initCalendarSidebar() {
|
|
|
7493
7562
|
}
|
|
7494
7563
|
}
|
|
7495
7564
|
}
|
|
7496
|
-
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, TASK_DELETE_NOTE
|
|
7565
|
+
var SIDEBAR_PREF, SHOW_RECURRING_PREF, SHOW_DONE_PREF, HORIZON_DAYS_PREF, HORIZON_DEFAULT_DAYS, HOLIDAY_HORIZON_MS, viewYear, viewMonth, viewDay, viewPinned, lastEvents, lastOverdueTasks, LAST_CALENDAR_PREF, calById, calendarList, hiddenCalendars, selectedTaskUuids, TASK_DELETE_NOTE;
|
|
7497
7566
|
var init_calendar_sidebar = __esm({
|
|
7498
7567
|
"client/components/calendar-sidebar.js"() {
|
|
7499
7568
|
"use strict";
|
|
@@ -7508,16 +7577,15 @@ var init_calendar_sidebar = __esm({
|
|
|
7508
7577
|
viewYear = (/* @__PURE__ */ new Date()).getFullYear();
|
|
7509
7578
|
viewMonth = (/* @__PURE__ */ new Date()).getMonth();
|
|
7510
7579
|
viewDay = (/* @__PURE__ */ new Date()).getDate();
|
|
7580
|
+
viewPinned = false;
|
|
7511
7581
|
lastEvents = [];
|
|
7512
7582
|
lastOverdueTasks = [];
|
|
7583
|
+
LAST_CALENDAR_PREF = "mailx.calendar.lastCreateCalendar";
|
|
7513
7584
|
calById = /* @__PURE__ */ new Map();
|
|
7514
7585
|
calendarList = [];
|
|
7515
7586
|
hiddenCalendars = /* @__PURE__ */ new Set();
|
|
7516
7587
|
selectedTaskUuids = /* @__PURE__ */ new Set();
|
|
7517
7588
|
TASK_DELETE_NOTE = "Removes it from Google Tasks. To mark it done instead, use the checkbox.";
|
|
7518
|
-
POST_CREATE_REFRESH_DELAYS_MS = [45e3, 12e4, 3e5];
|
|
7519
|
-
postCreateTimers = [];
|
|
7520
|
-
postCreateFocusHandler = null;
|
|
7521
7589
|
}
|
|
7522
7590
|
});
|
|
7523
7591
|
|