From bbf68fbfda09c8b0b73cbc087b68b99ee5b3e8a2 Mon Sep 17 00:00:00 2001 From: nig Date: Wed, 2 Aug 2023 16:19:08 +0200 Subject: [PATCH] remove altered instance exclusions before exporting calendar events this client-side fix makes invitations work for google calendar #5707 --- src/calendar/date/CalendarInvites.ts | 2 +- .../date/eventeditor/CalendarEventModel.ts | 4 ++- .../eventeditor/CalendarEventModelStrategy.ts | 28 +++++++++++-------- .../eventeditor/CalendarNotificationModel.ts | 12 ++++++-- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/calendar/date/CalendarInvites.ts b/src/calendar/date/CalendarInvites.ts index 7a107183895e..e314d1512153 100644 --- a/src/calendar/date/CalendarInvites.ts +++ b/src/calendar/date/CalendarInvites.ts @@ -160,7 +160,7 @@ export async function replyToEventInvitation( responseModel?.addRecipient(RecipientField.TO, previousMail.sender) try { - await notificationModel.send(eventClone, { responseModel, inviteModel: null, cancelModel: null, updateModel: null }) + await notificationModel.send(eventClone, [], { responseModel, inviteModel: null, cancelModel: null, updateModel: null }) } catch (e) { if (e instanceof UserError) { await Dialog.message(() => e.message) diff --git a/src/calendar/date/eventeditor/CalendarEventModel.ts b/src/calendar/date/eventeditor/CalendarEventModel.ts index 0a12247b09dc..1d12e3a17924 100644 --- a/src/calendar/date/eventeditor/CalendarEventModel.ts +++ b/src/calendar/date/eventeditor/CalendarEventModel.ts @@ -217,8 +217,10 @@ export async function makeCalendarEventModel( description: new SanitizedTextViewModel(initializationEvent.description, htmlSanitizer, uiUpdateCallback), }) + const recurrenceIds = async (uid?: string) => + uid == null ? [] : (await calendarModel.getEventsByUid(uid))?.alteredInstances.map((i) => i.recurrenceId) ?? [] const notificationModel = new CalendarNotificationModel(notificationSender, logins) - const applyStrategies = new CalendarEventApplyStrategies(calendarModel, logins, notificationModel, showProgress, zone) + const applyStrategies = new CalendarEventApplyStrategies(calendarModel, logins, notificationModel, recurrenceIds, showProgress, zone) const progenitor = () => calendarModel.resolveCalendarEventProgenitor(cleanInitialValues) const strategy = await selectStrategy(makeEditModels, applyStrategies, operation, progenitor, createCalendarEvent(initialValues), cleanInitialValues) return strategy && new CalendarEventModel(strategy, eventType, operation, logins.getUserController(), notificationSender, entityClient, calendars) diff --git a/src/calendar/date/eventeditor/CalendarEventModelStrategy.ts b/src/calendar/date/eventeditor/CalendarEventModelStrategy.ts index f94c1270bb58..8c996f8c17cb 100644 --- a/src/calendar/date/eventeditor/CalendarEventModelStrategy.ts +++ b/src/calendar/date/eventeditor/CalendarEventModelStrategy.ts @@ -39,6 +39,7 @@ export class CalendarEventApplyStrategies { private readonly calendarModel: CalendarModel, private readonly logins: LoginController, private readonly notificationModel: CalendarNotificationModel, + private readonly lazyRecurrenceIds: (uid?: string | null) => Promise>, private readonly showProgress: ShowProgressCallback = identity, private readonly zone: string, ) {} @@ -55,7 +56,7 @@ export class CalendarEventApplyStrategies { await this.showProgress( (async () => { - await this.notificationModel.send(newEvent, sendModels) + await this.notificationModel.send(newEvent, [], sendModels) await this.calendarModel.createEvent(newEvent, newAlarms, this.zone, groupRoot) })(), ) @@ -77,7 +78,8 @@ export class CalendarEventApplyStrategies { const { groupRoot } = calendar await this.showProgress( (async () => { - await this.notificationModel.send(newEvent, sendModels) + const recurrenceIds: Array = await this.lazyRecurrenceIds(uid) + await this.notificationModel.send(newEvent, recurrenceIds, sendModels) await this.calendarModel.updateEvent(newEvent, newAlarms, this.zone, groupRoot, existingEvent) const invalidateAlteredInstances = newEvent.repeatRule && newEvent.repeatRule.excludedDates.length === 0 @@ -102,7 +104,7 @@ export class CalendarEventApplyStrategies { sendModels.cancelModel = sendModels.updateModel sendModels.updateModel = null sendModels.inviteModel = null - await this.notificationModel.send(occurrence, sendModels) + await this.notificationModel.send(occurrence, [], sendModels) await this.calendarModel.deleteEvent(occurrence) } else { const { newEvent, newAlarms, sendModels } = assembleEditResultAndAssignFromExisting( @@ -115,7 +117,7 @@ export class CalendarEventApplyStrategies { newEvent.endTime = DateTime.fromJSDate(newEvent.startTime, { zone: this.zone }).plus(newDuration).toJSDate() // altered instances never have a repeat rule newEvent.repeatRule = null - await this.notificationModel.send(newEvent, sendModels) + await this.notificationModel.send(newEvent, [], sendModels) await this.calendarModel.updateEvent(newEvent, newAlarms, this.zone, groupRoot, occurrence) } } @@ -142,7 +144,7 @@ export class CalendarEventApplyStrategies { editModels, CalendarOperation.EditThis, ) - await this.notificationModel.send(newEvent, sendModels) + await this.notificationModel.send(newEvent, [], sendModels) // OLD: but we need to update the existing one as well, to add an exclusion for the original instance that we edited. editModelsForProgenitor.whoModel.shouldSendUpdates = true @@ -152,7 +154,9 @@ export class CalendarEventApplyStrategies { sendModels: progenitorSendModels, newAlarms: progenitorAlarms, } = assembleEditResultAndAssignFromExisting(progenitor, editModelsForProgenitor, CalendarOperation.EditAll) - await this.notificationModel.send(newProgenitor, progenitorSendModels) + const recurrenceIds = await this.lazyRecurrenceIds(progenitor.uid) + recurrenceIds.push(existingInstance.startTime) + await this.notificationModel.send(newProgenitor, recurrenceIds, progenitorSendModels) await this.calendarModel.updateEvent(newProgenitor, progenitorAlarms, this.zone, calendar.groupRoot, progenitor) // NEW @@ -167,7 +171,7 @@ export class CalendarEventApplyStrategies { const { groupRoot } = calendar await this.showProgress( (async () => { - await this.notificationModel.send(newEvent, sendModels) + await this.notificationModel.send(newEvent, [], sendModels) await this.calendarModel.updateEvent(newEvent, newAlarms, this.zone, groupRoot, existingInstance) })(), ) @@ -186,13 +190,13 @@ export class CalendarEventApplyStrategies { const { sendModels } = assembleEditResultAndAssignFromExisting(occurrence, editModels, CalendarOperation.DeleteAll) sendModels.cancelModel = sendModels.updateModel sendModels.updateModel = null - await this.notificationModel.send(occurrence, sendModels) + await this.notificationModel.send(occurrence, [], sendModels) } } sendModels.cancelModel = sendModels.updateModel sendModels.updateModel = null - await this.notificationModel.send(existingEvent, sendModels) + await this.notificationModel.send(existingEvent, [], sendModels) if (existingEvent.uid != null) { await this.calendarModel.deleteEventsByUid(existingEvent.uid) } @@ -214,7 +218,9 @@ export class CalendarEventApplyStrategies { editModelsForProgenitor, CalendarOperation.DeleteThis, ) - await this.notificationModel.send(newEvent, sendModels) + const recurrenceIds = await this.lazyRecurrenceIds(progenitor.uid) + recurrenceIds.push(existingInstance.startTime) + await this.notificationModel.send(newEvent, recurrenceIds, sendModels) await this.calendarModel.updateEvent(newEvent, newAlarms, this.zone, calendar.groupRoot, progenitor) })(), ) @@ -228,7 +234,7 @@ export class CalendarEventApplyStrategies { sendModels.updateModel = null await this.showProgress( (async () => { - await this.notificationModel.send(existingAlteredInstance, sendModels) + await this.notificationModel.send(existingAlteredInstance, [], sendModels) await this.calendarModel.deleteEvent(existingAlteredInstance) })(), ) diff --git a/src/calendar/date/eventeditor/CalendarNotificationModel.ts b/src/calendar/date/eventeditor/CalendarNotificationModel.ts index b06d456ce6dd..28550377f4d4 100644 --- a/src/calendar/date/eventeditor/CalendarNotificationModel.ts +++ b/src/calendar/date/eventeditor/CalendarNotificationModel.ts @@ -32,7 +32,7 @@ export class CalendarNotificationModel { * * will modify the attendee list of newEvent if invites/cancellations are sent. */ - async send(event: CalendarEvent, sendModels: CalendarNotificationSendModels): Promise { + async send(event: CalendarEvent, recurrenceIds: Array, sendModels: CalendarNotificationSendModels): Promise { if (sendModels.updateModel == null && sendModels.cancelModel == null && sendModels.inviteModel == null && sendModels.responseModel == null) { return } @@ -40,11 +40,19 @@ export class CalendarNotificationModel { const { getAvailablePlansWithCalendarInvites } = await import("../../../subscription/SubscriptionUtils.js") throw new UpgradeRequiredError("upgradeRequired_msg", await getAvailablePlansWithCalendarInvites()) } + // we need to exclude the exclusions that are only there because of altered instances specifically + // so google calendar handles our invitations + const recurrenceTimes = recurrenceIds.map((date) => date.getTime()) + const originalExclusions = event.repeatRule?.excludedDates ?? [] + const filteredExclusions = originalExclusions.filter(({ date }) => !recurrenceTimes.includes(date.getTime())) + if (event.repeatRule != null) event.repeatRule.excludedDates = filteredExclusions + const invitePromise = sendModels.inviteModel != null ? this.sendInvites(event, sendModels.inviteModel) : Promise.resolve() const cancelPromise = sendModels.cancelModel != null ? this.sendCancellation(event, sendModels.cancelModel) : Promise.resolve() const updatePromise = sendModels.updateModel != null ? this.sendUpdates(event, sendModels.updateModel) : Promise.resolve() const responsePromise = sendModels.responseModel != null ? this.respondToOrganizer(event, sendModels.responseModel) : Promise.resolve() - return await Promise.all([invitePromise, cancelPromise, updatePromise, responsePromise]).then() + await Promise.all([invitePromise, cancelPromise, updatePromise, responsePromise]) + if (event.repeatRule != null) event.repeatRule.excludedDates = originalExclusions } /**