Slack attachment in email notifications (#20946)
* First POC * Added author data and markdown support * WIP * styled fields * WIP * WIP * IMplementation done * Updated template * fixed mjml compilation error * removed card.mjml * Added test * Updated a styling * Updated a styling * CI * Updated generated templates * Explicitelly specified font for message attachments * Lint fix * Removed leftover existances of slack attachment
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
312bf283a8
Коммит
31e6cdd0e6
@@ -201,6 +201,18 @@ func truncateUserNames(name string, i int) string {
|
||||
return name
|
||||
}
|
||||
|
||||
type FieldRow struct {
|
||||
Cells []*model.SlackAttachmentField
|
||||
}
|
||||
|
||||
type EmailMessageAttachment struct {
|
||||
model.SlackAttachment
|
||||
|
||||
Pretext template.HTML
|
||||
Text template.HTML
|
||||
FieldRows []FieldRow
|
||||
}
|
||||
|
||||
type postData struct {
|
||||
SenderName string
|
||||
ChannelName string
|
||||
@@ -211,6 +223,7 @@ type postData struct {
|
||||
Time string
|
||||
ShowChannelIcon bool
|
||||
OtherChannelMembersCount int
|
||||
MessageAttachments []*EmailMessageAttachment
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,6 +258,7 @@ func (a *App) getNotificationEmailBody(c request.CTX, recipient *model.User, pos
|
||||
}
|
||||
pData.Message = template.HTML(normalizedPostMessage)
|
||||
pData.Time = translateFunc("app.notification.body.dm.time", messageTime)
|
||||
pData.MessageAttachments = a.processMessageAttachments(post)
|
||||
}
|
||||
|
||||
data := a.Srv().EmailService.NewEmailTemplateData(recipient.Locale)
|
||||
@@ -306,6 +320,81 @@ func (a *App) getNotificationEmailBody(c request.CTX, recipient *model.User, pos
|
||||
return a.Srv().TemplatesContainer().RenderToString("messages_notification", data)
|
||||
}
|
||||
|
||||
func (a *App) processMessageAttachments(post *model.Post) []*EmailMessageAttachment {
|
||||
emailMessageAttachments := []*EmailMessageAttachment{}
|
||||
|
||||
for _, messageAttachment := range post.Attachments() {
|
||||
emailMessageAttachment := &EmailMessageAttachment{
|
||||
SlackAttachment: *messageAttachment,
|
||||
Pretext: a.prepareTextForEmail(messageAttachment.Pretext),
|
||||
Text: a.prepareTextForEmail(messageAttachment.Text),
|
||||
}
|
||||
|
||||
stripedTitle, err := utils.StripMarkdown(emailMessageAttachment.Title)
|
||||
if err != nil {
|
||||
mlog.Warn("Failed parse to markdown from messageatatchment title", mlog.String("post_id", post.Id), mlog.Err(err))
|
||||
stripedTitle = ""
|
||||
}
|
||||
|
||||
emailMessageAttachment.Title = stripedTitle
|
||||
|
||||
shortFieldRow := FieldRow{}
|
||||
|
||||
for i := range messageAttachment.Fields {
|
||||
// Create a new instance to avoid altering the original pointer reference
|
||||
// We update field value to parse markdown.
|
||||
// If we do that on the original pointer, the rendered text in mattermost
|
||||
// becomes invalid as its no longer a markdown string, but rather an HTML string.
|
||||
field := &model.SlackAttachmentField{
|
||||
Title: messageAttachment.Fields[i].Title,
|
||||
Value: messageAttachment.Fields[i].Value,
|
||||
Short: messageAttachment.Fields[i].Short,
|
||||
}
|
||||
|
||||
if stringValue, ok := field.Value.(string); ok {
|
||||
field.Value = a.prepareTextForEmail(stringValue)
|
||||
}
|
||||
|
||||
if !field.Short {
|
||||
if len(shortFieldRow.Cells) > 0 {
|
||||
emailMessageAttachment.FieldRows = append(emailMessageAttachment.FieldRows, shortFieldRow)
|
||||
shortFieldRow = FieldRow{}
|
||||
}
|
||||
|
||||
emailMessageAttachment.FieldRows = append(emailMessageAttachment.FieldRows, FieldRow{[]*model.SlackAttachmentField{field}})
|
||||
} else {
|
||||
shortFieldRow.Cells = append(shortFieldRow.Cells, field)
|
||||
|
||||
if len(shortFieldRow.Cells) == 2 {
|
||||
emailMessageAttachment.FieldRows = append(emailMessageAttachment.FieldRows, shortFieldRow)
|
||||
shortFieldRow = FieldRow{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collect any leftover short fields
|
||||
if len(shortFieldRow.Cells) > 0 {
|
||||
emailMessageAttachment.FieldRows = append(emailMessageAttachment.FieldRows, shortFieldRow)
|
||||
shortFieldRow = FieldRow{}
|
||||
}
|
||||
|
||||
emailMessageAttachments = append(emailMessageAttachments, emailMessageAttachment)
|
||||
}
|
||||
|
||||
return emailMessageAttachments
|
||||
}
|
||||
|
||||
func (a *App) prepareTextForEmail(text string) template.HTML {
|
||||
escapedText := html.EscapeString(text)
|
||||
markdownText, err := utils.MarkdownToHTML(escapedText)
|
||||
if err != nil {
|
||||
mlog.Warn("Encountered error while converting markdown to HTML", mlog.Err(err))
|
||||
return template.HTML(text)
|
||||
}
|
||||
|
||||
return template.HTML(markdownText)
|
||||
}
|
||||
|
||||
type formattedPostTime struct {
|
||||
Time time.Time
|
||||
Year string
|
||||
|
||||
@@ -339,6 +339,103 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTime24Hour(t *testing.T)
|
||||
require.Contains(t, body, "14:30", fmt.Sprintf("Expected email text '14:30'. Got %s", body))
|
||||
}
|
||||
|
||||
func TestGetNotificationEmailBodyFullNotificationWithSlackAttachments(t *testing.T) {
|
||||
th := SetupWithStoreMock(t)
|
||||
defer th.TearDown()
|
||||
|
||||
recipient := &model.User{}
|
||||
post := &model.Post{
|
||||
Message: "This is the message",
|
||||
}
|
||||
|
||||
messageAttachments := []*model.SlackAttachment{
|
||||
{
|
||||
Color: "#FF0000",
|
||||
Pretext: "message attachment 1 pretext",
|
||||
AuthorName: "author name",
|
||||
AuthorLink: "https://example.com/slack_attachment_1/author_link",
|
||||
AuthorIcon: "https://example.com/slack_attachment_1/author_icon",
|
||||
Title: "message attachment 1 title",
|
||||
TitleLink: "https://example.com/slack_attachment_1/title_link",
|
||||
Text: "message attachment 1 text",
|
||||
ImageURL: "https://example.com/slack_attachment_1/image",
|
||||
ThumbURL: "https://example.com/slack_attachment_1/thumb",
|
||||
Fields: []*model.SlackAttachmentField{
|
||||
{
|
||||
Short: true,
|
||||
Title: "message attachment 1 field 1 title",
|
||||
Value: "message attachment 1 field 1 value",
|
||||
},
|
||||
{
|
||||
Short: false,
|
||||
Title: "message attachment 1 field 2 title",
|
||||
Value: "message attachment 1 field 2 value",
|
||||
},
|
||||
{
|
||||
Short: true,
|
||||
Title: "message attachment 1 field 3 title",
|
||||
Value: "message attachment 1 field 3 value",
|
||||
},
|
||||
{
|
||||
Short: true,
|
||||
Title: "message attachment 1 field 4 title",
|
||||
Value: "message attachment 1 field 4 value",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Color: "#FF0000",
|
||||
Pretext: "message attachment 2 pretext",
|
||||
AuthorName: "author name 2",
|
||||
Text: "message attachment 2 text",
|
||||
},
|
||||
}
|
||||
|
||||
model.ParseSlackAttachment(post, messageAttachments)
|
||||
|
||||
channel := &model.Channel{
|
||||
DisplayName: "ChannelName",
|
||||
Type: model.ChannelTypeOpen,
|
||||
}
|
||||
|
||||
channelName := "ChannelName"
|
||||
senderName := "sender"
|
||||
teamName := "testteam"
|
||||
teamURL := "http://localhost:8065/testteam"
|
||||
emailNotificationContentsType := model.EmailNotificationContentsFull
|
||||
translateFunc := i18n.GetUserTranslations("en")
|
||||
|
||||
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)
|
||||
|
||||
body, err := th.App.getNotificationEmailBody(th.Context, recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, body, "#FF0000")
|
||||
require.Contains(t, body, "message attachment 1 pretext")
|
||||
require.Contains(t, body, "author name")
|
||||
require.Contains(t, body, "https://example.com/slack_attachment_1/author_link")
|
||||
require.Contains(t, body, "https://example.com/slack_attachment_1/author_icon")
|
||||
require.Contains(t, body, "message attachment 1 title")
|
||||
require.Contains(t, body, "https://example.com/slack_attachment_1/title_link")
|
||||
require.Contains(t, body, "message attachment 1 text")
|
||||
require.Contains(t, body, "https://example.com/slack_attachment_1/image")
|
||||
require.Contains(t, body, "https://example.com/slack_attachment_1/thumb")
|
||||
require.Contains(t, body, "message attachment 1 field 1 title")
|
||||
require.Contains(t, body, "message attachment 1 field 1 value")
|
||||
require.Contains(t, body, "message attachment 1 field 2 title")
|
||||
require.Contains(t, body, "message attachment 1 field 2 value")
|
||||
require.Contains(t, body, "message attachment 1 field 3 title")
|
||||
require.Contains(t, body, "message attachment 1 field 3 value")
|
||||
require.Contains(t, body, "message attachment 1 field 4 title")
|
||||
require.Contains(t, body, "message attachment 1 field 4 value")
|
||||
require.Contains(t, body, "https://example.com/slack_attachment_1/thumb")
|
||||
require.Contains(t, body, "message attachment 2 pretext")
|
||||
require.Contains(t, body, "author name 2")
|
||||
require.Contains(t, body, "message attachment 2 text")
|
||||
}
|
||||
|
||||
// from here
|
||||
func TestGetNotificationEmailBodyGenericNotificationPublicChannel(t *testing.T) {
|
||||
th := SetupWithStoreMock(t)
|
||||
|
||||
@@ -8,7 +8,13 @@
|
||||
<mj-include path="./partials/header.mjml" />
|
||||
<mj-raw>{{if .Props.Message}}</mj-raw>
|
||||
<mj-raw>{{range .Props.Posts}}<div class="postCard"></mj-raw>
|
||||
<mj-include path="./partials/card.mjml" />
|
||||
<mj-section css-class="messageCard" padding="0px">
|
||||
<mj-group>
|
||||
<mj-include path="./partials/message_avatar_col.mjml" />
|
||||
<mj-include path="./partials/sender_info_col.mjml" />
|
||||
<mj-include path="./partials/message_button.mjml" />
|
||||
</mj-group>
|
||||
</mj-section>
|
||||
<mj-raw></div>{{end}}</mj-raw>
|
||||
<mj-raw>{{else}}</mj-raw>
|
||||
<mj-section padding="0px 0px 40px 0px">
|
||||
|
||||
@@ -356,7 +356,160 @@
|
||||
max-width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.messageAttachments * {
|
||||
font-family: Open Sans, sans-serif !important;
|
||||
}
|
||||
|
||||
.messageAttachmentContent {
|
||||
padding: 0px;
|
||||
border: 1px solid rgba(63, 67, 80, 0.16);
|
||||
margin-bottom: 20px;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.messageAttachmentContent>table,
|
||||
.attachment__body {
|
||||
font-family: Open Sans, sans-serif;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #3F4350;
|
||||
}
|
||||
|
||||
.messageAttachmentContent .attachment__author-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-right: 5px;
|
||||
border-radius: 50px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.attachment__author-name {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.messageAttachmentContent p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.attachment__title {
|
||||
padding: 0;
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.attachment__image {
|
||||
max-height: 300px;
|
||||
border: 1px solid transparent;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.attachment__thumb-image {
|
||||
max-width: 100%;
|
||||
max-height: 75px;
|
||||
}
|
||||
|
||||
.attachment__thumb-container {
|
||||
max-width: 80px;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.attachment__wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.attachment__body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.messageAttachmentContent>table.attachment__footer-container {
|
||||
color: #a3a3a3;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.attachment__footer-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.attachment__footer-container {
|
||||
color: #a3a3a3;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.messageAttachment_title {
|
||||
padding-top: 1em;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.pretext h1 {
|
||||
font-size: 28px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.pretext h2 {
|
||||
font-size: 25px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.pretext h3 {
|
||||
font-size: 22px;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.pretext h4 {
|
||||
font-size: 19px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.pretext h5 {
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.pretext h6 {
|
||||
font-size: 1em;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
.pretext>* {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.messageAttachments {
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.messageAttachments h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 2px 4px;
|
||||
font-size: 90%;
|
||||
background-color: rgba(63, 67, 80, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.messageAttachments a {
|
||||
background-color: unset !important;
|
||||
border: none !important;
|
||||
padding: unset !important;
|
||||
}
|
||||
</style>
|
||||
<!-- this empty mj attribute tag is required by MJML to compile successfully -->
|
||||
</head>
|
||||
|
||||
<body style="word-spacing:normal;">
|
||||
@@ -507,7 +660,96 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td><td style="vertical-align:top;width:552px;" ><![endif]-->
|
||||
<!--[if mso | IE]></td><![endif]-->
|
||||
{{if .MessageAttachments}}
|
||||
<div class="messageAttachments" style="margin-top: 22px;">
|
||||
{{range .MessageAttachments}}
|
||||
<div class="messageAttachment" style="vertical-align: top; font-family: Open Sans, sans-serif;" width="100%">
|
||||
<div class="pretext" style="text-align: left; font-size: 14px; line-height: 20px; color: #3F4350; padding: 0px; font-family: Open Sans, sans-serif;">
|
||||
{{.Pretext}}
|
||||
</div>
|
||||
<div class="messageAttachmentContent" style="border: 1px solid rgba(63, 67, 80, 0.16); margin-bottom: 20px; border-radius: 0 4px 4px 0; border-left: 4px solid {{.Color}}; padding: 12px; font-family: Open Sans, sans-serif;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="text-align: left; line-height: 20px; color: #3F4350; vertical-align: top; font-size: 14px; font-family: Open Sans, sans-serif;" width="100%" valign="top" align="left">
|
||||
<tbody style="font-family: Open Sans, sans-serif;">
|
||||
{{if or .AuthorIcon .AuthorName}}
|
||||
<tr style="font-family: Open Sans, sans-serif;">
|
||||
<td style="font-family: Open Sans, sans-serif;">
|
||||
{{if .AuthorLink}}<a href="{{.AuthorLink}}" style="color: #1C58D9; font-family: Open Sans, sans-serif; text-decoration: none; background-color: unset; border: none; padding: unset;">{{end}}
|
||||
{{if .AuthorIcon}}<img class="attachment__author-icon" alt="attachment author icon" src="{{.AuthorIcon}}" style="width: 14px; height: 14px; margin-right: 5px; border-radius: 50px; vertical-align: middle; font-family: Open Sans, sans-serif;" width="14" height="14">{{end}}
|
||||
{{if .AuthorName}}<span class="attachment__author-name" style="opacity: 0.6; font-family: Open Sans, sans-serif;">{{.AuthorName}}</span>{{end}}
|
||||
{{if .AuthorLink}}</a>{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{if .Title}}
|
||||
<tr style="font-family: Open Sans, sans-serif;">
|
||||
<td style="font-family: Open Sans, sans-serif;">
|
||||
<h1 class="attachment__title" style="padding: 0; margin: 5px 0; font-size: 14px; line-height: 18px; font-weight: 500; font-family: Open Sans, sans-serif;">
|
||||
{{if .TitleLink}}<a href="{{.TitleLink}}" style="color: #1C58D9; font-family: Open Sans, sans-serif; text-decoration: none; background-color: unset; border: none; padding: unset;">{{end}}
|
||||
{{.Title}}
|
||||
{{if .Title}}</a>{{end}}
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="attachment__wrapper" style="width: 100%; display: flex; flex-direction: row; gap: 12px; font-family: Open Sans, sans-serif;">
|
||||
<div class="attachment__body" style="text-align: left; font-size: 14px; line-height: 20px; color: #3F4350; flex: 1; font-family: Open Sans, sans-serif;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align: top; font-size: 14px; color: #3F4350; font-family: Open Sans, sans-serif;" width="100%" valign="top">
|
||||
<tbody style="font-family: Open Sans, sans-serif;">
|
||||
<tr style="font-family: Open Sans, sans-serif;">
|
||||
<td vertical-align="middle" class="messageButton" style="font-family: Open Sans, sans-serif;">
|
||||
<div style="font-family: Open Sans, sans-serif;">{{.Text}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{if .ImageURL}}
|
||||
<tr style="font-family: Open Sans, sans-serif;">
|
||||
<td style="font-family: Open Sans, sans-serif;">
|
||||
<img class="attachment__image" src="{{.ImageURL}}" style="max-height: 300px; border: 1px solid transparent; margin-bottom: 1em; font-family: Open Sans, sans-serif;">
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align: top; font-size: 14px; table-layout: fixed; margin-bottom: 0.7em; font-family: Open Sans, sans-serif;" width="100%" valign="top">
|
||||
{{range .FieldRows}}
|
||||
<tr class="messageAttachment_row" style="font-family: Open Sans, sans-serif;">
|
||||
{{ $length := len .Cells }}
|
||||
{{range .Cells}}
|
||||
<td class="messageAttachment_field" {{ if eq $length 1 }}colspan="2" {{end}} style="font-family: Open Sans, sans-serif;">
|
||||
<div class="messageAttachment_title" style="padding-top: 1em; font-weight: 600; margin-bottom: 4px; font-family: Open Sans, sans-serif;">{{.Title}}</div>
|
||||
<div style="font-family: Open Sans, sans-serif;">{{.Value}}</div>
|
||||
</td>
|
||||
{{end}}
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
</div>
|
||||
{{if .ThumbURL}}
|
||||
<div class="attachment__thumb-container" style="max-width: 80px; width: max-content; font-family: Open Sans, sans-serif;">
|
||||
<img class="attachment__thumb-image" src="{{.ThumbURL}}" style="max-width: 100%; max-height: 75px; font-family: Open Sans, sans-serif;">
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{if .Footer}}
|
||||
<table class="attachment__footer-container" border="0" cellpadding="0" cellspacing="0" role="presentation" style="text-align: left; line-height: 20px; color: #a3a3a3; vertical-align: top; font-size: 14px; font-family: Open Sans, sans-serif;" width="100%" valign="top" align="left">
|
||||
<tbody style="font-family: Open Sans, sans-serif;">
|
||||
<tr style="font-family: Open Sans, sans-serif;">
|
||||
<td style="font-family: Open Sans, sans-serif;">
|
||||
{{if .FooterIcon}}<img class="attachment__footer-icon" src="{{.FooterIcon}}" style="width: 16px; height: 16px; vertical-align: middle; font-family: Open Sans, sans-serif;" width="16" height="16">{{end}}
|
||||
<span style="font-size: 12px; font-family: Open Sans, sans-serif;">{{.Footer}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
<!--[if mso | IE]><td style="vertical-align:top;width:552px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
<mjml>
|
||||
<mj-head>
|
||||
<mj-include path="./partials/style.mjml" />
|
||||
<mj-include path="partials/message_attachment_styles.mjml" />
|
||||
</mj-head>
|
||||
<mj-body css-class="emailBody">
|
||||
<mj-wrapper mj-class="email">
|
||||
<mj-include path="./partials/logo.mjml" />
|
||||
<mj-include path="./partials/header.mjml" />
|
||||
<mj-raw>{{range .Props.Posts}}<div class="postCard"></mj-raw>
|
||||
<mj-include path="./partials/card.mjml" />
|
||||
<mj-section css-class="messageCard" padding="0px">
|
||||
<mj-group>
|
||||
<mj-include path="./partials/message_avatar_col.mjml" />
|
||||
<mj-include path="./partials/sender_info_col.mjml" />
|
||||
<mj-include path="partials/message_attachment.html" type="html" />
|
||||
<mj-include path="./partials/message_button.mjml" />
|
||||
</mj-group>
|
||||
</mj-section>
|
||||
<mj-raw></div>{{end}}</mj-raw>
|
||||
<mj-section padding="16px 0px 40px 0px">
|
||||
<mj-column>
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
<mj-section css-class="messageCard" padding="0px">
|
||||
<mj-group>
|
||||
<mj-column css-class="messageAvatarCol">
|
||||
<mj-image css-class="messageAvatar" src="cid:{{.SenderPhoto}}" padding="0px" />
|
||||
</mj-column>
|
||||
<mj-column css-class="senderInfoCol" width=90% >
|
||||
<mj-raw>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="postNameAndTime">
|
||||
<div class="senderName">{{.SenderName}}</div>
|
||||
{{if .Time}}
|
||||
<div class="time">{{.Time}}</div>
|
||||
{{end}}
|
||||
{{if .ChannelName}}
|
||||
<div class="channelBg">
|
||||
{{if .ShowChannelIcon}}
|
||||
<div class="channelLogo"><img src="{{$.Props.SiteURL}}/static/images/channel_icon.png" width=10px height=10px></img></div>
|
||||
{{end}}
|
||||
<div class="channelName">
|
||||
{{if .OtherChannelMembersCount}}
|
||||
<span class="gmChannelCount">{{.OtherChannelMembersCount}}</span>
|
||||
{{end}}
|
||||
{{.ChannelName}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</mj-raw>
|
||||
<mj-text css-class="senderMessage" padding="0px">
|
||||
{{.Message}}
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
<mj-column width=100%>
|
||||
<mj-raw>{{if .MessageURL}}</mj-raw>
|
||||
<mj-button href="{{.MessageURL}}" padding="16px 0px 0px 0px" css-class="messageButton">{{$.Props.MessageButton}}</mj-button>
|
||||
<mj-raw>{{end}}</mj-raw>
|
||||
</mj-column>
|
||||
</mj-group>
|
||||
</mj-section>
|
||||
146
templates/partials/message_attachment.css
Обычный файл
146
templates/partials/message_attachment.css
Обычный файл
@@ -0,0 +1,146 @@
|
||||
.messageAttachments * {
|
||||
font-family: Open Sans, sans-serif !important;
|
||||
}
|
||||
|
||||
.messageAttachmentContent {
|
||||
padding: 0px;
|
||||
border: 1px solid rgba(63, 67, 80, 0.16);
|
||||
margin-bottom: 20px;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.messageAttachmentContent > table, .attachment__body {
|
||||
font-family: Open Sans, sans-serif;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #3F4350;
|
||||
}
|
||||
|
||||
.messageAttachmentContent .attachment__author-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-right: 5px;
|
||||
border-radius: 50px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.attachment__author-name {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.messageAttachmentContent p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.attachment__title {
|
||||
padding: 0;
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.attachment__image {
|
||||
max-height: 300px;
|
||||
border: 1px solid transparent;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
|
||||
.attachment__thumb-image {
|
||||
max-width: 100%;
|
||||
max-height: 75px;
|
||||
}
|
||||
|
||||
.attachment__thumb-container {
|
||||
max-width: 80px;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.attachment__wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.attachment__body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.messageAttachmentContent > table.attachment__footer-container {
|
||||
color: #a3a3a3;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.attachment__footer-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.attachment__footer-container {
|
||||
color: #a3a3a3;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.messageAttachment_title {
|
||||
padding-top: 1em;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.pretext h1 {
|
||||
font-size: 28px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.pretext h2 {
|
||||
font-size: 25px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.pretext h3 {
|
||||
font-size: 22px;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.pretext h4 {
|
||||
font-size: 19px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.pretext h5 {
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.pretext h6 {
|
||||
font-size: 1em;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
.pretext > * {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.messageAttachments {
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.messageAttachments h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 2px 4px;
|
||||
font-size: 90%;
|
||||
background-color: rgba(63, 67, 80, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.messageAttachments a {
|
||||
background-color: unset !important;
|
||||
border: none !important;
|
||||
padding: unset !important;
|
||||
}
|
||||
95
templates/partials/message_attachment.html
Обычный файл
95
templates/partials/message_attachment.html
Обычный файл
@@ -0,0 +1,95 @@
|
||||
{{if .MessageAttachments}}
|
||||
<div class="messageAttachments">
|
||||
{{range .MessageAttachments}}
|
||||
<div class="messageAttachment" style="vertical-align:top;" width="100%">
|
||||
<div class="pretext" style="font-family: Open Sans, sans-serif; text-align: left; font-size: 14px; line-height: 20px; color: #3F4350; padding: 0px;">
|
||||
{{.Pretext}}
|
||||
</div>
|
||||
<div class="messageAttachmentContent" style="border-left: 4px solid {{.Color}}; padding: 12px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top; font-size: 14px" width="100%">
|
||||
<tbody>
|
||||
{{if or .AuthorIcon .AuthorName}}
|
||||
<tr>
|
||||
<td>
|
||||
{{if .AuthorLink}}<a href="{{.AuthorLink}}">{{end}}
|
||||
{{if .AuthorIcon}}<img class="attachment__author-icon" alt="attachment author icon" src="{{.AuthorIcon}}"/>{{end}}
|
||||
{{if .AuthorName}}<span class="attachment__author-name">{{.AuthorName}}</span>{{end}}
|
||||
{{if .AuthorLink}}</a>{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{if .Title}}
|
||||
<tr>
|
||||
<td>
|
||||
<h1 class="attachment__title">
|
||||
{{if .TitleLink}}<a href="{{.TitleLink}}">{{end}}
|
||||
{{.Title}}
|
||||
{{if .Title}}</a>{{end}}
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="attachment__wrapper">
|
||||
<div class="attachment__body">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top; font-size: 14px; color: #3F4350;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td vertical-align="middle" class="messageButton">
|
||||
<div>{{.Text}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{if .ImageURL}}
|
||||
<tr>
|
||||
<td>
|
||||
<img class="attachment__image" src="{{.ImageURL}}"/>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top; font-size: 14px; table-layout: fixed; margin-bottom: 0.7em;" width="100%">
|
||||
{{range .FieldRows}}
|
||||
<tr class="messageAttachment_row">
|
||||
{{ $length := len .Cells }}
|
||||
{{range .Cells}}
|
||||
<td
|
||||
class="messageAttachment_field"
|
||||
{{ if eq $length 1 }}colspan="2"{{end}}
|
||||
>
|
||||
<div class="messageAttachment_title">{{.Title}}</div>
|
||||
<div>{{.Value}}</div>
|
||||
</td>
|
||||
{{end}}
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{if .ThumbURL}}
|
||||
<div class="attachment__thumb-container">
|
||||
<img class="attachment__thumb-image" src="{{.ThumbURL}}"/>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if .Footer}}
|
||||
<table class="attachment__footer-container" border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top; font-size: 14px" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
{{if .FooterIcon}}<img class="attachment__footer-icon" src="{{.FooterIcon}}" style="vertical-align: middle;"/>{{end}}
|
||||
<span style="font-size: 12px;">{{.Footer}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
5
templates/partials/message_attachment_styles.mjml
Обычный файл
5
templates/partials/message_attachment_styles.mjml
Обычный файл
@@ -0,0 +1,5 @@
|
||||
<!-- this empty mj attribute tag is required by MJML to compile successfully -->
|
||||
<mj-attributes/>
|
||||
|
||||
<mj-include type="css" path="message_attachment.css"/>
|
||||
<mj-include type="css" css-inline="inline" path="message_attachment.css"/>
|
||||
3
templates/partials/message_avatar_col.mjml
Обычный файл
3
templates/partials/message_avatar_col.mjml
Обычный файл
@@ -0,0 +1,3 @@
|
||||
<mj-column css-class="messageAvatarCol">
|
||||
<mj-image css-class="messageAvatar" src="cid:{{.SenderPhoto}}" padding="0px" />
|
||||
</mj-column>
|
||||
5
templates/partials/message_button.mjml
Обычный файл
5
templates/partials/message_button.mjml
Обычный файл
@@ -0,0 +1,5 @@
|
||||
<mj-column width=100%>
|
||||
<mj-raw>{{if .MessageURL}}</mj-raw>
|
||||
<mj-button href="{{.MessageURL}}" padding="16px 0px 0px 0px" css-class="messageButton">{{$.Props.MessageButton}}</mj-button>
|
||||
<mj-raw>{{end}}</mj-raw>
|
||||
</mj-column>
|
||||
30
templates/partials/sender_info_col.mjml
Обычный файл
30
templates/partials/sender_info_col.mjml
Обычный файл
@@ -0,0 +1,30 @@
|
||||
<mj-column css-class="senderInfoCol" width=90% >
|
||||
<mj-raw>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="postNameAndTime">
|
||||
<div class="senderName">{{.SenderName}}</div>
|
||||
{{if .Time}}
|
||||
<div class="time">{{.Time}}</div>
|
||||
{{end}}
|
||||
{{if .ChannelName}}
|
||||
<div class="channelBg">
|
||||
{{if .ShowChannelIcon}}
|
||||
<div class="channelLogo"><img src="{{$.Props.SiteURL}}/static/images/channel_icon.png" width=10px height=10px></img></div>
|
||||
{{end}}
|
||||
<div class="channelName">
|
||||
{{if .OtherChannelMembersCount}}
|
||||
<span class="gmChannelCount">{{.OtherChannelMembersCount}}</span>
|
||||
{{end}}
|
||||
{{.ChannelName}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</mj-raw>
|
||||
<mj-text css-class="senderMessage" padding="0px">
|
||||
{{.Message}}
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
Ссылка в новой задаче
Block a user