mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
[MM-24714] Render markdown in email notifications (#18043)
Automatic Merge
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/i18n"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -213,10 +214,16 @@ func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post,
|
||||
if emailNotificationContentsType == model.EmailNotificationContentsFull {
|
||||
postMessage := a.GetMessageForNotification(post, translateFunc)
|
||||
postMessage = html.EscapeString(postMessage)
|
||||
normalizedPostMessage, err := a.generateHyperlinkForChannels(postMessage, teamName, landingURL)
|
||||
mdPostMessage, mdErr := utils.MarkdownToHTML(postMessage)
|
||||
if mdErr != nil {
|
||||
mlog.Warn("Encountered error while converting markdown to HTML", mlog.Err(mdErr))
|
||||
mdPostMessage = postMessage
|
||||
}
|
||||
|
||||
normalizedPostMessage, err := a.generateHyperlinkForChannels(mdPostMessage, teamName, landingURL)
|
||||
if err != nil {
|
||||
mlog.Warn("Encountered error while generating hyperlink for channels", mlog.String("team_name", teamName), mlog.Err(err))
|
||||
normalizedPostMessage = postMessage
|
||||
normalizedPostMessage = mdPostMessage
|
||||
}
|
||||
pData.Message = template.HTML(normalizedPostMessage)
|
||||
pData.Time = translateFunc("app.notification.body.dm.time", messageTime)
|
||||
|
||||
@@ -825,3 +825,112 @@ func TestLandingLinkPermalink(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, body, teamURL+"/pl/"+post.Id, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
|
||||
}
|
||||
|
||||
func TestMarkdownConversion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "markdown: escape string test",
|
||||
args: "<b>not bold</b>",
|
||||
want: "<b>not bold</b>",
|
||||
},
|
||||
{
|
||||
name: "markdown: strong",
|
||||
args: "This is **Mattermost**",
|
||||
want: "This is <strong>Mattermost</strong>",
|
||||
},
|
||||
{
|
||||
name: "markdown: blockquote",
|
||||
args: "Below is blockquote\n" +
|
||||
"> This is Mattermost blockquote\n" +
|
||||
"> on multiple lines!",
|
||||
want: "<blockquote>\n" +
|
||||
"<p>This is Mattermost blockquote\n" +
|
||||
"on multiple lines!</p>\n" +
|
||||
"</blockquote>",
|
||||
},
|
||||
{
|
||||
name: "markdown: emphasis",
|
||||
args: "This is *Mattermost*",
|
||||
want: "This is <em>Mattermost</em>",
|
||||
},
|
||||
{
|
||||
name: "markdown: links",
|
||||
args: "This is [Mattermost](https://mattermost.com)",
|
||||
want: "This is <a href=\"https://mattermost.com\">Mattermost</a>",
|
||||
},
|
||||
{
|
||||
name: "markdown: strikethrough",
|
||||
args: "This is ~~Mattermost~~",
|
||||
want: "This is <del>Mattermost</del>",
|
||||
},
|
||||
{
|
||||
name: "markdown: table",
|
||||
args: "| Tables | Are | Cool |\n" +
|
||||
"| ------------- |:-------------:| -----:|\n" +
|
||||
"| col 3 is | right-aligned | $1600 |\n" +
|
||||
"| col 2 is | centered | $12 |\n" +
|
||||
"| zebra stripes | are neat | $1 |\n",
|
||||
want: "<table>\n" +
|
||||
"<thead>\n" +
|
||||
"<tr>\n" +
|
||||
"<th>Tables</th>\n" +
|
||||
"<th style=\"text-align:center\">Are</th>\n" +
|
||||
"<th style=\"text-align:right\">Cool</th>\n" +
|
||||
"</tr>\n" +
|
||||
"</thead>\n" +
|
||||
"<tbody>\n" +
|
||||
"<tr>\n" +
|
||||
"<td>col 3 is</td>\n" +
|
||||
"<td style=\"text-align:center\">right-aligned</td>\n" +
|
||||
"<td style=\"text-align:right\">$1600</td>\n" +
|
||||
"</tr>\n" +
|
||||
"<tr>\n" +
|
||||
"<td>col 2 is</td>\n" +
|
||||
"<td style=\"text-align:center\">centered</td>\n" +
|
||||
"<td style=\"text-align:right\">$12</td>\n" +
|
||||
"</tr>\n" +
|
||||
"<tr>\n" +
|
||||
"<td>zebra stripes</td>\n" +
|
||||
"<td style=\"text-align:center\">are neat</td>\n" +
|
||||
"<td style=\"text-align:right\">$1</td>\n" +
|
||||
"</tr>\n" +
|
||||
"</tbody>\n" +
|
||||
"</table>",
|
||||
},
|
||||
{
|
||||
name: "markdown: multiline with header and links",
|
||||
args: "###### H6 header\n[link 1](https://mattermost.com) - [link 2](https://mattermost.com)",
|
||||
want: "<h6>H6 header</h6>\n" +
|
||||
"<p><a href=\"https://mattermost.com\">link 1</a> - <a href=\"https://mattermost.com\">link 2</a></p>",
|
||||
},
|
||||
}
|
||||
|
||||
th := SetupWithStoreMock(t)
|
||||
defer th.TearDown()
|
||||
|
||||
recipient := &model.User{}
|
||||
storeMock := th.App.Srv().Store.(*mocks.Store)
|
||||
teamStoreMock := mocks.TeamStore{}
|
||||
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
|
||||
storeMock.On("Team").Return(&teamStoreMock)
|
||||
channel := &model.Channel{
|
||||
DisplayName: "ChannelName",
|
||||
Type: model.ChannelTypeOpen,
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
post := &model.Post{
|
||||
Id: "Test_id",
|
||||
Message: tt.args,
|
||||
}
|
||||
got, err := th.App.getNotificationEmailBody(recipient, post, channel, "ChannelName", "sender", "testteam", "http://localhost:8065/landing#/testteam", model.EmailNotificationContentsFull, true, i18n.GetUserTranslations("en"), "user-avatar.png")
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, got, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"html"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
@@ -33,6 +35,28 @@ func StripMarkdown(markdown string) (string, error) {
|
||||
return strings.TrimSpace(buf.String()), nil
|
||||
}
|
||||
|
||||
// MarkdownToHTML takes a string containing Markdown and returns a string with HTML tagged version
|
||||
func MarkdownToHTML(markdown string) (string, error) {
|
||||
// Unescape any blockquote text to be parsed by the markdown parser.
|
||||
re := regexp.MustCompile(`^|\n(>)`)
|
||||
markdownClean := re.ReplaceAllFunc([]byte(markdown), func(s []byte) []byte {
|
||||
out := html.UnescapeString(string(s))
|
||||
return []byte(out)
|
||||
})
|
||||
|
||||
md := goldmark.New(
|
||||
goldmark.WithExtensions(extension.GFM),
|
||||
)
|
||||
|
||||
var b strings.Builder
|
||||
|
||||
err := md.Convert(markdownClean, &b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
type notificationRenderer struct {
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user