diff --git a/pkg/caldav/caldav.go b/pkg/caldav/caldav.go index d89f87636..f2c5dacab 100644 --- a/pkg/caldav/caldav.go +++ b/pkg/caldav/caldav.go @@ -21,7 +21,9 @@ import ( "strings" "time" + "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/richtext" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" ) @@ -179,8 +181,18 @@ DURATION:PT` + formatDuration(t.Duration) DTEND:` + makeCalDavTimeFromTimeStamp(t.End) } if t.Description != "" { - caldavtodos += ` -DESCRIPTION:` + escapeICalText(t.Description) + // CalDAV clients show plain text, so emit markdown. On the near-impossible + // conversion error, log it and keep the stored value (GetContent can't + // return an error) rather than drop the description. + description, err := richtext.HTMLToMarkdown(t.Description) + if err != nil { + log.Errorf("[CALDAV] Failed to convert description to markdown for task %q: %v", t.UID, err) + description = t.Description + } + if description != "" { + caldavtodos += ` +DESCRIPTION:` + escapeICalText(description) + } } if t.Completed.Unix() > 0 { caldavtodos += ` diff --git a/pkg/caldav/caldav_test.go b/pkg/caldav/caldav_test.go index 7ced0b5e4..76f2e755d 100644 --- a/pkg/caldav/caldav_test.go +++ b/pkg/caldav/caldav_test.go @@ -47,12 +47,11 @@ func TestParseTodos(t *testing.T) { }, todos: []*Todo{ { - Summary: "Todo #1", - Description: `Lorem Ipsum -Dolor sit amet`, - UID: "randommduid", - Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()), - Color: "affffe", + Summary: "Todo #1", + Description: `
Lorem Ipsum
Dolor sit amet
`, + UID: "randommduid", + Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()), + Color: "affffe", }, }, }, @@ -73,7 +72,7 @@ X-APPLE-CALENDAR-COLOR:#affffeFF X-OUTLOOK-COLOR:#affffeFF X-FUNAMBOL-COLOR:#affffeFF COLOR:#affffeFF -DESCRIPTION:Lorem Ipsum\nDolor sit amet +DESCRIPTION:Lorem Ipsum\n\nDolor sit amet LAST-MODIFIED:00010101T000000Z END:VTODO END:VCALENDAR`, @@ -438,6 +437,33 @@ END:VCALENDAR`, } } +func TestParseTodosRichTextDescription(t *testing.T) { + cfg := &Config{Name: "test", ProdID: "Vikunja"} + ts := time.Unix(1543626724, 0).In(config.GetTimeZone()) + + t.Run("rich html serializes as markdown", func(t *testing.T) { + out := ParseTodos(cfg, []*Todo{{ + Summary: "Todo", + UID: "uid", + Timestamp: ts, + Description: `Hello bold and
done