diff --git a/internal/cmd/mail/draft.go b/internal/cmd/mail/draft.go index 389b770..866a8a2 100644 --- a/internal/cmd/mail/draft.go +++ b/internal/cmd/mail/draft.go @@ -14,6 +14,7 @@ import ( "github.com/spf13/cobra" "github.com/yuin/goldmark" "github.com/yuin/goldmark/extension" + gmhtml "github.com/yuin/goldmark/renderer/html" xhtml "golang.org/x/net/html" gmailapi "github.com/open-cli-collective/google-cli/internal/api/gmail" @@ -616,6 +617,9 @@ func renderMarkdown(src []byte) ([]byte, error) { extension.Strikethrough, extension.TaskList, ), + // A newline in an email body is a line break to its author (a two-line + // sign-off, an address), not a CommonMark soft break that joins lines. + goldmark.WithRendererOptions(gmhtml.WithHardWraps()), ) var buf bytes.Buffer if err := md.Convert(src, &buf); err != nil { diff --git a/internal/cmd/mail/draft_test.go b/internal/cmd/mail/draft_test.go index 5232127..b198927 100644 --- a/internal/cmd/mail/draft_test.go +++ b/internal/cmd/mail/draft_test.go @@ -979,3 +979,17 @@ func TestDraftCommand_ReplyTo_RejectsHeaderInjection(t *testing.T) { testutil.Contains(t, err.Error(), "CR or LF") }) } + +func TestRenderMarkdown_SingleNewlineIsALineBreak(t *testing.T) { + out, err := renderMarkdown([]byte("Hi Sam,\n\nThanks,\nAlex")) + if err != nil { + t.Fatalf("renderMarkdown: %v", err) + } + got := string(out) + if !strings.Contains(got, "Thanks,
\nAlex") && !strings.Contains(got, "Thanks,
\nAlex") { + t.Errorf("sign-off lines were joined: %q", got) + } + if strings.Count(got, "

") != 2 { + t.Errorf("blank line should still separate paragraphs: %q", got) + } +}