summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--api/admin.go4
-rw-r--r--api/context.go1
-rw-r--r--api/license.go5
-rw-r--r--api/post_test.go18
-rw-r--r--api/user.go47
-rw-r--r--api/user_test.go28
-rw-r--r--i18n/en.json16
-rw-r--r--model/client.go11
-rw-r--r--model/search_params.go18
-rw-r--r--model/search_params_test.go10
-rw-r--r--model/utils.go20
-rw-r--r--model/utils_test.go43
-rw-r--r--store/sql_post_store.go1
-rw-r--r--store/sql_post_store_test.go7
-rw-r--r--store/sql_session_store.go18
-rw-r--r--store/sql_session_store_test.go22
-rw-r--r--store/store.go1
-rw-r--r--web/react/components/activity_log_modal.jsx9
-rw-r--r--web/react/components/admin_console/admin_navbar_dropdown.jsx25
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx134
-rw-r--r--web/react/components/admin_console/admin_sidebar_header.jsx9
-rw-r--r--web/react/components/admin_console/email_settings.jsx364
-rw-r--r--web/react/components/admin_console/select_team_modal.jsx21
-rw-r--r--web/react/components/error_bar.jsx18
-rw-r--r--web/react/components/loading_screen.jsx9
-rw-r--r--web/react/components/login.jsx27
-rw-r--r--web/react/components/msg_typing.jsx14
-rw-r--r--web/react/package.json1
-rw-r--r--web/react/pages/channel.jsx17
-rw-r--r--web/react/stores/channel_store.jsx56
-rw-r--r--web/react/stores/error_store.jsx4
-rw-r--r--web/react/stores/preference_store.jsx21
-rw-r--r--web/static/i18n/en.json88
-rw-r--r--web/static/i18n/es.json88
-rw-r--r--web/templates/channel.html2
-rw-r--r--web/templates/claim_account.html18
-rw-r--r--web/templates/docs.html3
-rw-r--r--web/templates/find_team.html3
-rw-r--r--web/templates/head.html13
-rw-r--r--web/templates/login.html2
-rw-r--r--web/templates/password_reset.html16
-rw-r--r--web/templates/verify.html11
-rw-r--r--web/templates/welcome.html37
-rw-r--r--web/web.go9
45 files changed, 1021 insertions, 270 deletions
diff --git a/Makefile b/Makefile
index c6668fbd4..1012b786e 100644
--- a/Makefile
+++ b/Makefile
@@ -197,7 +197,7 @@ travis-init:
build-container:
@echo Building in container
- docker run -e TRAVIS_BUILD_NUMBER=$(TRAVIS_BUILD_NUMBER) --link mattermost-mysql:mysql --link mattermost-postgres:postgres -v `pwd`:/go/src/github.com/mattermost/platform mattermost/builder:latest
+ cd .. && docker run -e TRAVIS_BUILD_NUMBER=$(TRAVIS_BUILD_NUMBER) --link mattermost-mysql:mysql --link mattermost-postgres:postgres -v `pwd`:/go/src/github.com/mattermost mattermost/builder:latest
stop-docker:
@echo Stopping docker containers
diff --git a/api/admin.go b/api/admin.go
index b19772fdf..0ea6341e2 100644
--- a/api/admin.go
+++ b/api/admin.go
@@ -73,7 +73,9 @@ func logClient(c *Context, w http.ResponseWriter, r *http.Request) {
}
if lvl == "ERROR" {
- err := model.NewAppError("client", msg, "")
+ err := &model.AppError{}
+ err.Message = msg
+ err.Where = "client"
c.LogError(err)
}
diff --git a/api/context.go b/api/context.go
index 41a52fa0c..b91981ecd 100644
--- a/api/context.go
+++ b/api/context.go
@@ -45,6 +45,7 @@ type Page struct {
User *model.User
Team *model.Team
Channel *model.Channel
+ Preferences *model.Preferences
PostID string
SessionTokenIndex int64
Locale string
diff --git a/api/license.go b/api/license.go
index 5c602a68e..4077c0e46 100644
--- a/api/license.go
+++ b/api/license.go
@@ -5,7 +5,6 @@ package api
import (
"bytes"
- "fmt"
l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
@@ -65,13 +64,13 @@ func addLicense(c *Context, w http.ResponseWriter, r *http.Request) {
license = model.LicenseFromJson(strings.NewReader(licenseStr))
if result := <-Srv.Store.User().AnalyticsUniqueUserCount(""); result.Err != nil {
- c.Err = model.NewAppError("addLicense", "Unable to count total unique users.", fmt.Sprintf("err=%v", result.Err.Error()))
+ c.Err = model.NewLocAppError("addLicense", "api.license.add_license.invalid_count.app_error", nil, result.Err.Error())
return
} else {
uniqueUserCount := result.Data.(int64)
if uniqueUserCount > int64(*license.Features.Users) {
- c.Err = model.NewAppError("addLicense", fmt.Sprintf("This license only supports %d users, when your system has %d unique users. Unique users are counted distinctly by email address. You can see total user count under Site Reports -> View Statistics.", *license.Features.Users, uniqueUserCount), "")
+ c.Err = model.NewLocAppError("addLicense", "api.license.add_license.unique_users.app_error", map[string]interface{}{"Users": *license.Features.Users, "Count": uniqueUserCount}, "")
return
}
}
diff --git a/api/post_test.go b/api/post_test.go
index a0b8cc9bd..72abcd5cc 100644
--- a/api/post_test.go
+++ b/api/post_test.go
@@ -601,13 +601,11 @@ func TestSearchPostsFromUser(t *testing.T) {
post2 := &model.Post{ChannelId: channel2.Id, Message: "sgtitlereview\n with return"}
post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post)
- // includes "X has joined the channel" messages for both user2 and user3
-
if result := Client.Must(Client.SearchPosts("from: " + user1.Username)).Data.(*model.PostList); len(result.Order) != 1 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
- if result := Client.Must(Client.SearchPosts("from: " + user2.Username)).Data.(*model.PostList); len(result.Order) != 3 {
+ if result := Client.Must(Client.SearchPosts("from: " + user2.Username)).Data.(*model.PostList); len(result.Order) != 1 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
@@ -615,6 +613,9 @@ func TestSearchPostsFromUser(t *testing.T) {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
+ post3 := &model.Post{ChannelId: channel1.Id, Message: "hullo"}
+ post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post)
+
if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " in:" + channel1.Name)).Data.(*model.PostList); len(result.Order) != 1 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
@@ -630,19 +631,22 @@ func TestSearchPostsFromUser(t *testing.T) {
// wait for the join/leave messages to be created for user3 since they're done asynchronously
time.Sleep(100 * time.Millisecond)
- if result := Client.Must(Client.SearchPosts("from: " + user2.Username)).Data.(*model.PostList); len(result.Order) != 3 {
+ if result := Client.Must(Client.SearchPosts("from: " + user2.Username)).Data.(*model.PostList); len(result.Order) != 2 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
- if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username)).Data.(*model.PostList); len(result.Order) != 5 {
+ if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username)).Data.(*model.PostList); len(result.Order) != 2 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
- if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username + " in:" + channel2.Name)).Data.(*model.PostList); len(result.Order) != 3 {
+ if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username + " in:" + channel2.Name)).Data.(*model.PostList); len(result.Order) != 1 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
- if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username + " in:" + channel2.Name + " joined")).Data.(*model.PostList); len(result.Order) != 2 {
+ post4 := &model.Post{ChannelId: channel2.Id, Message: "coconut"}
+ post4 = Client.Must(Client.CreatePost(post4)).Data.(*model.Post)
+
+ if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username + " in:" + channel2.Name + " coconut")).Data.(*model.PostList); len(result.Order) != 1 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
}
diff --git a/api/user.go b/api/user.go
index 473f0da54..6ad0f67ac 100644
--- a/api/user.go
+++ b/api/user.go
@@ -48,6 +48,7 @@ func InitUser(r *mux.Router) {
sr.Handle("/logout", ApiUserRequired(logout)).Methods("POST")
sr.Handle("/login_ldap", ApiAppHandler(loginLdap)).Methods("POST")
sr.Handle("/revoke_session", ApiUserRequired(revokeSession)).Methods("POST")
+ sr.Handle("/attach_device", ApiUserRequired(attachDeviceId)).Methods("POST")
sr.Handle("/switch_to_sso", ApiAppHandler(switchToSSO)).Methods("POST")
sr.Handle("/switch_to_email", ApiUserRequired(switchToEmail)).Methods("POST")
@@ -546,7 +547,6 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
}
}
}
-
} else {
session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthWebInDays)
}
@@ -718,6 +718,49 @@ func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(props)))
}
+func attachDeviceId(c *Context, w http.ResponseWriter, r *http.Request) {
+ props := model.MapFromJson(r.Body)
+
+ deviceId := props["device_id"]
+ if len(deviceId) == 0 {
+ c.SetInvalidParam("attachDevice", "deviceId")
+ return
+ }
+
+ if !(strings.HasPrefix(deviceId, model.PUSH_NOTIFY_APPLE+":") || strings.HasPrefix(deviceId, model.PUSH_NOTIFY_ANDROID+":")) {
+ c.SetInvalidParam("attachDevice", "deviceId")
+ return
+ }
+
+ // A special case where we logout of all other sessions with the same Id
+ if result := <-Srv.Store.Session().GetSessions(c.Session.UserId); result.Err != nil {
+ c.Err = result.Err
+ c.Err.StatusCode = http.StatusForbidden
+ return
+ } else {
+ sessions := result.Data.([]*model.Session)
+ for _, session := range sessions {
+ if session.DeviceId == deviceId && session.Id != c.Session.Id {
+ l4g.Debug(utils.T("api.user.login.revoking.app_error"), session.Id, c.Session.UserId)
+ RevokeSessionById(c, session.Id)
+ if c.Err != nil {
+ c.LogError(c.Err)
+ c.Err = nil
+ }
+ }
+ }
+ }
+
+ sessionCache.Remove(c.Session.Token)
+
+ if result := <-Srv.Store.Session().UpdateDeviceId(c.Session.Id, deviceId); result.Err != nil {
+ c.Err = result.Err
+ return
+ }
+
+ w.Write([]byte(deviceId))
+}
+
func RevokeSessionById(c *Context, sessionId string) {
if result := <-Srv.Store.Session().Get(sessionId); result.Err != nil {
c.Err = result.Err
@@ -1114,7 +1157,7 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
path := "teams/" + c.Session.TeamId + "/users/" + c.Session.UserId + "/profile.png"
if err := writeFile(buf.Bytes(), path); err != nil {
- c.Err = model.NewAppError("uploadProfileImage", "Couldn't upload profile image", "")
+ c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.upload_profile.app_error", nil, "")
return
}
diff --git a/api/user_test.go b/api/user_test.go
index 9a172805a..5f85bda0f 100644
--- a/api/user_test.go
+++ b/api/user_test.go
@@ -734,6 +734,34 @@ func TestUserUpdateRoles(t *testing.T) {
}
}
+func TestUserUpdateDeviceId(t *testing.T) {
+ Setup()
+
+ team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
+ team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+
+ user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(user.Id))
+
+ Client.LoginByEmail(team.Name, user.Email, "pwd")
+ deviceId := model.PUSH_NOTIFY_APPLE + ":1234567890"
+
+ if _, err := Client.AttachDeviceId(deviceId); err != nil {
+ t.Fatal(err)
+ }
+
+ if result := <-Srv.Store.Session().GetSessions(user.Id); result.Err != nil {
+ t.Fatal(result.Err)
+ } else {
+ sessions := result.Data.([]*model.Session)
+
+ if sessions[0].DeviceId != deviceId {
+ t.Fatal("Missing device Id")
+ }
+ }
+}
+
func TestUserUpdateActive(t *testing.T) {
Setup()
diff --git a/i18n/en.json b/i18n/en.json
index 72863acd9..12741fc68 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -588,6 +588,14 @@
"translation": "Could not open license file"
},
{
+ "id": "api.license.add_license.invalid_count.app_error",
+ "translation": "Unable to count total unique users."
+ },
+ {
+ "id": "api.license.add_license.unique_users.app_error",
+ "translation": "This license only supports {{.Users}} users, when your system has {{.Count}} unique users. Unique users are counted distinctly by email address. You can see total user count under Site Reports -> View Statistics."
+ },
+ {
"id": "api.license.add_license.save.app_error",
"translation": "License did not save properly."
},
@@ -1544,6 +1552,10 @@
"translation": "Could not encode profile image"
},
{
+ "id": "api.user.upload_profile_user.upload_profile.app_error",
+ "translation": "Couldn't upload profile image"
+ },
+ {
"id": "api.user.upload_profile_user.no_file.app_error",
"translation": "No file under 'image' in request"
},
@@ -2772,6 +2784,10 @@
"translation": "We couldn't update the roles"
},
{
+ "id": "store.sql_session.update_device_id.app_error",
+ "translation": "We couldn't update the device id"
+ },
+ {
"id": "store.sql_system.get.app_error",
"translation": "We encountered an error finding the system properties"
},
diff --git a/model/client.go b/model/client.go
index 8021c7039..d31ac1592 100644
--- a/model/client.go
+++ b/model/client.go
@@ -775,6 +775,17 @@ func (c *Client) UpdateUserRoles(data map[string]string) (*Result, *AppError) {
}
}
+func (c *Client) AttachDeviceId(deviceId string) (*Result, *AppError) {
+ data := make(map[string]string)
+ data["device_id"] = deviceId
+ if r, err := c.DoApiPost("/users/attach_device", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
+ }
+}
+
func (c *Client) UpdateActive(userId string, active bool) (*Result, *AppError) {
data := make(map[string]string)
data["user_id"] = userId
diff --git a/model/search_params.go b/model/search_params.go
index 17a64d980..9a7406a07 100644
--- a/model/search_params.go
+++ b/model/search_params.go
@@ -20,12 +20,7 @@ func splitWordsNoQuotes(text string) []string {
words := []string{}
for _, word := range strings.Fields(text) {
- word = puncStart.ReplaceAllString(word, "")
- word = puncEnd.ReplaceAllString(word, "")
-
- if len(word) != 0 {
- words = append(words, word)
- }
+ words = append(words, word)
}
return words
@@ -94,7 +89,16 @@ func parseSearchFlags(input []string) ([]string, [][2]string) {
}
if !isFlag {
- words = append(words, word)
+ // trim off surrounding punctuation
+ word = puncStart.ReplaceAllString(word, "")
+ word = puncEnd.ReplaceAllString(word, "")
+
+ // and remove extra pound #s
+ word = hashtagStart.ReplaceAllString(word, "#")
+
+ if len(word) != 0 {
+ words = append(words, word)
+ }
}
}
diff --git a/model/search_params_test.go b/model/search_params_test.go
index af4cbe595..59eb0a113 100644
--- a/model/search_params_test.go
+++ b/model/search_params_test.go
@@ -118,19 +118,19 @@ func TestParseSearchFlags(t *testing.T) {
t.Fatalf("got incorrect flags %v", flags)
}
- if words, flags := parseSearchFlags(splitWords("fruit: cherry")); len(words) != 2 || words[0] != "fruit:" || words[1] != "cherry" {
+ if words, flags := parseSearchFlags(splitWords("fruit: cherry")); len(words) != 2 || words[0] != "fruit" || words[1] != "cherry" {
t.Fatalf("got incorrect words %v", words)
} else if len(flags) != 0 {
t.Fatalf("got incorrect flags %v", flags)
}
- if words, flags := parseSearchFlags(splitWords("channel:")); len(words) != 1 || words[0] != "channel:" {
+ if words, flags := parseSearchFlags(splitWords("channel:")); len(words) != 1 || words[0] != "channel" {
t.Fatalf("got incorrect words %v", words)
} else if len(flags) != 0 {
t.Fatalf("got incorrect flags %v", flags)
}
- if words, flags := parseSearchFlags(splitWords("channel: first in: second from:")); len(words) != 1 || words[0] != "from:" {
+ if words, flags := parseSearchFlags(splitWords("channel: first in: second from:")); len(words) != 1 || words[0] != "from" {
t.Fatalf("got incorrect words %v", words)
} else if len(flags) != 2 || flags[0][0] != "channel" || flags[0][1] != "first" || flags[1][0] != "in" || flags[1][1] != "second" {
t.Fatalf("got incorrect flags %v", flags)
@@ -212,4 +212,8 @@ func TestParseSearchParams(t *testing.T) {
if sp := ParseSearchParams("testing in:channel from:someone"); len(sp) != 1 || sp[0].Terms != "testing" || len(sp[0].InChannels) != 1 || sp[0].InChannels[0] != "channel" || len(sp[0].FromUsers) != 1 || sp[0].FromUsers[0] != "someone" {
t.Fatalf("Incorrect output from parse search params: %v", sp[0])
}
+
+ if sp := ParseSearchParams("##hashtag +#plus+"); len(sp) != 1 || sp[0].Terms != "#hashtag #plus" || sp[0].IsHashtag != true || len(sp[0].InChannels) != 0 || len(sp[0].FromUsers) != 0 {
+ t.Fatalf("Incorrect output from parse search params: %v", sp[0])
+ }
}
diff --git a/model/utils.go b/model/utils.go
index 70b7e3bbd..695d4a0cb 100644
--- a/model/utils.go
+++ b/model/utils.go
@@ -71,16 +71,6 @@ func AppErrorFromJson(data io.Reader) *AppError {
}
}
-func NewAppError(where string, message string, details string) *AppError {
- ap := &AppError{}
- ap.Message = message
- ap.Where = where
- ap.DetailedError = details
- ap.StatusCode = 500
- ap.IsOAuth = false
- return ap
-}
-
func NewLocAppError(where string, id string, params map[string]interface{}, details string) *AppError {
ap := &AppError{}
ap.Id = id
@@ -298,8 +288,9 @@ func Etag(parts ...interface{}) string {
}
var validHashtag = regexp.MustCompile(`^(#[A-Za-zäöüÄÖÜß]+[A-Za-z0-9äöüÄÖÜß_\-]*[A-Za-z0-9äöüÄÖÜß])$`)
-var puncStart = regexp.MustCompile(`^[.,()&$!\?\[\]{}':;\\]+`)
-var puncEnd = regexp.MustCompile(`[.,()&$#!\?\[\]{}';\\]+$`)
+var puncStart = regexp.MustCompile(`^[.,()&$!\?\[\]{}':;\\<>\-+=%^*|]+`)
+var hashtagStart = regexp.MustCompile(`^#{2,}`)
+var puncEnd = regexp.MustCompile(`[.,()&$#!\?\[\]{}':;\\<>\-+=%^*|]+$`)
func ParseHashtags(text string) (string, string) {
words := strings.Fields(text)
@@ -307,8 +298,13 @@ func ParseHashtags(text string) (string, string) {
hashtagString := ""
plainString := ""
for _, word := range words {
+ // trim off surrounding punctuation
word = puncStart.ReplaceAllString(word, "")
word = puncEnd.ReplaceAllString(word, "")
+
+ // and remove extra pound #s
+ word = hashtagStart.ReplaceAllString(word, "#")
+
if validHashtag.MatchString(word) {
hashtagString += " " + word
} else {
diff --git a/model/utils_test.go b/model/utils_test.go
index 24ee4b7a6..02a08d113 100644
--- a/model/utils_test.go
+++ b/model/utils_test.go
@@ -27,7 +27,7 @@ func TestRandomString(t *testing.T) {
}
func TestAppError(t *testing.T) {
- err := NewAppError("TestAppError", "message", "")
+ err := NewLocAppError("TestAppError", "message", nil, "")
json := err.ToJson()
rerr := AppErrorFromJson(strings.NewReader(json))
if err.Message != rerr.Message {
@@ -83,19 +83,34 @@ func TestEtag(t *testing.T) {
}
var hashtags map[string]string = map[string]string{
- "#test": "#test",
- "test": "",
- "#test123": "#test123",
- "#123test123": "",
- "#test-test": "#test-test",
- "#test?": "#test",
- "hi #there": "#there",
- "#bug #idea": "#bug #idea",
- "#bug or #gif!": "#bug #gif",
- "#hüllo": "#hüllo",
- "#?test": "",
- "#-test": "",
- "#yo_yo": "#yo_yo",
+ "#test": "#test",
+ "test": "",
+ "#test123": "#test123",
+ "#123test123": "",
+ "#test-test": "#test-test",
+ "#test?": "#test",
+ "hi #there": "#there",
+ "#bug #idea": "#bug #idea",
+ "#bug or #gif!": "#bug #gif",
+ "#hüllo": "#hüllo",
+ "#?test": "",
+ "#-test": "",
+ "#yo_yo": "#yo_yo",
+ "(#brakets)": "#brakets",
+ ")#stekarb(": "#stekarb",
+ "<#less_than<": "#less_than",
+ ">#greater_than>": "#greater_than",
+ "-#minus-": "#minus",
+ "+#plus+": "#plus",
+ "=#equals=": "#equals",
+ "%#pct%": "#pct",
+ "&#and&": "#and",
+ "^#hat^": "#hat",
+ "##brown#": "#brown",
+ "*#star*": "#star",
+ "|#pipe|": "#pipe",
+ ":#colon:": "#colon",
+ ";#semi;": "#semi",
}
func TestParseHashtags(t *testing.T) {
diff --git a/store/sql_post_store.go b/store/sql_post_store.go
index a2b18a163..2d5d66e0d 100644
--- a/store/sql_post_store.go
+++ b/store/sql_post_store.go
@@ -642,6 +642,7 @@ func (s SqlPostStore) Search(teamId string, userId string, params *model.SearchP
Posts
WHERE
DeleteAt = 0
+ AND Type NOT LIKE '` + model.POST_SYSTEM_MESSAGE_PREFIX + `%'
POST_FILTER
AND ChannelId IN (
SELECT
diff --git a/store/sql_post_store_test.go b/store/sql_post_store_test.go
index a3e3e10dd..46b8d7678 100644
--- a/store/sql_post_store_test.go
+++ b/store/sql_post_store_test.go
@@ -676,6 +676,13 @@ func TestPostStoreSearch(t *testing.T) {
o1.Message = "corey mattermost new york"
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
+ o1a := &model.Post{}
+ o1a.ChannelId = c1.Id
+ o1a.UserId = model.NewId()
+ o1a.Message = "corey mattermost new york"
+ o1a.Type = model.POST_JOIN_LEAVE
+ o1a = (<-store.Post().Save(o1a)).Data.(*model.Post)
+
o2 := &model.Post{}
o2.ChannelId = c1.Id
o2.UserId = model.NewId()
diff --git a/store/sql_session_store.go b/store/sql_session_store.go
index 4762a1dfd..6532947f4 100644
--- a/store/sql_session_store.go
+++ b/store/sql_session_store.go
@@ -232,3 +232,21 @@ func (me SqlSessionStore) UpdateRoles(userId, roles string) StoreChannel {
return storeChannel
}
+
+func (me SqlSessionStore) UpdateDeviceId(id, deviceId string) StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+ if _, err := me.GetMaster().Exec("UPDATE Sessions SET DeviceId = :DeviceId WHERE Id = :Id", map[string]interface{}{"DeviceId": deviceId, "Id": id}); err != nil {
+ result.Err = model.NewLocAppError("SqlSessionStore.UpdateDeviceId", "store.sql_session.update_device_id.app_error", nil, "")
+ } else {
+ result.Data = deviceId
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
diff --git a/store/sql_session_store_test.go b/store/sql_session_store_test.go
index cec8e93b0..34d3128a6 100644
--- a/store/sql_session_store_test.go
+++ b/store/sql_session_store_test.go
@@ -157,6 +157,28 @@ func TestSessionRemoveToken(t *testing.T) {
}
}
+func TestSessionUpdateDeviceId(t *testing.T) {
+ Setup()
+
+ s1 := model.Session{}
+ s1.UserId = model.NewId()
+ s1.TeamId = model.NewId()
+ Must(store.Session().Save(&s1))
+
+ if rs1 := (<-store.Session().UpdateDeviceId(s1.Id, model.PUSH_NOTIFY_APPLE+":1234567890")); rs1.Err != nil {
+ t.Fatal(rs1.Err)
+ }
+
+ s2 := model.Session{}
+ s2.UserId = model.NewId()
+ s2.TeamId = model.NewId()
+ Must(store.Session().Save(&s2))
+
+ if rs2 := (<-store.Session().UpdateDeviceId(s2.Id, model.PUSH_NOTIFY_APPLE+":1234567890")); rs2.Err != nil {
+ t.Fatal(rs2.Err)
+ }
+}
+
func TestSessionStoreUpdateLastActivityAt(t *testing.T) {
Setup()
diff --git a/store/store.go b/store/store.go
index b91b08f27..3988f0c6a 100644
--- a/store/store.go
+++ b/store/store.go
@@ -137,6 +137,7 @@ type SessionStore interface {
PermanentDeleteSessionsByUser(teamId string) StoreChannel
UpdateLastActivityAt(sessionId string, time int64) StoreChannel
UpdateRoles(userId string, roles string) StoreChannel
+ UpdateDeviceId(id string, deviceId string) StoreChannel
}
type AuditStore interface {
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index 6a880f0ee..2c42f5971 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -100,12 +100,15 @@ export default class ActivityLogModal extends React.Component {
if (currentSession.props.platform === 'Windows') {
devicePicture = 'fa fa-windows';
+ } else if (currentSession.device_id && currentSession.device_id.indexOf('apple:') === 0) {
+ devicePicture = 'fa fa-apple';
+ devicePlatform = 'iPhone Native App';
+ } else if (currentSession.device_id && currentSession.device_id.indexOf('android:') === 0) {
+ devicePlatform = 'Android Native App';
+ devicePicture = 'fa fa-android';
} else if (currentSession.props.platform === 'Macintosh' ||
currentSession.props.platform === 'iPhone') {
devicePicture = 'fa fa-apple';
- } else if (currentSession.props.platform.browser.indexOf('Mattermost/') === 0) {
- devicePicture = 'fa fa-apple';
- devicePlatform = 'iPhone';
} else if (currentSession.props.platform === 'Linux') {
if (currentSession.props.os.indexOf('Android') >= 0) {
devicePlatform = 'Android';
diff --git a/web/react/components/admin_console/admin_navbar_dropdown.jsx b/web/react/components/admin_console/admin_navbar_dropdown.jsx
index 783d45de6..dc0b3c4cb 100644
--- a/web/react/components/admin_console/admin_navbar_dropdown.jsx
+++ b/web/react/components/admin_console/admin_navbar_dropdown.jsx
@@ -7,6 +7,8 @@ import TeamStore from '../../stores/team_store.jsx';
import Constants from '../../utils/constants.jsx';
+import {FormattedMessage} from 'mm-intl';
+
function getStateFromStores() {
return {currentTeam: TeamStore.getCurrent()};
}
@@ -66,7 +68,13 @@ export default class AdminNavbarDropdown extends React.Component {
<a
href={Utils.getWindowLocationOrigin() + '/' + this.state.currentTeam.name}
>
- {'Switch to ' + this.state.currentTeam.display_name}
+ <FormattedMessage
+ id='admin.nav.switch'
+ defaultMessage='Switch to {display_name}'
+ values={{
+ display_name: this.state.currentTeam.display_name
+ }}
+ />
</a>
</li>
<li>
@@ -74,7 +82,10 @@ export default class AdminNavbarDropdown extends React.Component {
href='#'
onClick={this.handleLogoutClick}
>
- {'Logout'}
+ <FormattedMessage
+ id='admin.nav.logout'
+ defaultMessage='Logout'
+ />
</a>
</li>
<li className='divider'></li>
@@ -83,7 +94,10 @@ export default class AdminNavbarDropdown extends React.Component {
target='_blank'
href='/static/help/help.html'
>
- {'Help'}
+ <FormattedMessage
+ id='admin.nav.help'
+ defaultMessage='Help'
+ />
</a>
</li>
<li>
@@ -91,7 +105,10 @@ export default class AdminNavbarDropdown extends React.Component {
target='_blank'
href='/static/help/report_problem.html'
>
- {'Report a Problem'}
+ <FormattedMessage
+ id='admin.nav.report'
+ defaultMessage='Report a Problem'
+ />
</a>
</li>
</ul>
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index 66f82c55b..d6bae1feb 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -5,6 +5,8 @@ import AdminSidebarHeader from './admin_sidebar_header.jsx';
import SelectTeamModal from './select_team_modal.jsx';
import * as Utils from '../../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
const Tooltip = ReactBootstrap.Tooltip;
const OverlayTrigger = ReactBootstrap.OverlayTrigger;
@@ -82,12 +84,27 @@ export default class AdminSidebar extends React.Component {
render() {
var count = '*';
- var teams = 'Loading';
+ var teams = (
+ <FormattedMessage
+ id='admin.sidebar.loading'
+ defaultMessage='Loading'
+ />
+ );
const removeTooltip = (
- <Tooltip id='remove-team-tooltip'>{'Remove team from sidebar menu'}</Tooltip>
+ <Tooltip id='remove-team-tooltip'>
+ <FormattedMessage
+ id='admin.sidebar.rmTeamSidebar'
+ defaultMessage='Remove team from sidebar menu'
+ />
+ </Tooltip>
);
const addTeamTooltip = (
- <Tooltip id='add-team-tooltip'>{'Add team from sidebar menu'}</Tooltip>
+ <Tooltip id='add-team-tooltip'>
+ <FormattedMessage
+ id='admin.sidebar.addTeamSidebar'
+ defaultMessage='Add team from sidebar menu'
+ />
+ </Tooltip>
);
if (this.props.teams != null) {
@@ -134,7 +151,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('team_users', team.id)}
onClick={this.handleClick.bind(this, 'team_users', team.id)}
>
- {'- Users'}
+ <FormattedMessage
+ id='admin.sidebar.users'
+ defaultMessage='- Users'
+ />
</a>
</li>
<li>
@@ -143,7 +163,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('team_analytics', team.id)}
onClick={this.handleClick.bind(this, 'team_analytics', team.id)}
>
- {'- Statistics'}
+ <FormattedMessage
+ id='admin.sidebar.statistics'
+ defaultMessage='- Statistics'
+ />
</a>
</li>
</ul>
@@ -166,7 +189,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('ldap_settings')}
onClick={this.handleClick.bind(this, 'ldap_settings', null)}
>
- {'LDAP Settings'}
+ <FormattedMessage
+ id='admin.sidebar.ldap'
+ defaultMessage='LDAP Settings'
+ />
</a>
</li>
);
@@ -179,7 +205,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('license')}
onClick={this.handleClick.bind(this, 'license', null)}
>
- {'Edition and License'}
+ <FormattedMessage
+ id='admin.sidebar.license'
+ defaultMessage='Edition and License'
+ />
</a>
</li>
);
@@ -196,7 +225,12 @@ export default class AdminSidebar extends React.Component {
<li>
<h4>
<span className='icon fa fa-gear'></span>
- <span>{'SITE REPORTS'}</span>
+ <span>
+ <FormattedMessage
+ id='admin.sidebar.reports'
+ defaultMessage='SITE REPORTS'
+ />
+ </span>
</h4>
</li>
</ul>
@@ -207,7 +241,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('system_analytics')}
onClick={this.handleClick.bind(this, 'system_analytics', null)}
>
- {'View Statistics'}
+ <FormattedMessage
+ id='admin.sidebar.view_statistics'
+ defaultMessage='View Statistics'
+ />
</a>
</li>
</ul>
@@ -215,7 +252,12 @@ export default class AdminSidebar extends React.Component {
<li>
<h4>
<span className='icon fa fa-gear'></span>
- <span>{'SETTINGS'}</span>
+ <span>
+ <FormattedMessage
+ id='admin.sidebar.settings'
+ defaultMessage='SETTINGS'
+ />
+ </span>
</h4>
</li>
</ul>
@@ -226,7 +268,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('service_settings')}
onClick={this.handleClick.bind(this, 'service_settings', null)}
>
- {'Service Settings'}
+ <FormattedMessage
+ id='admin.sidebar.service'
+ defaultMessage='Service Settings'
+ />
</a>
</li>
<li>
@@ -235,7 +280,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('team_settings')}
onClick={this.handleClick.bind(this, 'team_settings', null)}
>
- {'Team Settings'}
+ <FormattedMessage
+ id='admin.sidebar.team'
+ defaultMessage='Team Settings'
+ />
</a>
</li>
<li>
@@ -244,7 +292,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('sql_settings')}
onClick={this.handleClick.bind(this, 'sql_settings', null)}
>
- {'SQL Settings'}
+ <FormattedMessage
+ id='admin.sidebar.sql'
+ defaultMessage='SQL Settings'
+ />
</a>
</li>
<li>
@@ -253,7 +304,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('email_settings')}
onClick={this.handleClick.bind(this, 'email_settings', null)}
>
- {'Email Settings'}
+ <FormattedMessage
+ id='admin.sidebar.email'
+ defaultMessage='Email Settings'
+ />
</a>
</li>
<li>
@@ -262,7 +316,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('image_settings')}
onClick={this.handleClick.bind(this, 'image_settings', null)}
>
- {'File Settings'}
+ <FormattedMessage
+ id='admin.sidebar.file'
+ defaultMessage='File Settings'
+ />
</a>
</li>
<li>
@@ -271,7 +328,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('log_settings')}
onClick={this.handleClick.bind(this, 'log_settings', null)}
>
- {'Log Settings'}
+ <FormattedMessage
+ id='admin.sidebar.log'
+ defaultMessage='Log Settings'
+ />
</a>
</li>
<li>
@@ -280,7 +340,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('rate_settings')}
onClick={this.handleClick.bind(this, 'rate_settings', null)}
>
- {'Rate Limit Settings'}
+ <FormattedMessage
+ id='admin.sidebar.rate_limit'
+ defaultMessage='Rate Limit Settings'
+ />
</a>
</li>
<li>
@@ -289,7 +352,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('privacy_settings')}
onClick={this.handleClick.bind(this, 'privacy_settings', null)}
>
- {'Privacy Settings'}
+ <FormattedMessage
+ id='admin.sidebar.privacy'
+ defaultMessage='Privacy Settings'
+ />
</a>
</li>
<li>
@@ -298,7 +364,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('gitlab_settings')}
onClick={this.handleClick.bind(this, 'gitlab_settings', null)}
>
- {'GitLab Settings'}
+ <FormattedMessage
+ id='admin.sidebar.gitlab'
+ defaultMessage='GitLab Settings'
+ />
</a>
</li>
{ldapSettings}
@@ -308,7 +377,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('legal_and_support_settings')}
onClick={this.handleClick.bind(this, 'legal_and_support_settings', null)}
>
- {'Legal and Support Settings'}
+ <FormattedMessage
+ id='admin.sidebar.support'
+ defaultMessage='Legal and Support Settings'
+ />
</a>
</li>
</ul>
@@ -316,7 +388,15 @@ export default class AdminSidebar extends React.Component {
<li>
<h4>
<span className='icon fa fa-gear'></span>
- <span>{'TEAMS (' + count + ')'}</span>
+ <span>
+ <FormattedMessage
+ id='admin.sidebar.teams'
+ defaultMessage='TEAMS ({count})'
+ values={{
+ count: count
+ }}
+ />
+ </span>
<span className='menu-icon--right'>
<OverlayTrigger
delayShow={1000}
@@ -345,7 +425,12 @@ export default class AdminSidebar extends React.Component {
<li>
<h4>
<span className='icon fa fa-gear'></span>
- <span>{'OTHER'}</span>
+ <span>
+ <FormattedMessage
+ id='admin.sidebar.other'
+ defaultMessage='OTHER'
+ />
+ </span>
</h4>
</li>
</ul>
@@ -357,7 +442,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('logs')}
onClick={this.handleClick.bind(this, 'logs', null)}
>
- {'Logs'}
+ <FormattedMessage
+ id='admin.sidebar.logs'
+ defaultMessage='Logs'
+ />
</a>
</li>
</ul>
diff --git a/web/react/components/admin_console/admin_sidebar_header.jsx b/web/react/components/admin_console/admin_sidebar_header.jsx
index bfd479939..db499265e 100644
--- a/web/react/components/admin_console/admin_sidebar_header.jsx
+++ b/web/react/components/admin_console/admin_sidebar_header.jsx
@@ -5,6 +5,8 @@ import AdminNavbarDropdown from './admin_navbar_dropdown.jsx';
import UserStore from '../../stores/user_store.jsx';
import * as Utils from '../../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class SidebarHeader extends React.Component {
constructor(props) {
super(props);
@@ -51,7 +53,12 @@ export default class SidebarHeader extends React.Component {
{profilePicture}
<div className='header__info'>
<div className='user__name'>{'@' + me.username}</div>
- <div className='team__name'>{'System Console'}</div>
+ <div className='team__name'>
+ <FormattedMessage
+ id='admin.sidebarHeader.systemConsole'
+ defaultMessage='System Console'
+ />
+ </div>
</div>
</a>
<AdminNavbarDropdown ref='dropdown' />
diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx
index c568c5a77..ce3c8cd12 100644
--- a/web/react/components/admin_console/email_settings.jsx
+++ b/web/react/components/admin_console/email_settings.jsx
@@ -5,7 +5,68 @@ import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import crypto from 'crypto';
-export default class EmailSettings extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+var holders = defineMessages({
+ notificationDisplayExample: {
+ id: 'admin.email.notificationDisplayExample',
+ defaultMessage: 'Ex: "Mattermost Notification", "System", "No-Reply"'
+ },
+ notificationEmailExample: {
+ id: 'admin.email.notificationEmailExample',
+ defaultMessage: 'Ex: "mattermost@yourcompany.com", "admin@yourcompany.com"'
+ },
+ smtpUsernameExample: {
+ id: 'admin.email.smtpUsernameExample',
+ defaultMessage: 'Ex: "admin@yourcompany.com", "AKIADTOVBGERKLCBV"'
+ },
+ smtpPasswordExample: {
+ id: 'admin.email.smtpPasswordExample',
+ defaultMessage: 'Ex: "yourpassword", "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
+ },
+ smtpServerExample: {
+ id: 'admin.email.smtpServerExample',
+ defaultMessage: 'Ex: "smtp.yourcompany.com", "email-smtp.us-east-1.amazonaws.com"'
+ },
+ smtpPortExample: {
+ id: 'admin.email.smtpPortExample',
+ defaultMessage: 'Ex: "25", "465"'
+ },
+ connectionSecurityNone: {
+ id: 'admin.email.connectionSecurityNone',
+ defaultMessage: 'None'
+ },
+ connectionSecurityTls: {
+ id: 'admin.email.connectionSecurityTls',
+ defaultMessage: 'TLS (Recommended)'
+ },
+ connectionSecurityStart: {
+ id: 'admin.email.connectionSecurityStart',
+ defaultMessage: 'STARTTLS'
+ },
+ inviteSaltExample: {
+ id: 'admin.email.inviteSaltExample',
+ defaultMessage: 'Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"'
+ },
+ passwordSaltExample: {
+ id: 'admin.email.passwordSaltExample',
+ defaultMessage: 'Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"'
+ },
+ pushServerEx: {
+ id: 'admin.email.pushServerEx',
+ defaultMessage: 'E.g.: "https://push-test.mattermost.com"'
+ },
+ testing: {
+ id: 'admin.email.testing',
+ defaultMessage: 'Testing...'
+ },
+ saving: {
+ id: 'admin.email.saving',
+ defaultMessage: 'Saving Config...'
+ }
+});
+
+class EmailSettings extends React.Component {
constructor(props) {
super(props);
@@ -156,6 +217,7 @@ export default class EmailSettings extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
@@ -170,7 +232,11 @@ export default class EmailSettings extends React.Component {
if (this.state.emailSuccess) {
emailSuccess = (
<div className='alert alert-success'>
- <i className='fa fa-check'></i>{'No errors were reported while sending an email. Please check your inbox to make sure.'}
+ <i className='fa fa-check'></i>
+ <FormattedMessage
+ id='admin.email.emailSuccess'
+ defaultMessage='No errors were reported while sending an email. Please check your inbox to make sure.'
+ />
</div>
);
}
@@ -179,14 +245,26 @@ export default class EmailSettings extends React.Component {
if (this.state.emailFail) {
emailSuccess = (
<div className='alert alert-warning'>
- <i className='fa fa-warning'></i>{'Connection unsuccessful: ' + this.state.emailFail}
+ <i className='fa fa-warning'></i>
+ <FormattedMessage
+ id='admin.email.emailFail'
+ defaultMessage='Connection unsuccessful: {error}'
+ values={{
+ error: this.state.emailFail
+ }}
+ />
</div>
);
}
return (
<div className='wrapper--fixed'>
- <h3>{'Email Settings'}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.email.emailSettings'
+ defaultMessage='Email Settings'
+ />
+ </h3>
<form
className='form-horizontal'
role='form'
@@ -197,7 +275,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='allowSignUpWithEmail'
>
- {'Allow Sign Up With Email: '}
+ <FormattedMessage
+ id='admin.email.allowSignupTitle'
+ defaultMessage='Allow Sign Up With Email: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -209,7 +290,10 @@ export default class EmailSettings extends React.Component {
defaultChecked={this.props.config.EmailSettings.EnableSignUpWithEmail}
onChange={this.handleChange.bind(this, 'allowSignUpWithEmail_true')}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.email.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -219,9 +303,17 @@ export default class EmailSettings extends React.Component {
defaultChecked={!this.props.config.EmailSettings.EnableSignUpWithEmail}
onChange={this.handleChange.bind(this, 'allowSignUpWithEmail_false')}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.email.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'When true, Mattermost allows team creation and account signup using email and password. This value should be false only when you want to limit signup to a single-sign-on service like OAuth or LDAP.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.allowSignupDescription'
+ defaultMessage='When true, Mattermost allows team creation and account signup using email and password. This value should be false only when you want to limit signup to a single-sign-on service like OAuth or LDAP.'
+ />
+ </p>
</div>
</div>
@@ -230,7 +322,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='sendEmailNotifications'
>
- {'Send Email Notifications: '}
+ <FormattedMessage
+ id='admin.email.notificationsTitle'
+ defaultMessage='Send Email Notifications: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -242,7 +337,10 @@ export default class EmailSettings extends React.Component {
defaultChecked={this.props.config.EmailSettings.SendEmailNotifications}
onChange={this.handleChange.bind(this, 'sendEmailNotifications_true')}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.email.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -252,9 +350,17 @@ export default class EmailSettings extends React.Component {
defaultChecked={!this.props.config.EmailSettings.SendEmailNotifications}
onChange={this.handleChange.bind(this, 'sendEmailNotifications_false')}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.email.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.\nSetting this to true removes the Preview Mode banner (requires logging out and logging back in after setting is changed).'}</p>
+ <p className='help-text'>
+ <FormattedHTMLMessage
+ id='admin.email.notificationsDescription'
+ defaultMessage='Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.<br />Setting this to true removes the Preview Mode banner (requires logging out and logging back in after setting is changed).'
+ />
+ </p>
</div>
</div>
@@ -263,7 +369,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='requireEmailVerification'
>
- {'Require Email Verification: '}
+ <FormattedMessage
+ id='admin.email.requireVerificationTitle'
+ defaultMessage='Require Email Verification: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -276,7 +385,10 @@ export default class EmailSettings extends React.Component {
onChange={this.handleChange.bind(this, 'requireEmailVerification_true')}
disabled={!this.state.sendEmailNotifications}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.email.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -287,9 +399,17 @@ export default class EmailSettings extends React.Component {
onChange={this.handleChange.bind(this, 'requireEmailVerification_false')}
disabled={!this.state.sendEmailNotifications}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.email.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'Typically set to true in production. When true, Mattermost requires email verification after account creation prior to allowing login. Developers may set this field to false so skip sending verification emails for faster development.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.requireVerificationDescription'
+ defaultMessage='Typically set to true in production. When true, Mattermost requires email verification after account creation prior to allowing login. Developers may set this field to false so skip sending verification emails for faster development.'
+ />
+ </p>
</div>
</div>
@@ -298,7 +418,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='feedbackName'
>
- {'Notification Display Name:'}
+ <FormattedMessage
+ id='admin.email.notificationDisplayTitle'
+ defaultMessage='Notification Display Name:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -306,12 +429,17 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='feedbackName'
ref='feedbackName'
- placeholder='E.g.: "Mattermost Notification", "System", "No-Reply"'
+ placeholder={formatMessage(holders.notificationDisplayExample)}
defaultValue={this.props.config.EmailSettings.FeedbackName}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{'Display name on email account used when sending notification emails from Mattermost.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.notificationDisplayDescription'
+ defaultMessage='Display name on email account used when sending notification emails from Mattermost.'
+ />
+ </p>
</div>
</div>
@@ -320,7 +448,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='feedbackEmail'
>
- {'Notification Email Address:'}
+ <FormattedMessage
+ id='admin.email.notificationEmailTitle'
+ defaultMessage='Notification Email Address:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -328,12 +459,17 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='feedbackEmail'
ref='feedbackEmail'
- placeholder='E.g.: "mattermost@yourcompany.com", "admin@yourcompany.com"'
+ placeholder={formatMessage(holders.notificationEmailExample)}
defaultValue={this.props.config.EmailSettings.FeedbackEmail}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{'Email address displayed on email account used when sending notification emails from Mattermost.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.notificationEmailDescription'
+ defaultMessage='Email address displayed on email account used when sending notification emails from Mattermost.'
+ />
+ </p>
</div>
</div>
@@ -342,7 +478,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='SMTPUsername'
>
- {'SMTP Username:'}
+ <FormattedMessage
+ id='admin.email.smtpUsernameTitle'
+ defaultMessage='SMTP Username:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -350,12 +489,17 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='SMTPUsername'
ref='SMTPUsername'
- placeholder='E.g.: "admin@yourcompany.com", "AKIADTOVBGERKLCBV"'
+ placeholder={formatMessage(holders.smtpUsernameExample)}
defaultValue={this.props.config.EmailSettings.SMTPUsername}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{' Obtain this credential from administrator setting up your email server.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.smtpUsernameDescription'
+ defaultMessage=' Obtain this credential from administrator setting up your email server.'
+ />
+ </p>
</div>
</div>
@@ -364,7 +508,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='SMTPPassword'
>
- {'SMTP Password:'}
+ <FormattedMessage
+ id='admin.email.smtpPasswordTitle'
+ defaultMessage='SMTP Password:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -372,12 +519,17 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='SMTPPassword'
ref='SMTPPassword'
- placeholder='E.g.: "yourpassword", "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
+ placeholder={formatMessage(holders.smtpPasswordExample)}
defaultValue={this.props.config.EmailSettings.SMTPPassword}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{' Obtain this credential from administrator setting up your email server.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.smtpPasswordDescription'
+ defaultMessage=' Obtain this credential from administrator setting up your email server.'
+ />
+ </p>
</div>
</div>
@@ -386,7 +538,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='SMTPServer'
>
- {'SMTP Server:'}
+ <FormattedMessage
+ id='admin.email.smtpServerTitle'
+ defaultMessage='SMTP Server:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -394,12 +549,17 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='SMTPServer'
ref='SMTPServer'
- placeholder='E.g.: "smtp.yourcompany.com", "email-smtp.us-east-1.amazonaws.com"'
+ placeholder={formatMessage(holders.smtpServerExample)}
defaultValue={this.props.config.EmailSettings.SMTPServer}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{'Location of SMTP email server.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.smtpServerDescription'
+ defaultMessage='Location of SMTP email server.'
+ />
+ </p>
</div>
</div>
@@ -408,7 +568,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='SMTPPort'
>
- {'SMTP Port:'}
+ <FormattedMessage
+ id='admin.email.smtpPortTitle'
+ defaultMessage='SMTP Port:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -416,12 +579,17 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='SMTPPort'
ref='SMTPPort'
- placeholder='E.g.: "25", "465"'
+ placeholder={formatMessage(holders.smtpPortExample)}
defaultValue={this.props.config.EmailSettings.SMTPPort}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{'Port of SMTP email server.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.smtpPortDescription'
+ defaultMessage='Port of SMTP email server.'
+ />
+ </p>
</div>
</div>
@@ -430,7 +598,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='ConnectionSecurity'
>
- {'Connection Security:'}
+ <FormattedMessage
+ id='admin.email.connectionSecurityTitle'
+ defaultMessage='Connection Security:'
+ />
</label>
<div className='col-sm-8'>
<select
@@ -441,9 +612,9 @@ export default class EmailSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
>
- <option value=''>{'None'}</option>
- <option value='TLS'>{'TLS (Recommended)'}</option>
- <option value='STARTTLS'>{'STARTTLS'}</option>
+ <option value=''>{formatMessage(holders.connectionSecurityNone)}</option>
+ <option value='TLS'>{formatMessage(holders.connectionSecurityTls)}</option>
+ <option value='STARTTLS'>{formatMessage(holders.connectionSecurityStart)}</option>
</select>
<div className='help-text'>
<table
@@ -451,9 +622,29 @@ export default class EmailSettings extends React.Component {
cellPadding='5'
>
<tbody>
- <tr><td className='help-text'>{'None'}</td><td className='help-text'>{'Mattermost will send email over an unsecure connection.'}</td></tr>
- <tr><td className='help-text'>{'TLS'}</td><td className='help-text'>{'Encrypts the communication between Mattermost and your email server.'}</td></tr>
- <tr><td className='help-text'>{'STARTTLS'}</td><td className='help-text'>{'Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'}</td></tr>
+ <tr><td className='help-text'>
+ <FormattedMessage
+ id='admin.email.connectionSecurityNone'
+ defaultMessage='None'
+ />
+ </td><td className='help-text'>
+ <FormattedMessage
+ id='admin.email.connectionSecurityNoneDescription'
+ defaultMessage='Mattermost will send email over an unsecure connection.'
+ />
+ </td></tr>
+ <tr><td className='help-text'>{'TLS'}</td><td className='help-text'>
+ <FormattedMessage
+ id='admin.email.connectionSecurityTlsDescription'
+ defaultMessage='Encrypts the communication between Mattermost and your email server.'
+ />
+ </td></tr>
+ <tr><td className='help-text'>{'STARTTLS'}</td><td className='help-text'>
+ <FormattedMessage
+ id='admin.email.connectionSecurityStartDescription'
+ defaultMessage='Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'
+ />
+ </td></tr>
</tbody>
</table>
</div>
@@ -463,9 +654,12 @@ export default class EmailSettings extends React.Component {
onClick={this.handleTestConnection}
disabled={!this.state.sendEmailNotifications}
id='connection-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Testing...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.testing)}
>
- {'Test Connection'}
+ <FormattedMessage
+ id='admin.email.connectionSecurityTest'
+ defaultMessage='Test Connection'
+ />
</button>
{emailSuccess}
{emailFail}
@@ -478,7 +672,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='InviteSalt'
>
- {'Invite Salt:'}
+ <FormattedMessage
+ id='admin.email.inviteSaltTitle'
+ defaultMessage='Invite Salt:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -486,19 +683,27 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='InviteSalt'
ref='InviteSalt'
- placeholder='E.g.: "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"'
+ placeholder={formatMessage(holders.inviteSaltExample)}
defaultValue={this.props.config.EmailSettings.InviteSalt}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{'32-character salt added to signing of email invites. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.inviteSaltDescription'
+ defaultMessage='32-character salt added to signing of email invites. Randomly generated on install. Click "Re-Generate" to create new salt.'
+ />
+ </p>
<div className='help-text'>
<button
className='btn btn-default'
onClick={this.handleGenerateInvite}
disabled={!this.state.sendEmailNotifications}
>
- {'Re-Generate'}
+ <FormattedMessage
+ id='admin.email.regenerate'
+ defaultMessage='Re-Generate'
+ />
</button>
</div>
</div>
@@ -509,7 +714,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='PasswordResetSalt'
>
- {'Password Reset Salt:'}
+ <FormattedMessage
+ id='admin.email.passwordSaltTitle'
+ defaultMessage='Password Reset Salt:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -517,19 +725,27 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='PasswordResetSalt'
ref='PasswordResetSalt'
- placeholder='E.g.: "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"'
+ placeholder={formatMessage(holders.passwordSaltExample)}
defaultValue={this.props.config.EmailSettings.PasswordResetSalt}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{'32-character salt added to signing of password reset emails. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.passwordSaltDescription'
+ defaultMessage='32-character salt added to signing of password reset emails. Randomly generated on install. Click "Re-Generate" to create new salt.'
+ />
+ </p>
<div className='help-text'>
<button
className='btn btn-default'
onClick={this.handleGenerateReset}
disabled={!this.state.sendEmailNotifications}
>
- {'Re-Generate'}
+ <FormattedMessage
+ id='admin.email.regenerate'
+ defaultMessage='Re-Generate'
+ />
</button>
</div>
</div>
@@ -540,7 +756,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='sendPushNotifications'
>
- {'Send Push Notifications: '}
+ <FormattedMessage
+ id='admin.email.pushTitle'
+ defaultMessage='Send Push Notifications: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -552,7 +771,10 @@ export default class EmailSettings extends React.Component {
defaultChecked={this.props.config.EmailSettings.SendPushNotifications}
onChange={this.handleChange.bind(this, 'sendPushNotifications_true')}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.email.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -562,9 +784,17 @@ export default class EmailSettings extends React.Component {
defaultChecked={!this.props.config.EmailSettings.SendPushNotifications}
onChange={this.handleChange.bind(this, 'sendPushNotifications_false')}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.email.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'Typically set to true in production. When true, Mattermost attempts to send iOS and Android push notifications through the push notification server.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.pushDesc'
+ defaultMessage='Typically set to true in production. When true, Mattermost attempts to send iOS and Android push notifications through the push notification server.'
+ />
+ </p>
</div>
</div>
@@ -573,7 +803,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='PushNotificationServer'
>
- {'Push Notification Server:'}
+ <FormattedMessage
+ id='admin.email.pushServerTitle'
+ defaultMessage='Push Notification Server:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -581,12 +814,17 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='PushNotificationServer'
ref='PushNotificationServer'
- placeholder='E.g.: "https://push-test.mattermost.com"'
+ placeholder={formatMessage(holders.pushServerEx)}
defaultValue={this.props.config.EmailSettings.PushNotificationServer}
onChange={this.handleChange}
disabled={!this.state.sendPushNotifications}
/>
- <p className='help-text'>{'Location of Mattermost push notification service you can set up behind your firewall using https://github.com/mattermost/push-proxy. For testing you can use https://push-test.mattermost.com, which connects to the sample Mattermost iOS app in the public Apple AppStore. Please do not use test service for production deployments.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.pushServerDesc'
+ defaultMessage='Location of Mattermost push notification service you can set up behind your firewall using https://github.com/mattermost/push-proxy. For testing you can use https://push-test.mattermost.com, which connects to the sample Mattermost iOS app in the public Apple AppStore. Please do not use test service for production deployments.'
+ />
+ </p>
</div>
</div>
@@ -599,9 +837,12 @@ export default class EmailSettings extends React.Component {
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
>
- {'Save'}
+ <FormattedMessage
+ id='admin.email.save'
+ defaultMessage='Save'
+ />
</button>
</div>
</div>
@@ -613,5 +854,8 @@ export default class EmailSettings extends React.Component {
}
EmailSettings.propTypes = {
+ intl: intlShape.isRequired,
config: React.PropTypes.object
};
+
+export default injectIntl(EmailSettings); \ No newline at end of file
diff --git a/web/react/components/admin_console/select_team_modal.jsx b/web/react/components/admin_console/select_team_modal.jsx
index 858b6bbfe..e0d070b28 100644
--- a/web/react/components/admin_console/select_team_modal.jsx
+++ b/web/react/components/admin_console/select_team_modal.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import {FormattedMessage} from 'mm-intl';
+
var Modal = ReactBootstrap.Modal;
export default class SelectTeamModal extends React.Component {
@@ -45,7 +47,12 @@ export default class SelectTeamModal extends React.Component {
onHide={this.doCancel}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Select Team'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='admin.select_team.selectTeam'
+ defaultMessage='Select Team'
+ />
+ </Modal.Title>
</Modal.Header>
<form
role='form'
@@ -70,7 +77,10 @@ export default class SelectTeamModal extends React.Component {
className='btn btn-default'
onClick={this.doCancel}
>
- {'Close'}
+ <FormattedMessage
+ id='admin.select_team.close'
+ defaultMessage='Close'
+ />
</button>
<button
onClick={this.doSubmit}
@@ -78,7 +88,10 @@ export default class SelectTeamModal extends React.Component {
className='btn btn-primary'
tabIndex='2'
>
- {'Select'}
+ <FormattedMessage
+ id='admin.select_team.select'
+ defaultMessage='Select'
+ />
</button>
</Modal.Footer>
</form>
@@ -96,4 +109,4 @@ SelectTeamModal.propTypes = {
show: React.PropTypes.bool.isRequired,
onModalSubmit: React.PropTypes.func,
onModalDismissed: React.PropTypes.func
-};
+}; \ No newline at end of file
diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx
index e93545c25..f04185b46 100644
--- a/web/react/components/error_bar.jsx
+++ b/web/react/components/error_bar.jsx
@@ -3,6 +3,16 @@
import ErrorStore from '../stores/error_store.jsx';
+// import mm-intl is required for the tool to be able to extract the messages
+import {defineMessages} from 'mm-intl';
+
+var messages = defineMessages({
+ preview: {
+ id: 'error_bar.preview_mode',
+ defaultMessage: 'Preview Mode: Email notifications have not been configured'
+ }
+});
+
export default class ErrorBar extends React.Component {
constructor() {
super();
@@ -49,12 +59,7 @@ export default class ErrorBar extends React.Component {
componentWillMount() {
if (global.window.mm_config.SendEmailNotifications === 'false') {
- ErrorStore.storeLastError({message: this.props.intl.formatMessage(
- {
- id: 'error_bar.preview_mode',
- defaultMessage: 'Preview Mode: Email notifications have not been configured'
- }
- )});
+ ErrorStore.storeLastError({message: this.props.intl.formatMessage(messages.preview)});
this.onErrorChange();
}
}
@@ -82,6 +87,7 @@ export default class ErrorBar extends React.Component {
e.preventDefault();
}
+ ErrorStore.clearLastError();
this.setState({message: null});
}
diff --git a/web/react/components/loading_screen.jsx b/web/react/components/loading_screen.jsx
index 9849205f2..143b94467 100644
--- a/web/react/components/loading_screen.jsx
+++ b/web/react/components/loading_screen.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import {FormattedMessage} from 'mm-intl';
+
export default class LoadingScreen extends React.Component {
constructor(props) {
super(props);
@@ -13,7 +15,12 @@ export default class LoadingScreen extends React.Component {
style={{position: this.props.position}}
>
<div className='loading__content'>
- <h3>Loading</h3>
+ <h3>
+ <FormattedMessage
+ id='loading_screen.loading'
+ defaultMessage='Loading'
+ />
+ </h3>
<div className='round round-1'></div>
<div className='round round-2'></div>
<div className='round round-3'></div>
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index 6887489a7..3c1d66334 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -115,7 +115,7 @@ export default class Login extends React.Component {
}
let teamSignUp = null;
- if (global.window.mm_config.EnableTeamCreation === 'true') {
+ if (global.window.mm_config.EnableTeamCreation === 'true' && !Utils.isMobileApp()) {
teamSignUp = (
<div className='margin--extra'>
<a
@@ -137,6 +137,21 @@ export default class Login extends React.Component {
);
}
+ let findTeams = null;
+ if (!Utils.isMobileApp()) {
+ findTeams = (
+ <div className='form-group margin--extra form-group--small'>
+ <span>
+ <a href='/find_team'>
+ <FormattedMessage
+ id='login.find_teams'
+ defaultMessage='Find your other teams'
+ />
+ </a></span>
+ </div>
+ );
+ }
+
return (
<div className='signup-team__container'>
<h5 className='margin--less'>{'Sign in to:'}</h5>
@@ -147,15 +162,7 @@ export default class Login extends React.Component {
{emailSignup}
{ldapLogin}
{userSignUp}
- <div className='form-group margin--extra form-group--small'>
- <span>
- <a href='/find_team'>
- <FormattedMessage
- id='login.find_teams'
- defaultMessage='Find your other teams'
- />
- </a></span>
- </div>
+ {findTeams}
{forgotPassword}
{teamSignUp}
</div>
diff --git a/web/react/components/msg_typing.jsx b/web/react/components/msg_typing.jsx
index 78b67a216..35a832875 100644
--- a/web/react/components/msg_typing.jsx
+++ b/web/react/components/msg_typing.jsx
@@ -25,9 +25,17 @@ export default class MsgTyping extends React.Component {
SocketStore.addChangeListener(this.onChange);
}
- componentWillReceiveProps(newProps) {
- if (this.props.channelId !== newProps.channelId) {
- this.updateTypingText();
+ componentWillReceiveProps(nextProps) {
+ if (this.props.channelId !== nextProps.channelId) {
+ for (const u in this.typingUsers) {
+ if (!this.typingUsers.hasOwnProperty(u)) {
+ continue;
+ }
+
+ clearTimeout(this.typingUsers[u]);
+ }
+ this.typingUsers = {};
+ this.setState({text: ''});
}
}
diff --git a/web/react/package.json b/web/react/package.json
index 14b16b4e4..f36e55b40 100644
--- a/web/react/package.json
+++ b/web/react/package.json
@@ -8,6 +8,7 @@
"highlight.js": "8.9.1",
"keymirror": "0.1.1",
"marked": "mattermost/marked#cb85e5cc81bc7937dbb73c3c53d9532b1b97e3ca",
+ "mm-intl": "mattermost/mm-intl#805442fd474fa40cd586ddeda404dbbe8e60626d",
"object-assign": "4.0.1",
"twemoji": "1.4.1"
},
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index 1e28dab8b..bfb95e1fc 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -18,14 +18,8 @@ import RegisterAppModal from '../components/register_app_modal.jsx';
import ImportThemeModal from '../components/user_settings/import_theme_modal.jsx';
import InviteMemberModal from '../components/invite_member_modal.jsx';
-import PreferenceStore from '../stores/preference_store.jsx';
-
-import * as Utils from '../utils/utils.jsx';
-import * as AsyncClient from '../utils/async_client.jsx';
import * as EventHelpers from '../dispatcher/event_helpers.jsx';
-import Constants from '../utils/constants.jsx';
-
var IntlProvider = ReactIntl.IntlProvider;
class Root extends React.Component {
@@ -92,12 +86,6 @@ class Root extends React.Component {
}
}
-function onPreferenceChange() {
- const selectedFont = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', Constants.DEFAULT_FONT);
- Utils.applyFont(selectedFont);
- PreferenceStore.removeChangeListener(onPreferenceChange);
-}
-
global.window.setup_channel_page = function setup(props, team, channel) {
if (props.PostId === '') {
EventHelpers.emitChannelClickEvent(channel);
@@ -105,11 +93,8 @@ global.window.setup_channel_page = function setup(props, team, channel) {
EventHelpers.emitPostFocusEvent(props.PostId);
}
- PreferenceStore.addChangeListener(onPreferenceChange);
- AsyncClient.getAllPreferences();
-
ReactDOM.render(
<Root map={props} />,
document.getElementById('channel_view')
);
-}; \ No newline at end of file
+};
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index 93d996e0b..2337a573d 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -36,7 +36,7 @@ class ChannelStoreClass extends EventEmitter {
this.get = this.get.bind(this);
this.getMember = this.getMember.bind(this);
this.getByName = this.getByName.bind(this);
- this.pSetPostMode = this.pSetPostMode.bind(this);
+ this.setPostMode = this.setPostMode.bind(this);
this.getPostMode = this.getPostMode.bind(this);
this.setUnreadCount = this.setUnreadCount.bind(this);
this.setUnreadCounts = this.setUnreadCounts.bind(this);
@@ -95,7 +95,7 @@ class ChannelStoreClass extends EventEmitter {
this.removeListener(LEAVE_EVENT, callback);
}
findFirstBy(field, value) {
- var channels = this.pGetChannels();
+ var channels = this.getChannels();
for (var i = 0; i < channels.length; i++) {
if (channels[i][field] === value) {
return channels[i];
@@ -114,13 +114,13 @@ class ChannelStoreClass extends EventEmitter {
return this.findFirstBy('name', name);
}
getAll() {
- return this.pGetChannels();
+ return this.getChannels();
}
getAllMembers() {
- return this.pGetChannelMembers();
+ return this.getChannelMembers();
}
getMoreAll() {
- return this.pGetMoreChannels();
+ return this.getMoreChannels();
}
setCurrentId(id) {
this.currentId = id;
@@ -161,9 +161,9 @@ class ChannelStoreClass extends EventEmitter {
return null;
}
setChannelMember(member) {
- var members = this.pGetChannelMembers();
+ var members = this.getChannelMembers();
members[member.channel_id] = member;
- this.pStoreChannelMembers(members);
+ this.storeChannelMembers(members);
this.emitChange();
}
getCurrentExtraInfo() {
@@ -173,7 +173,7 @@ class ChannelStoreClass extends EventEmitter {
var extra = null;
if (channelId) {
- extra = this.pGetExtraInfos()[channelId];
+ extra = this.getExtraInfos()[channelId];
}
if (extra) {
@@ -186,7 +186,7 @@ class ChannelStoreClass extends EventEmitter {
return extra;
}
pStoreChannel(channel) {
- var channels = this.pGetChannels();
+ var channels = this.getChannels();
var found;
for (var i = 0; i < channels.length; i++) {
@@ -206,42 +206,42 @@ class ChannelStoreClass extends EventEmitter {
}
channels.sort(Utils.sortByDisplayName);
- this.pStoreChannels(channels);
+ this.storeChannels(channels);
}
- pStoreChannels(channels) {
+ storeChannels(channels) {
this.channels = channels;
}
- pGetChannels() {
+ getChannels() {
return this.channels;
}
pStoreChannelMember(channelMember) {
- var members = this.pGetChannelMembers();
+ var members = this.getChannelMembers();
members[channelMember.channel_id] = channelMember;
- this.pStoreChannelMembers(members);
+ this.storeChannelMembers(members);
}
- pStoreChannelMembers(channelMembers) {
+ storeChannelMembers(channelMembers) {
this.channelMembers = channelMembers;
}
- pGetChannelMembers() {
+ getChannelMembers() {
return this.channelMembers;
}
- pStoreMoreChannels(channels) {
+ storeMoreChannels(channels) {
this.moreChannels = channels;
}
- pGetMoreChannels() {
+ getMoreChannels() {
return this.moreChannels;
}
- pStoreExtraInfos(extraInfos) {
+ storeExtraInfos(extraInfos) {
this.extraInfos = extraInfos;
}
- pGetExtraInfos() {
+ getExtraInfos() {
return this.extraInfos;
}
isDefault(channel) {
return channel.name === Constants.DEFAULT_CHANNEL;
}
- pSetPostMode(mode) {
+ setPostMode(mode) {
this.postMode = mode;
}
@@ -292,21 +292,21 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
case ActionTypes.CLICK_CHANNEL:
ChannelStore.setCurrentId(action.id);
ChannelStore.resetCounts(action.id);
- ChannelStore.pSetPostMode(ChannelStore.POST_MODE_CHANNEL);
+ ChannelStore.setPostMode(ChannelStore.POST_MODE_CHANNEL);
ChannelStore.emitChange();
break;
case ActionTypes.RECIEVED_FOCUSED_POST: {
const post = action.post_list.posts[action.postId];
ChannelStore.setCurrentId(post.channel_id);
- ChannelStore.pSetPostMode(ChannelStore.POST_MODE_FOCUS);
+ ChannelStore.setPostMode(ChannelStore.POST_MODE_FOCUS);
ChannelStore.emitChange();
break;
}
case ActionTypes.RECIEVED_CHANNELS:
- ChannelStore.pStoreChannels(action.channels);
- ChannelStore.pStoreChannelMembers(action.members);
+ ChannelStore.storeChannels(action.channels);
+ ChannelStore.storeChannelMembers(action.members);
currentId = ChannelStore.getCurrentId();
if (currentId) {
ChannelStore.resetCounts(currentId);
@@ -329,14 +329,14 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
break;
case ActionTypes.RECIEVED_MORE_CHANNELS:
- ChannelStore.pStoreMoreChannels(action.channels);
+ ChannelStore.storeMoreChannels(action.channels);
ChannelStore.emitMoreChange();
break;
case ActionTypes.RECIEVED_CHANNEL_EXTRA_INFO:
- var extraInfos = ChannelStore.pGetExtraInfos();
+ var extraInfos = ChannelStore.getExtraInfos();
extraInfos[action.extra_info.id] = action.extra_info;
- ChannelStore.pStoreExtraInfos(extraInfos);
+ ChannelStore.storeExtraInfos(extraInfos);
ChannelStore.emitExtraInfoChange();
break;
diff --git a/web/react/stores/error_store.jsx b/web/react/stores/error_store.jsx
index 69d6cca7f..ed46d6b68 100644
--- a/web/react/stores/error_store.jsx
+++ b/web/react/stores/error_store.jsx
@@ -46,6 +46,10 @@ class ErrorStoreClass extends EventEmitter {
storeLastError(error) {
BrowserStore.setItem('last_error', error);
}
+
+ clearLastError() {
+ BrowserStore.removeItem('last_error');
+ }
}
var ErrorStore = new ErrorStoreClass();
diff --git a/web/react/stores/preference_store.jsx b/web/react/stores/preference_store.jsx
index 79eab4fe1..7ecaf0a95 100644
--- a/web/react/stores/preference_store.jsx
+++ b/web/react/stores/preference_store.jsx
@@ -133,6 +133,16 @@ class PreferenceStoreClass extends EventEmitter {
return preference;
}
+ setPreferences(newPreferences) {
+ const preferences = this.getAllPreferences();
+
+ for (const preference of newPreferences) {
+ preferences.set(getPreferenceKeyForModel(preference), preference);
+ }
+
+ this.setAllPreferences(preferences);
+ }
+
emitChange() {
this.emit(CHANGE_EVENT);
}
@@ -155,18 +165,11 @@ class PreferenceStoreClass extends EventEmitter {
this.emitChange();
break;
}
- case ActionTypes.RECIEVED_PREFERENCES: {
- const preferences = this.getAllPreferences();
-
- for (const preference of action.preferences) {
- preferences.set(getPreferenceKeyForModel(preference), preference);
- }
-
- this.setAllPreferences(preferences);
+ case ActionTypes.RECIEVED_PREFERENCES:
+ this.setPreferences(action.preferences);
this.emitChange();
break;
}
- }
}
}
diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json
index cb955ee6b..56744ddac 100644
--- a/web/static/i18n/en.json
+++ b/web/static/i18n/en.json
@@ -1,5 +1,87 @@
{
- "login.find_teams": "Find your other teams",
- "login.forgot_password": "I forgot my password",
- "error_bar.preview_mode": "Preview Mode: Email notifications have not been configured"
+ "admin.nav.switch": "Switch to {display_name}",
+ "admin.nav.logout": "Logout",
+ "admin.nav.help": "Help",
+ "admin.nav.report": "Report a Problem",
+ "admin.sidebarHeader.systemConsole": "System Console",
+ "admin.sidebar.loading": "Loading",
+ "admin.sidebar.rmTeamSidebar": "Remove team from sidebar menu",
+ "admin.sidebar.addTeamSidebar": "Add team from sidebar menu",
+ "admin.sidebar.users": "- Users",
+ "admin.sidebar.statistics": "- Statistics",
+ "admin.sidebar.ldap": "LDAP Settings",
+ "admin.sidebar.license": "Edition and License",
+ "admin.sidebar.reports": "SITE REPORTS",
+ "admin.sidebar.view_statistics": "View Statistics",
+ "admin.sidebar.settings": "SETTINGS",
+ "admin.sidebar.service": "Service Settings",
+ "admin.sidebar.team": "Team Settings",
+ "admin.sidebar.sql": "SQL Settings",
+ "admin.sidebar.email": "Email Settings",
+ "admin.sidebar.file": "File Settings",
+ "admin.sidebar.log": "Log Settings",
+ "admin.sidebar.rate_limit": "Rate Limit Settings",
+ "admin.sidebar.privacy": "Privacy Settings",
+ "admin.sidebar.gitlab": "GitLab Settings",
+ "admin.sidebar.support": "Legal and Support Settings",
+ "admin.sidebar.teams": "TEAMS ({count})",
+ "admin.sidebar.other": "OTHER",
+ "admin.sidebar.logs": "Logs",
+ "admin.email.notificationDisplayExample": "Ex: \"Mattermost Notification\", \"System\", \"No-Reply\"",
+ "admin.email.notificationEmailExample": "Ex: \"mattermost@yourcompany.com\", \"admin@yourcompany.com\"",
+ "admin.email.smtpUsernameExample": "Ex: \"admin@yourcompany.com\", \"AKIADTOVBGERKLCBV\"",
+ "admin.email.smtpPasswordExample": "Ex: \"yourpassword\", \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
+ "admin.email.smtpServerExample": "Ex: \"smtp.yourcompany.com\", \"email-smtp.us-east-1.amazonaws.com\"",
+ "admin.email.smtpPortExample": "Ex: \"25\", \"465\"",
+ "admin.email.connectionSecurityNone": "None",
+ "admin.email.connectionSecurityTls": "TLS (Recommended)",
+ "admin.email.connectionSecurityStart": "STARTTLS",
+ "admin.email.inviteSaltExample": "Ex \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"",
+ "admin.email.passwordSaltExample": "Ex \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"",
+ "admin.email.pushServerEx": "E.g.: \"https://push-test.mattermost.com\"",
+ "admin.email.testing": "Testing...",
+ "admin.email.saving": "Saving Config...",
+ "admin.email.emailSuccess": "No errors were reported while sending an email. Please check your inbox to make sure.",
+ "admin.email.emailFail": "Connection unsuccessful: {error}",
+ "admin.email.emailSettings": "Email Settings",
+ "admin.email.allowSignupTitle": "Allow Sign Up With Email: ",
+ "admin.email.true": "true",
+ "admin.email.false": "false",
+ "admin.email.allowSignupDescription": "When true, Mattermost allows team creation and account signup using email and password. This value should be false only when you want to limit signup to a single-sign-on service like OAuth or LDAP.",
+ "admin.email.notificationsTitle": "Send Email Notifications: ",
+ "admin.email.notificationsDescription": "Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.<br />Setting this to true removes the Preview Mode banner (requires logging out and logging back in after setting is changed).",
+ "admin.email.requireVerificationTitle": "Require Email Verification: ",
+ "admin.email.requireVerificationDescription": "Typically set to true in production. When true, Mattermost requires email verification after account creation prior to allowing login. Developers may set this field to false so skip sending verification emails for faster development.",
+ "admin.email.notificationDisplayTitle": "Notification Display Name:",
+ "admin.email.notificationDisplayDescription": "Display name on email account used when sending notification emails from Mattermost.",
+ "admin.email.notificationEmailTitle": "Notification Email Address:",
+ "admin.email.notificationEmailDescription": "Email address displayed on email account used when sending notification emails from Mattermost.",
+ "admin.email.smtpUsernameTitle": "SMTP Username:",
+ "admin.email.smtpUsernameDescription": " Obtain this credential from administrator setting up your email server.",
+ "admin.email.smtpPasswordTitle": "SMTP Password:",
+ "admin.email.smtpPasswordDescription": " Obtain this credential from administrator setting up your email server.",
+ "admin.email.smtpServerTitle": "SMTP Server:",
+ "admin.email.smtpServerDescription": "Location of SMTP email server.",
+ "admin.email.smtpPortTitle": "SMTP Port:",
+ "admin.email.smtpPortDescription": "Port of SMTP email server.",
+ "admin.email.connectionSecurityTitle": "Connection Security:",
+ "admin.email.connectionSecurityNoneDescription": "Mattermost will send email over an unsecure connection.",
+ "admin.email.connectionSecurityTlsDescription": "Encrypts the communication between Mattermost and your email server.",
+ "admin.email.connectionSecurityStartDescription": "Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.",
+ "admin.email.connectionSecurityTest": "Test Connection",
+ "admin.email.inviteSaltTitle": "Invite Salt:",
+ "admin.email.inviteSaltDescription": "32-character salt added to signing of email invites. Randomly generated on install. Click \"Re-Generate\" to create new salt.",
+ "admin.email.regenerate": "Re-Generate",
+ "admin.email.passwordSaltTitle": "Password Reset Salt:",
+ "admin.email.passwordSaltDescription": "32-character salt added to signing of password reset emails. Randomly generated on install. Click \"Re-Generate\" to create new salt.",
+ "admin.email.pushTitle": "Send Push Notifications: ",
+ "admin.email.pushDesc": "Typically set to true in production. When true, Mattermost attempts to send iOS and Android push notifications through the push notification server.",
+ "admin.email.pushServerTitle": "Push Notification Server:",
+ "admin.email.pushServerDesc": "Location of Mattermost push notification service you can set up behind your firewall using https://github.com/mattermost/push-proxy. For testing you can use https://push-test.mattermost.com, which connects to the sample Mattermost iOS app in the public Apple AppStore. Please do not use test service for production deployments.",
+ "admin.email.save": "Save",
+ "admin.select_team.selectTeam": "Select Team",
+ "admin.select_team.close": "Close",
+ "admin.select_team.select": "Select",
+ "error_bar.preview_mode": "Preview Mode: Email notifications have not been configured",
+ "loading_screen.loading": "Loading"
} \ No newline at end of file
diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json
index ade5f585e..bc53a78b0 100644
--- a/web/static/i18n/es.json
+++ b/web/static/i18n/es.json
@@ -1,5 +1,87 @@
{
- "login.find_teams": "Find your other teams (spanish!)",
- "login.forgot_password": "I forgot my password (spanish!)",
- "error_bar.preview_mode": "Preview Mode: Email notifications have not been configured (spanish!)"
+ "admin.email.allowSignupDescription": "Cuando está en verdadero, Mattermost permite la creación de equipos y cuentas utilizando el correo electrónico y contraseña. Este valor debe estar en falso sólo cuando quieres limitar el inicio de sesión a través de servicios tipo OAuth o LDAP.",
+ "admin.email.allowSignupTitle": "Permitir inicio de sesión con correo:",
+ "admin.email.connectionSecurityNone": "Ninguno",
+ "admin.email.connectionSecurityNoneDescription": "Mattermost enviará los correos electrónicos sobre conexiones no seguras.",
+ "admin.email.connectionSecurityStart": "STARTTLS",
+ "admin.email.connectionSecurityStartDescription": "Tomar la conexión insegura e intentar actualizarla hacia una conexión segura utilizando TLS.",
+ "admin.email.connectionSecurityTest": "Prueba de conexión",
+ "admin.email.connectionSecurityTitle": "Seguridad de conexión:",
+ "admin.email.connectionSecurityTls": "TLS (Recomendado)",
+ "admin.email.connectionSecurityTlsDescription": "Cifra la comunicación entre Mattermost y tu servidor de correo electrónico.",
+ "admin.email.emailFail": "Conexión fallida: {error}",
+ "admin.email.emailSettings": "Configuraciones de correo",
+ "admin.email.emailSuccess": "No fueron reportados errores mientras se enviada el correo. Favor validar en tu bandeja de entrada.",
+ "admin.email.false": "falso",
+ "admin.email.inviteSaltDescription": "32-caracter salt añadido a la firma de invitación de correos. Aleatoriamente generado en la instalación. Click \"Re-Generar\" para crear nuevo salt.",
+ "admin.email.inviteSaltExample": "Ej \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"",
+ "admin.email.inviteSaltTitle": "Salt de las invitaciones:",
+ "admin.email.notificationDisplayDescription": "Muestra el nombre en la cuenta del email utilizada para enviar notificaciones por correo electrónico desde Mattermost.",
+ "admin.email.notificationDisplayExample": "Ej: \"Notificación de Mattermost\", \"Sistema\", \"No-Responder\"",
+ "admin.email.notificationDisplayTitle": "Notificación de nombre mostrado:",
+ "admin.email.notificationEmailDescription": "La dirección de correo electrónico a mostrar en la cuenta de correo utilizada cuando se envian notificaciones por correo electrónico desde Mattermost.",
+ "admin.email.notificationEmailExample": "Ej: \"mattermost@tuempresa.com\", \"admin@tuempresa.com\"",
+ "admin.email.notificationEmailTitle": "Notificación de correo electrónico:",
+ "admin.email.notificationsDescription": "Normalmente se asigna como verdadero en producción. Cuando es verdadero, Mattermost intenta enviar las notificaciones por correo electrónico. Los desarrolladores puede que quieran dejar esta opción en falso para saltarse la configuración de correos para desarrollar más rápido.\nAsignar está opción como verdadero remueve la notificación de Modo de Prueba (requiere cerrar la sesión y abrirla nuevamente para que los cambios surjan efecto).",
+ "admin.email.notificationsTitle": "Enviar notificaciones por correo electrónico: ",
+ "admin.email.passwordSaltDescription": "Un salt de 32-caracteres es añadido a la firma de correos para restablecer la contraseña. Aleatoriamente generado en la instalación. Pincha \"Regenerar\" para crear un nuevo salt.",
+ "admin.email.passwordSaltExample": "Ej \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"",
+ "admin.email.passwordSaltTitle": "Resetear el Salt para las contraseñas:",
+ "admin.email.pushDesc": "Normalmente se asigna como verdadero en producción. Cuando está en verdadero, Mattermost intenta enviar notificaciones a dispositivos iOS y Android a través del servidor de notificaciones.",
+ "admin.email.pushServerDesc": "Ubicación del servicio de notificaciones push de Mattermost, puedes ubicarlo detras de un cortafuego utilizando https://github.com/mattermost/push-proxy. Para realizar pruebas puedes utilizar https://push-test.mattermost.com, el cual conecta con la ap de ejemplo de Mattermost en iOS publicada en el Apple AppStore. Por favor no utilices este servicio en producción.",
+ "admin.email.pushServerEx": "Ej.: \"https://push-test.mattermost.com\"",
+ "admin.email.pushServerTitle": "Servidor de Notificaciones:",
+ "admin.email.pushTitle": "Envío de Notificaciones: ",
+ "admin.email.regenerate": "Regenerar",
+ "admin.email.requireVerificationDescription": "Normalmente asignado como verdadero en producción. Cuando es verdadero, Mattermost requiere una verificación del correo electrónico después de crear la cuenta y antes de iniciar sesión por primera vez. Los desarrolladores pude que quieran dejar esta opción en falso para evitar la necesidad de verificar correos y así desarrollar más rápido.",
+ "admin.email.requireVerificationTitle": "Require verificación de correo electrónico: ",
+ "admin.email.save": "Guardar",
+ "admin.email.saving": "Guardando...",
+ "admin.email.smtpPasswordDescription": " Obten esta credencial del administrador del servidor de correos.",
+ "admin.email.smtpPasswordExample": "Ej: \"tucontraseña\", \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
+ "admin.email.smtpPasswordTitle": "Contraseña del SMTP:",
+ "admin.email.smtpPortDescription": "Puerto de servidor SMTP",
+ "admin.email.smtpPortExample": "Ej: \"25\", \"465\"",
+ "admin.email.smtpPortTitle": "Puerto del SMTP:",
+ "admin.email.smtpServerDescription": "Ubicación de SMTP.",
+ "admin.email.smtpServerExample": "Ej: \"smtp.tuempresa.com\", \"email-smtp.us-east-1.amazonaws.com\"",
+ "admin.email.smtpServerTitle": "Servidor SMTP:",
+ "admin.email.smtpUsernameDescription": " Obten esta credencial del administrado del servidor de correos.",
+ "admin.email.smtpUsernameExample": "Ej: \"admin@tuempresa.com\", \"AKIADTOVBGERKLCBV\"",
+ "admin.email.smtpUsernameTitle": "Usuario SMTP:",
+ "admin.email.testing": "Probando...",
+ "admin.email.true": "verdadero",
+ "admin.nav.help": "Ayuda",
+ "admin.nav.logout": "Cerrar sesión",
+ "admin.nav.report": "Reportar problema",
+ "admin.nav.switch": "Cambiar a {display_name}",
+ "admin.select_team.close": "Cerrar",
+ "admin.select_team.select": "Seleccionar",
+ "admin.select_team.selectTeam": "Seleccionar grupo",
+ "admin.sidebar.addTeamSidebar": "Agregar un equipo el menú lateral",
+ "admin.sidebar.email": "Configuración de correo",
+ "admin.sidebar.file": "Configuracion de archivos",
+ "admin.sidebar.gitlab": "Configuración de GitLab",
+ "admin.sidebar.ldap": "Configuración LDAP",
+ "admin.sidebar.license": "Edición y Licencia",
+ "admin.sidebar.loading": "Cargando",
+ "admin.sidebar.log": "Configuracion de log",
+ "admin.sidebar.logs": "Registros",
+ "admin.sidebar.other": "OTROS",
+ "admin.sidebar.privacy": "Configuración de privacidad",
+ "admin.sidebar.rate_limit": "Configuración de velocidad",
+ "admin.sidebar.reports": "REPORTES DEL SITIO",
+ "admin.sidebar.rmTeamSidebar": "Remover un equipo del menú lateral",
+ "admin.sidebar.service": "Configuración de servicio",
+ "admin.sidebar.settings": "CONFIGURACIONES",
+ "admin.sidebar.sql": "Configuración de SQL",
+ "admin.sidebar.statistics": "- Estadísticas",
+ "admin.sidebar.support": "Configuración de Soporte",
+ "admin.sidebar.team": "Configuración de equipo",
+ "admin.sidebar.teams": "EQUIPOS ({count})",
+ "admin.sidebar.users": "- Usuarios",
+ "admin.sidebar.view_statistics": "Ver Estadísticas",
+ "admin.sidebarHeader.systemConsole": "Consola de sistema",
+ "error_bar.preview_mode": "Modo de prueba: Las notificaciones por correo electrónico no han sido configuradas",
+ "loading_screen.loading": "Cargando"
} \ No newline at end of file
diff --git a/web/templates/channel.html b/web/templates/channel.html
index dcc50115b..94d79a022 100644
--- a/web/templates/channel.html
+++ b/web/templates/channel.html
@@ -6,7 +6,7 @@
<body>
<div id="channel_view" class='channel-view'></div>
<script>
-window.setup_channel_page({{ .Props }}, {{ .Team }}, {{ .Channel }}, {{ .User }});
+ window.setup_channel_page({{ .Props }}, {{ .Team }}, {{ .Channel }});
$('body').tooltip( {selector: '[data-toggle=tooltip]'} );
var modals = $('.modal-body').not('.edit-modal-body');
if($(window).height() > 1200){
diff --git a/web/templates/claim_account.html b/web/templates/claim_account.html
index 6c9f36fa7..bcf63fd95 100644
--- a/web/templates/claim_account.html
+++ b/web/templates/claim_account.html
@@ -5,8 +5,22 @@
<body class="white">
<div class="container-fluid">
<div class="inner__wrap">
- <div class="row content" id="claim"></div>
- </div>
+ <div class="row content">
+ <div class="signup-header">
+ <a href="/{{.Props.TeamName}}">{{.Props.TeamDisplayName}}</a>
+ </div>
+ <div class="col-sm-12">
+ <div class="signup-team__container">
+ <img class="signup-team-logo" src="/static/images/logo.png" />
+ <div id="claim"></div>
+ </div>
+ </div>
+ <div class="footer-push"></div>
+ </div>
+ <div class="row footer">
+ {{template "footer" . }}
+ </div>
+ <div>
</div>
<script>
window.setup_claim_account_page({{ .Props }});
diff --git a/web/templates/docs.html b/web/templates/docs.html
index 0e0f51648..1a20580fb 100644
--- a/web/templates/docs.html
+++ b/web/templates/docs.html
@@ -6,6 +6,9 @@
<div class="container-fluid">
<div class="inner__wrap">
<div class="row content">
+ <div class="signup-header">
+ <a href="/">{{ .ClientCfg.SiteName }}</a>
+ </div>
<div class="col-sm-12">
<div class="docs__page" id="docs"></div>
</div>
diff --git a/web/templates/find_team.html b/web/templates/find_team.html
index 9acf3ac64..d32ea0dc8 100644
--- a/web/templates/find_team.html
+++ b/web/templates/find_team.html
@@ -6,6 +6,9 @@
<div class="container-fluid">
<div class="inner__wrap">
<div class="row content">
+ <div class="signup-header">
+ <a href="/">{{ .ClientCfg.SiteName }}</a>
+ </div>
<div class="col-sm-12">
<div class="signup-team__container">
<img class="signup-team-logo" src="/static/images/logo.png" />
diff --git a/web/templates/head.html b/web/templates/head.html
index fc16eb2dc..b1ec905b5 100644
--- a/web/templates/head.html
+++ b/web/templates/head.html
@@ -74,6 +74,13 @@
window.mm_user = {{ .User }};
window.mm_channel = {{ .Channel }};
window.mm_locale = {{ .Locale }};
+ window.mm_preferences = {{ .Preferences }};
+
+ $(function() {
+ if (window.mm_preferences != null) {
+ PreferenceStore.setPreferences(window.mm_preferences);
+ }
+ });
if ({{.SessionTokenIndex}} >= 0) {
window.mm_session_token_index = {{.SessionTokenIndex}};
@@ -81,13 +88,13 @@
headers: {
'X-MM-TokenIndex': mm_session_token_index,
'Accept-Language': mm_locale
- }
+ }
});
} else {
$.ajaxSetup({
headers: {
'Accept-Language': mm_locale
- }
+ }
});
}
@@ -111,7 +118,7 @@
}
console.log('detected login from a different tab');
- window.location.href = '/' + window.mm_team.name;
+ location.reload();
}
});
});
diff --git a/web/templates/login.html b/web/templates/login.html
index f6a551220..be5e6bf4f 100644
--- a/web/templates/login.html
+++ b/web/templates/login.html
@@ -7,7 +7,7 @@
<div class="inner__wrap">
<div class="row content">
<div class="signup-header">
- {{.Props.TeamDisplayName}}
+ <a href="/">{{ .ClientCfg.SiteName }}</a>
</div>
<div class="col-sm-12">
<div id="login"></div>
diff --git a/web/templates/password_reset.html b/web/templates/password_reset.html
index 7f6335c92..df82285ef 100644
--- a/web/templates/password_reset.html
+++ b/web/templates/password_reset.html
@@ -5,7 +5,21 @@
<body class="white">
<div class="container-fluid">
<div class="inner__wrap">
- <div class="row content" id="reset"></div>
+ <div class="row content">
+ <div class="signup-header">
+ <a href="/{{.Props.TeamName}}">{{.Props.TeamDisplayName}}</a>
+ </div>
+ <div class="col-sm-12">
+ <div class="signup-team__container">
+ <img class="signup-team-logo" src="/static/images/logo.png" />
+ <div id="reset"></div>
+ </div>
+ </div>
+ <div class="footer-push"></div>
+ </div>
+ <div class="row footer">
+ {{template "footer" . }}
+ </div>
</div>
</div>
<script>
diff --git a/web/templates/verify.html b/web/templates/verify.html
index a49ba7930..bab329c7d 100644
--- a/web/templates/verify.html
+++ b/web/templates/verify.html
@@ -6,7 +6,16 @@
<div class="container-fluid">
<div class="inner__wrap">
<div class="row content">
- <div id="verify"></div>
+ <div class="signup-header">
+ <a href="/{{.Props.TeamName}}">{{.Props.TeamDisplayName}}</a>
+ </div>
+ <div class="col-sm-12">
+ <div class="signup-team__container">
+ <img class="signup-team-logo" src="/static/images/logo.png" />
+ <div id="verify"></div>
+ </div>
+ </div>
+ <div class="footer-push"></div>
</div>
<div class="row footer">
{{template "footer" . }}
diff --git a/web/templates/welcome.html b/web/templates/welcome.html
deleted file mode 100644
index 15c072226..000000000
--- a/web/templates/welcome.html
+++ /dev/null
@@ -1,37 +0,0 @@
-{{define "welcome"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body>
- <div class="container-fluid">
- <div class="inner__wrap">
- <div class="row header">
- <div id="navbar"></div>
- </div>
- <div class="row main">
- <div class="app__content">
- <div class="welcome-info">
- <h1>Welcome to {{ .ClientCfg.SiteName }}!</h1>
- <p>
- You do not appear to be part of any teams. Please contact your
- administrator to have him send you an invitation to a private team.
- Or you can start a new private team.
- </p>
- <div class="alert alert-warning">
- If you where invited to a team that you do not see you must
- confirm your email address first before gaining access to the
- team.
- </div>
- <div id="new_channel">
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <script>
- window.setup_welcome_page();
- </script>
-</body>
-</html>
-{{end}}
diff --git a/web/web.go b/web/web.go
index 95d6024f5..36349dd5e 100644
--- a/web/web.go
+++ b/web/web.go
@@ -518,6 +518,7 @@ func checkSessionSwitch(c *api.Context, w http.ResponseWriter, r *http.Request,
func doLoadChannel(c *api.Context, w http.ResponseWriter, r *http.Request, team *model.Team, channel *model.Channel, postid string) {
userChan := api.Srv.Store.User().Get(c.Session.UserId)
+ prefChan := api.Srv.Store.Preference().GetAll(c.Session.UserId)
var user *model.User
if ur := <-userChan; ur.Err != nil {
@@ -529,6 +530,13 @@ func doLoadChannel(c *api.Context, w http.ResponseWriter, r *http.Request, team
user = ur.Data.(*model.User)
}
+ var preferences model.Preferences
+ if result := <-prefChan; result.Err != nil {
+ l4g.Error("Error in getting preferences for id=%v", c.Session.UserId)
+ } else {
+ preferences = result.Data.(model.Preferences)
+ }
+
page := NewHtmlTemplatePage("channel", "", c.Locale)
page.Props["Title"] = channel.DisplayName + " - " + team.DisplayName + " " + page.ClientCfg["SiteName"]
page.Props["TeamDisplayName"] = team.DisplayName
@@ -538,6 +546,7 @@ func doLoadChannel(c *api.Context, w http.ResponseWriter, r *http.Request, team
page.Team = team
page.User = user
page.Channel = channel
+ page.Preferences = &preferences
page.Render(c, w)
}