diff --git a/internal/text/markdown.go b/internal/text/markdown.go index 13fb933789..1382cbf618 100644 --- a/internal/text/markdown.go +++ b/internal/text/markdown.go @@ -19,7 +19,9 @@ package text import ( + "bytes" "context" + "io" "github.com/russross/blackfriday/v2" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" @@ -33,18 +35,51 @@ var ( m *minify.M ) +type renderer struct { + f *formatter + ctx context.Context + mentions []*gtsmodel.Mention + tags []*gtsmodel.Tag + blackfriday.HTMLRenderer +} + +func (r *renderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus { + if node.Type == blackfriday.Text { + // call RenderNode to do the html escaping + var buff bytes.Buffer + status := r.HTMLRenderer.RenderNode(&buff, node, entering) + + html := buff.String() + html = r.f.ReplaceTags(r.ctx, html, r.tags) + html = r.f.ReplaceMentions(r.ctx, html, r.mentions) + + // we don't have much recourse if this fails + _, err := io.WriteString(w, html) + if err != nil { + log.Errorf("error outputting markdown text: %s", err) + } + return status + } + return r.HTMLRenderer.RenderNode(w, node, entering) +} + func (f *formatter) FromMarkdown(ctx context.Context, md string, mentions []*gtsmodel.Mention, tags []*gtsmodel.Tag) string { - // format tags nicely - content := f.ReplaceTags(ctx, md, tags) - // format mentions nicely - content = f.ReplaceMentions(ctx, content, mentions) + renderer := &renderer{ + f: f, + ctx: ctx, + mentions: mentions, + tags: tags, + HTMLRenderer: *blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{ + Flags: blackfriday.CommonHTMLFlags, + }), + } - // parse markdown - contentBytes := blackfriday.Run([]byte(content), blackfriday.WithExtensions(bfExtensions)) + // parse markdown, use custom renderer to add hashtag/mention links + contentBytes := blackfriday.Run([]byte(md), blackfriday.WithExtensions(bfExtensions), blackfriday.WithRenderer(renderer)) // clean anything dangerous out of it - content = SanitizeHTML(string(contentBytes)) + content := SanitizeHTML(string(contentBytes)) if m == nil { m = minify.New() diff --git a/internal/text/markdown_test.go b/internal/text/markdown_test.go index 3e156f43ef..4c9483c7ec 100644 --- a/internal/text/markdown_test.go +++ b/internal/text/markdown_test.go @@ -65,6 +65,10 @@ const ( mdWithFootnoteExpected = "
fox mulder,fbi.1
get ready, there’s a block quote coming:
" + mdHashtagAndCodeBlock = "#Hashtag\n\n```\n#Hashtag\n```" + mdHashtagAndCodeBlockExpected = "line1
line2line3
#Hashtag\n
"
+ mdMentionAndCodeBlock = "@the_mighty_zork\n\n```\n@the_mighty_zork\n```"
+ mdMentionAndCodeBlockExpected = "@the_mighty_zork\n
"
)
type MarkdownTestSuite struct {
@@ -133,6 +137,20 @@ func (suite *MarkdownTestSuite) TestParseWithBlockquote() {
suite.Equal(mdWithBlockQuoteExpected, s)
}
+func (suite *MarkdownTestSuite) TestParseHashtagWithCodeBlock() {
+ s := suite.formatter.FromMarkdown(context.Background(), mdHashtagAndCodeBlock, nil, []*gtsmodel.Tag{
+ suite.testTags["Hashtag"],
+ })
+ suite.Equal(mdHashtagAndCodeBlockExpected, s)
+}
+
+func (suite *MarkdownTestSuite) TestParseMentionWithCodeBlock() {
+ s := suite.formatter.FromMarkdown(context.Background(), mdMentionAndCodeBlock, []*gtsmodel.Mention{
+ suite.testMentions["local_user_2_mention_zork"],
+ }, nil)
+ suite.Equal(mdMentionAndCodeBlockExpected, s)
+}
+
func TestMarkdownTestSuite(t *testing.T) {
suite.Run(t, new(MarkdownTestSuite))
}