@luckydye/calendar 1.3.2 → 1.4.0

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/src/app.ts CHANGED
@@ -1,32 +1,36 @@
1
- import { CalendarViewElement } from "./lib.ts";
1
+ import { activeCalendarStore } from "./ActiveCalendarStore.js";
2
2
  import "./CalDAVConfig.ts";
3
3
  import type { CalDAVConfigElement } from "./CalDAVConfig.ts";
4
+ import { CalDAVSource } from "./CalDAVSource.js";
4
5
  import {
5
- CalendarIntegration,
6
6
  type Calendar,
7
+ CalendarIntegration,
7
8
  type CalendarSource,
8
9
  } from "./CalendarIntegration.js";
9
- import { CalDAVSource } from "./CalDAVSource.js";
10
- import { fetchICalEventsWithNotifications, parseICalEvents, parseICalEventsWithNotifications } from "./ICal.js";
11
- import { initializeTheme } from "./Theme.js";
12
- import { InMemorySource } from "./InMemorySource.js";
13
- import { GoogleCalendarSource } from "./GoogleCalendarSource.js";
14
- import { InhouseBookingSource } from "./InhouseBookingSource.js";
15
- import { fetchTimeseriesJsonEvents } from "./TimeseriesJson.js";
16
- import { queueStatus } from "./StatusMessage.js";
17
- import { activeCalendarStore } from "./ActiveCalendarStore.js";
18
10
  import type { CalendarEvent } from "./CalendarInternal.js";
19
11
  import { sanitizeEventDescription } from "./DescriptionSanitizer.js";
12
+ import { GoogleCalendarSource } from "./GoogleCalendarSource.js";
13
+ import {
14
+ fetchICalEventsWithNotifications,
15
+ parseICalEvents,
16
+ parseICalEventsWithNotifications,
17
+ } from "./ICal.js";
18
+ import { InMemorySource } from "./InMemorySource.js";
19
+ import { InhouseBookingSource } from "./InhouseBookingSource.js";
20
20
  import { registerKeybinds } from "./Keybinds.js";
21
- import "./StatusBar.js";
22
21
  import { NotificationScheduler } from "./NotificationScheduler.js";
23
- import serviceWorkerUrl from "./service-worker.js?url";
22
+ import "./StatusBar.js";
24
23
  import type { StatusBarElement } from "./StatusBar.js";
24
+ import { queueStatus } from "./StatusMessage.js";
25
+ import { initializeTheme } from "./Theme.js";
26
+ import { fetchTimeseriesJsonEvents } from "./TimeseriesJson.js";
27
+ import { CalendarViewElement } from "./lib.ts";
28
+ import serviceWorkerUrl from "./service-worker.js?url";
25
29
 
26
30
  try {
27
- customElements.define("calendar-view", CalendarViewElement);
28
- } catch(err) {
29
- console.error(err);
31
+ customElements.define("calendar-view", CalendarViewElement);
32
+ } catch (err) {
33
+ console.error(err);
30
34
  }
31
35
 
32
36
  // Initialize theme on app start
@@ -52,9 +56,8 @@ function getPromptApiLanguageModel(): PromptApiLanguageModel | null {
52
56
  const fromGlobal = (globalThis as { LanguageModel?: unknown }).LanguageModel;
53
57
  if (fromGlobal) return fromGlobal as PromptApiLanguageModel;
54
58
 
55
- const fromWindow = (
56
- window as { ai?: { languageModel?: unknown } }
57
- ).ai?.languageModel;
59
+ const fromWindow = (window as { ai?: { languageModel?: unknown } }).ai
60
+ ?.languageModel;
58
61
  if (!fromWindow) return null;
59
62
  return fromWindow as PromptApiLanguageModel;
60
63
  }
@@ -80,7 +83,10 @@ function abortActiveDescriptionSummary(): void {
80
83
  summaryRequestToken++;
81
84
  }
82
85
 
