summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.editorconfig4
-rw-r--r--api/post.go6
-rw-r--r--api/team.go10
-rw-r--r--api/user.go51
-rw-r--r--api/user_test.go45
-rw-r--r--i18n/en.json4
-rw-r--r--templates/email_change_subject.html1
-rw-r--r--templates/email_change_verify_subject.html1
-rw-r--r--templates/invite_subject.html1
-rw-r--r--templates/password_change_subject.html1
-rw-r--r--templates/post_subject.html1
-rw-r--r--templates/reset_subject.html1
-rw-r--r--templates/signin_change_subject.html1
-rw-r--r--templates/signup_team_subject.html1
-rw-r--r--templates/username_change_subject.html1
-rw-r--r--templates/verify_subject.html1
-rw-r--r--templates/welcome_subject.html1
-rw-r--r--utils/mail.go7
-rw-r--r--webapp/actions/websocket_actions.jsx2
-rw-r--r--webapp/components/admin_console/logs.jsx11
-rw-r--r--webapp/components/channel_header.jsx41
-rw-r--r--webapp/components/signup/components/signup_email.jsx6
-rw-r--r--webapp/components/signup/components/signup_ldap.jsx6
-rw-r--r--webapp/components/user_profile.jsx43
-rw-r--r--webapp/components/user_settings/user_settings_security.jsx23
-rw-r--r--webapp/i18n/en.json9
-rw-r--r--webapp/sass/components/_tooltip.scss6
-rw-r--r--webapp/sass/components/_webrtc.scss11
-rw-r--r--webapp/sass/responsive/_mobile.scss1
-rw-r--r--webapp/sass/utils/_mixins.scss61
-rw-r--r--webapp/stores/browser_store.jsx6
-rw-r--r--webapp/stores/post_store.jsx6
-rw-r--r--webapp/utils/channel_intro_messages.jsx29
-rw-r--r--webapp/utils/constants.jsx1
34 files changed, 229 insertions, 171 deletions
diff --git a/.editorconfig b/.editorconfig
index b8bc90393..caa8897a3 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -4,7 +4,7 @@ root = true
[*]
end_of_line = lf
-insert_final_newline = false
+insert_final_newline = true
charset = utf-8
[*.go]
@@ -28,4 +28,4 @@ indent_style = tab
[*.scss]
indent_style = space
-indent_size = 4 \ No newline at end of file
+indent_size = 4
diff --git a/api/post.go b/api/post.go
index e5e18cb52..f17df8831 100644
--- a/api/post.go
+++ b/api/post.go
@@ -842,9 +842,7 @@ func sendNotificationEmail(c *Context, post *model.Post, user *model.User, chann
"ChannelName": channelName, "Month": month, "Day": day, "Year": year}
}
- subjectPage := utils.NewHTMLTemplate("post_subject", user.Locale)
- subjectPage.Props["Subject"] = userLocale(mailTemplate, mailParameters)
- subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName
+ subject := fmt.Sprintf("[%v] %v", utils.Cfg.TeamSettings.SiteName, userLocale(mailTemplate, mailParameters))
bodyPage := utils.NewHTMLTemplate("post_body", user.Locale)
bodyPage.Props["SiteURL"] = c.GetSiteURL()
@@ -857,7 +855,7 @@ func sendNotificationEmail(c *Context, post *model.Post, user *model.User, chann
"Hour": fmt.Sprintf("%02d", tm.Hour()), "Minute": fmt.Sprintf("%02d", tm.Minute()),
"TimeZone": zone, "Month": month, "Day": day}))
- if err := utils.SendMail(user.Email, html.UnescapeString(subjectPage.Render()), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(user.Email, html.UnescapeString(subject), bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.post.send_notifications_and_forget.send.error"), user.Email, err)
}
}
diff --git a/api/team.go b/api/team.go
index b1a1ae3cd..e7db79ae2 100644
--- a/api/team.go
+++ b/api/team.go
@@ -69,8 +69,7 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- subjectPage := utils.NewHTMLTemplate("signup_team_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.signup_team_subject",
+ subject := c.T("api.templates.signup_team_subject",
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
bodyPage := utils.NewHTMLTemplate("signup_team_body", c.Locale)
@@ -89,7 +88,7 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_team_complete/?d=%s&h=%s", c.GetSiteURL(), url.QueryEscape(data), url.QueryEscape(hash))
- if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil {
c.Err = err
return
}
@@ -717,8 +716,7 @@ func InviteMembers(c *Context, team *model.Team, user *model.User, invites []str
senderRole := c.T("api.team.invite_members.member")
- subjectPage := utils.NewHTMLTemplate("invite_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.invite_subject",
+ subject := c.T("api.templates.invite_subject",
map[string]interface{}{"SenderName": sender, "TeamDisplayName": team.DisplayName, "SiteName": utils.ClientCfg["SiteName"]})
bodyPage := utils.NewHTMLTemplate("invite_body", c.Locale)
@@ -744,7 +742,7 @@ func InviteMembers(c *Context, team *model.Team, user *model.User, invites []str
l4g.Info(utils.T("api.team.invite_members.sending.info"), invite, bodyPage.Props["Link"])
}
- if err := utils.SendMail(invite, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(invite, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.team.invite_members.send.error"), err)
}
}
diff --git a/api/user.go b/api/user.go
index 9c24609ce..2238aca8b 100644
--- a/api/user.go
+++ b/api/user.go
@@ -192,6 +192,7 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
}
+// Check that a user's email domain matches a list of space-delimited domains as a string.
func CheckUserDomain(user *model.User, domains string) bool {
if len(domains) == 0 {
return true
@@ -358,8 +359,7 @@ func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service
func sendWelcomeEmail(c *Context, userId string, email string, siteURL string, verified bool) {
rawUrl, _ := url.Parse(siteURL)
- subjectPage := utils.NewHTMLTemplate("welcome_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.welcome_subject", map[string]interface{}{"ServerURL": rawUrl.Host})
+ subject := c.T("api.templates.welcome_subject", map[string]interface{}{"ServerURL": rawUrl.Host})
bodyPage := utils.NewHTMLTemplate("welcome_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
@@ -380,7 +380,7 @@ func sendWelcomeEmail(c *Context, userId string, email string, siteURL string, v
bodyPage.Props["VerifyUrl"] = link
}
- if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_welcome_email_and_forget.failed.error"), err)
}
}
@@ -427,8 +427,7 @@ func SendVerifyEmail(c *Context, userId, userEmail, siteURL string) {
url, _ := url.Parse(siteURL)
- subjectPage := utils.NewHTMLTemplate("verify_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.verify_subject",
+ subject := c.T("api.templates.verify_subject",
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
bodyPage := utils.NewHTMLTemplate("verify_body", c.Locale)
@@ -438,7 +437,7 @@ func SendVerifyEmail(c *Context, userId, userEmail, siteURL string) {
bodyPage.Props["VerifyUrl"] = link
bodyPage.Props["Button"] = c.T("api.templates.verify_body.button")
- if err := utils.SendMail(userEmail, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(userEmail, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_verify_email_and_forget.failed.error"), err)
}
}
@@ -1734,8 +1733,7 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) {
link := fmt.Sprintf("%s/reset_password_complete?code=%s", c.GetSiteURL(), url.QueryEscape(recovery.Code))
- subjectPage := utils.NewHTMLTemplate("reset_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.reset_subject")
+ subject := c.T("api.templates.reset_subject")
bodyPage := utils.NewHTMLTemplate("reset_body", c.Locale)
bodyPage.Props["SiteURL"] = c.GetSiteURL()
@@ -1744,7 +1742,7 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) {
bodyPage.Props["ResetUrl"] = link
bodyPage.Props["Button"] = c.T("api.templates.reset_body.button")
- if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil {
c.Err = model.NewLocAppError("sendPasswordReset", "api.user.send_password_reset.send.app_error", nil, "err="+err.Message)
return
}
@@ -1830,8 +1828,7 @@ func ResetPassword(c *Context, userId, newPassword string) *model.AppError {
}
func sendPasswordChangeEmail(c *Context, email, siteURL, method string) {
- subjectPage := utils.NewHTMLTemplate("password_change_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.password_change_subject",
+ subject := c.T("api.templates.password_change_subject",
map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "SiteName": utils.Cfg.TeamSettings.SiteName})
bodyPage := utils.NewHTMLTemplate("password_change_body", c.Locale)
@@ -1840,16 +1837,14 @@ func sendPasswordChangeEmail(c *Context, email, siteURL, method string) {
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.password_change_body.info",
map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "TeamURL": siteURL, "Method": method}))
- if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_password_change_email_and_forget.error"), err)
}
}
func sendEmailChangeEmail(c *Context, oldEmail, newEmail, siteURL string) {
- subjectPage := utils.NewHTMLTemplate("email_change_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.email_change_subject",
- map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName})
- subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName
+ subject := fmt.Sprintf("[%v] %v", utils.Cfg.TeamSettings.SiteName, c.T("api.templates.email_change_subject",
+ map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName}))
bodyPage := utils.NewHTMLTemplate("email_change_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
@@ -1857,7 +1852,7 @@ func sendEmailChangeEmail(c *Context, oldEmail, newEmail, siteURL string) {
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.email_change_body.info",
map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "NewEmail": newEmail}))
- if err := utils.SendMail(oldEmail, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(oldEmail, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_email_change_email_and_forget.error"), err)
}
}
@@ -1865,10 +1860,8 @@ func sendEmailChangeEmail(c *Context, oldEmail, newEmail, siteURL string) {
func SendEmailChangeVerifyEmail(c *Context, userId, newUserEmail, siteURL string) {
link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&email=%s", siteURL, userId, model.HashPassword(userId+utils.Cfg.EmailSettings.InviteSalt), url.QueryEscape(newUserEmail))
- subjectPage := utils.NewHTMLTemplate("email_change_verify_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.email_change_verify_subject",
- map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName})
- subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName
+ subject := fmt.Sprintf("[%v] %v", utils.Cfg.TeamSettings.SiteName, c.T("api.templates.email_change_verify_subject",
+ map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName}))
bodyPage := utils.NewHTMLTemplate("email_change_verify_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
@@ -1878,16 +1871,14 @@ func SendEmailChangeVerifyEmail(c *Context, userId, newUserEmail, siteURL string
bodyPage.Props["VerifyUrl"] = link
bodyPage.Props["VerifyButton"] = c.T("api.templates.email_change_verify_body.button")
- if err := utils.SendMail(newUserEmail, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(newUserEmail, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_email_change_verify_email_and_forget.error"), err)
}
}
func sendEmailChangeUsername(c *Context, oldUsername, newUsername, email, siteURL string) {
- subjectPage := utils.NewHTMLTemplate("username_change_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.username_change_subject",
- map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName})
- subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName
+ subject := fmt.Sprintf("[%v] %v", utils.Cfg.TeamSettings.SiteName, c.T("api.templates.username_change_subject",
+ map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName}))
bodyPage := utils.NewHTMLTemplate("email_change_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
@@ -1895,7 +1886,7 @@ func sendEmailChangeUsername(c *Context, oldUsername, newUsername, email, siteUR
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.username_change_body.info",
map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "NewUsername": newUsername}))
- if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_email_change_username_and_forget.error"), err)
}
@@ -1967,6 +1958,7 @@ func updateUserNotify(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
+// Check if the username is already used by another user. Return false if the username is invalid.
func IsUsernameTaken(name string) bool {
if !model.IsValidUsername(name) {
@@ -2239,8 +2231,7 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
}
func sendSignInChangeEmail(c *Context, email, siteURL, method string) {
- subjectPage := utils.NewHTMLTemplate("signin_change_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.singin_change_email.subject",
+ subject := c.T("api.templates.singin_change_email.subject",
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
bodyPage := utils.NewHTMLTemplate("signin_change_body", c.Locale)
@@ -2249,7 +2240,7 @@ func sendSignInChangeEmail(c *Context, email, siteURL, method string) {
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.singin_change_email.body.info",
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"], "Method": method}))
- if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_sign_in_change_email_and_forget.error"), err)
}
}
diff --git a/api/user_test.go b/api/user_test.go
index 1ffb2140c..f91d71177 100644
--- a/api/user_test.go
+++ b/api/user_test.go
@@ -80,6 +80,51 @@ func TestCreateUser(t *testing.T) {
}
}
+func TestCheckUserDomain(t *testing.T) {
+ th := Setup().InitBasic()
+ user := th.BasicUser
+
+ cases := []struct {
+ domains string
+ matched bool
+ }{
+ {"simulator.amazonses.com", true},
+ {"gmail.com", false},
+ {"", true},
+ {"gmail.com simulator.amazonses.com", true},
+ }
+ for _, c := range cases {
+ matched := CheckUserDomain(user, c.domains)
+ if matched != c.matched {
+ if c.matched {
+ t.Logf("'%v' should have matched '%v'", user.Email, c.domains)
+ } else {
+ t.Logf("'%v' should not have matched '%v'", user.Email, c.domains)
+ }
+ t.FailNow()
+ }
+ }
+}
+
+func TestIsUsernameTaken(t *testing.T) {
+ th := Setup().InitBasic()
+ user := th.BasicUser
+ taken := IsUsernameTaken(user.Username)
+
+ if !taken {
+ t.Logf("the username '%v' should be taken", user.Username)
+ t.FailNow()
+ }
+
+ newUsername := "randomUsername"
+ taken = IsUsernameTaken(newUsername)
+
+ if taken {
+ t.Logf("the username '%v' should not be taken", newUsername)
+ t.FailNow()
+ }
+}
+
func TestLogin(t *testing.T) {
th := Setup()
Client := th.CreateClient()
diff --git a/i18n/en.json b/i18n/en.json
index 1cb0f1dda..f8163557b 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -2676,6 +2676,10 @@
"translation": "AD/LDAP disabled or licence does not support AD/LDAP."
},
{
+ "id": "ent.ldap.create_fail",
+ "translation": "Unable to create LDAP user."
+ },
+ {
"id": "ent.ldap.do_login.bind_admin_user.app_error",
"translation": "Unable to bind to AD/LDAP server. Check BindUsername and BindPassword."
},
diff --git a/templates/email_change_subject.html b/templates/email_change_subject.html
deleted file mode 100644
index 540bc6eab..000000000
--- a/templates/email_change_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "email_change_subject"}}[{{.Props.SiteName}}] {{.Props.Subject}}{{end}}
diff --git a/templates/email_change_verify_subject.html b/templates/email_change_verify_subject.html
deleted file mode 100644
index 04da7593c..000000000
--- a/templates/email_change_verify_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "email_change_verify_subject"}}[{{.Props.SiteName}}] {{.Props.Subject}}{{end}}
diff --git a/templates/invite_subject.html b/templates/invite_subject.html
deleted file mode 100644
index 504915d50..000000000
--- a/templates/invite_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "invite_subject"}}{{.Props.Subject}}{{end}}
diff --git a/templates/password_change_subject.html b/templates/password_change_subject.html
deleted file mode 100644
index 897f1210d..000000000
--- a/templates/password_change_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "password_change_subject"}}{{.Props.Subject}}{{end}}
diff --git a/templates/post_subject.html b/templates/post_subject.html
deleted file mode 100644
index 9789d4142..000000000
--- a/templates/post_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "post_subject"}}[{{.Props.SiteName}}] {{.Props.Subject}}{{end}}
diff --git a/templates/reset_subject.html b/templates/reset_subject.html
deleted file mode 100644
index a2852d332..000000000
--- a/templates/reset_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "reset_subject"}}{{.Props.Subject}}{{end}}
diff --git a/templates/signin_change_subject.html b/templates/signin_change_subject.html
deleted file mode 100644
index 606dc4df3..000000000
--- a/templates/signin_change_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "signin_change_subject"}}{{.Props.Subject}}{{end}}
diff --git a/templates/signup_team_subject.html b/templates/signup_team_subject.html
deleted file mode 100644
index 413a5c8c1..000000000
--- a/templates/signup_team_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "signup_team_subject"}}{{.Props.Subject}}{{end}} \ No newline at end of file
diff --git a/templates/username_change_subject.html b/templates/username_change_subject.html
deleted file mode 100644
index 1c94bb7c0..000000000
--- a/templates/username_change_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "username_change_subject"}}[{{.Props.SiteName}}] {{.Props.Subject}}{{end}}
diff --git a/templates/verify_subject.html b/templates/verify_subject.html
deleted file mode 100644
index ad7fc2aaa..000000000
--- a/templates/verify_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "verify_subject"}}{{.Props.Subject}}{{end}}
diff --git a/templates/welcome_subject.html b/templates/welcome_subject.html
deleted file mode 100644
index 95189b900..000000000
--- a/templates/welcome_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "welcome_subject"}}{{.Props.Subject}}{{end}}
diff --git a/utils/mail.go b/utils/mail.go
index bb3ee7b17..6a348e52e 100644
--- a/utils/mail.go
+++ b/utils/mail.go
@@ -5,10 +5,10 @@ package utils
import (
"crypto/tls"
- "encoding/base64"
"fmt"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
+ "mime"
"net"
"net/mail"
"net/smtp"
@@ -16,10 +16,7 @@ import (
)
func encodeRFC2047Word(s string) string {
- // TODO: use `mime.BEncoding.Encode` instead when `go` >= 1.5
- // return mime.BEncoding.Encode("utf-8", s)
- dst := base64.StdEncoding.EncodeToString([]byte(s))
- return "=?utf-8?b?" + dst + "?="
+ return mime.BEncoding.Encode("utf-8", s)
}
func connectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) {
diff --git a/webapp/actions/websocket_actions.jsx b/webapp/actions/websocket_actions.jsx
index 36c6cbdc9..8632f4135 100644
--- a/webapp/actions/websocket_actions.jsx
+++ b/webapp/actions/websocket_actions.jsx
@@ -180,7 +180,7 @@ function handleNewPostEvent(msg) {
function handlePostEditEvent(msg) {
// Store post
const post = JSON.parse(msg.data.post);
- PostStore.storePost(post);
+ PostStore.storePost(post, false);
PostStore.emitChange();
// Update channel state
diff --git a/webapp/components/admin_console/logs.jsx b/webapp/components/admin_console/logs.jsx
index ad0277b7f..8dc0c1e2e 100644
--- a/webapp/components/admin_console/logs.jsx
+++ b/webapp/components/admin_console/logs.jsx
@@ -26,6 +26,12 @@ export default class Logs extends React.Component {
AsyncClient.getLogs();
}
+ componentDidUpdate() {
+ // Scroll Down to get the latest logs
+ var node = this.refs.logPanel;
+ node.scrollTop = node.scrollHeight;
+ }
+
componentWillUnmount() {
AdminStore.removeLogChangeListener(this.onLogListenerChange);
}
@@ -93,7 +99,10 @@ export default class Logs extends React.Component {
defaultMessage='Reload'
/>
</button>
- <div className='log__panel'>
+ <div
+ ref='logPanel'
+ className='log__panel'
+ >
{content}
</div>
</div>
diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx
index 1ce7b4a0e..a281e8e1b 100644
--- a/webapp/components/channel_header.jsx
+++ b/webapp/components/channel_header.jsx
@@ -300,6 +300,13 @@ export default class ChannelHeader extends React.Component {
if (isOffline || busy) {
circleClass = 'offline';
+ webrtcMessage = (
+ <FormattedMessage
+ id='channel_header.webrtc.offline'
+ defaultMessage='The user is offline'
+ />
+ );
+
if (busy) {
webrtcMessage = (
<FormattedMessage
@@ -317,6 +324,10 @@ export default class ChannelHeader extends React.Component {
);
}
+ const webrtcTooltip = (
+ <Tooltip id='webrtcTooltip'>{webrtcMessage}</Tooltip>
+ );
+
webrtc = (
<div className='webrtc__header'>
<a
@@ -324,28 +335,18 @@ export default class ChannelHeader extends React.Component {
onClick={() => this.initWebrtc(contact.id, !isOffline)}
disabled={isOffline}
>
- <svg
- id='webrtc-btn'
- className='webrtc__button'
- xmlns='http://www.w3.org/2000/svg'
+ <OverlayTrigger
+ delayShow={Constants.WEBRTC_TIME_DELAY}
+ placement='bottom'
+ overlay={webrtcTooltip}
>
- <circle
- className={circleClass}
- cx='16'
- cy='16'
- r='18'
+ <div
+ id='webrtc-btn'
+ className={'webrtc__button ' + circleClass}
>
- <title>
- {webrtcMessage}
- </title>
- </circle>
- <path
- className='off'
- transform='scale(0.4), translate(17,16)'
- d='M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zm-4 24l-8-6.4V32H12V16h16v6.4l8-6.4v16z'
- fill='white'
- />
- </svg>
+ <span dangerouslySetInnerHTML={{__html: Constants.VIDEO_ICON}}/>
+ </div>
+ </OverlayTrigger>
</a>
</div>
);
diff --git a/webapp/components/signup/components/signup_email.jsx b/webapp/components/signup/components/signup_email.jsx
index 2d4b3f277..b67179604 100644
--- a/webapp/components/signup/components/signup_email.jsx
+++ b/webapp/components/signup/components/signup_email.jsx
@@ -429,9 +429,11 @@ export default class SignupEmail extends React.Component {
<p>
<FormattedHTMLMessage
id='create_team.agreement'
- defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}."
+ defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='{TermsOfServiceLink}'>Terms of Service</a> and <a href='{PrivacyPolicyLink}'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}."
values={{
- siteName: global.window.mm_config.SiteName
+ siteName: global.window.mm_config.SiteName,
+ TermsOfServiceLink: global.window.mm_config.TermsOfServiceLink,
+ PrivacyPolicyLink: global.window.mm_config.PrivacyPolicyLink
}}
/>
</p>
diff --git a/webapp/components/signup/components/signup_ldap.jsx b/webapp/components/signup/components/signup_ldap.jsx
index 8c1b1bafb..bc8c073ad 100644
--- a/webapp/components/signup/components/signup_ldap.jsx
+++ b/webapp/components/signup/components/signup_ldap.jsx
@@ -179,9 +179,11 @@ export default class SignupLdap extends React.Component {
<p>
<FormattedHTMLMessage
id='create_team.agreement'
- defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}."
+ defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='{TermsOfServiceLink}'>Terms of Service</a> and <a href='{PrivacyPolicyLink}'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}."
values={{
- siteName: global.window.mm_config.SiteName
+ siteName: global.window.mm_config.SiteName,
+ TermsOfServiceLink: global.window.mm_config.TermsOfServiceLink,
+ PrivacyPolicyLink: global.window.mm_config.PrivacyPolicyLink
}}
/>
</p>
diff --git a/webapp/components/user_profile.jsx b/webapp/components/user_profile.jsx
index e69d917a3..21dbf9699 100644
--- a/webapp/components/user_profile.jsx
+++ b/webapp/components/user_profile.jsx
@@ -11,7 +11,7 @@ import Constants from 'utils/constants.jsx';
const UserStatuses = Constants.UserStatuses;
const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
-import {Popover, OverlayTrigger} from 'react-bootstrap';
+import {Popover, OverlayTrigger, Tooltip} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import React from 'react';
@@ -111,8 +111,19 @@ export default class UserProfile extends React.Component {
defaultMessage='New call unavailable until your existing call ends'
/>
);
+ } else {
+ webrtcMessage = (
+ <FormattedMessage
+ id='user_profile.webrtc.offline'
+ defaultMessage='The user is offline'
+ />
+ );
}
+ const webrtcTooltip = (
+ <Tooltip id='webrtcTooltip'>{webrtcMessage}</Tooltip>
+ );
+
webrtc = (
<div
className='webrtc__user-profile'
@@ -123,28 +134,18 @@ export default class UserProfile extends React.Component {
onClick={() => this.initWebrtc()}
disabled={!isOnline}
>
- <svg
- id='webrtc-btn'
- className='webrtc__button'
- xmlns='http://www.w3.org/2000/svg'
+ <OverlayTrigger
+ delayShow={Constants.WEBRTC_TIME_DELAY}
+ placement='top'
+ overlay={webrtcTooltip}
>
- <circle
- className={circleClass}
- cx='16'
- cy='16'
- r='18'
+ <div
+ id='webrtc-btn'
+ className={'webrtc__button ' + circleClass}
>
- <title>
- {webrtcMessage}
- </title>
- </circle>
- <path
- className='off'
- transform='scale(0.4), translate(17,16)'
- d='M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zm-4 24l-8-6.4V32H12V16h16v6.4l8-6.4v16z'
- fill='white'
- />
- </svg>
+ <span dangerouslySetInnerHTML={{__html: Constants.VIDEO_ICON}}/>
+ </div>
+ </OverlayTrigger>
</a>
</div>
);
diff --git a/webapp/components/user_settings/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security.jsx
index 0cee3dfca..1f049c4bd 100644
--- a/webapp/components/user_settings/user_settings_security.jsx
+++ b/webapp/components/user_settings/user_settings_security.jsx
@@ -473,6 +473,20 @@ export default class SecurityTab extends React.Component {
</div>
</div>
);
+ } else if (this.props.user.auth_service === Constants.SAML_SERVICE) {
+ inputs.push(
+ <div
+ key='oauthEmailInfo'
+ className='form-group'
+ >
+ <div className='setting-list__hint'>
+ <FormattedMessage
+ id='user.settings.security.passwordSamlCantUpdate'
+ defaultMessage='This field is handled through your login provider. If you want to change it, you need to do so through your login provider.'
+ />
+ </div>
+ </div>
+ );
}
updateSectionStatus = function resetSection(e) {
@@ -533,7 +547,7 @@ export default class SecurityTab extends React.Component {
describe = (
<FormattedMessage
id='user.settings.security.loginGitlab'
- defaultMessage='Login done through Gitlab'
+ defaultMessage='Login done through GitLab'
/>
);
} else if (this.props.user.auth_service === Constants.LDAP_SERVICE) {
@@ -543,6 +557,13 @@ export default class SecurityTab extends React.Component {
defaultMessage='Login done through AD/LDAP'
/>
);
+ } else if (this.props.user.auth_service === Constants.SAML_SERVICE) {
+ describe = (
+ <FormattedMessage
+ id='user.settings.security.loginSaml'
+ defaultMessage='Login done through SAML'
+ />
+ );
}
updateSectionStatus = function updateSection() {
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 220d4e2f9..532b44c23 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -998,6 +998,7 @@
"channel_header.viewMembers": "View Members",
"channel_header.webrtc.call": "Start Video Call",
"channel_header.webrtc.unavailable": "New call unavailable until your existing call ends",
+ "channel_header.webrtc.offline": "The user is offline",
"channel_info.about": "About",
"channel_info.close": "Close",
"channel_info.header": "Header:",
@@ -1108,7 +1109,7 @@
"create_post.shortcutsNotSupported": "Keyboard shortcuts are not supported on your device.",
"create_post.tutorialTip": "<h4>Sending Messages</h4><p>Type here to write a message and press <strong>ENTER</strong> to post it.</p><p>Click the <strong>Attachment</strong> button to upload an image or a file.</p>",
"create_post.write": "Write a message...",
- "create_team.agreement": "By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}.",
+ "create_team.agreement": "By proceeding to create your account and use {siteName}, you agree to our <a href={TermsOfServiceLink}>Terms of Service</a> and <a href={PrivacyPolicyLink}>Privacy Policy</a>. If you do not agree, you cannot use {siteName}.",
"create_team.display_name.back": "Back to previous step",
"create_team.display_name.charLength": "Name must be 2 or more characters up to a maximum of 15",
"create_team.display_name.nameHelp": "Name your team in any language. Your team name shows in menus and headings.",
@@ -1410,7 +1411,8 @@
"intro_messages.anyMember": " Any member can join and read this channel.",
"intro_messages.beginning": "Beginning of {name}",
"intro_messages.channel": "channel",
- "intro_messages.creator": "This is the start of the <strong>{name}</strong> {type}, created by <strong>{creator}</strong> on <strong>{date}</strong>",
+ "intro_messages.creator": "This is the start of the {name} {type}, created by {creator} on {date}.",
+ "intro_messages.purpose": " This {type}'s purpose is: {purpose}.",
"intro_messages.default": "<h4 class='channel-intro__title'>Beginning of {display_name}</h4><p class='channel-intro__content'><strong>Welcome to {display_name}!</strong><br/><br/>This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.</p>",
"intro_messages.group": "private group",
"intro_messages.invite": "Invite others to this {type}",
@@ -2012,6 +2014,7 @@
"user.settings.security.ldap": "AD/LDAP",
"user.settings.security.loginGitlab": "Login done through GitLab",
"user.settings.security.loginLdap": "Login done through AD/LDAP",
+ "user.settings.security.loginSaml": "Login done through SAML",
"user.settings.security.logoutActiveSessions": "View and Logout of Active Sessions",
"user.settings.security.method": "Sign-in Method",
"user.settings.security.newPassword": "New Password",
@@ -2040,6 +2043,7 @@
"user.settings.security.passwordErrorUppercaseSymbol": "Your password must contain at least {min} characters made up of at least one uppercase letter and at least one symbol (e.g. \"~!@#$%^&*()\").",
"user.settings.security.passwordGitlabCantUpdate": "Login occurs through GitLab. Password cannot be updated.",
"user.settings.security.passwordLdapCantUpdate": "Login occurs through AD/LDAP. Password cannot be updated.",
+ "user.settings.security.passwordSamlCantUpdate": "This field is handled through your login provider. If you want to change it, you need to do so through your login provider.",
"user.settings.security.passwordMatchError": "The new passwords you entered do not match.",
"user.settings.security.passwordMinLength": "Invalid minimum length, cannot show preview.",
"user.settings.security.retypePassword": "Retype New Password",
@@ -2055,6 +2059,7 @@
"user_list.notFound": "No users found",
"user_profile.webrtc.call": "Start Video Call",
"user_profile.webrtc.unavailable": "New call unavailable until your existing call ends",
+ "user_profile.webrtc.offline": "The user is offline",
"view_image.loading": "Loading ",
"view_image_popover.download": "Download",
"view_image_popover.file": "File {count} of {total}",
diff --git a/webapp/sass/components/_tooltip.scss b/webapp/sass/components/_tooltip.scss
index 5e71e3a7b..0049fe1b8 100644
--- a/webapp/sass/components/_tooltip.scss
+++ b/webapp/sass/components/_tooltip.scss
@@ -8,3 +8,9 @@
word-break: break-word;
}
}
+
+#webrtcTooltip {
+ .tooltip-inner {
+ word-break: normal;
+ }
+}
diff --git a/webapp/sass/components/_webrtc.scss b/webapp/sass/components/_webrtc.scss
index b025ab00c..c9deada26 100644
--- a/webapp/sass/components/_webrtc.scss
+++ b/webapp/sass/components/_webrtc.scss
@@ -2,10 +2,14 @@
.webrtc__user-profile {
@include webrtc-button;
+ position: absolute;
+ right: 7px;
text-align: center;
+ top: 5px;
#webrtc-btn {
- display: inherit;
+ height: 23px;
+ width: 23px;
}
}
@@ -15,6 +19,11 @@
margin-right: 10px;
position: relative;
top: 13px;
+
+ svg {
+ position: relative;
+ width: 20px;
+ }
}
.webrtc__notification--rhs {
diff --git a/webapp/sass/responsive/_mobile.scss b/webapp/sass/responsive/_mobile.scss
index 708fa4fca..00f89ff15 100644
--- a/webapp/sass/responsive/_mobile.scss
+++ b/webapp/sass/responsive/_mobile.scss
@@ -1337,7 +1337,6 @@
.modal-body {
padding-bottom: 35px;
}
-
}
.settings-modal {
diff --git a/webapp/sass/utils/_mixins.scss b/webapp/sass/utils/_mixins.scss
index e252086ae..6a041d6ec 100644
--- a/webapp/sass/utils/_mixins.scss
+++ b/webapp/sass/utils/_mixins.scss
@@ -32,68 +32,27 @@
@mixin webrtc-button {
.webrtc__button {
@include border-radius(50px);
+ background: $button--ready;
display: block;
- height: 32px;
- width: 32px;
+ height: 33px;
+ text-align: center;
+ width: 33px;
&.on,
&:hover {
background: darken($button--ready, 5%);
}
- &:hover circle {
- fill: darken($button--ready, 5%);
- }
-
- circle {
- fill: $button--ready;
-
- &.offline {
- fill: $video-circle-offline;
- }
- }
-
- path {
- .on {
- display: none;
- }
-
- .off {
- display: block;
- }
- }
-
- &.on {
- path {
- .on {
- display: block;
- }
-
- .off {
- display: none;
- }
- }
+ &.offline {
+ background: $video-circle-offline;
- circle {
- fill-opacity: 0;
+ &:hover {
+ background: $video-circle-offline;
}
}
- }
- a {
- &[disabled] {
- .webrtc__button {
- &:hover {
- background: none;
- box-shadow: none;
- }
-
- &:hover {
- circle {
- fill: $video-circle-offline;
- }
- }
- }
+ svg {
+ fill: $white;
}
}
}
diff --git a/webapp/stores/browser_store.jsx b/webapp/stores/browser_store.jsx
index 99aebc466..fcd177662 100644
--- a/webapp/stores/browser_store.jsx
+++ b/webapp/stores/browser_store.jsx
@@ -203,7 +203,11 @@ class BrowserStoreClass {
}
hasSeenLandingPage() {
- return JSON.parse(sessionStorage.getItem('__landingPageSeen__'));
+ if (this.isLocalStorageSupported()) {
+ return JSON.parse(sessionStorage.getItem('__landingPageSeen__'));
+ }
+
+ return true;
}
setLandingPageSeen(landingPageSeen) {
diff --git a/webapp/stores/post_store.jsx b/webapp/stores/post_store.jsx
index a4e49fc98..fbe5cd457 100644
--- a/webapp/stores/post_store.jsx
+++ b/webapp/stores/post_store.jsx
@@ -245,7 +245,7 @@ class PostStoreClass extends EventEmitter {
this.postsInfo[id].postList = combinedPosts;
}
- storePost(post) {
+ storePost(post, isNewPost = false) {
const postList = makePostListNonNull(this.getAllPosts(post.channel_id));
if (post.pending_post_id !== '') {
@@ -255,7 +255,7 @@ class PostStoreClass extends EventEmitter {
post.pending_post_id = '';
postList.posts[post.id] = post;
- if (postList.order.indexOf(post.id) === -1) {
+ if (isNewPost && postList.order.indexOf(post.id) === -1) {
postList.order.unshift(post.id);
}
@@ -629,7 +629,7 @@ PostStore.dispatchToken = AppDispatcher.register((payload) => {
PostStore.emitChange();
break;
case ActionTypes.RECEIVED_POST:
- PostStore.storePost(action.post);
+ PostStore.storePost(action.post, true);
PostStore.emitChange();
break;
case ActionTypes.RECEIVED_EDIT_POST:
diff --git a/webapp/utils/channel_intro_messages.jsx b/webapp/utils/channel_intro_messages.jsx
index 899e4e5a4..36d8cdb2a 100644
--- a/webapp/utils/channel_intro_messages.jsx
+++ b/webapp/utils/channel_intro_messages.jsx
@@ -147,10 +147,10 @@ export function createDefaultIntroMessage(channel, centeredIntro) {
export function createStandardIntroMessage(channel, centeredIntro) {
var uiName = channel.display_name;
- var creatorName = '';
-
+ var creatorName = Utils.displayUsername(channel.creator_id);
var uiType;
var memberMessage;
+
if (channel.type === 'P') {
uiType = (
<FormattedMessage
@@ -204,14 +204,30 @@ export function createStandardIntroMessage(channel, centeredIntro) {
} else {
createMessage = (
<span>
- <FormattedHTMLMessage
+ <FormattedMessage
id='intro_messages.creator'
- defaultMessage='This is the start of the <strong>{name}</strong> {type}, created by <strong>{creator}</strong> on <strong>{date}</strong>'
+ defaultMessage='This is the start of the {name} {type}, created by {creator} on {date}.'
values={{
name: (uiName),
type: (uiType),
- date,
- creator: creatorName
+ creator: (creatorName),
+ date
+ }}
+ />
+ </span>
+ );
+ }
+
+ var purposeMessage = '';
+ if (channel.purpose && channel.purpose !== '') {
+ purposeMessage = (
+ <span>
+ <FormattedMessage
+ id='intro_messages.purpose'
+ defaultMessage=" This {type}'s purpose is: {purpose}"
+ values={{
+ purpose: channel.purpose,
+ type: (uiType)
}}
/>
</span>
@@ -232,6 +248,7 @@ export function createStandardIntroMessage(channel, centeredIntro) {
<p className='channel-intro__content'>
{createMessage}
{memberMessage}
+ {purposeMessage}
<br/>
</p>
{createInviteChannelMemberButton(channel, uiType)}
diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx
index 0da17e4b9..d8965516e 100644
--- a/webapp/utils/constants.jsx
+++ b/webapp/utils/constants.jsx
@@ -374,6 +374,7 @@ export const Constants = {
COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>",
REPLY_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'viewBox='-158 242 18 18' style='enable-background:new -158 242 18 18;' xml:space='preserve'> <path d='M-142.2,252.6c-2-3-4.8-4.7-8.3-4.8v-3.3c0-0.2-0.1-0.3-0.2-0.3s-0.3,0-0.4,0.1l-6.9,6.2c-0.1,0.1-0.1,0.2-0.1,0.3 c0,0.1,0,0.2,0.1,0.3l6.9,6.4c0.1,0.1,0.3,0.1,0.4,0.1c0.1-0.1,0.2-0.2,0.2-0.4v-3.8c4.2,0,7.4,0.4,9.6,4.4c0.1,0.1,0.2,0.2,0.3,0.2 c0,0,0.1,0,0.1,0c0.2-0.1,0.3-0.3,0.2-0.4C-140.2,257.3-140.6,255-142.2,252.6z M-150.8,252.5c-0.2,0-0.4,0.2-0.4,0.4v3.3l-6-5.5 l6-5.3v2.8c0,0.2,0.2,0.4,0.4,0.4c3.3,0,6,1.5,8,4.5c0.5,0.8,0.9,1.6,1.2,2.3C-144,252.8-147.1,252.5-150.8,252.5z'/> </svg>",
SCROLL_BOTTOM_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'viewBox='-239 239 21 23' style='enable-background:new -239 239 21 23;' xml:space='preserve'> <path d='M-239,241.4l2.4-2.4l8.1,8.2l8.1-8.2l2.4,2.4l-10.5,10.6L-239,241.4z M-228.5,257.2l8.1-8.2l2.4,2.4l-10.5,10.6l-10.5-10.6 l2.4-2.4L-228.5,257.2z'/> </svg>",
+ VIDEO_ICON: "<svg width='55%'height='100%'viewBox='0 0 13 8'> <g transform='matrix(1,0,0,1,-507,-146)'> <g transform='matrix(0.0133892,0,0,0.014499,500.635,142.838)'> <path d='M1158,547.286L1158,644.276C1158,684.245 1125.55,716.694 1085.58,716.694L579.341,716.694C539.372,716.694 506.922,684.245 506.922,644.276L506.922,306.322C506.922,266.353 539.371,233.904 579.341,233.903L1085.58,233.903C1125.55,233.904 1158,266.353 1158,306.322L1158,402.939L1359.75,253.14C1365.83,248.362 1373.43,245.973 1382.56,245.973C1386.61,245.973 1390.83,246.602 1395.22,247.859C1408.4,252.134 1414.99,259.552 1414.99,270.113L1414.99,680.485C1414.99,691.046 1408.4,698.464 1395.22,702.739C1390.83,703.996 1386.61,704.624 1382.56,704.624C1373.43,704.624 1365.83,702.236 1359.75,697.458L1158,547.286Z'/> </g> </g> </svg>",
UPDATE_TYPING_MS: 5000,
THEMES: {
default: {