summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Makefile16
-rw-r--r--README.md10
-rw-r--r--api/channel.go39
-rw-r--r--api/file.go3
-rw-r--r--api/team.go4
-rw-r--r--api/templates/email_change_body.html2
-rw-r--r--api/templates/find_teams_body.html4
-rw-r--r--api/templates/invite_body.html2
-rw-r--r--api/templates/password_change_body.html2
-rw-r--r--api/templates/post_body.html2
-rw-r--r--api/templates/reset_body.html2
-rw-r--r--api/templates/signup_team_body.html2
-rw-r--r--api/templates/verify_body.html2
-rw-r--r--api/templates/welcome_body.html2
-rw-r--r--api/user.go8
-rw-r--r--api/web_hub.go10
-rw-r--r--config/config.json2
-rw-r--r--doc/README.md1
-rw-r--r--doc/api/Overview.md94
-rw-r--r--doc/install/Production-Ubuntu.md6
-rw-r--r--doc/integrations/Single-Sign-On/Gitlab.md2
-rw-r--r--doc/integrations/webhooks/Incoming.md2
-rw-r--r--model/message.go4
-rw-r--r--store/sql_post_store.go19
-rw-r--r--store/sql_post_store_test.go26
-rw-r--r--utils/config.go2
-rw-r--r--web/react/.eslintrc19
-rw-r--r--web/react/components/about_build_modal.jsx62
-rw-r--r--web/react/components/access_history_modal.jsx24
-rw-r--r--web/react/components/activity_log_modal.jsx29
-rw-r--r--web/react/components/admin_console/admin_controller.jsx2
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx272
-rw-r--r--web/react/components/admin_console/email_settings.jsx8
-rw-r--r--web/react/components/admin_console/image_settings.jsx2
-rw-r--r--web/react/components/admin_console/log_settings.jsx2
-rw-r--r--web/react/components/admin_console/user_item.jsx1
-rw-r--r--web/react/components/channel_header.jsx40
-rw-r--r--web/react/components/channel_members.jsx16
-rw-r--r--web/react/components/channel_notifications.jsx39
-rw-r--r--web/react/components/command_list.jsx2
-rw-r--r--web/react/components/create_comment.jsx15
-rw-r--r--web/react/components/create_post.jsx12
-rw-r--r--web/react/components/delete_channel_modal.jsx16
-rw-r--r--web/react/components/delete_post_modal.jsx24
-rw-r--r--web/react/components/edit_channel_modal.jsx10
-rw-r--r--web/react/components/email_verify.jsx4
-rw-r--r--web/react/components/error_bar.jsx72
-rw-r--r--web/react/components/file_upload.jsx2
-rw-r--r--web/react/components/get_link_modal.jsx45
-rw-r--r--web/react/components/login.jsx18
-rw-r--r--web/react/components/member_list_team_item.jsx44
-rw-r--r--web/react/components/mention.jsx6
-rw-r--r--web/react/components/mention_list.jsx91
-rw-r--r--web/react/components/more_channels.jsx98
-rw-r--r--web/react/components/more_direct_channels.jsx32
-rw-r--r--web/react/components/navbar_dropdown.jsx61
-rw-r--r--web/react/components/popover_list_members.jsx3
-rw-r--r--web/react/components/post_body.jsx2
-rw-r--r--web/react/components/post_deleted_modal.jsx36
-rw-r--r--web/react/components/post_info.jsx14
-rw-r--r--web/react/components/post_list.jsx14
-rw-r--r--web/react/components/rename_channel_modal.jsx20
-rw-r--r--web/react/components/rhs_comment.jsx129
-rw-r--r--web/react/components/rhs_thread.jsx11
-rw-r--r--web/react/components/sidebar.jsx34
-rw-r--r--web/react/components/sidebar_right.jsx6
-rw-r--r--web/react/components/signup_user_complete.jsx38
-rw-r--r--web/react/components/team_signup_choose_auth.jsx2
-rw-r--r--web/react/components/team_signup_email_item.jsx9
-rw-r--r--web/react/components/team_signup_send_invites_page.jsx6
-rw-r--r--web/react/components/team_signup_url_page.jsx30
-rw-r--r--web/react/components/team_signup_welcome_page.jsx3
-rw-r--r--web/react/components/team_signup_with_email.jsx2
-rw-r--r--web/react/components/team_signup_with_sso.jsx2
-rw-r--r--web/react/components/user_settings/import_theme_modal.jsx5
-rw-r--r--web/react/components/user_settings/user_settings_appearance.jsx14
-rw-r--r--web/react/components/user_settings/user_settings_security.jsx11
-rw-r--r--web/react/components/view_image.jsx2
-rw-r--r--web/react/components/view_image_popover_bar.jsx4
-rw-r--r--web/react/package.json26
-rw-r--r--web/react/stores/browser_store.jsx10
-rw-r--r--web/react/stores/socket_store.jsx6
-rw-r--r--web/react/stores/team_store.jsx8
-rw-r--r--web/react/utils/client.jsx14
-rw-r--r--web/react/utils/constants.jsx43
-rw-r--r--web/react/utils/utils.jsx40
-rw-r--r--web/sass-files/sass/partials/_access-history.scss2
-rw-r--r--web/sass-files/sass/partials/_activity-log.scss19
-rw-r--r--web/sass-files/sass/partials/_admin-console.scss329
-rw-r--r--web/sass-files/sass/partials/_base.scss32
-rw-r--r--web/sass-files/sass/partials/_forms.scss5
-rw-r--r--web/sass-files/sass/partials/_headers.scss1
-rw-r--r--web/sass-files/sass/partials/_mentions.scss5
-rw-r--r--web/sass-files/sass/partials/_modal.scss3
-rw-r--r--web/sass-files/sass/partials/_post.scss4
-rw-r--r--web/sass-files/sass/partials/_post_right.scss2
-rw-r--r--web/sass-files/sass/partials/_search.scss3
-rw-r--r--web/sass-files/sass/partials/_settings.scss5
-rw-r--r--web/sass-files/sass/partials/_signup.scss8
-rw-r--r--web/static/images/Battlehouse-logodark.pngbin6981 -> 0 bytes
-rw-r--r--web/static/images/Mattermost-logodark.pngbin10380 -> 0 bytes
-rw-r--r--web/static/images/logo-email.png (renamed from web/static/images/Bladekick-logodark.png)bin10380 -> 10380 bytes
-rw-r--r--web/templates/admin_console.html8
-rw-r--r--web/templates/head.html21
-rw-r--r--web/templates/verify.html25
-rw-r--r--web/web.go15
107 files changed, 1398 insertions, 972 deletions
diff --git a/.gitignore b/.gitignore
index ebd5e4342..fc9076e69 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,8 @@ node_modules
dist
npm-debug.log
-bundle*.js
+web/static/js/bundle*.js
+web/static/js/libs*.js
model/version.go
model/version.go.bak
diff --git a/Makefile b/Makefile
index 8d6d30735..b03ed5cba 100644
--- a/Makefile
+++ b/Makefile
@@ -43,6 +43,7 @@ travis:
@$(GO) clean $(GOFLAGS) -i ./...
@cd web/react/ && npm install
+ cd web/react/ && npm run build-libs
@echo Checking for style guide compliance
cd web/react && $(ESLINT) --quiet components/* dispatcher/* pages/* stores/* utils/*
@@ -83,10 +84,11 @@ travis:
mkdir -p web/static/js
cd web/react && npm run build
- cd web/sass-files && compass compile
+ cd web/sass-files && compass compile -e production --force
- mkdir -p $(DIST_PATH)/web
- cp -RL web/static $(DIST_PATH)/web
+ mkdir -p $(DIST_PATH)/web/static/js
+ cp -L web/static/js/*.min.js $(DIST_PATH)/web/static/js/
+ cp -RL web/static/js/jquery-dragster $(DIST_PATH)/web/static/js/
cp -RL web/templates $(DIST_PATH)/web
mkdir -p $(DIST_PATH)/api
@@ -97,6 +99,7 @@ travis:
cp README.md $(DIST_PATH)
mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js
+ mv $(DIST_PATH)/web/static/js/libs.min.js $(DIST_PATH)/web/static/js/libs-$(BUILD_NUMBER).min.js
@sed -i'.bak' 's|react-with-addons-0.13.3.js|react-with-addons-0.13.3.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|jquery-1.11.1.js|jquery-1.11.1.min.js|g' $(DIST_PATH)/web/templates/head.html
@@ -104,6 +107,7 @@ travis:
@sed -i'.bak' 's|react-bootstrap-0.25.1.js|react-bootstrap-0.25.1.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|perfect-scrollbar.js|perfect-scrollbar.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|bundle.js|bundle-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html
+ @sed -i'.bak' 's|libs.min.js|libs-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html
rm $(DIST_PATH)/web/templates/*.bak
mv doc/README.md doc/index.md
@@ -129,6 +133,7 @@ install:
fi
@cd web/react/ && npm install
+ @cd web/react/ && npm run build-libs
check: install
@echo Running ESLint...
@@ -185,6 +190,7 @@ clean:
rm -rf web/react/node_modules
rm -f web/static/js/bundle*.js
+ rm -f web/static/js/libs*.js
rm -f web/static/css/styles.css
rm -rf data/*
@@ -257,7 +263,7 @@ dist: install
mkdir -p web/static/js
cd web/react && npm run build
- cd web/sass-files && compass compile
+ cd web/sass-files && compass compile -e production --force
mkdir -p $(DIST_PATH)/web
cp -RL web/static $(DIST_PATH)/web
@@ -271,6 +277,7 @@ dist: install
cp README.md $(DIST_PATH)
mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js
+ mv $(DIST_PATH)/web/static/js/libs.min.js $(DIST_PATH)/web/static/js/libs-$(BUILD_NUMBER).min.js
@sed -i'.bak' 's|react-with-addons-0.13.3.js|react-with-addons-0.13.3.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|jquery-1.11.1.js|jquery-1.11.1.min.js|g' $(DIST_PATH)/web/templates/head.html
@@ -278,6 +285,7 @@ dist: install
@sed -i'.bak' 's|react-bootstrap-0.25.1.js|react-bootstrap-0.25.1.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|perfect-scrollbar.js|perfect-scrollbar.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|bundle.js|bundle-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html
+ @sed -i'.bak' 's|libs.min.js|libs-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html
rm $(DIST_PATH)/web/templates/*.bak
tar -C dist -czf $(DIST_PATH).tar.gz mattermost
diff --git a/README.md b/README.md
index 88f6dcb35..08f01f717 100644
--- a/README.md
+++ b/README.md
@@ -42,19 +42,19 @@ There are multiple ways to install Mattermost depending on your needs.
#### Quick Start Install for Product Evaluation
-- [Local Machine Install with Docker](doc/install/single-container-install.md) - Explore product functionality using a single-container Docker install on a local machine, including Mac OSX, Ubuntu, or Arch Linux). Optionally set up email and upgrade your instance using DockerHub.
+- [Local Machine Install with Docker](http://docs.mattermost.org/install/Docker-Single-Container/index.html) - Explore product functionality using a single-container Docker install on a local machine, including Mac OSX, Ubuntu, or Arch Linux). Optionally set up email and upgrade your instance using DockerHub.
-- [AWS EBS Install with Docker](doc/install/aws-ebs-setup.md) - Explore product functionality using a single-container Docker install for Amazon Web Services Elastic Beanstalk. Optionally set up email and upgrade your instance using DockerHub.
+- [AWS EBS Install with Docker](http://docs.mattermost.org/install/Amazon-Elastic-Beanstalk/index.html) - Explore product functionality using a single-container Docker install for Amazon Web Services Elastic Beanstalk. Optionally set up email and upgrade your instance using DockerHub.
#### Development Install
-- [Developer Machine Setup](doc/install/dev-setup.md) - Setup your local machine development environment using Docker on Mac OSX or Ubuntu. Pull the latest stable release or pull the latest code from our development build.
+- [Developer Machine Setup](http://docs.mattermost.org/developer/Setup/index.html) - Setup your local machine development environment using Docker on Mac OSX or Ubuntu. Pull the latest stable release or pull the latest code from our development build.
[![Build Status](https://travis-ci.org/mattermost/platform.svg?branch=master)](https://travis-ci.org/mattermost/platform)
#### Production Deployment
-Prior to production installation, please review [Mattermost system requirements](doc/install/requirements.md).
+Prior to production installation, please review [Mattermost system requirements](http://docs.mattermost.org/install/Requirements/index.html).
- [Production Install on Ubuntu 14.04](https://github.com/mattermost/platform/blob/release-1.0.0/doc/install/prod-ubuntu.md) - Install Mattermost for production environments.
@@ -90,7 +90,7 @@ Joining the Mattermost community is a great way to build relationships with othe
#### Contribute
- Share [feature ideas](http://www.mattermost.org/feature-requests/) with the Mattermost community
-- Review the [Mattermost Code Contribution Guidelines](doc/developer/code-contribution.md) to submit patches for the core product
+- Review the [Mattermost Code Contribution Guidelines](http://docs.mattermost.org/developer/Code-Contribution-Guidelines/index.html) to submit patches for the core product
- Consider building tools that help developers and IT professionals manage Mattermost more effectively (API documentation coming in Beta2)
#### Have other ideas or suggestions?
diff --git a/api/channel.go b/api/channel.go
index 5e13fa18a..0d22d7c00 100644
--- a/api/channel.go
+++ b/api/channel.go
@@ -472,6 +472,8 @@ func leaveChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ UpdateChannelAccessCacheAndForget(c.Session.TeamId, c.Session.UserId, channel.Id)
+
post := &model.Post{ChannelId: channel.Id, Message: fmt.Sprintf(
`%v has left the channel.`,
user.Username), Type: model.POST_JOIN_LEAVE}
@@ -706,20 +708,21 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- post := &model.Post{ChannelId: id, Message: fmt.Sprintf(
- `%v added to the channel by %v`,
- nUser.Username, oUser.Username), Type: model.POST_JOIN_LEAVE}
- if _, err := CreatePost(c, post, false); err != nil {
- l4g.Error("Failed to post add message %v", err)
- c.Err = model.NewAppError("addChannelMember", "Failed to add member to channel", "")
- return
- }
-
c.LogAudit("name=" + channel.Name + " user_id=" + userId)
- message := model.NewMessage(c.Session.TeamId, "", userId, model.ACTION_USER_ADDED)
+ go func() {
+ post := &model.Post{ChannelId: id, Message: fmt.Sprintf(
+ `%v added to the channel by %v`,
+ nUser.Username, oUser.Username), Type: model.POST_JOIN_LEAVE}
+ if _, err := CreatePost(c, post, false); err != nil {
+ l4g.Error("Failed to post add member to channel message, err=%v", err)
+ }
- PublishAndForget(message)
+ UpdateChannelAccessCache(c.Session.TeamId, userId, channel.Id)
+ message := model.NewMessage(c.Session.TeamId, channel.Id, userId, model.ACTION_USER_ADDED)
+
+ PublishAndForget(message)
+ }()
<-Srv.Store.Channel().UpdateLastViewedAt(id, oUser.Id)
w.Write([]byte(cm.ToJson()))
@@ -773,13 +776,17 @@ func removeChannelMember(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- message := model.NewMessage(c.Session.TeamId, "", userId, model.ACTION_USER_REMOVED)
- message.Add("channel_id", id)
- message.Add("remover", c.Session.UserId)
- PublishAndForget(message)
-
c.LogAudit("name=" + channel.Name + " user_id=" + userId)
+ go func() {
+ UpdateChannelAccessCache(c.Session.TeamId, userId, id)
+
+ message := model.NewMessage(c.Session.TeamId, "", userId, model.ACTION_USER_REMOVED)
+ message.Add("channel_id", id)
+ message.Add("remover", c.Session.UserId)
+ PublishAndForget(message)
+ }()
+
result := make(map[string]string)
result["channel_id"] = channel.Id
result["removed_user_id"] = userId
diff --git a/api/file.go b/api/file.go
index 5dc1db650..bb9aa00d8 100644
--- a/api/file.go
+++ b/api/file.go
@@ -23,7 +23,6 @@ import (
"image/jpeg"
"io"
"io/ioutil"
- "mime"
"net/http"
"net/url"
"os"
@@ -407,7 +406,7 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "max-age=2592000, public")
w.Header().Set("Content-Length", strconv.Itoa(len(f)))
- w.Header().Set("Content-Type", mime.TypeByExtension(filepath.Ext(filename)))
+ w.Header().Del("Content-Type") // Content-Type will be set automatically by the http writer
// attach extra headers to trigger a download on IE and Edge
ua := user_agent.New(r.UserAgent())
diff --git a/api/team.go b/api/team.go
index 8e5d634aa..bb60e0720 100644
--- a/api/team.go
+++ b/api/team.go
@@ -432,9 +432,9 @@ func emailTeams(c *Context, w http.ResponseWriter, r *http.Request) {
}
subjectPage := NewServerTemplatePage("find_teams_subject")
- subjectPage.Props["SiteURL"] = c.GetSiteURL()
+ subjectPage.ClientProps["SiteURL"] = c.GetSiteURL()
bodyPage := NewServerTemplatePage("find_teams_body")
- bodyPage.Props["SiteURL"] = c.GetSiteURL()
+ bodyPage.ClientProps["SiteURL"] = c.GetSiteURL()
if result := <-Srv.Store.Team().GetTeamsForEmail(email); result.Err != nil {
c.Err = result.Err
diff --git a/api/templates/email_change_body.html b/api/templates/email_change_body.html
index 5c5f6c777..d4e6abd02 100644
--- a/api/templates/email_change_body.html
+++ b/api/templates/email_change_body.html
@@ -9,7 +9,7 @@
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
<td style="padding: 20px 20px 10px; text-align:left;">
- <img src="{{.Props.SiteURL}}/static/images/{{.ClientProps.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
+ <img src="{{.Props.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
<tr>
diff --git a/api/templates/find_teams_body.html b/api/templates/find_teams_body.html
index 9d34b7a23..3046ee5f8 100644
--- a/api/templates/find_teams_body.html
+++ b/api/templates/find_teams_body.html
@@ -9,7 +9,7 @@
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
<td style="padding: 20px 20px 10px; text-align:left;">
- <img src="{{.Props.SiteURL}}/static/images/{{.ClientProps.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
+ <img src="{{.ClientProps.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
<tr>
@@ -42,7 +42,7 @@
<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="">
+ <img width="65" src="{{.ClientProps.SiteURL}}/static/images/circles.png" alt="">
</p>
<p style="padding: 0 50px;">
(c) 2015 SpinPunch, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
diff --git a/api/templates/invite_body.html b/api/templates/invite_body.html
index 9e1ce33b2..fdfcfa9f1 100644
--- a/api/templates/invite_body.html
+++ b/api/templates/invite_body.html
@@ -9,7 +9,7 @@
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
<td style="padding: 20px 20px 10px; text-align:left;">
- <img src="{{.Props.SiteURL}}/static/images/{{.ClientProps.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
+ <img src="{{.Props.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
<tr>
diff --git a/api/templates/password_change_body.html b/api/templates/password_change_body.html
index 3fef3a5c8..c420d7a69 100644
--- a/api/templates/password_change_body.html
+++ b/api/templates/password_change_body.html
@@ -9,7 +9,7 @@
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
<td style="padding: 20px 20px 10px; text-align:left;">
- <img src="{{.Props.SiteURL}}/static/images/{{.ClientProps.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
+ <img src="{{.Props.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
<tr>
diff --git a/api/templates/post_body.html b/api/templates/post_body.html
index a6b81e2f6..1dd30ca45 100644
--- a/api/templates/post_body.html
+++ b/api/templates/post_body.html
@@ -9,7 +9,7 @@
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
<td style="padding: 20px 20px 10px; text-align:left;">
- <img src="{{.Props.SiteURL}}/static/images/{{.ClientProps.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
+ <img src="{{.Props.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
<tr>
diff --git a/api/templates/reset_body.html b/api/templates/reset_body.html
index dc6152627..d388689cf 100644
--- a/api/templates/reset_body.html
+++ b/api/templates/reset_body.html
@@ -9,7 +9,7 @@
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
<td style="padding: 20px 20px 10px; text-align:left;">
- <img src="{{.Props.SiteURL}}/static/images/{{.ClientProps.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
+ <img src="{{.Props.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
<tr>
diff --git a/api/templates/signup_team_body.html b/api/templates/signup_team_body.html
index e6ffb3a5b..83c1679b9 100644
--- a/api/templates/signup_team_body.html
+++ b/api/templates/signup_team_body.html
@@ -9,7 +9,7 @@
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
<td style="padding: 20px 20px 10px; text-align:left;">
- <img src="{{.Props.SiteURL}}/static/images/{{.ClientProps.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
+ <img src="{{.Props.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
<tr>
diff --git a/api/templates/verify_body.html b/api/templates/verify_body.html
index 8187c8908..def067a84 100644
--- a/api/templates/verify_body.html
+++ b/api/templates/verify_body.html
@@ -9,7 +9,7 @@
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
<td style="padding: 20px 20px 10px; text-align:left;">
- <img src="{{.Props.SiteURL}}/static/images/{{.ClientProps.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
+ <img src="{{.Props.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
<tr>
diff --git a/api/templates/welcome_body.html b/api/templates/welcome_body.html
index 5fe3450b7..ff31ee8d5 100644
--- a/api/templates/welcome_body.html
+++ b/api/templates/welcome_body.html
@@ -9,7 +9,7 @@
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
<td style="padding: 20px 20px 10px; text-align:left;">
- <img src="{{.Props.SiteURL}}/static/images/{{.ClientProps.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
+ <img src="{{.Props.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
<tr>
diff --git a/api/user.go b/api/user.go
index 4d12f0f33..34cbec151 100644
--- a/api/user.go
+++ b/api/user.go
@@ -995,7 +995,7 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) {
}
if model.IsInRole(new_roles, model.ROLE_SYSTEM_ADMIN) && !c.IsSystemAdmin() {
- c.Err = model.NewAppError("updateRoles", "The system_admin role can only be set by another system admin", "")
+ c.Err = model.NewAppError("updateRoles", "The system admin role can only be set by another system admin", "")
c.Err.StatusCode = http.StatusForbidden
return
}
@@ -1018,6 +1018,12 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ if user.IsInRole(model.ROLE_SYSTEM_ADMIN) && !c.IsSystemAdmin() {
+ c.Err = model.NewAppError("updateRoles", "The system admin role can only by modified by another system admin", "")
+ c.Err.StatusCode = http.StatusForbidden
+ return
+ }
+
ruser := UpdateRoles(c, user, new_roles)
if c.Err != nil {
return
diff --git a/api/web_hub.go b/api/web_hub.go
index 44d405283..15528c612 100644
--- a/api/web_hub.go
+++ b/api/web_hub.go
@@ -30,11 +30,15 @@ func PublishAndForget(message *model.Message) {
}()
}
+func UpdateChannelAccessCache(teamId, userId, channelId string) {
+ if nh, ok := hub.teamHubs[teamId]; ok {
+ nh.UpdateChannelAccessCache(userId, channelId)
+ }
+}
+
func UpdateChannelAccessCacheAndForget(teamId, userId, channelId string) {
go func() {
- if nh, ok := hub.teamHubs[teamId]; ok {
- nh.UpdateChannelAccessCache(userId, channelId)
- }
+ UpdateChannelAccessCache(teamId, userId, channelId)
}()
}
diff --git a/config/config.json b/config/config.json
index b14175372..88da33215 100644
--- a/config/config.json
+++ b/config/config.json
@@ -89,4 +89,4 @@
"TokenEndpoint": "",
"UserApiEndpoint": ""
}
-}
+} \ No newline at end of file
diff --git a/doc/README.md b/doc/README.md
index d711b5d53..4c7c4cc0e 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -19,6 +19,7 @@
- [Code Contribution Guidelines](developer/Code-Contribution-Guidelines.md)
- [Developer Machine Setup](developer/Setup.md)
- [Mattermost Style Guide](developer/Style-Guide.md)
+- [API Overview](api/Overview.md)
## Usage Help
diff --git a/doc/api/Overview.md b/doc/api/Overview.md
new file mode 100644
index 000000000..02e11974e
--- /dev/null
+++ b/doc/api/Overview.md
@@ -0,0 +1,94 @@
+# API Overview
+
+This provides a basic overview of the Mattermost API. All examples assume there is a Mattermost instance running at http://localhost:8065.
+
+## Schema
+
+All API access is done through `yourdomain.com/api/v1/`, with all data being sent and received as JSON.
+
+
+## Authentication
+
+The majority of the Mattermost API involves interacting with teams. Therefore, most API methods require authentication as a user. There are two ways to authenticate into a Mattermost system.
+
+##### Session Token
+
+Make an HTTP POST to `yourdomain.com/api/v1/users/login` with a JSON body indicating the `name` of the team, the user's `email` and `password`.
+
+```
+curl -i -d '{"name":"exampleteam","email":"someone@nowhere.com","password":"thisisabadpassword"}' http://localhost:8065/api/v1/users/login
+```
+
+If successful, the response will contain a `Token` header and a User object in the body.
+
+```
+HTTP/1.1 200 OK
+Set-Cookie: MMSID=hyr5dmb1mbb49c44qmx4whniso; Path=/; Max-Age=2592000; HttpOnly
+Token: hyr5dmb1mbb49c44qmx4whniso
+X-Ratelimit-Limit: 10
+X-Ratelimit-Remaining: 9
+X-Ratelimit-Reset: 1
+X-Request-Id: smda55ckcfy89b6tia58shk5fh
+X-Version-Id: developer
+Date: Fri, 11 Sep 2015 13:21:14 GMT
+Content-Length: 657
+Content-Type: application/json; charset=utf-8
+
+{{user object as json}}
+```
+
+Include the `Token` as part of the `Authentication` header on your future API requests with the `Bearer` method.
+
+```
+curl -i -H 'Authorization: Bearer hyr5dmb1mbb49c44qmx4whniso' http://localhost:8065/api/v1/users/me
+```
+
+That's it! You should now be able to access the API as the user you logged in as.
+
+##### OAuth2
+
+Coming soon...
+
+
+## Client Errors
+
+All errors will return an appropriate HTTP response code along with the following JSON body:
+
+```
+{
+ "message": "", // the reason for the error
+ "detailed_error": "", // some extra details about the error
+ "request_id": "", // the ID of the request
+ "status_code": 0 // the HTTP status code
+}
+```
+
+
+## Rate Limiting
+
+Whenever you make an HTTP request to the Mattermost API you might notice the following headers included in the response:
+```
+X-Ratelimit-Limit: 10
+X-Ratelimit-Remaining: 9
+X-Ratelimit-Reset: 1441983590
+
+```
+
+These headers are telling you your current rate limit status.
+
+Header | Description
+--------------------- | -----------
+X-Ratelimit-Limit | The maximum number of requests you can make per second.
+X-Ratelimit-Remaining | The number of requests remaining in the current window.
+X-Ratelimit-Reset | The remaining UTC epoch seconds before the rate limit resets.
+
+If you exceed your rate limit for a window you will receive the following error in the body of the response:
+```
+HTTP/1.1 429 Too Many Requests
+Date: Tue, 10 Sep 2015 11:20:28 GMT
+X-RateLimit-Limit: 10
+X-RateLimit-Remaining: 0
+X-RateLimit-Reset: 1
+
+limit exceeded
+```
diff --git a/doc/install/Production-Ubuntu.md b/doc/install/Production-Ubuntu.md
index d2490062d..05a56c412 100644
--- a/doc/install/Production-Ubuntu.md
+++ b/doc/install/Production-Ubuntu.md
@@ -1,14 +1,14 @@
# Production Installation on Ubuntu 14.04 LTS
-## Install Ubuntu Server 14.04 LTS
-1. Set up 3 machines with Ubuntu 14.04 with 2GB of RAM or more. The servers will be used for the Load Balancer, Mattermost, and Database.
+## Install Ubuntu Server (x64) 14.04 LTS
+1. Set up 3 machines with Ubuntu 14.04 with 2GB of RAM or more. The servers will be used for the Load Balancer, Mattermost (this must be x64 to use pre-built binaries), and Database.
1. Make sure the system is up to date with the most recent security patches.
* ``` sudo apt-get update```
* ``` sudo apt-get upgrade```
## Set up Database Server
1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.1
-1. Install PostgreSQL 9.3+ (or MySQL 5.2+)
+1. Install PostgreSQL 9.3+ (or MySQL 5.6+)
* ``` sudo apt-get install postgresql postgresql-contrib```
1. PostgreSQL created a user account called `postgres`. You will need to log into that account with:
* ``` sudo -i -u postgres```
diff --git a/doc/integrations/Single-Sign-On/Gitlab.md b/doc/integrations/Single-Sign-On/Gitlab.md
index 6110db504..87889df66 100644
--- a/doc/integrations/Single-Sign-On/Gitlab.md
+++ b/doc/integrations/Single-Sign-On/Gitlab.md
@@ -6,6 +6,8 @@ The following steps can be used to configure Mattermost to use GitLab as a singl
2. Add a new application called "Mattermost" with the following as Redirect URIs:
* `<your-mattermost-url>/login/gitlab/complete` (example: http://localhost:8065/login/gitlab/complete)
* `<your-mattermost-url>/signup/gitlab/complete`
+
+ (Note: If your GitLab instance is set up to use SSL, your URIs must begin with https://. Otherwise, use http://).
3. Submit the application and copy the given _Id_ and _Secret_ into the appropriate _SSOSettings_ fields in config/config.json
diff --git a/doc/integrations/webhooks/Incoming.md b/doc/integrations/webhooks/Incoming.md
index 6e25f182e..0814eb420 100644
--- a/doc/integrations/webhooks/Incoming.md
+++ b/doc/integrations/webhooks/Incoming.md
@@ -56,7 +56,7 @@ payload={"channel": "off-topic", "text": "Hello, this is some text."}
Combining everything above, here is an example message made using a curl command:
```
-curl -i -X POST 'payload={"channel": "off-topic", "text": "Hello, this is some text."}' http://yourmattermost.com/hooks/xxxxxxxxxxxxxxxxxxxxxxxxxx
+curl -i -X POST -d 'payload={"channel": "off-topic", "text": "Hello, this is some text."}' http://yourmattermost.com/hooks/xxxxxxxxxxxxxxxxxxxxxxxxxx
```
A post with that text will be made to the Off-Topic channel.
diff --git a/model/message.go b/model/message.go
index ec4817b2a..8598bea0e 100644
--- a/model/message.go
+++ b/model/message.go
@@ -31,8 +31,8 @@ func (m *Message) Add(key string, value string) {
m.Props[key] = value
}
-func NewMessage(teamId string, channekId string, userId string, action string) *Message {
- return &Message{TeamId: teamId, ChannelId: channekId, UserId: userId, Action: action, Props: make(map[string]string)}
+func NewMessage(teamId string, channelId string, userId string, action string) *Message {
+ return &Message{TeamId: teamId, ChannelId: channelId, UserId: userId, Action: action, Props: make(map[string]string)}
}
func (o *Message) ToJson() string {
diff --git a/store/sql_post_store.go b/store/sql_post_store.go
index 668f45fbb..8d62eaad0 100644
--- a/store/sql_post_store.go
+++ b/store/sql_post_store.go
@@ -396,6 +396,17 @@ func (s SqlPostStore) getParentsPosts(channelId string, offset int, limit int) S
return storeChannel
}
+var specialSearchChar = []string{
+ "<",
+ ">",
+ "+",
+ "-",
+ "(",
+ ")",
+ "~",
+ "@",
+}
+
func (s SqlPostStore) Search(teamId string, userId string, terms string, isHashtagSearch bool) StoreChannel {
storeChannel := make(StoreChannel)
@@ -411,10 +422,10 @@ func (s SqlPostStore) Search(teamId string, userId string, terms string, isHasht
}
}
- // @ has a speical meaning in INNODB FULLTEXT indexes and
- // is reserved for calc'ing distances so you
- // cannot escape it so we replace it.
- terms = strings.Replace(terms, "@", " ", -1)
+ // these chars have speical meaning and can be treated as spaces
+ for _, c := range specialSearchChar {
+ terms = strings.Replace(terms, c, " ", -1)
+ }
var posts []*model.Post
diff --git a/store/sql_post_store_test.go b/store/sql_post_store_test.go
index 6a6364dc8..62d7b0100 100644
--- a/store/sql_post_store_test.go
+++ b/store/sql_post_store_test.go
@@ -516,7 +516,7 @@ func TestPostStoreSearch(t *testing.T) {
o4.ChannelId = c1.Id
o4.UserId = model.NewId()
o4.Hashtags = "#hashtag"
- o4.Message = "message"
+ o4.Message = "(message)blargh"
o4 = (<-store.Post().Save(o4)).Data.(*model.Post)
o5 := &model.Post{}
@@ -527,37 +527,37 @@ func TestPostStoreSearch(t *testing.T) {
r1 := (<-store.Post().Search(teamId, userId, "corey", false)).Data.(*model.PostList)
if len(r1.Order) != 1 && r1.Order[0] != o1.Id {
- t.Fatal("returned wrong serach result")
+ t.Fatal("returned wrong search result")
}
r3 := (<-store.Post().Search(teamId, userId, "new", false)).Data.(*model.PostList)
if len(r3.Order) != 2 && r3.Order[0] != o1.Id {
- t.Fatal("returned wrong serach result")
+ t.Fatal("returned wrong search result")
}
r4 := (<-store.Post().Search(teamId, userId, "john", false)).Data.(*model.PostList)
if len(r4.Order) != 1 && r4.Order[0] != o2.Id {
- t.Fatal("returned wrong serach result")
+ t.Fatal("returned wrong search result")
}
r5 := (<-store.Post().Search(teamId, userId, "matter*", false)).Data.(*model.PostList)
if len(r5.Order) != 1 && r5.Order[0] != o1.Id {
- t.Fatal("returned wrong serach result")
+ t.Fatal("returned wrong search result")
}
r6 := (<-store.Post().Search(teamId, userId, "#hashtag", true)).Data.(*model.PostList)
if len(r6.Order) != 1 && r6.Order[0] != o4.Id {
- t.Fatal("returned wrong serach result")
+ t.Fatal("returned wrong search result")
}
r7 := (<-store.Post().Search(teamId, userId, "#secret", true)).Data.(*model.PostList)
if len(r7.Order) != 1 && r7.Order[0] != o5.Id {
- t.Fatal("returned wrong serach result")
+ t.Fatal("returned wrong search result")
}
r8 := (<-store.Post().Search(teamId, userId, "@thisshouldmatchnothing", true)).Data.(*model.PostList)
if len(r8.Order) != 0 {
- t.Fatal("returned wrong serach result")
+ t.Fatal("returned wrong search result")
}
r9 := (<-store.Post().Search(teamId, userId, "mattermost jersey", false)).Data.(*model.PostList)
@@ -569,4 +569,14 @@ func TestPostStoreSearch(t *testing.T) {
if len(r10.Order) != 2 {
t.Fatal("returned wrong search result")
}
+
+ r11 := (<-store.Post().Search(teamId, userId, "message blargh", false)).Data.(*model.PostList)
+ if len(r11.Order) != 1 {
+ t.Fatal("returned wrong search result")
+ }
+
+ r12 := (<-store.Post().Search(teamId, userId, "blargh>", false)).Data.(*model.PostList)
+ if len(r12.Order) != 1 {
+ t.Fatal("returned wrong search result")
+ }
}
diff --git a/utils/config.go b/utils/config.go
index 0a2697e6c..0eea6dd6a 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -179,6 +179,8 @@ func getClientProperties(c *model.Config) map[string]string {
props["BuildHash"] = model.BuildHash
props["SiteName"] = c.TeamSettings.SiteName
+ props["EnableTeamCreation"] = strconv.FormatBool(c.TeamSettings.EnableTeamCreation)
+
props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider)
props["SegmentDeveloperKey"] = c.ServiceSettings.SegmentDeveloperKey
diff --git a/web/react/.eslintrc b/web/react/.eslintrc
index c0d0bb200..c4167829d 100644
--- a/web/react/.eslintrc
+++ b/web/react/.eslintrc
@@ -42,12 +42,13 @@
"valid-typeof": 2,
"block-scoped-var": 2,
- "complexity": [1, 8],
+ "complexity": [0, 8],
"consistent-return": 2,
"curly": [2, "all"],
"dot-location": [2, "object"],
"dot-notation": 2,
"eqeqeq": [2, "smart"],
+ "global-require": 2,
"guard-for-in": 2,
"no-alert": 2,
"no-array-constructor": 2,
@@ -109,30 +110,34 @@
"func-names": 2,
"func-style": [2, "declaration"],
"indent": [2, 4, {"SwitchCase": 0}],
+ "jsx-quotes": [2, "prefer-single"],
"key-spacing": [2, {"beforeColon": false, "afterColon": true}],
- "lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }],
"linebreak-style": 2,
+ "lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }],
"new-cap": 2,
"new-parens": 2,
"no-lonely-if": 2,
"no-mixed-spaces-and-tabs": 2,
"no-multiple-empty-lines": [2, {"max": 1}],
+ "no-negated-condition": 2,
+ "no-nested-ternary": 2,
"no-spaced-func": 2,
- "no-ternary": 2,
+ "no-ternary": 0,
"no-trailing-spaces": [2, { "skipBlankLines": false }],
"no-underscore-dangle": 2,
- "no-unneeded-ternary": 2,
+ "no-unneeded-ternary": [2, {"defaultAssignment": false}],
"object-curly-spacing": [2, "never"],
"one-var": [2, "never"],
"operator-linebreak": [2, "after"],
"padded-blocks": [2, "never"],
"quote-props": [2, "as-needed"],
"quotes": [2, "single", "avoid-escape"],
- "semi-spacing": [2, {"before": false, "after": true}],
"semi": [2, "always"],
+ "semi-spacing": [2, {"before": false, "after": true}],
"space-after-keywords": [2, "always"],
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, "never"],
+ "space-before-keywords": [2, "always"],
"space-in-parens": [2, "never"],
"space-infix-ops": 2,
"space-return-throw-case": 2,
@@ -167,7 +172,6 @@
"react/jsx-no-duplicate-props": [2, { "ignoreCase": false }],
"react/jsx-no-literals": 1,
"react/jsx-no-undef": 2,
- "react/jsx-quotes": [2, "single", "avoid-escape"],
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/no-danger": 0,
@@ -179,6 +183,7 @@
"react/prop-types": 2,
"react/self-closing-comp": 2,
"react/sort-comp": 0,
- "react/wrap-multilines": 2
+ "react/wrap-multilines": 2,
+ "react/no-direct-mutation-state": 2
}
}
diff --git a/web/react/components/about_build_modal.jsx b/web/react/components/about_build_modal.jsx
new file mode 100644
index 000000000..d582f6bc8
--- /dev/null
+++ b/web/react/components/about_build_modal.jsx
@@ -0,0 +1,62 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var Modal = ReactBootstrap.Modal;
+
+export default class AboutBuildModal extends React.Component {
+ constructor(props) {
+ super(props);
+ this.doHide = this.doHide.bind(this);
+ }
+
+ doHide() {
+ this.props.onModalDismissed();
+ }
+
+ render() {
+ const config = global.window.config;
+
+ return (
+ <Modal
+ show={this.props.show}
+ onHide={this.doHide}
+ >
+ <Modal.Header closeButton={true}>
+ <Modal.Title>{`Mattermost ${config.Version}`}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <div className='row form-group'>
+ <div className='col-sm-3 info__label'>{'Build Number:'}</div>
+ <div className='col-sm-9'>{config.BuildNumber}</div>
+ </div>
+ <div className='row form-group'>
+ <div className='col-sm-3 info__label'>{'Build Date:'}</div>
+ <div className='col-sm-9'>{config.BuildDate}</div>
+ </div>
+ <div className='row'>
+ <div className='col-sm-3 info__label'>{'Build Hash:'}</div>
+ <div className='col-sm-9'>{config.BuildHash}</div>
+ </div>
+ </Modal.Body>
+ <Modal.Footer>
+ <button
+ type='button'
+ className='btn btn-default'
+ onClick={this.doHide}
+ >
+ {'Close'}
+ </button>
+ </Modal.Footer>
+ </Modal>
+ );
+ }
+}
+
+AboutBuildModal.defaultProps = {
+ show: false
+};
+
+AboutBuildModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onModalDismissed: React.PropTypes.func.isRequired
+}; \ No newline at end of file
diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx
index a080150dd..0dfd36717 100644
--- a/web/react/components/access_history_modal.jsx
+++ b/web/react/components/access_history_modal.jsx
@@ -12,25 +12,31 @@ export default class AccessHistoryModal extends React.Component {
this.onAuditChange = this.onAuditChange.bind(this);
this.handleMoreInfo = this.handleMoreInfo.bind(this);
+ this.onHide = this.onHide.bind(this);
+ this.onShow = this.onShow.bind(this);
- this.state = this.getStateFromStoresForAudits();
- this.state.moreInfo = [];
+ let state = this.getStateFromStoresForAudits();
+ state.moreInfo = [];
+
+ this.state = state;
}
getStateFromStoresForAudits() {
return {
audits: UserStore.getAudits()
};
}
+ onShow() {
+ AsyncClient.getAudits();
+ }
+ onHide() {
+ $('#user_settings').modal('show');
+ this.setState({moreInfo: []});
+ }
componentDidMount() {
UserStore.addAuditsChangeListener(this.onAuditChange);
- $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function show() {
- AsyncClient.getAudits();
- });
+ $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow);
- $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function hide() {
- $('#user_settings').modal('show');
- this.setState({moreInfo: []});
- }.bind(this));
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide);
}
componentWillUnmount() {
UserStore.removeAuditsChangeListener(this.onAuditChange);
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index fe40385a0..ff370c32e 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -14,9 +14,13 @@ export default class ActivityLogModal extends React.Component {
this.submitRevoke = this.submitRevoke.bind(this);
this.onListenerChange = this.onListenerChange.bind(this);
this.handleMoreInfo = this.handleMoreInfo.bind(this);
+ this.onHide = this.onHide.bind(this);
+ this.onShow = this.onShow.bind(this);
- this.state = this.getStateFromStores();
- this.state.moreInfo = [];
+ let state = this.getStateFromStores();
+ state.moreInfo = [];
+
+ this.state = state;
}
getStateFromStores() {
return {
@@ -27,6 +31,11 @@ export default class ActivityLogModal extends React.Component {
}
submitRevoke(altId, e) {
e.preventDefault();
+ var modalContent = $(e.target).closest('.modal-content');
+ modalContent.addClass('animation--highlight');
+ setTimeout(() => {
+ modalContent.removeClass('animation--highlight');
+ }, 1500);
Client.revokeSession(altId,
function handleRevokeSuccess() {
AsyncClient.getSessions();
@@ -38,16 +47,18 @@ export default class ActivityLogModal extends React.Component {
}.bind(this)
);
}
+ onShow() {
+ AsyncClient.getSessions();
+ }
+ onHide() {
+ $('#user_settings').modal('show');
+ this.setState({moreInfo: []});
+ }
componentDidMount() {
UserStore.addSessionsChangeListener(this.onListenerChange);
- $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function handleShow() {
- AsyncClient.getSessions();
- });
+ $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow);
- $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function handleHide() {
- $('#user_settings').modal('show');
- this.setState({moreInfo: []});
- }.bind(this));
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide);
}
componentWillUnmount() {
UserStore.removeSessionsChangeListener(this.onListenerChange);
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx
index 92f0bbdce..f40e48f70 100644
--- a/web/react/components/admin_console/admin_controller.jsx
+++ b/web/react/components/admin_console/admin_controller.jsx
@@ -147,7 +147,7 @@ export default class AdminController extends React.Component {
}
return (
- <div className='container-fluid'>
+ <div>
<div
className='sidebar--menu'
id='sidebar-menu'
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index 4b9ff3cb8..f102661b2 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -129,141 +129,143 @@ export default class AdminSidebar extends React.Component {
<div className='sidebar--left sidebar--collapsable'>
<div>
<AdminSidebarHeader />
- <ul className='nav nav-pills nav-stacked'>
- <li>
- <ul className='nav nav__sub-menu'>
- <li>
- <h4>
- <span className='icon fa fa-gear'></span>
- <span>{'SETTINGS'}</span>
- </h4>
- </li>
- </ul>
- <ul className='nav nav__sub-menu padded'>
- <li>
- <a
- href='#'
- className={this.isSelected('service_settings')}
- onClick={this.handleClick.bind(this, 'service_settings', null)}
- >
- {'Service Settings'}
- </a>
- </li>
- <li>
- <a
- href='#'
- className={this.isSelected('team_settings')}
- onClick={this.handleClick.bind(this, 'team_settings', null)}
- >
- {'Team Settings'}
- </a>
- </li>
- <li>
- <a
- href='#'
- className={this.isSelected('sql_settings')}
- onClick={this.handleClick.bind(this, 'sql_settings', null)}
- >
- {'SQL Settings'}
- </a>
- </li>
- <li>
- <a
- href='#'
- className={this.isSelected('email_settings')}
- onClick={this.handleClick.bind(this, 'email_settings', null)}
- >
- {'Email Settings'}
- </a>
- </li>
- <li>
- <a
- href='#'
- className={this.isSelected('image_settings')}
- onClick={this.handleClick.bind(this, 'image_settings', null)}
- >
- {'File Settings'}
- </a>
- </li>
- <li>
- <a
- href='#'
- className={this.isSelected('log_settings')}
- onClick={this.handleClick.bind(this, 'log_settings', null)}
- >
- {'Log Settings'}
- </a>
- </li>
- <li>
- <a
- href='#'
- className={this.isSelected('rate_settings')}
- onClick={this.handleClick.bind(this, 'rate_settings', null)}
- >
- {'Rate Limit Settings'}
- </a>
- </li>
- <li>
- <a
- href='#'
- className={this.isSelected('privacy_settings')}
- onClick={this.handleClick.bind(this, 'privacy_settings', null)}
- >
- {'Privacy Settings'}
- </a>
- </li>
- <li>
- <a
- href='#'
- className={this.isSelected('gitlab_settings')}
- onClick={this.handleClick.bind(this, 'gitlab_settings', null)}
- >
- {'GitLab Settings'}
- </a>
- </li>
- </ul>
- <ul className='nav nav__sub-menu'>
- <li>
- <h4>
- <span className='icon fa fa-gear'></span>
- <span>{'TEAMS (' + count + ')'}</span>
- <span className='menu-icon--right'>
- <a
- href='#'
- onClick={this.showTeamSelect}
- >
- <i className='fa fa-plus'></i>
- </a>
- </span>
- </h4>
- </li>
- </ul>
- <ul className='nav nav__sub-menu padded'>
- <li>
- {teams}
- </li>
- </ul>
- <ul className='nav nav__sub-menu'>
- <li>
- <h4>
- <span className='icon fa fa-gear'></span>
- <span>{'OTHER'}</span>
- </h4>
- </li>
- </ul>
- <ul className='nav nav__sub-menu padded'>
- <li>
- <a
- href='#'
- className={this.isSelected('logs')}
- onClick={this.handleClick.bind(this, 'logs', null)}
- >
- {'Logs'}
- </a>
- </li>
- </ul>
- </li>
- </ul>
+ <div className='nav-pills__container'>
+ <ul className='nav nav-pills nav-stacked'>
+ <li>
+ <ul className='nav nav__sub-menu'>
+ <li>
+ <h4>
+ <span className='icon fa fa-gear'></span>
+ <span>{'SETTINGS'}</span>
+ </h4>
+ </li>
+ </ul>
+ <ul className='nav nav__sub-menu padded'>
+ <li>
+ <a
+ href='#'
+ className={this.isSelected('service_settings')}
+ onClick={this.handleClick.bind(this, 'service_settings', null)}
+ >
+ {'Service Settings'}
+ </a>
+ </li>
+ <li>
+ <a
+ href='#'
+ className={this.isSelected('team_settings')}
+ onClick={this.handleClick.bind(this, 'team_settings', null)}
+ >
+ {'Team Settings'}
+ </a>
+ </li>
+ <li>
+ <a
+ href='#'
+ className={this.isSelected('sql_settings')}
+ onClick={this.handleClick.bind(this, 'sql_settings', null)}
+ >
+ {'SQL Settings'}
+ </a>
+ </li>
+ <li>
+ <a
+ href='#'
+ className={this.isSelected('email_settings')}
+ onClick={this.handleClick.bind(this, 'email_settings', null)}
+ >
+ {'Email Settings'}
+ </a>
+ </li>
+ <li>
+ <a
+ href='#'
+ className={this.isSelected('image_settings')}
+ onClick={this.handleClick.bind(this, 'image_settings', null)}
+ >
+ {'File Settings'}
+ </a>
+ </li>
+ <li>
+ <a
+ href='#'
+ className={this.isSelected('log_settings')}
+ onClick={this.handleClick.bind(this, 'log_settings', null)}
+ >
+ {'Log Settings'}
+ </a>
+ </li>
+ <li>
+ <a
+ href='#'
+ className={this.isSelected('rate_settings')}
+ onClick={this.handleClick.bind(this, 'rate_settings', null)}
+ >
+ {'Rate Limit Settings'}
+ </a>
+ </li>
+ <li>
+ <a
+ href='#'
+ className={this.isSelected('privacy_settings')}
+ onClick={this.handleClick.bind(this, 'privacy_settings', null)}
+ >
+ {'Privacy Settings'}
+ </a>
+ </li>
+ <li>
+ <a
+ href='#'
+ className={this.isSelected('gitlab_settings')}
+ onClick={this.handleClick.bind(this, 'gitlab_settings', null)}
+ >
+ {'GitLab Settings'}
+ </a>
+ </li>
+ </ul>
+ <ul className='nav nav__sub-menu'>
+ <li>
+ <h4>
+ <span className='icon fa fa-gear'></span>
+ <span>{'TEAMS (' + count + ')'}</span>
+ <span className='menu-icon--right'>
+ <a
+ href='#'
+ onClick={this.showTeamSelect}
+ >
+ <i className='fa fa-plus'></i>
+ </a>
+ </span>
+ </h4>
+ </li>
+ </ul>
+ <ul className='nav nav__sub-menu padded'>
+ <li>
+ {teams}
+ </li>
+ </ul>
+ <ul className='nav nav__sub-menu'>
+ <li>
+ <h4>
+ <span className='icon fa fa-gear'></span>
+ <span>{'OTHER'}</span>
+ </h4>
+ </li>
+ </ul>
+ <ul className='nav nav__sub-menu padded'>
+ <li>
+ <a
+ href='#'
+ className={this.isSelected('logs')}
+ onClick={this.handleClick.bind(this, 'logs', null)}
+ >
+ {'Logs'}
+ </a>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </div>
</div>
<SelectTeamModal
diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx
index 762a4ab26..3432f69ff 100644
--- a/web/react/components/admin_console/email_settings.jsx
+++ b/web/react/components/admin_console/email_settings.jsx
@@ -437,7 +437,7 @@ export default class EmailSettings extends React.Component {
</select>
<div className='help-text'>
<table
- className='table-bordered'
+ className='table table-bordered'
cellPadding='5'
>
<tr><td className='help-text'>{'None'}</td><td className='help-text'>{'Mattermost will send email over an unsecure connection.'}</td></tr>
@@ -447,7 +447,7 @@ export default class EmailSettings extends React.Component {
</div>
<div className='help-text'>
<button
- className='help-link'
+ className='btn'
onClick={this.handleTestConnection}
disabled={!this.state.sendEmailNotifications}
id='connection-button'
@@ -482,7 +482,7 @@ export default class EmailSettings extends React.Component {
<p className='help-text'>{'32-character salt added to signing of email invites. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p>
<div className='help-text'>
<button
- className='help-link'
+ className='btn'
onClick={this.handleGenerateInvite}
disabled={!this.state.sendEmailNotifications}
>
@@ -513,7 +513,7 @@ export default class EmailSettings extends React.Component {
<p className='help-text'>{'32-character salt added to signing of password reset emails. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p>
<div className='help-text'>
<button
- className='help-link'
+ className='btn'
onClick={this.handleGenerateReset}
disabled={!this.state.sendEmailNotifications}
>
diff --git a/web/react/components/admin_console/image_settings.jsx b/web/react/components/admin_console/image_settings.jsx
index e52f516e8..e08d39ca8 100644
--- a/web/react/components/admin_console/image_settings.jsx
+++ b/web/react/components/admin_console/image_settings.jsx
@@ -460,7 +460,7 @@ export default class FileSettings extends React.Component {
<p className='help-text'>{'32-character salt added to signing of public image links. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p>
<div className='help-text'>
<button
- className='help-link'
+ className='btn btn-default'
onClick={this.handleGenerate}
>
{'Re-Generate'}
diff --git a/web/react/components/admin_console/log_settings.jsx b/web/react/components/admin_console/log_settings.jsx
index 1c39c60e8..608ef9cc0 100644
--- a/web/react/components/admin_console/log_settings.jsx
+++ b/web/react/components/admin_console/log_settings.jsx
@@ -253,7 +253,7 @@ export default class LogSettings extends React.Component {
{'Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'}
<div className='help-text'>
<table
- className='table-bordered'
+ className='table table-bordered'
cellPadding='5'
>
<tr><td className='help-text'>{'%T'}</td><td className='help-text'>{'Time (15:04:05 MST)'}</td></tr>
diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx
index 32812e875..c5c6e19d4 100644
--- a/web/react/components/admin_console/user_item.jsx
+++ b/web/react/components/admin_console/user_item.jsx
@@ -127,7 +127,6 @@ export default class UserItem extends React.Component {
if (user.delete_at > 0) {
currentRoles = 'Inactive';
- currentRoles = 'Inactive';
showMakeMember = false;
showMakeAdmin = false;
showMakeSystemAdmin = false;
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index b81936b57..f15974d35 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -132,7 +132,26 @@ export default class ChannelHeader extends React.Component {
}
let dropdownContents = [];
- if (!isDirect) {
+ if (isDirect) {
+ dropdownContents.push(
+ <li
+ key='edit_description_direct'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#edit_channel'
+ data-desc={channel.description}
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ >
+ Set Channel Description...
+ </a>
+ </li>
+ );
+ } else {
dropdownContents.push(
<li
key='view_info'
@@ -276,25 +295,6 @@ export default class ChannelHeader extends React.Component {
</li>
);
}
- } else {
- dropdownContents.push(
- <li
- key='edit_description_direct'
- role='presentation'
- >
- <a
- role='menuitem'
- href='#'
- data-toggle='modal'
- data-target='#edit_channel'
- data-desc={channel.description}
- data-title={channel.display_name}
- data-channelid={channel.id}
- >
- Set Channel Description...
- </a>
- </li>
- );
}
return (
diff --git a/web/react/components/channel_members.jsx b/web/react/components/channel_members.jsx
index 1eda6a104..53c854eb7 100644
--- a/web/react/components/channel_members.jsx
+++ b/web/react/components/channel_members.jsx
@@ -15,6 +15,8 @@ export default class ChannelMembers extends React.Component {
this.getStateFromStores = this.getStateFromStores.bind(this);
this.onChange = this.onChange.bind(this);
this.handleRemove = this.handleRemove.bind(this);
+ this.onHide = this.onHide.bind(this);
+ this.onShow = this.onShow.bind(this);
this.state = this.getStateFromStores();
}
@@ -63,16 +65,18 @@ export default class ChannelMembers extends React.Component {
channelName: channelName
};
}
+ onHide() {
+ this.setState({renderMembers: false});
+ }
+ onShow() {
+ this.setState({renderMembers: true});
+ }
componentDidMount() {
ChannelStore.addExtraInfoChangeListener(this.onChange);
ChannelStore.addChangeListener(this.onChange);
- $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function handleHide() {
- this.setState({renderMembers: false});
- }.bind(this));
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide);
- $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow() {
- this.setState({renderMembers: true});
- }.bind(this));
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
}
componentWillUnmount() {
ChannelStore.removeExtraInfoChangeListener(this.onChange);
diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx
index 45981b295..ed76b7bce 100644
--- a/web/react/components/channel_notifications.jsx
+++ b/web/react/components/channel_notifications.jsx
@@ -23,6 +23,7 @@ export default class ChannelNotifications extends React.Component {
this.handleSubmitMarkUnreadLevel = this.handleSubmitMarkUnreadLevel.bind(this);
this.handleUpdateMarkUnreadLevel = this.handleUpdateMarkUnreadLevel.bind(this);
this.createMarkUnreadLevelSection = this.createMarkUnreadLevelSection.bind(this);
+ this.onShow = this.onShow.bind(this);
this.state = {
notifyLevel: '',
@@ -32,30 +33,29 @@ export default class ChannelNotifications extends React.Component {
activeSection: ''
};
}
+ onShow(e) {
+ var button = e.relatedTarget;
+ var channelId = button.getAttribute('data-channelid');
+ const member = ChannelStore.getMember(channelId);
+ var notifyLevel = member.notify_props.desktop;
+ var markUnreadLevel = member.notify_props.mark_unread;
+
+ this.setState({
+ notifyLevel,
+ markUnreadLevel,
+ title: button.getAttribute('data-title'),
+ channelId
+ });
+ }
componentDidMount() {
ChannelStore.addChangeListener(this.onListenerChange);
- $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function showModal(e) {
- var button = e.relatedTarget;
- var channelId = button.getAttribute('data-channelid');
-
- const member = ChannelStore.getMember(channelId);
- var notifyLevel = member.notify_props.desktop;
- var markUnreadLevel = member.notify_props.mark_unread;
-
- this.setState({
- notifyLevel,
- markUnreadLevel,
- title: button.getAttribute('data-title'),
- channelId: channelId
- });
- }.bind(this));
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange);
}
-
onListenerChange() {
if (!this.state.channelId) {
return;
@@ -76,7 +76,6 @@ export default class ChannelNotifications extends React.Component {
updateSection(section) {
this.setState({activeSection: section});
}
-
handleSubmitNotifyLevel() {
var channelId = this.state.channelId;
var notifyLevel = this.state.notifyLevel;
@@ -103,12 +102,10 @@ export default class ChannelNotifications extends React.Component {
}
);
}
-
handleUpdateNotifyLevel(notifyLevel) {
this.setState({notifyLevel});
React.findDOMNode(this.refs.modal).focus();
}
-
createNotifyLevelSection(serverError) {
var handleUpdateSection;
@@ -198,9 +195,7 @@ export default class ChannelNotifications extends React.Component {
const extraInfo = (
<span>
- {'Selecting an option other than "Default" will override the global notification settings.'}
- <br/>
- {'Desktop notifications are available on Firefox, Safari, and Chrome.'}
+ {'Selecting an option other than "Default" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.'}
</span>
);
diff --git a/web/react/components/command_list.jsx b/web/react/components/command_list.jsx
index fea7085b7..e027e87ae 100644
--- a/web/react/components/command_list.jsx
+++ b/web/react/components/command_list.jsx
@@ -51,7 +51,7 @@ export default class CommandList extends React.Component {
this.setState({suggestions: data.suggestions, cmd: cmd});
}.bind(this),
function fail() {
- }
+ }
);
}
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index 5097b3aa5..550f85d3d 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -106,10 +106,11 @@ export default class CreateComment extends React.Component {
let state = {};
if (err.message === 'Invalid RootId parameter') {
+ PostStore.removePendingPost(post.channel_id, post.pending_post_id);
+
if ($('#post_deleted').length > 0) {
$('#post_deleted').modal('show');
}
- PostStore.removePendingPost(post.pending_post_id);
} else {
post.state = Constants.POST_FAILED;
PostStore.updatePendingPost(post);
@@ -170,7 +171,9 @@ export default class CreateComment extends React.Component {
this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
}
handleUploadError(err, clientId) {
- if (clientId !== -1) {
+ if (clientId === -1) {
+ this.setState({serverError: err});
+ } else {
let draft = PostStore.getCommentDraft(this.props.rootId);
const index = draft.uploadsInProgress.indexOf(clientId);
@@ -181,8 +184,6 @@ export default class CreateComment extends React.Component {
PostStore.storeCommentDraft(this.props.rootId, draft);
this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err});
- } else {
- this.setState({serverError: err});
}
}
handleTextDrop(text) {
@@ -196,15 +197,15 @@ export default class CreateComment extends React.Component {
// id can either be the path of an uploaded file or the client id of an in progress upload
let index = previews.indexOf(id);
- if (index !== -1) {
- previews.splice(index, 1);
- } else {
+ if (index === -1) {
index = uploadsInProgress.indexOf(id);
if (index !== -1) {
uploadsInProgress.splice(index, 1);
this.refs.fileUpload.cancelUpload(id);
}
+ } else {
+ previews.splice(index, 1);
}
let draft = PostStore.getCommentDraft(this.props.rootId);
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 0cd14747d..6e83f4faf 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -222,7 +222,9 @@ export default class CreatePost extends React.Component {
this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
}
handleUploadError(err, clientId) {
- if (clientId !== -1) {
+ if (clientId === -1) {
+ this.setState({serverError: err});
+ } else {
const draft = PostStore.getDraft(this.state.channelId);
const index = draft.uploadsInProgress.indexOf(clientId);
@@ -233,8 +235,6 @@ export default class CreatePost extends React.Component {
PostStore.storeDraft(this.state.channelId, draft);
this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err});
- } else {
- this.setState({serverError: err});
}
}
handleTextDrop(text) {
@@ -248,15 +248,15 @@ export default class CreatePost extends React.Component {
// id can either be the path of an uploaded file or the client id of an in progress upload
let index = previews.indexOf(id);
- if (index !== -1) {
- previews.splice(index, 1);
- } else {
+ if (index === -1) {
index = uploadsInProgress.indexOf(id);
if (index !== -1) {
uploadsInProgress.splice(index, 1);
this.refs.fileUpload.cancelUpload(id);
}
+ } else {
+ previews.splice(index, 1);
}
const draft = PostStore.getCurrentDraft();
diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx
index 44c54db72..71c636921 100644
--- a/web/react/components/delete_channel_modal.jsx
+++ b/web/react/components/delete_channel_modal.jsx
@@ -11,6 +11,7 @@ export default class DeleteChannelModal extends React.Component {
super(props);
this.handleDelete = this.handleDelete.bind(this);
+ this.onShow = this.onShow.bind(this);
this.state = {
title: '',
@@ -32,14 +33,15 @@ export default class DeleteChannelModal extends React.Component {
}
);
}
+ onShow(e) {
+ var button = $(e.relatedTarget);
+ this.setState({
+ title: button.attr('data-title'),
+ channelId: button.attr('data-channelid')
+ });
+ }
componentDidMount() {
- $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) {
- var button = $(e.relatedTarget);
- this.setState({
- title: button.attr('data-title'),
- channelId: button.attr('data-channelid')
- });
- }.bind(this));
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
}
render() {
const channel = ChannelStore.getCurrent();
diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx
index 075f9c742..8e48a7a1c 100644
--- a/web/react/components/delete_post_modal.jsx
+++ b/web/react/components/delete_post_modal.jsx
@@ -16,6 +16,7 @@ export default class DeletePostModal extends React.Component {
this.handleDelete = this.handleDelete.bind(this);
this.onListenerChange = this.onListenerChange.bind(this);
+ this.onShow = this.onShow.bind(this);
this.state = {title: '', postId: '', channelId: '', selectedList: PostStore.getSelectedPost(), comments: 0};
}
@@ -60,18 +61,19 @@ export default class DeletePostModal extends React.Component {
}
);
}
+ onShow(e) {
+ var newState = {};
+ if (BrowserStore.getItem('edit_state_transfer')) {
+ newState = BrowserStore.getItem('edit_state_transfer');
+ BrowserStore.removeItem('edit_state_transfer');
+ } else {
+ var button = e.relatedTarget;
+ newState = {title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), postId: $(button).attr('data-postid'), comments: $(button).attr('data-comments')};
+ }
+ this.setState(newState);
+ }
componentDidMount() {
- $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function freshOpen(e) {
- var newState = {};
- if (BrowserStore.getItem('edit_state_transfer')) {
- newState = BrowserStore.getItem('edit_state_transfer');
- BrowserStore.removeItem('edit_state_transfer');
- } else {
- var button = e.relatedTarget;
- newState = {title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), postId: $(button).attr('data-postid'), comments: $(button).attr('data-comments')};
- }
- this.setState(newState);
- }.bind(this));
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
PostStore.addSelectedPostChangeListener(this.onListenerChange);
}
componentWillUnmount() {
diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx
index e93bab431..27219aba5 100644
--- a/web/react/components/edit_channel_modal.jsx
+++ b/web/react/components/edit_channel_modal.jsx
@@ -11,6 +11,7 @@ export default class EditChannelModal extends React.Component {
this.handleEdit = this.handleEdit.bind(this);
this.handleUserInput = this.handleUserInput.bind(this);
this.handleClose = this.handleClose.bind(this);
+ this.onShow = this.onShow.bind(this);
this.state = {
description: '',
@@ -50,11 +51,12 @@ export default class EditChannelModal extends React.Component {
handleClose() {
this.setState({description: '', serverError: ''});
}
+ onShow(e) {
+ const button = e.relatedTarget;
+ this.setState({description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''});
+ }
componentDidMount() {
- $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) {
- const button = e.relatedTarget;
- this.setState({description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''});
- }.bind(this));
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
$(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
}
componentWillUnmount() {
diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx
index 8d3f15525..391de3326 100644
--- a/web/react/components/email_verify.jsx
+++ b/web/react/components/email_verify.jsx
@@ -38,8 +38,8 @@ export default class EmailVerify extends React.Component {
}
return (
- <div className='col-sm-offset-4 col-sm-4'>
- <div className='panel panel-default'>
+ <div className='col-sm-12'>
+ <div className='panel panel-default verify_panel'>
<div className='panel-heading'>
<h3 className='panel-title'>{title}</h3>
</div>
diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx
index 05726e860..5aa55be93 100644
--- a/web/react/components/error_bar.jsx
+++ b/web/react/components/error_bar.jsx
@@ -9,28 +9,71 @@ export default class ErrorBar extends React.Component {
this.onErrorChange = this.onErrorChange.bind(this);
this.handleClose = this.handleClose.bind(this);
+ this.resize = this.resize.bind(this);
this.prevTimer = null;
this.state = ErrorStore.getLastError();
- if (this.state && this.state.message) {
+ if (this.isValidError(this.state)) {
this.prevTimer = setTimeout(this.handleClose, 10000);
}
}
+ isValidError(s) {
+ if (!s) {
+ return false;
+ }
+
+ if (!s.message) {
+ return false;
+ }
+
+ if (s.connErrorCount && s.connErrorCount >= 1 && s.connErrorCount < 7) {
+ return false;
+ }
+
+ return true;
+ }
+
+ isConnectionError(s) {
+ if (!s.connErrorCount || s.connErrorCount === 0) {
+ return false;
+ }
+
+ if (s.connErrorCount > 7) {
+ return true;
+ }
+
+ return false;
+ }
+
+ resize() {
+ if (this.isValidError(this.state)) {
+ var height = $(React.findDOMNode(this)).outerHeight();
+ height = height < 30 ? 30 : height;
+ $('body').css('padding-top', height + 'px');
+ } else {
+ $('body').css('padding-top', '0');
+ }
+ }
+
componentDidMount() {
ErrorStore.addChangeListener(this.onErrorChange);
- $('body').css('padding-top', $(React.findDOMNode(this)).outerHeight());
+
$(window).resize(() => {
- if (this.state && this.state.message) {
- $('body').css('padding-top', $(React.findDOMNode(this)).outerHeight());
- }
+ this.resize();
});
+
+ this.resize();
}
componentWillUnmount() {
ErrorStore.removeChangeListener(this.onErrorChange);
}
+ componentDidUpdate() {
+ this.resize();
+ }
+
onErrorChange() {
var newState = ErrorStore.getLastError();
@@ -41,7 +84,9 @@ export default class ErrorBar extends React.Component {
if (newState) {
this.setState(newState);
- this.prevTimer = setTimeout(this.handleClose, 10000);
+ if (!this.isConnectionError(newState)) {
+ this.prevTimer = setTimeout(this.handleClose, 10000);
+ }
} else {
this.setState({message: null});
}
@@ -52,22 +97,11 @@ export default class ErrorBar extends React.Component {
e.preventDefault();
}
- ErrorStore.storeLastError(null);
- ErrorStore.emitChange();
-
- $('body').css('padding-top', '0');
+ this.setState({message: null});
}
render() {
- if (!this.state) {
- return <div/>;
- }
-
- if (!this.state.message) {
- return <div/>;
- }
-
- if (this.state.connErrorCount < 7) {
+ if (!this.isValidError(this.state)) {
return <div/>;
}
diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx
index 3dc4e5de2..0e9297b7b 100644
--- a/web/react/components/file_upload.jsx
+++ b/web/react/components/file_upload.jsx
@@ -97,7 +97,7 @@ export default class FileUpload extends React.Component {
element[0].type = 'text';
element[0].type = 'file';
}
- } catch(e) {
+ } catch (e) {
// Do nothing
}
}
diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx
index 5d8b13f00..6e0728862 100644
--- a/web/react/components/get_link_modal.jsx
+++ b/web/react/components/get_link_modal.jsx
@@ -8,18 +8,22 @@ export default class GetLinkModal extends React.Component {
super(props);
this.handleClick = this.handleClick.bind(this);
+ this.onShow = this.onShow.bind(this);
+ this.onHide = this.onHide.bind(this);
this.state = {copiedLink: false};
}
+ onShow(e) {
+ var button = e.relatedTarget;
+ this.setState({title: $(button).attr('data-title'), value: $(button).attr('data-value')});
+ }
+ onHide() {
+ this.setState({copiedLink: false});
+ }
componentDidMount() {
if (this.refs.modal) {
- $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) {
- var button = e.relatedTarget;
- this.setState({title: $(button).attr('data-title'), value: $(button).attr('data-value')});
- }.bind(this));
- $(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', function hide() {
- this.setState({copiedLink: false});
- }.bind(this));
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
+ $(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', this.onHide);
}
}
handleClick() {
@@ -39,8 +43,23 @@ export default class GetLinkModal extends React.Component {
}
render() {
var currentUser = UserStore.getCurrentUser();
- var copyLinkConfirm = null;
+ let copyLink = null;
+ if (document.queryCommandSupported('copy')) {
+ copyLink = (
+ <button
+ data-copy-btn='true'
+ type='button'
+ className='btn btn-primary pull-left'
+ onClick={this.handleClick}
+ data-clipboard-text={this.state.value}
+ >
+ Copy Link
+ </button>
+ );
+ }
+
+ var copyLinkConfirm = null;
if (this.state.copiedLink) {
copyLinkConfirm = <p className='alert alert-success copy-link-confirm'><i className='fa fa-check'></i> Link copied to clipboard.</p>;
}
@@ -94,15 +113,7 @@ export default class GetLinkModal extends React.Component {
>
Close
</button>
- <button
- data-copy-btn='true'
- type='button'
- className='btn btn-primary pull-left'
- onClick={this.handleClick}
- data-clipboard-text={this.state.value}
- >
- Copy Link
- </button>
+ {copyLink}
{copyLinkConfirm}
</div>
</div>
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index 8cc4f1483..54df75cbc 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -88,10 +88,10 @@ export default class Login extends React.Component {
let focusEmail = false;
let focusPassword = false;
- if (priorEmail !== '') {
- focusPassword = true;
- } else {
+ if (priorEmail === '') {
focusEmail = true;
+ } else {
+ focusPassword = true;
}
let loginMessage = [];
@@ -112,6 +112,17 @@ export default class Login extends React.Component {
errorClass = ' has-error';
}
+ const verifiedParam = Utils.getUrlParameter('verified');
+ let verifiedBox = '';
+ if (verifiedParam) {
+ verifiedBox = (
+ <div className='alert alert-success'>
+ <i className='fa fa-check' />
+ {' Email Verified'}
+ </div>
+ );
+ }
+
let emailSignup;
if (global.window.config.EnableSignUpWithEmail === 'true') {
emailSignup = (
@@ -175,6 +186,7 @@ export default class Login extends React.Component {
<h2 className='signup-team__name'>{teamDisplayName}</h2>
<h2 className='signup-team__subdomain'>on {global.window.config.SiteName}</h2>
<form onSubmit={this.handleSubmit}>
+ {verifiedBox}
<div className={'form-group' + errorClass}>
{serverError}
</div>
diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx
index b7e81f843..629fb2ec4 100644
--- a/web/react/components/member_list_team_item.jsx
+++ b/web/react/components/member_list_team_item.jsx
@@ -24,32 +24,32 @@ export default class MemberListTeamItem extends React.Component {
};
Client.updateRoles(data,
- function handleMakeMemberSuccess() {
+ () => {
AsyncClient.getProfiles();
},
- function handleMakeMemberError(err) {
+ (err) => {
this.setState({serverError: err.message});
- }.bind(this)
+ }
);
}
handleMakeActive() {
Client.updateActive(this.props.user.id, true,
- function handleMakeActiveSuccess() {
+ () => {
AsyncClient.getProfiles();
},
- function handleMakeActiveError(err) {
+ (err) => {
this.setState({serverError: err.message});
- }.bind(this)
+ }
);
}
handleMakeNotActive() {
Client.updateActive(this.props.user.id, false,
- function handleMakeNotActiveSuccess() {
+ () => {
AsyncClient.getProfiles();
},
- function handleMakeNotActiveError(err) {
+ (err) => {
this.setState({serverError: err.message});
- }.bind(this)
+ }
);
}
handleMakeAdmin() {
@@ -59,12 +59,12 @@ export default class MemberListTeamItem extends React.Component {
};
Client.updateRoles(data,
- function handleMakeAdminSuccess() {
+ () => {
AsyncClient.getProfiles();
},
- function handleMakeAdmitError(err) {
+ (err) => {
this.setState({serverError: err.message});
- }.bind(this)
+ }
);
}
render() {
@@ -82,14 +82,18 @@ export default class MemberListTeamItem extends React.Component {
const timestamp = UserStore.getCurrentUser().update_at;
if (user.roles.length > 0) {
- currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1);
+ if (user.roles.indexOf('system_admin') > -1) {
+ currentRoles = 'System Admin';
+ } else {
+ currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1);
+ }
}
const email = user.email;
- let showMakeMember = user.roles === 'admin';
- let showMakeAdmin = user.roles === '';
+ let showMakeMember = user.roles === 'admin' || user.roles === 'system_admin';
+ let showMakeAdmin = user.roles === '' || user.roles === 'system_admin';
let showMakeActive = false;
- let showMakeNotActive = true;
+ let showMakeNotActive = user.roles !== 'system_admin';
if (user.delete_at > 0) {
currentRoles = 'Inactive';
@@ -108,7 +112,7 @@ export default class MemberListTeamItem extends React.Component {
href='#'
onClick={this.handleMakeAdmin}
>
- Make Admin
+ {'Make Admin'}
</a>
</li>
);
@@ -123,7 +127,7 @@ export default class MemberListTeamItem extends React.Component {
href='#'
onClick={this.handleMakeMember}
>
- Make Member
+ {'Make Member'}
</a>
</li>
);
@@ -138,7 +142,7 @@ export default class MemberListTeamItem extends React.Component {
href='#'
onClick={this.handleMakeActive}
>
- Make Active
+ {'Make Active'}
</a>
</li>
);
@@ -153,7 +157,7 @@ export default class MemberListTeamItem extends React.Component {
href='#'
onClick={this.handleMakeNotActive}
>
- Make Inactive
+ {'Make Inactive'}
</a>
</li>
);
diff --git a/web/react/components/mention.jsx b/web/react/components/mention.jsx
index 3a09e843d..ef7cec408 100644
--- a/web/react/components/mention.jsx
+++ b/web/react/components/mention.jsx
@@ -18,7 +18,9 @@ export default class Mention extends React.Component {
var timestamp = UserStore.getCurrentUser().update_at;
if (this.props.id === 'allmention' || this.props.id === 'channelmention') {
icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>;
- } else if (this.props.id != null) {
+ } else if (this.props.id == null) {
+ icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>;
+ } else {
icon = (
<span>
<img
@@ -27,8 +29,6 @@ export default class Mention extends React.Component {
/>
</span>
);
- } else {
- icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>;
}
return (
<div
diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx
index 46a9d76ae..72f51013c 100644
--- a/web/react/components/mention_list.jsx
+++ b/web/react/components/mention_list.jsx
@@ -26,53 +26,64 @@ export default class MentionList extends React.Component {
this.addFirstMention = this.addFirstMention.bind(this);
this.isEmpty = this.isEmpty.bind(this);
this.scrollToMention = this.scrollToMention.bind(this);
+ this.onScroll = this.onScroll.bind(this);
+ this.onMentionListKey = this.onMentionListKey.bind(this);
+ this.onClick = this.onClick.bind(this);
this.state = {excludeUsers: [], mentionText: '-1', selectedMention: 0, selectedUsername: ''};
}
- componentDidMount() {
- PostStore.addMentionDataChangeListener(this.onListenerChange);
+ onScroll() {
+ if ($('.mentions--top').length) {
+ $('#reply_mention_tab .mentions--top').css({bottom: $(window).height() - $('.post-right__scroll #reply_textbox').offset().top});
+ }
+ }
+ onMentionListKey(e) {
+ if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 13 || e.which === 9)) {
+ e.stopPropagation();
+ e.preventDefault();
+ this.addCurrentMention();
+ } else if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 38 || e.which === 40)) {
+ e.stopPropagation();
+ e.preventDefault();
- $('.post-right__scroll').scroll(function onScroll() {
- if ($('.mentions--top').length) {
- $('#reply_mention_tab .mentions--top').css({bottom: $(window).height() - $('.post-right__scroll #reply_textbox').offset().top});
+ if (e.which === 38) {
+ if (this.getSelection(this.state.selectedMention - 1)) {
+ this.setState({selectedMention: this.state.selectedMention - 1, selectedUsername: this.refs['mention' + (this.state.selectedMention - 1)].props.username});
+ }
+ } else if (e.which === 40) {
+ if (this.getSelection(this.state.selectedMention + 1)) {
+ this.setState({selectedMention: this.state.selectedMention + 1, selectedUsername: this.refs['mention' + (this.state.selectedMention + 1)].props.username});
+ }
}
- });
- $('body').on('keydown.mentionlist', '#' + this.props.id,
- function onMentionListKey(e) {
- if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 13 || e.which === 9)) {
- e.stopPropagation();
- e.preventDefault();
- this.addCurrentMention();
- } else if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 38 || e.which === 40)) {
- e.stopPropagation();
- e.preventDefault();
+ this.scrollToMention(e.which);
+ }
+ }
+ onClick(e) {
+ if (!($('#' + this.props.id).is(e.target) || $('#' + this.props.id).has(e.target).length ||
+ ('mentionlist' in this.refs && $(React.findDOMNode(this.refs.mentionlist)).has(e.target).length))) {
+ this.setState({mentionText: '-1'});
+ }
+ }
+ componentDidMount() {
+ PostStore.addMentionDataChangeListener(this.onListenerChange);
- if (e.which === 38) {
- if (this.getSelection(this.state.selectedMention - 1)) {
- this.setState({selectedMention: this.state.selectedMention - 1, selectedUsername: this.refs['mention' + (this.state.selectedMention - 1)].props.username});
- }
- } else if (e.which === 40) {
- if (this.getSelection(this.state.selectedMention + 1)) {
- this.setState({selectedMention: this.state.selectedMention + 1, selectedUsername: this.refs['mention' + (this.state.selectedMention + 1)].props.username});
- }
- }
+ $('.post-right__scroll').scroll(this.onScroll);
- this.scrollToMention(e.which);
- }
- }.bind(this)
- );
- $(document).click(function onClick(e) {
- if (!($('#' + this.props.id).is(e.target) || $('#' + this.props.id).has(e.target).length ||
- ('mentionlist' in this.refs && $(React.findDOMNode(this.refs.mentionlist)).has(e.target).length))) {
- this.setState({mentionText: '-1'});
- }
- }.bind(this));
+ $('body').on('keydown.mentionlist', '#' + this.props.id, this.onMentionListKey);
+ $(document).click(this.onClick);
}
componentWillUnmount() {
PostStore.removeMentionDataChangeListener(this.onListenerChange);
$('body').off('keydown.mentionlist', '#' + this.props.id);
}
+
+ /*
+ * This component is poorly designed, nessesitating some state modification
+ * in the componentDidUpdate function. This is generally discouraged as it
+ * is a performance issue and breaks with good react design. This component
+ * should be redesigned.
+ */
componentDidUpdate() {
if (this.state.mentionText !== '-1') {
if (this.state.selectedUsername !== '' && (!this.getSelection(this.state.selectedMention) || this.state.selectedUsername !== this.refs['mention' + this.state.selectedMention].props.username)) {
@@ -80,17 +91,17 @@ export default class MentionList extends React.Component {
var foundMatch = false;
while (tempSelectedMention < this.state.selectedMention && this.getSelection(++tempSelectedMention)) {
if (this.state.selectedUsername === this.refs['mention' + tempSelectedMention].props.username) {
- this.setState({selectedMention: tempSelectedMention});
+ this.setState({selectedMention: tempSelectedMention}); //eslint-disable-line react/no-did-update-set-state
foundMatch = true;
break;
}
}
if (this.getSelection(0) && !foundMatch) {
- this.setState({selectedMention: 0, selectedUsername: this.refs.mention0.props.username});
+ this.setState({selectedMention: 0, selectedUsername: this.refs.mention0.props.username}); //eslint-disable-line react/no-did-update-set-state
}
}
} else if (this.state.selectedMention !== 0) {
- this.setState({selectedMention: 0, selectedUsername: ''});
+ this.setState({selectedMention: 0, selectedUsername: ''}); //eslint-disable-line react/no-did-update-set-state
}
}
onListenerChange(id, mentionText) {
@@ -124,10 +135,10 @@ export default class MentionList extends React.Component {
return true;
}
addCurrentMention() {
- if (!this.getSelection(this.state.selectedMention)) {
- this.addFirstMention();
- } else {
+ if (this.getSelection(this.state.selectedMention)) {
this.refs['mention' + this.state.selectedMention].handleClick();
+ } else {
+ this.addFirstMention();
}
}
addFirstMention() {
diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx
index 65cd40975..487192d91 100644
--- a/web/react/components/more_channels.jsx
+++ b/web/react/components/more_channels.jsx
@@ -60,9 +60,7 @@ export default class MoreChannels extends React.Component {
this.setState({joiningChannel: -1});
}.bind(this),
function joinFail(err) {
- this.setState({joiningChannel: -1});
- this.state.serverError = err.message;
- this.setState(this.state);
+ this.setState({joiningChannel: -1, serverError: err.message});
}.bind(this)
);
}
@@ -81,56 +79,54 @@ export default class MoreChannels extends React.Component {
if (this.state.channels != null) {
var channels = this.state.channels;
- if (!channels.loading) {
- if (channels.length) {
- moreChannels = (
- <table className='more-channel-table table'>
- <tbody>
- {channels.map(function cMap(channel, index) {
- var joinButton;
- if (self.state.joiningChannel === index) {
- joinButton = (
- <img
- className='join-channel-loading-gif'
- src='/static/images/load.gif'
- />
- );
- } else {
- joinButton = (
- <button
- onClick={self.handleJoin.bind(self, channel, index)}
- className='btn btn-primary'
- >
- Join
- </button>
- );
- }
+ if (channels.loading) {
+ moreChannels = <LoadingScreen />;
+ } else if (channels.length) {
+ moreChannels = (
+ <table className='more-channel-table table'>
+ <tbody>
+ {channels.map(function cMap(channel, index) {
+ var joinButton;
+ if (self.state.joiningChannel === index) {
+ joinButton = (
+ <img
+ className='join-channel-loading-gif'
+ src='/static/images/load.gif'
+ />
+ );
+ } else {
+ joinButton = (
+ <button
+ onClick={self.handleJoin.bind(self, channel, index)}
+ className='btn btn-primary'
+ >
+ Join
+ </button>
+ );
+ }
- return (
- <tr key={channel.id}>
- <td>
- <p className='more-channel-name'>{channel.display_name}</p>
- <p className='more-channel-description'>{channel.description}</p>
- </td>
- <td className='td--action'>
- {joinButton}
- </td>
- </tr>
- );
- })}
- </tbody>
- </table>
- );
- } else {
- moreChannels = (
- <div className='no-channel-message'>
- <p className='primary-message'>No more channels to join</p>
- <p className='secondary-message'>Click 'Create New Channel' to make a new one</p>
- </div>
- );
- }
+ return (
+ <tr key={channel.id}>
+ <td>
+ <p className='more-channel-name'>{channel.display_name}</p>
+ <p className='more-channel-description'>{channel.description}</p>
+ </td>
+ <td className='td--action'>
+ {joinButton}
+ </td>
+ </tr>
+ );
+ })}
+ </tbody>
+ </table>
+ );
} else {
- moreChannels = <LoadingScreen />;
+ moreChannels = (
+ <div className='no-channel-message'>
+ <p className='primary-message'>No more channels to join</p>
+ <p className='secondary-message'>Click 'Create New Channel' to make a new one</p>
+ </div>
+ );
}
}
diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx
index 54d77c358..c71abd43a 100644
--- a/web/react/components/more_direct_channels.jsx
+++ b/web/react/components/more_direct_channels.jsx
@@ -31,22 +31,7 @@ export default class MoreDirectChannels extends React.Component {
var active = '';
var handleClick = null;
- if (!channel.fake) {
- if (channel.id === ChannelStore.getCurrentId()) {
- active = 'active';
- }
-
- if (channel.unread) {
- badge = <span className='badge pull-right small'>{channel.unread}</span>;
- titleClass = 'unread-title';
- }
-
- handleClick = function clickHandler(e) {
- e.preventDefault();
- utils.switchChannel(channel);
- $(React.findDOMNode(self.refs.modal)).modal('hide');
- };
- } else {
+ if (channel.fake) {
// It's a direct message channel that doesn't exist yet so let's create it now
var otherUserId = utils.getUserIdFromChannelName(channel);
@@ -78,6 +63,21 @@ export default class MoreDirectChannels extends React.Component {
);
};
}
+ } else {
+ if (channel.id === ChannelStore.getCurrentId()) {
+ active = 'active';
+ }
+
+ if (channel.unread) {
+ badge = <span className='badge pull-right small'>{channel.unread}</span>;
+ titleClass = 'unread-title';
+ }
+
+ handleClick = function clickHandler(e) {
+ e.preventDefault();
+ utils.switchChannel(channel);
+ $(React.findDOMNode(self.refs.modal)).modal('hide');
+ };
}
return (
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
index 78057d10b..ff7a53848 100644
--- a/web/react/components/navbar_dropdown.jsx
+++ b/web/react/components/navbar_dropdown.jsx
@@ -6,6 +6,8 @@ var client = require('../utils/client.jsx');
var UserStore = require('../stores/user_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
+var AboutBuildModal = require('./about_build_modal.jsx');
+
var Constants = require('../utils/constants.jsx');
function getStateFromStores() {
@@ -18,7 +20,9 @@ export default class NavbarDropdown extends React.Component {
this.blockToggle = false;
this.handleLogoutClick = this.handleLogoutClick.bind(this);
+ this.handleAboutModal = this.handleAboutModal.bind(this);
this.onListenerChange = this.onListenerChange.bind(this);
+ this.aboutModalDismissed = this.aboutModalDismissed.bind(this);
this.state = getStateFromStores();
}
@@ -26,6 +30,12 @@ export default class NavbarDropdown extends React.Component {
e.preventDefault();
client.logout();
}
+ handleAboutModal() {
+ this.setState({showAboutModal: true});
+ }
+ aboutModalDismissed() {
+ this.setState({showAboutModal: false});
+ }
componentDidMount() {
UserStore.addTeamsChangeListener(this.onListenerChange);
TeamStore.addChangeListener(this.onListenerChange);
@@ -135,30 +145,35 @@ export default class NavbarDropdown extends React.Component {
var teams = [];
- teams.push(
- <li
- className='divider'
- key='div'
- >
- </li>
- );
-
if (this.state.teams.length > 1) {
+ teams.push(
+ <li
+ className='divider'
+ key='div'
+ >
+ </li>
+ );
+
this.state.teams.forEach((teamName) => {
if (teamName !== this.props.teamName) {
teams.push(<li key={teamName}><a href={Utils.getWindowLocationOrigin() + '/' + teamName}>{'Switch to ' + teamName}</a></li>);
}
});
}
- teams.push(<li key='newTeam_li'>
- <a
- key='newTeam_a'
- target='_blank'
- href={Utils.getWindowLocationOrigin() + '/signup_team'}
- >
- {'Create a New Team'}
- </a>
- </li>);
+
+ if (global.window.config.EnableTeamCreation === 'true') {
+ teams.push(
+ <li key='newTeam_li'>
+ <a
+ key='newTeam_a'
+ target='_blank'
+ href={Utils.getWindowLocationOrigin() + '/signup_team'}
+ >
+ {'Create a New Team'}
+ </a>
+ </li>
+ );
+ }
return (
<ul className='nav navbar-nav navbar-right'>
@@ -223,6 +238,18 @@ export default class NavbarDropdown extends React.Component {
{'Report a Problem'}
</a>
</li>
+ <li>
+ <a
+ href='#'
+ onClick={this.handleAboutModal}
+ >
+ {'About Mattermost'}
+ </a>
+ </li>
+ <AboutBuildModal
+ show={this.state.showAboutModal}
+ onModalDismissed={this.aboutModalDismissed}
+ />
</ul>
</li>
</ul>
diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx
index 95a88c3d6..aaaea3c6f 100644
--- a/web/react/components/popover_list_members.jsx
+++ b/web/react/components/popover_list_members.jsx
@@ -67,9 +67,6 @@ export default class PopoverListMembers extends React.Component {
>
<div
id='member_tooltip'
- data-placement='left'
- data-toggle='tooltip'
- title='View Channel Members'
>
{countText}
<span
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index 6cfd243de..1d94cab47 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -215,7 +215,7 @@ export default class PostBody extends React.Component {
comment = (
<p className='post-link'>
<span>
- {'Commented on '}{name}{apostrophe}{' message:'}
+ {'Commented on '}{name}{apostrophe}{' message: '}
<a
className='theme'
onClick={this.props.handleCommentClick}
diff --git a/web/react/components/post_deleted_modal.jsx b/web/react/components/post_deleted_modal.jsx
index d284a9d1b..3f487d20f 100644
--- a/web/react/components/post_deleted_modal.jsx
+++ b/web/react/components/post_deleted_modal.jsx
@@ -2,13 +2,41 @@
// See License.txt for license information.
var UserStore = require('../stores/user_store.jsx');
+var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+var Constants = require('../utils/constants.jsx');
+var ActionTypes = Constants.ActionTypes;
export default class PostDeletedModal extends React.Component {
constructor(props) {
super(props);
+ this.handleClose = this.handleClose.bind(this);
+
this.state = {};
}
+ componentDidMount() {
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', () => {
+ this.handleClose();
+ });
+ }
+ handleClose() {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_SEARCH,
+ results: null
+ });
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_SEARCH_TERM,
+ term: null,
+ do_search: false,
+ is_mention_search: false
+ });
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_POST_SELECTED,
+ results: null
+ });
+ }
render() {
var currentUser = UserStore.getCurrentUser();
@@ -31,17 +59,17 @@ export default class PostDeletedModal extends React.Component {
data-dismiss='modal'
aria-label='Close'
>
- <span aria-hidden='true'>&times;</span>
+ <span aria-hidden='true'>{'×'}</span>
</button>
<h4
className='modal-title'
id='myModalLabel'
>
- Comment could not be posted
+ {'Comment could not be posted'}
</h4>
</div>
<div className='modal-body'>
- <p>Someone deleted the message on which you tried to post a comment.</p>
+ <p>{'Someone deleted the message on which you tried to post a comment.'}</p>
</div>
<div className='modal-footer'>
<button
@@ -49,7 +77,7 @@ export default class PostDeletedModal extends React.Component {
className='btn btn-primary'
data-dismiss='modal'
>
- Okay
+ {'Okay'}
</button>
</div>
</div>
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index dba75ac5f..c1e8979a4 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -13,12 +13,6 @@ export default class PostInfo extends React.Component {
super(props);
this.state = {};
}
- shouldShowComment(state, type, isOwner) {
- if (state === Constants.POST_FAILED || state === Constants.POST_LOADING) {
- return false;
- }
- return isOwner || (this.props.allowReply === 'true' && type !== 'Comment');
- }
createDropdown() {
var post = this.props.post;
var isOwner = UserStore.getCurrentId() === post.user_id;
@@ -33,10 +27,6 @@ export default class PostInfo extends React.Component {
type = 'Comment';
}
- if (!this.shouldShowComment(post.state, type, isOwner)) {
- return '';
- }
-
var dropdownContents = [];
var dataComments = 0;
if (type === 'Post') {
@@ -106,6 +96,10 @@ export default class PostInfo extends React.Component {
);
}
+ if (dropdownContents.length === 0) {
+ return '';
+ }
+
return (
<div>
<a
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index b90197ac4..6741a9bdd 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -37,9 +37,11 @@ export default class PostList extends React.Component {
this.deactivate = this.deactivate.bind(this);
this.resize = this.resize.bind(this);
- this.state = this.getStateFromStores(props.channelId);
- this.state.numToDisplay = Constants.POST_CHUNK_SIZE;
- this.state.isFirstLoadComplete = false;
+ const state = this.getStateFromStores(props.channelId);
+ state.numToDisplay = Constants.POST_CHUNK_SIZE;
+ state.isFirstLoadComplete = false;
+
+ this.state = state;
}
getStateFromStores(id) {
var postList = PostStore.getPosts(id);
@@ -449,10 +451,10 @@ export default class PostList extends React.Component {
}
var createMessage;
- if (creatorName !== '') {
- createMessage = (<span>This is the start of the <strong>{uiName}</strong> {uiType}, created by <strong>{creatorName}</strong> on <strong>{utils.displayDate(channel.create_at)}</strong></span>);
- } else {
+ if (creatorName === '') {
createMessage = 'This is the start of the ' + uiName + ' ' + uiType + ', created on ' + utils.displayDate(channel.create_at) + '.';
+ } else {
+ createMessage = (<span>This is the start of the <strong>{uiName}</strong> {uiType}, created by <strong>{creatorName}</strong> on <strong>{utils.displayDate(channel.create_at)}</strong></span>);
}
return (
diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx
index 9d514c741..d60206ecf 100644
--- a/web/react/components/rename_channel_modal.jsx
+++ b/web/react/components/rename_channel_modal.jsx
@@ -15,6 +15,7 @@ export default class RenameChannelModal extends React.Component {
this.onDisplayNameChange = this.onDisplayNameChange.bind(this);
this.displayNameKeyUp = this.displayNameKeyUp.bind(this);
this.handleClose = this.handleClose.bind(this);
+ this.handleShow = this.handleShow.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
@@ -59,11 +60,11 @@ export default class RenameChannelModal extends React.Component {
state.invalid = true;
} else {
let cleanedName = Utils.cleanUpUrlable(channel.name);
- if (cleanedName !== channel.name) {
+ if (cleanedName === channel.name) {
+ state.nameError = '';
+ } else {
state.nameError = 'Must be lowercase alphanumeric characters';
state.invalid = true;
- } else {
- state.nameError = '';
}
}
@@ -103,7 +104,7 @@ export default class RenameChannelModal extends React.Component {
this.setState({channelName: channelName});
}
handleClose() {
- this.state = {
+ this.setState({
displayName: '',
channelName: '',
channelId: '',
@@ -111,13 +112,14 @@ export default class RenameChannelModal extends React.Component {
nameError: '',
displayNameError: '',
invalid: false
- };
+ });
+ }
+ handleShow(e) {
+ const button = $(e.relatedTarget);
+ this.setState({displayName: button.attr('data-display'), channelName: button.attr('data-name'), channelId: button.attr('data-channelid')});
}
componentDidMount() {
- $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) {
- const button = $(e.relatedTarget);
- this.setState({displayName: button.attr('data-display'), channelName: button.attr('data-name'), channelId: button.attr('data-channelid')});
- }.bind(this));
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.handleShow);
$(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
}
componentWillUnmount() {
diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx
index 5b4694eb1..aa355f8cc 100644
--- a/web/react/components/rhs_comment.jsx
+++ b/web/react/components/rhs_comment.jsx
@@ -70,19 +70,84 @@ export default class RhsComment extends React.Component {
componentDidUpdate() {
this.parseEmojis();
}
- render() {
+ createDropdown() {
var post = this.props.post;
- var currentUserCss = '';
- if (UserStore.getCurrentId() === post.user_id) {
- currentUserCss = 'current--user';
+ if (post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING || post.state === Constants.POST_DELETED) {
+ return '';
}
var isOwner = UserStore.getCurrentId() === post.user_id;
+ var isAdmin = Utils.isAdmin(UserStore.getCurrentUser().roles);
+
+ var dropdownContents = [];
+
+ if (isOwner) {
+ dropdownContents.push(
+ <li role='presentation'>
+ <a
+ href='#'
+ role='menuitem'
+ data-toggle='modal'
+ data-target='#edit_post'
+ data-title='Comment'
+ data-message={post.message}
+ data-postid={post.id}
+ data-channelid={post.channel_id}
+ >
+ Edit
+ </a>
+ </li>
+ );
+ }
- var type = 'Post';
- if (post.root_id.length > 0) {
- type = 'Comment';
+ if (isOwner || isAdmin) {
+ dropdownContents.push(
+ <li role='presentation'>
+ <a
+ href='#'
+ role='menuitem'
+ data-toggle='modal'
+ data-target='#delete_post'
+ data-title='Comment'
+ data-postid={post.id}
+ data-channelid={post.channel_id}
+ data-comments={0}
+ >
+ Delete
+ </a>
+ </li>
+ );
+ }
+
+ if (dropdownContents.length === 0) {
+ return '';
+ }
+
+ return (
+ <div className='dropdown'>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ data-toggle='dropdown'
+ aria-expanded='false'
+ />
+ <ul
+ className='dropdown-menu'
+ role='menu'
+ >
+ {dropdownContents}
+ </ul>
+ </div>
+ );
+ }
+ render() {
+ var post = this.props.post;
+
+ var currentUserCss = '';
+ if (UserStore.getCurrentId() === post.user_id) {
+ currentUserCss = 'current--user';
}
var timestamp = UserStore.getCurrentUser().update_at;
@@ -110,53 +175,7 @@ export default class RhsComment extends React.Component {
);
}
- var ownerOptions;
- if (isOwner && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) {
- ownerOptions = (
- <div className='dropdown'>
- <a
- href='#'
- className='dropdown-toggle theme'
- type='button'
- data-toggle='dropdown'
- aria-expanded='false'
- />
- <ul
- className='dropdown-menu'
- role='menu'
- >
- <li role='presentation'>
- <a
- href='#'
- role='menuitem'
- data-toggle='modal'
- data-target='#edit_post'
- data-title={type}
- data-message={post.message}
- data-postid={post.id}
- data-channelid={post.channel_id}
- >
- Edit
- </a>
- </li>
- <li role='presentation'>
- <a
- href='#'
- role='menuitem'
- data-toggle='modal'
- data-target='#delete_post'
- data-title={type}
- data-postid={post.id}
- data-channelid={post.channel_id}
- data-comments={0}
- >
- Delete
- </a>
- </li>
- </ul>
- </div>
- );
- }
+ var dropdown = this.createDropdown();
var fileAttachment;
if (post.filenames && post.filenames.length > 0) {
@@ -190,7 +209,7 @@ export default class RhsComment extends React.Component {
</time>
</li>
<li className='post-header-col post-header__reply'>
- {ownerOptions}
+ {dropdown}
</li>
</ul>
<div className='post-body'>
diff --git a/web/react/components/rhs_thread.jsx b/web/react/components/rhs_thread.jsx
index 2f23d80d9..27a784701 100644
--- a/web/react/components/rhs_thread.jsx
+++ b/web/react/components/rhs_thread.jsx
@@ -23,7 +23,7 @@ export default class RhsThread extends React.Component {
}
getStateFromStores() {
var postList = PostStore.getSelectedPost();
- if (!postList || postList.order.length < 1) {
+ if (!postList || postList.order.length < 1 || !postList.posts[postList.order[0]]) {
return {postList: {}};
}
@@ -49,7 +49,10 @@ export default class RhsThread extends React.Component {
}.bind(this));
}
componentDidUpdate() {
- $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight);
+ if ($('.post-right__scroll')[0]) {
+ $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight);
+ }
+
$('.post-right__scroll').perfectScrollbar('update');
this.resize();
}
@@ -67,7 +70,7 @@ export default class RhsThread extends React.Component {
// if something was changed in the channel like adding a
// comment or post then lets refresh the sidebar list
var currentSelected = PostStore.getSelectedPost();
- if (!currentSelected || currentSelected.order.length === 0) {
+ if (!currentSelected || currentSelected.order.length === 0 || !currentSelected.posts[currentSelected.order[0]]) {
return;
}
@@ -103,7 +106,7 @@ export default class RhsThread extends React.Component {
render() {
var postList = this.state.postList;
- if (postList == null) {
+ if (postList == null || !postList.order) {
return (
<div></div>
);
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index c0841a508..88eaed335 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -29,9 +29,11 @@ export default class Sidebar extends React.Component {
this.updateUnreadIndicators = this.updateUnreadIndicators.bind(this);
this.createChannelElement = this.createChannelElement.bind(this);
- this.state = this.getStateFromStores();
- this.state.modal = '';
- this.state.loadingDMChannel = -1;
+ const state = this.getStateFromStores();
+ state.modal = '';
+ state.loadingDMChannel = -1;
+
+ this.state = state;
}
getStateFromStores() {
var members = ChannelStore.getAllMembers();
@@ -65,7 +67,18 @@ export default class Sidebar extends React.Component {
var channel = ChannelStore.getByName(channelName);
- if (channel != null) {
+ if (channel == null) {
+ var tempChannel = {};
+ tempChannel.fake = true;
+ tempChannel.name = channelName;
+ tempChannel.display_name = teammate.username;
+ tempChannel.teammate_username = teammate.username;
+ tempChannel.status = UserStore.getStatus(teammate.id);
+ tempChannel.last_post_at = 0;
+ tempChannel.total_msg_count = 0;
+ tempChannel.type = 'D';
+ readDirectChannels.push(tempChannel);
+ } else {
channel.display_name = teammate.username;
channel.teammate_username = teammate.username;
@@ -80,17 +93,6 @@ export default class Sidebar extends React.Component {
} else {
readDirectChannels.push(channel);
}
- } else {
- var tempChannel = {};
- tempChannel.fake = true;
- tempChannel.name = channelName;
- tempChannel.display_name = teammate.username;
- tempChannel.teammate_username = teammate.username;
- tempChannel.status = UserStore.getStatus(teammate.id);
- tempChannel.last_post_at = 0;
- tempChannel.total_msg_count = 0;
- tempChannel.type = 'D';
- readDirectChannels.push(tempChannel);
}
}
@@ -203,7 +205,7 @@ export default class Sidebar extends React.Component {
const user = UserStore.getCurrentUser();
const member = ChannelStore.getMember(msg.channel_id);
- var notifyLevel = member.notify_props.desktop;
+ var notifyLevel = member && member.notify_props ? member.notify_props.desktop : 'default';
if (notifyLevel === 'default') {
notifyLevel = user.notify_props.desktop;
}
diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx
index 708cd04cb..573515a46 100644
--- a/web/react/components/sidebar_right.jsx
+++ b/web/react/components/sidebar_right.jsx
@@ -30,11 +30,11 @@ export default class SidebarRight extends React.Component {
PostStore.removeSelectedPostChangeListener(this.onSelectedChange);
}
componentDidUpdate() {
- if (!this.plScrolledToBottom) {
- $('.top-visible-post')[0].scrollIntoView();
- } else {
+ if (this.plScrolledToBottom) {
var postHolder = $('.post-list-holder-by-time').not('.inactive');
postHolder.scrollTop(postHolder[0].scrollHeight);
+ } else {
+ $('.top-visible-post')[0].scrollIntoView();
}
}
onSelectedChange(fromSearch) {
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx
index 495159efc..4e17c6d06 100644
--- a/web/react/components/signup_user_complete.jsx
+++ b/web/react/components/signup_user_complete.jsx
@@ -20,8 +20,6 @@ export default class SignupUserComplete extends React.Component {
initialState.user = {};
initialState.user.team_id = this.props.teamId;
initialState.user.email = this.props.email;
- initialState.hash = this.props.hash;
- initialState.data = this.props.data;
initialState.original_email = this.props.email;
}
@@ -41,15 +39,13 @@ export default class SignupUserComplete extends React.Component {
return;
}
- this.state.user.email = providedEmail;
-
- this.state.user.username = React.findDOMNode(this.refs.name).value.trim().toLowerCase();
- if (!this.state.user.username) {
+ const providedUsername = React.findDOMNode(this.refs.name).value.trim().toLowerCase();
+ if (!providedUsername) {
this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''});
return;
}
- var usernameError = Utils.isValidUsername(this.state.user.username);
+ const usernameError = Utils.isValidUsername(providedUsername);
if (usernameError === 'Cannot use a reserved word as a username.') {
this.setState({nameError: 'This username is reserved, please choose a new one.', emailError: '', passwordError: '', serverError: ''});
return;
@@ -63,23 +59,35 @@ export default class SignupUserComplete extends React.Component {
return;
}
- this.state.user.password = React.findDOMNode(this.refs.password).value.trim();
- if (!this.state.user.password || this.state.user.password .length < 5) {
+ const providedPassword = React.findDOMNode(this.refs.password).value.trim();
+ if (!providedPassword || providedPassword.length < 5) {
this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''});
return;
}
- this.setState({nameError: '', emailError: '', passwordError: '', serverError: ''});
+ const user = {
+ team_id: this.props.teamId,
+ email: providedEmail,
+ username: providedUsername,
+ password: providedPassword,
+ allow_marketing: true
+ };
- this.state.user.allow_marketing = true;
+ this.setState({
+ user,
+ nameError: '',
+ emailError: '',
+ passwordError: '',
+ serverError: ''
+ });
- client.createUser(this.state.user, this.state.data, this.state.hash,
+ client.createUser(user, this.props.data, this.props.hash,
function createUserSuccess() {
client.track('signup', 'signup_user_02_complete');
- client.loginByEmail(this.props.teamName, this.state.user.email, this.state.user.password,
+ client.loginByEmail(this.props.teamName, user.email, user.password,
function emailLoginSuccess(data) {
- UserStore.setLastEmail(this.state.user.email);
+ UserStore.setLastEmail(user.email);
UserStore.setCurrentUser(data);
if (this.props.hash > 0) {
BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'}));
@@ -88,7 +96,7 @@ export default class SignupUserComplete extends React.Component {
}.bind(this),
function emailLoginFailure(err) {
if (err.message === 'Login failed because email address has not been verified') {
- window.location.href = '/verify_email?email=' + encodeURIComponent(this.state.user.email) + '&teamname=' + encodeURIComponent(this.props.teamName);
+ window.location.href = '/verify_email?email=' + encodeURIComponent(user.email) + '&teamname=' + encodeURIComponent(this.props.teamName);
} else {
this.setState({serverError: err.message});
}
diff --git a/web/react/components/team_signup_choose_auth.jsx b/web/react/components/team_signup_choose_auth.jsx
index b8264b887..8cdeace03 100644
--- a/web/react/components/team_signup_choose_auth.jsx
+++ b/web/react/components/team_signup_choose_auth.jsx
@@ -52,7 +52,7 @@ export default class ChooseAuthPage extends React.Component {
<div>
{buttons}
<div className='form-group margin--extra-2x'>
- <span><a href='/find_team'>{'Find my team'}</a></span>
+ <span><a href='/find_team'>{'Find my teams'}</a></span>
</div>
</div>
);
diff --git a/web/react/components/team_signup_email_item.jsx b/web/react/components/team_signup_email_item.jsx
index 10bb2d69e..01330a46c 100644
--- a/web/react/components/team_signup_email_item.jsx
+++ b/web/react/components/team_signup_email_item.jsx
@@ -23,17 +23,14 @@ export default class TeamSignupEmailItem extends React.Component {
}
if (!Utils.isEmail(email)) {
- this.state.emailError = 'Please enter a valid email address';
- this.setState(this.state);
+ this.setState({emailError: 'Please enter a valid email address'});
return false;
} else if (email === teamEmail) {
- this.state.emailError = 'Please use a different email than the one used at signup';
- this.setState(this.state);
+ this.setState({emailError: 'Please use a different email than the one used at signup'});
return false;
}
- this.state.emailError = '';
- this.setState(this.state);
+ this.setState({emailError: ''});
return true;
}
render() {
diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/team_signup_send_invites_page.jsx
index 524bd5b50..8d8fb92ff 100644
--- a/web/react/components/team_signup_send_invites_page.jsx
+++ b/web/react/components/team_signup_send_invites_page.jsx
@@ -36,10 +36,10 @@ export default class TeamSignupSendInvitesPage extends React.Component {
var emails = [];
for (var i = 0; i < this.props.state.invites.length; i++) {
- if (!this.refs['email_' + i].validate(this.props.state.team.email)) {
- valid = false;
- } else {
+ if (this.refs['email_' + i].validate(this.props.state.team.email)) {
emails.push(this.refs['email_' + i].getValue());
+ } else {
+ valid = false;
}
}
diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/team_signup_url_page.jsx
index a3f89a217..a682bb49e 100644
--- a/web/react/components/team_signup_url_page.jsx
+++ b/web/react/components/team_signup_url_page.jsx
@@ -48,22 +48,20 @@ export default class TeamSignupUrlPage extends React.Component {
}
Client.findTeamByName(name,
- function success(data) {
- if (!data) {
- this.props.state.wizard = 'send_invites';
- this.props.state.team.type = 'O';
-
- this.props.state.team.name = name;
- this.props.updateParent(this.props.state);
- } else {
- this.state.nameError = 'This URL is unavailable. Please try another.';
- this.setState(this.state);
- }
- }.bind(this),
- function error(err) {
- this.state.nameError = err.message;
- this.setState(this.state);
- }.bind(this)
+ (data) => {
+ if (data) {
+ this.setState({nameError: 'This URL is unavailable. Please try another.'});
+ } else {
+ this.props.state.wizard = 'send_invites';
+ this.props.state.team.type = 'O';
+
+ this.props.state.team.name = name;
+ this.props.updateParent(this.props.state);
+ }
+ },
+ (err) => {
+ this.setState({nameError: err.message});
+ }
);
}
handleFocus(e) {
diff --git a/web/react/components/team_signup_welcome_page.jsx b/web/react/components/team_signup_welcome_page.jsx
index 626c6a17b..019456c9f 100644
--- a/web/react/components/team_signup_welcome_page.jsx
+++ b/web/react/components/team_signup_welcome_page.jsx
@@ -59,8 +59,7 @@ export default class TeamSignupWelcomePage extends React.Component {
}
}.bind(this),
function error(err) {
- this.state.serverError = err.message;
- this.setState(this.state);
+ this.setState({serverError: err.message});
}.bind(this)
);
}
diff --git a/web/react/components/team_signup_with_email.jsx b/web/react/components/team_signup_with_email.jsx
index 4fb1c0d01..f27def191 100644
--- a/web/react/components/team_signup_with_email.jsx
+++ b/web/react/components/team_signup_with_email.jsx
@@ -69,7 +69,7 @@ export default class EmailSignUpPage extends React.Component {
</button>
</div>
<div className='form-group margin--extra-2x'>
- <span><a href='/find_team'>{`Find my team`}</a></span>
+ <span><a href='/find_team'>{`Find my teams`}</a></span>
</div>
</form>
);
diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx
index 14f281f7a..5267f44b6 100644
--- a/web/react/components/team_signup_with_sso.jsx
+++ b/web/react/components/team_signup_with_sso.jsx
@@ -112,7 +112,7 @@ export default class SSOSignUpPage extends React.Component {
{serverError}
</div>
<div className='form-group margin--extra-2x'>
- <span><a href='/find_team'>{'Find my team'}</a></span>
+ <span><a href='/find_team'>{'Find my teams'}</a></span>
</div>
</form>
);
diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx
index 0f5cb59f5..3301c6596 100644
--- a/web/react/components/user_settings/import_theme_modal.jsx
+++ b/web/react/components/user_settings/import_theme_modal.jsx
@@ -49,7 +49,6 @@ export default class ImportThemeModal extends React.Component {
theme.sidebarText = colors[5];
theme.sidebarUnreadText = colors[5];
theme.sidebarTextHoverBg = colors[4];
- theme.sidebarTextHoverColor = colors[5];
theme.sidebarTextActiveBg = colors[2];
theme.sidebarTextActiveColor = colors[3];
theme.sidebarHeaderBg = colors[1];
@@ -150,7 +149,9 @@ export default class ImportThemeModal extends React.Component {
className='form-control'
onChange={this.handleChange}
/>
- {this.state.inputError}
+ <div className='input__help'>
+ {this.state.inputError}
+ </div>
</div>
</div>
</Modal.Body>
diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx
index c4a137ed8..be6cf1f42 100644
--- a/web/react/components/user_settings/user_settings_appearance.jsx
+++ b/web/react/components/user_settings/user_settings_appearance.jsx
@@ -214,14 +214,14 @@ export default class UserSettingsAppearance extends React.Component {
<div className='divider-dark first'/>
{themeUI}
<div className='divider-dark'/>
+ <br/>
+ <a
+ className='theme'
+ onClick={this.handleImportModal}
+ >
+ {'Import theme colors from Slack'}
+ </a>
</div>
- <br/>
- <a
- className='theme'
- onClick={this.handleImportModal}
- >
- {'Import theme colors from Slack'}
- </a>
</div>
);
}
diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx
index b59c08af0..4ff4775a7 100644
--- a/web/react/components/user_settings/user_settings_security.jsx
+++ b/web/react/components/user_settings/user_settings_security.jsx
@@ -251,17 +251,6 @@ export default class SecurityTab extends React.Component {
<div className='divider-dark first'/>
{passwordSection}
<div className='divider-dark'/>
- <ul
- className='section-min'
- >
- <li className='col-sm-10 section-title'>{'Version ' + global.window.config.Version}</li>
- <li className='col-sm-7 section-describe'>
- <div className='text-nowrap'>{'Build Number: ' + global.window.config.BuildNumber}</div>
- <div className='text-nowrap'>{'Build Date: ' + global.window.config.BuildDate}</div>
- <div className='text-nowrap'>{'Build Hash: ' + global.window.config.BuildHash}</div>
- </li>
- </ul>
- <div className='divider-dark'/>
<br></br>
<a
data-toggle='modal'
diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx
index a7fecb689..fe34034dc 100644
--- a/web/react/components/view_image.jsx
+++ b/web/react/components/view_image.jsx
@@ -350,7 +350,7 @@ export default class ViewImageModal extends React.Component {
totalFiles={this.props.filenames.length}
filename={name}
fileURL={fileUrl}
- onGetPublicLinkPressed={this.getPublicLink}
+ getPublicLink={this.getPublicLink}
/>
</div>
{leftArrow}
diff --git a/web/react/components/view_image_popover_bar.jsx b/web/react/components/view_image_popover_bar.jsx
index 68817d751..132212afb 100644
--- a/web/react/components/view_image_popover_bar.jsx
+++ b/web/react/components/view_image_popover_bar.jsx
@@ -14,7 +14,7 @@ export default class ViewImagePopoverBar extends React.Component {
href='#'
className='public-link text'
data-title='Public Image'
- onClick={this.getPublicLink}
+ onClick={this.props.getPublicLink}
>
{'Get Public Link'}
</a>
@@ -62,5 +62,5 @@ ViewImagePopoverBar.propTypes = {
totalFiles: React.PropTypes.number.isRequired,
filename: React.PropTypes.string.isRequired,
fileURL: React.PropTypes.string.isRequired,
- onGetPublicLinkPressed: React.PropTypes.func.isRequired
+ getPublicLink: React.PropTypes.func.isRequired
};
diff --git a/web/react/package.json b/web/react/package.json
index a9eba6c6c..e6a662375 100644
--- a/web/react/package.json
+++ b/web/react/package.json
@@ -5,27 +5,25 @@
"dependencies": {
"autolinker": "0.18.1",
"babel-runtime": "5.8.24",
- "bootstrap-colorpicker": "2.2.0",
"flux": "2.1.1",
"keymirror": "0.1.1",
"marked": "0.3.5",
"object-assign": "3.0.0",
- "react-zeroclipboard-mixin": "0.1.0",
"twemoji": "1.4.1"
},
"devDependencies": {
- "browserify": "11.0.1",
- "envify": "3.4.0",
- "babelify": "6.1.3",
+ "browserify": "11.2.0",
+ "babelify": "6.3.0",
"uglify-js": "2.4.24",
- "watchify": "3.3.1",
- "eslint": "1.3.1",
- "eslint-plugin-react": "3.3.1"
+ "watchify": "3.4.0",
+ "eslint": "1.6.0",
+ "eslint-plugin-react": "3.5.1"
},
"scripts": {
- "start": "watchify --extension=jsx -o ../static/js/bundle.js -v -d ./**/*.jsx",
- "build": "NODE_ENV=production browserify ./**/*.jsx | uglifyjs -c -m --screw-ie8 > ../static/js/bundle.min.js",
- "test": "jest"
+ "check": "",
+ "build-libs": "browserify -r crypto -r autolinker -r flux -r keymirror -r marked -r object-assign -r twemoji | uglifyjs -c -m --screw-ie8 > ../static/js/libs.min.js",
+ "start": "watchify --fast -x crypto -x node -x autolinker -x flux -x keymirror -x marked -x object-assign -x twemoji -o ../static/js/bundle.js -v -d ./**/*.jsx",
+ "build": "browserify -x crypto -x autolinker -x flux -x keymirror -x marked -x object-assign -x twemoji ./**/*.jsx | uglifyjs -c -m --screw-ie8 > ../static/js/bundle.min.js"
},
"browserify": {
"transform": [
@@ -36,11 +34,7 @@
"runtime"
]
}
- ],
- "envify"
+ ]
]
- },
- "jest": {
- "rootDir": "."
}
}
diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx
index d2dedb271..27a74fb2b 100644
--- a/web/react/stores/browser_store.jsx
+++ b/web/react/stores/browser_store.jsx
@@ -4,7 +4,7 @@
var UserStore;
function getPrefix() {
if (!UserStore) {
- UserStore = require('./user_store.jsx');
+ UserStore = require('./user_store.jsx'); //eslint-disable-line global-require
}
return UserStore.getCurrentId() + '_';
}
@@ -41,7 +41,13 @@ class BrowserStoreClass {
}
setGlobalItem(name, value) {
- localStorage.setItem(name, JSON.stringify(value));
+ try {
+ localStorage.setItem(name, JSON.stringify(value));
+ } catch (err) {
+ console.log('An error occurred while setting local storage, clearing all props'); //eslint-disable-line no-console
+ localStorage.clear();
+ window.location.href = window.location.href;
+ }
}
getGlobalItem(name, defaultValue) {
diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx
index 1d853f979..9f354965e 100644
--- a/web/react/stores/socket_store.jsx
+++ b/web/react/stores/socket_store.jsx
@@ -50,8 +50,10 @@ class SocketStoreClass extends EventEmitter {
}
this.failCount = 0;
- ErrorStore.storeLastError(null);
- ErrorStore.emitChange();
+ if (ErrorStore.getLastError()) {
+ ErrorStore.storeLastError(null);
+ ErrorStore.emitChange();
+ }
};
conn.onclose = () => {
diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx
index 1f33fe03b..fd9117747 100644
--- a/web/react/stores/team_store.jsx
+++ b/web/react/stores/team_store.jsx
@@ -10,12 +10,12 @@ var BrowserStore = require('../stores/browser_store.jsx');
var CHANGE_EVENT = 'change';
-var utils;
+var Utils;
function getWindowLocationOrigin() {
- if (!utils) {
- utils = require('../utils/utils.jsx');
+ if (!Utils) {
+ Utils = require('../utils/utils.jsx'); //eslint-disable-line global-require
}
- return utils.getWindowLocationOrigin();
+ return Utils.getWindowLocationOrigin();
}
class TeamStoreClass extends EventEmitter {
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 5cb165b4c..6dccfcdeb 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -2,6 +2,7 @@
var BrowserStore = require('../stores/browser_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
+var ErrorStore = require('../stores/error_store.jsx');
export function track(category, action, label, prop, val) {
global.window.analytics.track(action, {category: category, label: label, property: prop, value: val});
@@ -15,7 +16,7 @@ function handleError(methodName, xhr, status, err) {
var e = null;
try {
e = JSON.parse(xhr.responseText);
- } catch(parseError) {
+ } catch (parseError) {
e = null;
}
@@ -27,7 +28,16 @@ function handleError(methodName, xhr, status, err) {
msg = 'error in ' + methodName + ' status=' + status + ' statusCode=' + xhr.status + ' err=' + err;
if (xhr.status === 0) {
- e = {message: 'There appears to be a problem with your internet connection', connErrorCount: 1};
+ let errorCount = 1;
+ const oldError = ErrorStore.getLastError();
+ let connectError = 'There appears to be a problem with your internet connection';
+
+ if (oldError && oldError.connErrorCount) {
+ errorCount += oldError.connErrorCount;
+ connectError = 'We cannot reach the Mattermost service. The service may be down or misconfigured. Please contact an administrator to make sure the WebSocket port is configured properly.';
+ }
+
+ e = {message: connectError, connErrorCount: errorCount};
} else {
e = {message: 'We received an unexpected status code from the server (' + xhr.status + ')'};
}
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 67414dc3b..8fd0ab79b 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -122,10 +122,9 @@ module.exports = {
default: {
type: 'Mattermost',
sidebarBg: '#fafafa',
- sidebarText: '#999999',
+ sidebarText: '#333333',
sidebarUnreadText: '#333333',
sidebarTextHoverBg: '#e6f2fa',
- sidebarTextHoverColor: '#999999',
sidebarTextActiveBg: '#e1e1e1',
sidebarTextActiveColor: '#111111',
sidebarHeaderBg: '#2389d7',
@@ -138,15 +137,16 @@ module.exports = {
newMessageSeparator: '#FF8800',
linkColor: '#2389d7',
buttonBg: '#2389d7',
- buttonColor: '#FFFFFF'
+ buttonColor: '#FFFFFF',
+ mentionHighlightBg: '#fff2bb',
+ mentionHighlightLink: '#2f81b7'
},
organization: {
type: 'Organization',
sidebarBg: '#2071a7',
- sidebarText: '#bfcde8',
+ sidebarText: '#fff',
sidebarUnreadText: '#fff',
sidebarTextHoverBg: '#136197',
- sidebarTextHoverColor: '#bfcde8',
sidebarTextActiveBg: '#136197',
sidebarTextActiveColor: '#FFFFFF',
sidebarHeaderBg: '#2f81b7',
@@ -159,15 +159,16 @@ module.exports = {
newMessageSeparator: '#FF8800',
linkColor: '#2f81b7',
buttonBg: '#1dacfc',
- buttonColor: '#FFFFFF'
+ buttonColor: '#FFFFFF',
+ mentionHighlightBg: '#fff2bb',
+ mentionHighlightLink: '#2f81b7'
},
mattermostDark: {
type: 'Mattermost Dark',
sidebarBg: '#1B2C3E',
- sidebarText: '#bbbbbb',
+ sidebarText: '#fff',
sidebarUnreadText: '#fff',
sidebarTextHoverBg: '#4A5664',
- sidebarTextHoverColor: '#bbbbbb',
sidebarTextActiveBg: '#39769C',
sidebarTextActiveColor: '#FFFFFF',
sidebarHeaderBg: '#1B2C3E',
@@ -180,15 +181,16 @@ module.exports = {
newMessageSeparator: '#5de5da',
linkColor: '#A4FFEB',
buttonBg: '#4CBBA4',
- buttonColor: '#FFFFFF'
+ buttonColor: '#FFFFFF',
+ mentionHighlightBg: '#984063',
+ mentionHighlightLink: '#A4FFEB'
},
windows10: {
type: 'Windows Dark',
sidebarBg: '#171717',
- sidebarText: '#eee',
+ sidebarText: '#fff',
sidebarUnreadText: '#fff',
sidebarTextHoverBg: '#302e30',
- sidebarTextHoverColor: '#fff',
sidebarTextActiveBg: '#484748',
sidebarTextActiveColor: '#FFFFFF',
sidebarHeaderBg: '#1f1f1f',
@@ -201,7 +203,9 @@ module.exports = {
newMessageSeparator: '#CC992D',
linkColor: '#0177e7',
buttonBg: '#0177e7',
- buttonColor: '#FFFFFF'
+ buttonColor: '#FFFFFF',
+ mentionHighlightBg: '#784098',
+ mentionHighlightLink: '#A4FFEB'
}
},
THEME_ELEMENTS: [
@@ -230,10 +234,6 @@ module.exports = {
uiName: 'Sidebar Text Hover BG'
},
{
- id: 'sidebarTextHoverColor',
- uiName: 'Sidebar Text Hover Color'
- },
- {
id: 'sidebarTextActiveBg',
uiName: 'Sidebar Text Active BG'
},
@@ -263,7 +263,7 @@ module.exports = {
},
{
id: 'newMessageSeparator',
- uiName: 'New message separator'
+ uiName: 'New Message Separator'
},
{
id: 'linkColor',
@@ -273,10 +273,17 @@ module.exports = {
id: 'buttonBg',
uiName: 'Button BG'
},
-
{
id: 'buttonColor',
uiName: 'Button Text'
+ },
+ {
+ id: 'mentionHighlightBg',
+ uiName: 'Mention Highlight BG'
+ },
+ {
+ id: 'mentionHighlightLink',
+ uiName: 'Mention Highlight Link'
}
]
};
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 1bc082175..f79f3492f 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -231,10 +231,10 @@ function testUrlMatch(text) {
var matchText = match.getMatchedText();
linkData.text = matchText;
- if (matchText.trim().indexOf('http') !== 0) {
- linkData.link = 'http://' + matchText;
- } else {
+ if (matchText.trim().indexOf('http') === 0) {
linkData.link = matchText;
+ } else {
+ linkData.link = 'http://' + matchText;
}
result.push(linkData);
@@ -399,9 +399,9 @@ export function applyTheme(theme) {
}
if (theme.sidebarText) {
- changeCss('.sidebar--left .nav-pills__container li>a, .sidebar--right, .settings-modal .nav-pills>li a, .sidebar--menu', 'color:' + theme.sidebarText, 1);
+ changeCss('.sidebar--left .nav-pills__container li>a, .sidebar--right, .settings-modal .nav-pills>li a, .sidebar--menu', 'color:' + changeOpacity(theme.sidebarText, 0.6), 1);
changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li>a', 'color:' + theme.sidebarText, 1);
- changeCss('.sidebar--left .nav-pills__container li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.8), 1);
+ changeCss('.sidebar--left .nav-pills__container li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.6), 1);
changeCss('.sidebar--left .add-channel-btn:hover, .sidebar--left .add-channel-btn:focus', 'color:' + theme.sidebarText, 1);
changeCss('.sidebar--left, .sidebar--right .sidebar--right__header', 'border-color:' + changeOpacity(theme.sidebarText, 0.2), 1);
changeCss('.sidebar--left .status path', 'fill:' + changeOpacity(theme.sidebarText, 0.5), 1);
@@ -417,17 +417,13 @@ export function applyTheme(theme) {
changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li:hover a', 'background:' + theme.sidebarTextHoverBg, 1);
}
- if (theme.sidebarTextHoverColor) {
- changeCss('.sidebar--left .nav-pills__container li>a:hover, .sidebar--left .nav-pills__container li>a:focus, .settings-modal .nav-pills>li:hover a, .settings-modal .nav-pills>li:focus a', 'color:' + theme.sidebarTextHoverColor, 2);
- changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li:hover a', 'color:' + theme.sidebarTextHoverColor, 2);
- }
-
if (theme.sidebarTextActiveBg) {
changeCss('.sidebar--left .nav-pills__container li.active a, .sidebar--left .nav-pills__container li.active a:hover, .sidebar--left .nav-pills__container li.active a:focus, .settings-modal .nav-pills>li.active a, .settings-modal .nav-pills>li.active a:hover, .settings-modal .nav-pills>li.active a:active', 'background:' + theme.sidebarTextActiveBg, 1);
}
if (theme.sidebarTextActiveColor) {
changeCss('.sidebar--left .nav-pills__container li.active a, .sidebar--left .nav-pills__container li.active a:hover, .sidebar--left .nav-pills__container li.active a:focus, .settings-modal .nav-pills>li.active a, .settings-modal .nav-pills>li.active a:hover, .settings-modal .nav-pills>li.active a:active', 'color:' + theme.sidebarTextActiveColor, 2);
+ changeCss('.sidebar--left .nav-pills__container li.active a .status .online--icon', 'fill:' + theme.sidebarTextActiveColor, 2);
}
if (theme.sidebarHeaderBg) {
@@ -468,10 +464,11 @@ export function applyTheme(theme) {
changeCss('.date-separator .separator__text, .new-separator .separator__text', 'background:' + theme.centerChannelBg, 1);
changeCss('.post-image__column .post-image__details', 'background:' + theme.centerChannelBg, 1);
changeCss('.sidebar--right, .dropdown-menu, .popover', 'background:' + theme.centerChannelBg, 1);
+ changeCss('.search-bar__container .search__form .search-bar, .form-control', 'background:' + theme.centerChannelBg, 1);
}
if (theme.centerChannelColor) {
- changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .loading-screen .loading__content .round, .command-name, .modal .modal-content, .dropdown-menu, .popover, .mentions-name', 'color:' + theme.centerChannelColor, 1);
+ changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .command-name, .modal .modal-content, .dropdown-menu, .popover, .mentions-name', 'color:' + theme.centerChannelColor, 1);
changeCss('#post-create', 'color:' + theme.centerChannelColor, 2);
changeCss('.mentions--top, .command-box', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 3);
changeCss('.mentions--top, .command-box', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 2);
@@ -479,7 +476,7 @@ export function applyTheme(theme) {
changeCss('.dropdown-menu, .popover ', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px', 3);
changeCss('.dropdown-menu, .popover ', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px', 2);
changeCss('.dropdown-menu, .popover ', '-moz-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px', 1);
- changeCss('.post-body hr', 'background:' + theme.centerChannelColor, 1);
+ changeCss('.post-body hr, .loading-screen .loading__content .round', 'background:' + theme.centerChannelColor, 1);
changeCss('.channel-header .heading', 'color:' + theme.centerChannelColor, 1);
changeCss('.markdown__table tbody tr:nth-child(2n)', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.channel-header__info>div.dropdown .header-dropdown__icon', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1);
@@ -491,20 +488,21 @@ export function applyTheme(theme) {
changeCss('.post-image__column', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 2);
changeCss('.post-image__column .post-image__details', 'color:' + theme.centerChannelColor, 2);
changeCss('.post-image__column a, .post-image__column a:hover, .post-image__column a:focus', 'color:' + theme.centerChannelColor, 1);
- changeCss('.search-bar__container .search__form .search-bar', 'background: transparent; color:' + theme.centerChannelColor, 1);
+ changeCss('.search-bar__container .search__form .search-bar, .form-control', 'color:' + theme.centerChannelColor, 2);
changeCss('@media(max-width: 768px){.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: inherit;', 1);
- changeCss('.search-bar__container .search__form', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
+ changeCss('.input-group-addon, .search-bar__container .search__form, .form-control', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
+ changeCss('.form-control:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
changeCss('.channel-intro .channel-intro__content', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1);
changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2);
changeCss('.date-separator .separator__hr, .modal-footer, .modal .custom-textarea, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.modal .custom-textarea:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
changeCss('.channel-intro, .settings-modal .settings-table .settings-content .divider-dark, hr, .settings-modal .settings-table .settings-links', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
- changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment, .post.post--comment.other--root .post-comment, .post.same--root .post-body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
+ changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment, .post.post--comment.other--root .post-comment, .post.same--root .post-body, .modal .more-channel-table tbody>tr td, .member-div:first-child, .member-div, .access-history__table .access__report, .activity-log__table', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.1), 2);
changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
changeCss('@media(max-width: 1800px){.inner__wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
- changeCss('.post:hover, .sidebar--right .sidebar--right__header, .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
+ changeCss('.post:hover, .modal .more-channel-table tbody>tr:hover td, .sidebar--right .sidebar--right__header, .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.date-separator.hovered--before:after, .date-separator.hovered--after:before, .new-separator.hovered--after:before, .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.command-name:hover, .mentions-name:hover, .mentions-focus, .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1);
changeCss('.post.current--user:hover .post-body ', 'background: none;', 1);
@@ -529,6 +527,14 @@ export function applyTheme(theme) {
if (theme.buttonColor) {
changeCss('.btn.btn-primary', 'color:' + theme.buttonColor, 2);
}
+
+ if (theme.mentionHighlightBg) {
+ changeCss('.mention-highlight, .search-highlight', 'background:' + theme.mentionHighlightBg, 1);
+ }
+
+ if (theme.mentionHighlightLink) {
+ changeCss('.mention-highlight .mention-link', 'color:' + theme.mentionHighlightLink, 1);
+ }
}
export function changeCss(className, classValue, classRepeat) {
// we need invisible container to store additional css definitions
@@ -640,7 +646,7 @@ export function isValidUsername(name) {
error = 'Must be between 3 and 15 characters';
} else if (!(/^[a-z0-9\.\-\_]+$/).test(name)) {
error = "Must contain only letters, numbers, and the symbols '.', '-', and '_'.";
- } else if (!(/[a-z]/).test(name.charAt(0))) {
+ } else if (!(/[a-z]/).test(name.charAt(0))) { //eslint-disable-line no-negated-condition
error = 'First character must be a letter.';
} else {
for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++) {
diff --git a/web/sass-files/sass/partials/_access-history.scss b/web/sass-files/sass/partials/_access-history.scss
index 412a2a1d0..a3289ecc0 100644
--- a/web/sass-files/sass/partials/_access-history.scss
+++ b/web/sass-files/sass/partials/_access-history.scss
@@ -24,6 +24,6 @@
font-size: 15px;
}
.report__info {
- color: #999;
+ @include opacity(0.8);
}
} \ No newline at end of file
diff --git a/web/sass-files/sass/partials/_activity-log.scss b/web/sass-files/sass/partials/_activity-log.scss
index 3f0c3090d..2fb37a3bb 100644
--- a/web/sass-files/sass/partials/_activity-log.scss
+++ b/web/sass-files/sass/partials/_activity-log.scss
@@ -1,3 +1,20 @@
+@keyframes highlight {
+ from { background: rgba(yellow, 0.5);}
+ to { background: none;}
+}
+
+.animation--highlight {
+ &:before {
+ content: '';
+ animation: highlight 1.5s ease;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ }
+}
+
.activity-log__table {
display: table;
width: 100%;
@@ -26,7 +43,7 @@
}
}
.report__info {
- color: #999;
+ @include opacity(0.8);
}
}
.session-help-text {
diff --git a/web/sass-files/sass/partials/_admin-console.scss b/web/sass-files/sass/partials/_admin-console.scss
index 5037da415..09907da6d 100644
--- a/web/sass-files/sass/partials/_admin-console.scss
+++ b/web/sass-files/sass/partials/_admin-console.scss
@@ -1,193 +1,202 @@
-
-.sidebar--left { &.sidebar--collapsable {
- background: #333;
- .team__header {
- background: transparent;
- margin-bottom: 5px;
- }
- .nav {
- li {
- padding: 0;
- .icon {
- width: 17px;
- }
- > a {
- color: #fff;
- padding: 9px 15px;
- display: block;
- &:hover, &.active, &:focus {
- background-color: $primary-color;
- }
- }
- > h4 {
- background: #333;
- padding: 10px 10px;
- margin: 1px 0 0;
- }
- }
- .menu-icon--right {
- vertical-align: top;
- padding: 5px 10px;
- margin: -5px;
- float: right;
- .fa {
- font-size: 13px;
- right: -2px;
- position: relative;
- color: #fff;
- }
+#admin_controller {
+ > div {
+ height: 100%;
+ }
+ .table {
+ background: #fff;
+ }
+ .sidebar--left {
+ &.sidebar--collapsable {
+ background: #333;
+ .team__header {
+ background: transparent;
+ margin-bottom: 5px;
}
- &.nav__sub-menu {
- background: #111;
- -webkit-font-smoothing: auto;
- &.padded {
- padding: 5px 0;
- }
+ .nav {
li {
+ padding: 0;
+ .icon {
+ width: 17px;
+ }
> a {
- font-size: 13px;
- padding: 5px 15px;
- background: transparent;
- color: #bbb;
- &:hover {
- color: lighten($primary-color, 10);
- }
- &.active {
- color: #fff;
- font-weight: 600;
+ padding: 9px 15px;
+ display: block;
+ &:hover, &.active, &:focus {
+ background-color: #EAEAEA;
}
}
- .nav-more {
+ > h4 {
+ background: #333;
+ padding: 10px 10px;
+ margin: 1px 0 0;
+ }
+ }
+ .menu-icon--right {
+ vertical-align: top;
+ padding: 5px 10px;
+ margin: -5px;
+ float: right;
+ .fa {
font-size: 13px;
- padding: 5px 15px;
- background: transparent;
- color: #bbb;
- display: block;
- cursor: pointer;
- &:hover {
- color: lighten($primary-color, 10);
+ right: -2px;
+ position: relative;
+ color: #fff;
+ }
+ }
+ &.nav__sub-menu {
+ background: #111;
+ -webkit-font-smoothing: auto;
+ &.padded {
+ padding: 5px 0;
+ }
+ li {
+ > a {
+ font-size: 13px;
+ padding: 5px 15px;
+ background: transparent;
+ color: #bbb;
+ &:hover {
+ color: lighten($primary-color, 10);
+ }
+ &.active {
+ color: #fff;
+ font-weight: 600;
+ }
+ }
+ .nav-more {
+ font-size: 13px;
+ padding: 5px 15px;
+ background: transparent;
+ color: #bbb;
+ display: block;
+ cursor: pointer;
+ &:hover {
+ color: lighten($primary-color, 10);
+ }
}
}
}
- }
- &.nav__inner-menu {
- li {
- > a {
- padding-left: 20px;
+ &.nav__inner-menu {
+ li {
+ > a {
+ padding-left: 20px;
+ }
}
}
}
}
}
-}
-.log__panel {
- overflow: scroll;
- width: 100%;
- height: 800px;
- border: 1px solid #ddd;
- margin-top: 10px;
- padding: 5px;
- background-color: white;
-}
-
-.app__content {
- &.admin {
- overflow: auto;
- background-color: #f1f1f1;
- padding: 0 20px 20px;
- min-height: 600px;
- }
- .wrapper--fixed {
- max-width: 800px;
+ .log__panel {
+ overflow: scroll;
+ width: 100%;
+ height: 800px;
+ border: 1px solid #ddd;
+ margin-top: 10px;
+ padding: 5px;
+ background-color: white;
}
- .form-horizontal {
- margin-top: 40px;
- .control-label {
- text-align: left;
- padding-right: 0;
- font-weight: 600;
+
+ .app__content {
+ &.admin {
+ overflow: auto;
+ background-color: #f1f1f1;
+ padding: 0 20px 20px;
+ min-height: 600px;
}
- .form-group {
- margin-bottom: 25px;
+ .wrapper--fixed {
+ max-width: 800px;
}
- .help-text {
- margin: 10px 0 0 15px;
- color: #777;
- .help-link {
- margin-right: 5px;
+ .form-horizontal {
+ margin-top: 40px;
+ .control-label {
+ text-align: left;
+ padding-right: 0;
+ font-weight: 600;
}
- .btn {
- font-size: 13px;
+ .form-group {
+ margin-bottom: 25px;
+ }
+ .help-text {
+ margin: 10px 0 0 15px;
+ color: #777;
+ .help-link {
+ margin-right: 5px;
+ }
+ .btn {
+ font-size: 13px;
+ }
+ }
+ .alert {
+ display: inline-block;
+ padding: 5px 7px;
+ margin: 1em 0 0;
+ top: 1px;
+ position: relative;
+ .fa {
+ margin-right: 5px;
+ }
}
}
- .alert {
- display: inline-block;
- padding: 5px 7px;
- margin: 0;
- top: 1px;
- position: relative;
- }
- }
- .banner {
- background: #fff;
- border: 1px solid #ddd;
- padding: 0.7em 1.5em;
- font-size: 0.95em;
- margin: 2em 0;
- .banner__heading {
- font-size: 1.5em;
+ .banner {
+ background: #fff;
+ border: 1px solid #ddd;
+ padding: 0.7em 1.5em;
+ font-size: 0.95em;
+ margin: 2em 0;
+ .banner__heading {
+ font-size: 1.5em;
+ }
+ .banner__content {
+ width: 80%;
+ }
}
- .banner__content {
- width: 80%;
+ .popover {
+ border-radius: 3px;
+ width: 100%;
+ font-size: 0.95em;
}
- }
- .popover {
- border-radius: 3px;
- width: 100%;
- font-size: 0.95em;
- }
- .panel {
- border: none;
- background-color: transparent;
- }
- .panel-default {
- > .panel-heading {
- padding: 10px 0;
+ .panel {
+ border: none;
background-color: transparent;
}
- .panel-body {
- padding: 30px 0 10px;
+ .panel-default {
+ > .panel-heading {
+ padding: 10px 0;
+ background-color: transparent;
+ }
+ .panel-body {
+ padding: 30px 0 10px;
+ }
+ }
+ .panel-group {
+ margin-bottom: 50px;
}
- }
- .panel-group {
- margin-bottom: 50px;
- }
- .panel-title {
- font-size: 24px;
- line-height: 1.5;
- a {
- text-decoration: none;
- display: block;
- @include clearfix;
- &.collapsed {
- .fa-minus {
- display: none;
+ .panel-title {
+ font-size: 24px;
+ line-height: 1.5;
+ a {
+ text-decoration: none;
+ display: block;
+ @include clearfix;
+ &.collapsed {
+ .fa-minus {
+ display: none;
+ }
+ .fa-plus {
+ display: inline-block;
+ }
+ }
+ .fa {
+ font-size: 18px;
+ float: right;
+ margin-top: 8px;
+ color: #aaa;
}
.fa-plus {
- display: inline-block;
+ display: none;
}
}
- .fa {
- font-size: 18px;
- float: right;
- margin-top: 8px;
- color: #aaa;
- }
- .fa-plus {
- display: none;
- }
}
}
-
} \ No newline at end of file
diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss
index fa465ff91..18462d92a 100644
--- a/web/sass-files/sass/partials/_base.scss
+++ b/web/sass-files/sass/partials/_base.scss
@@ -34,16 +34,19 @@ body {
}
}
+.input-group-addon {
+ background: transparent;
+}
+
.popover {
color: #333;
- a {
- color: $primary-color;
- &:hover, &:focus {
- color: $primary-color;
+ &.bottom {
+ >.arrow:after {
+ border-bottom-color: rgba(white, 0.5);
}
}
.popover-title {
- background: rgba(black, 0.1);
+ background: rgba(black, 0.05);
}
}
@@ -60,7 +63,7 @@ body {
}
.word-break--all {
- word-break: break-all;
+ word-break: break-all;
}
a {
@@ -98,6 +101,9 @@ a:focus, a:hover {
.form-control {
@include border-radius(2px);
+ &:focus {
+ @include box-shadow(none);
+ }
&.no-resize {
resize: none;
}
@@ -121,6 +127,10 @@ a:focus, a:hover {
z-index: 100;
}
+.nav>li>a:focus, .nav>li>a:hover {
+ background: transparent;
+}
+
.btn {
@include single-transition(all, 0.25s, ease-in);
@include border-radius(1px);
@@ -165,9 +175,9 @@ a:focus, a:hover {
}
.emoji {
- width: 1.5em;
- height: 1.5em;
- display: inline-block;
- margin-bottom: 0.25em;
- background-size: contain;
+ width: 1.5em;
+ height: 1.5em;
+ display: inline-block;
+ margin-bottom: 0.25em;
+ background-size: contain;
}
diff --git a/web/sass-files/sass/partials/_forms.scss b/web/sass-files/sass/partials/_forms.scss
index 65ea161d4..6c1f7cc6c 100644
--- a/web/sass-files/sass/partials/_forms.scss
+++ b/web/sass-files/sass/partials/_forms.scss
@@ -16,11 +16,12 @@
}
}
.input__help {
- color: #777;
+ color: inherit;
margin: 10px 0 0 10px;
word-break: break-word;
+ @include opacity(0.8);
&.dark {
- color: #222;
+ @include opacity(1);
}
&.error {
color: #a94442;
diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss
index 9b9e5f573..8e353aff9 100644
--- a/web/sass-files/sass/partials/_headers.scss
+++ b/web/sass-files/sass/partials/_headers.scss
@@ -37,7 +37,6 @@
&.description {
overflow: hidden;
text-overflow: ellipsis;
- color: #888;
margin-top: 2px;
max-height: 45px;
}
diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss
index aff31e418..f59cefbc6 100644
--- a/web/sass-files/sass/partials/_mentions.scss
+++ b/web/sass-files/sass/partials/_mentions.scss
@@ -55,11 +55,6 @@
padding-left: 10px;
}
-.mention-link {
- color: $primary-color;
-}
-
.mention-highlight {
background-color:#fff2bb;
- color: #333;
} \ 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 96b26f251..2722333a4 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -144,11 +144,10 @@
font-size: 0.9em;
overflow: hidden;
text-overflow: ellipsis;
- color: #999;
+ @include opacity(0.8);
margin: 5px 0;
}
.more-channel-name {
- color: #444;
font-weight: 600;
font-size: 0.95em;
}
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index 8bf4b0534..ccd7fd425 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -16,7 +16,7 @@
}
.bad-connection {
- background-color: rgb(255, 255, 172);
+ background-color: rgb(255, 255, 172) !important;
}
.textarea-div {
@@ -257,7 +257,7 @@ body.ios {
line-height: 18px;
display: inline-block;
font-size: 13px;
- @include opacity(0.6);
+ @include opacity(0.7);
}
}
}
diff --git a/web/sass-files/sass/partials/_post_right.scss b/web/sass-files/sass/partials/_post_right.scss
index da5bcbad2..e4860b286 100644
--- a/web/sass-files/sass/partials/_post_right.scss
+++ b/web/sass-files/sass/partials/_post_right.scss
@@ -29,7 +29,7 @@
min-height: 100px;
}
.msg-typing {
- color: #555;
+ @include opacity(0.7);
float: left;
padding-top: 17px;
}
diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss
index bcb8b5eac..2de1b5380 100644
--- a/web/sass-files/sass/partials/_search.scss
+++ b/web/sass-files/sass/partials/_search.scss
@@ -104,7 +104,6 @@
padding: 10px;
}
-.search-highlight.theme, .search-highlight {
+.search-highlight {
background-color: #FFF2BB;
- color: #333;
}
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index aef7e83f9..8debb0b4e 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -81,6 +81,7 @@
.appearance-section {
.premade-themes {
+ margin-bottom: 10px;
.theme-label {
font-weight: 400;
margin-top: 5px;
@@ -240,10 +241,6 @@
margin-right:5px;
}
-.member-list-holder {
- background-color:#fff;
-}
-
.member-div {
border-bottom:1px solid lightgrey;
position:relative;
diff --git a/web/sass-files/sass/partials/_signup.scss b/web/sass-files/sass/partials/_signup.scss
index 924f0718a..fcf0d5d77 100644
--- a/web/sass-files/sass/partials/_signup.scss
+++ b/web/sass-files/sass/partials/_signup.scss
@@ -12,6 +12,9 @@
&.padding--less {
padding-top: 50px;
}
+ .form-control:focus {
+ @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6));
+ }
h1, h2, h3, h4, h5, h6, p {
line-height: 1.3;
@@ -330,3 +333,8 @@
.authorize-btn {
margin-right: 6px;
}
+
+.verify_panel {
+ margin: 60px auto auto auto;
+ max-width: 380px;
+}
diff --git a/web/static/images/Battlehouse-logodark.png b/web/static/images/Battlehouse-logodark.png
deleted file mode 100644
index 1fc5b68ca..000000000
--- a/web/static/images/Battlehouse-logodark.png
+++ /dev/null
Binary files differ
diff --git a/web/static/images/Mattermost-logodark.png b/web/static/images/Mattermost-logodark.png
deleted file mode 100644
index c16978ba8..000000000
--- a/web/static/images/Mattermost-logodark.png
+++ /dev/null
Binary files differ
diff --git a/web/static/images/Bladekick-logodark.png b/web/static/images/logo-email.png
index c16978ba8..c16978ba8 100644
--- a/web/static/images/Bladekick-logodark.png
+++ b/web/static/images/logo-email.png
Binary files differ
diff --git a/web/templates/admin_console.html b/web/templates/admin_console.html
index 1444d9b17..a046478f6 100644
--- a/web/templates/admin_console.html
+++ b/web/templates/admin_console.html
@@ -5,15 +5,15 @@
{{template "head" . }}
<body>
-<div id="error_bar"></div>
+<div id='error_bar'></div>
-<div id="admin_controller"></div>
+<div id='admin_controller' class='container-fluid'></div>
-<div id="select_team_modal"></div>
+<div id='select_team_modal'></div>
<script>
window.setup_admin_console_page();
-
+
$(document).ready(function(){
$('[data-toggle="tooltip"]').tooltip();
$('[data-toggle="popover"]').popover();
diff --git a/web/templates/head.html b/web/templates/head.html
index faac4975a..8039f48a1 100644
--- a/web/templates/head.html
+++ b/web/templates/head.html
@@ -1,6 +1,6 @@
{{define "head"}}
<head>
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="robots" content="noindex, nofollow">
<title>{{ .Props.Title }}</title>
@@ -22,24 +22,22 @@
window.config = {{ .ClientProps }};
</script>
-
+ <!-- CSS Should always go first -->
<link rel="stylesheet" href="/static/css/bootstrap-3.3.5.min.css">
- <link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css" rel="stylesheet">
- <link rel="stylesheet" href="/static/css/bootstrap-colorpicker.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css">
+ <link rel="stylesheet" href="/static/css/bootstrap-colorpicker.min.css">
+ <link rel="stylesheet" href="/static/css/styles.css">
+ <link rel="stylesheet" href="/static/css/google-fonts.css">
+
+ <link id="favicon" rel="icon" href="/static/images/favicon.ico" type="image/x-icon">
+ <link rel="shortcut icon" href="/static/images/favicon.ico" type="image/x-icon">
<script src="/static/js/react-with-addons-0.13.3.js"></script>
<script src="/static/js/jquery-1.11.1.js"></script>
<script src="/static/js/bootstrap-3.3.5.js"></script>
<script src="/static/js/bootstrap-colorpicker.min.js"></script>
<script src="/static/js/react-bootstrap-0.25.1.js"></script>
-
- <link id="favicon" rel="icon" href="/static/images/favicon.ico" type="image/x-icon">
- <link rel="shortcut icon" href="/static/images/favicon.ico" type="image/x-icon">
- <link href='/static/css/google-fonts.css' rel='stylesheet' type='text/css'>
- <link rel="stylesheet" href="/static/css/styles.css">
-
<script src="/static/js/perfect-scrollbar-0.6.5.jquery.js"></script>
-
<script src="/static/js/jquery-dragster/jquery.dragster.js"></script>
<style id="antiClickjack">body{display:none !important;}</style>
@@ -60,6 +58,7 @@
}
</script>
+ <script src="/static/js/libs.min.js"></script>
<script src="/static/js/bundle.js"></script>
<script type="text/javascript">
diff --git a/web/templates/verify.html b/web/templates/verify.html
index cb4832512..a49ba7930 100644
--- a/web/templates/verify.html
+++ b/web/templates/verify.html
@@ -1,16 +1,21 @@
{{define "verify"}}
<!DOCTYPE html>
<html>
-{{template "head" . }}
-<body>
- <div class="container-fluid">
- <div class="row">
- <div id="verify"></div>
+ {{template "head" . }}
+ <body class="white">
+ <div class="container-fluid">
+ <div class="inner__wrap">
+ <div class="row content">
+ <div id="verify"></div>
+ </div>
+ <div class="row footer">
+ {{template "footer" . }}
+ </div>
+ </div>
</div>
- </div>
- <script>
- window.setupVerifyPage({{ .Props }});
- </script>
-</body>
+ <script>
+ window.setupVerifyPage({{ .Props }});
+ </script>
+ </body>
</html>
{{end}}
diff --git a/web/web.go b/web/web.go
index 4ca60f61a..87c96659d 100644
--- a/web/web.go
+++ b/web/web.go
@@ -427,24 +427,17 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
}
- var isVerified string
- if len(userId) != 26 {
- isVerified = "false"
- } else if len(hashedId) == 0 {
- isVerified = "false"
- } else if model.ComparePassword(hashedId, userId) {
- isVerified = "true"
+ if len(userId) == 26 && len(hashedId) != 0 && model.ComparePassword(hashedId, userId) {
if c.Err = (<-api.Srv.Store.User().VerifyEmail(userId)).Err; c.Err != nil {
return
} else {
- c.LogAudit("")
+ c.LogAudit("Email Verified")
+ http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host+"/"+name+"/login?verified=true&email="+email, http.StatusTemporaryRedirect)
+ return
}
- } else {
- isVerified = "false"
}
page := NewHtmlTemplatePage("verify", "Email Verified")
- page.Props["IsVerified"] = isVerified
page.Props["TeamURL"] = c.GetTeamURLFromTeam(team)
page.Props["UserEmail"] = email
page.Props["ResendSuccess"] = resendSuccess