From bfd4851098015b8a539d3afcb1c04f96eac2483b Mon Sep 17 00:00:00 2001 From: Bruno Bernardino Date: Fri, 2 Jan 2026 10:40:40 +0000 Subject: [PATCH] Also fix calendar event imports with multiline fields Fixes #139 --- docker-compose.yml | 2 +- lib/utils/calendar.ts | 14 +++++++------- lib/utils/calendar_test.ts | 38 ++++++++++++++++++++++++++++++++++++++ lib/utils/contacts.ts | 8 ++++---- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 7d46787..b60fe71 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: website: - image: ghcr.io/bewcloud/bewcloud:v3.5.0 + image: ghcr.io/bewcloud/bewcloud:v3.5.1 restart: always ports: - 127.0.0.1:8000:8000 diff --git a/lib/utils/calendar.ts b/lib/utils/calendar.ts index a81901a..7d06eaf 100644 --- a/lib/utils/calendar.ts +++ b/lib/utils/calendar.ts @@ -57,7 +57,7 @@ export function getColorAsHex( } export function getIdFromVEvent(vEvent: string): string { - const lines = vEvent.split('\n').map((line) => line.trim()).filter(Boolean); + const lines = vEvent.split('\n').map((line) => line.trimEnd()).filter(Boolean); // Loop through every line and find the UID line for (const line of lines) { @@ -71,7 +71,7 @@ export function getIdFromVEvent(vEvent: string): string { } export function splitTextIntoVEvents(text: string): string[] { - const lines = text.split('\n').map((line) => line.trim()).filter(Boolean); + const lines = text.split('\n').map((line) => line.trimEnd()).filter(Boolean); const vEvents: string[] = []; const currentVEvent: string[] = []; let hasFoundBeginVEvent = false; @@ -154,7 +154,7 @@ export function generateVCalendar( const vCalendarText = events.map((event) => generateVEvent(event, createdDate)).join('\n'); return `BEGIN:VCALENDAR\nVERSION:2.0\nCALSCALE:GREGORIAN\n${vCalendarText}\nEND:VCALENDAR`.split('\n').map((line) => - line.trim() + line.trimEnd() ).filter( Boolean, ).join('\n'); @@ -212,14 +212,14 @@ END:VALARM` } END:VEVENT`; - return vEventText.split('\n').map((line) => line.trim()).filter(Boolean).join('\n'); + return vEventText.split('\n').map((line) => line.trimEnd()).filter(Boolean).join('\n'); } export function updateIcs( ics: string, event: CalendarEvent, ): string { - const lines = ics.split('\n').map((line) => line.trim()).filter(Boolean); + const lines = ics.split('\n').map((line) => line.trimEnd()).filter(Boolean); let replacedTitle = false; let replacedStartDate = false; @@ -313,7 +313,7 @@ export function updateIcs( // Put the final lines back updatedIcsLines.push(...endLines); - const updatedIcs = updatedIcsLines.map((line) => line.trim()).filter(Boolean).join('\n'); + const updatedIcs = updatedIcsLines.map((line) => line.trimEnd()).filter(Boolean).join('\n'); return updatedIcs; } @@ -346,7 +346,7 @@ export function parseVCalendar(text: string): CalendarEvent[] { } return previousLines; - }, [] as string[]).map((line) => line.trim()).filter(Boolean); + }, [] as string[]).map((line) => line.trimEnd()).filter(Boolean); const partialCalendarEvents: Partial[] = []; diff --git a/lib/utils/calendar_test.ts b/lib/utils/calendar_test.ts index d0f8128..d63d8a0 100644 --- a/lib/utils/calendar_test.ts +++ b/lib/utils/calendar_test.ts @@ -136,6 +136,44 @@ UID:incomplete SUMMARY:Incomplete Event`, expected: [], }, + { + input: `BEGIN:VEVENT +DTSTAMP:20250804T155354Z +DTSTART;VALUE=DATE:00000000 +DTEND;VALUE=DATE:00000000 +UID:f2476b38-d62f-4d02-a975-b54d97a629f0 +RRULE:FREQ=YEARLY +SUMMARY:🎂 First Middle Last +TRANSP:TRANSPARENT +X-NEXTCLOUD-BC-FIELD-TYPE:BDAY +X-NEXTCLOUD-BC-UNKNOWN-YEAR:1 +BEGIN:VALARM +TRIGGER;VALUE=DURATION:PT9H +ACTION:DISPLAY +DESCRIPTION:🎂 First Middle Last + And another line +END:VALARM +END:VEVENT`, + expected: [ + `BEGIN:VEVENT +DTSTAMP:20250804T155354Z +DTSTART;VALUE=DATE:00000000 +DTEND;VALUE=DATE:00000000 +UID:f2476b38-d62f-4d02-a975-b54d97a629f0 +RRULE:FREQ=YEARLY +SUMMARY:🎂 First Middle Last +TRANSP:TRANSPARENT +X-NEXTCLOUD-BC-FIELD-TYPE:BDAY +X-NEXTCLOUD-BC-UNKNOWN-YEAR:1 +BEGIN:VALARM +TRIGGER;VALUE=DURATION:PT9H +ACTION:DISPLAY +DESCRIPTION:🎂 First Middle Last + And another line +END:VALARM +END:VEVENT`, + ], + }, ]; for (const test of tests) { diff --git a/lib/utils/contacts.ts b/lib/utils/contacts.ts index dea57e4..88d1566 100644 --- a/lib/utils/contacts.ts +++ b/lib/utils/contacts.ts @@ -1,7 +1,7 @@ import { Contact } from '/lib/models/contacts.ts'; export function getIdFromVCard(vCard: string): string { - const lines = vCard.split('\n').map((line) => line.trim()).filter(Boolean); + const lines = vCard.split('\n').map((line) => line.trimEnd()).filter(Boolean); // Loop through every line and find the UID line for (const line of lines) { @@ -52,7 +52,7 @@ export function updateVCard( notes?: string; }, ): string { - const lines = vCard.split('\n').map((line) => line.trim()).filter(Boolean); + const lines = vCard.split('\n').map((line) => line.trimEnd()).filter(Boolean); let replacedName = false; let replacedFormattedName = false; @@ -105,7 +105,7 @@ export function updateVCard( updatedVCardLines.push('END:VCARD'); - const updatedVCard = updatedVCardLines.map((line) => line.trim()).filter(Boolean).join('\n'); + const updatedVCard = updatedVCardLines.map((line) => line.trimEnd()).filter(Boolean).join('\n'); return updatedVCard; } @@ -121,7 +121,7 @@ function getSafelyUnescapedTextFromVCard(text: string): string { type VCardVersion = '2.1' | '3.0' | '4.0'; export function parseVCard(text: string): Partial[] { - const lines = text.split('\n').map((line) => line.trim()).filter(Boolean); + const lines = text.split('\n').map((line) => line.trimEnd()).filter(Boolean); const partialContacts: Partial[] = [];