@luckydye/calendar 1.2.2 → 1.3.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/dist/calendar.js +397 -395
- package/package.json +1 -1
- package/src/CalDAVConfig.ts +191 -116
- package/src/CalendarInternal.ts +12 -0
- package/src/CalendarStorage.ts +5 -0
- package/src/CalendarView.ts +83 -86
- package/src/IndexedDBStorage.ts +13 -0
- package/src/InhouseBookingSource.ts +30 -1
- package/src/app.css +17 -1
- package/src/app.ts +168 -183
package/src/app.ts
CHANGED
|
@@ -116,6 +116,42 @@ async function sync(cal: Calendar, options?: { force?: boolean }): Promise<void>
|
|
|
116
116
|
await scheduleNotificationsForEvents(events);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
// Mutation coordination: batches post-mutation syncs so concurrent
|
|
120
|
+
// mutations don't each trigger their own reconciliation round-trip.
|
|
121
|
+
let pendingMutations = 0;
|
|
122
|
+
let mutationsSettledResolve: (() => void) | null = null;
|
|
123
|
+
let mutationsSettledPromise: Promise<void> | null = null;
|
|
124
|
+
const dirtyCalendars = new Set<Calendar>();
|
|
125
|
+
|
|
126
|
+
async function withMutation(cal: Calendar, fn: () => Promise<void>): Promise<void> {
|
|
127
|
+
if (pendingMutations === 0) {
|
|
128
|
+
mutationsSettledPromise = new Promise(r => { mutationsSettledResolve = r; });
|
|
129
|
+
}
|
|
130
|
+
pendingMutations++;
|
|
131
|
+
try {
|
|
132
|
+
await fn();
|
|
133
|
+
} finally {
|
|
134
|
+
dirtyCalendars.add(cal);
|
|
135
|
+
pendingMutations--;
|
|
136
|
+
if (pendingMutations === 0) {
|
|
137
|
+
const resolve = mutationsSettledResolve;
|
|
138
|
+
mutationsSettledResolve = null;
|
|
139
|
+
mutationsSettledPromise = null;
|
|
140
|
+
resolve?.();
|
|
141
|
+
const toSync = [...dirtyCalendars];
|
|
142
|
+
dirtyCalendars.clear();
|
|
143
|
+
for (const c of toSync) {
|
|
144
|
+
await sync(c, { force: true });
|
|
145
|
+
}
|
|
146
|
+
updateStatusBar();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function waitForMutations(): Promise<void> {
|
|
152
|
+
if (mutationsSettledPromise) await mutationsSettledPromise;
|
|
153
|
+
}
|
|
154
|
+
|
|
119
155
|
// Initialize calendar with storage first, then sync sources
|
|
120
156
|
(async () => {
|
|
121
157
|
await calendar.initPromise;
|
|
@@ -126,18 +162,33 @@ async function sync(cal: Calendar, options?: { force?: boolean }): Promise<void>
|
|
|
126
162
|
loadedCalendars.set(sampleCalendar.id, sampleCalendar);
|
|
127
163
|
activeCalendarStore.registerCalendar(sampleCalendar);
|
|
128
164
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
165
|
+
// Create sidebar with CalDAV config
|
|
166
|
+
const sidebar = document.createElement("div");
|
|
167
|
+
sidebar.slot = "sidebar";
|
|
168
|
+
sidebar.className = "caldav-sidebar";
|
|
169
|
+
|
|
170
|
+
config = document.createElement("caldav-config") as CalDAVConfigElement;
|
|
171
|
+
config.addEventListener("sources-changed", async () => {
|
|
172
|
+
await syncCalDAV();
|
|
173
|
+
});
|
|
174
|
+
config.addEventListener("collapsed-changed", (e: Event) => {
|
|
175
|
+
const { collapsed } = (e as CustomEvent).detail;
|
|
176
|
+
sidebar.classList.toggle("collapsed", collapsed);
|
|
135
177
|
});
|
|
136
|
-
|
|
137
|
-
|
|
178
|
+
config.addEventListener("active-calendar-changed", (e: Event) => {
|
|
179
|
+
const { calendarId } = (e as CustomEvent).detail;
|
|
180
|
+
activeCalendarStore.setActive(calendarId);
|
|
181
|
+
updateActiveCalendarColor();
|
|
182
|
+
});
|
|
183
|
+
sidebar.classList.toggle("collapsed", config.collapsed);
|
|
184
|
+
sidebar.appendChild(config);
|
|
185
|
+
calendarElement.appendChild(sidebar);
|
|
138
186
|
|
|
139
|
-
//
|
|
140
|
-
|
|
187
|
+
// Subscribe to active calendar store changes → update sidebar
|
|
188
|
+
activeCalendarStore.subscribe(() => {
|
|
189
|
+
config.activeCalendarId = activeCalendarStore.getActiveId();
|
|
190
|
+
updateActiveCalendarColor();
|
|
191
|
+
});
|
|
141
192
|
|
|
142
193
|
// Sync CalDAV sources (this will call updateEnabledCalendars after calendars are registered)
|
|
143
194
|
await syncCalDAV();
|
|
@@ -146,55 +197,10 @@ async function sync(cal: Calendar, options?: { force?: boolean }): Promise<void>
|
|
|
146
197
|
updateActiveCalendarColor();
|
|
147
198
|
})();
|
|
148
199
|
|
|
149
|
-
// CalDAV configuration UI
|
|
150
|
-
calendarElement.addEventListener("caldav-config", () => {
|
|
151
|
-
showCalDAVConfig();
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
function showCalDAVConfig() {
|
|
155
|
-
// Remove existing modal if present
|
|
156
|
-
const existing = document.querySelector("caldav-config-modal");
|
|
157
|
-
if (existing) {
|
|
158
|
-
existing.remove();
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Create modal overlay
|
|
163
|
-
const modal = document.createElement("div");
|
|
164
|
-
modal.className = "caldav-config-modal";
|
|
165
|
-
modal.style.cssText = `
|
|
166
|
-
position: fixed;
|
|
167
|
-
top: 6.5rem;
|
|
168
|
-
left: 0.5rem;
|
|
169
|
-
bottom: 7rem;
|
|
170
|
-
width: 400px;
|
|
171
|
-
z-index: 1000;
|
|
172
|
-
`;
|
|
173
|
-
|
|
174
|
-
// Create CalDAV config component
|
|
175
|
-
const config = document.createElement("caldav-config") as CalDAVConfigElement;
|
|
176
|
-
|
|
177
|
-
config.addEventListener("close", () => {
|
|
178
|
-
modal.remove();
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
config.addEventListener("sources-changed", async () => {
|
|
182
|
-
await syncCalDAV();
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
modal.appendChild(config);
|
|
186
|
-
document.body.appendChild(modal);
|
|
187
|
-
|
|
188
|
-
// Close on backdrop click
|
|
189
|
-
modal.addEventListener("click", (e) => {
|
|
190
|
-
if (e.target === modal) {
|
|
191
|
-
modal.remove();
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
200
|
|
|
196
201
|
// Store for loaded calendars
|
|
197
202
|
const loadedCalendars: Map<string, Calendar> = new Map();
|
|
203
|
+
let config: CalDAVConfigElement;
|
|
198
204
|
|
|
199
205
|
function findCalendarForEvent(event: CalendarEvent): Calendar | undefined {
|
|
200
206
|
if (event.calendarId) {
|
|
@@ -449,6 +455,7 @@ let configuredSourceIds: Set<string> = new Set();
|
|
|
449
455
|
|
|
450
456
|
// Initial CalDAV sync - sync all configured sources
|
|
451
457
|
async function syncCalDAV(force = false) {
|
|
458
|
+
await waitForMutations();
|
|
452
459
|
const saved = localStorage.getItem("caldav-sources");
|
|
453
460
|
if (!saved) return;
|
|
454
461
|
|
|
@@ -478,7 +485,15 @@ async function syncCalDAV(force = false) {
|
|
|
478
485
|
configuredSourceIds = newSourceIds;
|
|
479
486
|
|
|
480
487
|
for (const source of sources) {
|
|
481
|
-
if (!source.enabled)
|
|
488
|
+
if (!source.enabled) {
|
|
489
|
+
// Mark existing calendars from this source as disabled
|
|
490
|
+
for (const cal of loadedCalendars.values()) {
|
|
491
|
+
if (cal.sourceId === source.id) {
|
|
492
|
+
cal.enabled = false;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
482
497
|
|
|
483
498
|
if (source.type === "caldav") {
|
|
484
499
|
if (
|
|
@@ -563,8 +578,14 @@ async function syncCalDAV(force = false) {
|
|
|
563
578
|
updateEnabledCalendars();
|
|
564
579
|
updateLockedCalendars();
|
|
565
580
|
|
|
566
|
-
//
|
|
567
|
-
|
|
581
|
+
// Push calendar list to sidebar
|
|
582
|
+
config.calendars = Array.from(loadedCalendars.values()).map(cal => ({
|
|
583
|
+
id: cal.id,
|
|
584
|
+
name: cal.name,
|
|
585
|
+
color: cal.color,
|
|
586
|
+
sourceId: cal.sourceId,
|
|
587
|
+
}));
|
|
588
|
+
config.activeCalendarId = activeCalendarStore.getActiveId();
|
|
568
589
|
updateActiveCalendarColor();
|
|
569
590
|
}
|
|
570
591
|
|
|
@@ -582,6 +603,55 @@ calendarElement.addEventListener("selection-change", (e) => {
|
|
|
582
603
|
console.log("Selection changed:", e.detail.selectedEvents);
|
|
583
604
|
});
|
|
584
605
|
|
|
606
|
+
function showDeleteConfirmDialog(count: number): Promise<boolean> {
|
|
607
|
+
return new Promise((resolve) => {
|
|
608
|
+
const dialog = document.createElement("dialog");
|
|
609
|
+
dialog.style.cssText =
|
|
610
|
+
"margin:auto;color:var(--text-primary);border:1px solid var(--grid-color-strong);border-radius:var(--border-radius);padding:16px;min-width:280px;";
|
|
611
|
+
|
|
612
|
+
const message = document.createElement("p");
|
|
613
|
+
message.style.cssText = "margin:0 0 16px 0;";
|
|
614
|
+
message.textContent = `Delete ${count} event${count === 1 ? "" : "s"}?`;
|
|
615
|
+
|
|
616
|
+
const buttons = document.createElement("div");
|
|
617
|
+
buttons.style.cssText = "display:flex;gap:8px;justify-content:flex-end;";
|
|
618
|
+
|
|
619
|
+
const cancel = document.createElement("button");
|
|
620
|
+
cancel.textContent = "Cancel";
|
|
621
|
+
cancel.style.cssText =
|
|
622
|
+
"padding:6px 12px;background:var(--bg-item);color:var(--text-primary);border:1px solid var(--grid-color-strong);border-radius:var(--border-radius-sm);cursor:pointer;";
|
|
623
|
+
cancel.addEventListener("click", () => {
|
|
624
|
+
dialog.close();
|
|
625
|
+
resolve(false);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
const confirm = document.createElement("button");
|
|
629
|
+
confirm.textContent = "Delete";
|
|
630
|
+
confirm.style.cssText =
|
|
631
|
+
"padding:6px 12px;background:var(--color-danger, #e53e3e);color:#fff;border:none;border-radius:var(--border-radius-sm);cursor:pointer;";
|
|
632
|
+
confirm.addEventListener("click", () => {
|
|
633
|
+
dialog.close();
|
|
634
|
+
resolve(true);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
dialog.addEventListener("close", () => {
|
|
638
|
+
dialog.remove();
|
|
639
|
+
});
|
|
640
|
+
dialog.addEventListener("click", (ev) => {
|
|
641
|
+
if (ev.target === dialog) {
|
|
642
|
+
dialog.close();
|
|
643
|
+
resolve(false);
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
buttons.append(cancel, confirm);
|
|
648
|
+
dialog.append(message, buttons);
|
|
649
|
+
document.body.appendChild(dialog);
|
|
650
|
+
dialog.showModal();
|
|
651
|
+
confirm.focus();
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
585
655
|
function showProjectPickerDialog(
|
|
586
656
|
projects: Array<{ id: number; name: string }>,
|
|
587
657
|
): Promise<number | null> {
|
|
@@ -595,7 +665,7 @@ function showProjectPickerDialog(
|
|
|
595
665
|
|
|
596
666
|
const dialog = document.createElement("dialog");
|
|
597
667
|
dialog.style.cssText =
|
|
598
|
-
"
|
|
668
|
+
"margin:auto;color:var(--text-primary);border:1px solid var(--grid-color-strong);border-radius:var(--border-radius);padding:16px;min-width:300px;max-width:480px;";
|
|
599
669
|
|
|
600
670
|
const input = document.createElement("input");
|
|
601
671
|
input.type = "text";
|
|
@@ -676,9 +746,24 @@ calendarElement.addEventListener("create-event", async (e) => {
|
|
|
676
746
|
id = `0:0:${projectId}`;
|
|
677
747
|
}
|
|
678
748
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
749
|
+
calendarElement.internal.applyEventOptimistically({
|
|
750
|
+
id,
|
|
751
|
+
title: "New Event",
|
|
752
|
+
start,
|
|
753
|
+
end,
|
|
754
|
+
calendar: calendar.name,
|
|
755
|
+
calendarId: calendar.calendarUrl ?? calendar.id,
|
|
756
|
+
sourceId: calendar.sourceId,
|
|
757
|
+
color: calendar.color,
|
|
758
|
+
lastSynced: new Date(),
|
|
759
|
+
});
|
|
760
|
+
try {
|
|
761
|
+
await withMutation(calendar, () => calendar.createEvent({ id, title: "New Event", start, end }));
|
|
762
|
+
} catch (error) {
|
|
763
|
+
calendarElement.internal.removeEventOptimistically(id);
|
|
764
|
+
console.error("Failed to create event:", error);
|
|
765
|
+
queueStatus(`Failed to create event: ${error instanceof Error ? error.message : String(error)}`);
|
|
766
|
+
}
|
|
682
767
|
});
|
|
683
768
|
|
|
684
769
|
calendarElement.addEventListener("move-event", async (e) => {
|
|
@@ -707,11 +792,10 @@ calendarElement.addEventListener("move-event", async (e) => {
|
|
|
707
792
|
calendar.name,
|
|
708
793
|
);
|
|
709
794
|
try {
|
|
710
|
-
|
|
711
|
-
await
|
|
712
|
-
console.log("Move completed on calendar:", calendar.name);
|
|
713
|
-
updateStatusBar();
|
|
795
|
+
calendarElement.internal.applyEventOptimistically({ ...event, start, end });
|
|
796
|
+
await withMutation(calendar, () => calendar.updateEvent(event.id, { start, end }));
|
|
714
797
|
} catch (error) {
|
|
798
|
+
calendarElement.internal.applyEventOptimistically(event);
|
|
715
799
|
console.error("Failed to move event:", error);
|
|
716
800
|
queueStatus(`Failed to move event: ${error instanceof Error ? error.message : String(error)}`);
|
|
717
801
|
}
|
|
@@ -739,8 +823,8 @@ calendarElement.addEventListener("update-event", async (e) => {
|
|
|
739
823
|
event
|
|
740
824
|
);
|
|
741
825
|
try {
|
|
742
|
-
|
|
743
|
-
await
|
|
826
|
+
calendarElement.internal.applyEventOptimistically({ ...event, ...updates });
|
|
827
|
+
await withMutation(calendar, () => calendar.updateEvent(event.id, updates));
|
|
744
828
|
|
|
745
829
|
if ("reminders" in updates) {
|
|
746
830
|
const eventWithReminders = { ...event, ...updates };
|
|
@@ -750,10 +834,8 @@ calendarElement.addEventListener("update-event", async (e) => {
|
|
|
750
834
|
calendarElement.selectedEventForDetail = eventWithReminders;
|
|
751
835
|
}
|
|
752
836
|
}
|
|
753
|
-
|
|
754
|
-
console.log("Update completed on calendar:", calendar.name);
|
|
755
|
-
updateStatusBar();
|
|
756
837
|
} catch (error) {
|
|
838
|
+
calendarElement.internal.applyEventOptimistically(event);
|
|
757
839
|
console.error("Failed to update event:", error);
|
|
758
840
|
queueStatus(`Failed to update event: ${error instanceof Error ? error.message : String(error)}`);
|
|
759
841
|
}
|
|
@@ -762,6 +844,13 @@ calendarElement.addEventListener("update-event", async (e) => {
|
|
|
762
844
|
calendarElement.addEventListener("delete-events", async (e) => {
|
|
763
845
|
const { events } = e.detail;
|
|
764
846
|
|
|
847
|
+
const confirmed = await showDeleteConfirmDialog(events.length);
|
|
848
|
+
if (!confirmed) return;
|
|
849
|
+
|
|
850
|
+
for (const event of events) {
|
|
851
|
+
calendarElement.internal.removeEventOptimistically(event.id);
|
|
852
|
+
}
|
|
853
|
+
|
|
765
854
|
// Group events by their calendarId
|
|
766
855
|
const eventsByCalendar = new Map<string, typeof events>();
|
|
767
856
|
for (const event of events) {
|
|
@@ -800,17 +889,19 @@ calendarElement.addEventListener("delete-events", async (e) => {
|
|
|
800
889
|
calendarEvents.map((ev) => ev.id),
|
|
801
890
|
);
|
|
802
891
|
try {
|
|
892
|
+
await withMutation(calendar, async () => {
|
|
893
|
+
for (const event of calendarEvents) {
|
|
894
|
+
await calendar.deleteEvent(event.id);
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
} catch (error) {
|
|
803
898
|
for (const event of calendarEvents) {
|
|
804
|
-
|
|
899
|
+
calendarElement.internal.applyEventOptimistically(event);
|
|
805
900
|
}
|
|
806
|
-
await sync(calendar, { force: true });
|
|
807
|
-
} catch (error) {
|
|
808
901
|
console.error("Failed to delete event:", error);
|
|
809
902
|
queueStatus(`Failed to delete event: ${error instanceof Error ? error.message : String(error)}`);
|
|
810
903
|
}
|
|
811
904
|
}
|
|
812
|
-
console.log("Delete completed");
|
|
813
|
-
updateStatusBar();
|
|
814
905
|
});
|
|
815
906
|
|
|
816
907
|
calendarElement.addEventListener("force-sync", async () => {
|
|
@@ -897,71 +988,6 @@ calendarElement.addEventListener("import-ical", async (e) => {
|
|
|
897
988
|
}
|
|
898
989
|
});
|
|
899
990
|
|
|
900
|
-
// --- Active Calendar Selector UI ---
|
|
901
|
-
function createActiveCalendarSelector() {
|
|
902
|
-
// Check if selector already exists in the slot
|
|
903
|
-
if (document.getElementById("active-calendar-selector")) {
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
const selector = document.createElement("div");
|
|
908
|
-
selector.id = "active-calendar-selector";
|
|
909
|
-
selector.setAttribute("slot", "toolbar-center");
|
|
910
|
-
selector.style.cssText = `
|
|
911
|
-
display: flex;
|
|
912
|
-
align-items: center;
|
|
913
|
-
gap: 8px;
|
|
914
|
-
font-size: 13px;
|
|
915
|
-
margin-right: auto;
|
|
916
|
-
`;
|
|
917
|
-
|
|
918
|
-
const label = document.createElement("span");
|
|
919
|
-
label.style.cssText = `
|
|
920
|
-
color: var(--text-muted, rgba(255, 255, 255, 0.5));
|
|
921
|
-
white-space: nowrap;
|
|
922
|
-
`;
|
|
923
|
-
|
|
924
|
-
const select = document.createElement("select");
|
|
925
|
-
select.id = "active-calendar-select";
|
|
926
|
-
select.style.cssText = `
|
|
927
|
-
background: var(--bg-input, rgba(0, 0, 0, 0.3));
|
|
928
|
-
border: 1px solid var(--grid-color, rgba(255, 255, 255, 0.1));
|
|
929
|
-
border-radius: var(--border-radius-sm, 4px);
|
|
930
|
-
color: var(--text-primary, rgba(255, 255, 255, 0.9));
|
|
931
|
-
padding: 4px 8px;
|
|
932
|
-
font-size: 13px;
|
|
933
|
-
cursor: pointer;
|
|
934
|
-
min-width: 150px;
|
|
935
|
-
`;
|
|
936
|
-
|
|
937
|
-
select.addEventListener("change", (e) => {
|
|
938
|
-
const target = e.target as HTMLSelectElement;
|
|
939
|
-
try {
|
|
940
|
-
activeCalendarStore.setActive(target.value || null);
|
|
941
|
-
updateActiveCalendarColor();
|
|
942
|
-
const calendar = activeCalendarStore.getActiveCalendar();
|
|
943
|
-
if (calendar) {
|
|
944
|
-
queueStatus(`Active calendar: ${calendar.name}`);
|
|
945
|
-
}
|
|
946
|
-
} catch (err) {
|
|
947
|
-
console.error("Failed to set active calendar:", err);
|
|
948
|
-
queueStatus(`Error: ${err.message}`);
|
|
949
|
-
}
|
|
950
|
-
});
|
|
951
|
-
|
|
952
|
-
selector.appendChild(label);
|
|
953
|
-
selector.appendChild(select);
|
|
954
|
-
calendarElement.appendChild(selector);
|
|
955
|
-
|
|
956
|
-
// Subscribe to changes and update the selector and color
|
|
957
|
-
activeCalendarStore.subscribe(() => {
|
|
958
|
-
updateActiveCalendarSelector();
|
|
959
|
-
updateActiveCalendarColor();
|
|
960
|
-
});
|
|
961
|
-
|
|
962
|
-
// Initial update
|
|
963
|
-
updateActiveCalendarSelector();
|
|
964
|
-
}
|
|
965
991
|
|
|
966
992
|
function updateActiveCalendarColor() {
|
|
967
993
|
const calendar = activeCalendarStore.getActiveCalendar();
|
|
@@ -1002,44 +1028,3 @@ function updateLockedCalendars() {
|
|
|
1002
1028
|
calendar.setLockedCalendars(lockedCalendarIdentifiers);
|
|
1003
1029
|
}
|
|
1004
1030
|
|
|
1005
|
-
function getActiveCalendarSelect(): HTMLSelectElement | null {
|
|
1006
|
-
// Look in the light DOM first (where slotted content lives)
|
|
1007
|
-
const selector = document.getElementById("active-calendar-selector");
|
|
1008
|
-
if (selector) {
|
|
1009
|
-
return selector.querySelector(
|
|
1010
|
-
"#active-calendar-select",
|
|
1011
|
-
) as HTMLSelectElement | null;
|
|
1012
|
-
}
|
|
1013
|
-
return null;
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
function updateActiveCalendarSelector() {
|
|
1017
|
-
const select = getActiveCalendarSelect();
|
|
1018
|
-
if (!select) return;
|
|
1019
|
-
|
|
1020
|
-
const calendars = activeCalendarStore.getAvailableCalendars();
|
|
1021
|
-
const activeId = activeCalendarStore.getActiveId();
|
|
1022
|
-
|
|
1023
|
-
// Clear current options
|
|
1024
|
-
select.innerHTML = "";
|
|
1025
|
-
|
|
1026
|
-
if (calendars.length === 0) {
|
|
1027
|
-
const option = document.createElement("option");
|
|
1028
|
-
option.value = "";
|
|
1029
|
-
option.textContent = "No calendars";
|
|
1030
|
-
option.disabled = true;
|
|
1031
|
-
option.selected = true;
|
|
1032
|
-
select.appendChild(option);
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
for (const calendar of calendars) {
|
|
1037
|
-
const option = document.createElement("option");
|
|
1038
|
-
option.value = calendar.id;
|
|
1039
|
-
option.textContent = calendar.name;
|
|
1040
|
-
if (calendar.id === activeId) {
|
|
1041
|
-
option.selected = true;
|
|
1042
|
-
}
|
|
1043
|
-
select.appendChild(option);
|
|
1044
|
-
}
|
|
1045
|
-
}
|