summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md4
-rw-r--r--api/admin_test.go14
-rw-r--r--api/channel_test.go56
-rw-r--r--api/command_test.go12
-rw-r--r--api/file_test.go10
-rw-r--r--api/oauth_test.go4
-rw-r--r--api/post.go116
-rw-r--r--api/post_test.go84
-rw-r--r--api/preference.go2
-rw-r--r--api/preference_test.go16
-rw-r--r--api/team.go1
-rw-r--r--api/team_test.go24
-rw-r--r--api/templates/email_change_body.html16
-rw-r--r--api/templates/email_change_verify_body.html16
-rw-r--r--api/templates/email_footer.html13
-rw-r--r--api/templates/email_info.html9
-rw-r--r--api/templates/find_teams_body.html16
-rw-r--r--api/templates/invite_body.html16
-rw-r--r--api/templates/password_change_body.html16
-rw-r--r--api/templates/post_body.html18
-rw-r--r--api/templates/reset_body.html16
-rw-r--r--api/templates/signup_team_body.html16
-rw-r--r--api/templates/verify_body.html16
-rw-r--r--api/templates/welcome_body.html10
-rw-r--r--api/user_test.go50
-rw-r--r--api/web_socket_test.go4
-rw-r--r--api/web_team_hub.go4
-rw-r--r--api/webhook_test.go14
-rw-r--r--config/config.json8
-rw-r--r--doc/developer/tests/test-syntax-highlighting.md2
-rw-r--r--doc/help/Markdown.md2
-rw-r--r--doc/help/Messaging.md34
-rw-r--r--doc/help/Sign-in.md2
-rw-r--r--doc/install/Production-Debian.md3
-rw-r--r--doc/install/Production-Ubuntu.md3
-rw-r--r--doc/process/accepting-pull-request.md28
-rw-r--r--doc/process/release-process.md141
-rw-r--r--docker/dev/config_docker.json8
-rw-r--r--docker/local/config_docker.json8
-rw-r--r--model/client.go2
-rw-r--r--model/config.go56
-rw-r--r--model/message.go17
-rw-r--r--model/outgoing_webhook.go2
-rw-r--r--model/outgoing_webhook_test.go2
-rw-r--r--model/preference.go2
-rw-r--r--model/preference_test.go2
-rw-r--r--model/preferences.go2
-rw-r--r--model/team_test.go2
-rw-r--r--model/utils.go12
-rw-r--r--model/utils_test.go17
-rw-r--r--store/sql_post_store.go2
-rw-r--r--store/sql_preference_store.go2
-rw-r--r--store/sql_preference_store_test.go2
-rw-r--r--utils/config.go8
-rw-r--r--web/react/components/admin_console/admin_controller.jsx3
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx11
-rw-r--r--web/react/components/admin_console/legal_and_support_settings.jsx222
-rw-r--r--web/react/components/admin_console/service_settings.jsx2
-rw-r--r--web/react/components/edit_channel_header_modal.jsx52
-rw-r--r--web/react/components/invite_member_modal.jsx19
-rw-r--r--web/react/components/more_direct_channels.jsx28
-rw-r--r--web/react/components/navbar_dropdown.jsx46
-rw-r--r--web/react/components/sidebar.jsx50
-rw-r--r--web/react/components/sidebar_right_menu.jsx43
-rw-r--r--web/react/components/team_general_tab.jsx133
-rw-r--r--web/react/components/tutorial/tutorial_intro_screens.jsx25
-rw-r--r--web/react/components/user_settings/custom_theme_chooser.jsx22
-rw-r--r--web/react/components/user_settings/manage_outgoing_hooks.jsx4
-rw-r--r--web/react/components/user_settings/user_settings_general.jsx15
-rw-r--r--web/react/dispatcher/event_helpers.jsx7
-rw-r--r--web/react/stores/channel_store.jsx4
-rw-r--r--web/react/stores/preference_store.jsx14
-rw-r--r--web/react/stores/socket_store.jsx9
-rw-r--r--web/react/utils/client.jsx7
-rw-r--r--web/react/utils/constants.jsx50
-rw-r--r--web/react/utils/utils.jsx5
-rw-r--r--web/sass-files/sass/partials/_docs.scss19
-rw-r--r--web/sass-files/sass/partials/_modal.scss7
-rw-r--r--web/sass-files/sass/partials/_popover.scss4
-rw-r--r--web/sass-files/sass/styles.scss3
-rw-r--r--web/static/images/themes/code_themes/githubLarge.pngbin0 -> 83246 bytes
-rw-r--r--web/static/images/themes/code_themes/monokaiLarge.pngbin0 -> 82658 bytes
-rw-r--r--web/static/images/themes/code_themes/solarized_darkLarge.pngbin0 -> 81942 bytes
-rw-r--r--web/static/images/themes/code_themes/solarized_lightLarge.pngbin0 -> 82868 bytes
-rw-r--r--web/templates/docs.html2
-rw-r--r--web/templates/footer.html27
-rw-r--r--web/web.go2
-rw-r--r--web/web_test.go4
88 files changed, 1213 insertions, 588 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd620490b..0bbb2be93 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -139,7 +139,7 @@ To limit the impact of this security issue, Mattermost v1.2.0 has been removed f
#### Syntax Highlighting
-- Syntax highlight for code blocks now available for `Diff, Apache, Makefile, HTTP, JSON, Markdown, JavaScript, CSS, nginx, ObjectiveC, Python, XML, Perl, Bash, PHP, Coffee, C, SQL, Go, Ruby, Java, and ini`
+- Syntax highlight for code blocks now available for `Diff, Apache, Makefile, HTTP, JSON, Markdown, JavaScript, CSS, nginx, ObjectiveC, Python, XML, Perl, Bash, PHP, CoffeeScript, C, SQL, Go, Ruby, Java, and ini`
#### Usability Improvements
@@ -176,7 +176,7 @@ User Interface
- Member list in Channel display now scrollable, and includes Message button to message channel members directly
- Added ability to edit previous message by hitting UP arrow
- Syntax highlighting added for code blocks
- - Languages include `Diff, Apache, Makefile, HTTP, JSON, Markdown, Java, CSS, nginx, ObjectiveC, Python, XML, Perl, Bash, PHP, Coffee, C, SQL, Go, Ruby, Java, and ini`.
+ - Languages include `Diff, Apache, Makefile, HTTP, JSON, Markdown, Java, CSS, nginx, ObjectiveC, Python, XML, Perl, Bash, PHP, CoffeeScript, C, SQL, Go, Ruby, Java, and ini`.
- Use by adding the name of the language on the first link of the code block, for example: ```python
- Syntax color theme can be defined under **Account Settings** > **Appearance Settings** > **Custom Theme**
- Updated Drag & Drop UI
diff --git a/api/admin_test.go b/api/admin_test.go
index 0a1682a99..f7b6a7eeb 100644
--- a/api/admin_test.go
+++ b/api/admin_test.go
@@ -17,7 +17,7 @@ func TestGetLogs(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -61,7 +61,7 @@ func TestGetConfig(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -95,7 +95,7 @@ func TestSaveConfig(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -129,7 +129,7 @@ func TestEmailTest(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -157,7 +157,7 @@ func TestGetAnalyticsStandard(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -223,7 +223,7 @@ func TestGetPostCount(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -268,7 +268,7 @@ func TestUserCountsWithPostsByDay(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
diff --git a/api/channel_test.go b/api/channel_test.go
index e7e1f4eb0..4ef164cba 100644
--- a/api/channel_test.go
+++ b/api/channel_test.go
@@ -22,7 +22,7 @@ func TestCreateChannel(t *testing.T) {
team2 := &model.Team{DisplayName: "Name Team 2", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -99,11 +99,11 @@ func TestCreateDirectChannel(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -158,11 +158,11 @@ func TestUpdateChannel(t *testing.T) {
userTeamAdmin = Client.Must(Client.CreateUser(userTeamAdmin, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userTeamAdmin.Id))
- userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
userChannelAdmin = Client.Must(Client.CreateUser(userChannelAdmin, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userChannelAdmin.Id))
- userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
userStd = Client.Must(Client.CreateUser(userStd, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userStd.Id))
userStd.Roles = ""
@@ -236,7 +236,7 @@ func TestUpdateChannelHeader(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -276,7 +276,7 @@ func TestUpdateChannelHeader(t *testing.T) {
t.Fatal("should have errored on bad channel header")
}
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -295,7 +295,7 @@ func TestUpdateChannelPurpose(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -335,7 +335,7 @@ func TestUpdateChannelPurpose(t *testing.T) {
t.Fatal("should have errored on bad channel purpose")
}
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -354,7 +354,7 @@ func TestGetChannel(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -417,7 +417,7 @@ func TestGetMoreChannel(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -429,7 +429,7 @@ func TestGetMoreChannel(t *testing.T) {
channel2 := &model.Channel{DisplayName: "B Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -461,7 +461,7 @@ func TestGetChannelCounts(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -502,7 +502,7 @@ func TestJoinChannel(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -514,7 +514,7 @@ func TestJoinChannel(t *testing.T) {
channel3 := &model.Channel{DisplayName: "B Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -530,7 +530,7 @@ func TestJoinChannel(t *testing.T) {
data["user_id"] = user1.Id
rchannel := Client.Must(Client.CreateDirectChannel(data)).Data.(*model.Channel)
- user3 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user3 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
Client.LoginByEmail(team.Name, user3.Email, "pwd")
@@ -546,7 +546,7 @@ func TestLeaveChannel(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -558,7 +558,7 @@ func TestLeaveChannel(t *testing.T) {
channel3 := &model.Channel{DisplayName: "B Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -599,7 +599,7 @@ func TestDeleteChannel(t *testing.T) {
userTeamAdmin = Client.Must(Client.CreateUser(userTeamAdmin, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userTeamAdmin.Id))
- userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
userChannelAdmin = Client.Must(Client.CreateUser(userChannelAdmin, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userChannelAdmin.Id))
@@ -631,7 +631,7 @@ func TestDeleteChannel(t *testing.T) {
t.Fatal("should have failed to post to deleted channel")
}
- userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
userStd = Client.Must(Client.CreateUser(userStd, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userStd.Id))
@@ -665,7 +665,7 @@ func TestGetChannelExtraInfo(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -753,7 +753,7 @@ func TestAddChannelMember(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -762,7 +762,7 @@ func TestAddChannelMember(t *testing.T) {
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -815,7 +815,7 @@ func TestRemoveChannelMember(t *testing.T) {
userTeamAdmin = Client.Must(Client.CreateUser(userTeamAdmin, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userTeamAdmin.Id))
- userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
userChannelAdmin = Client.Must(Client.CreateUser(userChannelAdmin, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userChannelAdmin.Id))
@@ -831,7 +831,7 @@ func TestRemoveChannelMember(t *testing.T) {
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
- userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
userStd = Client.Must(Client.CreateUser(userStd, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userStd.Id))
@@ -881,7 +881,7 @@ func TestUpdateNotifyProps(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -983,7 +983,7 @@ func TestUpdateNotifyProps(t *testing.T) {
t.Fatal("Should have errored - bad mark unread level")
}
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
Client.LoginByEmail(team.Name, user2.Email, "pwd")
@@ -1003,7 +1003,7 @@ func TestFuzzyChannel(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
diff --git a/api/command_test.go b/api/command_test.go
index 9327850f3..f38cf1397 100644
--- a/api/command_test.go
+++ b/api/command_test.go
@@ -19,7 +19,7 @@ func TestSuggestRootCommands(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -68,7 +68,7 @@ func TestLogoutCommands(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -86,7 +86,7 @@ func TestJoinCommands(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -100,7 +100,7 @@ func TestJoinCommands(t *testing.T) {
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
Client.Must(Client.LeaveChannel(channel2.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -162,7 +162,7 @@ func TestEchoCommand(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -200,7 +200,7 @@ func TestLoadTestUrlCommand(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
diff --git a/api/file_test.go b/api/file_test.go
index b337eadc4..b5501e4bd 100644
--- a/api/file_test.go
+++ b/api/file_test.go
@@ -27,7 +27,7 @@ func TestUploadFile(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -142,7 +142,7 @@ func TestGetFile(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -213,7 +213,7 @@ func TestGetFile(t *testing.T) {
team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
- user2 := &model.User{TeamId: team2.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team2.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -321,11 +321,11 @@ func TestGetPublicLink(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
diff --git a/api/oauth_test.go b/api/oauth_test.go
index 64d9c6fd9..7d825ef5a 100644
--- a/api/oauth_test.go
+++ b/api/oauth_test.go
@@ -18,7 +18,7 @@ func TestRegisterApp(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Password: "pwd"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
@@ -75,7 +75,7 @@ func TestAllowOAuth(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Password: "pwd"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
diff --git a/api/post.go b/api/post.go
index d1b0ae980..b9bd2c052 100644
--- a/api/post.go
+++ b/api/post.go
@@ -236,9 +236,68 @@ func handlePostEventsAndForget(c *Context, post *model.Post, triggerWebhooks boo
if triggerWebhooks {
handleWebhookEventsAndForget(c, post, team, channel, user)
}
+
+ if channel.Type == model.CHANNEL_DIRECT {
+ go makeDirectChannelVisible(c.Session.TeamId, post.ChannelId)
+ }
}()
}
+func makeDirectChannelVisible(teamId string, channelId string) {
+ var members []model.ChannelMember
+ if result := <-Srv.Store.Channel().GetMembers(channelId); result.Err != nil {
+ l4g.Error("Failed to get channel members channel_id=%v err=%v", channelId, result.Err.Message)
+ return
+ } else {
+ members = result.Data.([]model.ChannelMember)
+ }
+
+ if len(members) != 2 {
+ l4g.Error("Failed to get 2 members for a direct channel channel_id=%v", channelId)
+ return
+ }
+
+ // make sure the channel is visible to both members
+ for i, member := range members {
+ otherUserId := members[1-i].UserId
+
+ if result := <-Srv.Store.Preference().Get(member.UserId, model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, otherUserId); result.Err != nil {
+ // create a new preference since one doesn't exist yet
+ preference := &model.Preference{
+ UserId: member.UserId,
+ Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW,
+ Name: otherUserId,
+ Value: "true",
+ }
+
+ if saveResult := <-Srv.Store.Preference().Save(&model.Preferences{*preference}); saveResult.Err != nil {
+ l4g.Error("Failed to save direct channel preference user_id=%v other_user_id=%v err=%v", member.UserId, otherUserId, saveResult.Err.Message)
+ } else {
+ message := model.NewMessage(teamId, channelId, member.UserId, model.ACTION_PREFERENCE_CHANGED)
+ message.Add("preference", preference.ToJson())
+
+ PublishAndForget(message)
+ }
+ } else {
+ preference := result.Data.(model.Preference)
+
+ if preference.Value != "true" {
+ // update the existing preference to make the channel visible
+ preference.Value = "true"
+
+ if updateResult := <-Srv.Store.Preference().Save(&model.Preferences{preference}); updateResult.Err != nil {
+ l4g.Error("Failed to update direct channel preference user_id=%v other_user_id=%v err=%v", member.UserId, otherUserId, updateResult.Err.Message)
+ } else {
+ message := model.NewMessage(teamId, channelId, member.UserId, model.ACTION_PREFERENCE_CHANGED)
+ message.Add("preference", preference.ToJson())
+
+ PublishAndForget(message)
+ }
+ }
+ }
+ }
+}
+
func handleWebhookEventsAndForget(c *Context, post *model.Post, team *model.Team, channel *model.Channel, user *model.User) {
go func() {
if !utils.Cfg.ServiceSettings.EnableOutgoingWebhooks {
@@ -432,35 +491,52 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team,
splitF := func(c rune) bool {
return model.SplitRunes[c]
}
- splitMessage := strings.FieldsFunc(post.Message, splitF)
+ splitMessage := strings.Fields(post.Message)
for _, word := range splitMessage {
+ var userIds []string
// Non-case-sensitive check for regular keys
- userIds1, keyMatch := keywordMap[strings.ToLower(word)]
+ if ids, match := keywordMap[strings.ToLower(word)]; match {
+ userIds = append(userIds, ids...)
+ }
// Case-sensitive check for first name
- userIds2, firstNameMatch := keywordMap[word]
+ if ids, match := keywordMap[word]; match {
+ userIds = append(userIds, ids...)
+ }
- userIds := append(userIds1, userIds2...)
+ if len(userIds) == 0 {
+ // No matches were found with the string split just on whitespace so try further splitting
+ // the message on punctuation
+ splitWords := strings.FieldsFunc(word, splitF)
- // If one of the non-case-senstive keys or the first name matches the word
- // then we add en entry to the sendEmail map
- if keyMatch || firstNameMatch {
- for _, userId := range userIds {
- if post.UserId == userId {
- continue
- }
- sendEmail := true
- if _, ok := profileMap[userId].NotifyProps["email"]; ok && profileMap[userId].NotifyProps["email"] == "false" {
- sendEmail = false
+ for _, splitWord := range splitWords {
+ // Non-case-sensitive check for regular keys
+ if ids, match := keywordMap[strings.ToLower(splitWord)]; match {
+ userIds = append(userIds, ids...)
}
- if sendEmail && (profileMap[userId].IsAway() || profileMap[userId].IsOffline()) {
- toEmailMap[userId] = true
- } else {
- toEmailMap[userId] = false
+
+ // Case-sensitive check for first name
+ if ids, match := keywordMap[splitWord]; match {
+ userIds = append(userIds, ids...)
}
}
}
+
+ for _, userId := range userIds {
+ if post.UserId == userId {
+ continue
+ }
+ sendEmail := true
+ if _, ok := profileMap[userId].NotifyProps["email"]; ok && profileMap[userId].NotifyProps["email"] == "false" {
+ sendEmail = false
+ }
+ if sendEmail && (profileMap[userId].IsAway() || profileMap[userId].IsOffline()) {
+ toEmailMap[userId] = true
+ } else {
+ toEmailMap[userId] = false
+ }
+ }
}
for id := range toEmailMap {
@@ -477,8 +553,7 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team,
teamURL := c.GetSiteURL() + "/" + team.Name
// Build and send the emails
- location, _ := time.LoadLocation("UTC")
- tm := time.Unix(post.CreateAt/1000, 0).In(location)
+ tm := time.Unix(post.CreateAt/1000, 0)
subjectPage := NewServerTemplatePage("post_subject")
subjectPage.Props["SiteURL"] = c.GetSiteURL()
@@ -510,6 +585,7 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team,
bodyPage.Props["Minute"] = fmt.Sprintf("%02d", tm.Minute())
bodyPage.Props["Month"] = tm.Month().String()[:3]
bodyPage.Props["Day"] = fmt.Sprintf("%d", tm.Day())
+ bodyPage.Props["TimeZone"], _ = tm.Zone()
bodyPage.Props["PostMessage"] = model.ClearMentionTags(post.Message)
bodyPage.Props["TeamLink"] = teamURL + "/channels/" + channel.Name
diff --git a/api/post_test.go b/api/post_test.go
index 0cb437e88..a0b8cc9bd 100644
--- a/api/post_test.go
+++ b/api/post_test.go
@@ -21,11 +21,11 @@ func TestCreatePost(t *testing.T) {
team2 := &model.Team{DisplayName: "Name Team 2", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -103,7 +103,7 @@ func TestCreatePost(t *testing.T) {
t.Fatal("Should have been forbidden")
}
- user3 := &model.User{TeamId: team2.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user3 := &model.User{TeamId: team2.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user3.Id))
@@ -132,11 +132,11 @@ func TestUpdatePost(t *testing.T) {
team2 := &model.Team{DisplayName: "Name Team 2", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -200,7 +200,7 @@ func TestGetPosts(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -265,7 +265,7 @@ func TestGetPostsSince(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -335,7 +335,7 @@ func TestGetPostsBeforeAfter(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -412,7 +412,7 @@ func TestSearchPosts(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -462,7 +462,7 @@ func TestSearchHashtagPosts(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -493,7 +493,7 @@ func TestSearchPostsInChannel(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -575,7 +575,7 @@ func TestSearchPostsFromUser(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -590,7 +590,7 @@ func TestSearchPostsFromUser(t *testing.T) {
post1 := &model.Post{ChannelId: channel1.Id, Message: "sgtitlereview with space"}
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -619,7 +619,7 @@ func TestSearchPostsFromUser(t *testing.T) {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
- user3 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user3 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user3.Id))
@@ -653,7 +653,7 @@ func TestGetPostsCache(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -706,7 +706,7 @@ func TestDeletePosts(t *testing.T) {
userAdmin = Client.Must(Client.CreateUser(userAdmin, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userAdmin.Id))
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -763,7 +763,7 @@ func TestEmailMention(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: "corey@test.com", Nickname: "Bob Bobby", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: "corey+test@test.com", Nickname: "Bob Bobby", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -785,7 +785,7 @@ func TestFuzzyPosts(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -805,3 +805,51 @@ func TestFuzzyPosts(t *testing.T) {
}
}
}
+
+func TestMakeDirectChannelVisible(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)
+
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(user1.Id))
+
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(user2.Id))
+
+ // user2 will be created with prefs created to show user1 in the sidebar so set that to false to get rid of it
+ Client.LoginByEmail(team.Name, user2.Email, "pwd")
+
+ preferences := &model.Preferences{
+ {
+ UserId: user2.Id,
+ Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW,
+ Name: user1.Id,
+ Value: "false",
+ },
+ }
+ Client.Must(Client.SetPreferences(preferences))
+
+ Client.LoginByEmail(team.Name, user1.Email, "pwd")
+
+ channel := Client.Must(Client.CreateDirectChannel(map[string]string{"user_id": user2.Id})).Data.(*model.Channel)
+
+ makeDirectChannelVisible(team.Id, channel.Id)
+
+ if result, err := Client.GetPreference(model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, user2.Id); err != nil {
+ t.Fatal("Errored trying to set direct channel to be visible for user1")
+ } else if pref := result.Data.(*model.Preference); pref.Value != "true" {
+ t.Fatal("Failed to set direct channel to be visible for user1")
+ }
+
+ Client.LoginByEmail(team.Name, user2.Email, "pwd")
+
+ if result, err := Client.GetPreference(model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, user1.Id); err != nil {
+ t.Fatal("Errored trying to set direct channel to be visible for user2")
+ } else if pref := result.Data.(*model.Preference); pref.Value != "true" {
+ t.Fatal("Failed to set direct channel to be visible for user2")
+ }
+}
diff --git a/api/preference.go b/api/preference.go
index 6d6ac1a7f..e9c74aafe 100644
--- a/api/preference.go
+++ b/api/preference.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api
diff --git a/api/preference_test.go b/api/preference_test.go
index 2f6204246..6bebe205c 100644
--- a/api/preference_test.go
+++ b/api/preference_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api
@@ -15,11 +15,11 @@ func TestGetAllPreferences(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -68,7 +68,7 @@ func TestSetPreferences(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -99,7 +99,7 @@ func TestSetPreferences(t *testing.T) {
}
// not able to update as a different user
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -116,11 +116,11 @@ func TestGetPreferenceCategory(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -172,7 +172,7 @@ func TestGetPreference(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
diff --git a/api/team.go b/api/team.go
index 2cc7106dc..0918b40e2 100644
--- a/api/team.go
+++ b/api/team.go
@@ -514,6 +514,7 @@ func InviteMembers(c *Context, team *model.Team, user *model.User, invites []str
subjectPage.Props["TeamDisplayName"] = team.DisplayName
bodyPage := NewServerTemplatePage("invite_body")
+ bodyPage.Props["SiteURL"] = c.GetSiteURL()
bodyPage.Props["TeamURL"] = c.GetTeamURL()
bodyPage.Props["TeamDisplayName"] = team.DisplayName
bodyPage.Props["SenderName"] = sender
diff --git a/api/team_test.go b/api/team_test.go
index 0b7d2ed9c..cba043bbb 100644
--- a/api/team_test.go
+++ b/api/team_test.go
@@ -25,7 +25,7 @@ func TestCreateFromSignupTeam(t *testing.T) {
Setup()
props := make(map[string]string)
- props["email"] = strings.ToLower(model.NewId()) + "corey@test.com"
+ props["email"] = strings.ToLower(model.NewId()) + "corey+test@test.com"
props["name"] = "Test Company name"
props["time"] = fmt.Sprintf("%v", model.GetMillis())
@@ -35,7 +35,7 @@ func TestCreateFromSignupTeam(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
user := model.User{Email: props["email"], Nickname: "Corey Hulen", Password: "hello"}
- ts := model.TeamSignup{Team: team, User: user, Invites: []string{"corey@test.com"}, Data: data, Hash: hash}
+ ts := model.TeamSignup{Team: team, User: user, Invites: []string{"corey+test@test.com"}, Data: data, Hash: hash}
rts, err := Client.CreateTeamFromSignup(&ts)
if err != nil {
@@ -77,7 +77,7 @@ func TestCreateTeam(t *testing.T) {
t.Fatal(err)
}
- user := &model.User{TeamId: rteam.Data.(*model.Team).Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: rteam.Data.(*model.Team).Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -114,7 +114,7 @@ func TestFindTeamByEmail(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -141,7 +141,7 @@ func TestGetAllTeams(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -174,7 +174,7 @@ func TestTeamPermDelete(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -217,7 +217,7 @@ func TestFindTeamByDomain(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -257,7 +257,7 @@ func TestFindTeamByEmailSend(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.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")
@@ -282,14 +282,14 @@ func TestInviteMembers(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.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")
invite := make(map[string]string)
- invite["email"] = model.NewId() + "corey@test.com"
+ invite["email"] = model.NewId() + "corey+test@test.com"
invite["first_name"] = "Test"
invite["last_name"] = "Guy"
invites := &model.Invites{Invites: []map[string]string{invite}}
@@ -315,7 +315,7 @@ func TestUpdateTeamDisplayName(t *testing.T) {
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -368,7 +368,7 @@ func TestGetMyTeam(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
diff --git a/api/templates/email_change_body.html b/api/templates/email_change_body.html
index df2db8730..7349aee6f 100644
--- a/api/templates/email_change_body.html
+++ b/api/templates/email_change_body.html
@@ -22,25 +22,13 @@
</td>
</tr>
<tr>
- <td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
- Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
- Best wishes,<br>
- The {{.ClientCfg.SiteName}} Team<br>
- </td>
+ {{template "email_info" . }}
</tr>
</table>
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
- <p style="margin: 25px 0;">
- <img width="65" src="{{.Props.SiteURL}}/static/images/circles.png" alt="">
- </p>
- <p style="padding: 0 50px;">
- (c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
- If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
- </p>
- </td>
+ {{template "email_footer" . }}
</tr>
</table>
</td>
diff --git a/api/templates/email_change_verify_body.html b/api/templates/email_change_verify_body.html
index f6bc3bc39..9d2c559b3 100644
--- a/api/templates/email_change_verify_body.html
+++ b/api/templates/email_change_verify_body.html
@@ -25,25 +25,13 @@
</td>
</tr>
<tr>
- <td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
- Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
- Best wishes,<br>
- The {{.ClientCfg.SiteName}} Team<br>
- </td>
+ {{template "email_info" . }}
</tr>
</table>
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
- <p style="margin: 25px 0;">
- <img width="65" src="{{.Props.SiteURL}}/static/images/circles.png" alt="">
- </p>
- <p style="padding: 0 50px;">
- (c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
- If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
- </p>
- </td>
+ {{template "email_footer" . }}
</tr>
</table>
</td>
diff --git a/api/templates/email_footer.html b/api/templates/email_footer.html
new file mode 100644
index 000000000..e3ff9c584
--- /dev/null
+++ b/api/templates/email_footer.html
@@ -0,0 +1,13 @@
+{{define "email_footer"}}
+
+<td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
+ <p style="margin: 25px 0;">
+ <img width="65" src="{{.Props.SiteURL}}/static/images/circles.png" alt="">
+ </p>
+ <p style="padding: 0 50px;">
+ (c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
+ To change your notification preferences, log in to your team site and go to Account Settings > Notifications.
+ </p>
+</td>
+
+{{end}}
diff --git a/api/templates/email_info.html b/api/templates/email_info.html
new file mode 100644
index 000000000..48725d144
--- /dev/null
+++ b/api/templates/email_info.html
@@ -0,0 +1,9 @@
+{{define "email_info"}}
+
+<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
+ Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
+ Best wishes,<br>
+ The {{.ClientCfg.SiteName}} Team<br>
+</td>
+
+{{end}}
diff --git a/api/templates/find_teams_body.html b/api/templates/find_teams_body.html
index 4669d51c1..0b52af033 100644
--- a/api/templates/find_teams_body.html
+++ b/api/templates/find_teams_body.html
@@ -30,25 +30,13 @@
</td>
</tr>
<tr>
- <td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
- Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
- Best wishes,<br>
- The {{.ClientCfg.SiteName}} Team<br>
- </td>
+ {{template "email_info" . }}
</tr>
</table>
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
- <p style="margin: 25px 0;">
- <img width="65" src="{{.ClientCfg.SiteURL}}/static/images/circles.png" alt="">
- </p>
- <p style="padding: 0 50px;">
- (c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
- If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
- </p>
- </td>
+ {{template "email_footer" . }}
</tr>
</table>
</td>
diff --git a/api/templates/invite_body.html b/api/templates/invite_body.html
index d98f91357..a81d0c3d5 100644
--- a/api/templates/invite_body.html
+++ b/api/templates/invite_body.html
@@ -27,25 +27,13 @@
</td>
</tr>
<tr>
- <td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
- Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
- Best wishes,<br>
- The {{.ClientCfg.SiteName}} Team<br>
- </td>
+ {{template "email_info" . }}
</tr>
</table>
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
- <p style="margin: 25px 0;">
- <img width="65" src="{{.Props.SiteURL}}/static/images/circles.png" alt="">
- </p>
- <p style="padding: 0 50px;">
- (c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
- If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
- </p>
- </td>
+ {{template "email_footer" . }}
</tr>
</table>
</td>
diff --git a/api/templates/password_change_body.html b/api/templates/password_change_body.html
index 2e1df3ff2..82f4d5429 100644
--- a/api/templates/password_change_body.html
+++ b/api/templates/password_change_body.html
@@ -22,25 +22,13 @@
</td>
</tr>
<tr>
- <td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
- Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
- Best wishes,<br>
- The {{.ClientCfg.SiteName}} Team<br>
- </td>
+ {{template "email_info" . }}
</tr>
</table>
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
- <p style="margin: 25px 0;">
- <img width="65" src="{{.Props.SiteURL}}/static/images/circles.png" alt="">
- </p>
- <p style="padding: 0 50px;">
- (c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
- If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
- </p>
- </td>
+ {{template "email_footer" . }}
</tr>
</table>
</td>
diff --git a/api/templates/post_body.html b/api/templates/post_body.html
index 182134b1a..468d5e205 100644
--- a/api/templates/post_body.html
+++ b/api/templates/post_body.html
@@ -18,32 +18,20 @@
<tr>
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
<h2 style="font-weight: normal; margin-top: 10px;">You were mentioned</h2>
- <p>CHANNEL: {{.Props.ChannelName}}<br>{{.Props.SenderName}} - {{.Props.Hour}}:{{.Props.Minute}} GMT, {{.Props.Month}} {{.Props.Day}}<br><pre style="text-align:left;font-family: 'Lato', sans-serif; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;">{{.Props.PostMessage}}</pre></p>
+ <p>CHANNEL: {{.Props.ChannelName}}<br>{{.Props.SenderName}} - {{.Props.Hour}}:{{.Props.Minute}} {{.Props.TimeZone}}, {{.Props.Month}} {{.Props.Day}}<br><pre style="text-align:left;font-family: 'Lato', sans-serif; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;">{{.Props.PostMessage}}</pre></p>
<p style="margin: 20px 0 15px">
<a href="{{.Props.TeamLink}}" style="background: #2389D7; display: inline-block; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 170px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Go To Channel</a>
</p>
</td>
</tr>
<tr>
- <td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
- Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
- Best wishes,<br>
- The {{.ClientCfg.SiteName}} Team<br>
- </td>
+ {{template "email_info" . }}
</tr>
</table>
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
- <p style="margin: 25px 0;">
- <img width="65" src="{{.Props.SiteURL}}/static/images/circles.png" alt="">
- </p>
- <p style="padding: 0 50px;">
- (c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
- If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
- </p>
- </td>
+ {{template "email_footer" . }}
</tr>
</table>
</td>
diff --git a/api/templates/reset_body.html b/api/templates/reset_body.html
index 5e5f6cafc..a608c804a 100644
--- a/api/templates/reset_body.html
+++ b/api/templates/reset_body.html
@@ -25,25 +25,13 @@
</td>
</tr>
<tr>
- <td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
- Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
- Best wishes,<br>
- The {{.ClientCfg.SiteName}} Team<br>
- </td>
+ {{template "email_info" . }}
</tr>
</table>
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
- <p style="margin: 25px 0;">
- <img width="65" src="{{.Props.SiteURL}}/static/images/circles.png" alt="">
- </p>
- <p style="padding: 0 50px;">
- (c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
- If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
- </p>
- </td>
+ {{template "email_footer" . }}
</tr>
</table>
</td>
diff --git a/api/templates/signup_team_body.html b/api/templates/signup_team_body.html
index 6f3deb28b..2f384ac43 100644
--- a/api/templates/signup_team_body.html
+++ b/api/templates/signup_team_body.html
@@ -25,25 +25,13 @@
</td>
</tr>
<tr>
- <td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
- Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
- Best wishes,<br>
- The {{.ClientCfg.SiteName}} Team<br>
- </td>
+ {{template "email_info" . }}
</tr>
</table>
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
- <p style="margin: 25px 0;">
- <img width="65" src="{{.Props.SiteURL}}/static/images/circles.png" alt="">
- </p>
- <p style="padding: 0 50px;">
- (c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
- If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
- </p>
- </td>
+ {{template "email_footer" . }}
</tr>
</table>
</td>
diff --git a/api/templates/verify_body.html b/api/templates/verify_body.html
index a93de9a71..97571d9e3 100644
--- a/api/templates/verify_body.html
+++ b/api/templates/verify_body.html
@@ -25,25 +25,13 @@
</td>
</tr>
<tr>
- <td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
- Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
- Best wishes,<br>
- The {{.ClientCfg.SiteName}} Team<br>
- </td>
+ {{template "email_info" . }}
</tr>
</table>
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
- <p style="margin: 25px 0;">
- <img width="65" src="{{.Props.SiteURL}}/static/images/circles.png" alt="">
- </p>
- <p style="padding: 0 50px;">
- (c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
- If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
- </p>
- </td>
+ {{template "email_footer" . }}
</tr>
</table>
</td>
diff --git a/api/templates/welcome_body.html b/api/templates/welcome_body.html
index 485bc6351..dbb94cf06 100644
--- a/api/templates/welcome_body.html
+++ b/api/templates/welcome_body.html
@@ -37,15 +37,7 @@
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
- <p style="margin: 25px 0;">
- <img width="65" src="{{.Props.SiteURL}}/static/images/circles.png" alt="">
- </p>
- <p style="padding: 0 50px;">
- (c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
- If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
- </p>
- </td>
+ {{template "email_footer" . }}
</tr>
</table>
</td>
diff --git a/api/user_test.go b/api/user_test.go
index 63a1e337b..731450321 100644
--- a/api/user_test.go
+++ b/api/user_test.go
@@ -28,7 +28,7 @@ func TestCreateUser(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "hello"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "hello"}
ruser, err := Client.CreateUser(&user, "")
if err != nil {
@@ -79,7 +79,7 @@ func TestCreateUserAllowedDomains(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_INVITE, AllowedDomains: "spinpunch.com, @nowh.com,@hello.com"}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "hello"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "hello"}
_, err := Client.CreateUser(&user, "")
if err == nil {
@@ -99,7 +99,7 @@ func TestLogin(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
@@ -139,7 +139,7 @@ func TestLogin(t *testing.T) {
team2 := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_INVITE}
rteam2 := Client.Must(Client.CreateTeam(&team2))
- user2 := model.User{TeamId: rteam2.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := model.User{TeamId: rteam2.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
if _, err := Client.CreateUserFromSignup(&user2, "junk", "1231312"); err == nil {
t.Fatal("Should have errored, signed up without hashed email")
@@ -168,7 +168,7 @@ func TestSessions(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
@@ -220,18 +220,18 @@ func TestGetUser(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
- user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser2, _ := Client.CreateUser(&user2, "")
store.Must(Srv.Store.User().VerifyEmail(ruser2.Data.(*model.User).Id))
team2 := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam2, _ := Client.CreateTeam(&team2)
- user3 := model.User{TeamId: rteam2.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user3 := model.User{TeamId: rteam2.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser3, _ := Client.CreateUser(&user3, "")
store.Must(Srv.Store.User().VerifyEmail(ruser3.Data.(*model.User).Id))
@@ -312,7 +312,7 @@ func TestGetAudits(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
@@ -365,7 +365,7 @@ func TestUserCreateImage(t *testing.T) {
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: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -399,7 +399,7 @@ func TestUserUploadProfileImage(t *testing.T) {
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: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -505,7 +505,7 @@ func TestUserUpdate(t *testing.T) {
time1 := model.GetMillis()
- user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd", LastActivityAt: time1, LastPingAt: time1, Roles: ""}
+ user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd", LastActivityAt: time1, LastPingAt: time1, Roles: ""}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -561,7 +561,7 @@ func TestUserUpdate(t *testing.T) {
t.Fatal("Should have errored")
}
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -580,7 +580,7 @@ func TestUserUpdatePassword(t *testing.T) {
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: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -623,7 +623,7 @@ func TestUserUpdatePassword(t *testing.T) {
t.Fatal(err)
}
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
Client.LoginByEmail(team.Name, user2.Email, "pwd")
@@ -643,7 +643,7 @@ func TestUserUpdateRoles(t *testing.T) {
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -713,7 +713,7 @@ func TestUserUpdateActive(t *testing.T) {
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -773,7 +773,7 @@ func TestUserPermDelete(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -812,7 +812,7 @@ func TestSendPasswordReset(t *testing.T) {
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: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -845,7 +845,7 @@ func TestSendPasswordReset(t *testing.T) {
t.Fatal("Should have errored - bad name")
}
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", AuthData: "1", AuthService: "random"}
+ user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", AuthData: "1", AuthService: "random"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -862,7 +862,7 @@ func TestResetPassword(t *testing.T) {
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: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -939,7 +939,7 @@ func TestResetPassword(t *testing.T) {
t.Fatal("Should have errored - domain team doesn't match user team")
}
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", AuthData: "1", AuthService: "random"}
+ user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", AuthData: "1", AuthService: "random"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -960,7 +960,7 @@ func TestUserUpdateNotify(t *testing.T) {
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: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd", Roles: ""}
+ user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd", Roles: ""}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -1055,11 +1055,11 @@ func TestStatuses(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
- user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser2 := Client.Must(Client.CreateUser(&user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser2.Id))
diff --git a/api/web_socket_test.go b/api/web_socket_test.go
index 63c4dd05d..24e860628 100644
--- a/api/web_socket_test.go
+++ b/api/web_socket_test.go
@@ -20,7 +20,7 @@ func TestSocket(t *testing.T) {
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)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
@@ -39,7 +39,7 @@ func TestSocket(t *testing.T) {
t.Fatal(err)
}
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
Client.LoginByEmail(team.Name, user2.Email, "pwd")
diff --git a/api/web_team_hub.go b/api/web_team_hub.go
index 6a25b7d3d..2c2386317 100644
--- a/api/web_team_hub.go
+++ b/api/web_team_hub.go
@@ -95,9 +95,11 @@ func ShouldSendEvent(webCon *WebConn, msg *model.Message) bool {
return false
}
} else {
- // Don't share a user's view events with other users
+ // Don't share a user's view or preference events with other users
if msg.Action == model.ACTION_CHANNEL_VIEWED {
return false
+ } else if msg.Action == model.ACTION_PREFERENCE_CHANGED {
+ return false
}
// Only report events to a user who is the subject of the event, or is in the channel of the event
diff --git a/api/webhook_test.go b/api/webhook_test.go
index 4c04a9922..85117ec18 100644
--- a/api/webhook_test.go
+++ b/api/webhook_test.go
@@ -17,7 +17,7 @@ func TestCreateIncomingHook(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -80,7 +80,7 @@ func TestListIncomingHooks(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -118,7 +118,7 @@ func TestDeleteIncomingHook(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -158,7 +158,7 @@ func TestCreateOutgoingHook(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -221,7 +221,7 @@ func TestListOutgoingHooks(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -259,7 +259,7 @@ func TestDeleteOutgoingHook(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -299,7 +299,7 @@ func TestRegenOutgoingHookToken(t *testing.T) {
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: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
diff --git a/config/config.json b/config/config.json
index 4220d1055..4477ec63b 100644
--- a/config/config.json
+++ b/config/config.json
@@ -82,6 +82,14 @@
"ShowEmailAddress": true,
"ShowFullName": true
},
+ "SupportSettings": {
+ "TermsOfServiceLink": "/static/help/terms.html",
+ "PrivacyPolicyLink": "/static/help/privacy.html",
+ "AboutLink": "/static/help/about.html",
+ "HelpLink": "/static/help/help.html",
+ "ReportAProblemLink": "/static/help/report_problem.html",
+ "SupportEmail": "feedback@mattermost.com"
+ },
"GitLabSettings": {
"Enable": false,
"Secret": "",
diff --git a/doc/developer/tests/test-syntax-highlighting.md b/doc/developer/tests/test-syntax-highlighting.md
index 7f8f4cdaa..b1568c385 100644
--- a/doc/developer/tests/test-syntax-highlighting.md
+++ b/doc/developer/tests/test-syntax-highlighting.md
@@ -133,7 +133,7 @@ echo "Hello World"
### CoffeeScript
-``` coffee
+``` coffeescript
console.log(“Hello world!”);
```
diff --git a/doc/help/Markdown.md b/doc/help/Markdown.md
index 64ce4fc83..d185a4160 100644
--- a/doc/help/Markdown.md
+++ b/doc/help/Markdown.md
@@ -31,7 +31,7 @@ code block
To add syntax highlighting, type the language to be highlighted after the ``` at the beginning of the code block.
Supported languages are:
-`diff, apache, makefile, http, json, markdown, javascript, css, nginx, objectivec, python, xml, perl, bash, php, coffee (CoffeeScript), cs (C#), cpp (C++), sql, go, ruby, java, ini, latex`
+`diff, apache, makefile, http, json, markdown, javascript, css, nginx, objectivec, python, xml, perl, bash, php, coffeescript, cs (C#), cpp (C++), sql, go, ruby, java, ini, latex`
Example:
diff --git a/doc/help/Messaging.md b/doc/help/Messaging.md
index 548892650..2063ad41c 100644
--- a/doc/help/Messaging.md
+++ b/doc/help/Messaging.md
@@ -1,18 +1,18 @@
# Messaging
-## Writing Messages
+### Writing Messages
-You can write messages using the input box with the text "Write a message..." at the bottom of Mattermost.
+You can write messages using the input box with the text "Write a message..." at the bottom of Mattermost.
-Press **ENTER** to send a message. Use **Shift+ENTER** to create a new line without sending a message.
+Press **ENTER** to send a message. Use **Shift+ENTER** to create a new line without sending a message.
-## Formatting Messages
+### Formatting Messages
-Mattermost messages are formatted using a standard called "markdown". Here are examples:
+Mattermost messages are formatted using a standard called "markdown". Here are examples:
| Text Entered | How it appears |
|:---------------|:---------------|
-|`**bold**`| **bold** |
+|`**bold**`| **bold** |
| `_italic_`|_italic_|
|`[hyperlink](http://mattermost.org)`|[hyperlink](http://mattermost.org)|
|`![embedded image](https://travis-ci.org/mattermost/platform.svg)`|![embedded image](https://travis-ci.org/mattermost/platform.svg)|
@@ -21,27 +21,27 @@ Mattermost messages are formatted using a standard called "markdown". Here are e
Emojis provided free from [Emoji One](http://emojione.com/). Check out a full list of Emojis [here](http://emoji.codes/).
-## Mentioning Teammates
+### Mentioning Teammates
-You can mention a teammate by using the `@` symbol plus their username to send them a special notification to draw their attention.
+You can mention a teammate by using the `@` symbol plus their username to send them a special notification to draw their attention.
-For example, you might write:
+For example, you might write:
```
@alice how did your interview go with the new candidate?
-```
+```
-Which sends a special mention notification to **alice** to check your message.
+Which sends a special mention notification to **alice** to check your message.
-To mention a teammate, press `@` and you should see a list of team members who can be messaged. You can either type their username or use the **Up** and **Down** arrow keys and then **ENTER** to select them to be mentioned.
+To mention a teammate, press `@` and you should see a list of team members who can be messaged. You can either type their username or use the **Up** and **Down** arrow keys and then **ENTER** to select them to be mentioned.
-You can configure how you'd like to be alerted about mentions of your username, your first name, your nickname, or other keywords from **Account Settings** > **Notifications** and you can set channel-specific preferences from **[Channel Name]** > **Notification Preferences**
+You can configure how you'd like to be alerted about mentions of your username, your first name, your nickname, or other keywords from **Account Settings** > **Notifications** and you can set channel-specific preferences from **[Channel Name]** > **Notification Preferences**
-## Messages Dropdown Menu
+### Messages Dropdown Menu
-To get to the Messages Dropdown Menu, hover over a message and click on the [...] menu. This shows a dropdown list containing additional actions you can perform on a message:
+To get to the Messages Dropdown Menu, hover over a message and click on the [...] menu. This shows a dropdown list containing additional actions you can perform on a message:
- **Reply:** Opens up the sidebar so you can reply to a message in a comment thread.
-- **Permalink:** Creates a link to the message. Sharing this link with other users in the channel lets them view the linked message in the Message Archives.
-- **Delete:** Deletes the message so it is no longer visible. Team Administrators and System Administrators can also delete another user's message.
+- **Permalink:** Creates a link to the message. Sharing this link with other users in the channel lets them view the linked message in the Message Archives.
+- **Delete:** Deletes the message so it is no longer visible. Team Administrators and System Administrators can also delete another user's message.
- **Edit:** Lets you edit your own message.
diff --git a/doc/help/Sign-in.md b/doc/help/Sign-in.md
index 5b7b375c7..3b91af1fc 100644
--- a/doc/help/Sign-in.md
+++ b/doc/help/Sign-in.md
@@ -24,4 +24,4 @@ If your account in a given team has been given the System Administrator role, it
#### Logging Out
-You can log out from the main menu, which is accessed by clicking the three dots in the top header on the left side of the screen. Clicking "Logout" logs you out of all the teams you are signed-in to that share the same email address.
+You can log out from the main menu, which is accessed by clicking the three dots in the top header on the left side of the screen. Clicking "Logout" logs you out of all teams that are signed-in and open in your browser.
diff --git a/doc/install/Production-Debian.md b/doc/install/Production-Debian.md
index e97f3188b..13ff051a3 100644
--- a/doc/install/Production-Debian.md
+++ b/doc/install/Production-Debian.md
@@ -269,7 +269,8 @@ exit 0
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
ssl_prefer_server_ciphers on;
-
+ ssl_session_cache shared:SSL:10m;
+
# add to location / above
location / {
gzip off;
diff --git a/doc/install/Production-Ubuntu.md b/doc/install/Production-Ubuntu.md
index d6b98981c..1022f30cf 100644
--- a/doc/install/Production-Ubuntu.md
+++ b/doc/install/Production-Ubuntu.md
@@ -164,7 +164,8 @@ exec bin/platform
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
ssl_prefer_server_ciphers on;
-
+ ssl_session_cache shared:SSL:10m;
+
# add to location / above
location / {
gzip off;
diff --git a/doc/process/accepting-pull-request.md b/doc/process/accepting-pull-request.md
new file mode 100644
index 000000000..f2bbb3f05
--- /dev/null
+++ b/doc/process/accepting-pull-request.md
@@ -0,0 +1,28 @@
+# Accepting Pull Requests (APRs)
+
+Per the [Contributor Guide](https://github.com/mattermost/platform/blob/master/CONTRIBUTING.md), only PRs that reference Jira tickets with the "accepting pull requests" label should be submitted to this repo. This system ensures:
+
+1. Proposed changes have been thoughtfully reviewed
+2. Proposed changes can be tested, documented, and supported by volunteers
+3. The change meets the [fast, obvious, forgiving](http://www.mattermost.org/design-principles/) design principle for the project.
+4. The change [aligns to the stated purpose of the project](http://www.mattermost.org/vision/#mattermost-teams-v1)
+
+Key contributors and core team members are responsible opening Jira tickets that meet the above requirements, which are then reviewed in triage meetings.
+
+When opening Jira tickets for accepting-pull-requests, please:
+
+1. Use titles that clearly describe the needed change and its value
+2. Include (Proposed APR) in title, so triage team knows to add the APR tag if appropriate
+3. Provide unambiguous description so that feature can be tested by any volunteer
+4. Link back to community discussions related to this change
+5. Apply "accepting-pull-requests", and optionally as "good-first-contribution".
+6. Post link back to community channels
+
+Example of post back to the community:
+
+```
+Thanks @[USERNAME],
+
+I created a ticket [for accepting pull requests on this issue](http://link). It will be reviewed in the triage meeting for addition to [the Accepting Pull Requests issues list](https://mattermost.atlassian.net/browse/PLT-797?filter=10101). [Code Contribution Guidelines](https://github.com/mattermost/platform/blob/master/CONTRIBUTING.md) are available for anyone interested in contributing to this project.
+```
+
diff --git a/doc/process/release-process.md b/doc/process/release-process.md
index 96bd2b050..294f5aa79 100644
--- a/doc/process/release-process.md
+++ b/doc/process/release-process.md
@@ -1,68 +1,81 @@
-We're working on making internal processes in the Mattermost core team more transparent for the community. Below is a working draft of our software development process, which will be updated live as we refine our process.
-
-Questions, feedback, comments always welcome,
-
-----------
+# Mattermost Release Process
Mattermost core team works on a monthly release process, with a new version shipping on the 16th of each month.
This document outlines the development process for the Mattermost core team, which draws from what we find works best for us from Agile, Scrum and Software Development Lifecycle approaches.
-This is a working document that will update as our process evolves.
-
+Notes:
+- All cut-off dates are based on 10am PST (UTC-07/08) on the day stated.
+- T-minus counts are measured in "working days" (weekdays other than major holidays concurrent in US and Canada) prior to release day.
-### - Beginning of release
-- (Ops) Queue an agenda item for first team meeting of the release to review Roadmap
+### (Code complete date of previous release) Beginning of release
+- Pre-work for the current release begins at the code complete date of the previous release. See "Code Complete" section below for details.
-### - (10 weekdays before release date) Cut-off for major features
-- No major features can be committed to the current release after this date
-- (Dev) Prioritize reviewing, updating, and merging of all pull requests that are going to make it into the release
- - There should be no more tickets in the [pull request queue](https://github.com/mattermost/platform/pulls) marked for the current release
+### (T-minus 10 working days) Cut-off for major features
+- No pull requests for major features should be submitted to the current release after this date (except if release manager decides to add "release-exception" label)
+- (Ops) Post this checklist in Release channel
+- (PM) Write compatibility updates for config.json and database changes [See example](https://github.com/mattermost/platform/blob/master/CHANGELOG.md#compatibility)
+- (PM) Confirm changes to config.json in compatibility section of Changelog are written back to [settings documentation](https://github.com/mattermost/platform/blob/master/doc/install/Configuration-Settings.md)
+- (Dev) Prioritize reviewing, updating, and merging of pull requests for current release until there are no more tickets in the [pull request queue](https://github.com/mattermost/platform/pulls) marked for the current release
- (Leads) Meets to prioritize the final tickets of the release
- Backlog is reviewed and major features that won’t make it are moved to next release
- Triage tickets
- - Review roadmap for next release
-- (Marketing) Writes the "Highlights" section of the Changelog
-- (PM) Write compatibility updates for config.json and database changes [See example](https://github.com/mattermost/platform/blob/master/CHANGELOG.md#compatibility)
+ - Finalize roadmap for next release
+ - Draft roadmap for release after next (used to prioritize design tasks)
+- (Marketing) Drafts marketing bullet points for next release based off of roadmap
+- (Marketing) Submits pull request for "Highlights" section of the Changelog
+- (Marketing) Notes date of announcements in release channel
- (PM) Update [Upgrade Guide](https://github.com/mattermost/platform/blob/master/doc/install/Upgrade-Guide.md) for any steps needed to upgrade to new version
-- (PM) Prepare tickets for cutting RCs builds, filing issue in GitLab omnibus to take RC candidate, testing GitLab RC with Mattermost
-- (Stand-up) Each team member discusses worst bug
-
-### - (8 weekdays before release date) Feature Complete and Stabilization
-- After the cut-off time for Feature Complete, Dev prioritizes reviewing PRs and committing to master so Stabilization period can begin, with testing and high priority bug fixes
-- During Stabilization period only BUGS can be committed to master, non-bug tickets are tagged for next version and wait until after a release candidate is cut to be added to master
- - (PM) Review all [S1 bugs](https://mattermost.atlassian.net/secure/IssueNavigator.jspa?mode=hide&requestId=10600) and mark important ones as high priority
- - (Dev + PM) Exceptions can be made by triage team consensus across PM and Dev. List of approved changes for release candidate 1 here: https://mattermost.atlassian.net/issues/?filter=10204
-- (PM) Documentation
+- (PM) Prepare tickets for [cutting RC builds](https://mattermost.atlassian.net/browse/PLT-985), [creating the final release candidate](https://mattermost.atlassian.net/browse/PLT-986), [creating AMIs](https://mattermost.atlassian.net/browse/PLT-1213), and [testing GitLab RC with Mattermost](https://mattermost.atlassian.net/browse/PLT-1013)
+- (Stand-up) Each team member discusses worst bug (10-15s)
+
+### (T-minus 8 working days) Feature Complete and Stabilization
+- No pull requests for features can be submitted to the current release after this date (except if release manager add "release-exception" label to Jira ticket)
+- (Ops) Post this checklist in Release channel
+- (Dev) Prioritize reviewing, updating, and merging of pull requests for current release until there are no more tickets in the [pull request queue](https://github.com/mattermost/platform/pulls) marked for the current release
+- **Stablization** period begins when all features for release have been committed.
+ - During this period, only **bugs** can be committed to master. Non-bug pull requests are tagged for next version and wait until after a release candidate is cut to be committed to master
+ - (RM) Exceptions can be made by release manager by setting priority to "Highest" and labelling "release-exception", which will add ticket to [Hotfix list for release candidate](https://mattermost.atlassian.net/issues/?filter=10204).
+ - (PM) Review all [Severity 1 bugs (data loss or security)](https://mattermost.atlassian.net/secure/IssueNavigator.jspa?mode=hide&requestId=10600) to consider for adding to Hotfix list.
+- (PM) Complete documentation
- (PM) Make Changelog PR with updates for latest feature additions and changes
- (PM) Make Changelog PR with updates to contributors
- (PM) Make NOTICE.txt PR for any new libraries added from dev, if not added already
- (PM) Prioritize any developer documentation tickets
-- (PM and devs) Sign-off testing of their feature areas (i.e. PM/dev either signs-off that their area is well tested, or they flag that potential quality issues may exist)
-- (Ops) Mail out mugs to any new contributors
-- (Team) Select "Top Contributor" for the release from external contributions to be mentioned in release announcement
-- (Marketing) Decides announce date (discuss in meeting)
+ - (PM) Draft [GitLab ticket](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/942) to take next Mattermost version in the Omnibus, but do not post until RC1 is cut
+- (PM) Check testing is complete
+ - (PM) Works with Ops to check the [Quality Gate](https://github.com/mattermost/process/blob/master/release/quality-gates.md) for feature complete
+ - (PM + Dev) Sign-off testing of their feature areas (i.e. PM/dev either signs-off that their area is well tested, or they flag that potential quality issues may exist)
+- **(Team) Feature Complete Meeting (10:15am PST)**
+ - (PM) Leads review of Changelog
+ - (Team) Each team member discusses worst bug (10-15s)
+ - (PM) Review feature list for next release
+ - (Marketing) Share draft of marketing announce for next release
+- (Marketing) Communicates checklist of items needed by specific dates to write the blog post announce (e.g. screenshots, GIFs,
- (Ops) Post Announce Date in Release channel + update the channel header to reflect date
-- (Marketing) Communicates checklist of items needed by specific dates to write the blog post announce (e.g. screenshots, GIFs, documentation) and begins to write the blog post, tweet, and email for the release announcement
-- (PM) Works with Ops to check the Quality Gate for feature complete
-- (PM) Communicate to team the plan for next release
-- (Stand-up) Each team member discusses worst bug
+- (Ops) Mail out mugs to any new contributors
-### - (5 weekdays before release date) Code Complete and Release Candidate Cut
-- (Team) Meets to discuss release at 10am PST
- - (PM) Each area changed in latest release is assigned a PM owner to lead testing
- - (Ops) Walks through each item of the **Code Complete and Release Candidate Cut** checklist
+### (T-minus 5 working days) Code Complete and Release Candidate Cut
+- (Ops) Post this checklist in Release channel
+- (Ops) For the next release, create team meetings on Feature Complete and Code Complete dates
+- (PM) Remove "Under Development" notice for current release from Changelog on master
+- **(Team) Code Complete Meeting (10:15am PST meeting)**
+ - (Ops) Walks through each item of this checklist
+ - (PM) Assigns each area of the release testing spreadsheet to a team member
- (Dev) Last check of tickets that need to be merged before RC1
- - (Team) Each team member discusses worst bug
-- After 10am PST meeting the release is considered “Code Complete”.
- - (Dev) Completes final reviews and updates of PRs marked for the release version
- - There should be no more tickets in the [pull request queue](https://github.com/mattermost/platform/pulls) marked for the current release
- - Master is tagged and branched and “Release Candidate 1″ is cut (e.g. 1.1.0-RC1) according to the Release Candidate Checklist
+ - (Team) Each team member discusses worst bug (10-15s)
+- **Code Complete** is declared after meeting
+ - (Dev) Prioritize reviewing, updating, and merging of pull requests for current release until there are no more tickets in the [pull request queue](https://github.com/mattermost/platform/pulls) marked for the current release
+ - (Build) Master is tagged and branched and “Release Candidate 1″ is cut (e.g. 1.1.0-RC1) according to the [Release Candidate Checklist](https://github.com/mattermost/process/blob/master/release/create-release-candidate.md)
- (PM) Create meta issue for regressions in GitHub (see [example](https://github.com/mattermost/platform/issues/574))
-
-### - (4 weekdays before release date) Release Candidate Testing
-- Final testing is conducted by the team on the acceptance server and any issues found are filed
- - (Dev) Tests upgrade from previous version to current version, following the [Upgrade Guide](https://github.com/mattermost/platform/blob/master/doc/install/Upgrade-Guide.md)
+ - (PM) Include link to meta-issue in release notes of RC1
+ - (PM) Tweet announcement that RC1 is ready (see [example](https://twitter.com/mattermosthq/status/664172166368264192))
+ - (PM) Submit GitLab ticket to take next Mattermost version in the Omnibus
+
+### (T-minus 4 working days) Release Candidate Testing
+- (Team) Final testing is conducted by the team on the acceptance server and any issues found are filed
+- (Build) Tests upgrade from previous version to current version, following the [Upgrade Guide](https://github.com/mattermost/platform/blob/master/doc/install/Upgrade-Guide.md)
+ - Database upgrade should be tested on both MySQL and Postgres
- (Ops) Posts copy of the **Release Candidate Testing** checklist into Town Square in PRODUCTION
- (Ops) Moves meeting, test and community channels over to the production version of RC, and posts in Town Square asking everyone to move communication over to the new team for testing purposes
- (PM) Test feature areas and post bugs to Bugs/Issues in PRODUCTION
@@ -72,6 +85,7 @@ This is a working document that will update as our process evolves.
- (PM) Posts links to all issues found in RC as comments on the meta issue
- (PM) Updates description to include approved fixes
- (PM) Posts screenshot and link to final tickets for next RC to the Release room
+ - (PM) Updates Release Notes with any new issues that will not be fixed for the current version
- (PM & DEV leads) Triage hotfix candidates and decide on whether and when to cut next RC or final
- (Dev) PRs for hotfixes made to release branch, and changes from release branch are merged into master
- (Ops) Tests approved fixes on master
@@ -81,19 +95,28 @@ This is a working document that will update as our process evolves.
- (Ops) verifies each of the issues in meta ticket is fixed
- (PM) If no blocking issues are found, PM, Dev and Ops signs off on the release
-### - (2 weekdays before release date) Release
- - (Dev) Tags a new release (e.g. 1.1.0) and runs an official build which should be essentially identical to the last RC
+### (T-minus 2 working days) Release Build Cut
+- (Ops) Post this checklist in Release channel
+- (Build) Tags a new release (e.g. 1.1.0) and runs an official build which should be essentially identical to the last RC
- (PM) Any significant issues that were found and not fixed for the final release are noted in the release notes
- - If an urgent and important issue needs to be addressed between major releases, a hotfix release (e.g. 1.1.1) may be released, however this should be very rare, given a monthly cadence
- - (PM) Copy and paste the Release Notes from the Changelog to the Release Description
- - (PM) Update the mattermost.org/download page
- - (Dev) Delete RCs after final version is shipped
- - (PM) Close final GitHub RC meta ticket
-
-### - (0 weekdays before release date) End of Release
-- (PM) Makes sure marketing has been posted (animated GIFs, screenshots, mail announcement, Tweets, blog posts)
+ - If an urgent and important issue needs to be addressed between major releases, a bug fix release (e.g. 1.1.1) may be created
+- (PM) Copy and paste the Release Notes from the Changelog to the Release Description
+- (PM) Update the mattermost.org/download page
+- (PM) Update the AMI links on mattermost.org/installation
+- (PM) Close final GitHub RC meta ticket
+- (Dev) Delete RCs after final version is shipped
+- (Marketing) Finalize marketing
+ - (Marketing) Finalize mailchimp email blast
+ - (Marketing) Finalize blog post and put on timer for release
+ - (Marketing) Finalize tweet announcement
+ - (Marketing) Finalize announcement on general mailing list
+ - (Marketing) Finalize announcement for gitlab.mattermost.com
+
+### (T-minus 0 working days) Release Day
+- (Ops) Post this checklist in Release channel
+- (PM) Confirm marketing has been posted (animated GIFs, screenshots, mail announcement, Tweets, blog posts)
- (PM) Close the release in Jira
+- (PM) Set header of next release as UNDER DEVELOPMENT in CHANGELOG on master
- (Dev) Check if any libraries need to be updated for the next release, and if so bring up in weekly team meeting
-- (Ops) Post important dates for the next release in the header of the Release channel
-- (Ops) Queue an agenda item for next team meeting for "Stepping Back" Q&A
-- (Ops) Queue an agenda item for next team meeting for Roadmap review
+- (Ops) Post key dates for the next release in the header of the Release channel
+- (Ops) Queue an agenda item for next team meeting for Release Process Kaizen/Q&A
diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json
index a35abb9da..ab1373a44 100644
--- a/docker/dev/config_docker.json
+++ b/docker/dev/config_docker.json
@@ -82,6 +82,14 @@
"ShowEmailAddress": true,
"ShowFullName": true
},
+ "SupportSettings": {
+ "TermsOfServiceLink": "/static/help/terms.html",
+ "PrivacyPolicyLink": "/static/help/privacy.html",
+ "AboutLink": "/static/help/about.html",
+ "HelpLink": "/static/help/help.html",
+ "ReportAProblemLink": "/static/help/report_problem.html",
+ "SupportEmail": "feedback@mattermost.com"
+ },
"GitLabSettings": {
"Enable": false,
"Secret": "",
diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json
index a35abb9da..ab1373a44 100644
--- a/docker/local/config_docker.json
+++ b/docker/local/config_docker.json
@@ -82,6 +82,14 @@
"ShowEmailAddress": true,
"ShowFullName": true
},
+ "SupportSettings": {
+ "TermsOfServiceLink": "/static/help/terms.html",
+ "PrivacyPolicyLink": "/static/help/privacy.html",
+ "AboutLink": "/static/help/about.html",
+ "HelpLink": "/static/help/help.html",
+ "ReportAProblemLink": "/static/help/report_problem.html",
+ "SupportEmail": "feedback@mattermost.com"
+ },
"GitLabSettings": {
"Enable": false,
"Secret": "",
diff --git a/model/client.go b/model/client.go
index ac85b0d1c..d3f76817d 100644
--- a/model/client.go
+++ b/model/client.go
@@ -230,7 +230,7 @@ func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) {
}
func (c *Client) CreateUserFromSignup(user *User, data string, hash string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/users/create?d="+data+"&h="+hash, user.ToJson()); err != nil {
+ if r, err := c.DoApiPost("/users/create?d="+url.QueryEscape(data)+"&h="+hash, user.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
diff --git a/model/config.go b/model/config.go
index 14ce444c5..9030f91ae 100644
--- a/model/config.go
+++ b/model/config.go
@@ -113,6 +113,15 @@ type PrivacySettings struct {
ShowFullName bool
}
+type SupportSettings struct {
+ TermsOfServiceLink *string
+ PrivacyPolicyLink *string
+ AboutLink *string
+ HelpLink *string
+ ReportAProblemLink *string
+ SupportEmail *string
+}
+
type TeamSettings struct {
SiteName string
MaxUsersPerTeam int
@@ -132,6 +141,7 @@ type Config struct {
EmailSettings EmailSettings
RateLimitSettings RateLimitSettings
PrivacySettings PrivacySettings
+ SupportSettings SupportSettings
GitLabSettings SSOSettings
}
@@ -164,6 +174,23 @@ func ConfigFromJson(data io.Reader) *Config {
}
func (o *Config) SetDefaults() {
+
+ if len(o.SqlSettings.AtRestEncryptKey) == 0 {
+ o.SqlSettings.AtRestEncryptKey = NewRandomString(32)
+ }
+
+ if len(o.FileSettings.PublicLinkSalt) == 0 {
+ o.FileSettings.PublicLinkSalt = NewRandomString(32)
+ }
+
+ if len(o.EmailSettings.InviteSalt) == 0 {
+ o.EmailSettings.InviteSalt = NewRandomString(32)
+ }
+
+ if len(o.EmailSettings.PasswordResetSalt) == 0 {
+ o.EmailSettings.PasswordResetSalt = NewRandomString(32)
+ }
+
if o.ServiceSettings.EnableSecurityFixAlert == nil {
o.ServiceSettings.EnableSecurityFixAlert = new(bool)
*o.ServiceSettings.EnableSecurityFixAlert = true
@@ -189,6 +216,35 @@ func (o *Config) SetDefaults() {
*o.EmailSettings.PushNotificationServer = ""
}
+ if o.SupportSettings.TermsOfServiceLink == nil {
+ o.SupportSettings.TermsOfServiceLink = new(string)
+ *o.SupportSettings.TermsOfServiceLink = "/static/help/terms.html"
+ }
+
+ if o.SupportSettings.PrivacyPolicyLink == nil {
+ o.SupportSettings.PrivacyPolicyLink = new(string)
+ *o.SupportSettings.PrivacyPolicyLink = "/static/help/privacy.html"
+ }
+
+ if o.SupportSettings.AboutLink == nil {
+ o.SupportSettings.AboutLink = new(string)
+ *o.SupportSettings.AboutLink = "/static/help/about.html"
+ }
+
+ if o.SupportSettings.HelpLink == nil {
+ o.SupportSettings.HelpLink = new(string)
+ *o.SupportSettings.HelpLink = "/static/help/help.html"
+ }
+
+ if o.SupportSettings.ReportAProblemLink == nil {
+ o.SupportSettings.ReportAProblemLink = new(string)
+ *o.SupportSettings.ReportAProblemLink = "/static/help/report_problem.html"
+ }
+
+ if o.SupportSettings.SupportEmail == nil {
+ o.SupportSettings.SupportEmail = new(string)
+ *o.SupportSettings.SupportEmail = "feedback@mattermost.com"
+ }
}
func (o *Config) IsValid() *AppError {
diff --git a/model/message.go b/model/message.go
index 2725353ac..1cb350bbf 100644
--- a/model/message.go
+++ b/model/message.go
@@ -9,14 +9,15 @@ import (
)
const (
- ACTION_TYPING = "typing"
- ACTION_POSTED = "posted"
- ACTION_POST_EDITED = "post_edited"
- ACTION_POST_DELETED = "post_deleted"
- ACTION_CHANNEL_VIEWED = "channel_viewed"
- ACTION_NEW_USER = "new_user"
- ACTION_USER_ADDED = "user_added"
- ACTION_USER_REMOVED = "user_removed"
+ ACTION_TYPING = "typing"
+ ACTION_POSTED = "posted"
+ ACTION_POST_EDITED = "post_edited"
+ ACTION_POST_DELETED = "post_deleted"
+ ACTION_CHANNEL_VIEWED = "channel_viewed"
+ ACTION_NEW_USER = "new_user"
+ ACTION_USER_ADDED = "user_added"
+ ACTION_USER_REMOVED = "user_removed"
+ ACTION_PREFERENCE_CHANGED = "preference_changed"
)
type Message struct {
diff --git a/model/outgoing_webhook.go b/model/outgoing_webhook.go
index 9a1b89a85..0b4fd6bbe 100644
--- a/model/outgoing_webhook.go
+++ b/model/outgoing_webhook.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
diff --git a/model/outgoing_webhook_test.go b/model/outgoing_webhook_test.go
index 0d1cd773e..665b85b6f 100644
--- a/model/outgoing_webhook_test.go
+++ b/model/outgoing_webhook_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
diff --git a/model/preference.go b/model/preference.go
index 4f2ba0099..a3230959c 100644
--- a/model/preference.go
+++ b/model/preference.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
diff --git a/model/preference_test.go b/model/preference_test.go
index 66b7ac50b..e29250bba 100644
--- a/model/preference_test.go
+++ b/model/preference_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
diff --git a/model/preferences.go b/model/preferences.go
index 1ef16151f..f11b5fd80 100644
--- a/model/preferences.go
+++ b/model/preferences.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
diff --git a/model/team_test.go b/model/team_test.go
index 112d48a9d..4b691e76a 100644
--- a/model/team_test.go
+++ b/model/team_test.go
@@ -45,7 +45,7 @@ func TestTeamIsValid(t *testing.T) {
t.Fatal("should be invalid")
}
- o.Email = "corey@hulen.com"
+ o.Email = "corey+test@hulen.com"
o.DisplayName = strings.Repeat("01234567890", 20)
if err := o.IsValid(true); err == nil {
t.Fatal("should be invalid")
diff --git a/model/utils.go b/model/utils.go
index b49b4bb24..5596b06ff 100644
--- a/model/utils.go
+++ b/model/utils.go
@@ -5,6 +5,7 @@ package model
import (
"bytes"
+ "crypto/rand"
"encoding/base32"
"encoding/json"
"fmt"
@@ -81,6 +82,17 @@ func NewId() string {
return b.String()
}
+func NewRandomString(length int) string {
+ var b bytes.Buffer
+ str := make([]byte, length+8)
+ rand.Read(str)
+ encoder := base32.NewEncoder(encoding, &b)
+ encoder.Write(str)
+ encoder.Close()
+ b.Truncate(length) // removes the '==' padding
+ return b.String()
+}
+
// GetMillis is a convience method to get milliseconds since epoch.
func GetMillis() int64 {
return time.Now().UnixNano() / int64(time.Millisecond)
diff --git a/model/utils_test.go b/model/utils_test.go
index 7f14bcdf0..aab535fc9 100644
--- a/model/utils_test.go
+++ b/model/utils_test.go
@@ -17,6 +17,15 @@ func TestNewId(t *testing.T) {
}
}
+func TestRandomString(t *testing.T) {
+ for i := 0; i < 1000; i++ {
+ r := NewRandomString(32)
+ if len(r) != 32 {
+ t.Fatal("should be 32 chars")
+ }
+ }
+}
+
func TestAppError(t *testing.T) {
err := NewAppError("TestAppError", "message", "")
json := err.ToJson()
@@ -47,21 +56,21 @@ func TestMapJson(t *testing.T) {
}
func TestValidEmail(t *testing.T) {
- if !IsValidEmail("corey@hulen.com") {
+ if !IsValidEmail("corey+test@hulen.com") {
t.Error("email should be valid")
}
- if IsValidEmail("@corey@hulen.com") {
+ if IsValidEmail("@corey+test@hulen.com") {
t.Error("should be invalid")
}
}
func TestValidLower(t *testing.T) {
- if !IsLower("corey@hulen.com") {
+ if !IsLower("corey+test@hulen.com") {
t.Error("should be valid")
}
- if IsLower("Corey@hulen.com") {
+ if IsLower("Corey+test@hulen.com") {
t.Error("should be invalid")
}
}
diff --git a/store/sql_post_store.go b/store/sql_post_store.go
index be770c09e..40dca9930 100644
--- a/store/sql_post_store.go
+++ b/store/sql_post_store.go
@@ -38,8 +38,6 @@ func NewSqlPostStore(sqlStore *SqlStore) PostStore {
}
func (s SqlPostStore) UpgradeSchemaIfNeeded() {
- s.RemoveColumnIfExists("Posts", "ImgCount") // remove after 1.3 release
- s.GetMaster().Exec(`UPDATE Preferences SET Type = :NewType WHERE Type = :CurrentType`, map[string]string{"NewType": model.POST_JOIN_LEAVE, "CurrentType": "join_leave"}) // remove after 1.3 release
}
func (s SqlPostStore) CreateIndexesIfNotExists() {
diff --git a/store/sql_preference_store.go b/store/sql_preference_store.go
index f73dad3ac..307761150 100644
--- a/store/sql_preference_store.go
+++ b/store/sql_preference_store.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package store
diff --git a/store/sql_preference_store_test.go b/store/sql_preference_store_test.go
index 6f8f44f47..ec9d1df6c 100644
--- a/store/sql_preference_store_test.go
+++ b/store/sql_preference_store_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package store
diff --git a/utils/config.go b/utils/config.go
index 93edc0214..0789c101d 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -183,6 +183,7 @@ func getClientConfig(c *model.Config) map[string]string {
props["SiteName"] = c.TeamSettings.SiteName
props["EnableTeamCreation"] = strconv.FormatBool(c.TeamSettings.EnableTeamCreation)
+ props["EnableUserCreation"] = strconv.FormatBool(c.TeamSettings.EnableUserCreation)
props["RestrictTeamNames"] = strconv.FormatBool(*c.TeamSettings.RestrictTeamNames)
props["EnableTeamListing"] = strconv.FormatBool(*c.TeamSettings.EnableTeamListing)
@@ -204,6 +205,13 @@ func getClientConfig(c *model.Config) map[string]string {
props["ShowEmailAddress"] = strconv.FormatBool(c.PrivacySettings.ShowEmailAddress)
+ props["TermsOfServiceLink"] = *c.SupportSettings.TermsOfServiceLink
+ props["PrivacyPolicyLink"] = *c.SupportSettings.PrivacyPolicyLink
+ props["AboutLink"] = *c.SupportSettings.AboutLink
+ props["HelpLink"] = *c.SupportSettings.HelpLink
+ props["ReportAProblemLink"] = *c.SupportSettings.ReportAProblemLink
+ props["SupportEmail"] = *c.SupportSettings.SupportEmail
+
props["EnablePublicLink"] = strconv.FormatBool(c.FileSettings.EnablePublicLink)
props["ProfileHeight"] = fmt.Sprintf("%v", c.FileSettings.ProfileHeight)
props["ProfileWidth"] = fmt.Sprintf("%v", c.FileSettings.ProfileWidth)
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx
index 4f144b0dd..e587c4f84 100644
--- a/web/react/components/admin_console/admin_controller.jsx
+++ b/web/react/components/admin_console/admin_controller.jsx
@@ -18,6 +18,7 @@ import GitLabSettingsTab from './gitlab_settings.jsx';
import SqlSettingsTab from './sql_settings.jsx';
import TeamSettingsTab from './team_settings.jsx';
import ServiceSettingsTab from './service_settings.jsx';
+import LegalAndSupportSettingsTab from './legal_and_support_settings.jsx';
import TeamUsersTab from './team_users.jsx';
import TeamAnalyticsTab from './team_analytics.jsx';
@@ -148,6 +149,8 @@ export default class AdminController extends React.Component {
tab = <TeamSettingsTab config={this.state.config} />;
} else if (this.state.selected === 'service_settings') {
tab = <ServiceSettingsTab config={this.state.config} />;
+ } else if (this.state.selected === 'legal_and_support_settings') {
+ tab = <LegalAndSupportSettingsTab config={this.state.config} />;
} else if (this.state.selected === 'team_users') {
if (this.state.teams) {
tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />;
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index cc98c495e..da445da37 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -108,7 +108,7 @@ export default class AdminSidebar extends React.Component {
<a
href='#'
onClick={this.handleClick.bind(this, 'team_users', team.id)}
- className={'nav__sub-menu-item ' + this.isSelected('team_users', team.id)}
+ className={'nav__sub-menu-item ' + this.isSelected('team_users', team.id) + ' ' + this.isSelected('team_analytics', team.id)}
>
{team.name}
<OverlayTrigger
@@ -252,6 +252,15 @@ export default class AdminSidebar extends React.Component {
{'GitLab Settings'}
</a>
</li>
+ <li>
+ <a
+ href='#'
+ className={this.isSelected('legal_and_support_settings')}
+ onClick={this.handleClick.bind(this, 'legal_and_support_settings', null)}
+ >
+ {'Legal and Support Settings'}
+ </a>
+ </li>
</ul>
<ul className='nav nav__sub-menu'>
<li>
diff --git a/web/react/components/admin_console/legal_and_support_settings.jsx b/web/react/components/admin_console/legal_and_support_settings.jsx
new file mode 100644
index 000000000..b00e4b6bd
--- /dev/null
+++ b/web/react/components/admin_console/legal_and_support_settings.jsx
@@ -0,0 +1,222 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Client from '../../utils/client.jsx';
+import * as AsyncClient from '../../utils/async_client.jsx';
+
+export default class LegalAndSupportSettings extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleChange = this.handleChange.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+
+ this.state = {
+ saveNeeded: false,
+ serverError: null
+ };
+ }
+
+ handleChange() {
+ var s = {saveNeeded: true, serverError: this.state.serverError};
+ this.setState(s);
+ }
+
+ handleSubmit(e) {
+ e.preventDefault();
+ $('#save-button').button('loading');
+
+ var config = this.props.config;
+
+ config.SupportSettings.TermsOfServiceLink = ReactDOM.findDOMNode(this.refs.TermsOfServiceLink).value.trim();
+ config.SupportSettings.PrivacyPolicyLink = ReactDOM.findDOMNode(this.refs.PrivacyPolicyLink).value.trim();
+ config.SupportSettings.AboutLink = ReactDOM.findDOMNode(this.refs.AboutLink).value.trim();
+ config.SupportSettings.HelpLink = ReactDOM.findDOMNode(this.refs.HelpLink).value.trim();
+ config.SupportSettings.ReportAProblemLink = ReactDOM.findDOMNode(this.refs.ReportAProblemLink).value.trim();
+ config.SupportSettings.SupportEmail = ReactDOM.findDOMNode(this.refs.SupportEmail).value.trim();
+
+ Client.saveConfig(
+ config,
+ () => {
+ AsyncClient.getConfig();
+ this.setState({
+ serverError: null,
+ saveNeeded: false
+ });
+ $('#save-button').button('reset');
+ },
+ (err) => {
+ this.setState({
+ serverError: err.message,
+ saveNeeded: true
+ });
+ $('#save-button').button('reset');
+ }
+ );
+ }
+
+ render() {
+ var serverError = '';
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
+ }
+
+ var saveClass = 'btn';
+ if (this.state.saveNeeded) {
+ saveClass = 'btn btn-primary';
+ }
+
+ return (
+ <div className='wrapper--fixed'>
+
+ <h3>{'Legal and Support Settings'}</h3>
+ <form
+ className='form-horizontal'
+ role='form'
+ >
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='TermsOfServiceLink'
+ >
+ {'Terms of Service link:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='TermsOfServiceLink'
+ ref='TermsOfServiceLink'
+ defaultValue={this.props.config.SupportSettings.TermsOfServiceLink}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'Link to Terms of Service available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='PrivacyPolicyLink'
+ >
+ {'Privacy Policy link:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='PrivacyPolicyLink'
+ ref='PrivacyPolicyLink'
+ defaultValue={this.props.config.SupportSettings.PrivacyPolicyLink}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'Link to Privacy Policy available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='AboutLink'
+ >
+ {'About link:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='AboutLink'
+ ref='AboutLink'
+ defaultValue={this.props.config.SupportSettings.AboutLink}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'Link to About page for more information on your Mattermost deployment, for example its purpose and audience within your organization. Defaults to Mattermost information page.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='HelpLink'
+ >
+ {'Help link:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='HelpLink'
+ ref='HelpLink'
+ defaultValue={this.props.config.SupportSettings.HelpLink}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'Link to help documentation from team site main menu. Typically not changed unless your organization chooses to create custom documentation.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='ReportAProblemLink'
+ >
+ {'Report a Problem link:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='ReportAProblemLink'
+ ref='ReportAProblemLink'
+ defaultValue={this.props.config.SupportSettings.ReportAProblemLink}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'Link to help documentation from team site main menu. By default this points to the peer-to-peer troubleshooting forum where users can search for, find and request help with technical issues.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='SupportEmail'
+ >
+ {'Support email:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='SupportEmail'
+ ref='SupportEmail'
+ defaultValue={this.props.config.SupportSettings.SupportEmail}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'Email shown during tutorial for end users to ask support questions.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <div className='col-sm-12'>
+ {serverError}
+ <button
+ disabled={!this.state.saveNeeded}
+ type='submit'
+ className={saveClass}
+ onClick={this.handleSubmit}
+ id='save-button'
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
+ >
+ {'Save'}
+ </button>
+ </div>
+ </div>
+
+ </form>
+ </div>
+ );
+ }
+}
+
+LegalAndSupportSettings.propTypes = {
+ config: React.PropTypes.object
+};
diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx
index 908eb709a..1f5faf1d4 100644
--- a/web/react/components/admin_console/service_settings.jsx
+++ b/web/react/components/admin_console/service_settings.jsx
@@ -36,7 +36,7 @@ export default class ServiceSettings extends React.Component {
config.ServiceSettings.SegmentDeveloperKey = ReactDOM.findDOMNode(this.refs.SegmentDeveloperKey).value.trim();
config.ServiceSettings.GoogleDeveloperKey = ReactDOM.findDOMNode(this.refs.GoogleDeveloperKey).value.trim();
config.ServiceSettings.EnableIncomingWebhooks = ReactDOM.findDOMNode(this.refs.EnableIncomingWebhooks).checked;
- config.ServiceSettings.EnableOutgoingWebhooks = React.findDOMNode(this.refs.EnableOutgoingWebhooks).checked;
+ config.ServiceSettings.EnableOutgoingWebhooks = ReactDOM.findDOMNode(this.refs.EnableOutgoingWebhooks).checked;
config.ServiceSettings.EnablePostUsernameOverride = ReactDOM.findDOMNode(this.refs.EnablePostUsernameOverride).checked;
config.ServiceSettings.EnablePostIconOverride = ReactDOM.findDOMNode(this.refs.EnablePostIconOverride).checked;
config.ServiceSettings.EnableTesting = ReactDOM.findDOMNode(this.refs.EnableTesting).checked;
diff --git a/web/react/components/edit_channel_header_modal.jsx b/web/react/components/edit_channel_header_modal.jsx
index 209e30fcc..e4817f6e4 100644
--- a/web/react/components/edit_channel_header_modal.jsx
+++ b/web/react/components/edit_channel_header_modal.jsx
@@ -1,8 +1,9 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import * as Client from '../utils/client.jsx';
-import * as AsyncClient from '../utils/async_client.jsx';
+import Constants from '../utils/constants.jsx';
import * as Utils from '../utils/utils.jsx';
const Modal = ReactBootstrap.Modal;
@@ -11,12 +12,14 @@ export default class EditChannelHeaderModal extends React.Component {
constructor(props) {
super(props);
- this.handleEdit = this.handleEdit.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
this.onShow = this.onShow.bind(this);
this.onHide = this.onHide.bind(this);
this.state = {
+ header: props.channel.header,
serverError: ''
};
}
@@ -27,27 +30,38 @@ export default class EditChannelHeaderModal extends React.Component {
}
}
+ componentWillReceiveProps(nextProps) {
+ if (this.props.channel.header !== nextProps.channel.header) {
+ this.setState({
+ header: nextProps.channel.header
+ });
+ }
+ }
+
componentDidUpdate(prevProps) {
if (this.props.show && !prevProps.show) {
this.onShow();
}
}
- handleEdit() {
- var data = {};
- data.channel_id = this.props.channel.id;
-
- if (data.channel_id.length !== 26) {
- return;
- }
-
- data.channel_header = ReactDOM.findDOMNode(this.refs.textarea).value;
+ handleChange(e) {
+ this.setState({
+ header: e.target.value
+ });
+ }
- Client.updateChannelHeader(data,
- () => {
+ handleSubmit() {
+ Client.updateChannelHeader(
+ this.props.channel.id,
+ this.state.header,
+ (channel) => {
this.setState({serverError: ''});
- AsyncClient.getChannel(this.props.channel.id);
this.onHide();
+
+ AppDispatcher.handleServerAction({
+ type: Constants.ActionTypes.RECIEVED_CHANNEL,
+ channel
+ });
},
(err) => {
if (err.message === 'Invalid channel_header parameter') {
@@ -66,7 +80,8 @@ export default class EditChannelHeaderModal extends React.Component {
onHide() {
this.setState({
- serverError: ''
+ serverError: '',
+ header: this.props.channel.header
});
this.props.onHide();
@@ -94,7 +109,8 @@ export default class EditChannelHeaderModal extends React.Component {
rows='6'
id='edit_header'
maxLength='1024'
- defaultValue={this.props.channel.header}
+ value={this.state.header}
+ onChange={this.handleChange}
/>
{serverError}
</Modal.Body>
@@ -102,14 +118,14 @@ export default class EditChannelHeaderModal extends React.Component {
<button
type='button'
className='btn btn-default'
- onClick={this.props.onHide}
+ onClick={this.onHide}
>
{'Cancel'}
</button>
<button
type='button'
className='btn btn-primary'
- onClick={this.handleEdit}
+ onClick={this.handleSubmit}
>
{'Save'}
</button>
diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx
index 649ec7321..56bc00a7e 100644
--- a/web/react/components/invite_member_modal.jsx
+++ b/web/react/components/invite_member_modal.jsx
@@ -33,6 +33,7 @@ export default class InviteMemberModal extends React.Component {
firstNameErrors: {},
lastNameErrors: {},
emailEnabled: global.window.mm_config.SendEmailNotifications === 'true',
+ userCreationEnabled: global.window.mm_config.EnableUserCreation === 'true',
showConfirmModal: false,
isSendingEmails: false
};
@@ -252,7 +253,7 @@ export default class InviteMemberModal extends React.Component {
ref={'first_name' + index}
placeholder='First name'
maxLength='64'
- disabled={!this.state.emailEnabled}
+ disabled={!this.state.emailEnabled || !this.state.userCreationEnabled}
spellCheck='false'
/>
{firstNameError}
@@ -266,7 +267,7 @@ export default class InviteMemberModal extends React.Component {
ref={'last_name' + index}
placeholder='Last name'
maxLength='64'
- disabled={!this.state.emailEnabled}
+ disabled={!this.state.emailEnabled || !this.state.userCreationEnabled}
spellCheck='false'
/>
{lastNameError}
@@ -285,7 +286,7 @@ export default class InviteMemberModal extends React.Component {
className='form-control'
placeholder='email@domain.com'
maxLength='64'
- disabled={!this.state.emailEnabled}
+ disabled={!this.state.emailEnabled || !this.state.userCreationEnabled}
spellCheck='false'
/>
{emailError}
@@ -303,7 +304,7 @@ export default class InviteMemberModal extends React.Component {
var content = null;
var sendButton = null;
- if (this.state.emailEnabled) {
+ if (this.state.emailEnabled && this.state.userCreationEnabled) {
content = (
<div>
{serverError}
@@ -337,7 +338,7 @@ export default class InviteMemberModal extends React.Component {
{sendButtonLabel}
</button>
);
- } else {
+ } else if (this.state.userCreationEnabled) {
var teamInviteLink = null;
if (currentUser && TeamStore.getCurrent().type === 'O') {
var link = (
@@ -358,10 +359,16 @@ export default class InviteMemberModal extends React.Component {
content = (
<div>
- <p>Email is currently disabled for your team, and email invitations cannot be sent. Contact your system administrator to enable email and email invitations.</p>
+ <p>{'Email is currently disabled for your team, and email invitations cannot be sent. Contact your system administrator to enable email and email invitations.'}</p>
{teamInviteLink}
</div>
);
+ } else {
+ content = (
+ <div>
+ <p>{'User creation has been disabled for your team. Please ask your team administrator for details.'}</p>
+ </div>
+ );
}
return (
diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx
index cf40af6ae..3661b19e6 100644
--- a/web/react/components/more_direct_channels.jsx
+++ b/web/react/components/more_direct_channels.jsx
@@ -47,6 +47,21 @@ export default class MoreDirectChannels extends React.Component {
UserStore.addChangeListener(this.handleUserChange);
}
+ componentDidUpdate(prevProps) {
+ if (!prevProps.show && this.props.show) {
+ this.onShow();
+ }
+ }
+
+ onShow() {
+ if (Utils.isMobile()) {
+ $(ReactDOM.findDOMNode(this.refs.userList)).css('max-height', $(window).height() - 250);
+ } else {
+ $(ReactDOM.findDOMNode(this.refs.userList)).perfectScrollbar();
+ $(ReactDOM.findDOMNode(this.refs.userList)).css('max-height', $(window).height() - 300);
+ }
+ }
+
handleFilterChange() {
const filter = ReactDOM.findDOMNode(this.refs.filter).value;
@@ -164,15 +179,6 @@ export default class MoreDirectChannels extends React.Component {
);
}
- componentDidUpdate(prevProps) {
- if (!prevProps.show && this.props.show) {
- $(ReactDOM.findDOMNode(this.refs.userList)).css('max-height', $(window).height() - 50);
- if ($(window).width() > 768) {
- $(ReactDOM.findDOMNode(this.refs.userList)).perfectScrollbar();
- }
- }
- }
-
render() {
if (!this.props.show) {
return null;
@@ -217,8 +223,8 @@ export default class MoreDirectChannels extends React.Component {
<Modal.Header closeButton={true}>
<Modal.Title>{'Direct Messages'}</Modal.Title>
</Modal.Header>
- <Modal.Body>
- <div className='row filter-row'>
+ <Modal.Body ref='modalBody'>
+ <div className='filter-row'>
<div className='col-sm-6'>
<input
ref='filter'
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
index c286ee6f9..d4ec5a5f5 100644
--- a/web/react/components/navbar_dropdown.jsx
+++ b/web/react/components/navbar_dropdown.jsx
@@ -184,6 +184,34 @@ export default class NavbarDropdown extends React.Component {
);
}
+ let helpLink = null;
+ if (global.window.mm_config.HelpLink) {
+ helpLink = (
+ <li>
+ <a
+ target='_blank'
+ href={global.window.mm_config.HelpLink}
+ >
+ {'Help'}
+ </a>
+ </li>
+ );
+ }
+
+ let reportLink = null;
+ if (global.window.mm_config.ReportAProblemLink) {
+ reportLink = (
+ <li>
+ <a
+ target='_blank'
+ href={global.window.mm_config.ReportAProblemLink}
+ >
+ {'Report a Problem'}
+ </a>
+ </li>
+ );
+ }
+
return (
<ul className='nav navbar-nav navbar-right'>
<li
@@ -230,22 +258,8 @@ export default class NavbarDropdown extends React.Component {
{sysAdminLink}
{teams}
<li className='divider'></li>
- <li>
- <a
- target='_blank'
- href='/static/help/help.html'
- >
- {'Help'}
- </a>
- </li>
- <li>
- <a
- target='_blank'
- href='/static/help/report_problem.html'
- >
- {'Report a Problem'}
- </a>
- </li>
+ {helpLink}
+ {reportLink}
<li>
<a
href='#'
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 3d7f449d1..8393440cb 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -71,49 +71,47 @@ export default class Sidebar extends React.Component {
getStateFromStores() {
const members = ChannelStore.getAllMembers();
const currentChannelId = ChannelStore.getCurrentId();
+ const currentUserId = UserStore.getCurrentId();
const channels = Object.assign([], ChannelStore.getAll());
channels.sort((a, b) => a.display_name.localeCompare(b.display_name));
const publicChannels = channels.filter((channel) => channel.type === Constants.OPEN_CHANNEL);
const privateChannels = channels.filter((channel) => channel.type === Constants.PRIVATE_CHANNEL);
- const directChannels = channels.filter((channel) => channel.type === Constants.DM_CHANNEL);
const preferences = PreferenceStore.getPreferences(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
- var visibleDirectChannels = [];
- for (var i = 0; i < directChannels.length; i++) {
- const dm = directChannels[i];
- const teammate = Utils.getDirectTeammate(dm.id);
- if (!teammate) {
+ const directChannels = [];
+ for (const preference of preferences) {
+ if (preference.value !== 'true') {
continue;
}
- const member = members[dm.id];
- const msgCount = dm.total_msg_count - member.msg_count;
+ const teammateId = preference.name;
- // always show a channel if either it is the current one or if it is unread, but it is not currently being left
- const forceShow = (currentChannelId === dm.id || msgCount > 0) && !this.isLeaving.get(dm.id);
- const preferenceShow = preferences.some((preference) => (preference.name === teammate.id && preference.value !== 'false'));
+ let directChannel = channels.find(Utils.isDirectChannelForUser.bind(null, teammateId));
- if (preferenceShow || forceShow) {
- dm.display_name = Utils.displayUsername(teammate.id);
- dm.teammate_id = teammate.id;
- dm.status = UserStore.getStatus(teammate.id);
+ // a direct channel doesn't exist yet so create a fake one
+ if (!directChannel) {
+ directChannel = {
+ name: Utils.getDirectChannelName(currentUserId, teammateId),
+ last_post_at: 0,
+ total_msg_count: 0,
+ type: Constants.DM_CHANNEL,
+ fake: true
+ };
+ }
- visibleDirectChannels.push(dm);
+ directChannel.display_name = Utils.displayUsername(teammateId);
+ directChannel.teammate_id = teammateId;
+ directChannel.status = UserStore.getStatus(teammateId);
- if (forceShow && !preferenceShow) {
- // make sure that unread direct channels are visible
- const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, teammate.id, 'true');
- AsyncClient.savePreferences([preference]);
- }
- }
+ directChannels.push(directChannel);
}
- const hiddenDirectChannelCount = UserStore.getActiveOnlyProfileList(true).length - visibleDirectChannels.length;
+ directChannels.sort(this.sortChannelsByDisplayName);
- visibleDirectChannels.sort(this.sortChannelsByDisplayName);
+ const hiddenDirectChannelCount = UserStore.getActiveOnlyProfileList(true).length - directChannels.length;
const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
@@ -122,7 +120,7 @@ export default class Sidebar extends React.Component {
members,
publicChannels,
privateChannels,
- visibleDirectChannels,
+ directChannels,
hiddenDirectChannelCount,
unreadCounts: JSON.parse(JSON.stringify(ChannelStore.getUnreadCounts())),
showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.CHANNEL_POPOVER
@@ -484,7 +482,7 @@ export default class Sidebar extends React.Component {
const privateChannelItems = this.state.privateChannels.map(this.createChannelElement);
- const directMessageItems = this.state.visibleDirectChannels.map((channel, index, arr) => {
+ const directMessageItems = this.state.directChannels.map((channel, index, arr) => {
return this.createChannelElement(channel, index, arr, this.handleLeaveDirectChannel);
});
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index d93d146d8..20c2bf696 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -49,7 +49,7 @@ export default class SidebarRightMenu extends React.Component {
href='#'
onClick={EventHelpers.showInviteMemberModal}
>
- <i className='fa fa-user'></i>Invite New Member
+ <i className='fa fa-user'></i>{'Invite New Member'}
</a>
</li>
);
@@ -75,7 +75,7 @@ export default class SidebarRightMenu extends React.Component {
href='#'
data-toggle='modal'
data-target='#team_settings'
- ><i className='fa fa-globe'></i>Team Settings</a>
+ ><i className='fa fa-globe'></i>{'Team Settings'}</a>
</li>
);
manageLink = (
@@ -93,7 +93,7 @@ export default class SidebarRightMenu extends React.Component {
<a
href={'/admin_console?' + utils.getSessionIndex()}
>
- <i className='fa fa-wrench'></i>System Console</a>
+ <i className='fa fa-wrench'></i>{'System Console'}</a>
</li>
);
}
@@ -107,6 +107,27 @@ export default class SidebarRightMenu extends React.Component {
teamDisplayName = this.props.teamDisplayName;
}
+ let helpLink = null;
+ if (global.window.mm_config.HelpLink) {
+ helpLink = (
+ <li>
+ <a
+ target='_blank'
+ href={global.window.mm_config.HelpLink}
+ ><i className='fa fa-question'></i>{'Help'}</a></li>
+ );
+ }
+
+ let reportLink = null;
+ if (global.window.mm_config.ReportAProblemLink) {
+ reportLink = (
+ <li>
+ <a
+ target='_blank'
+ href={global.window.mm_config.ReportAProblemLink}
+ ><i className='fa fa-phone'></i>{'Report a Problem'}</a></li>
+ );
+ }
return (
<div>
<div className='team__header theme'>
@@ -123,7 +144,7 @@ export default class SidebarRightMenu extends React.Component {
href='#'
onClick={() => this.setState({showUserSettingsModal: true})}
>
- <i className='fa fa-cog'></i>Account Settings
+ <i className='fa fa-cog'></i>{'Account Settings'}
</a>
</li>
{teamSettingsLink}
@@ -135,18 +156,10 @@ export default class SidebarRightMenu extends React.Component {
<a
href='#'
onClick={this.handleLogoutClick}
- ><i className='fa fa-sign-out'></i>Logout</a></li>
+ ><i className='fa fa-sign-out'></i>{'Logout'}</a></li>
<li className='divider'></li>
- <li>
- <a
- target='_blank'
- href='/static/help/help.html'
- ><i className='fa fa-question'></i>Help</a></li>
- <li>
- <a
- target='_blank'
- href='/static/help/report_problem.html'
- ><i className='fa fa-phone'></i>Report a Problem</a></li>
+ {helpLink}
+ {reportLink}
</ul>
</div>
<UserSettingsModal
diff --git a/web/react/components/team_general_tab.jsx b/web/react/components/team_general_tab.jsx
index 03715d585..dc615f2e8 100644
--- a/web/react/components/team_general_tab.jsx
+++ b/web/react/components/team_general_tab.jsx
@@ -12,6 +12,7 @@ export default class GeneralTab extends React.Component {
constructor(props) {
super(props);
+ this.updateSection = this.updateSection.bind(this);
this.handleNameSubmit = this.handleNameSubmit.bind(this);
this.handleInviteIdSubmit = this.handleInviteIdSubmit.bind(this);
this.handleOpenInviteSubmit = this.handleOpenInviteSubmit.bind(this);
@@ -27,11 +28,22 @@ export default class GeneralTab extends React.Component {
this.handleTeamListingRadio = this.handleTeamListingRadio.bind(this);
this.handleGenerateInviteId = this.handleGenerateInviteId.bind(this);
- this.state = {
- name: props.team.display_name,
- invite_id: props.team.invite_id,
- allow_open_invite: props.team.allow_open_invite,
- allow_team_listing: props.team.allow_team_listing,
+ this.state = this.setupInitialState(props);
+ }
+
+ updateSection(section) {
+ this.setState(this.setupInitialState(this.props));
+ this.props.updateSection(section);
+ }
+
+ setupInitialState(props) {
+ const team = props.team;
+
+ return {
+ name: team.display_name,
+ invite_id: team.invite_id,
+ allow_open_invite: team.allow_open_invite,
+ allow_team_listing: team.allow_team_listing,
serverError: '',
clientError: ''
};
@@ -71,7 +83,7 @@ export default class GeneralTab extends React.Component {
(team) => {
TeamStore.saveTeam(team);
TeamStore.emitChange();
- this.props.updateSection('');
+ this.updateSection('');
},
(err) => {
state.serverError = err.message;
@@ -91,7 +103,7 @@ export default class GeneralTab extends React.Component {
(team) => {
TeamStore.saveTeam(team);
TeamStore.emitChange();
- this.props.updateSection('');
+ this.updateSection('');
},
(err) => {
state.serverError = err.message;
@@ -129,7 +141,7 @@ export default class GeneralTab extends React.Component {
(team) => {
TeamStore.saveTeam(team);
TeamStore.emitChange();
- this.props.updateSection('');
+ this.updateSection('');
},
(err) => {
state.serverError = err.message;
@@ -164,7 +176,7 @@ export default class GeneralTab extends React.Component {
(team) => {
TeamStore.saveTeam(team);
TeamStore.emitChange();
- this.props.updateSection('');
+ this.updateSection('');
},
(err) => {
state.serverError = err.message;
@@ -180,8 +192,7 @@ export default class GeneralTab extends React.Component {
}
handleClose() {
- this.setState({clientError: '', serverError: ''});
- this.props.updateSection('');
+ this.updateSection('');
}
componentDidMount() {
@@ -195,36 +206,36 @@ export default class GeneralTab extends React.Component {
onUpdateNameSection(e) {
e.preventDefault();
if (this.props.activeSection === 'name') {
- this.props.updateSection('');
+ this.updateSection('');
} else {
- this.props.updateSection('name');
+ this.updateSection('name');
}
}
onUpdateInviteIdSection(e) {
e.preventDefault();
if (this.props.activeSection === 'invite_id') {
- this.props.updateSection('');
+ this.updateSection('');
} else {
- this.props.updateSection('invite_id');
+ this.updateSection('invite_id');
}
}
onUpdateOpenInviteSection(e) {
e.preventDefault();
if (this.props.activeSection === 'open_invite') {
- this.props.updateSection('');
+ this.updateSection('');
} else {
- this.props.updateSection('open_invite');
+ this.updateSection('open_invite');
}
}
onUpdateTeamListingSection(e) {
e.preventDefault();
if (this.props.activeSection === 'team_listing') {
- this.props.updateSection('');
+ this.updateSection('');
} else {
- this.props.updateSection('team_listing');
+ this.updateSection('team_listing');
}
}
@@ -248,44 +259,59 @@ export default class GeneralTab extends React.Component {
serverError = this.state.serverError;
}
+ const enableTeamListing = global.window.mm_config.EnableTeamListing === 'true';
+
let teamListingSection;
if (this.props.activeSection === 'team_listing') {
- const inputs = [
- <div key='userTeamListingOptions'>
- <div className='radio'>
- <label>
- <input
- name='userTeamListingOptions'
- type='radio'
- defaultChecked={this.state.allow_team_listing}
- onChange={this.handleTeamListingRadio.bind(this, true)}
- />
- {'Yes'}
- </label>
- <br/>
+ const inputs = [];
+ let submitHandle = null;
+
+ if (enableTeamListing) {
+ submitHandle = this.handleTeamListingSubmit;
+
+ inputs.push(
+ <div key='userTeamListingOptions'>
+ <div className='radio'>
+ <label>
+ <input
+ name='userTeamListingOptions'
+ type='radio'
+ defaultChecked={this.state.allow_team_listing}
+ onChange={this.handleTeamListingRadio.bind(this, true)}
+ />
+ {'Yes'}
+ </label>
+ <br/>
+ </div>
+ <div className='radio'>
+ <label>
+ <input
+ ref='teamListingRadioNo'
+ name='userTeamListingOptions'
+ type='radio'
+ defaultChecked={!this.state.allow_team_listing}
+ onChange={this.handleTeamListingRadio.bind(this, false)}
+ />
+ {'No'}
+ </label>
+ <br/>
+ </div>
+ <div><br/>{'Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.'}</div>
</div>
- <div className='radio'>
- <label>
- <input
- ref='teamListingRadioNo'
- name='userTeamListingOptions'
- type='radio'
- defaultChecked={!this.state.allow_team_listing}
- onChange={this.handleTeamListingRadio.bind(this, false)}
- />
- {'No'}
- </label>
- <br/>
+ );
+ } else {
+ inputs.push(
+ <div key='userTeamListingOptions'>
+ <div><br/>{'Contact your system administrator to turn on the team directory on the system home page.'}</div>
</div>
- <div><br/>{'Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.'}</div>
- </div>
- ];
+ );
+ }
teamListingSection = (
<SettingItemMax
title='Include this team in the Team Directory'
inputs={inputs}
- submit={this.handleTeamListingSubmit}
+ submit={submitHandle}
server_error={serverError}
client_error={clientError}
updateSection={this.onUpdateTeamListingSection}
@@ -293,10 +319,15 @@ export default class GeneralTab extends React.Component {
);
} else {
let describe = '';
- if (this.state.allow_team_listing === true) {
- describe = 'Yes';
+
+ if (enableTeamListing) {
+ if (this.state.allow_team_listing === true) {
+ describe = 'Yes';
+ } else {
+ describe = 'No';
+ }
} else {
- describe = 'No';
+ describe = 'Team directory is turned off for this system.';
}
teamListingSection = (
diff --git a/web/react/components/tutorial/tutorial_intro_screens.jsx b/web/react/components/tutorial/tutorial_intro_screens.jsx
index 9360d31f8..7ab1e5512 100644
--- a/web/react/components/tutorial/tutorial_intro_screens.jsx
+++ b/web/react/components/tutorial/tutorial_intro_screens.jsx
@@ -112,23 +112,30 @@ export default class TutorialIntroScreens extends React.Component {
const circles = this.createCircles();
- return (
- <div>
- <h3>{'You’re all set'}</h3>
- <p>
- {inviteModalLink}
- {' when you’re ready.'}
- </p>
+ let supportInfo = null;
+ if (global.window.mm_config.SupportEmail) {
+ supportInfo = (
<p>
{'Need anything, just email us at '}
<a
- href='mailto:feedback@mattermost.com'
+ href={'mailto:' + global.window.mm_config.SupportEmail}
target='_blank'
>
- {'feedback@mattermost.com'}
+ {global.window.mm_config.SupportEmail}
</a>
{'.'}
</p>
+ );
+ }
+
+ return (
+ <div>
+ <h3>{'You’re all set'}</h3>
+ <p>
+ {inviteModalLink}
+ {' when you’re ready.'}
+ </p>
+ {supportInfo}
{'Click “Next” to enter Town Square. This is the first channel teammates see when they sign up. Use it for posting updates everyone needs to know.'}
{circles}
</div>
diff --git a/web/react/components/user_settings/custom_theme_chooser.jsx b/web/react/components/user_settings/custom_theme_chooser.jsx
index 35f836adb..778c74c23 100644
--- a/web/react/components/user_settings/custom_theme_chooser.jsx
+++ b/web/react/components/user_settings/custom_theme_chooser.jsx
@@ -3,6 +3,9 @@
import Constants from '../../utils/constants.jsx';
+const OverlayTrigger = ReactBootstrap.OverlayTrigger;
+const Popover = ReactBootstrap.Popover;
+
export default class CustomThemeChooser extends React.Component {
constructor(props) {
super(props);
@@ -72,6 +75,19 @@ export default class CustomThemeChooser extends React.Component {
);
});
+ var popoverContent = (
+ <Popover
+ bsStyle='info'
+ id='code-popover'
+ className='code-popover'
+ >
+ <img
+ width='200'
+ src={'/static/images/themes/code_themes/' + theme[element.id] + 'Large.png'}
+ />
+ </Popover>
+ );
+
elements.push(
<div
className='col-sm-4 form-group'
@@ -90,11 +106,17 @@ export default class CustomThemeChooser extends React.Component {
>
{codeThemeOptions}
</select>
+ <OverlayTrigger
+ placement='top'
+ overlay={popoverContent}
+ ref='headerOverlay'
+ >
<span className='input-group-addon'>
<img
src={'/static/images/themes/code_themes/' + theme[element.id] + '.png'}
/>
</span>
+ </OverlayTrigger>
</div>
</div>
);
diff --git a/web/react/components/user_settings/manage_outgoing_hooks.jsx b/web/react/components/user_settings/manage_outgoing_hooks.jsx
index 9c0fe3709..ede639691 100644
--- a/web/react/components/user_settings/manage_outgoing_hooks.jsx
+++ b/web/react/components/user_settings/manage_outgoing_hooks.jsx
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import LoadingScreen from '../loading_screen.jsx';
@@ -36,7 +36,7 @@ export default class ManageOutgoingHooks extends React.Component {
if (this.state.triggerWords.length !== 0) {
hook.trigger_words = this.state.triggerWords.trim().split(',');
}
- hook.callback_urls = this.state.callbackURLs.split('\n');
+ hook.callback_urls = this.state.callbackURLs.split('\n').map((url) => url.trim());
Client.addOutgoingHook(
hook,
diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx
index 962efd7a2..7c1a1297f 100644
--- a/web/react/components/user_settings/user_settings_general.jsx
+++ b/web/react/components/user_settings/user_settings_general.jsx
@@ -51,7 +51,7 @@ export default class UserSettingsGeneralTab extends React.Component {
}
if (user.username === username) {
- this.setState({clientError: 'You must submit a new username.', emailError: '', serverError: ''});
+ this.updateSection('');
return;
}
@@ -66,7 +66,7 @@ export default class UserSettingsGeneralTab extends React.Component {
const nickname = this.state.nickname.trim();
if (user.nickname === nickname) {
- this.setState({clientError: 'You must submit a new nickname.', emailError: '', serverError: ''});
+ this.updateSection('');
return;
}
@@ -82,7 +82,7 @@ export default class UserSettingsGeneralTab extends React.Component {
const lastName = this.state.lastName.trim();
if (user.first_name === firstName && user.last_name === lastName) {
- this.setState({clientError: 'You must submit a new first or last name.', emailError: '', serverError: ''});
+ this.updateSection('');
return;
}
@@ -98,10 +98,6 @@ export default class UserSettingsGeneralTab extends React.Component {
const email = this.state.email.trim().toLowerCase();
const confirmEmail = this.state.confirmEmail.trim().toLowerCase();
- if (user.email === email) {
- return;
- }
-
if (email === '' || !Utils.isEmail(email)) {
this.setState({emailError: 'Please enter a valid email address.', clientError: '', serverError: ''});
return;
@@ -112,6 +108,11 @@ export default class UserSettingsGeneralTab extends React.Component {
return;
}
+ if (user.email === email) {
+ this.updateSection('');
+ return;
+ }
+
user.email = email;
this.submitUser(user, true);
}
diff --git a/web/react/dispatcher/event_helpers.jsx b/web/react/dispatcher/event_helpers.jsx
index fa4d1d64f..a03923c1a 100644
--- a/web/react/dispatcher/event_helpers.jsx
+++ b/web/react/dispatcher/event_helpers.jsx
@@ -172,3 +172,10 @@ export function emitClearSuggestions(suggestionId) {
id: suggestionId
});
}
+
+export function emitPreferenceChangedEvent(preference) {
+ AppDispatcher.handleServerAction({
+ type: Constants.ActionTypes.RECIEVED_PREFERENCE,
+ preference
+ });
+}
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index 0bfde77b4..afc960fcf 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -317,7 +317,9 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
case ActionTypes.RECIEVED_CHANNEL:
ChannelStore.pStoreChannel(action.channel);
- ChannelStore.pStoreChannelMember(action.member);
+ if (action.member) {
+ ChannelStore.pStoreChannelMember(action.member);
+ }
currentId = ChannelStore.getCurrentId();
if (currentId) {
ChannelStore.resetCounts(currentId);
diff --git a/web/react/stores/preference_store.jsx b/web/react/stores/preference_store.jsx
index 068bc29c2..543129aca 100644
--- a/web/react/stores/preference_store.jsx
+++ b/web/react/stores/preference_store.jsx
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import Constants from '../utils/constants.jsx';
@@ -90,8 +90,8 @@ class PreferenceStoreClass extends EventEmitter {
return preference;
}
- emitChange(preferences) {
- this.emit(CHANGE_EVENT, preferences);
+ emitChange() {
+ this.emit(CHANGE_EVENT);
}
addChangeListener(callback) {
@@ -106,6 +106,12 @@ class PreferenceStoreClass extends EventEmitter {
const action = payload.action;
switch (action.type) {
+ case ActionTypes.RECIEVED_PREFERENCE: {
+ const preference = action.preference;
+ this.setPreference(preference.category, preference.name, preference.value);
+ this.emitChange();
+ break;
+ }
case ActionTypes.RECIEVED_PREFERENCES: {
const preferences = this.getAllPreferences();
@@ -114,7 +120,7 @@ class PreferenceStoreClass extends EventEmitter {
}
this.setAllPreferences(preferences);
- this.emitChange(preferences);
+ this.emitChange();
break;
}
}
diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx
index d5aed40cf..24fa79ca6 100644
--- a/web/react/stores/socket_store.jsx
+++ b/web/react/stores/socket_store.jsx
@@ -136,6 +136,10 @@ class SocketStoreClass extends EventEmitter {
handleChannelViewedEvent(msg);
break;
+ case SocketEvents.PREFERENCE_CHANGED:
+ handlePreferenceChangedEvent(msg);
+ break;
+
default:
}
}
@@ -281,6 +285,11 @@ function handleChannelViewedEvent(msg) {
}
}
+function handlePreferenceChangedEvent(msg) {
+ const preference = JSON.parse(msg.props.preference);
+ EventHelpers.emitPreferenceChangedEvent(preference);
+}
+
var SocketStore = new SocketStoreClass();
/*SocketStore.dispatchToken = AppDispatcher.register((payload) => {
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 09e962161..5d02a8c88 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -590,7 +590,12 @@ export function updateChannel(channel, success, error) {
track('api', 'api_channels_update');
}
-export function updateChannelHeader(data, success, error) {
+export function updateChannelHeader(channelId, header, success, error) {
+ const data = {
+ channel_id: channelId,
+ channel_header: header
+ };
+
$.ajax({
url: '/api/v1/channels/update_header',
dataType: 'json',
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 891bc4624..5f027a409 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -35,6 +35,7 @@ export default {
RECIEVED_AUDITS: null,
RECIEVED_TEAMS: null,
RECIEVED_STATUSES: null,
+ RECIEVED_PREFERENCE: null,
RECIEVED_PREFERENCES: null,
RECIEVED_MSG: null,
@@ -74,7 +75,8 @@ export default {
NEW_USER: 'new_user',
USER_ADDED: 'user_added',
USER_REMOVED: 'user_removed',
- TYPING: 'typing'
+ TYPING: 'typing',
+ PREFERENCE_CHANGED: 'preference_changed'
},
//SPECIAL_MENTIONS: ['all', 'channel'],
@@ -164,29 +166,6 @@ export default {
UPDATE_TYPING_MS: 5000,
THEMES: {
default: {
- type: 'Mattermost',
- sidebarBg: '#fafafa',
- sidebarText: '#333333',
- sidebarUnreadText: '#333333',
- sidebarTextHoverBg: '#e6f2fa',
- sidebarTextActiveBorder: '#378FD2',
- sidebarTextActiveColor: '#111111',
- sidebarHeaderBg: '#2389d7',
- sidebarHeaderTextColor: '#ffffff',
- onlineIndicator: '#7DBE00',
- mentionBj: '#2389d7',
- mentionColor: '#ffffff',
- centerChannelBg: '#ffffff',
- centerChannelColor: '#333333',
- newMessageSeparator: '#FF8800',
- linkColor: '#2389d7',
- buttonBg: '#2389d7',
- buttonColor: '#FFFFFF',
- mentionHighlightBg: '#fff2bb',
- mentionHighlightLink: '#2f81b7',
- codeTheme: 'github'
- },
- organization: {
type: 'Organization',
sidebarBg: '#2071a7',
sidebarText: '#fff',
@@ -209,6 +188,29 @@ export default {
mentionHighlightLink: '#2f81b7',
codeTheme: 'github'
},
+ mattermost: {
+ type: 'Mattermost',
+ sidebarBg: '#fafafa',
+ sidebarText: '#333333',
+ sidebarUnreadText: '#333333',
+ sidebarTextHoverBg: '#e6f2fa',
+ sidebarTextActiveBorder: '#378FD2',
+ sidebarTextActiveColor: '#111111',
+ sidebarHeaderBg: '#2389d7',
+ sidebarHeaderTextColor: '#ffffff',
+ onlineIndicator: '#7DBE00',
+ mentionBj: '#2389d7',
+ mentionColor: '#ffffff',
+ centerChannelBg: '#ffffff',
+ centerChannelColor: '#333333',
+ newMessageSeparator: '#FF8800',
+ linkColor: '#2389d7',
+ buttonBg: '#2389d7',
+ buttonColor: '#FFFFFF',
+ mentionHighlightBg: '#fff2bb',
+ mentionHighlightLink: '#2f81b7',
+ codeTheme: 'github'
+ },
mattermostDark: {
type: 'Mattermost Dark',
sidebarBg: '#1B2C3E',
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index c2e4276b0..fb8b89252 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -1138,6 +1138,11 @@ export function getUserIdFromChannelName(channel) {
return otherUserId;
}
+// Returns true if the given channel is a direct channel between the current user and the given one
+export function isDirectChannelForUser(otherUserId, channel) {
+ return channel.type === Constants.DM_CHANNEL && getUserIdFromChannelName(channel) === otherUserId;
+}
+
export function importSlack(file, success, error) {
var formData = new FormData();
formData.append('file', file, file.name);
diff --git a/web/sass-files/sass/partials/_docs.scss b/web/sass-files/sass/partials/_docs.scss
new file mode 100644
index 000000000..f4e7cc314
--- /dev/null
+++ b/web/sass-files/sass/partials/_docs.scss
@@ -0,0 +1,19 @@
+@charset "UTF-8";
+
+.docs__page {
+ line-height: 1.7;
+ padding-bottom: 20px;
+
+ > div {
+ width: 1170px;
+ margin: 0 auto;
+ padding: 0 15px;
+ max-width: 100%;
+ }
+
+ h1.markdown__heading {
+ border-bottom: 1px solid #ddd;
+ padding-bottom: 1rem;
+ margin: 1em 0 1em;
+ }
+} \ No newline at end of file
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index a082ffb22..6f70e502f 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -381,9 +381,9 @@
.more-modal {
.user-list {
+ overflow-y: auto;
+ overflow-x: hidden;
margin-top: 10px;
- overflow: auto;
- -webkit-overflow-scrolling: touch;
max-height: 500px;
position: relative;
}
@@ -394,11 +394,12 @@
.modal-body {
padding: 10px 0 20px;
+ overflow-x: hidden;
}
.filter-row {
margin-top: 10px;
- padding: 0 15px;
+ @include clearfix;
}
.member-count {
diff --git a/web/sass-files/sass/partials/_popover.scss b/web/sass-files/sass/partials/_popover.scss
index bc55b7ff7..1ae07fe5b 100644
--- a/web/sass-files/sass/partials/_popover.scss
+++ b/web/sass-files/sass/partials/_popover.scss
@@ -10,6 +10,10 @@
display: inline-block;
}
+.code-popover .popover-content {
+ padding: 5px;
+}
+
.user-popover__image {
margin: 0 0 10px;
@include border-radius(128px);
diff --git a/web/sass-files/sass/styles.scss b/web/sass-files/sass/styles.scss
index c93372175..7bf3574d2 100644
--- a/web/sass-files/sass/styles.scss
+++ b/web/sass-files/sass/styles.scss
@@ -49,6 +49,9 @@
// Responsive Css
@import "partials/responsive";
+// Docs Css
+@import "partials/docs";
+
// Standalone Css
@import "partials/oauth";
diff --git a/web/static/images/themes/code_themes/githubLarge.png b/web/static/images/themes/code_themes/githubLarge.png
new file mode 100644
index 000000000..cffc6e012
--- /dev/null
+++ b/web/static/images/themes/code_themes/githubLarge.png
Binary files differ
diff --git a/web/static/images/themes/code_themes/monokaiLarge.png b/web/static/images/themes/code_themes/monokaiLarge.png
new file mode 100644
index 000000000..7224950af
--- /dev/null
+++ b/web/static/images/themes/code_themes/monokaiLarge.png
Binary files differ
diff --git a/web/static/images/themes/code_themes/solarized_darkLarge.png b/web/static/images/themes/code_themes/solarized_darkLarge.png
new file mode 100644
index 000000000..582df48f9
--- /dev/null
+++ b/web/static/images/themes/code_themes/solarized_darkLarge.png
Binary files differ
diff --git a/web/static/images/themes/code_themes/solarized_lightLarge.png b/web/static/images/themes/code_themes/solarized_lightLarge.png
new file mode 100644
index 000000000..d2c2702fb
--- /dev/null
+++ b/web/static/images/themes/code_themes/solarized_lightLarge.png
Binary files differ
diff --git a/web/templates/docs.html b/web/templates/docs.html
index 21659e810..0e0f51648 100644
--- a/web/templates/docs.html
+++ b/web/templates/docs.html
@@ -7,7 +7,7 @@
<div class="inner__wrap">
<div class="row content">
<div class="col-sm-12">
- <div id="docs"></div>
+ <div class="docs__page" id="docs"></div>
</div>
<div class="footer-push"></div>
</div>
diff --git a/web/templates/footer.html b/web/templates/footer.html
index dc1a7c9d0..60dd5a40e 100644
--- a/web/templates/footer.html
+++ b/web/templates/footer.html
@@ -12,9 +12,28 @@
</div>
</div>
<script>
- document.getElementById("help_link").setAttribute("href", '/static/help/help.html');
- document.getElementById("terms_link").setAttribute("href", '/static/help/terms.html');
- document.getElementById("privacy_link").setAttribute("href", '/static/help/privacy.html');
- document.getElementById("about_link").setAttribute("href", '/static/help/about.html');
+ if (window.mm_config.HelpLink) {
+ document.getElementById("help_link").setAttribute("href", window.mm_config.HelpLink);
+ } else {
+ $("#help_link").remove();
+ }
+
+ if (window.mm_config.TermsOfServiceLink) {
+ document.getElementById("terms_link").setAttribute("href", window.mm_config.TermsOfServiceLink);
+ } else {
+ $("#terms_link").remove();
+ }
+
+ if (window.mm_config.PrivacyPolicyLink) {
+ document.getElementById("privacy_link").setAttribute("href", window.mm_config.PrivacyPolicyLink);
+ } else {
+ $("#privacy_link").remove();
+ }
+
+ if (window.mm_config.AboutLink) {
+ document.getElementById("about_link").setAttribute("href", window.mm_config.AboutLink);
+ } else {
+ $("#about_link").remove();
+ }
</script>
{{end}}
diff --git a/web/web.go b/web/web.go
index a72bff2bf..63544229b 100644
--- a/web/web.go
+++ b/web/web.go
@@ -564,7 +564,7 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) {
return
} else {
c.LogAudit("Email Verified")
- http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host+"/"+name+"/login?verified=true&email="+email, http.StatusTemporaryRedirect)
+ http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host+"/"+name+"/login?verified=true&email="+url.QueryEscape(email), http.StatusTemporaryRedirect)
return
}
}
diff --git a/web/web_test.go b/web/web_test.go
index 4e15037ad..8d40810b5 100644
--- a/web/web_test.go
+++ b/web/web_test.go
@@ -60,7 +60,7 @@ func TestGetAccessToken(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := ApiClient.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Password: "pwd"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Password: "pwd"}
ruser := ApiClient.Must(ApiClient.CreateUser(&user, "")).Data.(*model.User)
store.Must(api.Srv.Store.User().VerifyEmail(ruser.Id))
@@ -189,7 +189,7 @@ func TestIncomingWebhook(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = ApiClient.Must(ApiClient.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user = ApiClient.Must(ApiClient.CreateUser(user, "")).Data.(*model.User)
store.Must(api.Srv.Store.User().VerifyEmail(user.Id))