From 27775e5d03e7ff5e7f7b1f387c9b4afecf327725 Mon Sep 17 00:00:00 2001
From: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Date: Wed, 17 Dec 2025 20:32:12 +0530
Subject: [PATCH] fix: preserve newlines and links in calendar event
descriptions (#25868)
The event type description is stored as HTML from the rich text editor.
When converting to plain text for calendar events, the HTML tags were
being stripped without preserving the formatting, causing:
- Newlines to be lost (text runs together)
- Links to be broken
This fix:
- Converts
,
, , , tags to newlines
- Preserves links in readable format: 'text (url)' or just 'url'
- Normalizes multiple newlines to max 2 for cleaner output
Affects all calendar integrations (Google, Outlook, etc.)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---
packages/lib/CalEventParser.ts | 31 ++++++++++++++++++++++++++++++-
1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/packages/lib/CalEventParser.ts b/packages/lib/CalEventParser.ts
index 22edbe7479..f5bfc8caf3 100644
--- a/packages/lib/CalEventParser.ts
+++ b/packages/lib/CalEventParser.ts
@@ -112,11 +112,40 @@ export const getAppsStatus = (calEvent: Pick, t: TF
`;
};
+/**
+ * Converts HTML to plain text while preserving line breaks and links.
+ * Used for calendar event descriptions that are stored as HTML from the rich text editor.
+ */
+const htmlToPlainText = (html: string): string => {
+ return (
+ html
+ // Convert links to markdown-style format: [text](url)
+ .replace(/]*?\s+)?href="([^"]*)"[^>]*>([^<]*)<\/a>/gi, (_, url, text) => {
+ // If link text is the same as URL, just show the URL
+ if (text === url || !text.trim()) {
+ return url;
+ }
+ return `${text} (${url})`;
+ })
+ // Convert block-level elements to newlines
+ .replace(/
/gi, "\n")
+ .replace(/<\/p>/gi, "\n")
+ .replace(/<\/div>/gi, "\n")
+ .replace(/<\/li>/gi, "\n")
+ .replace(/<\/h[1-6]>/gi, "\n")
+ // Strip remaining HTML tags
+ .replace(/<\/?[^>]+(>|$)/g, "")
+ // Normalize multiple newlines to max 2
+ .replace(/\n{3,}/g, "\n\n")
+ .trim()
+ );
+};
+
export const getDescription = (calEvent: Pick, t: TFunction) => {
if (!calEvent.description) {
return "";
}
- const plainText = calEvent.description.replace(/<\/?[^>]+(>|$)/g, "").replace(/_/g, " ");
+ const plainText = htmlToPlainText(calEvent.description);
return `${t("description")}\n${plainText}`;
};