feat(caldav): serialize task descriptions as markdown
CalDAV clients render DESCRIPTION as plain text, so convert the stored HTML to markdown when serializing VTODOs. On the near-impossible conversion error, log it and keep the stored value.
This commit is contained in:
parent
9015bad65c
commit
a728e50796
|
|
@ -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 += `
|
||||
|
|
|
|||
|
|
@ -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: `<p>Lorem Ipsum</p><p>Dolor sit amet</p>`,
|
||||
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: `<p>Hello <strong>bold</strong> and <mention-user data-id="user1" data-label="User One">@User One</mention-user></p>` +
|
||||
`<ul data-type="taskList"><li data-checked="true" data-type="taskItem"><label><input type="checkbox" checked="checked"><span></span></label><div><p>done</p></div></li></ul>`,
|
||||
}})
|
||||
// iCal escapes the markdown's newlines as "\n".
|
||||
assert.Contains(t, out, `DESCRIPTION:Hello **bold** and @user1\n\n- [x] done`)
|
||||
})
|
||||
|
||||
t.Run("empty html omits the description line", func(t *testing.T) {
|
||||
out := ParseTodos(cfg, []*Todo{{Summary: "Todo", UID: "uid", Timestamp: ts, Description: "<p></p>"}})
|
||||
assert.NotContains(t, out, "DESCRIPTION:")
|
||||
})
|
||||
|
||||
t.Run("plain text description is unaffected", func(t *testing.T) {
|
||||
out := ParseTodos(cfg, []*Todo{{Summary: "Todo", UID: "uid", Timestamp: ts, Description: "just plain text"}})
|
||||
assert.Contains(t, out, "DESCRIPTION:just plain text")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetCaldavColor(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
Loading…
Reference in New Issue