diff --git a/components/calendar/AddEventModal.tsx b/components/calendar/AddEventModal.tsx index 9bf1e27..2f93796 100644 --- a/components/calendar/AddEventModal.tsx +++ b/components/calendar/AddEventModal.tsx @@ -61,7 +61,7 @@ export default function AddEventModal(

New Event

diff --git a/components/calendar/ImportEventsModal.tsx b/components/calendar/ImportEventsModal.tsx index db6947e..14d6c90 100644 --- a/components/calendar/ImportEventsModal.tsx +++ b/components/calendar/ImportEventsModal.tsx @@ -33,7 +33,7 @@ export default function ImportEventsModal(

Import Events

diff --git a/components/calendar/ViewEventModal.tsx b/components/calendar/ViewEventModal.tsx index 311d271..5877951 100644 --- a/components/calendar/ViewEventModal.tsx +++ b/components/calendar/ViewEventModal.tsx @@ -28,7 +28,7 @@ export default function ViewEventModal(

{calendarEvent.title}

@@ -83,6 +83,20 @@ export default function ViewEventModal(
) : null} + {Array.isArray(calendarEvent.extra.attendees) && calendarEvent.extra.attendees.length > 0 + ? ( +
+ {calendarEvent.extra.attendees.map((attendee) => ( +

+ + {attendee.name || attendee.email} + {' '} + - {attendee.status} +

+ ))} +
+ ) + : null}

TODO: reminders

diff --git a/lib/utils/calendar.ts b/lib/utils/calendar.ts index 88010dc..e24b3f2 100644 --- a/lib/utils/calendar.ts +++ b/lib/utils/calendar.ts @@ -1,4 +1,4 @@ -import { Calendar, CalendarEvent } from '../types.ts'; +import { Calendar, CalendarEvent, CalendarEventAttendee } from '../types.ts'; export const CALENDAR_COLOR_OPTIONS = [ 'bg-red-700', @@ -47,6 +47,24 @@ export const CALENDAR_BORDER_COLOR_OPTIONS = [ 'border-rose-700', ] as const; +function getVCalendarAttendeeStatus(status: CalendarEventAttendee['status']) { + if (status === 'accepted' || status === 'rejected') { + return status.toUpperCase(); + } + + return `NEEDS-ACTION`; +} + +function getAttendeeStatusFromVCalendar( + status: 'NEEDS-ACTION' | 'ACCEPTED' | 'REJECTED', +): CalendarEventAttendee['status'] { + if (status === 'ACCEPTED' || status === 'REJECTED') { + return status.toLowerCase() as CalendarEventAttendee['status']; + } + + return 'invited'; +} + // TODO: Build this export function formatCalendarEventsToVCalendar( calendarEvents: CalendarEvent[], @@ -57,10 +75,17 @@ export function formatCalendarEventsToVCalendar( DTSTAMP:${new Date(calendarEvent.created_at).toISOString().substring(0, 19).replaceAll('-', '').replaceAll(':', '')} DTSTART:${new Date(calendarEvent.start_date).toISOString().substring(0, 19).replaceAll('-', '').replaceAll(':', '')} DTEND:${new Date(calendarEvent.end_date).toISOString().substring(0, 19).replaceAll('-', '').replaceAll(':', '')} -ORGANIZER;CN=:MAILTO:${calendarEvent.extra.organizer_email} +ORGANIZER;CN=:mailto:${calendarEvent.extra.organizer_email} SUMMARY:${calendarEvent.title} TRANSP:${getCalendarEventTransparency(calendarEvent, calendars).toUpperCase()} ${calendarEvent.extra.uid ? `UID:${calendarEvent.extra.uid}` : ''} +${ + calendarEvent.extra.attendees?.map((attendee) => + `ATTENDEE;PARTSTAT=${getVCalendarAttendeeStatus(attendee.status)};CN=${ + attendee.name || '' + }:mailto:${attendee.email}` + ).join('\n') || '' + } END:VEVENT` ).join('\n'); @@ -74,7 +99,18 @@ END:VEVENT` type VCalendarVersion = '1.0' | '2.0'; export function parseVCalendarFromTextContents(text: string): Partial[] { - const lines = text.split('\n').map((line) => line.trim()).filter(Boolean); + // Lines that start with a space should be moved to the line above them, as it's the same field/value to parse + const lines = text.split('\n').reduce((previousLines, currentLine) => { + if (currentLine.startsWith(' ')) { + previousLines[previousLines.length - 1] = `${previousLines[previousLines.length - 1]}${ + currentLine.substring(1).replaceAll('\r', '') + }`; + } else { + previousLines.push(currentLine.replaceAll('\r', '')); + } + + return previousLines; + }, [] as string[]).map((line) => line.trim()).filter(Boolean); const partialCalendarEvents: Partial[] = []; @@ -95,7 +131,7 @@ export function parseVCalendarFromTextContents(text: string): Partial