[GH-15906][MM-22844] Redesign message notification emails. (#17184)

* Redesign message notification emails.

* Fix tests and linter.

* Fix tests

* Fix tests

* gofmt.

* Fix date separator

* Update html files

* Remove date for message notification email.

* Modify subtitle for mentions and direct and group messages.

* Fix lint error

* Fix DM subtitle.

* Fixing translations

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Jesús Espino <jespinog@gmail.com>
This commit is contained in:
Jyoti Patel 2021-05-10 11:50:44 -04:00 committed by GitHub
parent bb35a29ed9
commit d97daaa1de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1265 additions and 666 deletions

View file

@ -4,9 +4,11 @@
package app
import (
"bytes"
"context"
"fmt"
"html/template"
"io"
"net/http"
"strconv"
"sync"
@ -201,33 +203,61 @@ func (es *EmailService) sendBatchedEmailNotification(userID string, notification
translateFunc := i18n.GetUserTranslations(user.Locale)
displayNameFormat := *es.srv.Config().TeamSettings.TeammateNameDisplay
siteURL := *es.srv.Config().ServiceSettings.SiteURL
var contents string
for _, notification := range notifications {
sender, err := es.srv.Store.User().Get(context.Background(), notification.post.UserId)
if err != nil {
mlog.Warn("Unable to find sender of post for batched email notification")
continue
postsData := make([]*postData, 0 /* len */, len(notifications) /* cap */)
embeddedFiles := make(map[string]io.Reader)
emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL
if license := es.srv.License(); license != nil && *license.Features.EmailNotificationContents {
emailNotificationContentsType = *es.srv.Config().EmailSettings.EmailNotificationContentsType
}
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
for i, notification := range notifications {
sender, errSender := es.srv.Store.User().Get(context.Background(), notification.post.UserId)
if errSender != nil {
mlog.Warn("Unable to find sender of post for batched email notification")
}
channel, errCh := es.srv.Store.Channel().Get(notification.post.ChannelId, true)
if errCh != nil {
mlog.Warn("Unable to find channel of post for batched email notification")
}
senderProfileImage, _, errProfileImage := es.srv.GetProfileImage(sender)
if errProfileImage != nil {
mlog.Warn("Unable to get the sender user profile image.", mlog.String("user_id", sender.Id), mlog.Err(errProfileImage))
}
senderPhoto := fmt.Sprintf("user-avatar-%d.png", i)
if senderProfileImage != nil {
embeddedFiles[senderPhoto] = bytes.NewReader(senderProfileImage)
}
tm := time.Unix(notification.post.CreateAt/1000, 0)
timezone, _ := tm.Zone()
t := translateFunc("api.email_batching.send_batched_email_notification.time", map[string]interface{}{
"Hour": tm.Hour(),
"Minute": fmt.Sprintf("%02d", tm.Minute()),
"Month": translateFunc(tm.Month().String()),
"Day": tm.Day(),
"Year": tm.Year(),
"Timezone": timezone,
})
MessageURL := siteURL + "/" + notification.teamName + "/pl/" + notification.post.Id
postsData = append(postsData, &postData{
SenderPhoto: senderPhoto,
SenderName: sender.GetDisplayName(displayNameFormat),
Time: t,
ChannelName: channel.DisplayName,
Message: template.HTML(es.srv.GetMessageForNotification(notification.post, translateFunc)),
MessageURL: MessageURL,
})
}
channel, errCh := es.srv.Store.Channel().Get(notification.post.ChannelId, true)
if errCh != nil {
mlog.Warn("Unable to find channel of post for batched email notification")
continue
}
emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL
if license := es.srv.License(); license != nil && *license.Features.EmailNotificationContents {
emailNotificationContentsType = *es.srv.Config().EmailSettings.EmailNotificationContentsType
}
postContent, err := es.renderBatchedPost(notification, channel, sender, *es.srv.Config().ServiceSettings.SiteURL, displayNameFormat, translateFunc, user.Locale, emailNotificationContentsType)
if err != nil {
mlog.Warn("Unable to render post for batched email notification template", mlog.Err(err))
continue
}
contents += postContent
}
tm := time.Unix(notifications[0].post.CreateAt/1000, 0)
@ -239,59 +269,31 @@ func (es *EmailService) sendBatchedEmailNotification(userID string, notification
"Day": tm.Day(),
})
data := es.newEmailTemplateData(user.Locale)
data.Props["SiteURL"] = *es.srv.Config().ServiceSettings.SiteURL
data.Props["Posts"] = template.HTML(contents)
data.Props["BodyText"] = translateFunc("api.email_batching.send_batched_email_notification.body_text", len(notifications))
body, err2 := es.srv.TemplatesContainer().RenderToString("post_batched_body", data)
if err2 != nil {
mlog.Warn("Unable build the batched email notification template", mlog.Err(err2))
return
firstSender, err := es.srv.Store.User().Get(context.Background(), notifications[0].post.UserId)
if err != nil {
mlog.Warn("Unable to find sender of post for batched email notification")
}
if nErr := es.sendNotificationMail(user.Email, subject, body); nErr != nil {
data := es.newEmailTemplateData(user.Locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = translateFunc("api.email_batching.send_batched_email_notification.title", len(notifications)-1, map[string]interface{}{
"SenderName": firstSender.GetDisplayName(displayNameFormat),
})
data.Props["SubTitle"] = translateFunc("api.email_batching.send_batched_email_notification.subTitle")
data.Props["Button"] = translateFunc("api.email_batching.send_batched_email_notification.button")
data.Props["ButtonURL"] = siteURL
data.Props["Posts"] = postsData
data.Props["MessageButton"] = translateFunc("api.email_batching.send_batched_email_notification.messageButton")
data.Props["NotificationFooterTitle"] = translateFunc("app.notification.footer.title")
data.Props["NotificationFooterInfoLogin"] = translateFunc("app.notification.footer.infoLogin")
data.Props["NotificationFooterInfo"] = translateFunc("app.notification.footer.info")
renderedPage, renderErr := es.srv.TemplatesContainer().RenderToString("messages_notification", data)
if renderErr != nil {
mlog.Error("Unable to render email", mlog.Err(renderErr))
}
if nErr := es.sendNotificationMail(user.Email, subject, renderedPage); nErr != nil {
mlog.Warn("Unable to send batched email notification", mlog.String("email", user.Email), mlog.Err(nErr))
}
}
func (es *EmailService) renderBatchedPost(notification *batchedNotification, channel *model.Channel, sender *model.User, siteURL string, displayNameFormat string, translateFunc i18n.TranslateFunc, userLocale string, emailNotificationContentsType string) (string, error) {
// don't include message contents if email notification contents type is set to generic
var templateName = "post_batched_post_generic"
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
templateName = "post_batched_post_full"
}
data := es.newEmailTemplateData(userLocale)
data.Props["Button"] = translateFunc("api.email_batching.render_batched_post.go_to_post")
data.Props["PostMessage"] = es.srv.GetMessageForNotification(notification.post, translateFunc)
data.Props["PostLink"] = siteURL + "/" + notification.teamName + "/pl/" + notification.post.Id
data.Props["SenderName"] = sender.GetDisplayName(displayNameFormat)
tm := time.Unix(notification.post.CreateAt/1000, 0)
timezone, _ := tm.Zone()
data.Props["Date"] = translateFunc("api.email_batching.render_batched_post.date", map[string]interface{}{
"Year": tm.Year(),
"Month": translateFunc(tm.Month().String()),
"Day": tm.Day(),
"Hour": tm.Hour(),
"Minute": fmt.Sprintf("%02d", tm.Minute()),
"Timezone": timezone,
})
if channel.Type == model.CHANNEL_DIRECT {
data.Props["ChannelName"] = translateFunc("api.email_batching.render_batched_post.direct_message")
} else if channel.Type == model.CHANNEL_GROUP {
data.Props["ChannelName"] = translateFunc("api.email_batching.render_batched_post.group_message")
} else {
// don't include channel name if email notification contents type is set to generic
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
data.Props["ChannelName"] = channel.DisplayName
} else {
data.Props["ChannelName"] = translateFunc("api.email_batching.render_batched_post.notification")
}
}
return es.srv.TemplatesContainer().RenderToString(templateName, data)
}

View file

@ -283,55 +283,3 @@ func TestCheckPendingNotificationsCantParseInterval(t *testing.T) {
require.Nil(t, job.pendingNotifications[th.BasicUser.Id], "should have sent queued post")
}
/*
* Ensures that post contents are not included in notification email when email notification content type is set to generic
*/
func TestRenderBatchedPostGeneric(t *testing.T) {
th := SetupWithStoreMock(t)
defer th.TearDown()
var post = &model.Post{}
post.Message = "This is the message"
var notification = &batchedNotification{}
notification.post = post
var channel = &model.Channel{}
channel.DisplayName = "Some Test Channel"
var sender = &model.User{}
sender.Email = "sender@test.com"
translateFunc := func(translationID string, args ...interface{}) string {
// mock translateFunc just returns the translation id - this is good enough for our purposes
return translationID
}
rendered, err := th.Server.EmailService.renderBatchedPost(notification, channel, sender, "http://localhost:8065", "", translateFunc, "en", model.EMAIL_NOTIFICATION_CONTENTS_GENERIC)
require.NoError(t, err)
require.NotContains(t, rendered, post.Message, "Rendered email should not contain post contents when email notification contents type is set to Generic.")
}
/*
* Ensures that post contents included in notification email when email notification content type is set to full
*/
func TestRenderBatchedPostFull(t *testing.T) {
th := SetupWithStoreMock(t)
defer th.TearDown()
var post = &model.Post{}
post.Message = "This is the message"
var notification = &batchedNotification{}
notification.post = post
var channel = &model.Channel{}
channel.DisplayName = "Some Test Channel"
var sender = &model.User{}
sender.Email = "sender@test.com"
translateFunc := func(translationID string, args ...interface{}) string {
// mock translateFunc just returns the translation id - this is good enough for our purposes
return translationID
}
rendered, err := th.Server.EmailService.renderBatchedPost(notification, channel, sender, "http://localhost:8065", "", translateFunc, "en", model.EMAIL_NOTIFICATION_CONTENTS_FULL)
require.NoError(t, err)
require.Contains(t, rendered, post.Message, "Rendered email should contain post contents when email notification contents type is set to Full.")
}

View file

@ -128,15 +128,7 @@ func (a *App) TestFileStoreConnectionWithConfig(cfg *model.FileSettings) *model.
}
func (a *App) ReadFile(path string) ([]byte, *model.AppError) {
backend, err := a.FileBackend()
if err != nil {
return nil, err
}
result, nErr := backend.ReadFile(path)
if nErr != nil {
return nil, model.NewAppError("ReadFile", "api.file.read_file.app_error", nil, nErr.Error(), http.StatusInternalServerError)
}
return result, nil
return a.srv.ReadFile(path)
}
// Caller must close the first return value
@ -202,16 +194,7 @@ func (a *App) MoveFile(oldPath, newPath string) *model.AppError {
}
func (a *App) WriteFile(fr io.Reader, path string) (int64, *model.AppError) {
backend, err := a.FileBackend()
if err != nil {
return 0, err
}
result, nErr := backend.WriteFile(fr, path)
if nErr != nil {
return result, model.NewAppError("WriteFile", "api.file.write_file.app_error", nil, nErr.Error(), http.StatusInternalServerError)
}
return result, nil
return a.srv.WriteFile(fr, path)
}
func (a *App) AppendFile(fr io.Reader, path string) (int64, *model.AppError) {

View file

@ -254,8 +254,11 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
}
if a.userAllowsEmail(profileMap[id], channelMemberNotifyPropsMap[id], post) {
err := a.sendNotificationEmail(notification, profileMap[id], team)
senderProfileImage, _, err := a.GetProfileImage(sender)
if err != nil {
a.Log().Warn("Unable to get the sender user profile image.", mlog.String("user_id", sender.Id), mlog.Err(err))
}
if err := a.sendNotificationEmail(notification, profileMap[id], team, senderProfileImage); err != nil {
mlog.Warn("Unable to send notification email.", mlog.Err(err))
}
}

View file

@ -4,9 +4,11 @@
package app
import (
"bytes"
"fmt"
"html"
"html/template"
"io"
"net/url"
"path/filepath"
"strings"
@ -18,7 +20,7 @@ import (
"github.com/pkg/errors"
)
func (a *App) sendNotificationEmail(notification *PostNotification, user *model.User, team *model.Team) error {
func (a *App) sendNotificationEmail(notification *PostNotification, user *model.User, team *model.Team, senderProfileImage []byte) error {
channel := notification.Channel
post := notification.Post
@ -95,14 +97,24 @@ func (a *App) sendNotificationEmail(notification *PostNotification, user *model.
subjectText = getNotificationEmailSubject(user, post, translateFunc, *a.Config().TeamSettings.SiteName, team.DisplayName, useMilitaryTime)
}
senderPhoto := ""
embeddedFiles := make(map[string]io.Reader)
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL && senderProfileImage != nil {
senderPhoto = "user-avatar.png"
embeddedFiles = map[string]io.Reader{
senderPhoto: bytes.NewReader(senderProfileImage),
}
}
landingURL := a.GetSiteURL() + "/landing#/" + team.Name
var bodyText, err = a.getNotificationEmailBody(user, post, channel, channelName, senderName, team.Name, landingURL, emailNotificationContentsType, useMilitaryTime, translateFunc)
var bodyText, err = a.getNotificationEmailBody(user, post, channel, channelName, senderName, team.Name, landingURL, emailNotificationContentsType, useMilitaryTime, translateFunc, senderPhoto)
if err != nil {
return errors.Wrap(err, "unable to render the email notification template")
}
a.Srv().Go(func() {
if nErr := a.Srv().EmailService.sendNotificationMail(user.Email, html.UnescapeString(subjectText), bodyText); nErr != nil {
if nErr := a.Srv().EmailService.sendMailWithEmbeddedFiles(user.Email, html.UnescapeString(subjectText), bodyText, embeddedFiles); nErr != nil {
mlog.Error("Error while sending the email", mlog.String("user_email", user.Email), mlog.Err(nErr))
}
})
@ -162,15 +174,33 @@ func getGroupMessageNotificationEmailSubject(user *model.User, post *model.Post,
return translateFunc("app.notification.subject.group_message.generic", subjectParameters)
}
type postData struct {
SenderName string
ChannelName string
Message template.HTML
MessageURL string
SenderPhoto string
PostPhoto string
Time string
}
/**
* Computes the email body for notification messages
*/
func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post, channel *model.Channel, channelName string, senderName string, teamName string, landingURL string, emailNotificationContentsType string, useMilitaryTime bool, translateFunc i18n.TranslateFunc) (string, error) {
// only include message contents in notification email if email notification contents type is set to full
var templateName = "post_body_generic"
data := a.Srv().EmailService.newEmailTemplateData(recipient.Locale)
func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post, channel *model.Channel, channelName string, senderName string, teamName string, landingURL string, emailNotificationContentsType string, useMilitaryTime bool, translateFunc i18n.TranslateFunc, senderPhoto string) (string, error) {
pData := postData{
SenderName: senderName,
SenderPhoto: senderPhoto,
}
t := getFormattedPostTime(recipient, post, useMilitaryTime, translateFunc)
messageTime := map[string]interface{}{
"Hour": t.Hour,
"Minute": t.Minute,
"TimeZone": t.TimeZone,
}
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
templateName = "post_body_full"
postMessage := a.GetMessageForNotification(post, translateFunc)
postMessage = html.EscapeString(postMessage)
normalizedPostMessage, err := a.generateHyperlinkForChannels(postMessage, teamName, landingURL)
@ -178,72 +208,47 @@ func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post,
mlog.Warn("Encountered error while generating hyperlink for channels", mlog.String("team_name", teamName), mlog.Err(err))
normalizedPostMessage = postMessage
}
data.Props["PostMessage"] = template.HTML(normalizedPostMessage)
pData.Message = template.HTML(normalizedPostMessage)
pData.Time = translateFunc("app.notification.body.dm.time", messageTime)
}
data := a.Srv().EmailService.newEmailTemplateData(recipient.Locale)
data.Props["SiteURL"] = a.GetSiteURL()
if teamName != "select_team" {
data.Props["TeamLink"] = landingURL + "/pl/" + post.Id
data.Props["ButtonURL"] = landingURL + "/pl/" + post.Id
} else {
data.Props["TeamLink"] = landingURL
}
t := getFormattedPostTime(recipient, post, useMilitaryTime, translateFunc)
info := map[string]interface{}{
"Hour": t.Hour,
"Minute": t.Minute,
"TimeZone": t.TimeZone,
"Month": t.Month,
"Day": t.Day,
}
if channel.Type == model.CHANNEL_DIRECT {
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
data.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.full")
data.Props["Info1"] = ""
info["SenderName"] = senderName
data.Props["Info2"] = translateFunc("app.notification.body.text.direct.full", info)
} else {
data.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.generic", map[string]interface{}{
"SenderName": senderName,
})
data.Props["Info"] = translateFunc("app.notification.body.text.direct.generic", info)
}
} else if channel.Type == model.CHANNEL_GROUP {
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
data.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.full")
data.Props["Info1"] = translateFunc("app.notification.body.text.group_message.full",
map[string]interface{}{
"ChannelName": channelName,
})
info["SenderName"] = senderName
data.Props["Info2"] = translateFunc("app.notification.body.text.group_message.full2", info)
} else {
data.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.generic", map[string]interface{}{
"SenderName": senderName,
})
data.Props["Info"] = translateFunc("app.notification.body.text.group_message.generic", info)
}
} else {
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
data.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.full")
data.Props["Info1"] = translateFunc("app.notification.body.text.notification.full",
map[string]interface{}{
"ChannelName": channelName,
})
info["SenderName"] = senderName
data.Props["Info2"] = translateFunc("app.notification.body.text.notification.full2", info)
} else {
data.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.generic", map[string]interface{}{
"SenderName": senderName,
})
data.Props["Info"] = translateFunc("app.notification.body.text.notification.generic", info)
}
data.Props["ButtonURL"] = landingURL
}
data.Props["SenderName"] = senderName
data.Props["Button"] = translateFunc("api.templates.post_body.button")
data.Props["NotificationFooterTitle"] = translateFunc("app.notification.footer.title")
data.Props["NotificationFooterInfoLogin"] = translateFunc("app.notification.footer.infoLogin")
data.Props["NotificationFooterInfo"] = translateFunc("app.notification.footer.info")
return a.Srv().TemplatesContainer().RenderToString(templateName, data)
if channel.Type == model.CHANNEL_DIRECT {
// Direct Messages
data.Props["Title"] = translateFunc("app.notification.body.dm.title", map[string]interface{}{"SenderName": senderName})
data.Props["SubTitle"] = translateFunc("app.notification.body.dm.subTitle", map[string]interface{}{"SenderName": senderName})
} else if channel.Type == model.CHANNEL_GROUP {
// Group Messages
data.Props["Title"] = translateFunc("app.notification.body.group.title", map[string]interface{}{"SenderName": senderName})
data.Props["SubTitle"] = translateFunc("app.notification.body.group.subTitle", map[string]interface{}{"SenderName": senderName})
} else {
// mentions
data.Props["Title"] = translateFunc("app.notification.body.mention.title", map[string]interface{}{"SenderName": senderName})
data.Props["SubTitle"] = translateFunc("app.notification.body.mention.subTitle", map[string]interface{}{"SenderName": senderName, "ChannelName": channelName})
pData.ChannelName = channelName
}
// only include posts in notification email if email notification contents type is set to full
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
data.Props["Posts"] = []postData{pData}
} else {
data.Props["Posts"] = []postData{}
}
return a.Srv().TemplatesContainer().RenderToString("messages_notification", data)
}
type formattedPostTime struct {

View file

@ -8,7 +8,6 @@ import (
"fmt"
"html/template"
"regexp"
"strings"
"testing"
"time"
@ -91,11 +90,9 @@ func TestGetNotificationEmailBodyFullNotificationPublicChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new notification.", fmt.Sprintf("Expected email text 'You have a new notification. Got %s", body))
require.Contains(t, body, "Channel: "+channel.DisplayName, "Expected email text 'Channel: %s'. Got %s", channel.DisplayName, body)
require.Contains(t, body, senderName+" - ", fmt.Sprintf("Expected email text '%s - '. Got %s", senderName, body))
require.Contains(t, body, "mentioned you in a message", fmt.Sprintf("Expected email text 'mentioned you in a message. Got %s", body))
require.Contains(t, body, post.Message, fmt.Sprintf("Expected email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@ -124,11 +121,9 @@ func TestGetNotificationEmailBodyFullNotificationGroupChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new Group Message.", fmt.Sprintf("Expected email text 'You have a new Group Message. Got "+body))
require.Contains(t, body, "Channel: ChannelName", fmt.Sprintf("Expected email text 'Channel: ChannelName'. Got %s", body))
require.Contains(t, body, senderName+" - ", fmt.Sprintf("Expected email text '%s - '. Got %s", senderName, body))
require.Contains(t, body, "sent you a new message", fmt.Sprintf("Expected email text 'sent you a new message. Got "+body))
require.Contains(t, body, post.Message, fmt.Sprintf("Expected email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@ -157,11 +152,9 @@ func TestGetNotificationEmailBodyFullNotificationPrivateChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new notification.", fmt.Sprintf("Expected email text 'You have a new notification. Got "+body))
require.Contains(t, body, "Channel: "+channel.DisplayName, fmt.Sprintf("Expected email text 'Channel: "+channel.DisplayName+"'. Got "+body))
require.Contains(t, body, senderName+" - ", fmt.Sprintf("Expected email text '%s - '. Got %s", senderName, body))
require.Contains(t, body, "mentioned you in a message", fmt.Sprintf("Expected email text 'mentioned you in a message. Got "+body))
require.Contains(t, body, post.Message, fmt.Sprintf("Expected email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@ -190,10 +183,9 @@ func TestGetNotificationEmailBodyFullNotificationDirectChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new Direct Message.", fmt.Sprintf("Expected email text 'You have a new Direct Message. Got "+body))
require.Contains(t, body, senderName+" - ", fmt.Sprintf("Expected email text '%s - '. Got %s", senderName, body))
require.Contains(t, body, "sent you a new message", fmt.Sprintf("Expected email text 'sent you a new message. Got "+body))
require.Contains(t, body, post.Message, fmt.Sprintf("Expected email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@ -226,11 +218,11 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTimeWithTimezone(t *testi
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc, "user-avatar.png")
require.NoError(t, err)
r, _ := regexp.Compile("E([S|D]+)T")
zone := r.FindString(body)
require.Contains(t, body, "sender - 9:43 AM "+zone+", April 25", fmt.Sprintf("Expected email text 'sender - 9:43 AM %s, April 25'. Got %s", zone, body))
require.Contains(t, body, "9:43 AM "+zone, fmt.Sprintf("Expected email text '9:43 AM %s'. Got %s", zone, body))
}
func TestGetNotificationEmailBodyFullNotificationLocaleTimeNoTimezone(t *testing.T) {
@ -276,10 +268,10 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTimeNoTimezone(t *testing
tmp, err := template.New("foo").Parse(`{{.}}`)
require.NoError(t, err)
var text bytes.Buffer
err = tmp.Execute(&text, fmt.Sprintf("sender - %s:%s %s, %s %s", formattedTime.Hour, formattedTime.Minute, formattedTime.TimeZone, formattedTime.Month, formattedTime.Day))
err = tmp.Execute(&text, fmt.Sprintf("%s:%s %s", formattedTime.Hour, formattedTime.Minute, formattedTime.TimeZone))
require.NoError(t, err)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
postTimeLine := text.String()
require.Contains(t, body, postTimeLine, fmt.Sprintf("Expected email text '%s'. Got %s", postTimeLine, body))
@ -313,10 +305,9 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTime12Hour(t *testing.T)
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "sender - 2:30 PM", fmt.Sprintf("Expected email text 'sender - 2:30 PM'. Got %s", body))
require.Contains(t, body, "April 25", fmt.Sprintf("Expected email text 'April 25'. Got %s", body))
require.Contains(t, body, "2:30 PM", fmt.Sprintf("Expected email text '2:30 PM'. Got %s", body))
}
func TestGetNotificationEmailBodyFullNotificationLocaleTime24Hour(t *testing.T) {
@ -347,10 +338,9 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTime24Hour(t *testing.T)
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "sender - 14:30", fmt.Sprintf("Expected email text 'sender - 14:30'. Got %s", body))
require.Contains(t, body, "April 25", fmt.Sprintf("Expected email text 'April 25'. Got %s", body))
require.Contains(t, body, "14:30", fmt.Sprintf("Expected email text '14:30'. Got %s", body))
}
// from here
@ -378,11 +368,9 @@ func TestGetNotificationEmailBodyGenericNotificationPublicChannel(t *testing.T)
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new notification from "+senderName, fmt.Sprintf("Expected email text 'You have a new notification from %s'. Got %s", senderName, body))
require.False(t, strings.Contains(body, "Channel: "+channel.DisplayName), fmt.Sprintf("Did not expect email text 'CHANNEL: %s'. Got %s", channel.DisplayName, body))
require.False(t, strings.Contains(body, post.Message), fmt.Sprintf("Did not expect email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, "mentioned you in a message", fmt.Sprintf("Expected email text 'mentioned you in a message. Got %s", body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@ -410,11 +398,9 @@ func TestGetNotificationEmailBodyGenericNotificationGroupChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new Group Message from "+senderName, fmt.Sprintf("Expected email text 'You have a new Group Message from %s'. Got %s", senderName, body))
require.False(t, strings.Contains(body, "CHANNEL: "+channel.DisplayName), fmt.Sprintf("Did not expect email text 'CHANNEL: %s'. Got %s", channel.DisplayName, body))
require.False(t, strings.Contains(body, post.Message), fmt.Sprintf("Did not expect email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, "sent you a new message", fmt.Sprintf("Expected email text 'sent you a new message. Got "+body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@ -442,11 +428,9 @@ func TestGetNotificationEmailBodyGenericNotificationPrivateChannel(t *testing.T)
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new notification from "+senderName, fmt.Sprintf("Expected email text 'You have a new notification from %s'. Got %s", senderName, body))
require.False(t, strings.Contains(body, "CHANNEL: "+channel.DisplayName), fmt.Sprintf("Did not expect email text 'CHANNEL: %s'. Got %s", channel.DisplayName, body))
require.False(t, strings.Contains(body, post.Message), fmt.Sprintf("Did not expect email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, "mentioned you in a message", fmt.Sprintf("Expected email text 'mentioned you in a message. Got %s", body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@ -474,11 +458,9 @@ func TestGetNotificationEmailBodyGenericNotificationDirectChannel(t *testing.T)
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new Direct Message from "+senderName, fmt.Sprintf("Expected email text 'You have a new Direct Message from "+senderName+"'. Got "+body))
require.False(t, strings.Contains(body, "CHANNEL: "+channel.DisplayName), fmt.Sprintf("Did not expect email text 'CHANNEL: %s'. Got %s", channel.DisplayName, body))
require.False(t, strings.Contains(body, post.Message), fmt.Sprintf("Did not expect email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, "sent you a new message", fmt.Sprintf("Expected email text 'sent you a new message. Got "+body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@ -510,7 +492,7 @@ func TestGetNotificationEmailEscapingChars(t *testing.T) {
body, err := th.App.getNotificationEmailBody(recipient, post, ch,
channelName, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc)
emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
assert.NotContains(t, body, message)
@ -554,7 +536,7 @@ func TestGetNotificationEmailBodyPublicChannelMention(t *testing.T) {
body, err := th.App.getNotificationEmailBody(recipient, post, ch,
ch.Name, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc)
emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
channelURL := teamURL + "/channels/" + ch.Name
mention := "~" + ch.Name
@ -620,7 +602,7 @@ func TestGetNotificationEmailBodyMultiPublicChannelMention(t *testing.T) {
body, err := th.App.getNotificationEmailBody(recipient, post, ch,
ch.Name, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc)
emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
channelURL := teamURL + "/channels/" + ch.Name
channelURL2 := teamURL + "/channels/" + ch2.Name
@ -669,7 +651,7 @@ func TestGetNotificationEmailBodyPrivateChannelMention(t *testing.T) {
body, err := th.App.getNotificationEmailBody(recipient, post, ch,
ch.Name, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc)
emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
channelURL := teamURL + "/channels/" + ch.Name
mention := "~" + ch.Name
@ -813,7 +795,7 @@ func TestLandingLink(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@ -843,7 +825,7 @@ func TestLandingLinkPermalink(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, teamURL+"/pl/"+post.Id, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}

View file

@ -4,12 +4,14 @@
package app
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"hash/maphash"
"html/template"
"io"
"io/ioutil"
"net"
"net/http"
@ -2114,6 +2116,76 @@ func (a *App) generateSupportPacketYaml() (*model.FileData, string) {
return nil, warning
}
func (s *Server) GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) {
if *s.Config().FileSettings.DriverName == "" {
img, appErr := s.GetDefaultProfileImage(user)
if appErr != nil {
return nil, false, appErr
}
return img, false, nil
}
path := "users/" + user.Id + "/profile.png"
data, err := s.ReadFile(path)
if err != nil {
img, appErr := s.GetDefaultProfileImage(user)
if appErr != nil {
return nil, false, appErr
}
if user.LastPictureUpdate == 0 {
if _, err := s.WriteFile(bytes.NewReader(img), path); err != nil {
return nil, false, err
}
}
return img, true, nil
}
return data, false, nil
}
func (s *Server) GetDefaultProfileImage(user *model.User) ([]byte, *model.AppError) {
var img []byte
var appErr *model.AppError
if user.IsBot {
img = model.BotDefaultImage
appErr = nil
} else {
img, appErr = CreateProfileImage(user.Username, user.Id, *s.Config().FileSettings.InitialFont)
}
if appErr != nil {
return nil, appErr
}
return img, nil
}
func (s *Server) ReadFile(path string) ([]byte, *model.AppError) {
backend, err := s.FileBackend()
if err != nil {
return nil, err
}
result, nErr := backend.ReadFile(path)
if nErr != nil {
return nil, model.NewAppError("ReadFile", "api.file.read_file.app_error", nil, nErr.Error(), http.StatusInternalServerError)
}
return result, nil
}
func (s *Server) WriteFile(fr io.Reader, path string) (int64, *model.AppError) {
backend, err := s.FileBackend()
if err != nil {
return 0, err
}
result, nErr := backend.WriteFile(fr, path)
if nErr != nil {
return result, model.NewAppError("WriteFile", "api.file.write_file.app_error", nil, nErr.Error(), http.StatusInternalServerError)
}
return result, nil
}
func runDNDStatusExpireJob(a *App) {
if a.IsLeader() {
a.srv.dndnTaskMut.Lock()

View file

@ -875,48 +875,11 @@ func getFont(initialFont string) (*truetype.Font, error) {
}
func (a *App) GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) {
if *a.Config().FileSettings.DriverName == "" {
img, appErr := a.GetDefaultProfileImage(user)
if appErr != nil {
return nil, false, appErr
}
return img, false, nil
}
path := "users/" + user.Id + "/profile.png"
data, err := a.ReadFile(path)
if err != nil {
img, appErr := a.GetDefaultProfileImage(user)
if appErr != nil {
return nil, false, appErr
}
if user.LastPictureUpdate == 0 {
if _, err := a.WriteFile(bytes.NewReader(img), path); err != nil {
return nil, false, err
}
}
return img, true, nil
}
return data, false, nil
return a.srv.GetProfileImage(user)
}
func (a *App) GetDefaultProfileImage(user *model.User) ([]byte, *model.AppError) {
var img []byte
var appErr *model.AppError
if user.IsBot {
img = model.BotDefaultImage
appErr = nil
} else {
img, appErr = CreateProfileImage(user.Username, user.Id, *a.Config().FileSettings.InitialFont)
}
if appErr != nil {
return nil, appErr
}
return img, nil
return a.srv.GetDefaultProfileImage(user)
}
func (a *App) SetDefaultProfileImage(user *model.User) *model.AppError {

View file

@ -1611,31 +1611,16 @@
"translation": "Email batching has been disabled by the system administrator."
},
{
"id": "api.email_batching.render_batched_post.date",
"translation": "{{.Hour}}:{{.Minute}} {{.Timezone}}, {{.Month}} {{.Day}}"
"id": "api.email_batching.send_batched_email_notification.button",
"translation": "Open Mattermost"
},
{
"id": "api.email_batching.render_batched_post.direct_message",
"translation": "Direct Message from "
"id": "api.email_batching.send_batched_email_notification.messageButton",
"translation": "View this message"
},
{
"id": "api.email_batching.render_batched_post.go_to_post",
"translation": "Go to Post"
},
{
"id": "api.email_batching.render_batched_post.group_message",
"translation": "Group Message from "
},
{
"id": "api.email_batching.render_batched_post.notification",
"translation": "Notification from "
},
{
"id": "api.email_batching.send_batched_email_notification.body_text",
"translation": {
"one": "You have a new notification.",
"other": "You have {{.Count}} new notifications."
}
"id": "api.email_batching.send_batched_email_notification.subTitle",
"translation": "See below for a summary of your new messages."
},
{
"id": "api.email_batching.send_batched_email_notification.subject",
@ -1644,6 +1629,17 @@
"other": "[{{.SiteName}}] New Notifications for {{.Month}} {{.Day}}, {{.Year}}"
}
},
{
"id": "api.email_batching.send_batched_email_notification.time",
"translation": "{{.Hour}}:{{.Minute}} {{.TimeZone}}"
},
{
"id": "api.email_batching.send_batched_email_notification.title",
"translation": {
"one": "{{ .SenderName }} sent you new message",
"other": "{{ .SenderName }} and {{.Count}} others sent you new messages"
}
},
{
"id": "api.emoji.create.duplicate.app_error",
"translation": "Unable to create emoji. Another emoji with the same name already exists."
@ -3488,7 +3484,7 @@
},
{
"id": "api.templates.post_body.button",
"translation": "Go To Post"
"translation": "View Message"
},
{
"id": "api.templates.questions_footer.info",
@ -5447,60 +5443,44 @@
"translation": "No license present"
},
{
"id": "app.notification.body.intro.direct.full",
"translation": "You have a new Direct Message."
"id": "app.notification.body.dm.subTitle",
"translation": "While you were away, {{.SenderName}} sent you a new Direct Message."
},
{
"id": "app.notification.body.intro.direct.generic",
"translation": "You have a new Direct Message from {{.SenderName}}"
"id": "app.notification.body.dm.time",
"translation": "{{.Hour}}:{{.Minute}} {{.TimeZone}}"
},
{
"id": "app.notification.body.intro.group_message.full",
"translation": "You have a new Group Message."
"id": "app.notification.body.dm.title",
"translation": "{{.SenderName}} sent you a new message"
},
{
"id": "app.notification.body.intro.group_message.generic",
"translation": "You have a new Group Message from {{.SenderName}}"
"id": "app.notification.body.group.subTitle",
"translation": "While you were away, {{.SenderName}} sent a message to your group."
},
{
"id": "app.notification.body.intro.notification.full",
"translation": "You have a new notification."
"id": "app.notification.body.group.title",
"translation": "{{.SenderName}} sent you a new message"
},
{
"id": "app.notification.body.intro.notification.generic",
"translation": "You have a new notification from {{.SenderName}}"
"id": "app.notification.body.mention.subTitle",
"translation": "While you were away, {{.SenderName}} mentioned you in the {{.ChannelName}} channel."
},
{
"id": "app.notification.body.text.direct.full",
"translation": "{{.SenderName}} - {{.Hour}}:{{.Minute}} {{.TimeZone}}, {{.Month}} {{.Day}}"
"id": "app.notification.body.mention.title",
"translation": "{{.SenderName}} mentioned you in a message"
},
{
"id": "app.notification.body.text.direct.generic",
"translation": "{{.Hour}}:{{.Minute}} {{.TimeZone}}, {{.Month}} {{.Day}}"
"id": "app.notification.footer.info",
"translation": " and go to Account Settings > Notifications"
},
{
"id": "app.notification.body.text.group_message.full",
"translation": "Channel: {{.ChannelName}}"
"id": "app.notification.footer.infoLogin",
"translation": "Login to Mattermost"
},
{
"id": "app.notification.body.text.group_message.full2",
"translation": "{{.SenderName}} - {{.Hour}}:{{.Minute}} {{.TimeZone}}, {{.Month}} {{.Day}}"
},
{
"id": "app.notification.body.text.group_message.generic",
"translation": "{{.Hour}}:{{.Minute}} {{.TimeZone}}, {{.Month}} {{.Day}}"
},
{
"id": "app.notification.body.text.notification.full",
"translation": "Channel: {{.ChannelName}}"
},
{
"id": "app.notification.body.text.notification.full2",
"translation": "{{.SenderName}} - {{.Hour}}:{{.Minute}} {{.TimeZone}}, {{.Month}} {{.Day}}"
},
{
"id": "app.notification.body.text.notification.generic",
"translation": "{{.Hour}}:{{.Minute}} {{.TimeZone}}, {{.Month}} {{.Day}}"
"id": "app.notification.footer.title",
"translation": "Want to change your notifications settings?"
},
{
"id": "app.notification.subject.direct.full",

View file

@ -125,7 +125,7 @@
font-weight: 600 !important;
font-size: 28px !important;
line-height: 36px !important;
letter-spacing: -0.02em !important;
letter-spacing: -0.01em !important;
color: #3D3C40 !important;
}
@ -144,6 +144,17 @@
padding: 15px 24px !important;
}
.messageButton a {
background-color: #FFFFFF !important;
border: 1px solid #FFFFFF !important;
box-sizing: border-box !important;
color: #166DE0 !important;
padding: 12px 20px !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 14px !important;
}
.info div {
font-size: 14px !important;
line-height: 20px !important;
@ -212,13 +223,52 @@
width: 32px !important;
}
.senderName div {
.postNameAndTime {
padding: 0px 0px 4px 0px !important;
display: flex;
}
.senderName {
font-family: Open Sans, sans-serif;
text-align: left !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 20px !important;
color: #3D3C40 !important;
padding: 0px 0px 4px 0px !important;
}
.time {
font-family: Open Sans, sans-serif;
font-size: 12px;
line-height: 16px;
color: rgba(61, 60, 64, 0.56);
padding: 2px 6px;
align-items: center;
float: left;
}
.channelBg {
background: rgba(61, 60, 64, 0.08);
border-radius: 4px;
display: flex;
}
.channelLogo {
width: 10px;
height: 10px;
padding: 5px 4px 5px 6px;
float: left;
}
.channelName {
font-family: Open Sans, sans-serif;
font-weight: 600;
font-size: 10px;
line-height: 16px;
letter-spacing: 0.01em;
text-transform: uppercase;
color: rgba(61, 60, 64, 0.64);
padding: 2px 6px 2px 0px;
}
.senderMessage div {
@ -383,7 +433,7 @@
<tbody>
<tr>
<td style="width:276px;">
<img alt="" height="auto" src="cid:user-avatar.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="276" />
<img alt="" height="auto" src="cid:{{.SenderPhoto}}" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="276" />
</td>
</tr>
</tbody>
@ -398,15 +448,41 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="center" class="senderName" style="font-size:0px;padding:0px;word-break:break-word;">
<div style="font-family:Open Sans, sans-serif;font-size:13px;line-height:1;text-align:center;color:#000000;">{{.Props.SenderName}}</div>
<td>
<div class="postNameAndTime">
<div class="senderName">{{.SenderName}}</div>
{{if .Time}}
<div class="time">{{.Time}}</div>
{{end}}
{{if .ChannelName}}
<div class="channelBg">
<div class="channelLogo"><img src="{{$.Props.SiteURL}}/static/images/channel_icon.png" width=10px height=10px></img></div>
<div class="channelName">{{.ChannelName}}</div>
</div>
{{end}}
</div>
</td>
</tr>
<tr>
<td align="center" class="senderMessage" style="font-size:0px;padding:0px;word-break:break-word;">
<div style="font-family:Open Sans, sans-serif;font-size:13px;line-height:1;text-align:center;color:#000000;">{{.Props.Message}}</div>
<div style="font-family:Open Sans, sans-serif;font-size:13px;line-height:1;text-align:center;color:#000000;">{{.Message}}</div>
</td>
</tr>
{{if .MessageURL}}
<tr>
<td align="center" vertical-align="middle" class="messageButton" style="font-size:0px;padding:16px 0px 0px 0px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
<tr>
<td align="center" bgcolor="#FFFFFF" role="presentation" style="border:none;border-radius:4px;cursor:auto;mso-padding-alt:10px 25px;background:#FFFFFF;" valign="middle">
<a href="{{.MessageURL}}" style="display:inline-block;background:#FFFFFF;color:#ffffff;font-family:Open Sans, sans-serif;font-size:13px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:4px;" target="_blank">
{{$.Props.MessageButton}}
</a>
</td>
</tr>
</table>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>

View file

@ -0,0 +1,550 @@
{{define "messages_notification"}}
<!-- FILE: messages_notification.mjml -->
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<title>
</title>
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
#outlook a {
padding: 0;
}
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table,
td {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
p {
display: block;
margin: 13px 0;
}
</style>
<!--[if mso]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.mj-outlook-group-fix { width:100% !important; }
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,700" rel="stylesheet" type="text/css">
<style type="text/css">
@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,700);
</style>
<!--<![endif]-->
<style type="text/css">
@media only screen and (min-width:480px) {
.mj-column-per-100 {
width: 100% !important;
max-width: 100%;
}
.mj-column-per-50 {
width: 50% !important;
max-width: 50%;
}
.mj-column-per-90 {
width: 90% !important;
max-width: 90%;
}
}
</style>
<style type="text/css">
@media only screen and (max-width:480px) {
table.mj-full-width-mobile {
width: 100% !important;
}
td.mj-full-width-mobile {
width: auto !important;
}
}
</style>
<style type="text/css">
@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,600,700);
.emailBody {
background: #F3F3F3 !important;
}
.emailBody a {
text-decoration: none !important;
color: #166DE0 !important;
}
.title div {
font-weight: 600 !important;
font-size: 28px !important;
line-height: 36px !important;
letter-spacing: -0.01em !important;
color: #3D3C40 !important;
}
.subTitle div {
font-size: 16px !important;
line-height: 24px !important;
color: rgba(61, 60, 64, 0.64) !important;
}
.button a {
background-color: #166DE0 !important;
font-weight: 600 !important;
font-size: 16px !important;
line-height: 18px !important;
color: #FFFFFF !important;
padding: 15px 24px !important;
}
.messageButton a {
background-color: #FFFFFF !important;
border: 1px solid #FFFFFF !important;
box-sizing: border-box !important;
color: #166DE0 !important;
padding: 12px 20px !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 14px !important;
}
.info div {
font-size: 14px !important;
line-height: 20px !important;
color: #3D3C40 !important;
padding: 40px 0px !important;
}
.footerTitle div {
font-weight: 600 !important;
font-size: 16px !important;
line-height: 24px !important;
color: #3D3C40 !important;
padding: 0px 0px 4px 0px !important;
}
.footerInfo div {
font-size: 14px !important;
line-height: 20px !important;
color: #3D3C40 !important;
padding: 0px 48px 0px 48px !important;
}
.footerInfo a {
color: #166DE0 !important;
}
.appDownloadButton a {
background-color: #FFFFFF !important;
border: 1px solid #166DE0 !important;
box-sizing: border-box !important;
color: #166DE0 !important;
padding: 13px 20px !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 14px !important;
}
.emailFooter div {
font-size: 12px !important;
line-height: 16px !important;
color: rgba(61, 60, 64, 0.56) !important;
padding: 8px 24px 8px 24px !important;
}
.postCard {
padding: 0px 24px 40px 24px !important;
}
.messageCard {
background: #FFFFFF !important;
border: 1px solid rgba(61, 60, 64, 0.08) !important;
box-sizing: border-box !important;
box-shadow: 0px 8px 24px rgba(0, 0, 0, 0.12) !important;
border-radius: 4px !important;
padding: 32px !important;
}
.messageAvatar img {
width: 32px !important;
height: 32px !important;
padding: 0px !important;
border-radius: 32px !important;
}
.messageAvatarCol {
width: 32px !important;
}
.postNameAndTime {
padding: 0px 0px 4px 0px !important;
display: flex;
}
.senderName {
font-family: Open Sans, sans-serif;
text-align: left !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 20px !important;
color: #3D3C40 !important;
}
.time {
font-family: Open Sans, sans-serif;
font-size: 12px;
line-height: 16px;
color: rgba(61, 60, 64, 0.56);
padding: 2px 6px;
align-items: center;
float: left;
}
.channelBg {
background: rgba(61, 60, 64, 0.08);
border-radius: 4px;
display: flex;
}
.channelLogo {
width: 10px;
height: 10px;
padding: 5px 4px 5px 6px;
float: left;
}
.channelName {
font-family: Open Sans, sans-serif;
font-weight: 600;
font-size: 10px;
line-height: 16px;
letter-spacing: 0.01em;
text-transform: uppercase;
color: rgba(61, 60, 64, 0.64);
padding: 2px 6px 2px 0px;
}
.senderMessage div {
text-align: left !important;
font-size: 14px !important;
line-height: 20px !important;
color: #3D3C40 !important;
padding: 0px !important;
}
.senderInfoCol {
width: 394px !important;
padding: 0px 0px 0px 12px !important;
}
@media all and (min-width: 541px) {
.emailBody {
padding: 32px !important;
}
}
@media all and (max-width: 540px) and (min-width: 401px) {
.emailBody {
padding: 16px !important;
}
.messageCard {
padding: 16px !important;
}
.senderInfoCol {
width: 80% !important;
padding: 0px 0px 0px 12px !important;
}
}
@media all and (max-width: 400px) {
.emailBody {
padding: 0px !important;
}
.footerInfo div {
padding: 0px !important;
}
.messageCard {
padding: 16px !important;
}
.postCard {
padding: 0px 0px 40px 0px !important;
}
.senderInfoCol {
width: 80% !important;
padding: 0px 0px 0px 12px !important;
}
}
</style>
</head>
<body style="word-spacing:normal;">
<div class="emailBody" style="">
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;border-radius:8px;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;border-radius:8px;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:24px;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:552px;" width="552" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:552px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0px 0px 40px 0px;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" 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>
<tr>
<td align="center" style="font-size:0px;padding:0px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
<tbody>
<tr>
<td style="width:132px;">
<img alt="" height="21" src="{{.Props.SiteURL}}/static/images/logo_email_blue.png" style="border:0;display:block;outline:none;text-decoration:none;height:21.76px;width:100%;font-size:13px;" width="132" />
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:552px;" width="552" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:552px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0px 24px 40px 24px;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:504px;" ><![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>
<tr>
<td align="center" class="title" style="font-size:0px;padding:0px;word-break:break-word;">
<div style="font-family:Open Sans, sans-serif;font-size:13px;line-height:1;text-align:center;color:#000000;">{{.Props.Title}}</div>
</td>
</tr>
<tr>
<td align="center" class="subTitle" style="font-size:0px;padding:16px 24px 16px 24px;word-break:break-word;">
<div style="font-family:Open Sans, sans-serif;font-size:13px;line-height:1;text-align:center;color:#000000;">{{.Props.SubTitle}}</div>
</td>
</tr>
<tr>
<td align="center" vertical-align="middle" class="button" style="font-size:0px;padding:0px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
<tr>
<td align="center" bgcolor="#FFFFFF" role="presentation" style="border:none;border-radius:4px;cursor:auto;mso-padding-alt:10px 25px;background:#FFFFFF;" valign="middle">
<a href="{{.Props.ButtonURL}}" style="display:inline-block;background:#FFFFFF;color:#ffffff;font-family:Open Sans, sans-serif;font-size:13px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:4px;" target="_blank">
{{.Props.Button}}
</a>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table></td></tr><![endif]-->
{{range .Props.Posts}}
<div class="postCard">
<!--[if mso | IE]><tr><td class="messageCard-outlook" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="messageCard-outlook" style="width:552px;" width="552" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div class="messageCard" style="margin:0px auto;max-width:552px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="width:552px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0;line-height:0;text-align:left;display:inline-block;width:100%;direction:ltr;">
<!--[if mso | IE]><table border="0" cellpadding="0" cellspacing="0" role="presentation" ><tr><td style="vertical-align:top;width:276px;" ><![endif]-->
<div class="mj-column-per-50 mj-outlook-group-fix messageAvatarCol" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:50%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="center" class="messageAvatar" style="font-size:0px;padding:0px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
<tbody>
<tr>
<td style="width:276px;">
<img alt="" height="auto" src="cid:{{.SenderPhoto}}" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="276" />
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td><td style="vertical-align:top;width:496px;" ><![endif]-->
<div class="mj-column-per-90 mj-outlook-group-fix senderInfoCol" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:90%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td>
<div class="postNameAndTime">
<div class="senderName">{{.SenderName}}</div>
{{if .Time}}
<div class="time">{{.Time}}</div>
{{end}}
{{if .ChannelName}}
<div class="channelBg">
<div class="channelLogo"><img src="{{$.Props.SiteURL}}/static/images/channel_icon.png" width=10px height=10px></img></div>
<div class="channelName">{{.ChannelName}}</div>
</div>
{{end}}
</div>
</td>
</tr>
<tr>
<td align="center" class="senderMessage" style="font-size:0px;padding:0px;word-break:break-word;">
<div style="font-family:Open Sans, sans-serif;font-size:13px;line-height:1;text-align:center;color:#000000;">{{.Message}}</div>
</td>
</tr>
{{if .MessageURL}}
<tr>
<td align="center" vertical-align="middle" class="messageButton" style="font-size:0px;padding:16px 0px 0px 0px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
<tr>
<td align="center" bgcolor="#FFFFFF" role="presentation" style="border:none;border-radius:4px;cursor:auto;mso-padding-alt:10px 25px;background:#FFFFFF;" valign="middle">
<a href="{{.MessageURL}}" style="display:inline-block;background:#FFFFFF;color:#ffffff;font-family:Open Sans, sans-serif;font-size:13px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:4px;" target="_blank">
{{$.Props.MessageButton}}
</a>
</td>
</tr>
</table>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table></td></tr><![endif]-->
</div>{{end}}
<!--[if mso | IE]><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:552px;" width="552" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:552px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:16px 0px 40px 0px;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" 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>
<tr>
<td align="center" class="footerTitle" style="font-size:0px;padding:0px;word-break:break-word;">
<div style="font-family:Open Sans, sans-serif;font-size:13px;line-height:1;text-align:center;color:#000000;">{{.Props.NotificationFooterTitle}}</div>
</td>
</tr>
<tr>
<td align="center" class="footerInfo" style="font-size:0px;padding:0px;word-break:break-word;">
<div style="font-family:Open Sans, sans-serif;font-size:13px;line-height:1;text-align:center;color:#000000;"><a href="{{.Props.SiteURL}}">{{.Props.NotificationFooterInfoLogin}}</a>{{.Props.NotificationFooterInfo}}</div>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:552px;" width="552" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:552px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" 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>
<tr>
<td align="center" class="emailFooter" style="font-size:0px;padding:0px;word-break:break-word;">
<div style="font-family:Open Sans, sans-serif;font-size:13px;line-height:1;text-align:center;color:#000000;">{{.Props.Organization}}
{{.Props.FooterV2}}
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</div>
</body>
</html>
{{end}}

View file

@ -0,0 +1,25 @@
<mjml>
<mj-head>
<mj-include path="./partials/style.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-raw></div>{{end}}</mj-raw>
<mj-section padding="16px 0px 40px 0px">
<mj-column>
<mj-text css-class="footerTitle" padding="0px">
{{.Props.NotificationFooterTitle}}
</mj-text>
<mj-text css-class="footerInfo" padding="0px">
<a href="{{.Props.SiteURL}}">{{.Props.NotificationFooterInfoLogin}}</a>{{.Props.NotificationFooterInfo}}
</mj-text>
</mj-column>
</mj-section>
<mj-include path="./partials/email_footer.mjml" />
</mj-wrapper>
</mj-body>
</mjml>

View file

@ -1,15 +1,33 @@
<mj-section css-class="messageCard" padding="0px">
<mj-group>
<mj-column css-class="messageAvatarCol">
<mj-image css-class="messageAvatar" src="cid:user-avatar.png" padding="0px" />
<mj-image css-class="messageAvatar" src="cid:{{.SenderPhoto}}" padding="0px" />
</mj-column>
<mj-column css-class="senderInfoCol" width=90% >
<mj-text css-class="senderName" padding="0px">
{{.Props.SenderName}}
</mj-text>
<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">
<div class="channelLogo"><img src="{{$.Props.SiteURL}}/static/images/channel_icon.png" width=10px height=10px></img></div>
<div class="channelName">{{.ChannelName}}</div>
</div>
{{end}}
</div>
</td>
</tr>
</mj-raw>
<mj-text css-class="senderMessage" padding="0px">
{{.Props.Message}}
{{.Message}}
</mj-text>
<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>
</mj-section>

View file

@ -11,168 +11,217 @@
@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,600,700);
.emailBody {
background:#F3F3F3 !important;
background: #F3F3F3 !important;
}
.emailBody a{
text-decoration:none !important;
color:#166DE0 !important;
text-decoration: none !important;
color: #166DE0 !important;
}
.title div {
font-weight:600 !important;
font-size:28px !important;
line-height:36px !important;
letter-spacing:-0.02em !important;
color:#3D3C40 !important;
font-weight: 600 !important;
font-size: 28px !important;
line-height: 36px !important;
letter-spacing: -0.01em !important;
color: #3D3C40 !important;
}
.subTitle div {
font-size:16px !important;
line-height:24px !important;
color:rgba(61, 60, 64, 0.64) !important;
font-size: 16px !important;
line-height: 24px !important;
color: rgba(61, 60, 64, 0.64) !important;
}
.button a {
background-color:#166DE0 !important;
font-weight:600 !important;
font-size:16px !important;
line-height:18px !important;
color:#FFFFFF !important;
padding:15px 24px !important;
background-color: #166DE0 !important;
font-weight: 600 !important;
font-size: 16px !important;
line-height: 18px !important;
color: #FFFFFF !important;
padding: 15px 24px !important;
}
.messageButton a{
background-color: #FFFFFF !important;
border: 1px solid #FFFFFF !important;
box-sizing: border-box !important;
color: #166DE0 !important;
padding: 12px 20px !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 14px !important;
}
.info div {
font-size:14px !important;
line-height:20px !important;
color:#3D3C40 !important;
padding:40px 0px !important;
font-size: 14px !important;
line-height: 20px !important;
color: #3D3C40 !important;
padding: 40px 0px !important;
}
.footerTitle div {
font-weight:600 !important;
font-size:16px !important;
line-height:24px !important;
color:#3D3C40 !important;
padding:0px 0px 4px 0px !important;
font-weight: 600 !important;
font-size: 16px !important;
line-height: 24px !important;
color: #3D3C40 !important;
padding: 0px 0px 4px 0px !important;
}
.footerInfo div {
font-size:14px !important;
line-height:20px !important;
color:#3D3C40 !important;
padding:0px 48px 0px 48px !important;
font-size: 14px !important;
line-height: 20px !important;
color: #3D3C40 !important;
padding: 0px 48px 0px 48px !important;
}
.footerInfo a {
color:#166DE0 !important;
color: #166DE0 !important;
}
.appDownloadButton a{
background-color:#FFFFFF !important;
border:1px solid #166DE0 !important;
background-color: #FFFFFF !important;
border: 1px solid #166DE0 !important;
box-sizing: border-box !important;
color:#166DE0 !important;
padding:13px 20px !important;
font-weight:600 !important;
font-size:14px !important;
line-height:14px !important;
color: #166DE0 !important;
padding: 13px 20px !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 14px !important;
}
.emailFooter div {
font-size:12px !important;
line-height:16px !important;
color:rgba(61, 60, 64, 0.56) !important;
padding:8px 24px 8px 24px !important;
font-size: 12px !important;
line-height: 16px !important;
color: rgba(61, 60, 64, 0.56) !important;
padding: 8px 24px 8px 24px !important;
}
.postCard {
padding:0px 24px 40px 24px !important;
padding: 0px 24px 40px 24px !important;
}
.messageCard {
background:#FFFFFF !important;
border:1px solid rgba(61, 60, 64, 0.08) !important;
box-sizing:border-box !important;
box-shadow:0px 8px 24px rgba(0, 0, 0, 0.12) !important;
border-radius:4px !important;
padding:32px !important;
background: #FFFFFF !important;
border: 1px solid rgba(61, 60, 64, 0.08) !important;
box-sizing: border-box !important;
box-shadow: 0px 8px 24px rgba(0, 0, 0, 0.12) !important;
border-radius: 4px !important;
padding: 32px !important;
}
.messageAvatar img {
width:32px !important;
height:32px !important;
padding:0px !important;
border-radius:32px !important;
width: 32px !important;
height: 32px !important;
padding: 0px !important;
border-radius: 32px !important;
}
.messageAvatarCol {
width:32px !important;
width: 32px !important;
}
.senderName div {
text-align:left !important;
font-weight:600 !important;
font-size:14px !important;
line-height:20px !important;
color:#3D3C40 !important;
padding:0px 0px 4px 0px !important;
.postNameAndTime {
padding: 0px 0px 4px 0px !important;
display: flex;
}
.senderName {
font-family: Open Sans, sans-serif;
text-align: left !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 20px !important;
color: #3D3C40 !important;
}
.time {
font-family: Open Sans, sans-serif;
font-size: 12px;
line-height: 16px;
color: rgba(61, 60, 64, 0.56);
padding: 2px 6px;
align-items: center;
float: left;
}
.channelBg {
background: rgba(61, 60, 64, 0.08);
border-radius: 4px;
display: flex;
}
.channelLogo {
width: 10px;
height: 10px;
padding: 5px 4px 5px 6px;
float: left;
}
.channelName {
font-family: Open Sans, sans-serif;
font-weight: 600;
font-size: 10px;
line-height: 16px;
letter-spacing: 0.01em;
text-transform: uppercase;
color: rgba(61, 60, 64, 0.64);
padding: 2px 6px 2px 0px;
}
.senderMessage div {
text-align:left !important;
font-size:14px !important;
line-height:20px !important;
color:#3D3C40 !important;
padding:0px !important;
text-align: left !important;
font-size: 14px !important;
line-height: 20px !important;
color: #3D3C40 !important;
padding: 0px !important;
}
.senderInfoCol {
width:394px !important;
padding:0px 0px 0px 12px !important;
width: 394px !important;
padding: 0px 0px 0px 12px !important;
}
@media all and (min-width: 541px) {
.emailBody {
padding:32px !important;
padding: 32px !important;
}
}
@media all and (max-width: 540px) and (min-width: 401px) {
.emailBody {
padding:16px !important;
padding: 16px !important;
}
.messageCard {
padding:16px !important;
padding: 16px !important;
}
.senderInfoCol {
width:80% !important;
padding:0px 0px 0px 12px !important;
width: 80% !important;
padding: 0px 0px 0px 12px !important;
}
}
@media all and (max-width: 400px) {
.emailBody {
padding:0px !important;
padding: 0px !important;
}
.footerInfo div {
padding:0px !important;
padding: 0px !important;
}
.messageCard {
padding:16px !important;
padding: 16px !important;
}
.postCard {
padding:0px 0px 40px 0px !important;
padding: 0px 0px 40px 0px !important;
}
.senderInfoCol {
width:80% !important;
padding:0px 0px 0px 12px !important;
width: 80% !important;
padding: 0px 0px 0px 12px !important;
}
}

View file

@ -1,43 +0,0 @@
{{define "post_batched_body"}}
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="margin-top: 20px; line-height: 1.7; color: #555;">
<tr>
<td>
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 660px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF;">
<tr>
<td style="border: 1px solid #ddd;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
<td style="padding: 20px 20px 10px; text-align:left;">
<img src="{{.Props.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
<tr>
<td>
<table border="0" cellpadding="0" cellspacing="0" style="padding: 20px 50px 0; text-align: center; width: 100%;">
<tr>
<td style="border-bottom: 1px solid #ddd; margin: 10px 0 20px;">
<p style="font-weight: normal; text-align: left;">
{{.Props.BodyText}}
</p>
{{.Props.Posts}}
</td>
</tr>
<tr>
{{template "email_info" . }}
</tr>
</table>
</td>
</tr>
<tr>
{{template "email_footer" . }}
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
{{end}}

View file

@ -1,38 +0,0 @@
{{define "post_batched_post_full"}}
<style type="text/css">
@media screen and (max-width: 480px){
a[class="post_btn"] {
float: none !important;
}
}
</style>
<table style="border-top: 1px solid #ddd; padding: 20px 0; width: 100%">
<tr>
<td style="text-align: left">
<span style="font-size: 16px; font-weight: bold; color: #555; margin: 0 0 5px; display: inline-block;" >
{{.Props.ChannelName}}
</span>
<br/>
<div style="margin: 5px 0 0;">
<span style="font-weight: bold; white-space: nowrap;">
@{{.Props.SenderName}}
</span>
<span style="color: #AAA; font-size: 12px; margin-left: 2px;">
{{.Props.Date}}
</span>
</div>
</td>
</tr>
<tr>
<td colspan=2>
<pre style="text-align:left; font-family: 'Lato', sans-serif; margin: 0px; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word; line-height: 20px;">{{.Props.PostMessage}}</pre>
<a class="post_btn" href="{{.Props.PostLink}}" style="font-size: 13px; background: #2389D7; display: inline-block; border-radius: 2px; color: #fff; padding: 6px 0; width: 120px; text-decoration: none; float:left; text-align: center; margin: 15px 0 5px;">
{{.Props.Button}}
</a>
</td>
</tr>
</table>
{{end}}

View file

@ -1,37 +0,0 @@
{{define "post_batched_post_generic"}}
<style type="text/css">
@media screen and (max-width: 480px){
a[class="post_btn"] {
float: none !important;
}
}
</style>
<table style="border-top: 1px solid #ddd; padding: 20px 0; width: 100%">
<tr>
<td style="text-align: left">
<span style="font-size: 16px; font-weight: bold; color: #555; margin: 0 0 5px; display: inline-block;" >
{{.Props.ChannelName}}
</span>
<span style="font-weight: bold; white-space: nowrap; color: #555;">
@{{.Props.SenderName}}
</span>
<br/>
<div style="margin: 5px 0 0;">
<span style="color: #AAA; font-size: 12px; margin-left: 2px;">
{{.Props.Date}}
</span>
</div>
</td>
</tr>
<tr>
<td colspan=2>
<a class="post_btn" href="{{.Props.PostLink}}" style="font-size: 13px; background: #2389D7; display: inline-block; border-radius: 2px; color: #fff; padding: 6px 0; width: 120px; text-decoration: none; float:left; text-align: center; margin: 15px 0 5px;">
{{.Props.Button}}
</a>
</td>
</tr>
</table>
{{end}}

View file

@ -1,45 +0,0 @@
{{define "post_body_full"}}
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="margin-top: 20px; line-height: 1.7; color: #555;">
<tr>
<td>
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 660px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF;">
<tr>
<td style="border: 1px solid #ddd;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
<td style="padding: 20px 20px 10px; text-align:left;">
<img src="{{.Props.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
<tr>
<td>
<table border="0" cellpadding="0" cellspacing="0" style="padding: 20px 50px 0; text-align: center; margin: 0 auto">
<tr>
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
<h2 style="font-weight: normal; margin-top: 10px;">{{.Props.BodyText}}</h2>
<p>{{.Props.Info1}}<br>{{.Props.Info2}}<br><pre style="text-align:left;font-family: 'Lato', sans-serif; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;">{{.Props.PostMessage}}</pre></p>
<p style="margin: 20px 0 15px">
<a href="{{.Props.TeamLink}}" style="background: #2389D7; display: inline-block; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 170px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.Button}}</a>
</p>
</td>
</tr>
<tr>
{{template "email_info" . }}
</tr>
</table>
</td>
</tr>
<tr>
{{template "email_footer" . }}
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
{{end}}

View file

@ -1,44 +0,0 @@
{{define "post_body_generic"}}
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="margin-top: 20px; line-height: 1.7; color: #555;">
<tr>
<td>
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 660px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF;">
<tr>
<td style="border: 1px solid #ddd;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
<td style="padding: 20px 20px 10px; text-align:left;">
<img src="{{.Props.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
<tr>
<td>
<table border="0" cellpadding="0" cellspacing="0" style="padding: 20px 50px 0; text-align: center; margin: 0 auto">
<tr>
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
<h2 style="font-weight: normal; margin-top: 10px;">{{.Props.BodyText}}</h2>
<p>{{.Props.Info}}</p>
<p style="margin: 20px 0 15px">
<a href="{{.Props.TeamLink}}" style="background: #2389D7; display: inline-block; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 170px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.Button}}</a>
</p>
</td>
</tr>
<tr>
{{template "email_info" . }}
</tr>
</table>
</td>
</tr>
<tr>
{{template "email_footer" . }}
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
{{end}}

View file

@ -105,7 +105,7 @@
font-weight: 600 !important;
font-size: 28px !important;
line-height: 36px !important;
letter-spacing: -0.02em !important;
letter-spacing: -0.01em !important;
color: #3D3C40 !important;
}
@ -124,6 +124,17 @@
padding: 15px 24px !important;
}
.messageButton a {
background-color: #FFFFFF !important;
border: 1px solid #FFFFFF !important;
box-sizing: border-box !important;
color: #166DE0 !important;
padding: 12px 20px !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 14px !important;
}
.info div {
font-size: 14px !important;
line-height: 20px !important;
@ -192,13 +203,52 @@
width: 32px !important;
}
.senderName div {
.postNameAndTime {
padding: 0px 0px 4px 0px !important;
display: flex;
}
.senderName {
font-family: Open Sans, sans-serif;
text-align: left !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 20px !important;
color: #3D3C40 !important;
padding: 0px 0px 4px 0px !important;
}
.time {
font-family: Open Sans, sans-serif;
font-size: 12px;
line-height: 16px;
color: rgba(61, 60, 64, 0.56);
padding: 2px 6px;
align-items: center;
float: left;
}
.channelBg {
background: rgba(61, 60, 64, 0.08);
border-radius: 4px;
display: flex;
}
.channelLogo {
width: 10px;
height: 10px;
padding: 5px 4px 5px 6px;
float: left;
}
.channelName {
font-family: Open Sans, sans-serif;
font-weight: 600;
font-size: 10px;
line-height: 16px;
letter-spacing: 0.01em;
text-transform: uppercase;
color: rgba(61, 60, 64, 0.64);
padding: 2px 6px 2px 0px;
}
.senderMessage div {

View file

@ -105,7 +105,7 @@
font-weight: 600 !important;
font-size: 28px !important;
line-height: 36px !important;
letter-spacing: -0.02em !important;
letter-spacing: -0.01em !important;
color: #3D3C40 !important;
}
@ -124,6 +124,17 @@
padding: 15px 24px !important;
}
.messageButton a {
background-color: #FFFFFF !important;
border: 1px solid #FFFFFF !important;
box-sizing: border-box !important;
color: #166DE0 !important;
padding: 12px 20px !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 14px !important;
}
.info div {
font-size: 14px !important;
line-height: 20px !important;
@ -192,13 +203,52 @@
width: 32px !important;
}
.senderName div {
.postNameAndTime {
padding: 0px 0px 4px 0px !important;
display: flex;
}
.senderName {
font-family: Open Sans, sans-serif;
text-align: left !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 20px !important;
color: #3D3C40 !important;
padding: 0px 0px 4px 0px !important;
}
.time {
font-family: Open Sans, sans-serif;
font-size: 12px;
line-height: 16px;
color: rgba(61, 60, 64, 0.56);
padding: 2px 6px;
align-items: center;
float: left;
}
.channelBg {
background: rgba(61, 60, 64, 0.08);
border-radius: 4px;
display: flex;
}
.channelLogo {
width: 10px;
height: 10px;
padding: 5px 4px 5px 6px;
float: left;
}
.channelName {
font-family: Open Sans, sans-serif;
font-weight: 600;
font-size: 10px;
line-height: 16px;
letter-spacing: 0.01em;
text-transform: uppercase;
color: rgba(61, 60, 64, 0.64);
padding: 2px 6px 2px 0px;
}
.senderMessage div {

View file

@ -105,7 +105,7 @@
font-weight: 600 !important;
font-size: 28px !important;
line-height: 36px !important;
letter-spacing: -0.02em !important;
letter-spacing: -0.01em !important;
color: #3D3C40 !important;
}
@ -124,6 +124,17 @@
padding: 15px 24px !important;
}
.messageButton a {
background-color: #FFFFFF !important;
border: 1px solid #FFFFFF !important;
box-sizing: border-box !important;
color: #166DE0 !important;
padding: 12px 20px !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 14px !important;
}
.info div {
font-size: 14px !important;
line-height: 20px !important;
@ -192,13 +203,52 @@
width: 32px !important;
}
.senderName div {
.postNameAndTime {
padding: 0px 0px 4px 0px !important;
display: flex;
}
.senderName {
font-family: Open Sans, sans-serif;
text-align: left !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 20px !important;
color: #3D3C40 !important;
padding: 0px 0px 4px 0px !important;
}
.time {
font-family: Open Sans, sans-serif;
font-size: 12px;
line-height: 16px;
color: rgba(61, 60, 64, 0.56);
padding: 2px 6px;
align-items: center;
float: left;
}
.channelBg {
background: rgba(61, 60, 64, 0.08);
border-radius: 4px;
display: flex;
}
.channelLogo {
width: 10px;
height: 10px;
padding: 5px 4px 5px 6px;
float: left;
}
.channelName {
font-family: Open Sans, sans-serif;
font-weight: 600;
font-size: 10px;
line-height: 16px;
letter-spacing: 0.01em;
text-transform: uppercase;
color: rgba(61, 60, 64, 0.64);
padding: 2px 6px 2px 0px;
}
.senderMessage div {