83
- async function streamDescriptionSummary(key: string, description: string): Promise<void> {
86
+ async function streamDescriptionSummary(
87
+ key: string,
88
+ description: string,
89
+ ): Promise<void> {
84
90
  const sanitizedDescription = sanitizeEventDescription(description);
85
91
  abortActiveDescriptionSummary();
86
92
  activeSummaryKey = key;
@@ -89,7 +95,10 @@ async function streamDescriptionSummary(key: string, description: string): Promi
89
95
 
90
96
  const languageModel = getPromptApiLanguageModel();
91
97
  if (!languageModel) {
92
- calendarElement.failDescriptionSummary(key, "Prompt API not available in this browser.");
98
+ calendarElement.failDescriptionSummary(
99
+ key,
100
+ "Prompt API not available in this browser.",
101
+ );
93
102
  activeSummaryKey = null;
94
103
  return;
95
104
  }
@@ -167,16 +176,23 @@ async function streamDescriptionSummary(key: string, description: string): Promi
167
176
  }
168
177
  }
169
178
 
170
- registerKeybinds(
171
- [
172
- { key: "r", cmdOrCtrl: true, shift: true, action: () => calendarElement.forceSync() },
173
- { key: "c", cmdOrCtrl: true, action: () => calendarElement.copySelectedEvents() },
174
- { key: "Backspace", action: () => calendarElement.deleteSelectedEvents() },
175
- { key: "Delete", action: () => calendarElement.deleteSelectedEvents() },
176
- { key: "Escape", action: () => calendarElement.escape() },
177
- { key: "t", action: () => calendarElement.scrollToToday() },
178
- ],
179
- );
179
+ registerKeybinds([
180
+ {
181
+ key: "r",
182
+ cmdOrCtrl: true,
183
+ shift: true,
184
+ action: () => calendarElement.forceSync(),
185
+ },
186
+ {
187
+ key: "c",
188
+ cmdOrCtrl: true,
189
+ action: () => calendarElement.copySelectedEvents(),
190
+ },
191
+ { key: "Backspace", action: () => calendarElement.deleteSelectedEvents() },
192
+ { key: "Delete", action: () => calendarElement.deleteSelectedEvents() },
193
+ { key: "Escape", action: () => calendarElement.escape() },
194
+ { key: "t", action: () => calendarElement.scrollToToday() },
195
+ ]);
180
196
 
181
197
  let workerPromise: Promise<ServiceWorkerRegistration> | undefined;
182
198
 
