refactor recurring event modification (#173)

This commit is contained in:
Camille Moussu
2025-10-07 18:16:00 +02:00
committed by GitHub
parent 3f8f3eff23
commit c86db8fa49
15 changed files with 1924 additions and 176 deletions
+112 -2
View File
@@ -1,9 +1,15 @@
import { api } from "../../utils/apiUtils";
import { TIMEZONES } from "../../utils/timezone-data";
import { CalendarEvent } from "./EventsTypes";
import { calendarEventToJCal, parseCalendarEvent } from "./eventUtils";
import {
calendarEventToJCal,
makeTimezone,
makeVevent,
parseCalendarEvent,
} from "./eventUtils";
import ICAL from "ical.js";
export async function getEvent(event: CalendarEvent) {
export async function getEvent(event: CalendarEvent, isMaster?: boolean) {
const response = await api.get(`dav${event.URL}`);
const eventData = await response.text();
const eventical = ICAL.parse(eventData);
@@ -13,6 +19,9 @@ export async function getEvent(event: CalendarEvent) {
event.calId,
event.URL
);
if (isMaster) {
return { ...event, ...eventjson };
}
return { ...eventjson, ...event };
}
@@ -37,6 +46,107 @@ export async function putEvent(event: CalendarEvent, calOwnerEmail?: string) {
return response;
}
export async function putEventWithOverrides(
updatedEvent: CalendarEvent,
calOwnerEmail?: string
) {
const vevents = await getAllRecurrentEvent(updatedEvent);
const updatedVevent = makeVevent(
updatedEvent,
updatedEvent.timezone,
calOwnerEmail,
!updatedEvent.recurrenceId
);
let replaced = false;
for (let i = 0; i < vevents.length; i++) {
const ve = vevents[i];
const recurrenceId = ve[1].find(([k]: string[]) => k === "recurrence-id");
if (recurrenceId && recurrenceId[3] === updatedEvent.recurrenceId) {
vevents[i] = updatedVevent; // replace
replaced = true;
break;
}
}
if (!replaced && updatedEvent.recurrenceId) {
vevents.push(updatedVevent); // add new override
}
const timezoneData = TIMEZONES.zones[updatedEvent.timezone];
const vtimezone = makeTimezone(timezoneData, updatedEvent);
const newJCal = ["vcalendar", [], [...vevents, vtimezone.component.jCal]];
return api(`dav${updatedEvent.URL}`, {
method: "PUT",
body: JSON.stringify(newJCal),
headers: {
"content-type": "text/calendar; charset=utf-8",
},
});
}
export const deleteEventInstance = async (
event: CalendarEvent,
calOwnerEmail?: string
) => {
const seriesEvent = await getEvent(
{
...event,
uid: event.uid.split("/")[0],
},
true
);
seriesEvent.exdates = [...(seriesEvent.exdates || []), event.start];
delete seriesEvent.recurrenceId;
return putEvent(seriesEvent, calOwnerEmail);
};
export const updateSeries = async (
event: CalendarEvent,
calOwnerEmail?: string
) => {
const vevents = await getAllRecurrentEvent(event);
const masterIndex = vevents.findIndex(
([, props]: [string, string[]]) =>
!props.find(([k]) => k.toLowerCase() === "recurrence-id")
);
if (masterIndex === -1) {
throw new Error("No master VEVENT found for this series");
}
const rrule = vevents[0][1].find(([k]: string[]) => k === "rrule");
const tzid = event.timezone;
const updatedMaster = makeVevent(event, tzid, calOwnerEmail, true);
const newRrule = updatedMaster[1].find(([k]: string[]) => k === "rrule");
if (!newRrule) {
updatedMaster[1].push(rrule);
}
vevents[masterIndex] = updatedMaster;
const timezoneData = TIMEZONES.zones[event.timezone];
const vtimezone = makeTimezone(timezoneData, event);
const newJCal = ["vcalendar", [], [...vevents, vtimezone.component.jCal]];
return api(`dav${event.URL}`, {
method: "PUT",
body: JSON.stringify(newJCal),
headers: {
"content-type": "text/calendar; charset=utf-8",
},
});
};
async function getAllRecurrentEvent(event: CalendarEvent) {
const response = await api.get(`dav${event.URL}`);
const eventData = await response.text();
const jcal = ICAL.parse(eventData);
const vevents = jcal[2].filter(([name]: string[]) => name === "vevent");
return vevents;
}
export async function moveEvent(event: CalendarEvent, newUrl: string) {
const response = await api(`dav${event.URL}`, {
method: "MOVE",