Also fix calendar event imports with multiline fields

Fixes #139
This commit is contained in:
Bruno Bernardino 2026-01-02 10:40:40 +00:00
parent 0a7e03326f
commit bfd4851098
No known key found for this signature in database
GPG key ID: D1B0A69ADD114ECE
4 changed files with 50 additions and 12 deletions

View file

@ -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

View file

@ -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<CalendarEvent>[] = [];

View file

@ -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) {

View file

@ -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<Contact>[] {
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<Contact>[] = [];