@@ -192,10 +208,14 @@ if ("serviceWorker" in navigator) {
192
208
  // Initialize notification scheduler
193
209
  const notificationScheduler = new NotificationScheduler(() => workerPromise);
194
210
 
195
- async function scheduleNotificationsForEvents(events: CalendarEvent[]): Promise<void> {
211
+ async function scheduleNotificationsForEvents(
212
+ events: CalendarEvent[],
213
+ ): Promise<void> {
196
214
  const now = new Date();
197
215
  const next24Hours = new Date(now.getTime() + 24 * 60 * 60 * 1000);
198
- const upcoming = events.filter(e => e.start >= now && e.start <= next24Hours && e.reminders?.length);
216
+ const upcoming = events.filter(
217
+ (e) => e.start >= now && e.start <= next24Hours && e.reminders?.length,
218
+ );
199
219
  for (const event of upcoming) {
200
220
  await notificationScheduler.scheduleEventNotifications(event);
201
221
  }
@@ -236,9 +256,13 @@ async function requestNotificationPermission() {
236
256
  }
237
257
  }
238
258
 
239
- calendarElement.addEventListener("event-click", () => {
240
- requestNotificationPermission();
241
- }, { once: true });
259
+ calendarElement.addEventListener(
260
+ "event-click",
261
+ () => {
262
+ requestNotificationPermission();
263
+ },
264
+ { once: true },
265
+ );
242
266
 
243
267
  calendarElement.addEventListener("request-description-summary", (e: Event) => {
244
268
  const { key, description } = (e as CustomEvent).detail as {
@@ -258,7 +282,10 @@ calendarElement.addEventListener("cancel-description-summary", (e: Event) => {
258
282
  // Load iCal files
259
283
  const integration = new CalendarIntegration(calendar);
260
284
 
261
- async function sync(cal: Calendar, options?: { force?: boolean }): Promise<void> {
285
+ async function sync(
286
+ cal: Calendar,
287
+ options?: { force?: boolean },
288
+ ): Promise<void> {
262
289
  const events = await integration.sync(cal, options);
263
290
  await scheduleNotificationsForEvents(events);
264
291
  }
@@ -270,9 +297,14 @@ let mutationsSettledResolve: (() => void) | null = null;
270
297
  let mutationsSettledPromise: Promise<void> | null = null;
271
298
  const dirtyCalendars = new Set<Calendar>();
272
299
 
273
- async function withMutation(cal: Calendar, fn: () => Promise<void>): Promise<void> {
300
+ async function withMutation(
301
+ cal: Calendar,
302
+ fn: () => Promise<void>,
303
+ ): Promise<void> {
274
304
  if (pendingMutations === 0) {
275
- mutationsSettledPromise = new Promise(r => { mutationsSettledResolve = r; });
305
+ mutationsSettledPromise = new Promise((r) => {
306
+ mutationsSettledResolve = r;
307
+ });
276
308
  }
277
309
  pendingMutations++;
278
310
  try {
@@ -304,11 +336,11 @@ async function waitForMutations(): Promise<void> {
304
336
  await calendar.initPromise;
305
337
 
306
338
  // Register sample source
307
- const sampleSource = new InMemorySource('sample', "Sample Events", '#10b981');
339
+ const sampleSource = new InMemorySource("sample", "Sample Events", "#10b981");
308
340
  const sampleCalendar = createInMemoryCalendar(sampleSource);
309
341
  loadedCalendars.set(sampleCalendar.id, sampleCalendar);
310
342
  activeCalendarStore.registerCalendar(sampleCalendar);
311
-
343
+
312
344
  // Create sidebar with CalDAV config
313
345
  const sidebar = document.createElement("div");
314
346
  sidebar.slot = "sidebar";
@@ -344,7 +376,6 @@ async function waitForMutations(): Promise<void> {
344
376
  updateActiveCalendarColor();
345
377
  })();
346
378
 
347
-
348
379
  // Store for loaded calendars
349
380
  const loadedCalendars: Map<string, Calendar> = new Map();
350
381
  let config: CalDAVConfigElement;
@@ -389,7 +420,7 @@ async function createCalDAVCalendars(
389
420
  username,
390
421
  password,
391
422
  },
392
- source.enabled
423
+ source.enabled,
393
424
  );
394
425
 
395
426
  // Fetch all calendars and the current user's email from the CalDAV server
@@ -413,7 +444,11 @@ async function createCalDAVCalendars(
413
444
  calendarUrl: calInfo.url,
414
445
 
415
446
  async fetchEvents(): Promise<CalendarEvent[]> {
416
- return caldavSource.fetchEventsForCalendar(calInfo.url, calInfo.displayName, calInfo.color);
447
+ return caldavSource.fetchEventsForCalendar(
448
+ calInfo.url,
449
+ calInfo.displayName,
450
+ calInfo.color,
451
+ );
417
452
  },
418
453
 
419
454
  async createEvent(
@@ -426,12 +461,15 @@ async function createCalDAVCalendars(
426
461
  event: CalendarEvent,
427
462
  updates: Partial<CalendarEvent>,
428
463
  ): Promise<CalendarEvent> {
429
- return caldavSource.updateEvent(event, { ...updates, calendarId: calInfo.url });
464
+ return caldavSource.updateEvent(event, {
465
+ ...updates,
466
+ calendarId: calInfo.url,
467
+ });
430
468
  },
431
469
 
432
470
  async deleteEvent(id: string): Promise<void> {
433
- const eventUrl = `${calInfo.url.replace(/\/$/, '')}/${id}.ics`;
434
- await caldavSource.request(eventUrl, 'DELETE', undefined, {});
471
+ const eventUrl = `${calInfo.url.replace(/\/$/, "")}/${id}.ics`;
472
+ await caldavSource.request(eventUrl, "DELETE", undefined, {});
435
473
  },
436
474
  });
437
475
  }
@@ -451,7 +489,11 @@ function createICalCalendar(source: CalendarSource): Calendar {
451
489
  sourceType: "ical",
452
490
 
453
491
  async fetchEvents(): Promise<CalendarEvent[]> {
454
- const result = await fetchICalEventsWithNotifications(source.credentials, source.color, source.name);
492
+ const result = await fetchICalEventsWithNotifications(
493
+ source.credentials,
494
+ source.color,
495
+ source.name,
496
+ );
455
497
  return result.events;
456
498
  },
457
499
  // iCal calendars are read-only by default
@@ -471,7 +513,11 @@ function createTimeseriesJsonCalendar(source: CalendarSource): Calendar {
471
513
 
472
514
  async fetchEvents(): Promise<CalendarEvent[]> {
473
515
  return fetchTimeseriesJsonEvents(
474
- source.credentials as { url: string; timestampField?: string; titleField?: string },
516
+ source.credentials as {
517
+ url: string;
518
+ timestampField?: string;
519
+ titleField?: string;
520
+ },
475
521
  source.color,
476
522
  source.name,
477
523
  source.id,
@@ -494,7 +540,7 @@ function createGoogleCalendar(source: CalendarSource): Calendar {
494
540
  tokenExpiry: source.credentials.tokenExpiry,
495
541
  calendarId: calendarId,
496
542
  },
497
- source.enabled
543
+ source.enabled,
498
544
  );
499
545
 
500
546
  return {
@@ -542,7 +588,7 @@ function createInhouseCalendar(source: CalendarSource): Calendar {
542
588
  unitId: source.credentials.unitId,
543
589
  startHour: source.credentials.startHour,
544
590
  },
545
- source.enabled
591
+ source.enabled,
546
592
  );
547
593
 
548
594
  return {
@@ -710,7 +756,9 @@ async function syncCalDAV(force = false) {
710
756
  }
711
757
  } else if (source.type === "timeseries-json") {
712
758
  if (!source.credentials?.url) {
713
- console.warn(`Skipping Timeseries JSON source ${source.name}: missing URL`);
759
+ console.warn(
760
+ `Skipping Timeseries JSON source ${source.name}: missing URL`,
761
+ );
714
762
  continue;
715
763
  }
716
764
 
@@ -721,11 +769,16 @@ async function syncCalDAV(force = false) {
721
769
  try {
722
770
  await sync(calendar, { force });
723
771
  } catch (error) {
724
- console.error(`Sync error for Timeseries JSON source ${source.name}:`, error);
772
+ console.error(
773
+ `Sync error for Timeseries JSON source ${source.name}:`,
774
+ error,
775
+ );
725
776
  }
726
777
  } else if (source.type === "google") {
727
778
  if (!source.credentials?.accessToken) {
728
- console.warn(`Skipping Google Calendar source ${source.name}: missing access token`);
779
+ console.warn(
780
+ `Skipping Google Calendar source ${source.name}: missing access token`,
781
+ );
729
782
  continue;
730
783
  }
731
784
 
@@ -737,11 +790,19 @@ async function syncCalDAV(force = false) {
737
790
  try {
738
791
  await sync(calendar, { force });
739
792
  } catch (error) {
740
- console.error(`Sync error for Google Calendar source ${source.name}:`, error);
793
+ console.error(
794
+ `Sync error for Google Calendar source ${source.name}:`,
795
+ error,
796
+ );
741
797
  }
742
798
  } else if (source.type === "inhouse") {
743
- if (!source.credentials?.sessionCookie || !source.credentials?.employeeId) {
744
- console.warn(`Skipping Inhouse Booking source ${source.name}: missing session cookie or employee ID`);
799
+ if (
800
+ !source.credentials?.sessionCookie ||
801
+ !source.credentials?.employeeId
802
+ ) {
803
+ console.warn(
804
+ `Skipping Inhouse Booking source ${source.name}: missing session cookie or employee ID`,
805
+ );
745
806
  continue;
746
807
  }
747
808
 
@@ -752,7 +813,10 @@ async function syncCalDAV(force = false) {
752
813
  try {
753
814
  await sync(calendar, { force });
754
815
  } catch (error) {
755
- console.error(`Sync error for Inhouse Booking source ${source.name}:`, error);
816
+ console.error(
817
+ `Sync error for Inhouse Booking source ${source.name}:`,
818
+ error,
819
+ );
756
820
  }
757
821
  } else {
758
822
  console.warn(`Unknown source type for ${source.name}: ${source.type}`);
@@ -764,7 +828,7 @@ async function syncCalDAV(force = false) {
764
828
  updateLockedCalendars();
765
829
 
766
830
  // Push calendar list to sidebar
767
- config.calendars = Array.from(loadedCalendars.values()).map(cal => ({
831
+ config.calendars = Array.from(loadedCalendars.values()).map((cal) => ({
768
832
  id: cal.id,
769
833
  name: cal.name,
770
834
  color: cal.color,
@@ -867,7 +931,7 @@ function showProjectPickerDialog(
867
931
  const filtered = filter
868
932
  ? projects.filter((p) =>
869
933
  p.name.toLowerCase().includes(filter.toLowerCase()),
870
- )
934
+ )
871
935
  : projects;
872
936
  for (const project of filtered) {
873
937
  const item = document.createElement("button");
@@ -944,11 +1008,17 @@ calendarElement.addEventListener("create-event", async (e) => {
944
1008
  lastSynced: new Date(),
945
1009
  });
946
1010
  try {
947
- await withMutation(calendar, () => calendar.createEvent({ id, title: "New Event", start, end, isAllDay }));
1011
+ await withMutation(calendar, () =>
1012
+ calendar.createEvent({ id, title: "New Event", start, end, isAllDay }),
1013
+ );
948
1014
  } catch (error) {
949
1015
  calendarElement.internal.removeEventOptimistically(id);
950
1016
  console.error("Failed to create event:", error);
951
- queueStatus(`Failed to create event: ${error instanceof Error ? error.message : String(error)}`);
1017
+ queueStatus(
1018
+ `Failed to create event: ${
1019
+ error instanceof Error ? error.message : String(error)
1020
+ }`,
1021
+ );
952
1022
  }
953
1023
  });
954
1024
 
@@ -969,17 +1039,16 @@ calendarElement.addEventListener("update-event", async (e) => {
969
1039
  return;
970
1040
  }
971
1041
 
972
- console.log(
973
- "Updating event:",
974
- event
975
- );
1042
+ console.log("Updating event:", event);
976
1043
  try {
977
1044
  calendarElement.internal.applyEventOptimistically({ ...event, ...updates });
978
1045
  await withMutation(calendar, () => calendar.updateEvent(event, updates));
979
1046
 
980
1047
  if ("reminders" in updates) {
981
1048
  const eventWithReminders = { ...event, ...updates };
982
- await notificationScheduler.scheduleEventNotifications(eventWithReminders);
1049
+ await notificationScheduler.scheduleEventNotifications(
1050
+ eventWithReminders,
1051
+ );
983
1052
 
984
1053
  if (calendarElement.selectedEventForDetail?.id === event.id) {
985
1054
  calendarElement.selectedEventForDetail = eventWithReminders;
@@ -988,7 +1057,11 @@ calendarElement.addEventListener("update-event", async (e) => {
988
1057
  } catch (error) {
989
1058
  calendarElement.internal.applyEventOptimistically(event);
990
1059
  console.error("Failed to update event:", error);
991
- queueStatus(`Failed to update event: ${error instanceof Error ? error.message : String(error)}`);
1060
+ queueStatus(
1061
+ `Failed to update event: ${
1062
+ error instanceof Error ? error.message : String(error)
1063
+ }`,
1064
+ );
992
1065
  }
993
1066
  });
994
1067
 
@@ -1020,9 +1093,7 @@ calendarElement.addEventListener("delete-events", async (e) => {
1020
1093
  const calendar = findCalendarForEvent(calendarEvents[0]);
1021
1094
 
1022
1095
  if (!calendar) {
1023
- queueStatus(
1024
- `Cannot delete events: calendar "${calendarId}" not found.`,
1025
- );
1096
+ queueStatus(`Cannot delete events: calendar "${calendarId}" not found.`);
1026
1097
  continue;
1027
1098
  }
1028
1099
 
@@ -1050,7 +1121,11 @@ calendarElement.addEventListener("delete-events", async (e) => {
1050
1121
  calendarElement.internal.applyEventOptimistically(event);
1051
1122
  }
1052
1123
  console.error("Failed to delete event:", error);
1053
- queueStatus(`Failed to delete event: ${error instanceof Error ? error.message : String(error)}`);
1124
+ queueStatus(
1125
+ `Failed to delete event: ${
1126
+ error instanceof Error ? error.message : String(error)
1127
+ }`,
1128
+ );
1054
1129
  }
1055
1130
  }
1056
1131
  });
@@ -1068,7 +1143,8 @@ calendarElement.addEventListener("force-sync", async () => {
1068
1143
 
1069
1144
  calendarElement.addEventListener("load-notifications", async () => {
1070
1145
  try {
1071
- const notifications = await notificationScheduler.getScheduledNotifications();
1146
+ const notifications =
1147
+ await notificationScheduler.getScheduledNotifications();
1072
1148
  calendarElement.setScheduledNotifications(notifications);
1073
1149
  } catch (error) {
1074
1150
  console.error("Failed to load scheduled notifications:", error);
@@ -1139,7 +1215,6 @@ calendarElement.addEventListener("import-ical", async (e) => {
1139
1215
  }
1140
1216
  });
1141
1217
 
1142
-
1143
1218
  function updateActiveCalendarColor() {
1144
1219
  const calendar = activeCalendarStore.getActiveCalendar();
1145
1220
  calendarElement.activeCalendarColor = calendar?.color ?? null;
@@ -1148,8 +1223,8 @@ function updateActiveCalendarColor() {
1148
1223
 
1149
1224
  function updateEnabledCalendars() {
1150
1225
  const enabledCalendarIdentifiers = Array.from(loadedCalendars.values())
1151
- .filter(cal => cal.enabled)
1152
- .flatMap(cal => {
1226
+ .filter((cal) => cal.enabled)
1227
+ .flatMap((cal) => {
1153
1228
  // For CalDAV, use the calendar URL; for others, use the source ID
1154
1229
  const identifiers = [];
1155
1230
  if (cal.calendarUrl) {
@@ -1165,8 +1240,8 @@ function updateEnabledCalendars() {
1165
1240
 
1166
1241
  function updateLockedCalendars() {
1167
1242
  const lockedCalendarIdentifiers = Array.from(loadedCalendars.values())
1168
- .filter(cal => (cal as { locked?: boolean }).locked)
1169
- .flatMap(cal => {
1243
+ .filter((cal) => (cal as { locked?: boolean }).locked)
1244
+ .flatMap((cal) => {
1170
1245
  // For CalDAV, use the calendar URL; for others, use the source ID
1171
1246
  const identifiers = [];
1172
1247
  if (cal.calendarUrl) {