summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/post.go5
-rw-r--r--api/post_test.go4
-rw-r--r--api/team.go4
-rw-r--r--api/templates/invite_body.html6
-rw-r--r--api/user.go9
-rw-r--r--doc/help/Search.md12
-rw-r--r--doc/help/Team-Statistics.md24
-rw-r--r--doc/install/Upgrade-Guide.md53
-rw-r--r--model/channel.go7
-rw-r--r--model/oauth.go3
-rw-r--r--model/post.go7
-rw-r--r--model/preference.go3
-rw-r--r--model/team.go3
-rw-r--r--model/user.go7
-rw-r--r--web/react/components/access_history_modal.jsx70
-rw-r--r--web/react/components/activity_log_modal.jsx72
-rw-r--r--web/react/components/admin_console/user_item.jsx90
-rw-r--r--web/react/components/center_panel.jsx8
-rw-r--r--web/react/components/channel_header.jsx61
-rw-r--r--web/react/components/channel_invite_modal.jsx146
-rw-r--r--web/react/components/channel_members.jsx200
-rw-r--r--web/react/components/channel_members_modal.jsx210
-rw-r--r--web/react/components/confirm_modal.jsx79
-rw-r--r--web/react/components/create_post.jsx6
-rw-r--r--web/react/components/edit_channel_modal.jsx6
-rw-r--r--web/react/components/edit_channel_purpose_modal.jsx7
-rw-r--r--web/react/components/invite_member_modal.jsx177
-rw-r--r--web/react/components/member_list.jsx30
-rw-r--r--web/react/components/member_list_team_item.jsx70
-rw-r--r--web/react/components/navbar.jsx69
-rw-r--r--web/react/components/navbar_dropdown.jsx17
-rw-r--r--web/react/components/posts_view_container.jsx8
-rw-r--r--web/react/components/rename_channel_modal.jsx6
-rw-r--r--web/react/components/sidebar.jsx7
-rw-r--r--web/react/components/sidebar_header.jsx2
-rw-r--r--web/react/components/sidebar_right_menu.jsx28
-rw-r--r--web/react/components/team_members.jsx10
-rw-r--r--web/react/components/team_settings_modal.jsx6
-rw-r--r--web/react/components/tutorial/tutorial_tip.jsx2
-rw-r--r--web/react/components/user_settings/code_theme_chooser.jsx55
-rw-r--r--web/react/components/user_settings/custom_theme_chooser.jsx79
-rw-r--r--web/react/components/user_settings/import_theme_modal.jsx12
-rw-r--r--web/react/components/user_settings/user_settings.jsx40
-rw-r--r--web/react/components/user_settings/user_settings_advanced.jsx23
-rw-r--r--web/react/components/user_settings/user_settings_appearance.jsx81
-rw-r--r--web/react/components/user_settings/user_settings_developer.jsx11
-rw-r--r--web/react/components/user_settings/user_settings_display.jsx26
-rw-r--r--web/react/components/user_settings/user_settings_general.jsx25
-rw-r--r--web/react/components/user_settings/user_settings_integrations.jsx22
-rw-r--r--web/react/components/user_settings/user_settings_modal.jsx215
-rw-r--r--web/react/components/user_settings/user_settings_notifications.jsx28
-rw-r--r--web/react/components/user_settings/user_settings_security.jsx77
-rw-r--r--web/react/pages/channel.jsx36
-rw-r--r--web/react/stores/modal_store.jsx42
-rw-r--r--web/react/stores/user_store.jsx20
-rw-r--r--web/react/utils/channel_intro_mssages.jsx5
-rw-r--r--web/react/utils/constants.jsx46
-rw-r--r--web/react/utils/text_formatting.jsx26
-rw-r--r--web/react/utils/utils.jsx18
-rw-r--r--web/sass-files/sass/partials/_access-history.scss4
-rw-r--r--web/sass-files/sass/partials/_activity-log.scss4
-rw-r--r--web/sass-files/sass/partials/_base.scss2
-rw-r--r--web/sass-files/sass/partials/_content.scss8
-rw-r--r--web/sass-files/sass/partials/_headers.scss1
-rw-r--r--web/sass-files/sass/partials/_markdown.scss1
-rw-r--r--web/sass-files/sass/partials/_modal.scss4
-rw-r--r--web/sass-files/sass/partials/_post.scss36
-rw-r--r--web/sass-files/sass/partials/_settings.scss13
-rw-r--r--web/sass-files/sass/partials/_tutorial.scss14
-rw-r--r--web/sass-files/sass/partials/_videos.scss1
-rw-r--r--web/static/images/themes/code_themes/github.pngbin9648 -> 18321 bytes
-rw-r--r--web/static/images/themes/code_themes/monokai.pngbin9303 -> 18119 bytes
-rw-r--r--web/static/images/themes/code_themes/solarized_dark.pngbin8172 -> 17856 bytes
-rw-r--r--web/static/images/themes/code_themes/solarized_light.pngbin8860 -> 17934 bytes
-rw-r--r--web/static/js/perfect-scrollbar-0.6.7.jquery.js9
-rw-r--r--web/static/js/perfect-scrollbar-0.6.7.jquery.min.js2
-rw-r--r--web/templates/channel.html2
-rw-r--r--web/templates/head.html2
78 files changed, 1417 insertions, 1107 deletions
diff --git a/api/post.go b/api/post.go
index 31a7ab3b5..b52db8752 100644
--- a/api/post.go
+++ b/api/post.go
@@ -890,7 +890,10 @@ func searchPosts(c *Context, w http.ResponseWriter, r *http.Request) {
channels := []store.StoreChannel{}
for _, params := range paramsList {
- channels = append(channels, Srv.Store.Post().Search(c.Session.TeamId, c.Session.UserId, params))
+ // don't allow users to search for everything
+ if params.Terms != "*" {
+ channels = append(channels, Srv.Store.Post().Search(c.Session.TeamId, c.Session.UserId, params))
+ }
}
posts := &model.PostList{}
diff --git a/api/post_test.go b/api/post_test.go
index 3452c9788..0cb437e88 100644
--- a/api/post_test.go
+++ b/api/post_test.go
@@ -450,6 +450,10 @@ func TestSearchPosts(t *testing.T) {
if len(r3.Order) != 1 && r3.Order[0] == post3.Id {
t.Fatal("wrong serach")
}
+
+ if r4 := Client.Must(Client.SearchPosts("*")).Data.(*model.PostList); len(r4.Order) != 0 {
+ t.Fatal("searching for just * shouldn't return any results")
+ }
}
func TestSearchHashtagPosts(t *testing.T) {
diff --git a/api/team.go b/api/team.go
index 7d746d922..862970887 100644
--- a/api/team.go
+++ b/api/team.go
@@ -510,16 +510,14 @@ func InviteMembers(c *Context, team *model.Team, user *model.User, invites []str
}
subjectPage := NewServerTemplatePage("invite_subject")
- subjectPage.Props["SiteURL"] = c.GetSiteURL()
subjectPage.Props["SenderName"] = sender
subjectPage.Props["TeamDisplayName"] = team.DisplayName
bodyPage := NewServerTemplatePage("invite_body")
- bodyPage.Props["SiteURL"] = c.GetSiteURL()
+ bodyPage.Props["TeamURL"] = c.GetTeamURL()
bodyPage.Props["TeamDisplayName"] = team.DisplayName
bodyPage.Props["SenderName"] = sender
bodyPage.Props["SenderStatus"] = senderRole
- bodyPage.Props["Email"] = invite
props := make(map[string]string)
props["email"] = invite
props["id"] = team.Id
diff --git a/api/templates/invite_body.html b/api/templates/invite_body.html
index 930bc099d..d98f91357 100644
--- a/api/templates/invite_body.html
+++ b/api/templates/invite_body.html
@@ -18,10 +18,12 @@
<tr>
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
<h2 style="font-weight: normal; margin-top: 10px;">You've been invited</h2>
- <p>{{.Props.TeamDisplayName}} started using {{.ClientCfg.SiteName}}.<br> The team {{.Props.SenderStatus}} <strong>{{.Props.SenderName}}</strong>, has invited you to join <strong>{{.Props.TeamDisplayName}}</strong>.</p>
- <p style="margin: 20px 0 15px">
+ <p>The team {{.Props.SenderStatus}} <strong>{{.Props.SenderName}}</strong>, has invited you to join <strong>{{.Props.TeamDisplayName}}</strong>.</p>
+ <p style="margin: 30px 0 15px">
<a href="{{.Props.Link}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Join Team</a>
</p>
+ <br/>
+ <p>Mattermost lets you share messages and files from your PC or phone, with instant search and archiving. After you’ve joined <strong>{{.Props.TeamDisplayName}}</strong>, you can sign-in to your new team and access these features anytime from the web address:<br/><br/><a href="{{.Props.TeamURL}}">{{.Props.TeamURL}}</a></p>
</td>
</tr>
<tr>
diff --git a/api/user.go b/api/user.go
index 42d3a43e7..c871d7c79 100644
--- a/api/user.go
+++ b/api/user.go
@@ -87,6 +87,8 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
hash := r.URL.Query().Get("h")
+ sendWelcomeEmail := true
+
if IsVerifyHashRequired(user, team, hash) {
data := r.URL.Query().Get("d")
props := model.MapFromJson(strings.NewReader(data))
@@ -109,6 +111,7 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
user.Email = props["email"]
user.EmailVerified = true
+ sendWelcomeEmail = false
}
if len(user.AuthData) > 0 && len(user.AuthService) > 0 {
@@ -120,6 +123,10 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ if sendWelcomeEmail {
+ sendWelcomeEmailAndForget(ruser.Id, ruser.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team), ruser.EmailVerified)
+ }
+
w.Write([]byte(ruser.ToJson()))
}
@@ -198,8 +205,6 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User {
l4g.Error("Encountered an issue joining default channels user_id=%s, team_id=%s, err=%v", ruser.Id, ruser.TeamId, err)
}
- sendWelcomeEmailAndForget(ruser.Id, ruser.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team), user.EmailVerified)
-
addDirectChannelsAndForget(ruser)
if user.EmailVerified {
diff --git a/doc/help/Search.md b/doc/help/Search.md
index 02ecf7d40..51095aac6 100644
--- a/doc/help/Search.md
+++ b/doc/help/Search.md
@@ -2,14 +2,16 @@
The search box in Mattermost brings back results from any channel of which you’re a member. No results are returned from channels where you are not a member - even if they are open channels.
-Some things to know about search:
+#### Some things to know about search:
- Multiple search terms are connected with “OR” by default. Typing in `Mattermost website` returns results containing “Mattermost” or “website”
-- You can use quotes to return search results for exact terms, like `"Mattermost website"` will only return messages containing the entire phrase `"Mattermost website"` and not return messages with only `Mattermost` or `website`
-- You can use the `*` character for wildcard searches that match within words. For example: Searching for `rea*` brings back messages containing `reach`, `reason` and other words starting with `rea`.
+- Use `from:` to find posts from specific users and `in:` to find posts in specific channels. For example: Searching `Mattermost in:town-square` only returns messages in Town Square that contain `Mattermost`
+- Use quotes to return search results for exact terms. For example: Searching `"Mattermost website"` returns messages containing the entire phrase `"Mattermost website"` and not messages containing only `Mattermost` or `website`
+- Use the `*` character for wildcard searches that match within words. For example: Searching for `rea*` brings back messages containing `reach`, `reason` and other words starting with `rea`.
-#### Limitations
+#### Limitations:
- Search in Mattermost uses the full text search features included in either a MySQL or Postgres database, which has some limitations
- Special cases that are not supported in default full text search, such as searching for IP addresses like `10.100.200.101`, can be added in future as the search feature evolves
- - Searches with fewer than three characters will return no results, so for searching in Chinese try adding * to the end of queries
+ - Two letter searches and common words like "this", "a" and "is" won't appear in search results
+ - For searching in Chinese try adding * to the end of queries
diff --git a/doc/help/Team-Statistics.md b/doc/help/Team-Statistics.md
new file mode 100644
index 000000000..05d63794b
--- /dev/null
+++ b/doc/help/Team-Statistics.md
@@ -0,0 +1,24 @@
+## Team Statistics
+___
+Statistics on users, posts and channels are tracked for each team and viewable in the System Console. System Administrators can access statistics for your Mattermost teams by clicking the **three-dot menu**, then click **System Console**. Under the *Teams* section on the left side you’ll see a list of the teams that belong to your domain. Click **Statistics** under the team of interest to open the stats page. Here is some helpful terminology:
+
+**Total Users**
+The total number of accounts created, regardless of if the accounts are active or inactive.
+
+**Total Posts**
+The total number of posts made by your team, including deleted posts or those made by incoming and outgoing webhook integrations.
+
+**Public Groups**
+The number of public channels created by your team, including channels that may have been archived.
+
+**Private Group**
+The number of private groups created by your team, including groups that may have been archived.
+
+**Active Users With Posts**
+Users who logged in and made a post on a certain day.
+
+**Recently Active Users**
+Users that have logged in and had recent browser activity in Mattermost.
+
+**Newly Created Users**
+Users that have recently completed the signup process to create a Mattermost account on the team.
diff --git a/doc/install/Upgrade-Guide.md b/doc/install/Upgrade-Guide.md
index fc3a0711f..7f4eeaeb9 100644
--- a/doc/install/Upgrade-Guide.md
+++ b/doc/install/Upgrade-Guide.md
@@ -1,19 +1,53 @@
# Mattermost Upgrade Guide
-### Upgrading Mattermost in GitLab 8.0 to GitLab 8.1 with omnibus
+### Upgrading Mattermost to Next Major Release
+
+Each release of Mattermost contains logic to upgrade it from the previously major build version. For example, version 1.2 upgrades the database and configuration data schema for a Mattermost version 1.1 server. The following procedure outlines how to upgrade Mattermost to the next major release version.
+
+1. Download the **next major build release** of your server
+ 1. Determine the current version of your Mattermost server
+ 1. Go to any team site, opening the main menu at the top right of the left-hand sidebar and selecting **About Mattermost**
+ 2. Identify the next major build release of your Mattermost server from the list of [stable Mattermost releases](https://github.com/mattermost/platform/releases)
+ 1. For example, if your current version is 1.1.0, you want to select version 1.2.0.
+ 1. In some cases there will be **minor build releases**, such as 1.2.1 and 1.2.2. The minor build number indicates a bug fix or security issue release. Testing on minor build versions is less extensive than on major build versions and it is recommended that you use the minor build only if you need the specific additions included.
+ 3. Review Release Notes
+ 1. Check the release notes for the version of Mattermost you are able to install, and note any setting changes in the **Compatibility** section that apply to your deployment
+ 4. Download the `mattermost.tar.gz` file with the correct version for your upgrade
+ 1. You can use `wget` to retrieve a specific version. For example, to download v1.1.0 run `wget https://github.com/mattermost/platform/releases/download/v1.1.0/mattermost.tar.gz`
+2. Stop the Mattermost Server
+ 1. As best practice, consider posting to the Town Square channel of active teams pre-announcing the scheduled downtime to apply these upgrade procedures
+ 2. To stop the server run `sudo stop mattermost`
+2. Backup your data
+ 1. Back up your `config.json` file, which contains your system configuration. This will be used to restore your current settings after the new version is installed
+ 2. Backup your database using your organization's standard procedures for backing up MySQL or PostgreSQL
+ 3. If you're using local file storage, back up the location where files are stored
+4. Decompress `mattermost.tar.gz` and use its contents to replace the current version of Mattermost on disk
+ 1. Run `tar -xvzf mattermost.tar.gz`
+5. Restore the state of your server by copying the backed up version of `config.json` in place of the default `config.json`
+6. Start your server and address any setting changes relevant in the latest version of Mattermost
+ 1. Run `sudo start mattermost`
+ 2. The server will upgrade your database schema to be compatibile with the new release, as well as upgrade your `config.json` file to the latest format, using default values for new settings added
+ 3. Go to the System Console to update any settings that have been added or modified based on the **Compatibility** documentation in the release notes
+7. Test the system is working by going to the URL of an existing team
+
+### Upgrading from Mattermost Beta (Version 0.7)
+
+The following instructions apply to updating installations of Mattermost v0.7-Beta to Mattermost 1.1.
+
+#### Upgrading Mattermost in GitLab 8.0 to GitLab 8.1 with omnibus
Mattermost 0.7.1-beta in GitLab 8.0 was a pre-release of Mattermost and Mattermost v1.1.1 in GitLab 8.1 was [updated significantly](https://github.com/mattermost/platform/blob/master/CHANGELOG.md#configjson-changes-from-v07-to-v10) to get to a stable, forwards-compatible platform for Mattermost.
The Mattermost team didn't think it made sense for GitLab omnibus to attempt an automated re-configuration of Mattermost (since 0.7.1-beta was a pre-release) given the scale of change, so we're providing instructions for GitLab users who have customized their Mattermost deployments in 8.0 to move to 8.1:
-1. Follow the [Upgrading Mattermost v0.7.1-beta to v1.1.1 instructions](https://github.com/mattermost/platform/blob/master/doc/install/Upgrade-Guide.md#upgrading-mattermost-v071-beta-to-v111) below to identify the settings in Mattermost's `config.json` file that differ from defaults and need to be updated from GitLab 8.0 to 8.1.
+1. Follow the [Upgrading Mattermost v0.7.1-beta to v1.1.1 instructions](https://github.com/mattermost/platform/blob/master/doc/install/Upgrade-Guide.md#upgrading-mattermost-v071-beta-to-v111) below to identify the settings in Mattermost's `config.json` file that differ from defaults and need to be updated from GitLab 8.0 to 8.1
2. Upgrade to GitLab 8.1 using omnibus, and allowing it overwrite `config.json` to the new Mattermost v1.1.1 format
-3. Manually update `config.json` to new settings identified in Step 1.
+3. Manually update `config.json` to new settings identified in Step 1
-Optionally, you can use the new [System Console user interface](https://github.com/mattermost/platform/blob/master/doc/install/Configuration-Settings.md) to make changes to your new `config.json` file.
+Optionally, you can use the new [System Console user interface](https://github.com/mattermost/platform/blob/master/doc/install/Configuration-Settings.md) to make changes to your new `config.json` file.
-### Upgrading Mattermost v0.7.1-beta to v1.1.1
+#### Upgrading Mattermost v0.7.1-beta to v1.1.1
_Note: [Mattermost v1.1.1](https://github.com/mattermost/platform/releases/tag/v1.1.1) is a special release of Mattermost v1.1 that upgrades the database to Mattermost v1.1 from EITHER Mattermost v0.7 or Mattermost v1.0. The following instructions are for upgrading from Mattermost v0.7.1-beta to v1.1.1 and skipping the upgrade to Mattermost v1.0._
@@ -23,11 +57,6 @@ If you've manually changed Mattermost v0.7.1-beta configuration by updating the
2. For each setting that you changed, check [the changelog documentation](https://github.com/mattermost/platform/blob/master/CHANGELOG.md#configjson-changes-from-v07-to-v10) on whether the configuration setting has changed between v0.7 and v1.1.1
-3. Update your new [`config.json` file in Mattermost v1.1](https://github.com/mattermost/platform/blob/v1.1.0/config/config.json), based on your preferences and the changelog documentation above.
-
-Optionally, you can use the new [System Console user interface](https://github.com/mattermost/platform/blob/master/doc/install/Configuration-Settings.md) to make changes to your new `config.json` file.
-
-
-
-
+3. Update your new [`config.json` file in Mattermost v1.1](https://github.com/mattermost/platform/blob/v1.1.0/config/config.json), based on your preferences and the changelog documentation above
+Optionally, you can use the new [System Console user interface](https://github.com/mattermost/platform/blob/master/doc/install/Configuration-Settings.md) to make changes to your new `config.json` file.
diff --git a/model/channel.go b/model/channel.go
index ac54a7e44..0ce09f4bc 100644
--- a/model/channel.go
+++ b/model/channel.go
@@ -6,6 +6,7 @@ package model
import (
"encoding/json"
"io"
+ "unicode/utf8"
)
const (
@@ -74,7 +75,7 @@ func (o *Channel) IsValid() *AppError {
return NewAppError("Channel.IsValid", "Update at must be a valid time", "id="+o.Id)
}
- if len(o.DisplayName) > 64 {
+ if utf8.RuneCountInString(o.DisplayName) > 64 {
return NewAppError("Channel.IsValid", "Invalid display name", "id="+o.Id)
}
@@ -90,11 +91,11 @@ func (o *Channel) IsValid() *AppError {
return NewAppError("Channel.IsValid", "Invalid type", "id="+o.Id)
}
- if len(o.Header) > 1024 {
+ if utf8.RuneCountInString(o.Header) > 1024 {
return NewAppError("Channel.IsValid", "Invalid header", "id="+o.Id)
}
- if len(o.Purpose) > 128 {
+ if utf8.RuneCountInString(o.Purpose) > 128 {
return NewAppError("Channel.IsValid", "Invalid purpose", "id="+o.Id)
}
diff --git a/model/oauth.go b/model/oauth.go
index 0320e7ec7..67825dd97 100644
--- a/model/oauth.go
+++ b/model/oauth.go
@@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "unicode/utf8"
)
type OAuthApp struct {
@@ -57,7 +58,7 @@ func (a *OAuthApp) IsValid() *AppError {
return NewAppError("OAuthApp.IsValid", "Invalid homepage", "app_id="+a.Id)
}
- if len(a.Description) > 512 {
+ if utf8.RuneCountInString(a.Description) > 512 {
return NewAppError("OAuthApp.IsValid", "Invalid description", "app_id="+a.Id)
}
diff --git a/model/post.go b/model/post.go
index 11f3ad0d5..e0074b348 100644
--- a/model/post.go
+++ b/model/post.go
@@ -6,6 +6,7 @@ package model
import (
"encoding/json"
"io"
+ "unicode/utf8"
)
const (
@@ -94,11 +95,11 @@ func (o *Post) IsValid() *AppError {
return NewAppError("Post.IsValid", "Invalid original id", "")
}
- if len(o.Message) > 4000 {
+ if utf8.RuneCountInString(o.Message) > 4000 {
return NewAppError("Post.IsValid", "Invalid message", "id="+o.Id)
}
- if len(o.Hashtags) > 1000 {
+ if utf8.RuneCountInString(o.Hashtags) > 1000 {
return NewAppError("Post.IsValid", "Invalid hashtags", "id="+o.Id)
}
@@ -106,7 +107,7 @@ func (o *Post) IsValid() *AppError {
return NewAppError("Post.IsValid", "Invalid type", "id="+o.Type)
}
- if len(ArrayToJson(o.Filenames)) > 4000 {
+ if utf8.RuneCountInString(ArrayToJson(o.Filenames)) > 4000 {
return NewAppError("Post.IsValid", "Invalid filenames", "id="+o.Id)
}
diff --git a/model/preference.go b/model/preference.go
index 44279f71a..bcd0237f1 100644
--- a/model/preference.go
+++ b/model/preference.go
@@ -6,6 +6,7 @@ package model
import (
"encoding/json"
"io"
+ "unicode/utf8"
)
const (
@@ -52,7 +53,7 @@ func (o *Preference) IsValid() *AppError {
return NewAppError("Preference.IsValid", "Invalid name", "name="+o.Name)
}
- if len(o.Value) > 128 {
+ if utf8.RuneCountInString(o.Value) > 128 {
return NewAppError("Preference.IsValid", "Value is too long", "value="+o.Value)
}
diff --git a/model/team.go b/model/team.go
index 5c9cf5a26..e7dde4766 100644
--- a/model/team.go
+++ b/model/team.go
@@ -9,6 +9,7 @@ import (
"io"
"regexp"
"strings"
+ "unicode/utf8"
)
const (
@@ -122,7 +123,7 @@ func (o *Team) IsValid(restrictTeamNames bool) *AppError {
return NewAppError("Team.IsValid", "Invalid email", "id="+o.Id)
}
- if len(o.DisplayName) == 0 || len(o.DisplayName) > 64 {
+ if utf8.RuneCountInString(o.DisplayName) == 0 || utf8.RuneCountInString(o.DisplayName) > 64 {
return NewAppError("Team.IsValid", "Invalid name", "id="+o.Id)
}
diff --git a/model/user.go b/model/user.go
index 15016f8dc..871d1bf2d 100644
--- a/model/user.go
+++ b/model/user.go
@@ -10,6 +10,7 @@ import (
"io"
"regexp"
"strings"
+ "unicode/utf8"
)
const (
@@ -80,15 +81,15 @@ func (u *User) IsValid() *AppError {
return NewAppError("User.IsValid", "Invalid email", "user_id="+u.Id)
}
- if len(u.Nickname) > 64 {
+ if utf8.RuneCountInString(u.Nickname) > 64 {
return NewAppError("User.IsValid", "Invalid nickname", "user_id="+u.Id)
}
- if len(u.FirstName) > 64 {
+ if utf8.RuneCountInString(u.FirstName) > 64 {
return NewAppError("User.IsValid", "Invalid first name", "user_id="+u.Id)
}
- if len(u.LastName) > 64 {
+ if utf8.RuneCountInString(u.LastName) > 64 {
return NewAppError("User.IsValid", "Invalid last name", "user_id="+u.Id)
}
diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx
index f0a31ce90..27959ec7e 100644
--- a/web/react/components/access_history_modal.jsx
+++ b/web/react/components/access_history_modal.jsx
@@ -1,6 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+var Modal = ReactBootstrap.Modal;
var UserStore = require('../stores/user_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var AsyncClient = require('../utils/async_client.jsx');
@@ -30,16 +31,23 @@ export default class AccessHistoryModal extends React.Component {
}
onShow() {
AsyncClient.getAudits();
+
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 300);
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
+ }
}
onHide() {
- $('#user_settings').modal('show');
this.setState({moreInfo: []});
+ this.props.onModalDismissed();
}
componentDidMount() {
UserStore.addAuditsChangeListener(this.onAuditChange);
- $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow);
-
- $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide);
+ }
+ componentDidUpdate(prevProps) {
+ if (this.props.show && !prevProps.show) {
+ this.onShow();
+ }
}
componentWillUnmount() {
UserStore.removeAuditsChangeListener(this.onAuditChange);
@@ -380,43 +388,23 @@ export default class AccessHistoryModal extends React.Component {
}
return (
- <div>
- <div
- className='modal fade'
- ref='modal'
- id='access-history'
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
- >
- <div className='modal-dialog modal-lg'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>{'×'}</span>
- </button>
- <h4
- className='modal-title'
- id='myModalLabel'
- >
- {'Access History'}
- </h4>
- </div>
- <div
- ref='modalBody'
- className='modal-body'
- >
- {content}
- </div>
- </div>
- </div>
- </div>
- </div>
+ <Modal
+ show={this.props.show}
+ onHide={this.onHide}
+ bsSize='large'
+ >
+ <Modal.Header closeButton={true}>
+ <Modal.Title>{'Access History'}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body ref='modalBody'>
+ {content}
+ </Modal.Body>
+ </Modal>
);
}
}
+
+AccessHistoryModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onModalDismissed: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index 2c944913f..ef3077470 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -4,6 +4,7 @@
const UserStore = require('../stores/user_store.jsx');
const Client = require('../utils/client.jsx');
const AsyncClient = require('../utils/async_client.jsx');
+const Modal = ReactBootstrap.Modal;
const LoadingScreen = require('./loading_screen.jsx');
const Utils = require('../utils/utils.jsx');
@@ -49,16 +50,23 @@ export default class ActivityLogModal extends React.Component {
}
onShow() {
AsyncClient.getSessions();
+
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 300);
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
+ }
}
onHide() {
- $('#user_settings').modal('show');
this.setState({moreInfo: []});
+ this.props.onModalDismissed();
}
componentDidMount() {
UserStore.addSessionsChangeListener(this.onListenerChange);
- $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow);
-
- $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide);
+ }
+ componentDidUpdate(prevProps) {
+ if (this.props.show && !prevProps.show) {
+ this.onShow();
+ }
}
componentWillUnmount() {
UserStore.removeSessionsChangeListener(this.onListenerChange);
@@ -151,44 +159,24 @@ export default class ActivityLogModal extends React.Component {
}
return (
- <div>
- <div
- className='modal fade'
- ref='modal'
- id='activity-log'
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
- >
- <div className='modal-dialog modal-lg'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>&times;</span>
- </button>
- <h4
- className='modal-title'
- id='myModalLabel'
- >
- Active Sessions
- </h4>
- </div>
- <p className='session-help-text'>Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session.</p>
- <div
- ref='modalBody'
- className='modal-body'
- >
- {content}
- </div>
- </div>
- </div>
- </div>
- </div>
+ <Modal
+ show={this.props.show}
+ onHide={this.onHide}
+ bsSize='large'
+ >
+ <Modal.Header closeButton={true}>
+ <Modal.Title>{'Active Sessions'}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body ref='modalBody'>
+ <p className='session-help-text'>{'Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the \'Logout\' button below to end a session.'}</p>
+ {content}
+ </Modal.Body>
+ </Modal>
);
}
}
+
+ActivityLogModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onModalDismissed: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx
index f7e92672d..2badaf0e5 100644
--- a/web/react/components/admin_console/user_item.jsx
+++ b/web/react/components/admin_console/user_item.jsx
@@ -212,50 +212,52 @@ export default class UserItem extends React.Component {
}
return (
- <div className='row member-div'>
- <img
- className='post-profile-img pull-left'
- src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
- height='36'
- width='36'
- />
- <span className='member-name'>{Utils.getDisplayName(user)}</span>
- <span className='member-email'>{email}</span>
- <div className='dropdown member-drop'>
- <a
- href='#'
- className='dropdown-toggle theme'
- type='button'
- id='channel_header_dropdown'
- data-toggle='dropdown'
- aria-expanded='true'
- >
- <span>{currentRoles} </span>
- <span className='caret'></span>
- </a>
- <ul
- className='dropdown-menu member-menu'
- role='menu'
- aria-labelledby='channel_header_dropdown'
- >
- {makeAdmin}
- {makeMember}
- {makeActive}
- {makeNotActive}
- {makeSystemAdmin}
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- onClick={this.handleResetPassword}
- >
- {'Reset Password'}
- </a>
- </li>
- </ul>
- </div>
- {serverError}
- </div>
+ <tr>
+ <td className='row member-div'>
+ <img
+ className='post-profile-img pull-left'
+ src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
+ height='36'
+ width='36'
+ />
+ <span className='member-name'>{Utils.getDisplayName(user)}</span>
+ <span className='member-email'>{email}</span>
+ <div className='dropdown member-drop'>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ id='channel_header_dropdown'
+ data-toggle='dropdown'
+ aria-expanded='true'
+ >
+ <span>{currentRoles} </span>
+ <span className='caret'></span>
+ </a>
+ <ul
+ className='dropdown-menu member-menu'
+ role='menu'
+ aria-labelledby='channel_header_dropdown'
+ >
+ {makeAdmin}
+ {makeMember}
+ {makeActive}
+ {makeNotActive}
+ {makeSystemAdmin}
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleResetPassword}
+ >
+ {'Reset Password'}
+ </a>
+ </li>
+ </ul>
+ </div>
+ {serverError}
+ </td>
+ </tr>
);
}
}
diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx
index 242c2c637..3ee40bb86 100644
--- a/web/react/components/center_panel.jsx
+++ b/web/react/components/center_panel.jsx
@@ -21,7 +21,7 @@ export default class CenterPanel extends React.Component {
this.onPreferenceChange = this.onPreferenceChange.bind(this);
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
this.state = {showTutorialScreens: parseInt(tutorialPref.value, 10) === TutorialSteps.INTRO_SCREENS};
}
componentDidMount() {
@@ -31,7 +31,7 @@ export default class CenterPanel extends React.Component {
PreferenceStore.removeChangeListener(this.onPreferenceChange);
}
onPreferenceChange() {
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
this.setState({showTutorialScreens: parseInt(tutorialPref.value, 10) <= TutorialSteps.INTRO_SCREENS});
}
render() {
@@ -61,9 +61,7 @@ export default class CenterPanel extends React.Component {
<div id='channel-header'>
<ChannelHeader />
</div>
- <div id='post-list'>
- {postsContainer}
- </div>
+ {postsContainer}
<div
className='post-create__container'
id='post-create'
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 20f106f30..895dc5fe4 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -1,20 +1,23 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-const ChannelStore = require('../stores/channel_store.jsx');
-const UserStore = require('../stores/user_store.jsx');
-const SearchStore = require('../stores/search_store.jsx');
-const PreferenceStore = require('../stores/preference_store.jsx');
const NavbarSearchBox = require('./search_bar.jsx');
-const AsyncClient = require('../utils/async_client.jsx');
-const Client = require('../utils/client.jsx');
-const TextFormatting = require('../utils/text_formatting.jsx');
-const Utils = require('../utils/utils.jsx');
const MessageWrapper = require('./message_wrapper.jsx');
const PopoverListMembers = require('./popover_list_members.jsx');
const EditChannelPurposeModal = require('./edit_channel_purpose_modal.jsx');
+const ChannelInviteModal = require('./channel_invite_modal.jsx');
+const ChannelMembersModal = require('./channel_members_modal.jsx');
+
+const ChannelStore = require('../stores/channel_store.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const SearchStore = require('../stores/search_store.jsx');
+const PreferenceStore = require('../stores/preference_store.jsx');
const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const Utils = require('../utils/utils.jsx');
+const TextFormatting = require('../utils/text_formatting.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const Client = require('../utils/client.jsx');
const Constants = require('../utils/constants.jsx');
const ActionTypes = Constants.ActionTypes;
@@ -31,6 +34,8 @@ export default class ChannelHeader extends React.Component {
const state = this.getStateFromStores();
state.showEditChannelPurposeModal = false;
+ state.showInviteModal = false;
+ state.showMembersModal = false;
this.state = state;
}
getStateFromStores() {
@@ -86,7 +91,7 @@ export default class ChannelHeader extends React.Component {
let terms = '';
if (user.notify_props && user.notify_props.mention_keys) {
- let termKeys = UserStore.getCurrentMentionKeys();
+ const termKeys = UserStore.getCurrentMentionKeys();
if (user.notify_props.all === 'true' && termKeys.indexOf('@all') !== -1) {
termKeys.splice(termKeys.indexOf('@all'), 1);
}
@@ -146,7 +151,7 @@ export default class ChannelHeader extends React.Component {
channelTerm = 'Group';
}
- let dropdownContents = [];
+ const dropdownContents = [];
if (isDirect) {
dropdownContents.push(
<li
@@ -162,7 +167,7 @@ export default class ChannelHeader extends React.Component {
data-title={channel.display_name}
data-channelid={channel.id}
>
- Set Channel Header...
+ {'Set Channel Header...'}
</a>
</li>
);
@@ -179,7 +184,7 @@ export default class ChannelHeader extends React.Component {
data-channelid={channel.id}
href='#'
>
- View Info
+ {'View Info'}
</a>
</li>
);
@@ -192,11 +197,10 @@ export default class ChannelHeader extends React.Component {
>
<a
role='menuitem'
- data-toggle='modal'
- data-target='#channel_invite'
href='#'
+ onClick={() => this.setState({showInviteModal: true})}
>
- Add Members
+ {'Add Members'}
</a>
</li>
);
@@ -209,11 +213,10 @@ export default class ChannelHeader extends React.Component {
>
<a
role='menuitem'
- data-toggle='modal'
- data-target='#channel_members'
href='#'
+ onClick={() => this.setState({showMembersModal: true})}
>
- Manage Members
+ {'Manage Members'}
</a>
</li>
);
@@ -234,7 +237,7 @@ export default class ChannelHeader extends React.Component {
data-title={channel.display_name}
data-channelid={channel.id}
>
- Set {channelTerm} Header...
+ {'Set '}{channelTerm}{' Header...'}
</a>
</li>
);
@@ -248,7 +251,7 @@ export default class ChannelHeader extends React.Component {
href='#'
onClick={() => this.setState({showEditChannelPurposeModal: true})}
>
- Set {channelTerm} Purpose...
+ {'Set '}{channelTerm}{' Purpose...'}
</a>
</li>
);
@@ -265,7 +268,7 @@ export default class ChannelHeader extends React.Component {
data-title={channel.display_name}
data-channelid={channel.id}
>
- Notification Preferences
+ {'Notification Preferences'}
</a>
</li>
);
@@ -286,7 +289,7 @@ export default class ChannelHeader extends React.Component {
data-name={channel.name}
data-channelid={channel.id}
>
- Rename {channelTerm}...
+ {'Rename '}{channelTerm}{'...'}
</a>
</li>
);
@@ -303,7 +306,7 @@ export default class ChannelHeader extends React.Component {
data-title={channel.display_name}
data-channelid={channel.id}
>
- Delete {channelTerm}...
+ {'Delete '}{channelTerm}{'...'}
</a>
</li>
);
@@ -319,7 +322,7 @@ export default class ChannelHeader extends React.Component {
href='#'
onClick={this.handleLeave}
>
- Leave {channelTerm}
+ {'Leave '}{channelTerm}
</a>
</li>
);
@@ -397,7 +400,7 @@ export default class ChannelHeader extends React.Component {
href='#'
onClick={this.searchMentions}
>
- Recent Mentions
+ {'Recent Mentions'}
</a>
</li>
</ul>
@@ -411,6 +414,14 @@ export default class ChannelHeader extends React.Component {
onModalDismissed={() => this.setState({showEditChannelPurposeModal: false})}
channel={channel}
/>
+ <ChannelInviteModal
+ show={this.state.showInviteModal}
+ onModalDismissed={() => this.setState({showInviteModal: false})}
+ />
+ <ChannelMembersModal
+ show={this.state.showMembersModal}
+ onModalDismissed={() => this.setState({showMembersModal: false})}
+ />
</div>
);
}
diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx
index e90d1a666..7c1032321 100644
--- a/web/react/components/channel_invite_modal.jsx
+++ b/web/react/components/channel_invite_modal.jsx
@@ -1,26 +1,25 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserStore = require('../stores/user_store.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var MemberList = require('./member_list.jsx');
-var LoadingScreen = require('./loading_screen.jsx');
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
+const MemberList = require('./member_list.jsx');
+const LoadingScreen = require('./loading_screen.jsx');
+
+const UserStore = require('../stores/user_store.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
+
+const Utils = require('../utils/utils.jsx');
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+
+const Modal = ReactBootstrap.Modal;
export default class ChannelInviteModal extends React.Component {
constructor() {
super();
- this.componentDidMount = this.componentDidMount.bind(this);
- this.componentWillUnmount = this.componentWillUnmount.bind(this);
- this.onShow = this.onShow.bind(this);
- this.onHide = this.onHide.bind(this);
this.onListenerChange = this.onListenerChange.bind(this);
this.handleInvite = this.handleInvite.bind(this);
- this.isShown = false;
this.state = this.getStateFromStores();
}
getStateFromStores() {
@@ -39,7 +38,7 @@ export default class ChannelInviteModal extends React.Component {
}
}
- nonmembers.sort(function sortByUsername(a, b) {
+ nonmembers.sort((a, b) => {
return a.username.localeCompare(b.username);
});
@@ -49,35 +48,37 @@ export default class ChannelInviteModal extends React.Component {
}
return {
- nonmembers: nonmembers,
- memberIds: memberIds,
- channelName: channelName,
- loading: loading
+ nonmembers,
+ memberIds,
+ channelName,
+ loading
};
}
- componentDidMount() {
- $(ReactDOM.findDOMNode(this)).on('hidden.bs.modal', this.onHide);
- $(ReactDOM.findDOMNode(this)).on('show.bs.modal', this.onShow);
-
- ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
- ChannelStore.addChangeListener(this.onListenerChange);
- UserStore.addChangeListener(this.onListenerChange);
- }
- componentWillUnmount() {
- ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
- ChannelStore.removeChangeListener(this.onListenerChange);
- UserStore.removeChangeListener(this.onListenerChange);
- }
onShow() {
- this.isShown = true;
- this.onListenerChange();
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
+ }
}
- onHide() {
- this.isShown = false;
+ componentDidUpdate(prevProps) {
+ if (this.props.show && !prevProps.show) {
+ this.onShow();
+ }
+ }
+ componentWillReceiveProps(nextProps) {
+ if (!this.props.show && nextProps.show) {
+ ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
+ ChannelStore.addChangeListener(this.onListenerChange);
+ UserStore.addChangeListener(this.onListenerChange);
+ this.onListenerChange();
+ } else if (this.props.show && !nextProps.show) {
+ ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
+ ChannelStore.removeChangeListener(this.onListenerChange);
+ UserStore.removeChangeListener(this.onListenerChange);
+ }
}
onListenerChange() {
var newState = this.getStateFromStores();
- if (!utils.areStatesEqual(this.state, newState) && this.isShown) {
+ if (!Utils.areStatesEqual(this.state, newState)) {
this.setState(newState);
}
}
@@ -90,8 +91,8 @@ export default class ChannelInviteModal extends React.Component {
var data = {};
data.user_id = userId;
- client.addChannelMember(ChannelStore.getCurrentId(), data,
- function sucess() {
+ Client.addChannelMember(ChannelStore.getCurrentId(), data,
+ () => {
var nonmembers = this.state.nonmembers;
var memberIds = this.state.memberIds;
@@ -103,16 +104,20 @@ export default class ChannelInviteModal extends React.Component {
}
}
- this.setState({inviteError: null, memberIds: memberIds, nonmembers: nonmembers});
+ this.setState({inviteError: null, memberIds, nonmembers});
AsyncClient.getChannelExtraInfo(true);
- }.bind(this),
-
- function error(err) {
+ },
+ (err) => {
this.setState({inviteError: err.message});
- }.bind(this)
+ }
);
}
render() {
+ var maxHeight = 1000;
+ if (Utils.windowHeight() <= 1200) {
+ maxHeight = Utils.windowHeight() - 300;
+ }
+
var inviteError = null;
if (this.state.inviteError) {
inviteError = (<label className='has-error control-label'>{this.state.inviteError}</label>);
@@ -121,7 +126,7 @@ export default class ChannelInviteModal extends React.Component {
var currentMember = ChannelStore.getCurrentMember();
var isAdmin = false;
if (currentMember) {
- isAdmin = utils.isAdmin(currentMember.roles) || utils.isAdmin(UserStore.getCurrentUser().roles);
+ isAdmin = Utils.isAdmin(currentMember.roles) || Utils.isAdmin(UserStore.getCurrentUser().roles);
}
var content;
@@ -138,43 +143,36 @@ export default class ChannelInviteModal extends React.Component {
}
return (
- <div
- className='modal fade more-modal'
- id='channel_invite'
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
+ <Modal
+ dialogClassName='more-modal'
+ show={this.props.show}
+ onHide={this.props.onModalDismissed}
>
- <div
- className='modal-dialog'
- role='document'
- >
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>&times;</span>
- </button>
- <h4 className='modal-title'>Add New Members to <span className='name'>{this.state.channelName}</span></h4>
- </div>
- <div className='modal-body'>
+ <Modal.Header closeButton={true}>
+ <Modal.Title>{'Add New Members to '}<span className='name'>{this.state.channelName}</span></Modal.Title>
+ </Modal.Header>
+ <Modal.Body
+ ref='modalBody'
+ style={{maxHeight}}
+ >
{inviteError}
{content}
- </div>
- <div className='modal-footer'>
+ </Modal.Body>
+ <Modal.Footer>
<button
type='button'
className='btn btn-default'
- data-dismiss='modal'
- >Close</button>
- </div>
- </div>
- </div>
- </div>
+ onClick={this.props.onModalDismissed}
+ >
+ {'Close'}
+ </button>
+ </Modal.Footer>
+ </Modal>
);
}
}
+
+ChannelInviteModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onModalDismissed: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/channel_members.jsx b/web/react/components/channel_members.jsx
deleted file mode 100644
index d0ea7278b..000000000
--- a/web/react/components/channel_members.jsx
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-const UserStore = require('../stores/user_store.jsx');
-const ChannelStore = require('../stores/channel_store.jsx');
-const AsyncClient = require('../utils/async_client.jsx');
-const MemberList = require('./member_list.jsx');
-const Client = require('../utils/client.jsx');
-const Utils = require('../utils/utils.jsx');
-
-export default class ChannelMembers extends React.Component {
- constructor(props) {
- super(props);
-
- 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();
- }
- getStateFromStores() {
- const users = UserStore.getActiveOnlyProfiles();
- let memberList = ChannelStore.getCurrentExtraInfo().members;
-
- let nonmemberList = [];
- for (let id in users) {
- if (users.hasOwnProperty(id)) {
- let found = false;
- for (let i = 0; i < memberList.length; i++) {
- if (memberList[i].id === id) {
- found = true;
- break;
- }
- }
- if (!found) {
- nonmemberList.push(users[id]);
- }
- }
- }
-
- function compareByUsername(a, b) {
- if (a.username < b.username) {
- return -1;
- } else if (a.username > b.username) {
- return 1;
- }
-
- return 0;
- }
-
- memberList.sort(compareByUsername);
- nonmemberList.sort(compareByUsername);
-
- const channel = ChannelStore.getCurrent();
- let channelName = '';
- if (channel) {
- channelName = channel.display_name;
- }
-
- return {
- nonmemberList: nonmemberList,
- memberList: memberList,
- channelName: channelName
- };
- }
- onHide() {
- this.setState({renderMembers: false});
- }
- onShow() {
- this.setState({renderMembers: true});
- }
- componentDidMount() {
- ChannelStore.addExtraInfoChangeListener(this.onChange);
- ChannelStore.addChangeListener(this.onChange);
- $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide);
-
- $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
- }
- componentWillUnmount() {
- ChannelStore.removeExtraInfoChangeListener(this.onChange);
- ChannelStore.removeChangeListener(this.onChange);
- }
- onChange() {
- const newState = this.getStateFromStores();
- if (!Utils.areStatesEqual(this.state, newState)) {
- this.setState(newState);
- }
- }
- handleRemove(userId) {
- // Make sure the user is a member of the channel
- let memberList = this.state.memberList;
- let found = false;
- for (let i = 0; i < memberList.length; i++) {
- if (memberList[i].id === userId) {
- found = true;
- break;
- }
- }
-
- if (!found) {
- return;
- }
-
- let data = {};
- data.user_id = userId;
-
- Client.removeChannelMember(ChannelStore.getCurrentId(), data,
- function handleRemoveSuccess() {
- let oldMember;
- for (let i = 0; i < memberList.length; i++) {
- if (userId === memberList[i].id) {
- oldMember = memberList[i];
- memberList.splice(i, 1);
- break;
- }
- }
-
- let nonmemberList = this.state.nonmemberList;
- if (oldMember) {
- nonmemberList.push(oldMember);
- }
-
- this.setState({memberList: memberList, nonmemberList: nonmemberList});
- AsyncClient.getChannelExtraInfo(true);
- }.bind(this),
- function handleRemoveError(err) {
- this.setState({inviteError: err.message});
- }.bind(this)
- );
- }
- render() {
- const currentMember = ChannelStore.getCurrentMember();
- let isAdmin = false;
- if (currentMember) {
- isAdmin = Utils.isAdmin(currentMember.roles) || Utils.isAdmin(UserStore.getCurrentUser().roles);
- }
-
- var memberList = null;
- if (this.state.renderMembers) {
- memberList = (
- <MemberList
- memberList={this.state.memberList}
- isAdmin={isAdmin}
- handleRemove={this.handleRemove}
- />
- );
- }
-
- return (
- <div
- className='modal fade more-modal'
- ref='modal'
- id='channel_members'
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
- >
- <div className='modal-dialog'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>×</span>
- </button>
- <h4 className='modal-title'><span className='name'>{this.state.channelName}</span> Members</h4>
- <a
- className='btn btn-md btn-primary'
- data-toggle='modal'
- data-target='#channel_invite'
- >
- <i className='glyphicon glyphicon-envelope'/> Add New Members
- </a>
- </div>
- <div
- ref='modalBody'
- className='modal-body'
- >
- {memberList}
- </div>
- <div className='modal-footer'>
- <button
- type='button'
- className='btn btn-default'
- data-dismiss='modal'
- >
- Close
- </button>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/web/react/components/channel_members_modal.jsx b/web/react/components/channel_members_modal.jsx
new file mode 100644
index 000000000..2fa7ae8ff
--- /dev/null
+++ b/web/react/components/channel_members_modal.jsx
@@ -0,0 +1,210 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const MemberList = require('./member_list.jsx');
+const ChannelInviteModal = require('./channel_invite_modal.jsx');
+
+const UserStore = require('../stores/user_store.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
+
+const AsyncClient = require('../utils/async_client.jsx');
+const Client = require('../utils/client.jsx');
+const Utils = require('../utils/utils.jsx');
+
+const Modal = ReactBootstrap.Modal;
+
+export default class ChannelMembersModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getStateFromStores = this.getStateFromStores.bind(this);
+ this.onChange = this.onChange.bind(this);
+ this.handleRemove = this.handleRemove.bind(this);
+
+ const state = this.getStateFromStores();
+ state.showInviteModal = false;
+ this.state = state;
+ }
+ getStateFromStores() {
+ const users = UserStore.getActiveOnlyProfiles();
+ const memberList = ChannelStore.getCurrentExtraInfo().members;
+
+ const nonmemberList = [];
+ for (const id in users) {
+ if (users.hasOwnProperty(id)) {
+ let found = false;
+ for (let i = 0; i < memberList.length; i++) {
+ if (memberList[i].id === id) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ nonmemberList.push(users[id]);
+ }
+ }
+ }
+
+ function compareByUsername(a, b) {
+ if (a.username < b.username) {
+ return -1;
+ } else if (a.username > b.username) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ memberList.sort(compareByUsername);
+ nonmemberList.sort(compareByUsername);
+
+ const channel = ChannelStore.getCurrent();
+ let channelName = '';
+ if (channel) {
+ channelName = channel.display_name;
+ }
+
+ return {
+ nonmemberList,
+ memberList,
+ channelName
+ };
+ }
+ onShow() {
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
+ }
+ }
+ componentDidUpdate(prevProps) {
+ if (this.props.show && !prevProps.show) {
+ this.onShow();
+ }
+ }
+ componentWillReceiveProps(nextProps) {
+ if (!this.props.show && nextProps.show) {
+ ChannelStore.addExtraInfoChangeListener(this.onChange);
+ ChannelStore.addChangeListener(this.onChange);
+ } else if (this.props.show && !nextProps.show) {
+ ChannelStore.removeExtraInfoChangeListener(this.onChange);
+ ChannelStore.removeChangeListener(this.onChange);
+ }
+ }
+ onChange() {
+ const newState = this.getStateFromStores();
+ if (!Utils.areStatesEqual(this.state, newState)) {
+ this.setState(newState);
+ }
+ }
+ handleRemove(userId) {
+ // Make sure the user is a member of the channel
+ const memberList = this.state.memberList;
+ let found = false;
+ for (let i = 0; i < memberList.length; i++) {
+ if (memberList[i].id === userId) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ return;
+ }
+
+ const data = {};
+ data.user_id = userId;
+
+ Client.removeChannelMember(ChannelStore.getCurrentId(), data,
+ () => {
+ let oldMember;
+ for (let i = 0; i < memberList.length; i++) {
+ if (userId === memberList[i].id) {
+ oldMember = memberList[i];
+ memberList.splice(i, 1);
+ break;
+ }
+ }
+
+ const nonmemberList = this.state.nonmemberList;
+ if (oldMember) {
+ nonmemberList.push(oldMember);
+ }
+
+ this.setState({memberList, nonmemberList});
+ AsyncClient.getChannelExtraInfo(true);
+ },
+ (err) => {
+ this.setState({inviteError: err.message});
+ }
+ );
+ }
+ render() {
+ var maxHeight = 1000;
+ if (Utils.windowHeight() <= 1200) {
+ maxHeight = Utils.windowHeight() - 300;
+ }
+
+ const currentMember = ChannelStore.getCurrentMember();
+ let isAdmin = false;
+ if (currentMember) {
+ isAdmin = Utils.isAdmin(currentMember.roles) || Utils.isAdmin(UserStore.getCurrentUser().roles);
+ }
+
+ return (
+ <div>
+ <Modal
+ dialogClassName='more-modal'
+ show={this.props.show}
+ onHide={this.props.onModalDismissed}
+ >
+ <Modal.Header closeButton={true}>
+ <Modal.Title><span className='name'>{this.state.channelName}</span>{' Members'}</Modal.Title>
+ <a
+ className='btn btn-md btn-primary'
+ href='#'
+ onClick={() => {
+ this.setState({showInviteModal: true});
+ this.props.onModalDismissed();
+ }}
+ >
+ <i className='glyphicon glyphicon-envelope'/>{' Add New Members'}
+ </a>
+ </Modal.Header>
+ <Modal.Body
+ ref='modalBody'
+ style={{maxHeight}}
+ >
+ <div className='team-member-list'>
+ <MemberList
+ memberList={this.state.memberList}
+ isAdmin={isAdmin}
+ handleRemove={this.handleRemove}
+ />
+ </div>
+ </Modal.Body>
+ <Modal.Footer>
+ <button
+ type='button'
+ className='btn btn-default'
+ onClick={this.props.onModalDismissed}
+ >
+ {'Close'}
+ </button>
+ </Modal.Footer>
+ </Modal>
+ <ChannelInviteModal
+ show={this.state.showInviteModal}
+ onModalDismissed={() => this.setState({showInviteModal: false})}
+ />
+ </div>
+ );
+ }
+}
+
+ChannelMembersModal.defaultProps = {
+ show: false
+};
+
+ChannelMembersModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onModalDismissed: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/confirm_modal.jsx b/web/react/components/confirm_modal.jsx
index 12002f33f..cdef1c1ea 100644
--- a/web/react/components/confirm_modal.jsx
+++ b/web/react/components/confirm_modal.jsx
@@ -1,70 +1,63 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+const Modal = ReactBootstrap.Modal;
+
export default class ConfirmModal extends React.Component {
constructor(props) {
super(props);
this.handleConfirm = this.handleConfirm.bind(this);
-
- this.state = {};
}
+
handleConfirm() {
- $('#' + this.props.parent_id).attr('data-confirm', 'true');
- $('#' + this.props.parent_id).modal('hide');
- $('#' + this.props.id).modal('hide');
+ this.props.onConfirm();
}
+
render() {
return (
- <div
- className='modal fade'
- id={this.props.id}
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
+ <Modal
+ className='modal-confirm'
+ show={this.props.show}
+ onHide={this.props.onCancel}
>
- <div className='modal-dialog'>
- <div className='modal-content'>
- <div className='modal-header'>
- <h4 className='modal-title'>{this.props.title}</h4>
- </div>
- <div className='modal-body'>
- {this.props.message}
- </div>
- <div className='modal-footer'>
- <button
- type='button'
- className='btn btn-default'
- data-dismiss='modal'
- >
- Cancel
- </button>
- <button
- onClick={this.handleConfirm}
- type='button'
- className='btn btn-primary'
- >
- {this.props.confirm_button}
- </button>
- </div>
- </div>
- </div>
- </div>
+ <Modal.Header closeButton={false}>
+ <Modal.Title>{this.props.title}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ {this.props.message}
+ </Modal.Body>
+ <Modal.Footer>
+ <button
+ type='button'
+ className='btn btn-default'
+ onClick={this.props.onCancel}
+ >
+ {'Cancel'}
+ </button>
+ <button
+ type='button'
+ className='btn btn-primary'
+ onClick={this.props.onConfirm}
+ >
+ {this.props.confirm_button}
+ </button>
+ </Modal.Footer>
+ </Modal>
);
}
}
ConfirmModal.defaultProps = {
- parent_id: '',
- id: '',
title: '',
message: '',
confirm_button: ''
};
ConfirmModal.propTypes = {
- parent_id: React.PropTypes.string,
- id: React.PropTypes.string,
+ show: React.PropTypes.bool.isRequired,
title: React.PropTypes.string,
message: React.PropTypes.string,
- confirm_button: React.PropTypes.string
+ confirm_button: React.PropTypes.string,
+ onConfirm: React.PropTypes.func.isRequired,
+ onCancel: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 1545cdfaa..5a69c9bfb 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -50,7 +50,7 @@ export default class CreatePost extends React.Component {
PostStore.clearDraftUploads();
const draft = this.getCurrentDraft();
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
this.state = {
channelId: ChannelStore.getCurrentId(),
@@ -236,8 +236,6 @@ export default class CreatePost extends React.Component {
PostStore.storeCurrentDraft(draft);
}
resizePostHolder() {
- const height = this.state.windowHeight - $(ReactDOM.findDOMNode(this.refs.topDiv)).height() - 50;
- $('.post-list-holder-by-time').css('height', `${height}px`);
if (this.state.windowWidth > 960) {
$('#post_textbox').focus();
}
@@ -338,7 +336,7 @@ export default class CreatePost extends React.Component {
}
}
onPreferenceChange() {
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
this.setState({
showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.POST_POPOVER,
ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value
diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx
index 5b3c74e82..2557a55ca 100644
--- a/web/react/components/edit_channel_modal.jsx
+++ b/web/react/components/edit_channel_modal.jsx
@@ -12,6 +12,7 @@ export default class EditChannelModal extends React.Component {
this.handleUserInput = this.handleUserInput.bind(this);
this.handleClose = this.handleClose.bind(this);
this.onShow = this.onShow.bind(this);
+ this.handleShown = this.handleShown.bind(this);
this.state = {
header: '',
@@ -55,9 +56,13 @@ export default class EditChannelModal extends React.Component {
const button = e.relatedTarget;
this.setState({header: $(button).attr('data-header'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''});
}
+ handleShown() {
+ $('#edit_channel #edit_header').focus();
+ }
componentDidMount() {
$(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
$(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
+ $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.handleShown);
}
componentWillUnmount() {
$(ReactDOM.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose);
@@ -114,6 +119,7 @@ export default class EditChannelModal extends React.Component {
<textarea
className='form-control no-resize'
rows='6'
+ id='edit_header'
maxLength='1024'
value={this.state.header}
onChange={this.handleUserInput}
diff --git a/web/react/components/edit_channel_purpose_modal.jsx b/web/react/components/edit_channel_purpose_modal.jsx
index 4d162cfe7..4cb96a3ff 100644
--- a/web/react/components/edit_channel_purpose_modal.jsx
+++ b/web/react/components/edit_channel_purpose_modal.jsx
@@ -15,6 +15,12 @@ export default class EditChannelPurposeModal extends React.Component {
this.state = {serverError: ''};
}
+ componentDidUpdate() {
+ if (this.props.show) {
+ $(ReactDOM.findDOMNode(this.refs.purpose)).focus();
+ }
+ }
+
handleHide() {
this.setState({serverError: ''});
@@ -77,6 +83,7 @@ export default class EditChannelPurposeModal extends React.Component {
return (
<Modal
className='modal-edit-channel-purpose'
+ ref='modal'
show={this.props.show}
onHide={this.handleHide}
>
diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx
index bea700725..c09477a69 100644
--- a/web/react/components/invite_member_modal.jsx
+++ b/web/react/components/invite_member_modal.jsx
@@ -2,55 +2,50 @@
// See License.txt for license information.
var utils = require('../utils/utils.jsx');
+var ActionTypes = require('../utils/constants.jsx').ActionTypes;
+var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Client = require('../utils/client.jsx');
+var ModalStore = require('../stores/modal_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
var ConfirmModal = require('./confirm_modal.jsx');
+const Modal = ReactBootstrap.Modal;
+
export default class InviteMemberModal extends React.Component {
constructor(props) {
super(props);
+ this.handleToggle = this.handleToggle.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
+ this.handleHide = this.handleHide.bind(this);
this.addInviteFields = this.addInviteFields.bind(this);
this.clearFields = this.clearFields.bind(this);
this.removeInviteFields = this.removeInviteFields.bind(this);
this.state = {
+ show: false,
inviteIds: [0],
idCount: 0,
emailErrors: {},
firstNameErrors: {},
lastNameErrors: {},
- emailEnabled: global.window.mm_config.SendEmailNotifications === 'true'
+ emailEnabled: global.window.mm_config.SendEmailNotifications === 'true',
+ showConfirmModal: false
};
}
componentDidMount() {
- var self = this;
- $('#invite_member').on('hide.bs.modal', function hide(e) {
- if ($('#invite_member').attr('data-confirm') === 'true') {
- $('#invite_member').attr('data-confirm', 'false');
- return;
- }
-
- var notEmpty = false;
- for (var i = 0; i < self.state.inviteIds.length; i++) {
- var index = self.state.inviteIds[i];
- if (ReactDOM.findDOMNode(self.refs['email' + index]).value.trim() !== '') {
- notEmpty = true;
- break;
- }
- }
+ ModalStore.addModalListener(ActionTypes.TOGGLE_INVITE_MEMBER_MODAL, this.handleToggle);
+ }
- if (notEmpty) {
- $('#confirm_invite_modal').modal('show');
- e.preventDefault();
- }
- });
+ componentWillUnmount() {
+ ModalStore.removeModalListener(ActionTypes.TOGGLE_INVITE_MEMBER_MODAL, this.handleToggle);
+ }
- $('#invite_member').on('hidden.bs.modal', function show() {
- self.clearFields();
+ handleToggle(value) {
+ this.setState({
+ show: value
});
}
@@ -94,25 +89,57 @@ export default class InviteMemberModal extends React.Component {
var data = {};
data.invites = invites;
- Client.inviteMembers(data,
- function success() {
- $(ReactDOM.findDOMNode(this.refs.modal)).attr('data-confirm', 'true');
- $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
- }.bind(this),
- function fail(err) {
+ Client.inviteMembers(
+ data,
+ () => {
+ this.handleHide(false);
+ },
+ (err) => {
if (err.message === 'This person is already on your team') {
emailErrors[err.detailed_error] = err.message;
this.setState({emailErrors: emailErrors});
} else {
this.setState({serverError: err.message});
}
- }.bind(this)
+ }
);
}
- componentDidUpdate() {
- $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200);
- $(ReactDOM.findDOMNode(this.refs.modalBody)).css('overflow-y', 'scroll');
+ handleHide(requireConfirm) {
+ if (requireConfirm) {
+ var notEmpty = false;
+ for (var i = 0; i < this.state.inviteIds.length; i++) {
+ var index = this.state.inviteIds[i];
+ if (ReactDOM.findDOMNode(this.refs['email' + index]).value.trim() !== '') {
+ notEmpty = true;
+ break;
+ }
+ }
+
+ if (notEmpty) {
+ this.setState({
+ showConfirmModal: true
+ });
+
+ return;
+ }
+ }
+
+ this.clearFields();
+
+ this.setState({
+ show: false,
+ showConfirmModal: false
+ });
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (!prevState.show && this.state.show) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 300);
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
+ }
+ }
}
addInviteFields() {
@@ -292,7 +319,7 @@ export default class InviteMemberModal extends React.Component {
);
} else {
var teamInviteLink = null;
- if (currentUser && this.props.teamType === 'O') {
+ if (currentUser && TeamStore.getCurrent().type === 'O') {
var linkUrl = utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + TeamStore.getCurrent().invite_id;
var link =
(
@@ -302,11 +329,7 @@ export default class InviteMemberModal extends React.Component {
data-target='#get_link'
data-title='Team Invite'
data-value={linkUrl}
- onClick={
- function click() {
- $('#invite_member').modal('hide');
- }
- }
+ onClick={() => this.handleHide(this, false)}
>Team Invite Link</a>
);
@@ -327,64 +350,54 @@ export default class InviteMemberModal extends React.Component {
return (
<div>
- <div
- className='modal fade'
- ref='modal'
- id='invite_member'
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
+ <Modal
+ className='modal-invite-member'
+ show={this.state.show}
+ onHide={this.handleHide.bind(this, true)}
+ enforceFocus={!this.state.showConfirmModal}
>
- <div className='modal-dialog'>
- <div className='modal-content'>
- <div className='modal-header'>
+ <Modal.Header closeButton={true}>
+ <Modal.Title>{'Invite New Member'}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body ref='modalBody'>
+ <form role='form'>
+ {inviteSections}
+ </form>
+ {content}
+ </Modal.Body>
+ <Modal.Footer>
<button
type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
+ className='btn btn-default'
+ onClick={this.handleHide.bind(this, true)}
>
- <span aria-hidden='true'>×</span>
+ {'Cancel'}
</button>
- <h4
- className='modal-title'
- id='myModalLabel'
- >Invite New Member</h4>
- </div>
- <div
- ref='modalBody'
- className='modal-body'
- >
- <form role='form'>
- {inviteSections}
- </form>
- {content}
- </div>
- <div className='modal-footer'>
- <button
- type='button'
- className='btn btn-default'
- data-dismiss='modal'
- >Cancel</button>
- {sendButton}
- </div>
- </div>
- </div>
- </div>
+ {sendButton}
+ </Modal.Footer>
+ </Modal>
<ConfirmModal
- id='confirm_invite_modal'
- parent_id='invite_member'
title='Discard Invitations?'
message='You have unsent invitations, are you sure you want to discard them?'
confirm_button='Yes, Discard'
+ show={this.state.showConfirmModal}
+ onConfirm={this.handleHide.bind(this, false)}
+ onCancel={() => this.setState({showConfirmModal: false})}
/>
</div>
);
}
- return <div/>;
+
+ return null;
+ }
+
+ static show() {
+ AppDispatcher.handleViewAction({
+ type: ActionTypes.TOGGLE_INVITE_MEMBER_MODAL,
+ value: true
+ });
}
}
InviteMemberModal.propTypes = {
- teamType: React.PropTypes.string
};
diff --git a/web/react/components/member_list.jsx b/web/react/components/member_list.jsx
index 70eb0a500..0238c7920 100644
--- a/web/react/components/member_list.jsx
+++ b/web/react/components/member_list.jsx
@@ -15,28 +15,28 @@ export default class MemberList extends React.Component {
members = this.props.memberList;
}
- var message = '';
+ var message = null;
if (members.length === 0) {
- message = <span>No users to add.</span>;
+ message = <tr><td>No users to add.</td></tr>;
}
return (
<table className='table more-table member-list-holder'>
<tbody>
- {members.map(function mymembers(member) {
- return (
- <MemberListItem
- key={member.id}
- member={member}
- isAdmin={this.props.isAdmin}
- handleInvite={this.props.handleInvite}
- handleRemove={this.props.handleRemove}
- handleMakeAdmin={this.props.handleMakeAdmin}
- />
- );
- }, this)}
+ {members.map(function mymembers(member) {
+ return (
+ <MemberListItem
+ key={member.id}
+ member={member}
+ isAdmin={this.props.isAdmin}
+ handleInvite={this.props.handleInvite}
+ handleRemove={this.props.handleRemove}
+ handleMakeAdmin={this.props.handleMakeAdmin}
+ />
+ );
+ }, this)}
+ {message}
</tbody>
- {message}
</table>
);
}
diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx
index 14db05cdb..1fa369068 100644
--- a/web/react/components/member_list_team_item.jsx
+++ b/web/react/components/member_list_team_item.jsx
@@ -166,40 +166,42 @@ export default class MemberListTeamItem extends React.Component {
}
return (
- <div className='row member-div'>
- <img
- className='post-profile-img pull-left'
- src={`/api/v1/users/${user.id}/image?time=${timestamp}&${Utils.getSessionIndex()}`}
- height='36'
- width='36'
- />
- <span className='member-name'>{Utils.getDisplayName(user)}</span>
- <span className='member-email'>{email}</span>
- <div className='dropdown member-drop'>
- <a
- href='#'
- className='dropdown-toggle theme'
- type='button'
- id='channel_header_dropdown'
- data-toggle='dropdown'
- aria-expanded='true'
- >
- <span>{currentRoles} </span>
- <span className='caret'></span>
- </a>
- <ul
- className='dropdown-menu member-menu'
- role='menu'
- aria-labelledby='channel_header_dropdown'
- >
- {makeAdmin}
- {makeMember}
- {makeActive}
- {makeNotActive}
- </ul>
- </div>
- {serverError}
- </div>
+ <tr>
+ <td className='row member-div'>
+ <img
+ className='post-profile-img pull-left'
+ src={`/api/v1/users/${user.id}/image?time=${timestamp}&${Utils.getSessionIndex()}`}
+ height='36'
+ width='36'
+ />
+ <span className='member-name'>{Utils.getDisplayName(user)}</span>
+ <span className='member-email'>{email}</span>
+ <div className='dropdown member-drop'>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ id='channel_header_dropdown'
+ data-toggle='dropdown'
+ aria-expanded='true'
+ >
+ <span>{currentRoles} </span>
+ <span className='caret'></span>
+ </a>
+ <ul
+ className='dropdown-menu member-menu'
+ role='menu'
+ aria-labelledby='channel_header_dropdown'
+ >
+ {makeAdmin}
+ {makeMember}
+ {makeActive}
+ {makeNotActive}
+ </ul>
+ </div>
+ {serverError}
+ </td>
+ </tr>
);
}
}
diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx
index f7778f25f..ff53816c7 100644
--- a/web/react/components/navbar.jsx
+++ b/web/react/components/navbar.jsx
@@ -1,22 +1,26 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var Client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var TeamStore = require('../stores/team_store.jsx');
-var MessageWrapper = require('./message_wrapper.jsx');
-var NotifyCounts = require('./notify_counts.jsx');
const EditChannelPurposeModal = require('./edit_channel_purpose_modal.jsx');
+const MessageWrapper = require('./message_wrapper.jsx');
+const NotifyCounts = require('./notify_counts.jsx');
+const ChannelMembersModal = require('./channel_members_modal.jsx');
+const ChannelInviteModal = require('./channel_invite_modal.jsx');
+
+const UserStore = require('../stores/user_store.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
+const TeamStore = require('../stores/team_store.jsx');
+
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
const Utils = require('../utils/utils.jsx');
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var Popover = ReactBootstrap.Popover;
-var OverlayTrigger = ReactBootstrap.OverlayTrigger;
+const Popover = ReactBootstrap.Popover;
+const OverlayTrigger = ReactBootstrap.OverlayTrigger;
export default class Navbar extends React.Component {
constructor(props) {
@@ -29,6 +33,8 @@ export default class Navbar extends React.Component {
const state = this.getStateFromStores();
state.showEditChannelPurposeModal = false;
+ state.showMembersModal = false;
+ state.showInviteModal = false;
this.state = state;
}
getStateFromStores() {
@@ -45,17 +51,18 @@ export default class Navbar extends React.Component {
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChange);
+ ChannelStore.removeExtraInfoChangeListener(this.onChange);
}
handleSubmit(e) {
e.preventDefault();
}
handleLeave() {
Client.leaveChannel(this.state.channel.id,
- function success() {
+ () => {
AsyncClient.getChannels(true);
window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square';
},
- function error(err) {
+ (err) => {
AsyncClient.dispatchError(err, 'handleLeave');
}
);
@@ -104,7 +111,7 @@ export default class Navbar extends React.Component {
data-channelid={channel.id}
href='#'
>
- View Info
+ {'View Info'}
</a>
</li>
);
@@ -120,7 +127,7 @@ export default class Navbar extends React.Component {
data-title={channel.display_name}
data-channelid={channel.id}
>
- Set Channel Header...
+ {'Set Channel Header...'}
</a>
</li>
);
@@ -145,11 +152,10 @@ export default class Navbar extends React.Component {
<li role='presentation'>
<a
role='menuitem'
- data-toggle='modal'
- data-target='#channel_invite'
href='#'
+ onClick={() => this.setState({showInviteModal: false})}
>
- Add Members
+ {'Add Members'}
</a>
</li>
);
@@ -161,7 +167,7 @@ export default class Navbar extends React.Component {
href='#'
onClick={this.handleLeave}
>
- Leave Channel
+ {'Leave Channel'}
</a>
</li>
);
@@ -175,11 +181,10 @@ export default class Navbar extends React.Component {
<li role='presentation'>
<a
role='menuitem'
- data-toggle='modal'
- data-target='#channel_members'
href='#'
+ onClick={() => this.setState({showMembersModal: true})}
>
- Manage Members
+ {'Manage Members'}
</a>
</li>
);
@@ -195,7 +200,7 @@ export default class Navbar extends React.Component {
data-name={channel.name}
data-channelid={channel.id}
>
- Rename Channel...
+ {'Rename Channel...'}
</a>
</li>
);
@@ -210,7 +215,7 @@ export default class Navbar extends React.Component {
data-title={channel.display_name}
data-channelid={channel.id}
>
- Delete Channel...
+ {'Delete Channel...'}
</a>
</li>
);
@@ -228,7 +233,7 @@ export default class Navbar extends React.Component {
data-title={channel.display_name}
data-channelid={channel.id}
>
- Notification Preferences
+ {'Notification Preferences'}
</a>
</li>
);
@@ -299,7 +304,7 @@ export default class Navbar extends React.Component {
data-toggle='collapse'
data-target='#navbar-collapse-1'
>
- <span className='sr-only'>Toggle sidebar</span>
+ <span className='sr-only'>{'Toggle sidebar'}</span>
<span className='icon-bar'></span>
<span className='icon-bar'></span>
<span className='icon-bar'></span>
@@ -315,7 +320,7 @@ export default class Navbar extends React.Component {
data-target='#sidebar-nav'
onClick={this.toggleLeftSidebar}
>
- <span className='sr-only'>Toggle sidebar</span>
+ <span className='sr-only'>{'Toggle sidebar'}</span>
<span className='icon-bar'></span>
<span className='icon-bar'></span>
<span className='icon-bar'></span>
@@ -426,6 +431,14 @@ export default class Navbar extends React.Component {
onModalDismissed={() => this.setState({showEditChannelPurposeModal: false})}
channel={channel}
/>
+ <ChannelMembersModal
+ show={this.state.showMembersModal}
+ onModalDismissed={() => this.setState({showMembersModal: false})}
+ />
+ <ChannelInviteModal
+ show={this.state.showInviteModal}
+ onModalDismissed={() => this.setState({showInviteModal: false})}
+ />
</div>
);
}
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
index 029b9c137..0b755f377 100644
--- a/web/react/components/navbar_dropdown.jsx
+++ b/web/react/components/navbar_dropdown.jsx
@@ -7,6 +7,8 @@ var UserStore = require('../stores/user_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
var AboutBuildModal = require('./about_build_modal.jsx');
+var InviteMemberModal = require('./invite_member_modal.jsx');
+var UserSettingsModal = require('./user_settings/user_settings_modal.jsx');
var Constants = require('../utils/constants.jsx');
@@ -33,7 +35,10 @@ export default class NavbarDropdown extends React.Component {
this.onListenerChange = this.onListenerChange.bind(this);
this.aboutModalDismissed = this.aboutModalDismissed.bind(this);
- this.state = getStateFromStores();
+ const state = getStateFromStores();
+ state.showUserSettingsModal = false;
+ state.showAboutModal = false;
+ this.state = state;
}
handleLogoutClick(e) {
e.preventDefault();
@@ -88,8 +93,7 @@ export default class NavbarDropdown extends React.Component {
<li>
<a
href='#'
- data-toggle='modal'
- data-target='#invite_member'
+ onClick={InviteMemberModal.show}
>
{'Invite New Member'}
</a>
@@ -210,8 +214,7 @@ export default class NavbarDropdown extends React.Component {
<li>
<a
href='#'
- data-toggle='modal'
- data-target='#user_settings'
+ onClick={() => this.setState({showUserSettingsModal: true})}
>
{'Account Settings'}
</a>
@@ -256,6 +259,10 @@ export default class NavbarDropdown extends React.Component {
{'About Mattermost'}
</a>
</li>
+ <UserSettingsModal
+ show={this.state.showUserSettingsModal}
+ onModalDismissed={() => this.setState({showUserSettingsModal: false})}
+ />
<AboutBuildModal
show={this.state.showAboutModal}
onModalDismissed={this.aboutModalDismissed}
diff --git a/web/react/components/posts_view_container.jsx b/web/react/components/posts_view_container.jsx
index 7671ca01d..761664602 100644
--- a/web/react/components/posts_view_container.jsx
+++ b/web/react/components/posts_view_container.jsx
@@ -81,7 +81,7 @@ export default class PostsViewContainer extends React.Component {
}
}
onChannelChange() {
- const postLists = Object.assign({}, this.state.postLists);
+ const postLists = this.state.postLists.slice();
const channels = this.state.channels.slice();
const channelId = ChannelStore.getCurrentId();
@@ -112,7 +112,7 @@ export default class PostsViewContainer extends React.Component {
postLists});
}
onChannelLeave(id) {
- const postLists = Object.assign({}, this.state.postLists);
+ const postLists = this.state.postLists.slice();
const channels = this.state.channels.slice();
const index = channels.indexOf(id);
if (index !== -1) {
@@ -123,7 +123,7 @@ export default class PostsViewContainer extends React.Component {
}
onPostsChange() {
const channels = this.state.channels;
- const postLists = Object.assign({}, this.state.postLists);
+ const postLists = this.state.postLists.slice();
const newPostsView = this.getChannelPosts(channels[this.state.currentChannelIndex]);
postLists[this.state.currentChannelIndex] = newPostsView;
@@ -261,7 +261,7 @@ export default class PostsViewContainer extends React.Component {
}
return (
- <div>{postListCtls}</div>
+ <div id='post-list'>{postListCtls}</div>
);
}
}
diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx
index 80f0956f2..9fb3af035 100644
--- a/web/react/components/rename_channel_modal.jsx
+++ b/web/react/components/rename_channel_modal.jsx
@@ -16,6 +16,7 @@ export default class RenameChannelModal extends React.Component {
this.displayNameKeyUp = this.displayNameKeyUp.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handleShow = this.handleShow.bind(this);
+ this.handleShown = this.handleShown.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
@@ -118,9 +119,13 @@ export default class RenameChannelModal extends React.Component {
const button = $(e.relatedTarget);
this.setState({displayName: button.attr('data-display'), channelName: button.attr('data-name'), channelId: button.attr('data-channelid')});
}
+ handleShown() {
+ $('#rename_channel #display_name').focus();
+ }
componentDidMount() {
$(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.handleShow);
$(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
+ $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.handleShown);
}
componentWillUnmount() {
$(ReactDOM.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose);
@@ -176,6 +181,7 @@ export default class RenameChannelModal extends React.Component {
onChange={this.onDisplayNameChange}
type='text'
ref='displayName'
+ id='display_name'
className='form-control'
placeholder='Enter display name'
value={this.state.displayName}
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index aab9919a4..f5ce5c10e 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -20,6 +20,7 @@ const Utils = require('../utils/utils.jsx');
const Constants = require('../utils/constants.jsx');
const Preferences = Constants.Preferences;
const TutorialSteps = Constants.TutorialSteps;
+const NotificationPrefs = Constants.NotificationPrefs;
const Tooltip = ReactBootstrap.Tooltip;
const OverlayTrigger = ReactBootstrap.OverlayTrigger;
@@ -76,6 +77,8 @@ export default class Sidebar extends React.Component {
if (ch.type === 'D') {
chMentionCount = chUnreadCount;
chUnreadCount = 0;
+ } else if (chMember.notify_props && chMember.notify_props.mark_unread === NotificationPrefs.MENTION) {
+ chUnreadCount = 0;
}
channelUnreadCounts[ch.id] = {msgs: chUnreadCount, mentions: chMentionCount};
@@ -143,7 +146,7 @@ export default class Sidebar extends React.Component {
visibleDirectChannels.sort(this.sortChannelsByDisplayName);
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
return {
activeId: currentChannelId,
@@ -362,7 +365,7 @@ export default class Sidebar extends React.Component {
var unread = false;
if (channelMember) {
msgCount = unreadCount.msgs + unreadCount.mentions;
- unread = (msgCount > 0 && channelMember.notify_props.mark_unread !== 'mention') || channelMember.mention_count > 0;
+ unread = msgCount > 0 || channelMember.mention_count > 0;
}
if (unread) {
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index 46730e1e6..bc7f6ba50 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -31,7 +31,7 @@ export default class SidebarHeader extends React.Component {
PreferenceStore.removeChangeListener(this.onPreferenceChange);
}
getStateFromStores() {
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
return {showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.MENU_POPOVER};
}
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index 9350bbd42..2135e3ef3 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+var InviteMemberModal = require('./invite_member_modal.jsx');
+var UserSettingsModal = require('./user_settings/user_settings_modal.jsx');
var UserStore = require('../stores/user_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
var client = require('../utils/client.jsx');
@@ -15,6 +17,10 @@ export default class SidebarRightMenu extends React.Component {
super(props);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
+
+ this.state = {
+ showUserSettingsModal: false
+ };
}
handleLogoutClick(e) {
@@ -38,10 +44,12 @@ export default class SidebarRightMenu extends React.Component {
inviteLink = (
<li>
- <a href='#'
- data-toggle='modal'
- data-target='#invite_member'
- ><i className='glyphicon glyphicon-user'></i>Invite New Member</a>
+ <a
+ href='#'
+ onClick={InviteMemberModal.show}
+ >
+ <i className='glyphicon glyphicon-user'></i>Invite New Member
+ </a>
</li>
);
@@ -115,9 +123,11 @@ export default class SidebarRightMenu extends React.Component {
<li>
<a
href='#'
- data-toggle='modal'
- data-target='#user_settings'
- ><i className='glyphicon glyphicon-cog'></i>Account Settings</a></li>
+ onClick={() => this.setState({showUserSettingsModal: true})}
+ >
+ <i className='glyphicon glyphicon-cog'></i>Account Settings
+ </a>
+ </li>
{teamSettingsLink}
{inviteLink}
{teamLink}
@@ -141,6 +151,10 @@ export default class SidebarRightMenu extends React.Component {
><i className='glyphicon glyphicon-earphone'></i>Report a Problem</a></li>
</ul>
</div>
+ <UserSettingsModal
+ show={this.state.showUserSettingsModal}
+ onModalDismissed={() => this.setState({showUserSettingsModal: false})}
+ />
</div>
);
}
diff --git a/web/react/components/team_members.jsx b/web/react/components/team_members.jsx
index 33590c89a..ac1ebf52d 100644
--- a/web/react/components/team_members.jsx
+++ b/web/react/components/team_members.jsx
@@ -79,7 +79,7 @@ export default class TeamMembers extends React.Component {
return (
<div
- className='modal fade'
+ className='modal fade more-modal'
ref='modal'
id='team_members'
tabIndex='-1'
@@ -106,12 +106,10 @@ export default class TeamMembers extends React.Component {
ref='modalBody'
className='modal-body'
>
- <div className='channel-settings'>
- <div className='team-member-list'>
- {renderMembers}
- </div>
- {serverError}
+ <div className='team-member-list'>
+ {renderMembers}
</div>
+ {serverError}
</div>
<div className='modal-footer'>
<button
diff --git a/web/react/components/team_settings_modal.jsx b/web/react/components/team_settings_modal.jsx
index 17fe31c65..4d47db2a8 100644
--- a/web/react/components/team_settings_modal.jsx
+++ b/web/react/components/team_settings_modal.jsx
@@ -17,11 +17,13 @@ export default class TeamSettingsModal extends React.Component {
};
}
componentDidMount() {
- $('body').on('click', '.modal-back', function handleBackClick() {
+ const modal = $(ReactDOM.findDOMNode(this.refs.modal));
+
+ modal.on('click', '.modal-back', function handleBackClick() {
$(this).closest('.modal-dialog').removeClass('display--content');
$(this).closest('.modal-dialog').find('.settings-table .nav li.active').removeClass('active');
});
- $('body').on('click', '.modal-header .close', () => {
+ modal.on('click', '.modal-header .close', () => {
setTimeout(() => {
$('.modal-dialog.display--content').removeClass('display--content');
}, 500);
diff --git a/web/react/components/tutorial/tutorial_tip.jsx b/web/react/components/tutorial/tutorial_tip.jsx
index c85acb346..3094b2f4c 100644
--- a/web/react/components/tutorial/tutorial_tip.jsx
+++ b/web/react/components/tutorial/tutorial_tip.jsx
@@ -98,7 +98,7 @@ export default class TutorialTip extends React.Component {
<div className='tutorial__circles'>{dots}</div>
<div className='text-right'>
<button
- className='btn btn-default'
+ className='btn btn-primary'
onClick={this.handleNext}
>
{buttonText}
diff --git a/web/react/components/user_settings/code_theme_chooser.jsx b/web/react/components/user_settings/code_theme_chooser.jsx
deleted file mode 100644
index eef4b24ba..000000000
--- a/web/react/components/user_settings/code_theme_chooser.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-var Constants = require('../../utils/constants.jsx');
-
-export default class CodeThemeChooser extends React.Component {
- constructor(props) {
- super(props);
- this.state = {};
- }
- render() {
- const theme = this.props.theme;
-
- const premadeThemes = [];
- for (const k in Constants.CODE_THEMES) {
- if (Constants.CODE_THEMES.hasOwnProperty(k)) {
- let activeClass = '';
- if (k === theme.codeTheme) {
- activeClass = 'active';
- }
-
- premadeThemes.push(
- <div
- className='col-xs-6 col-sm-3 premade-themes'
- key={'premade-theme-key' + k}
- >
- <div
- className={activeClass}
- onClick={() => this.props.updateTheme(k)}
- >
- <label>
- <img
- className='img-responsive'
- src={'/static/images/themes/code_themes/' + k + '.png'}
- />
- <div className='theme-label'>{Constants.CODE_THEMES[k]}</div>
- </label>
- </div>
- </div>
- );
- }
- }
-
- return (
- <div className='row'>
- {premadeThemes}
- </div>
- );
- }
-}
-
-CodeThemeChooser.propTypes = {
- theme: React.PropTypes.object.isRequired,
- updateTheme: React.PropTypes.func.isRequired
-};
diff --git a/web/react/components/user_settings/custom_theme_chooser.jsx b/web/react/components/user_settings/custom_theme_chooser.jsx
index 095e5b622..895d0c500 100644
--- a/web/react/components/user_settings/custom_theme_chooser.jsx
+++ b/web/react/components/user_settings/custom_theme_chooser.jsx
@@ -55,28 +55,70 @@ export default class CustomThemeChooser extends React.Component {
const elements = [];
let colors = '';
Constants.THEME_ELEMENTS.forEach((element, index) => {
- elements.push(
- <div
- className='col-sm-4 form-group'
- key={'custom-theme-key' + index}
- >
- <label className='custom-label'>{element.uiName}</label>
+ if (element.id === 'codeTheme') {
+ const codeThemeOptions = [];
+
+ element.themes.forEach((codeTheme, codeThemeIndex) => {
+ codeThemeOptions.push(
+ <option
+ key={'code-theme-key' + codeThemeIndex}
+ value={codeTheme.id}
+ >
+ {codeTheme.uiName}
+ </option>
+ );
+ });
+
+ elements.push(
<div
- className='input-group color-picker'
- id={element.id}
+ className='col-sm-4 form-group'
+ key={'custom-theme-key' + index}
>
- <input
- className='form-control'
- type='text'
- defaultValue={theme[element.id]}
- onChange={this.onInputChange}
- />
- <span className='input-group-addon'><i></i></span>
+ <label className='custom-label'>{element.uiName}</label>
+ <div
+ className='input-group theme-group dropdown'
+ id={element.id}
+ >
+ <select
+ className='form-control'
+ type='text'
+ defaultValue={theme[element.id]}
+ onChange={this.onInputChange}
+ >
+ {codeThemeOptions}
+ </select>
+ <span className='input-group-addon'>
+ <img
+ src={'/static/images/themes/code_themes/' + theme[element.id] + '.png'}
+ />
+ </span>
+ </div>
</div>
- </div>
- );
+ );
+ } else {
+ elements.push(
+ <div
+ className='col-sm-4 form-group'
+ key={'custom-theme-key' + index}
+ >
+ <label className='custom-label'>{element.uiName}</label>
+ <div
+ className='input-group color-picker'
+ id={element.id}
+ >
+ <input
+ className='form-control'
+ type='text'
+ defaultValue={theme[element.id]}
+ onChange={this.onInputChange}
+ />
+ <span className='input-group-addon'><i></i></span>
+ </div>
+ </div>
+ );
- colors += theme[element.id] + ',';
+ colors += theme[element.id] + ',';
+ }
});
colors += theme.codeTheme;
@@ -87,6 +129,7 @@ export default class CustomThemeChooser extends React.Component {
{'Copy and paste to share theme colors:'}
</label>
<input
+ readOnly='true'
type='text'
className='form-control'
value={colors}
diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx
index 1a9ac0ad3..4d594bb1b 100644
--- a/web/react/components/user_settings/import_theme_modal.jsx
+++ b/web/react/components/user_settings/import_theme_modal.jsx
@@ -1,6 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+const ModalStore = require('../../stores/modal_store.jsx');
const UserStore = require('../../stores/user_store.jsx');
const Utils = require('../../utils/utils.jsx');
const Client = require('../../utils/client.jsx');
@@ -24,10 +25,10 @@ export default class ImportThemeModal extends React.Component {
};
}
componentDidMount() {
- UserStore.addImportModalListener(this.updateShow);
+ ModalStore.addModalListener(ActionTypes.TOGGLE_IMPORT_THEME_MODAL, this.updateShow);
}
componentWillUnmount() {
- UserStore.removeImportModalListener(this.updateShow);
+ ModalStore.removeModalListener(ActionTypes.TOGGLE_IMPORT_THEME_MODAL, this.updateShow);
}
updateShow(show) {
this.setState({show});
@@ -49,7 +50,7 @@ export default class ImportThemeModal extends React.Component {
theme.sidebarText = colors[5];
theme.sidebarUnreadText = colors[5];
theme.sidebarTextHoverBg = colors[4];
- theme.sidebarTextActiveBg = colors[2];
+ theme.sidebarTextActiveBorder = colors[2];
theme.sidebarTextActiveColor = colors[3];
theme.sidebarHeaderBg = colors[1];
theme.sidebarHeaderTextColor = colors[5];
@@ -58,9 +59,13 @@ export default class ImportThemeModal extends React.Component {
theme.mentionColor = '#ffffff';
theme.centerChannelBg = '#ffffff';
theme.centerChannelColor = '#333333';
+ theme.newMessageSeparator = '#F80';
theme.linkColor = '#2389d7';
theme.buttonBg = '#26a970';
theme.buttonColor = '#ffffff';
+ theme.mentionHighlightBg = '#fff2bb';
+ theme.mentionHighlightLink = '#2f81b7';
+ theme.codeTheme = 'github';
let user = UserStore.getCurrentUser();
user.theme_props = theme;
@@ -74,7 +79,6 @@ export default class ImportThemeModal extends React.Component {
this.setState({show: false});
Utils.applyTheme(theme);
- $('#user_settings').modal('show');
},
(err) => {
var state = this.getStateFromStores();
diff --git a/web/react/components/user_settings/user_settings.jsx b/web/react/components/user_settings/user_settings.jsx
index 546e26ca3..e089ce973 100644
--- a/web/react/components/user_settings/user_settings.jsx
+++ b/web/react/components/user_settings/user_settings.jsx
@@ -16,6 +16,7 @@ export default class UserSettings extends React.Component {
constructor(props) {
super(props);
+ this.getActiveTab = this.getActiveTab.bind(this);
this.onListenerChange = this.onListenerChange.bind(this);
this.state = {user: UserStore.getCurrentUser()};
@@ -29,10 +30,14 @@ export default class UserSettings extends React.Component {
UserStore.removeChangeListener(this.onListenerChange);
}
+ getActiveTab() {
+ return this.refs.activeTab;
+ }
+
onListenerChange() {
var user = UserStore.getCurrentUser();
if (!utils.areStatesEqual(this.state.user, user)) {
- this.setState({user: user});
+ this.setState({user});
}
}
@@ -41,10 +46,13 @@ export default class UserSettings extends React.Component {
return (
<div>
<GeneralTab
+ ref='activeTab'
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
/>
</div>
);
@@ -52,10 +60,14 @@ export default class UserSettings extends React.Component {
return (
<div>
<SecurityTab
+ ref='activeTab'
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
+ setEnforceFocus={this.props.setEnforceFocus}
/>
</div>
);
@@ -63,10 +75,13 @@ export default class UserSettings extends React.Component {
return (
<div>
<NotificationsTab
+ ref='activeTab'
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
/>
</div>
);
@@ -74,9 +89,14 @@ export default class UserSettings extends React.Component {
return (
<div>
<AppearanceTab
+ ref='activeTab'
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
+ setEnforceFocus={this.props.setEnforceFocus}
+ setRequireConfirm={this.props.setRequireConfirm}
/>
</div>
);
@@ -84,8 +104,11 @@ export default class UserSettings extends React.Component {
return (
<div>
<DeveloperTab
+ ref='activeTab'
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
/>
</div>
);
@@ -93,10 +116,13 @@ export default class UserSettings extends React.Component {
return (
<div>
<IntegrationsTab
+ ref='activeTab'
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
/>
</div>
);
@@ -104,10 +130,13 @@ export default class UserSettings extends React.Component {
return (
<div>
<DisplayTab
+ ref='activeTab'
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
/>
</div>
);
@@ -115,10 +144,13 @@ export default class UserSettings extends React.Component {
return (
<div>
<AdvancedTab
+ ref='activeTab'
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
/>
</div>
);
@@ -132,5 +164,9 @@ UserSettings.propTypes = {
activeTab: React.PropTypes.string,
activeSection: React.PropTypes.string,
updateSection: React.PropTypes.func,
- updateTab: React.PropTypes.func
+ updateTab: React.PropTypes.func,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired,
+ setEnforceFocus: React.PropTypes.func.isRequired,
+ setRequireConfirm: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_advanced.jsx b/web/react/components/user_settings/user_settings_advanced.jsx
index 910444735..2616981ba 100644
--- a/web/react/components/user_settings/user_settings_advanced.jsx
+++ b/web/react/components/user_settings/user_settings_advanced.jsx
@@ -13,7 +13,6 @@ export default class AdvancedSettingsDisplay extends React.Component {
this.updateSection = this.updateSection.bind(this);
this.updateSetting = this.updateSetting.bind(this);
- this.handleClose = this.handleClose.bind(this);
this.setupInitialState = this.setupInitialState.bind(this);
this.state = this.setupInitialState();
@@ -59,18 +58,6 @@ export default class AdvancedSettingsDisplay extends React.Component {
this.props.updateSection(section);
}
- handleClose() {
- this.updateSection('');
- }
-
- componentDidMount() {
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
- }
-
- componentWillUnmount() {
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- }
-
render() {
const serverError = this.state.serverError || null;
let ctrlSendSection;
@@ -139,6 +126,7 @@ export default class AdvancedSettingsDisplay extends React.Component {
className='close'
data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
</button>
@@ -146,7 +134,10 @@ export default class AdvancedSettingsDisplay extends React.Component {
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
{'Advanced Settings'}
</h4>
</div>
@@ -165,5 +156,7 @@ AdvancedSettingsDisplay.propTypes = {
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
- activeSection: React.PropTypes.string
+ activeSection: React.PropTypes.string,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx
index 7b4b54e27..d73b5f476 100644
--- a/web/react/components/user_settings/user_settings_appearance.jsx
+++ b/web/react/components/user_settings/user_settings_appearance.jsx
@@ -7,7 +7,6 @@ var Utils = require('../../utils/utils.jsx');
const CustomThemeChooser = require('./custom_theme_chooser.jsx');
const PremadeThemeChooser = require('./premade_theme_chooser.jsx');
-const CodeThemeChooser = require('./code_theme_chooser.jsx');
const AppDispatcher = require('../../dispatcher/app_dispatcher.jsx');
const Constants = require('../../utils/constants.jsx');
const ActionTypes = Constants.ActionTypes;
@@ -19,14 +18,13 @@ export default class UserSettingsAppearance extends React.Component {
this.onChange = this.onChange.bind(this);
this.submitTheme = this.submitTheme.bind(this);
this.updateTheme = this.updateTheme.bind(this);
- this.updateCodeTheme = this.updateCodeTheme.bind(this);
- this.handleClose = this.handleClose.bind(this);
+ this.deactivate = this.deactivate.bind(this);
+ this.resetFields = this.resetFields.bind(this);
this.handleImportModal = this.handleImportModal.bind(this);
this.state = this.getStateFromStores();
- this.originalTheme = this.state.theme;
- this.originalCodeTheme = this.state.theme.codeTheme;
+ this.originalTheme = Object.assign({}, this.state.theme);
}
componentDidMount() {
UserStore.addChangeListener(this.onChange);
@@ -34,7 +32,6 @@ export default class UserSettingsAppearance extends React.Component {
if (this.props.activeSection === 'theme') {
$(ReactDOM.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
}
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
}
componentDidUpdate() {
if (this.props.activeSection === 'theme') {
@@ -44,14 +41,13 @@ export default class UserSettingsAppearance extends React.Component {
}
componentWillUnmount() {
UserStore.removeChangeListener(this.onChange);
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
}
getStateFromStores() {
const user = UserStore.getCurrentUser();
let theme = null;
if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) {
- theme = user.theme_props;
+ theme = Object.assign({}, user.theme_props);
} else {
theme = $.extend(true, {}, Constants.THEMES.default);
}
@@ -73,6 +69,8 @@ export default class UserSettingsAppearance extends React.Component {
if (!Utils.areStatesEqual(this.state, newState)) {
this.setState(newState);
}
+
+ this.props.setEnforceFocus(true);
}
submitTheme(e) {
e.preventDefault();
@@ -86,11 +84,11 @@ export default class UserSettingsAppearance extends React.Component {
me: data
});
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- this.props.updateTab('general');
+ this.props.setRequireConfirm(false);
+ this.originalTheme = Object.assign({}, this.state.theme);
+
$('.ps-container.modal-body').scrollTop(0);
$('.ps-container.modal-body').perfectScrollbar('update');
- $('#user_settings').modal('hide');
},
(err) => {
var state = this.getStateFromStores();
@@ -100,40 +98,47 @@ export default class UserSettingsAppearance extends React.Component {
);
}
updateTheme(theme) {
- if (!theme.codeTheme) {
- theme.codeTheme = this.state.theme.codeTheme;
+ let themeChanged = this.state.theme.length === theme.length;
+ if (!themeChanged) {
+ for (const field in theme) {
+ if (theme.hasOwnProperty(field)) {
+ if (this.state.theme[field] !== theme[field]) {
+ themeChanged = true;
+ break;
+ }
+ }
+ }
}
- this.setState({theme});
- Utils.applyTheme(theme);
- }
- updateCodeTheme(codeTheme) {
- var theme = this.state.theme;
- theme.codeTheme = codeTheme;
+
+ this.props.setRequireConfirm(themeChanged);
+
this.setState({theme});
Utils.applyTheme(theme);
}
updateType(type) {
this.setState({type});
}
- handleClose() {
+ deactivate() {
const state = this.getStateFromStores();
- state.serverError = null;
- state.theme.codeTheme = this.originalCodeTheme;
Utils.applyTheme(state.theme);
-
+ }
+ resetFields() {
+ const state = this.getStateFromStores();
+ state.serverError = null;
this.setState(state);
- $('.ps-container.modal-body').scrollTop(0);
- $('.ps-container.modal-body').perfectScrollbar('update');
- $('#user_settings').modal('hide');
+ Utils.applyTheme(state.theme);
+
+ this.props.setRequireConfirm(false);
}
handleImportModal() {
- $('#user_settings').modal('hide');
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_IMPORT_THEME_MODAL,
value: true
});
+
+ this.props.setEnforceFocus(false);
}
render() {
var serverError;
@@ -187,12 +192,6 @@ export default class UserSettingsAppearance extends React.Component {
</div>
{custom}
<hr />
- <strong className='radio'>{'Code Theme'}</strong>
- <CodeThemeChooser
- theme={this.state.theme}
- updateTheme={this.updateCodeTheme}
- />
- <hr />
{serverError}
<a
className='btn btn-sm btn-primary'
@@ -204,7 +203,7 @@ export default class UserSettingsAppearance extends React.Component {
<a
className='btn btn-sm theme'
href='#'
- onClick={this.handleClose}
+ onClick={this.resetFields}
>
{'Cancel'}
</a>
@@ -218,8 +217,8 @@ export default class UserSettingsAppearance extends React.Component {
<button
type='button'
className='close'
- data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
</button>
@@ -227,7 +226,11 @@ export default class UserSettingsAppearance extends React.Component {
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>{'Appearance Settings'}
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
+ {'Appearance Settings'}
</h4>
</div>
<div className='user-settings'>
@@ -253,5 +256,9 @@ UserSettingsAppearance.defaultProps = {
};
UserSettingsAppearance.propTypes = {
activeSection: React.PropTypes.string,
- updateTab: React.PropTypes.func
+ updateTab: React.PropTypes.func,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired,
+ setRequireConfirm: React.PropTypes.func.isRequired,
+ setEnforceFocus: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_developer.jsx b/web/react/components/user_settings/user_settings_developer.jsx
index c2d7a9710..e6adba1d4 100644
--- a/web/react/components/user_settings/user_settings_developer.jsx
+++ b/web/react/components/user_settings/user_settings_developer.jsx
@@ -63,6 +63,7 @@ export default class DeveloperTab extends React.Component {
className='close'
data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
</button>
@@ -70,7 +71,11 @@ export default class DeveloperTab extends React.Component {
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>{'Developer Settings'}
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
+ {'Developer Settings'}
</h4>
</div>
<div className='user-settings'>
@@ -89,5 +94,7 @@ DeveloperTab.defaultProps = {
};
DeveloperTab.propTypes = {
activeSection: React.PropTypes.string,
- updateSection: React.PropTypes.func
+ updateSection: React.PropTypes.func,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx
index d086c78a9..43c8d33d1 100644
--- a/web/react/components/user_settings/user_settings_display.jsx
+++ b/web/react/components/user_settings/user_settings_display.jsx
@@ -25,7 +25,6 @@ export default class UserSettingsDisplay extends React.Component {
this.handleClockRadio = this.handleClockRadio.bind(this);
this.handleNameRadio = this.handleNameRadio.bind(this);
this.updateSection = this.updateSection.bind(this);
- this.handleClose = this.handleClose.bind(this);
this.state = getDisplayStateFromStores();
}
@@ -53,15 +52,6 @@ export default class UserSettingsDisplay extends React.Component {
this.setState(getDisplayStateFromStores());
this.props.updateSection(section);
}
- handleClose() {
- this.updateSection('');
- }
- componentDidMount() {
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
- }
- componentWillUnmount() {
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- }
render() {
const serverError = this.state.serverError || null;
let clockSection;
@@ -182,13 +172,13 @@ export default class UserSettingsDisplay extends React.Component {
</label>
<br/>
</div>
- <div><br/>{'How should other users be shown in Direct Messages list?'}</div>
+ <div><br/>{'Set what name to display in the Direct Messages list.'}</div>
</div>
];
nameFormatSection = (
<SettingItemMax
- title='Show real names, nick names or usernames?'
+ title='Teammate Name Display'
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
@@ -210,7 +200,7 @@ export default class UserSettingsDisplay extends React.Component {
nameFormatSection = (
<SettingItemMin
- title='Show real names, nick names or usernames?'
+ title='Teammate Name Display'
describe={describe}
updateSection={() => {
this.props.updateSection('name_format');
@@ -227,6 +217,7 @@ export default class UserSettingsDisplay extends React.Component {
className='close'
data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
</button>
@@ -234,7 +225,10 @@ export default class UserSettingsDisplay extends React.Component {
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
{'Display Settings'}
</h4>
</div>
@@ -255,5 +249,7 @@ UserSettingsDisplay.propTypes = {
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
- activeSection: React.PropTypes.string
+ activeSection: React.PropTypes.string,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx
index 3adac197a..9f0c16194 100644
--- a/web/react/components/user_settings/user_settings_general.jsx
+++ b/web/react/components/user_settings/user_settings_general.jsx
@@ -32,7 +32,6 @@ export default class UserSettingsGeneralTab extends React.Component {
this.updatePicture = this.updatePicture.bind(this);
this.updateSection = this.updateSection.bind(this);
- this.handleClose = this.handleClose.bind(this);
this.setupInitialState = this.setupInitialState.bind(this);
this.state = this.setupInitialState(props);
@@ -210,20 +209,6 @@ export default class UserSettingsGeneralTab extends React.Component {
this.submitActive = false;
this.props.updateSection(section);
}
- handleClose() {
- $(ReactDOM.findDOMNode(this)).find('.form-control').each(function clearForms() {
- this.value = '';
- });
-
- this.setState(assign({}, this.setupInitialState(this.props), {clientError: null, serverError: null, emailError: null}));
- this.props.updateSection('');
- }
- componentDidMount() {
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
- }
- componentWillUnmount() {
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- }
setupInitialState(props) {
var user = props.user;
@@ -579,6 +564,7 @@ export default class UserSettingsGeneralTab extends React.Component {
className='close'
data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
</button>
@@ -586,7 +572,10 @@ export default class UserSettingsGeneralTab extends React.Component {
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
{'General Settings'}
</h4>
</div>
@@ -613,5 +602,7 @@ UserSettingsGeneralTab.propTypes = {
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
- activeSection: React.PropTypes.string
+ activeSection: React.PropTypes.string,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_integrations.jsx b/web/react/components/user_settings/user_settings_integrations.jsx
index 4a9915a1f..744a6beea 100644
--- a/web/react/components/user_settings/user_settings_integrations.jsx
+++ b/web/react/components/user_settings/user_settings_integrations.jsx
@@ -11,24 +11,12 @@ export default class UserSettingsIntegrationsTab extends React.Component {
super(props);
this.updateSection = this.updateSection.bind(this);
- this.handleClose = this.handleClose.bind(this);
this.state = {};
}
updateSection(section) {
this.props.updateSection(section);
}
- handleClose() {
- this.updateSection('');
- $('.ps-container.modal-body').scrollTop(0);
- $('.ps-container.modal-body').perfectScrollbar('update');
- }
- componentDidMount() {
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
- }
- componentWillUnmount() {
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- }
render() {
let incomingHooksSection;
let outgoingHooksSection;
@@ -104,6 +92,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
className='close'
data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
</button>
@@ -111,7 +100,10 @@ export default class UserSettingsIntegrationsTab extends React.Component {
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
{'Integration Settings'}
</h4>
</div>
@@ -132,5 +124,7 @@ UserSettingsIntegrationsTab.propTypes = {
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
- activeSection: React.PropTypes.string
+ activeSection: React.PropTypes.string,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx
index 18dd490e7..4dcf32cb9 100644
--- a/web/react/components/user_settings/user_settings_modal.jsx
+++ b/web/react/components/user_settings/user_settings_modal.jsx
@@ -1,34 +1,161 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var SettingsSidebar = require('../settings_sidebar.jsx');
-var UserSettings = require('./user_settings.jsx');
+const ConfirmModal = require('../confirm_modal.jsx');
+const Modal = ReactBootstrap.Modal;
+const SettingsSidebar = require('../settings_sidebar.jsx');
+const UserSettings = require('./user_settings.jsx');
export default class UserSettingsModal extends React.Component {
constructor(props) {
super(props);
+ this.handleHide = this.handleHide.bind(this);
+ this.handleHidden = this.handleHidden.bind(this);
+ this.handleCollapse = this.handleCollapse.bind(this);
+ this.handleConfirm = this.handleConfirm.bind(this);
+ this.handleCancelConfirmation = this.handleCancelConfirmation.bind(this);
+
+ this.deactivateTab = this.deactivateTab.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.collapseModal = this.collapseModal.bind(this);
+
this.updateTab = this.updateTab.bind(this);
this.updateSection = this.updateSection.bind(this);
- this.state = {active_tab: 'general', active_section: ''};
+ this.state = {
+ active_tab: 'general',
+ active_section: '',
+ showConfirmModal: false,
+ enforceFocus: true
+ };
+
+ this.requireConfirm = false;
+ }
+
+ componentDidUpdate(prevProps) {
+ if (!prevProps.show && this.props.show) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 300);
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
+ }
+ }
+ }
+
+ // Called when the close button is pressed on the main modal
+ handleHide() {
+ if (this.requireConfirm) {
+ this.afterConfirm = () => this.handleHide();
+ this.showConfirmModal();
+
+ return false;
+ }
+
+ this.deactivateTab();
+ this.props.onModalDismissed();
}
- componentDidMount() {
- $('body').on('click', '.modal-back', function changeDisplay() {
- $(this).closest('.modal-dialog').removeClass('display--content');
+
+ // called after the dialog is fully hidden and faded out
+ handleHidden() {
+ this.setState({
+ active_tab: 'general',
+ active_section: ''
});
- $('body').on('click', '.modal-header .close', () => {
- setTimeout(() => {
- $('.modal-dialog.display--content').removeClass('display--content');
- }, 500);
+ }
+
+ // Called to hide the settings pane when on mobile
+ handleCollapse() {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).closest('.modal-dialog').removeClass('display--content');
+
+ this.deactivateTab();
+
+ this.setState({
+ active_tab: '',
+ active_section: ''
});
}
- updateTab(tab) {
- this.setState({active_tab: tab});
+
+ handleConfirm() {
+ this.setState({
+ showConfirmModal: false,
+ enforceFocus: true
+ });
+
+ this.requireConfirm = false;
+
+ if (this.afterConfirm) {
+ this.afterConfirm();
+ this.afterConfirm = null;
+ }
}
- updateSection(section) {
- this.setState({active_section: section});
+
+ handleCancelConfirmation() {
+ this.setState({
+ showConfirmModal: false,
+ enforceFocus: true
+ });
+
+ this.afterConfirm = null;
}
+
+ showConfirmModal(afterConfirm) {
+ this.setState({
+ showConfirmModal: true,
+ enforceFocus: false
+ });
+
+ if (afterConfirm) {
+ this.afterConfirm = afterConfirm;
+ }
+ }
+
+ // Called to let settings tab perform cleanup before being closed
+ deactivateTab() {
+ const activeTab = this.refs.userSettings.getActiveTab();
+ if (activeTab && activeTab.deactivate) {
+ activeTab.deactivate();
+ }
+ }
+
+ // Called by settings tabs when their close button is pressed
+ closeModal() {
+ if (this.requireConfirm) {
+ this.showConfirmModal(this.closeModal);
+ } else {
+ this.handleHide();
+ }
+ }
+
+ // Called by settings tabs when their back button is pressed
+ collapseModal() {
+ if (this.requireConfirm) {
+ this.showConfirmModal(this.collapseModal);
+ } else {
+ this.handleCollapse();
+ }
+ }
+
+ updateTab(tab, skipConfirm) {
+ if (!skipConfirm && this.requireConfirm) {
+ this.showConfirmModal(() => this.updateTab(tab, true));
+ } else {
+ this.deactivateTab();
+
+ this.setState({
+ active_tab: tab,
+ active_section: ''
+ });
+ }
+ }
+
+ updateSection(section, skipConfirm) {
+ if (!skipConfirm && this.requireConfirm) {
+ this.showConfirmModal(() => this.updateSection(section, true));
+ } else {
+ this.setState({active_section: section});
+ }
+ }
+
render() {
var tabs = [];
tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'});
@@ -46,33 +173,17 @@ export default class UserSettingsModal extends React.Component {
tabs.push({name: 'advanced', uiName: 'Advanced', icon: 'glyphicon glyphicon-list-alt'});
return (
- <div
- className='modal fade'
- ref='modal'
- id='user_settings'
- role='dialog'
- tabIndex='-1'
- aria-hidden='true'
+ <Modal
+ dialogClassName='settings-modal'
+ show={this.props.show}
+ onHide={this.handleHide}
+ onExited={this.handleHidden}
+ enforceFocus={this.state.enforceFocus}
>
- <div className='modal-dialog settings-modal'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>{'×'}</span>
- </button>
- <h4
- className='modal-title'
- ref='title'
- >
- {'Account Settings'}
- </h4>
- </div>
- <div className='modal-body'>
+ <Modal.Header closeButton={true}>
+ <Modal.Title>{'Account Settings'}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body ref='modalBody'>
<div className='settings-table'>
<div className='settings-links'>
<SettingsSidebar
@@ -83,17 +194,33 @@ export default class UserSettingsModal extends React.Component {
</div>
<div className='settings-content minimize-settings'>
<UserSettings
+ ref='userSettings'
activeTab={this.state.active_tab}
activeSection={this.state.active_section}
updateSection={this.updateSection}
updateTab={this.updateTab}
+ closeModal={this.closeModal}
+ collapseModal={this.collapseModal}
+ setEnforceFocus={(enforceFocus) => this.setState({enforceFocus})}
+ setRequireConfirm={(requireConfirm) => this.requireConfirm = requireConfirm}
/>
</div>
</div>
- </div>
- </div>
- </div>
- </div>
+ </Modal.Body>
+ <ConfirmModal
+ title='Discard Changes?'
+ message='You have unsaved changes, are you sure you want to discard them?'
+ confirm_button='Yes, Discard'
+ show={this.state.showConfirmModal}
+ onConfirm={this.handleConfirm}
+ onCancel={this.handleCancelConfirmation}
+ />
+ </Modal>
);
}
}
+
+UserSettingsModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onModalDismissed: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx
index 2b904763c..c6f47804f 100644
--- a/web/react/components/user_settings/user_settings_notifications.jsx
+++ b/web/react/components/user_settings/user_settings_notifications.jsx
@@ -7,7 +7,6 @@ var SettingItemMax = require('../setting_item_max.jsx');
var client = require('../../utils/client.jsx');
var AsyncClient = require('../../utils/async_client.jsx');
var utils = require('../../utils/utils.jsx');
-var assign = require('object-assign');
function getNotificationsStateFromStores() {
var user = UserStore.getCurrentUser();
@@ -77,7 +76,6 @@ export default class NotificationsTab extends React.Component {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
- this.handleClose = this.handleClose.bind(this);
this.updateSection = this.updateSection.bind(this);
this.onListenerChange = this.onListenerChange.bind(this);
this.handleNotifyRadio = this.handleNotifyRadio.bind(this);
@@ -128,27 +126,15 @@ export default class NotificationsTab extends React.Component {
}.bind(this)
);
}
- handleClose() {
- $(ReactDOM.findDOMNode(this)).find('.form-control').each(function clearField() {
- this.value = '';
- });
-
- this.setState(assign({}, getNotificationsStateFromStores(), {serverError: null}));
-
- this.props.updateTab('general');
- }
updateSection(section) {
this.setState(getNotificationsStateFromStores());
this.props.updateSection(section);
}
componentDidMount() {
UserStore.addChangeListener(this.onListenerChange);
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
}
componentWillUnmount() {
UserStore.removeChangeListener(this.onListenerChange);
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- this.props.updateSection('');
}
onListenerChange() {
var newState = getNotificationsStateFromStores();
@@ -644,15 +630,19 @@ export default class NotificationsTab extends React.Component {
className='close'
data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
- <span aria-hidden='true'>&times;</span>
+ <span aria-hidden='true'>{'×'}</span>
</button>
<h4
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>
- Notifications
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
+ {'Notification Settings'}
</h4>
</div>
<div
@@ -686,5 +676,7 @@ NotificationsTab.propTypes = {
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
activeSection: React.PropTypes.string,
- activeTab: React.PropTypes.string
+ activeTab: React.PropTypes.string,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx
index 983a10df0..61d13ed8b 100644
--- a/web/react/components/user_settings/user_settings_security.jsx
+++ b/web/react/components/user_settings/user_settings_security.jsx
@@ -3,6 +3,8 @@
var SettingItemMin = require('../setting_item_min.jsx');
var SettingItemMax = require('../setting_item_max.jsx');
+var AccessHistoryModal = require('../access_history_modal.jsx');
+var ActivityLogModal = require('../activity_log_modal.jsx');
var Client = require('../../utils/client.jsx');
var AsyncClient = require('../../utils/async_client.jsx');
var Constants = require('../../utils/constants.jsx');
@@ -11,14 +13,34 @@ export default class SecurityTab extends React.Component {
constructor(props) {
super(props);
+ this.showAccessHistoryModal = this.showAccessHistoryModal.bind(this);
+ this.showActivityLogModal = this.showActivityLogModal.bind(this);
+ this.hideModals = this.hideModals.bind(this);
this.submitPassword = this.submitPassword.bind(this);
this.updateCurrentPassword = this.updateCurrentPassword.bind(this);
this.updateNewPassword = this.updateNewPassword.bind(this);
this.updateConfirmPassword = this.updateConfirmPassword.bind(this);
- this.handleClose = this.handleClose.bind(this);
this.setupInitialState = this.setupInitialState.bind(this);
- this.state = this.setupInitialState();
+ const state = this.setupInitialState();
+ state.showAccessHistoryModal = false;
+ state.showActivityLogModal = false;
+ this.state = state;
+ }
+ showAccessHistoryModal() {
+ this.props.setEnforceFocus(false);
+ this.setState({showAccessHistoryModal: true});
+ }
+ showActivityLogModal() {
+ this.props.setEnforceFocus(false);
+ this.setState({showActivityLogModal: true});
+ }
+ hideModals() {
+ this.props.setEnforceFocus(true);
+ this.setState({
+ showAccessHistoryModal: false,
+ showActivityLogModal: false
+ });
}
submitPassword(e) {
e.preventDefault();
@@ -75,30 +97,9 @@ export default class SecurityTab extends React.Component {
updateConfirmPassword(e) {
this.setState({confirmPassword: e.target.value});
}
- handleHistoryOpen() {
- $('#user_settings').modal('hide');
- }
- handleDevicesOpen() {
- $('#user_settings').modal('hide');
- }
- handleClose() {
- $(ReactDOM.findDOMNode(this)).find('.form-control').each(function resetValue() {
- this.value = '';
- });
- this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
-
- this.props.updateTab('general');
- }
setupInitialState() {
return {currentPassword: '', newPassword: '', confirmPassword: ''};
}
- componentDidMount() {
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
- }
- componentWillUnmount() {
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- this.props.updateSection('');
- }
render() {
var serverError;
if (this.state.serverError) {
@@ -236,14 +237,19 @@ export default class SecurityTab extends React.Component {
className='close'
data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
- <span aria-hidden='true'>&times;</span>
+ <span aria-hidden='true'>{'×'}</span>
</button>
<h4
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>Security Settings
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
+ {'Security Settings'}
</h4>
</div>
<div className='user-settings'>
@@ -253,25 +259,29 @@ export default class SecurityTab extends React.Component {
<div className='divider-dark'/>
<br></br>
<a
- data-toggle='modal'
className='security-links theme'
- data-target='#access-history'
href='#'
- onClick={this.handleHistoryOpen}
+ onClick={this.showAccessHistoryModal}
>
<i className='fa fa-clock-o'></i>View Access History
</a>
<b> </b>
<a
- data-toggle='modal'
className='security-links theme'
- data-target='#activity-log'
href='#'
- onClick={this.handleDevicesOpen}
+ onClick={this.showActivityLogModal}
>
<i className='fa fa-globe'></i>View and Logout of Active Sessions
</a>
</div>
+ <AccessHistoryModal
+ show={this.state.showAccessHistoryModal}
+ onModalDismissed={this.hideModals}
+ />
+ <ActivityLogModal
+ show={this.state.showActivityLogModal}
+ onModalDismissed={this.hideModals}
+ />
</div>
);
}
@@ -285,5 +295,8 @@ SecurityTab.propTypes = {
user: React.PropTypes.object,
activeSection: React.PropTypes.string,
updateSection: React.PropTypes.func,
- updateTab: React.PropTypes.func
+ updateTab: React.PropTypes.func,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired,
+ setEnforceFocus: React.PropTypes.func.isRequired
};
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index 067dcde50..8781d52a5 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -9,7 +9,6 @@ var ErrorStore = require('../stores/error_store.jsx');
var MentionList = require('../components/mention_list.jsx');
var GetLinkModal = require('../components/get_link_modal.jsx');
-var MemberInviteModal = require('../components/invite_member_modal.jsx');
var EditChannelModal = require('../components/edit_channel_modal.jsx');
var DeleteChannelModal = require('../components/delete_channel_modal.jsx');
var RenameChannelModal = require('../components/rename_channel_modal.jsx');
@@ -18,17 +17,13 @@ var DeletePostModal = require('../components/delete_post_modal.jsx');
var MoreChannelsModal = require('../components/more_channels.jsx');
var PostDeletedModal = require('../components/post_deleted_modal.jsx');
var ChannelNotificationsModal = require('../components/channel_notifications.jsx');
-var UserSettingsModal = require('../components/user_settings/user_settings_modal.jsx');
var TeamSettingsModal = require('../components/team_settings_modal.jsx');
-var ChannelMembersModal = require('../components/channel_members.jsx');
-var ChannelInviteModal = require('../components/channel_invite_modal.jsx');
var TeamMembersModal = require('../components/team_members.jsx');
var ChannelInfoModal = require('../components/channel_info_modal.jsx');
-var AccessHistoryModal = require('../components/access_history_modal.jsx');
-var ActivityLogModal = require('../components/activity_log_modal.jsx');
var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx');
var RegisterAppModal = require('../components/register_app_modal.jsx');
var ImportThemeModal = require('../components/user_settings/import_theme_modal.jsx');
+var InviteMemberModal = require('../components/invite_member_modal.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var Constants = require('../utils/constants.jsx');
@@ -83,8 +78,8 @@ function setupChannelPage(props) {
);
ReactDOM.render(
- <UserSettingsModal />,
- document.getElementById('user_settings_modal')
+ <InviteMemberModal />,
+ document.getElementById('invite_member_modal')
);
ReactDOM.render(
@@ -103,11 +98,6 @@ function setupChannelPage(props) {
);
ReactDOM.render(
- <MemberInviteModal teamType={props.TeamType} />,
- document.getElementById('invite_member_modal')
- );
-
- ReactDOM.render(
<EditChannelModal />,
document.getElementById('edit_channel_modal')
);
@@ -128,16 +118,6 @@ function setupChannelPage(props) {
);
ReactDOM.render(
- <ChannelMembersModal />,
- document.getElementById('channel_members_modal')
- );
-
- ReactDOM.render(
- <ChannelInviteModal />,
- document.getElementById('channel_invite_modal')
- );
-
- ReactDOM.render(
<ChannelInfoModal />,
document.getElementById('channel_info_modal')
);
@@ -163,16 +143,6 @@ function setupChannelPage(props) {
);
ReactDOM.render(
- <AccessHistoryModal />,
- document.getElementById('access_history_modal')
- );
-
- ReactDOM.render(
- <ActivityLogModal />,
- document.getElementById('activity_log_modal')
- );
-
- ReactDOM.render(
<RemovedFromChannelModal />,
document.getElementById('removed_from_channel_modal')
);
diff --git a/web/react/stores/modal_store.jsx b/web/react/stores/modal_store.jsx
new file mode 100644
index 000000000..dc65d48da
--- /dev/null
+++ b/web/react/stores/modal_store.jsx
@@ -0,0 +1,42 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const EventEmitter = require('events').EventEmitter;
+
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
+
+class ModalStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.addModalListener = this.addModalListener.bind(this);
+ this.removeModalListener = this.removeModalListener.bind(this);
+
+ this.handleEventPayload = this.handleEventPayload.bind(this);
+ this.dispatchToken = AppDispatcher.register(this.handleEventPayload);
+ }
+
+ addModalListener(action, callback) {
+ this.on(action, callback);
+ }
+
+ removeModalListener(action, callback) {
+ this.removeListener(action, callback);
+ }
+
+ handleEventPayload(payload) {
+ const action = payload.action;
+
+ switch (action.type) {
+ case ActionTypes.TOGGLE_IMPORT_THEME_MODAL:
+ case ActionTypes.TOGGLE_INVITE_MEMBER_MODAL:
+ this.emit(action.type, action.value);
+ break;
+ }
+ }
+}
+
+const ModalStore = new ModalStoreClass();
+export default ModalStore;
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index aedb3dc09..4fa7224b7 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -13,7 +13,6 @@ var CHANGE_EVENT_SESSIONS = 'change_sessions';
var CHANGE_EVENT_AUDITS = 'change_audits';
var CHANGE_EVENT_TEAMS = 'change_teams';
var CHANGE_EVENT_STATUSES = 'change_statuses';
-var TOGGLE_IMPORT_MODAL_EVENT = 'toggle_import_modal';
class UserStoreClass extends EventEmitter {
constructor() {
@@ -34,9 +33,6 @@ class UserStoreClass extends EventEmitter {
this.emitStatusesChange = this.emitStatusesChange.bind(this);
this.addStatusesChangeListener = this.addStatusesChangeListener.bind(this);
this.removeStatusesChangeListener = this.removeStatusesChangeListener.bind(this);
- this.emitToggleImportModal = this.emitToggleImportModal.bind(this);
- this.addImportModalListener = this.addImportModalListener.bind(this);
- this.removeImportModalListener = this.removeImportModalListener.bind(this);
this.getCurrentId = this.getCurrentId.bind(this);
this.getCurrentUser = this.getCurrentUser.bind(this);
this.setCurrentUser = this.setCurrentUser.bind(this);
@@ -124,18 +120,6 @@ class UserStoreClass extends EventEmitter {
this.removeListener(CHANGE_EVENT_STATUSES, callback);
}
- emitToggleImportModal(value) {
- this.emit(TOGGLE_IMPORT_MODAL_EVENT, value);
- }
-
- addImportModalListener(callback) {
- this.on(TOGGLE_IMPORT_MODAL_EVENT, callback);
- }
-
- removeImportModalListener(callback) {
- this.removeListener(TOGGLE_IMPORT_MODAL_EVENT, callback);
- }
-
getCurrentUser() {
if (this.getProfiles()[global.window.mm_user.id] == null) {
this.saveProfile(global.window.mm_user);
@@ -353,10 +337,6 @@ UserStore.dispatchToken = AppDispatcher.register((payload) => {
UserStore.pSetStatuses(action.statuses);
UserStore.emitStatusesChange();
break;
- case ActionTypes.TOGGLE_IMPORT_THEME_MODAL:
- UserStore.emitToggleImportModal(action.value);
- break;
-
default:
}
});
diff --git a/web/react/utils/channel_intro_mssages.jsx b/web/react/utils/channel_intro_mssages.jsx
index b3f868456..161c79761 100644
--- a/web/react/utils/channel_intro_mssages.jsx
+++ b/web/react/utils/channel_intro_mssages.jsx
@@ -3,6 +3,7 @@
// See License.txt for license information.
const Utils = require('./utils.jsx');
+const InviteMemberModal = require('../components/invite_member_modal.jsx');
const UserProfile = require('../components/user_profile.jsx');
const ChannelStore = require('../stores/channel_store.jsx');
const Constants = require('../utils/constants.jsx');
@@ -109,8 +110,7 @@ export function createDefaultIntroMessage(channel) {
<a
className='intro-links'
href='#'
- data-toggle='modal'
- data-target='#invite_member'
+ onClick={InviteMemberModal.show}
>
<i className='fa fa-user-plus'></i>{'Invite others to this team'}
</a>
@@ -213,6 +213,7 @@ export function createStandardIntroMessage(channel) {
>
<i className='fa fa-user-plus'></i>{'Invite others to this ' + uiType}
</a>
+
</div>
);
}
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 39be577df..58ee8e2d2 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -39,7 +39,8 @@ module.exports = {
RECIEVED_LOGS: null,
RECIEVED_ALL_TEAMS: null,
- TOGGLE_IMPORT_THEME_MODAL: null
+ TOGGLE_IMPORT_THEME_MODAL: null,
+ TOGGLE_INVITE_MEMBER_MODAL: null
}),
PayloadSources: keyMirror({
@@ -158,7 +159,8 @@ module.exports = {
buttonBg: '#2389d7',
buttonColor: '#FFFFFF',
mentionHighlightBg: '#fff2bb',
- mentionHighlightLink: '#2f81b7'
+ mentionHighlightLink: '#2f81b7',
+ codeTheme: 'github'
},
organization: {
type: 'Organization',
@@ -180,7 +182,8 @@ module.exports = {
buttonBg: '#1dacfc',
buttonColor: '#FFFFFF',
mentionHighlightBg: '#fff2bb',
- mentionHighlightLink: '#2f81b7'
+ mentionHighlightLink: '#2f81b7',
+ codeTheme: 'github'
},
mattermostDark: {
type: 'Mattermost Dark',
@@ -202,7 +205,8 @@ module.exports = {
buttonBg: '#4CBBA4',
buttonColor: '#FFFFFF',
mentionHighlightBg: '#984063',
- mentionHighlightLink: '#A4FFEB'
+ mentionHighlightLink: '#A4FFEB',
+ codeTheme: 'solarized_dark'
},
windows10: {
type: 'Windows Dark',
@@ -224,7 +228,8 @@ module.exports = {
buttonBg: '#0177e7',
buttonColor: '#FFFFFF',
mentionHighlightBg: '#784098',
- mentionHighlightLink: '#A4FFEB'
+ mentionHighlightLink: '#A4FFEB',
+ codeTheme: 'monokai'
}
},
THEME_ELEMENTS: [
@@ -303,14 +308,30 @@ module.exports = {
{
id: 'mentionHighlightLink',
uiName: 'Mention Highlight Link'
+ },
+ {
+ id: 'codeTheme',
+ uiName: 'Code Theme',
+ themes: [
+ {
+ id: 'solarized_dark',
+ uiName: 'Solarized Dark'
+ },
+ {
+ id: 'solarized_light',
+ uiName: 'Solarized Light'
+ },
+ {
+ id: 'github',
+ uiName: 'GitHub'
+ },
+ {
+ id: 'monokai',
+ uiName: 'Monokai'
+ }
+ ]
}
],
- CODE_THEMES: {
- github: 'GitHub',
- solarized_light: 'Solarized light',
- monokai: 'Monokai',
- solarized_dark: 'Solarized Dark'
- },
DEFAULT_CODE_THEME: 'github',
Preferences: {
CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show',
@@ -363,5 +384,8 @@ module.exports = {
BOTTOM: 1,
POST: 2,
SIDEBAR_OPEN: 3
+ },
+ NotificationPrefs: {
+ MENTION: 'mention'
}
};
diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx
index 2de858a17..ac26107cc 100644
--- a/web/react/utils/text_formatting.jsx
+++ b/web/react/utils/text_formatting.jsx
@@ -64,22 +64,6 @@ export function doFormatText(text, options) {
return output;
}
-export function doFormatEmoticons(text) {
- const tokens = new Map();
-
- let output = Emoticons.handleEmoticons(text, tokens);
- output = replaceTokens(output, tokens);
-
- return output;
-}
-
-export function doFormatMentions(text) {
- const tokens = new Map();
- let output = autolinkAtMentions(text, tokens);
- output = replaceTokens(output, tokens);
- return output;
-}
-
export function sanitizeHtml(text) {
let output = text;
@@ -182,11 +166,15 @@ function autolinkAtMentions(text, tokens) {
}
let output = text;
- output = output.replace(/(^|\s)(@([a-z0-9.\-_]*))/gi, replaceAtMentionWithToken);
+ output = output.replace(/(^|[^a-z0-9])(@([a-z0-9.\-_]*))/gi, replaceAtMentionWithToken);
return output;
}
+function escapeRegex(text) {
+ return text.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+}
+
function highlightCurrentMentions(text, tokens) {
let output = text;
@@ -226,7 +214,7 @@ function highlightCurrentMentions(text, tokens) {
}
for (const mention of UserStore.getCurrentMentionKeys()) {
- output = output.replace(new RegExp(`(^|\\W)(${mention})\\b`, 'gi'), replaceCurrentMentionWithToken);
+ output = output.replace(new RegExp(`(^|\\W)(${escapeRegex(mention)})\\b`, 'gi'), replaceCurrentMentionWithToken);
}
return output;
@@ -306,7 +294,7 @@ function highlightSearchTerm(text, tokens, searchTerm) {
return prefix + alias;
}
- return output.replace(new RegExp(`()(${searchTerm})`, 'gi'), replaceSearchTermWithToken);
+ return output.replace(new RegExp(`()(${escapeRegex(searchTerm)})`, 'gi'), replaceSearchTermWithToken);
}
function replaceTokens(text, tokens) {
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index e8d34dccd..fff9c460b 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -151,10 +151,14 @@ export function notifyMe(title, body, channel) {
}
}
+var canDing = true;
+
export function ding() {
- if (!isBrowserFirefox()) {
+ if (!isBrowserFirefox() && canDing) {
var audio = new Audio('/static/images/ding.mp3');
audio.play();
+ canDing = false;
+ setTimeout(() => canDing = true, 3000);
}
}
@@ -438,11 +442,6 @@ export function toTitleCase(str) {
}
export function applyTheme(theme) {
- if (!theme.codeTheme) {
- theme.codeTheme = Constants.DEFAULT_CODE_THEME;
- }
- updateCodeTheme(theme.codeTheme);
-
if (theme.sidebarBg) {
changeCss('.sidebar--left, .settings-modal .settings-table .settings-links, .sidebar--menu', 'background:' + theme.sidebarBg, 1);
}
@@ -533,7 +532,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, .loading-screen .loading__content .round, .tutorial__circles .circle, .tip-overlay .tutorial__circles .circle.active', 'background:' + theme.centerChannelColor, 1);
+ changeCss('.post-body hr, .loading-screen .loading__content .round, .tutorial__circles .circle', '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);
@@ -598,6 +597,11 @@ export function applyTheme(theme) {
if (theme.mentionHighlightLink) {
changeCss('.mention-highlight .mention-link', 'color:' + theme.mentionHighlightLink, 1);
}
+
+ if (!theme.codeTheme) {
+ theme.codeTheme = Constants.DEFAULT_CODE_THEME;
+ }
+ updateCodeTheme(theme.codeTheme);
}
export function changeCss(className, classValue, classRepeat) {
// we need invisible container to store additional css definitions
diff --git a/web/sass-files/sass/partials/_access-history.scss b/web/sass-files/sass/partials/_access-history.scss
index a3289ecc0..c8a0b28bd 100644
--- a/web/sass-files/sass/partials/_access-history.scss
+++ b/web/sass-files/sass/partials/_access-history.scss
@@ -19,10 +19,6 @@
border-bottom: 1px solid #ddd;
padding-bottom: 15px;
}
- .report__time {
- font-weight: 600;
- font-size: 15px;
- }
.report__info {
@include opacity(0.8);
}
diff --git a/web/sass-files/sass/partials/_activity-log.scss b/web/sass-files/sass/partials/_activity-log.scss
index 2fb37a3bb..f61c35a28 100644
--- a/web/sass-files/sass/partials/_activity-log.scss
+++ b/web/sass-files/sass/partials/_activity-log.scss
@@ -36,7 +36,7 @@
text-align: right;
}
.report__platform {
- font-size: 16px;
+ font-size: 15px;
font-weight: 600;
.fa {
margin-right: 6px;
@@ -47,5 +47,5 @@
}
}
.session-help-text {
- padding: 20px 20px 5px 20px;
+ padding: 0 0 20px;
} \ 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 2830026c9..ad4a65c00 100644
--- a/web/sass-files/sass/partials/_base.scss
+++ b/web/sass-files/sass/partials/_base.scss
@@ -31,7 +31,7 @@ body {
}
.container-fluid {
- @include clearfix;
+ @include legacy-pie-clearfix;
height: 100%;
position: relative;
}
diff --git a/web/sass-files/sass/partials/_content.scss b/web/sass-files/sass/partials/_content.scss
index 49b3916a9..d86e225f3 100644
--- a/web/sass-files/sass/partials/_content.scss
+++ b/web/sass-files/sass/partials/_content.scss
@@ -18,15 +18,15 @@
margin-left: 220px;
position: relative;
background: #fff;
+ display: flex;
+ flex-direction: column;
.channel__wrap & {
padding-top: 0;
}
}
#post-create {
+ flex: 0 0 auto;
background: #fff;
- position: absolute;
- bottom: 0;
- left: 0;
width: 100%;
z-index: 3;
}
@@ -61,4 +61,4 @@
.delete-message-text {
margin-top: 10px;
-} \ No newline at end of file
+}
diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss
index 5c8313454..74a7cecff 100644
--- a/web/sass-files/sass/partials/_headers.scss
+++ b/web/sass-files/sass/partials/_headers.scss
@@ -1,6 +1,7 @@
#channel-header {
padding: 3px 0;
height: 58px;
+ flex: 0 0 58px;
}
.row {
&.header {
diff --git a/web/sass-files/sass/partials/_markdown.scss b/web/sass-files/sass/partials/_markdown.scss
index 87e809694..241377252 100644
--- a/web/sass-files/sass/partials/_markdown.scss
+++ b/web/sass-files/sass/partials/_markdown.scss
@@ -9,6 +9,7 @@
}
}
.markdown-inline-img {
+ -moz-force-broken-image-icon: 1;
max-height: 500px;
height: 500px;
}
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 0333e0c65..6270c8608 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -174,7 +174,7 @@
td {
width: 100%;
white-space: nowrap;
- overflow: hidden;
+ @include legacy-pie-clearfix;
text-overflow: ellipsis;
padding: 8px 8px 8px 15px;
&.td--action {
@@ -368,7 +368,7 @@
}
.modal-body {
- padding: 10px 0 0;
+ padding: 10px 0 20px;
@include clearfix;
}
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index e11f9b640..ef19ac601 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -182,12 +182,16 @@ body.ios {
}
#post-list {
+ flex: 1 1 auto;
+ position: relative;
+ overflow-y: hidden;
.post-list-holder-by-time {
background: #fff;
overflow-y: scroll;
width: 100%;
padding: 1em 0 0;
- position: relative;
+ position: absolute;
+ height: 100%;
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
width: 0px !important;
@@ -208,6 +212,20 @@ body.ios {
.post-list__content {
display: table-cell;
vertical-align: bottom;
+ div {
+ &:last-child {
+ .post {
+ .post-header {
+ .post-header-col.post-header__reply {
+ .dropdown-menu {
+ top: auto;
+ bottom: 25px;
+ }
+ }
+ }
+ }
+ }
+ }
}
}
.more-messages-text {
@@ -383,16 +401,6 @@ body.ios {
}
}
}
- &.post--last {
- .post-header {
- .post-header-col.post-header__reply {
- .dropdown-menu {
- top: auto;
- bottom: 25px;
- }
- }
- }
- }
.post-create-footer {
padding: 0;
}
@@ -441,10 +449,10 @@ body.ios {
&.post-profile-img__container {
float: left;
.post-profile-img {
- width: 36px;
- height: 36px;
+ width: 36px;
+ height: 36px;
margin-right: 10px;
- vertical-align: inherit;
+ vertical-align: inherit;
@include border-radius(50px);
}
}
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index 96a6cf2ab..b304450bc 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -13,6 +13,10 @@
.settings-modal {
width:800px;
max-width: 100%;
+ .modal-content {
+ width:800px;
+ max-width: 100%;
+ }
.modal-back {
width: 40px;
height: 56px;
@@ -121,6 +125,15 @@
}
.appearance-section {
+ .theme-group {
+ .input-group-addon {
+ padding: 4px 5px;
+ width: 40px;
+ img {
+ border: 1px solid rgba(black, 0.15);
+ }
+ }
+ }
.premade-themes {
margin-bottom: 10px;
.theme-label {
diff --git a/web/sass-files/sass/partials/_tutorial.scss b/web/sass-files/sass/partials/_tutorial.scss
index 70216aa97..c1bf5fd59 100644
--- a/web/sass-files/sass/partials/_tutorial.scss
+++ b/web/sass-files/sass/partials/_tutorial.scss
@@ -107,11 +107,6 @@
width: 7px;
height: 7px;
margin: 0 4px;
- &.active {
- background: #000;
- @include opacity(0.4);
- }
-
}
}
@@ -153,10 +148,17 @@
padding-bottom: 100px;
padding: 20px 40px 40px;
.tutorial__steps {
+ position: relative;
max-width: 310px;
- min-height: 420px;
+ min-height: 350px;
+ margin-bottom: 50px;
text-align: left;
display: inline-block;
+ padding-bottom: 30px;
+ }
+ .btn-primary {
+ position: absolute;
+ bottom: 0;
}
}
h1 {
diff --git a/web/sass-files/sass/partials/_videos.scss b/web/sass-files/sass/partials/_videos.scss
index bcfc28f19..bb36b6223 100644
--- a/web/sass-files/sass/partials/_videos.scss
+++ b/web/sass-files/sass/partials/_videos.scss
@@ -52,6 +52,7 @@
}
.gif-div {
+ -moz-force-broken-image-icon: 1;
position:relative;
max-width: 450px;
max-height: 500px;
diff --git a/web/static/images/themes/code_themes/github.png b/web/static/images/themes/code_themes/github.png
index d0538d6c0..a49b877b1 100644
--- a/web/static/images/themes/code_themes/github.png
+++ b/web/static/images/themes/code_themes/github.png
Binary files differ
diff --git a/web/static/images/themes/code_themes/monokai.png b/web/static/images/themes/code_themes/monokai.png
index 8f92d2a18..a71225b68 100644
--- a/web/static/images/themes/code_themes/monokai.png
+++ b/web/static/images/themes/code_themes/monokai.png
Binary files differ
diff --git a/web/static/images/themes/code_themes/solarized_dark.png b/web/static/images/themes/code_themes/solarized_dark.png
index 76055c678..07774ff20 100644
--- a/web/static/images/themes/code_themes/solarized_dark.png
+++ b/web/static/images/themes/code_themes/solarized_dark.png
Binary files differ
diff --git a/web/static/images/themes/code_themes/solarized_light.png b/web/static/images/themes/code_themes/solarized_light.png
index b9595c22d..fc71dbb87 100644
--- a/web/static/images/themes/code_themes/solarized_light.png
+++ b/web/static/images/themes/code_themes/solarized_light.png
Binary files differ
diff --git a/web/static/js/perfect-scrollbar-0.6.7.jquery.js b/web/static/js/perfect-scrollbar-0.6.7.jquery.js
index 9e93de017..6c25fa91f 100644
--- a/web/static/js/perfect-scrollbar-0.6.7.jquery.js
+++ b/web/static/js/perfect-scrollbar-0.6.7.jquery.js
@@ -443,10 +443,11 @@ function bindClickRailHandler(element, i) {
function pageOffset(el) {
return el.getBoundingClientRect();
}
- var stopPropagation = window.Event.prototype.stopPropagation.bind;
if (i.settings.stopPropagationOnClick) {
- i.event.bind(i.scrollbarY, 'click', stopPropagation);
+ i.event.bind(i.scrollbarY, 'click', function (e) {
+ e.stopPropagation();
+ });
}
i.event.bind(i.scrollbarYRail, 'click', function (e) {
var halfOfScrollbarLength = h.toInt(i.scrollbarYHeight / 2);
@@ -467,7 +468,9 @@ function bindClickRailHandler(element, i) {
});
if (i.settings.stopPropagationOnClick) {
- i.event.bind(i.scrollbarX, 'click', stopPropagation);
+ i.event.bind(i.scrollbarY, 'click', function (e) {
+ e.stopPropagation();
+ });
}
i.event.bind(i.scrollbarXRail, 'click', function (e) {
var halfOfScrollbarLength = h.toInt(i.scrollbarXWidth / 2);
diff --git a/web/static/js/perfect-scrollbar-0.6.7.jquery.min.js b/web/static/js/perfect-scrollbar-0.6.7.jquery.min.js
index ade0c1836..8b0f0056c 100644
--- a/web/static/js/perfect-scrollbar-0.6.7.jquery.min.js
+++ b/web/static/js/perfect-scrollbar-0.6.7.jquery.min.js
@@ -1,2 +1,2 @@
/* perfect-scrollbar v0.6.7 */
-!function t(e,n,r){function o(l,s){if(!n[l]){if(!e[l]){var a="function"==typeof require&&require;if(!s&&a)return a(l,!0);if(i)return i(l,!0);var c=new Error("Cannot find module '"+l+"'");throw c.code="MODULE_NOT_FOUND",c}var u=n[l]={exports:{}};e[l][0].call(u.exports,function(t){var n=e[l][1][t];return o(n?n:t)},u,u.exports,t,e,n,r)}return n[l].exports}for(var i="function"==typeof require&&require,l=0;l<r.length;l++)o(r[l]);return o}({1:[function(t,e,n){"use strict";function r(t){t.fn.perfectScrollbar=function(e){return this.each(function(){if("object"==typeof e||"undefined"==typeof e){var n=e;i.get(this)||o.initialize(this,n)}else{var r=e;"update"===r?o.update(this):"destroy"===r&&o.destroy(this)}return t(this)})}}var o=t("../main"),i=t("../plugin/instances");if("function"==typeof define&&define.amd)define(["jquery"],r);else{var l=window.jQuery?window.jQuery:window.$;"undefined"!=typeof l&&r(l)}e.exports=r},{"../main":7,"../plugin/instances":18}],2:[function(t,e,n){"use strict";function r(t,e){var n=t.className.split(" ");n.indexOf(e)<0&&n.push(e),t.className=n.join(" ")}function o(t,e){var n=t.className.split(" "),r=n.indexOf(e);r>=0&&n.splice(r,1),t.className=n.join(" ")}n.add=function(t,e){t.classList?t.classList.add(e):r(t,e)},n.remove=function(t,e){t.classList?t.classList.remove(e):o(t,e)},n.list=function(t){return t.classList?Array.prototype.slice.apply(t.classList):t.className.split(" ")}},{}],3:[function(t,e,n){"use strict";function r(t,e){return window.getComputedStyle(t)[e]}function o(t,e,n){return"number"==typeof n&&(n=n.toString()+"px"),t.style[e]=n,t}function i(t,e){for(var n in e){var r=e[n];"number"==typeof r&&(r=r.toString()+"px"),t.style[n]=r}return t}var l={};l.e=function(t,e){var n=document.createElement(t);return n.className=e,n},l.appendTo=function(t,e){return e.appendChild(t),t},l.css=function(t,e,n){return"object"==typeof e?i(t,e):"undefined"==typeof n?r(t,e):o(t,e,n)},l.matches=function(t,e){return"undefined"!=typeof t.matches?t.matches(e):"undefined"!=typeof t.matchesSelector?t.matchesSelector(e):"undefined"!=typeof t.webkitMatchesSelector?t.webkitMatchesSelector(e):"undefined"!=typeof t.mozMatchesSelector?t.mozMatchesSelector(e):"undefined"!=typeof t.msMatchesSelector?t.msMatchesSelector(e):void 0},l.remove=function(t){"undefined"!=typeof t.remove?t.remove():t.parentNode&&t.parentNode.removeChild(t)},l.queryChildren=function(t,e){return Array.prototype.filter.call(t.childNodes,function(t){return l.matches(t,e)})},e.exports=l},{}],4:[function(t,e,n){"use strict";var r=function(t){this.element=t,this.events={}};r.prototype.bind=function(t,e){"undefined"==typeof this.events[t]&&(this.events[t]=[]),this.events[t].push(e),this.element.addEventListener(t,e,!1)},r.prototype.unbind=function(t,e){var n="undefined"!=typeof e;this.events[t]=this.events[t].filter(function(r){return n&&r!==e?!0:(this.element.removeEventListener(t,r,!1),!1)},this)},r.prototype.unbindAll=function(){for(var t in this.events)this.unbind(t)};var o=function(){this.eventElements=[]};o.prototype.eventElement=function(t){var e=this.eventElements.filter(function(e){return e.element===t})[0];return"undefined"==typeof e&&(e=new r(t),this.eventElements.push(e)),e},o.prototype.bind=function(t,e,n){this.eventElement(t).bind(e,n)},o.prototype.unbind=function(t,e,n){this.eventElement(t).unbind(e,n)},o.prototype.unbindAll=function(){for(var t=0;t<this.eventElements.length;t++)this.eventElements[t].unbindAll()},o.prototype.once=function(t,e,n){var r=this.eventElement(t),o=function(t){r.unbind(e,o),n(t)};r.bind(e,o)},e.exports=o},{}],5:[function(t,e,n){"use strict";e.exports=function(){function t(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}return function(){return t()+t()+"-"+t()+"-"+t()+"-"+t()+"-"+t()+t()+t()}}()},{}],6:[function(t,e,n){"use strict";var r=t("./class"),o=t("./dom");n.toInt=function(t){return parseInt(t,10)||0},n.clone=function(t){if(null===t)return null;if("object"==typeof t){var e={};for(var n in t)e[n]=this.clone(t[n]);return e}return t},n.extend=function(t,e){var n=this.clone(t);for(var r in e)n[r]=this.clone(e[r]);return n},n.isEditable=function(t){return o.matches(t,"input,[contenteditable]")||o.matches(t,"select,[contenteditable]")||o.matches(t,"textarea,[contenteditable]")||o.matches(t,"button,[contenteditable]")},n.removePsClasses=function(t){for(var e=r.list(t),n=0;n<e.length;n++){var o=e[n];0===o.indexOf("ps-")&&r.remove(t,o)}},n.outerWidth=function(t){return this.toInt(o.css(t,"width"))+this.toInt(o.css(t,"paddingLeft"))+this.toInt(o.css(t,"paddingRight"))+this.toInt(o.css(t,"borderLeftWidth"))+this.toInt(o.css(t,"borderRightWidth"))},n.startScrolling=function(t,e){r.add(t,"ps-in-scrolling"),"undefined"!=typeof e?r.add(t,"ps-"+e):(r.add(t,"ps-x"),r.add(t,"ps-y"))},n.stopScrolling=function(t,e){r.remove(t,"ps-in-scrolling"),"undefined"!=typeof e?r.remove(t,"ps-"+e):(r.remove(t,"ps-x"),r.remove(t,"ps-y"))},n.env={isWebKit:"WebkitAppearance"in document.documentElement.style,supportsTouch:"ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch,supportsIePointer:null!==window.navigator.msMaxTouchPoints}},{"./class":2,"./dom":3}],7:[function(t,e,n){"use strict";var r=t("./plugin/destroy"),o=t("./plugin/initialize"),i=t("./plugin/update");e.exports={initialize:o,update:i,destroy:r}},{"./plugin/destroy":9,"./plugin/initialize":17,"./plugin/update":21}],8:[function(t,e,n){"use strict";e.exports={maxScrollbarLength:null,minScrollbarLength:null,scrollXMarginOffset:0,scrollYMarginOffset:0,stopPropagationOnClick:!0,suppressScrollX:!1,suppressScrollY:!1,swipePropagation:!0,useBothWheelAxes:!1,useKeyboard:!0,useSelectionScroll:!1,wheelPropagation:!1,wheelSpeed:1}},{}],9:[function(t,e,n){"use strict";var r=t("../lib/dom"),o=t("../lib/helper"),i=t("./instances");e.exports=function(t){var e=i.get(t);e&&(e.event.unbindAll(),r.remove(e.scrollbarX),r.remove(e.scrollbarY),r.remove(e.scrollbarXRail),r.remove(e.scrollbarYRail),o.removePsClasses(t),i.remove(t))}},{"../lib/dom":3,"../lib/helper":6,"./instances":18}],10:[function(t,e,n){"use strict";function r(t,e){function n(t){return t.getBoundingClientRect()}var r=window.Event.prototype.stopPropagation.bind;e.settings.stopPropagationOnClick&&e.event.bind(e.scrollbarY,"click",r),e.event.bind(e.scrollbarYRail,"click",function(r){var i=o.toInt(e.scrollbarYHeight/2),a=e.railYRatio*(r.pageY-window.scrollY-n(e.scrollbarYRail).top-i),c=e.railYRatio*(e.railYHeight-e.scrollbarYHeight),u=a/c;0>u?u=0:u>1&&(u=1),s(t,"top",(e.contentHeight-e.containerHeight)*u),l(t),r.stopPropagation()}),e.settings.stopPropagationOnClick&&e.event.bind(e.scrollbarX,"click",r),e.event.bind(e.scrollbarXRail,"click",function(r){var i=o.toInt(e.scrollbarXWidth/2),a=e.railXRatio*(r.pageX-window.scrollX-n(e.scrollbarXRail).left-i),c=e.railXRatio*(e.railXWidth-e.scrollbarXWidth),u=a/c;0>u?u=0:u>1&&(u=1),s(t,"left",(e.contentWidth-e.containerWidth)*u-e.negativeScrollAdjustment),l(t),r.stopPropagation()})}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],11:[function(t,e,n){"use strict";function r(t,e){function n(n){var o=r+n*e.railXRatio,i=e.scrollbarXRail.getBoundingClientRect().left+e.railXRatio*(e.railXWidth-e.scrollbarXWidth);0>o?e.scrollbarXLeft=0:o>i?e.scrollbarXLeft=i:e.scrollbarXLeft=o;var s=l.toInt(e.scrollbarXLeft*(e.contentWidth-e.containerWidth)/(e.containerWidth-e.railXRatio*e.scrollbarXWidth))-e.negativeScrollAdjustment;c(t,"left",s)}var r=null,o=null,s=function(e){n(e.pageX-o),a(t),e.stopPropagation(),e.preventDefault()},u=function(){l.stopScrolling(t,"x"),e.event.unbind(e.ownerDocument,"mousemove",s)};e.event.bind(e.scrollbarX,"mousedown",function(n){o=n.pageX,r=l.toInt(i.css(e.scrollbarX,"left"))*e.railXRatio,l.startScrolling(t,"x"),e.event.bind(e.ownerDocument,"mousemove",s),e.event.once(e.ownerDocument,"mouseup",u),n.stopPropagation(),n.preventDefault()})}function o(t,e){function n(n){var o=r+n*e.railYRatio,i=e.scrollbarYRail.getBoundingClientRect().top+e.railYRatio*(e.railYHeight-e.scrollbarYHeight);0>o?e.scrollbarYTop=0:o>i?e.scrollbarYTop=i:e.scrollbarYTop=o;var s=l.toInt(e.scrollbarYTop*(e.contentHeight-e.containerHeight)/(e.containerHeight-e.railYRatio*e.scrollbarYHeight));c(t,"top",s)}var r=null,o=null,s=function(e){n(e.pageY-o),a(t),e.stopPropagation(),e.preventDefault()},u=function(){l.stopScrolling(t,"y"),e.event.unbind(e.ownerDocument,"mousemove",s)};e.event.bind(e.scrollbarY,"mousedown",function(n){o=n.pageY,r=l.toInt(i.css(e.scrollbarY,"top"))*e.railYRatio,l.startScrolling(t,"y"),e.event.bind(e.ownerDocument,"mousemove",s),e.event.once(e.ownerDocument,"mouseup",u),n.stopPropagation(),n.preventDefault()})}var i=t("../../lib/dom"),l=t("../../lib/helper"),s=t("../instances"),a=t("../update-geometry"),c=t("../update-scroll");e.exports=function(t){var e=s.get(t);r(t,e),o(t,e)}},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],12:[function(t,e,n){"use strict";function r(t,e){function n(n,r){var o=t.scrollTop;if(0===n){if(!e.scrollbarYActive)return!1;if(0===o&&r>0||o>=e.contentHeight-e.containerHeight&&0>r)return!e.settings.wheelPropagation}var i=t.scrollLeft;if(0===r){if(!e.scrollbarXActive)return!1;if(0===i&&0>n||i>=e.contentWidth-e.containerWidth&&n>0)return!e.settings.wheelPropagation}return!0}var r=!1;e.event.bind(t,"mouseenter",function(){r=!0}),e.event.bind(t,"mouseleave",function(){r=!1});var i=!1;e.event.bind(e.ownerDocument,"keydown",function(a){if((!a.isDefaultPrevented||!a.isDefaultPrevented())&&r){var c=document.activeElement?document.activeElement:e.ownerDocument.activeElement;if(c){for(;c.shadowRoot;)c=c.shadowRoot.activeElement;if(o.isEditable(c))return}var u=0,d=0;switch(a.which){case 37:u=-30;break;case 38:d=30;break;case 39:u=30;break;case 40:d=-30;break;case 33:d=90;break;case 32:d=a.shiftKey?90:-90;break;case 34:d=-90;break;case 35:d=a.ctrlKey?-e.contentHeight:-e.containerHeight;break;case 36:d=a.ctrlKey?t.scrollTop:e.containerHeight;break;default:return}s(t,"top",t.scrollTop-d),s(t,"left",t.scrollLeft+u),l(t),i=n(u,d),i&&a.preventDefault()}})}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],13:[function(t,e,n){"use strict";function r(t,e){function n(n,r){var o=t.scrollTop;if(0===n){if(!e.scrollbarYActive)return!1;if(0===o&&r>0||o>=e.contentHeight-e.containerHeight&&0>r)return!e.settings.wheelPropagation}var i=t.scrollLeft;if(0===r){if(!e.scrollbarXActive)return!1;if(0===i&&0>n||i>=e.contentWidth-e.containerWidth&&n>0)return!e.settings.wheelPropagation}return!0}function r(t){var e=t.deltaX,n=-1*t.deltaY;return("undefined"==typeof e||"undefined"==typeof n)&&(e=-1*t.wheelDeltaX/6,n=t.wheelDeltaY/6),t.deltaMode&&1===t.deltaMode&&(e*=10,n*=10),e!==e&&n!==n&&(e=0,n=t.wheelDelta),[e,n]}function i(e,n){var r=t.querySelector("textarea:hover");if(r){var o=r.scrollHeight-r.clientHeight;if(o>0&&!(0===r.scrollTop&&n>0||r.scrollTop===o&&0>n))return!0;var i=r.scrollLeft-r.clientWidth;if(i>0&&!(0===r.scrollLeft&&0>e||r.scrollLeft===i&&e>0))return!0}return!1}function a(a){if(o.env.isWebKit||!t.querySelector("select:focus")){var u=r(a),d=u[0],p=u[1];i(d,p)||(c=!1,e.settings.useBothWheelAxes?e.scrollbarYActive&&!e.scrollbarXActive?(p?s(t,"top",t.scrollTop-p*e.settings.wheelSpeed):s(t,"top",t.scrollTop+d*e.settings.wheelSpeed),c=!0):e.scrollbarXActive&&!e.scrollbarYActive&&(d?s(t,"left",t.scrollLeft+d*e.settings.wheelSpeed):s(t,"left",t.scrollLeft-p*e.settings.wheelSpeed),c=!0):(s(t,"top",t.scrollTop-p*e.settings.wheelSpeed),s(t,"left",t.scrollLeft+d*e.settings.wheelSpeed)),l(t),c=c||n(d,p),c&&(a.stopPropagation(),a.preventDefault()))}}var c=!1;"undefined"!=typeof window.onwheel?e.event.bind(t,"wheel",a):"undefined"!=typeof window.onmousewheel&&e.event.bind(t,"mousewheel",a)}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],14:[function(t,e,n){"use strict";function r(t,e){e.event.bind(t,"scroll",function(){i(t)})}var o=t("../instances"),i=t("../update-geometry");e.exports=function(t){var e=o.get(t);r(t,e)}},{"../instances":18,"../update-geometry":19}],15:[function(t,e,n){"use strict";function r(t,e){function n(){var t=window.getSelection?window.getSelection():document.getSelection?document.getSelection():"";return 0===t.toString().length?null:t.getRangeAt(0).commonAncestorContainer}function r(){c||(c=setInterval(function(){return i.get(t)?(s(t,"top",t.scrollTop+u.top),s(t,"left",t.scrollLeft+u.left),void l(t)):void clearInterval(c)},50))}function a(){c&&(clearInterval(c),c=null),o.stopScrolling(t)}var c=null,u={top:0,left:0},d=!1;e.event.bind(e.ownerDocument,"selectionchange",function(){t.contains(n())?d=!0:(d=!1,a())}),e.event.bind(window,"mouseup",function(){d&&(d=!1,a())}),e.event.bind(window,"mousemove",function(e){if(d){var n={x:e.pageX,y:e.pageY},i={left:t.offsetLeft,right:t.offsetLeft+t.offsetWidth,top:t.offsetTop,bottom:t.offsetTop+t.offsetHeight};n.x<i.left+3?(u.left=-5,o.startScrolling(t,"x")):n.x>i.right-3?(u.left=5,o.startScrolling(t,"x")):u.left=0,n.y<i.top+3?(i.top+3-n.y<5?u.top=-5:u.top=-20,o.startScrolling(t,"y")):n.y>i.bottom-3?(n.y-i.bottom+3<5?u.top=5:u.top=20,o.startScrolling(t,"y")):u.top=0,0===u.top&&0===u.left?a():r()}})}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],16:[function(t,e,n){"use strict";function r(t,e,n,r){function s(n,r){var o=t.scrollTop,i=t.scrollLeft,l=Math.abs(n),s=Math.abs(r);if(s>l){if(0>r&&o===e.contentHeight-e.containerHeight||r>0&&0===o)return!e.settings.swipePropagation}else if(l>s&&(0>n&&i===e.contentWidth-e.containerWidth||n>0&&0===i))return!e.settings.swipePropagation;return!0}function a(e,n){l(t,"top",t.scrollTop-n),l(t,"left",t.scrollLeft-e),i(t)}function c(){Y=!0}function u(){Y=!1}function d(t){return t.targetTouches?t.targetTouches[0]:t}function p(t){return t.targetTouches&&1===t.targetTouches.length?!0:t.pointerType&&"mouse"!==t.pointerType&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE?!0:!1}function f(t){if(p(t)){w=!0;var e=d(t);b.pageX=e.pageX,b.pageY=e.pageY,g=(new Date).getTime(),null!==y&&clearInterval(y),t.stopPropagation()}}function h(t){if(!Y&&w&&p(t)){var e=d(t),n={pageX:e.pageX,pageY:e.pageY},r=n.pageX-b.pageX,o=n.pageY-b.pageY;a(r,o),b=n;var i=(new Date).getTime(),l=i-g;l>0&&(m.x=r/l,m.y=o/l,g=i),s(r,o)&&(t.stopPropagation(),t.preventDefault())}}function v(){!Y&&w&&(w=!1,clearInterval(y),y=setInterval(function(){return o.get(t)?Math.abs(m.x)<.01&&Math.abs(m.y)<.01?void clearInterval(y):(a(30*m.x,30*m.y),m.x*=.8,void(m.y*=.8)):void clearInterval(y)},10))}var b={},g=0,m={},y=null,Y=!1,w=!1;n&&(e.event.bind(window,"touchstart",c),e.event.bind(window,"touchend",u),e.event.bind(t,"touchstart",f),e.event.bind(t,"touchmove",h),e.event.bind(t,"touchend",v)),r&&(window.PointerEvent?(e.event.bind(window,"pointerdown",c),e.event.bind(window,"pointerup",u),e.event.bind(t,"pointerdown",f),e.event.bind(t,"pointermove",h),e.event.bind(t,"pointerup",v)):window.MSPointerEvent&&(e.event.bind(window,"MSPointerDown",c),e.event.bind(window,"MSPointerUp",u),e.event.bind(t,"MSPointerDown",f),e.event.bind(t,"MSPointerMove",h),e.event.bind(t,"MSPointerUp",v)))}var o=t("../instances"),i=t("../update-geometry"),l=t("../update-scroll");e.exports=function(t,e,n){var i=o.get(t);r(t,i,e,n)}},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],17:[function(t,e,n){"use strict";var r=t("../lib/class"),o=t("../lib/helper"),i=t("./instances"),l=t("./update-geometry"),s=t("./handler/click-rail"),a=t("./handler/drag-scrollbar"),c=t("./handler/keyboard"),u=t("./handler/mouse-wheel"),d=t("./handler/native-scroll"),p=t("./handler/selection"),f=t("./handler/touch");e.exports=function(t,e){e="object"==typeof e?e:{},r.add(t,"ps-container");var n=i.add(t);n.settings=o.extend(n.settings,e),s(t),a(t),u(t),d(t),n.settings.useSelectionScroll&&p(t),(o.env.supportsTouch||o.env.supportsIePointer)&&f(t,o.env.supportsTouch,o.env.supportsIePointer),n.settings.useKeyboard&&c(t),l(t)}},{"../lib/class":2,"../lib/helper":6,"./handler/click-rail":10,"./handler/drag-scrollbar":11,"./handler/keyboard":12,"./handler/mouse-wheel":13,"./handler/native-scroll":14,"./handler/selection":15,"./handler/touch":16,"./instances":18,"./update-geometry":19}],18:[function(t,e,n){"use strict";function r(t){var e=this;e.settings=d.clone(a),e.containerWidth=null,e.containerHeight=null,e.contentWidth=null,e.contentHeight=null,e.isRtl="rtl"===s.css(t,"direction"),e.isNegativeScroll=function(){var e=t.scrollLeft,n=null;return t.scrollLeft=-1,n=t.scrollLeft<0,t.scrollLeft=e,n}(),e.negativeScrollAdjustment=e.isNegativeScroll?t.scrollWidth-t.clientWidth:0,e.event=new c,e.ownerDocument=t.ownerDocument||document,e.scrollbarXRail=s.appendTo(s.e("div","ps-scrollbar-x-rail"),t),e.scrollbarX=s.appendTo(s.e("div","ps-scrollbar-x"),e.scrollbarXRail),e.scrollbarXActive=null,e.scrollbarXWidth=null,e.scrollbarXLeft=null,e.scrollbarXBottom=d.toInt(s.css(e.scrollbarXRail,"bottom")),e.isScrollbarXUsingBottom=e.scrollbarXBottom===e.scrollbarXBottom,e.scrollbarXTop=e.isScrollbarXUsingBottom?null:d.toInt(s.css(e.scrollbarXRail,"top")),e.railBorderXWidth=d.toInt(s.css(e.scrollbarXRail,"borderLeftWidth"))+d.toInt(s.css(e.scrollbarXRail,"borderRightWidth")),s.css(e.scrollbarXRail,"display","block"),e.railXMarginWidth=d.toInt(s.css(e.scrollbarXRail,"marginLeft"))+d.toInt(s.css(e.scrollbarXRail,"marginRight")),s.css(e.scrollbarXRail,"display",""),e.railXWidth=null,e.railXRatio=null,e.scrollbarYRail=s.appendTo(s.e("div","ps-scrollbar-y-rail"),t),e.scrollbarY=s.appendTo(s.e("div","ps-scrollbar-y"),e.scrollbarYRail),e.scrollbarYActive=null,e.scrollbarYHeight=null,e.scrollbarYTop=null,e.scrollbarYRight=d.toInt(s.css(e.scrollbarYRail,"right")),e.isScrollbarYUsingRight=e.scrollbarYRight===e.scrollbarYRight,e.scrollbarYLeft=e.isScrollbarYUsingRight?null:d.toInt(s.css(e.scrollbarYRail,"left")),e.scrollbarYOuterWidth=e.isRtl?d.outerWidth(e.scrollbarY):null,e.railBorderYWidth=d.toInt(s.css(e.scrollbarYRail,"borderTopWidth"))+d.toInt(s.css(e.scrollbarYRail,"borderBottomWidth")),s.css(e.scrollbarYRail,"display","block"),e.railYMarginHeight=d.toInt(s.css(e.scrollbarYRail,"marginTop"))+d.toInt(s.css(e.scrollbarYRail,"marginBottom")),s.css(e.scrollbarYRail,"display",""),e.railYHeight=null,e.railYRatio=null}function o(t){return"undefined"==typeof t.dataset?t.getAttribute("data-ps-id"):t.dataset.psId}function i(t,e){"undefined"==typeof t.dataset?t.setAttribute("data-ps-id",e):t.dataset.psId=e}function l(t){"undefined"==typeof t.dataset?t.removeAttribute("data-ps-id"):delete t.dataset.psId}var s=t("../lib/dom"),a=t("./default-setting"),c=t("../lib/event-manager"),u=t("../lib/guid"),d=t("../lib/helper"),p={};n.add=function(t){var e=u();return i(t,e),p[e]=new r(t),p[e]},n.remove=function(t){delete p[o(t)],l(t)},n.get=function(t){return p[o(t)]}},{"../lib/dom":3,"../lib/event-manager":4,"../lib/guid":5,"../lib/helper":6,"./default-setting":8}],19:[function(t,e,n){"use strict";function r(t,e){return t.settings.minScrollbarLength&&(e=Math.max(e,t.settings.minScrollbarLength)),t.settings.maxScrollbarLength&&(e=Math.min(e,t.settings.maxScrollbarLength)),e}function o(t,e){var n={width:e.railXWidth};e.isRtl?n.left=e.negativeScrollAdjustment+t.scrollLeft+e.containerWidth-e.contentWidth:n.left=t.scrollLeft,e.isScrollbarXUsingBottom?n.bottom=e.scrollbarXBottom-t.scrollTop:n.top=e.scrollbarXTop+t.scrollTop,l.css(e.scrollbarXRail,n);var r={top:t.scrollTop,height:e.railYHeight};e.isScrollbarYUsingRight?e.isRtl?r.right=e.contentWidth-(e.negativeScrollAdjustment+t.scrollLeft)-e.scrollbarYRight-e.scrollbarYOuterWidth:r.right=e.scrollbarYRight-t.scrollLeft:e.isRtl?r.left=e.negativeScrollAdjustment+t.scrollLeft+2*e.containerWidth-e.contentWidth-e.scrollbarYLeft-e.scrollbarYOuterWidth:r.left=e.scrollbarYLeft+t.scrollLeft,l.css(e.scrollbarYRail,r),l.css(e.scrollbarX,{left:e.scrollbarXLeft,width:e.scrollbarXWidth-e.railBorderXWidth}),l.css(e.scrollbarY,{top:e.scrollbarYTop,height:e.scrollbarYHeight-e.railBorderYWidth})}var i=t("../lib/class"),l=t("../lib/dom"),s=t("../lib/helper"),a=t("./instances"),c=t("./update-scroll");e.exports=function(t){var e=a.get(t);e.containerWidth=t.clientWidth,e.containerHeight=t.clientHeight,e.contentWidth=t.scrollWidth,e.contentHeight=t.scrollHeight;var n;t.contains(e.scrollbarXRail)||(n=l.queryChildren(t,".ps-scrollbar-x-rail"),n.length>0&&n.forEach(function(t){l.remove(t)}),l.appendTo(e.scrollbarXRail,t)),t.contains(e.scrollbarYRail)||(n=l.queryChildren(t,".ps-scrollbar-y-rail"),n.length>0&&n.forEach(function(t){l.remove(t)}),l.appendTo(e.scrollbarYRail,t)),!e.settings.suppressScrollX&&e.containerWidth+e.settings.scrollXMarginOffset<e.contentWidth?(e.scrollbarXActive=!0,e.railXWidth=e.containerWidth-e.railXMarginWidth,e.railXRatio=e.containerWidth/e.railXWidth,e.scrollbarXWidth=r(e,s.toInt(e.railXWidth*e.containerWidth/e.contentWidth)),e.scrollbarXLeft=s.toInt((e.negativeScrollAdjustment+t.scrollLeft)*(e.railXWidth-e.scrollbarXWidth)/(e.contentWidth-e.containerWidth))):(e.scrollbarXActive=!1,e.scrollbarXWidth=0,e.scrollbarXLeft=0,t.scrollLeft=0),!e.settings.suppressScrollY&&e.containerHeight+e.settings.scrollYMarginOffset<e.contentHeight?(e.scrollbarYActive=!0,e.railYHeight=e.containerHeight-e.railYMarginHeight,e.railYRatio=e.containerHeight/e.railYHeight,e.scrollbarYHeight=r(e,s.toInt(e.railYHeight*e.containerHeight/e.contentHeight)),e.scrollbarYTop=s.toInt(t.scrollTop*(e.railYHeight-e.scrollbarYHeight)/(e.contentHeight-e.containerHeight))):(e.scrollbarYActive=!1,e.scrollbarYHeight=0,e.scrollbarYTop=0,c(t,"top",0)),e.scrollbarXLeft>=e.railXWidth-e.scrollbarXWidth&&(e.scrollbarXLeft=e.railXWidth-e.scrollbarXWidth),e.scrollbarYTop>=e.railYHeight-e.scrollbarYHeight&&(e.scrollbarYTop=e.railYHeight-e.scrollbarYHeight),o(t,e),i[e.scrollbarXActive?"add":"remove"](t,"ps-active-x"),i[e.scrollbarYActive?"add":"remove"](t,"ps-active-y")}},{"../lib/class":2,"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-scroll":20}],20:[function(t,e,n){"use strict";var r,o,i=t("./instances"),l=document.createEvent("Event"),s=document.createEvent("Event"),a=document.createEvent("Event"),c=document.createEvent("Event"),u=document.createEvent("Event"),d=document.createEvent("Event"),p=document.createEvent("Event"),f=document.createEvent("Event"),h=document.createEvent("Event"),v=document.createEvent("Event");l.initEvent("ps-scroll-up",!0,!0),s.initEvent("ps-scroll-down",!0,!0),a.initEvent("ps-scroll-left",!0,!0),c.initEvent("ps-scroll-right",!0,!0),u.initEvent("ps-scroll-y",!0,!0),d.initEvent("ps-scroll-x",!0,!0),p.initEvent("ps-x-reach-start",!0,!0),f.initEvent("ps-x-reach-end",!0,!0),h.initEvent("ps-y-reach-start",!0,!0),v.initEvent("ps-y-reach-end",!0,!0),e.exports=function(t,e,n){if("undefined"==typeof t)throw"You must provide an element to the update-scroll function";if("undefined"==typeof e)throw"You must provide an axis to the update-scroll function";if("undefined"==typeof n)throw"You must provide a value to the update-scroll function";if("top"===e&&0>=n)return t.scrollTop=0,void t.dispatchEvent(h);if("left"===e&&0>=n)return t.scrollLeft=0,void t.dispatchEvent(p);var b=i.get(t);return"top"===e&&n>b.contentHeight-b.containerHeight?(t.scrollTop=b.contentHeight-b.containerHeight,void t.dispatchEvent(v)):"left"===e&&n>b.contentWidth-b.containerWidth?(t.scrollLeft=b.contentWidth-b.containerWidth,void t.dispatchEvent(f)):(r||(r=t.scrollTop),o||(o=t.scrollLeft),"top"===e&&r>n&&t.dispatchEvent(l),"top"===e&&n>r&&t.dispatchEvent(s),"left"===e&&o>n&&t.dispatchEvent(a),"left"===e&&n>o&&t.dispatchEvent(c),"top"===e&&(t.scrollTop=r=n,t.dispatchEvent(u)),void("left"===e&&(t.scrollLeft=o=n,t.dispatchEvent(d))))}},{"./instances":18}],21:[function(t,e,n){"use strict";var r=t("../lib/dom"),o=t("../lib/helper"),i=t("./instances"),l=t("./update-geometry");e.exports=function(t){var e=i.get(t);e&&(e.negativeScrollAdjustment=e.isNegativeScroll?t.scrollWidth-t.clientWidth:0,r.css(e.scrollbarXRail,"display","block"),r.css(e.scrollbarYRail,"display","block"),e.railXMarginWidth=o.toInt(r.css(e.scrollbarXRail,"marginLeft"))+o.toInt(r.css(e.scrollbarXRail,"marginRight")),e.railYMarginHeight=o.toInt(r.css(e.scrollbarYRail,"marginTop"))+o.toInt(r.css(e.scrollbarYRail,"marginBottom")),r.css(e.scrollbarXRail,"display","none"),r.css(e.scrollbarYRail,"display","none"),l(t),r.css(e.scrollbarXRail,"display",""),r.css(e.scrollbarYRail,"display",""))}},{"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-geometry":19}]},{},[1]); \ No newline at end of file
+!function t(e,n,r){function o(l,s){if(!n[l]){if(!e[l]){var a="function"==typeof require&&require;if(!s&&a)return a(l,!0);if(i)return i(l,!0);var c=new Error("Cannot find module '"+l+"'");throw c.code="MODULE_NOT_FOUND",c}var u=n[l]={exports:{}};e[l][0].call(u.exports,function(t){var n=e[l][1][t];return o(n?n:t)},u,u.exports,t,e,n,r)}return n[l].exports}for(var i="function"==typeof require&&require,l=0;l<r.length;l++)o(r[l]);return o}({1:[function(t,e,n){"use strict";function r(t){t.fn.perfectScrollbar=function(e){return this.each(function(){if("object"==typeof e||"undefined"==typeof e){var n=e;i.get(this)||o.initialize(this,n)}else{var r=e;"update"===r?o.update(this):"destroy"===r&&o.destroy(this)}return t(this)})}}var o=t("../main"),i=t("../plugin/instances");if("function"==typeof define&&define.amd)define(["jquery"],r);else{var l=window.jQuery?window.jQuery:window.$;"undefined"!=typeof l&&r(l)}e.exports=r},{"../main":7,"../plugin/instances":18}],2:[function(t,e,n){"use strict";function r(t,e){var n=t.className.split(" ");n.indexOf(e)<0&&n.push(e),t.className=n.join(" ")}function o(t,e){var n=t.className.split(" "),r=n.indexOf(e);r>=0&&n.splice(r,1),t.className=n.join(" ")}n.add=function(t,e){t.classList?t.classList.add(e):r(t,e)},n.remove=function(t,e){t.classList?t.classList.remove(e):o(t,e)},n.list=function(t){return t.classList?Array.prototype.slice.apply(t.classList):t.className.split(" ")}},{}],3:[function(t,e,n){"use strict";function r(t,e){return window.getComputedStyle(t)[e]}function o(t,e,n){return"number"==typeof n&&(n=n.toString()+"px"),t.style[e]=n,t}function i(t,e){for(var n in e){var r=e[n];"number"==typeof r&&(r=r.toString()+"px"),t.style[n]=r}return t}var l={};l.e=function(t,e){var n=document.createElement(t);return n.className=e,n},l.appendTo=function(t,e){return e.appendChild(t),t},l.css=function(t,e,n){return"object"==typeof e?i(t,e):"undefined"==typeof n?r(t,e):o(t,e,n)},l.matches=function(t,e){return"undefined"!=typeof t.matches?t.matches(e):"undefined"!=typeof t.matchesSelector?t.matchesSelector(e):"undefined"!=typeof t.webkitMatchesSelector?t.webkitMatchesSelector(e):"undefined"!=typeof t.mozMatchesSelector?t.mozMatchesSelector(e):"undefined"!=typeof t.msMatchesSelector?t.msMatchesSelector(e):void 0},l.remove=function(t){"undefined"!=typeof t.remove?t.remove():t.parentNode&&t.parentNode.removeChild(t)},l.queryChildren=function(t,e){return Array.prototype.filter.call(t.childNodes,function(t){return l.matches(t,e)})},e.exports=l},{}],4:[function(t,e,n){"use strict";var r=function(t){this.element=t,this.events={}};r.prototype.bind=function(t,e){"undefined"==typeof this.events[t]&&(this.events[t]=[]),this.events[t].push(e),this.element.addEventListener(t,e,!1)},r.prototype.unbind=function(t,e){var n="undefined"!=typeof e;this.events[t]=this.events[t].filter(function(r){return n&&r!==e?!0:(this.element.removeEventListener(t,r,!1),!1)},this)},r.prototype.unbindAll=function(){for(var t in this.events)this.unbind(t)};var o=function(){this.eventElements=[]};o.prototype.eventElement=function(t){var e=this.eventElements.filter(function(e){return e.element===t})[0];return"undefined"==typeof e&&(e=new r(t),this.eventElements.push(e)),e},o.prototype.bind=function(t,e,n){this.eventElement(t).bind(e,n)},o.prototype.unbind=function(t,e,n){this.eventElement(t).unbind(e,n)},o.prototype.unbindAll=function(){for(var t=0;t<this.eventElements.length;t++)this.eventElements[t].unbindAll()},o.prototype.once=function(t,e,n){var r=this.eventElement(t),o=function(t){r.unbind(e,o),n(t)};r.bind(e,o)},e.exports=o},{}],5:[function(t,e,n){"use strict";e.exports=function(){function t(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}return function(){return t()+t()+"-"+t()+"-"+t()+"-"+t()+"-"+t()+t()+t()}}()},{}],6:[function(t,e,n){"use strict";var r=t("./class"),o=t("./dom");n.toInt=function(t){return parseInt(t,10)||0},n.clone=function(t){if(null===t)return null;if("object"==typeof t){var e={};for(var n in t)e[n]=this.clone(t[n]);return e}return t},n.extend=function(t,e){var n=this.clone(t);for(var r in e)n[r]=this.clone(e[r]);return n},n.isEditable=function(t){return o.matches(t,"input,[contenteditable]")||o.matches(t,"select,[contenteditable]")||o.matches(t,"textarea,[contenteditable]")||o.matches(t,"button,[contenteditable]")},n.removePsClasses=function(t){for(var e=r.list(t),n=0;n<e.length;n++){var o=e[n];0===o.indexOf("ps-")&&r.remove(t,o)}},n.outerWidth=function(t){return this.toInt(o.css(t,"width"))+this.toInt(o.css(t,"paddingLeft"))+this.toInt(o.css(t,"paddingRight"))+this.toInt(o.css(t,"borderLeftWidth"))+this.toInt(o.css(t,"borderRightWidth"))},n.startScrolling=function(t,e){r.add(t,"ps-in-scrolling"),"undefined"!=typeof e?r.add(t,"ps-"+e):(r.add(t,"ps-x"),r.add(t,"ps-y"))},n.stopScrolling=function(t,e){r.remove(t,"ps-in-scrolling"),"undefined"!=typeof e?r.remove(t,"ps-"+e):(r.remove(t,"ps-x"),r.remove(t,"ps-y"))},n.env={isWebKit:"WebkitAppearance"in document.documentElement.style,supportsTouch:"ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch,supportsIePointer:null!==window.navigator.msMaxTouchPoints}},{"./class":2,"./dom":3}],7:[function(t,e,n){"use strict";var r=t("./plugin/destroy"),o=t("./plugin/initialize"),i=t("./plugin/update");e.exports={initialize:o,update:i,destroy:r}},{"./plugin/destroy":9,"./plugin/initialize":17,"./plugin/update":21}],8:[function(t,e,n){"use strict";e.exports={maxScrollbarLength:null,minScrollbarLength:null,scrollXMarginOffset:0,scrollYMarginOffset:0,stopPropagationOnClick:!0,suppressScrollX:!1,suppressScrollY:!1,swipePropagation:!0,useBothWheelAxes:!1,useKeyboard:!0,useSelectionScroll:!1,wheelPropagation:!1,wheelSpeed:1}},{}],9:[function(t,e,n){"use strict";var r=t("../lib/dom"),o=t("../lib/helper"),i=t("./instances");e.exports=function(t){var e=i.get(t);e&&(e.event.unbindAll(),r.remove(e.scrollbarX),r.remove(e.scrollbarY),r.remove(e.scrollbarXRail),r.remove(e.scrollbarYRail),o.removePsClasses(t),i.remove(t))}},{"../lib/dom":3,"../lib/helper":6,"./instances":18}],10:[function(t,e,n){"use strict";function r(t,e){function n(t){return t.getBoundingClientRect()}e.settings.stopPropagationOnClick&&e.event.bind(e.scrollbarY,"click",function(t){t.stopPropagation()}),e.event.bind(e.scrollbarYRail,"click",function(r){var i=o.toInt(e.scrollbarYHeight/2),a=e.railYRatio*(r.pageY-window.scrollY-n(e.scrollbarYRail).top-i),c=e.railYRatio*(e.railYHeight-e.scrollbarYHeight),u=a/c;0>u?u=0:u>1&&(u=1),s(t,"top",(e.contentHeight-e.containerHeight)*u),l(t),r.stopPropagation()}),e.settings.stopPropagationOnClick&&e.event.bind(e.scrollbarY,"click",function(t){t.stopPropagation()}),e.event.bind(e.scrollbarXRail,"click",function(r){var i=o.toInt(e.scrollbarXWidth/2),a=e.railXRatio*(r.pageX-window.scrollX-n(e.scrollbarXRail).left-i),c=e.railXRatio*(e.railXWidth-e.scrollbarXWidth),u=a/c;0>u?u=0:u>1&&(u=1),s(t,"left",(e.contentWidth-e.containerWidth)*u-e.negativeScrollAdjustment),l(t),r.stopPropagation()})}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],11:[function(t,e,n){"use strict";function r(t,e){function n(n){var o=r+n*e.railXRatio,i=e.scrollbarXRail.getBoundingClientRect().left+e.railXRatio*(e.railXWidth-e.scrollbarXWidth);0>o?e.scrollbarXLeft=0:o>i?e.scrollbarXLeft=i:e.scrollbarXLeft=o;var s=l.toInt(e.scrollbarXLeft*(e.contentWidth-e.containerWidth)/(e.containerWidth-e.railXRatio*e.scrollbarXWidth))-e.negativeScrollAdjustment;c(t,"left",s)}var r=null,o=null,s=function(e){n(e.pageX-o),a(t),e.stopPropagation(),e.preventDefault()},u=function(){l.stopScrolling(t,"x"),e.event.unbind(e.ownerDocument,"mousemove",s)};e.event.bind(e.scrollbarX,"mousedown",function(n){o=n.pageX,r=l.toInt(i.css(e.scrollbarX,"left"))*e.railXRatio,l.startScrolling(t,"x"),e.event.bind(e.ownerDocument,"mousemove",s),e.event.once(e.ownerDocument,"mouseup",u),n.stopPropagation(),n.preventDefault()})}function o(t,e){function n(n){var o=r+n*e.railYRatio,i=e.scrollbarYRail.getBoundingClientRect().top+e.railYRatio*(e.railYHeight-e.scrollbarYHeight);0>o?e.scrollbarYTop=0:o>i?e.scrollbarYTop=i:e.scrollbarYTop=o;var s=l.toInt(e.scrollbarYTop*(e.contentHeight-e.containerHeight)/(e.containerHeight-e.railYRatio*e.scrollbarYHeight));c(t,"top",s)}var r=null,o=null,s=function(e){n(e.pageY-o),a(t),e.stopPropagation(),e.preventDefault()},u=function(){l.stopScrolling(t,"y"),e.event.unbind(e.ownerDocument,"mousemove",s)};e.event.bind(e.scrollbarY,"mousedown",function(n){o=n.pageY,r=l.toInt(i.css(e.scrollbarY,"top"))*e.railYRatio,l.startScrolling(t,"y"),e.event.bind(e.ownerDocument,"mousemove",s),e.event.once(e.ownerDocument,"mouseup",u),n.stopPropagation(),n.preventDefault()})}var i=t("../../lib/dom"),l=t("../../lib/helper"),s=t("../instances"),a=t("../update-geometry"),c=t("../update-scroll");e.exports=function(t){var e=s.get(t);r(t,e),o(t,e)}},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],12:[function(t,e,n){"use strict";function r(t,e){function n(n,r){var o=t.scrollTop;if(0===n){if(!e.scrollbarYActive)return!1;if(0===o&&r>0||o>=e.contentHeight-e.containerHeight&&0>r)return!e.settings.wheelPropagation}var i=t.scrollLeft;if(0===r){if(!e.scrollbarXActive)return!1;if(0===i&&0>n||i>=e.contentWidth-e.containerWidth&&n>0)return!e.settings.wheelPropagation}return!0}var r=!1;e.event.bind(t,"mouseenter",function(){r=!0}),e.event.bind(t,"mouseleave",function(){r=!1});var i=!1;e.event.bind(e.ownerDocument,"keydown",function(a){if((!a.isDefaultPrevented||!a.isDefaultPrevented())&&r){var c=document.activeElement?document.activeElement:e.ownerDocument.activeElement;if(c){for(;c.shadowRoot;)c=c.shadowRoot.activeElement;if(o.isEditable(c))return}var u=0,d=0;switch(a.which){case 37:u=-30;break;case 38:d=30;break;case 39:u=30;break;case 40:d=-30;break;case 33:d=90;break;case 32:d=a.shiftKey?90:-90;break;case 34:d=-90;break;case 35:d=a.ctrlKey?-e.contentHeight:-e.containerHeight;break;case 36:d=a.ctrlKey?t.scrollTop:e.containerHeight;break;default:return}s(t,"top",t.scrollTop-d),s(t,"left",t.scrollLeft+u),l(t),i=n(u,d),i&&a.preventDefault()}})}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],13:[function(t,e,n){"use strict";function r(t,e){function n(n,r){var o=t.scrollTop;if(0===n){if(!e.scrollbarYActive)return!1;if(0===o&&r>0||o>=e.contentHeight-e.containerHeight&&0>r)return!e.settings.wheelPropagation}var i=t.scrollLeft;if(0===r){if(!e.scrollbarXActive)return!1;if(0===i&&0>n||i>=e.contentWidth-e.containerWidth&&n>0)return!e.settings.wheelPropagation}return!0}function r(t){var e=t.deltaX,n=-1*t.deltaY;return("undefined"==typeof e||"undefined"==typeof n)&&(e=-1*t.wheelDeltaX/6,n=t.wheelDeltaY/6),t.deltaMode&&1===t.deltaMode&&(e*=10,n*=10),e!==e&&n!==n&&(e=0,n=t.wheelDelta),[e,n]}function i(e,n){var r=t.querySelector("textarea:hover");if(r){var o=r.scrollHeight-r.clientHeight;if(o>0&&!(0===r.scrollTop&&n>0||r.scrollTop===o&&0>n))return!0;var i=r.scrollLeft-r.clientWidth;if(i>0&&!(0===r.scrollLeft&&0>e||r.scrollLeft===i&&e>0))return!0}return!1}function a(a){if(o.env.isWebKit||!t.querySelector("select:focus")){var u=r(a),d=u[0],p=u[1];i(d,p)||(c=!1,e.settings.useBothWheelAxes?e.scrollbarYActive&&!e.scrollbarXActive?(p?s(t,"top",t.scrollTop-p*e.settings.wheelSpeed):s(t,"top",t.scrollTop+d*e.settings.wheelSpeed),c=!0):e.scrollbarXActive&&!e.scrollbarYActive&&(d?s(t,"left",t.scrollLeft+d*e.settings.wheelSpeed):s(t,"left",t.scrollLeft-p*e.settings.wheelSpeed),c=!0):(s(t,"top",t.scrollTop-p*e.settings.wheelSpeed),s(t,"left",t.scrollLeft+d*e.settings.wheelSpeed)),l(t),c=c||n(d,p),c&&(a.stopPropagation(),a.preventDefault()))}}var c=!1;"undefined"!=typeof window.onwheel?e.event.bind(t,"wheel",a):"undefined"!=typeof window.onmousewheel&&e.event.bind(t,"mousewheel",a)}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],14:[function(t,e,n){"use strict";function r(t,e){e.event.bind(t,"scroll",function(){i(t)})}var o=t("../instances"),i=t("../update-geometry");e.exports=function(t){var e=o.get(t);r(t,e)}},{"../instances":18,"../update-geometry":19}],15:[function(t,e,n){"use strict";function r(t,e){function n(){var t=window.getSelection?window.getSelection():document.getSelection?document.getSelection():"";return 0===t.toString().length?null:t.getRangeAt(0).commonAncestorContainer}function r(){c||(c=setInterval(function(){return i.get(t)?(s(t,"top",t.scrollTop+u.top),s(t,"left",t.scrollLeft+u.left),void l(t)):void clearInterval(c)},50))}function a(){c&&(clearInterval(c),c=null),o.stopScrolling(t)}var c=null,u={top:0,left:0},d=!1;e.event.bind(e.ownerDocument,"selectionchange",function(){t.contains(n())?d=!0:(d=!1,a())}),e.event.bind(window,"mouseup",function(){d&&(d=!1,a())}),e.event.bind(window,"mousemove",function(e){if(d){var n={x:e.pageX,y:e.pageY},i={left:t.offsetLeft,right:t.offsetLeft+t.offsetWidth,top:t.offsetTop,bottom:t.offsetTop+t.offsetHeight};n.x<i.left+3?(u.left=-5,o.startScrolling(t,"x")):n.x>i.right-3?(u.left=5,o.startScrolling(t,"x")):u.left=0,n.y<i.top+3?(i.top+3-n.y<5?u.top=-5:u.top=-20,o.startScrolling(t,"y")):n.y>i.bottom-3?(n.y-i.bottom+3<5?u.top=5:u.top=20,o.startScrolling(t,"y")):u.top=0,0===u.top&&0===u.left?a():r()}})}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],16:[function(t,e,n){"use strict";function r(t,e,n,r){function s(n,r){var o=t.scrollTop,i=t.scrollLeft,l=Math.abs(n),s=Math.abs(r);if(s>l){if(0>r&&o===e.contentHeight-e.containerHeight||r>0&&0===o)return!e.settings.swipePropagation}else if(l>s&&(0>n&&i===e.contentWidth-e.containerWidth||n>0&&0===i))return!e.settings.swipePropagation;return!0}function a(e,n){l(t,"top",t.scrollTop-n),l(t,"left",t.scrollLeft-e),i(t)}function c(){Y=!0}function u(){Y=!1}function d(t){return t.targetTouches?t.targetTouches[0]:t}function p(t){return t.targetTouches&&1===t.targetTouches.length?!0:t.pointerType&&"mouse"!==t.pointerType&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE?!0:!1}function f(t){if(p(t)){w=!0;var e=d(t);b.pageX=e.pageX,b.pageY=e.pageY,g=(new Date).getTime(),null!==y&&clearInterval(y),t.stopPropagation()}}function h(t){if(!Y&&w&&p(t)){var e=d(t),n={pageX:e.pageX,pageY:e.pageY},r=n.pageX-b.pageX,o=n.pageY-b.pageY;a(r,o),b=n;var i=(new Date).getTime(),l=i-g;l>0&&(m.x=r/l,m.y=o/l,g=i),s(r,o)&&(t.stopPropagation(),t.preventDefault())}}function v(){!Y&&w&&(w=!1,clearInterval(y),y=setInterval(function(){return o.get(t)?Math.abs(m.x)<.01&&Math.abs(m.y)<.01?void clearInterval(y):(a(30*m.x,30*m.y),m.x*=.8,void(m.y*=.8)):void clearInterval(y)},10))}var b={},g=0,m={},y=null,Y=!1,w=!1;n&&(e.event.bind(window,"touchstart",c),e.event.bind(window,"touchend",u),e.event.bind(t,"touchstart",f),e.event.bind(t,"touchmove",h),e.event.bind(t,"touchend",v)),r&&(window.PointerEvent?(e.event.bind(window,"pointerdown",c),e.event.bind(window,"pointerup",u),e.event.bind(t,"pointerdown",f),e.event.bind(t,"pointermove",h),e.event.bind(t,"pointerup",v)):window.MSPointerEvent&&(e.event.bind(window,"MSPointerDown",c),e.event.bind(window,"MSPointerUp",u),e.event.bind(t,"MSPointerDown",f),e.event.bind(t,"MSPointerMove",h),e.event.bind(t,"MSPointerUp",v)))}var o=t("../instances"),i=t("../update-geometry"),l=t("../update-scroll");e.exports=function(t,e,n){var i=o.get(t);r(t,i,e,n)}},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],17:[function(t,e,n){"use strict";var r=t("../lib/class"),o=t("../lib/helper"),i=t("./instances"),l=t("./update-geometry"),s=t("./handler/click-rail"),a=t("./handler/drag-scrollbar"),c=t("./handler/keyboard"),u=t("./handler/mouse-wheel"),d=t("./handler/native-scroll"),p=t("./handler/selection"),f=t("./handler/touch");e.exports=function(t,e){e="object"==typeof e?e:{},r.add(t,"ps-container");var n=i.add(t);n.settings=o.extend(n.settings,e),s(t),a(t),u(t),d(t),n.settings.useSelectionScroll&&p(t),(o.env.supportsTouch||o.env.supportsIePointer)&&f(t,o.env.supportsTouch,o.env.supportsIePointer),n.settings.useKeyboard&&c(t),l(t)}},{"../lib/class":2,"../lib/helper":6,"./handler/click-rail":10,"./handler/drag-scrollbar":11,"./handler/keyboard":12,"./handler/mouse-wheel":13,"./handler/native-scroll":14,"./handler/selection":15,"./handler/touch":16,"./instances":18,"./update-geometry":19}],18:[function(t,e,n){"use strict";function r(t){var e=this;e.settings=d.clone(a),e.containerWidth=null,e.containerHeight=null,e.contentWidth=null,e.contentHeight=null,e.isRtl="rtl"===s.css(t,"direction"),e.isNegativeScroll=function(){var e=t.scrollLeft,n=null;return t.scrollLeft=-1,n=t.scrollLeft<0,t.scrollLeft=e,n}(),e.negativeScrollAdjustment=e.isNegativeScroll?t.scrollWidth-t.clientWidth:0,e.event=new c,e.ownerDocument=t.ownerDocument||document,e.scrollbarXRail=s.appendTo(s.e("div","ps-scrollbar-x-rail"),t),e.scrollbarX=s.appendTo(s.e("div","ps-scrollbar-x"),e.scrollbarXRail),e.scrollbarXActive=null,e.scrollbarXWidth=null,e.scrollbarXLeft=null,e.scrollbarXBottom=d.toInt(s.css(e.scrollbarXRail,"bottom")),e.isScrollbarXUsingBottom=e.scrollbarXBottom===e.scrollbarXBottom,e.scrollbarXTop=e.isScrollbarXUsingBottom?null:d.toInt(s.css(e.scrollbarXRail,"top")),e.railBorderXWidth=d.toInt(s.css(e.scrollbarXRail,"borderLeftWidth"))+d.toInt(s.css(e.scrollbarXRail,"borderRightWidth")),s.css(e.scrollbarXRail,"display","block"),e.railXMarginWidth=d.toInt(s.css(e.scrollbarXRail,"marginLeft"))+d.toInt(s.css(e.scrollbarXRail,"marginRight")),s.css(e.scrollbarXRail,"display",""),e.railXWidth=null,e.railXRatio=null,e.scrollbarYRail=s.appendTo(s.e("div","ps-scrollbar-y-rail"),t),e.scrollbarY=s.appendTo(s.e("div","ps-scrollbar-y"),e.scrollbarYRail),e.scrollbarYActive=null,e.scrollbarYHeight=null,e.scrollbarYTop=null,e.scrollbarYRight=d.toInt(s.css(e.scrollbarYRail,"right")),e.isScrollbarYUsingRight=e.scrollbarYRight===e.scrollbarYRight,e.scrollbarYLeft=e.isScrollbarYUsingRight?null:d.toInt(s.css(e.scrollbarYRail,"left")),e.scrollbarYOuterWidth=e.isRtl?d.outerWidth(e.scrollbarY):null,e.railBorderYWidth=d.toInt(s.css(e.scrollbarYRail,"borderTopWidth"))+d.toInt(s.css(e.scrollbarYRail,"borderBottomWidth")),s.css(e.scrollbarYRail,"display","block"),e.railYMarginHeight=d.toInt(s.css(e.scrollbarYRail,"marginTop"))+d.toInt(s.css(e.scrollbarYRail,"marginBottom")),s.css(e.scrollbarYRail,"display",""),e.railYHeight=null,e.railYRatio=null}function o(t){return"undefined"==typeof t.dataset?t.getAttribute("data-ps-id"):t.dataset.psId}function i(t,e){"undefined"==typeof t.dataset?t.setAttribute("data-ps-id",e):t.dataset.psId=e}function l(t){"undefined"==typeof t.dataset?t.removeAttribute("data-ps-id"):delete t.dataset.psId}var s=t("../lib/dom"),a=t("./default-setting"),c=t("../lib/event-manager"),u=t("../lib/guid"),d=t("../lib/helper"),p={};n.add=function(t){var e=u();return i(t,e),p[e]=new r(t),p[e]},n.remove=function(t){delete p[o(t)],l(t)},n.get=function(t){return p[o(t)]}},{"../lib/dom":3,"../lib/event-manager":4,"../lib/guid":5,"../lib/helper":6,"./default-setting":8}],19:[function(t,e,n){"use strict";function r(t,e){return t.settings.minScrollbarLength&&(e=Math.max(e,t.settings.minScrollbarLength)),t.settings.maxScrollbarLength&&(e=Math.min(e,t.settings.maxScrollbarLength)),e}function o(t,e){var n={width:e.railXWidth};e.isRtl?n.left=e.negativeScrollAdjustment+t.scrollLeft+e.containerWidth-e.contentWidth:n.left=t.scrollLeft,e.isScrollbarXUsingBottom?n.bottom=e.scrollbarXBottom-t.scrollTop:n.top=e.scrollbarXTop+t.scrollTop,l.css(e.scrollbarXRail,n);var r={top:t.scrollTop,height:e.railYHeight};e.isScrollbarYUsingRight?e.isRtl?r.right=e.contentWidth-(e.negativeScrollAdjustment+t.scrollLeft)-e.scrollbarYRight-e.scrollbarYOuterWidth:r.right=e.scrollbarYRight-t.scrollLeft:e.isRtl?r.left=e.negativeScrollAdjustment+t.scrollLeft+2*e.containerWidth-e.contentWidth-e.scrollbarYLeft-e.scrollbarYOuterWidth:r.left=e.scrollbarYLeft+t.scrollLeft,l.css(e.scrollbarYRail,r),l.css(e.scrollbarX,{left:e.scrollbarXLeft,width:e.scrollbarXWidth-e.railBorderXWidth}),l.css(e.scrollbarY,{top:e.scrollbarYTop,height:e.scrollbarYHeight-e.railBorderYWidth})}var i=t("../lib/class"),l=t("../lib/dom"),s=t("../lib/helper"),a=t("./instances"),c=t("./update-scroll");e.exports=function(t){var e=a.get(t);e.containerWidth=t.clientWidth,e.containerHeight=t.clientHeight,e.contentWidth=t.scrollWidth,e.contentHeight=t.scrollHeight;var n;t.contains(e.scrollbarXRail)||(n=l.queryChildren(t,".ps-scrollbar-x-rail"),n.length>0&&n.forEach(function(t){l.remove(t)}),l.appendTo(e.scrollbarXRail,t)),t.contains(e.scrollbarYRail)||(n=l.queryChildren(t,".ps-scrollbar-y-rail"),n.length>0&&n.forEach(function(t){l.remove(t)}),l.appendTo(e.scrollbarYRail,t)),!e.settings.suppressScrollX&&e.containerWidth+e.settings.scrollXMarginOffset<e.contentWidth?(e.scrollbarXActive=!0,e.railXWidth=e.containerWidth-e.railXMarginWidth,e.railXRatio=e.containerWidth/e.railXWidth,e.scrollbarXWidth=r(e,s.toInt(e.railXWidth*e.containerWidth/e.contentWidth)),e.scrollbarXLeft=s.toInt((e.negativeScrollAdjustment+t.scrollLeft)*(e.railXWidth-e.scrollbarXWidth)/(e.contentWidth-e.containerWidth))):(e.scrollbarXActive=!1,e.scrollbarXWidth=0,e.scrollbarXLeft=0,t.scrollLeft=0),!e.settings.suppressScrollY&&e.containerHeight+e.settings.scrollYMarginOffset<e.contentHeight?(e.scrollbarYActive=!0,e.railYHeight=e.containerHeight-e.railYMarginHeight,e.railYRatio=e.containerHeight/e.railYHeight,e.scrollbarYHeight=r(e,s.toInt(e.railYHeight*e.containerHeight/e.contentHeight)),e.scrollbarYTop=s.toInt(t.scrollTop*(e.railYHeight-e.scrollbarYHeight)/(e.contentHeight-e.containerHeight))):(e.scrollbarYActive=!1,e.scrollbarYHeight=0,e.scrollbarYTop=0,c(t,"top",0)),e.scrollbarXLeft>=e.railXWidth-e.scrollbarXWidth&&(e.scrollbarXLeft=e.railXWidth-e.scrollbarXWidth),e.scrollbarYTop>=e.railYHeight-e.scrollbarYHeight&&(e.scrollbarYTop=e.railYHeight-e.scrollbarYHeight),o(t,e),i[e.scrollbarXActive?"add":"remove"](t,"ps-active-x"),i[e.scrollbarYActive?"add":"remove"](t,"ps-active-y")}},{"../lib/class":2,"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-scroll":20}],20:[function(t,e,n){"use strict";var r,o,i=t("./instances"),l=document.createEvent("Event"),s=document.createEvent("Event"),a=document.createEvent("Event"),c=document.createEvent("Event"),u=document.createEvent("Event"),d=document.createEvent("Event"),p=document.createEvent("Event"),f=document.createEvent("Event"),h=document.createEvent("Event"),v=document.createEvent("Event");l.initEvent("ps-scroll-up",!0,!0),s.initEvent("ps-scroll-down",!0,!0),a.initEvent("ps-scroll-left",!0,!0),c.initEvent("ps-scroll-right",!0,!0),u.initEvent("ps-scroll-y",!0,!0),d.initEvent("ps-scroll-x",!0,!0),p.initEvent("ps-x-reach-start",!0,!0),f.initEvent("ps-x-reach-end",!0,!0),h.initEvent("ps-y-reach-start",!0,!0),v.initEvent("ps-y-reach-end",!0,!0),e.exports=function(t,e,n){if("undefined"==typeof t)throw"You must provide an element to the update-scroll function";if("undefined"==typeof e)throw"You must provide an axis to the update-scroll function";if("undefined"==typeof n)throw"You must provide a value to the update-scroll function";if("top"===e&&0>=n)return t.scrollTop=0,void t.dispatchEvent(h);if("left"===e&&0>=n)return t.scrollLeft=0,void t.dispatchEvent(p);var b=i.get(t);return"top"===e&&n>b.contentHeight-b.containerHeight?(t.scrollTop=b.contentHeight-b.containerHeight,void t.dispatchEvent(v)):"left"===e&&n>b.contentWidth-b.containerWidth?(t.scrollLeft=b.contentWidth-b.containerWidth,void t.dispatchEvent(f)):(r||(r=t.scrollTop),o||(o=t.scrollLeft),"top"===e&&r>n&&t.dispatchEvent(l),"top"===e&&n>r&&t.dispatchEvent(s),"left"===e&&o>n&&t.dispatchEvent(a),"left"===e&&n>o&&t.dispatchEvent(c),"top"===e&&(t.scrollTop=r=n,t.dispatchEvent(u)),void("left"===e&&(t.scrollLeft=o=n,t.dispatchEvent(d))))}},{"./instances":18}],21:[function(t,e,n){"use strict";var r=t("../lib/dom"),o=t("../lib/helper"),i=t("./instances"),l=t("./update-geometry");e.exports=function(t){var e=i.get(t);e&&(e.negativeScrollAdjustment=e.isNegativeScroll?t.scrollWidth-t.clientWidth:0,r.css(e.scrollbarXRail,"display","block"),r.css(e.scrollbarYRail,"display","block"),e.railXMarginWidth=o.toInt(r.css(e.scrollbarXRail,"marginLeft"))+o.toInt(r.css(e.scrollbarXRail,"marginRight")),e.railYMarginHeight=o.toInt(r.css(e.scrollbarYRail,"marginTop"))+o.toInt(r.css(e.scrollbarYRail,"marginBottom")),r.css(e.scrollbarXRail,"display","none"),r.css(e.scrollbarYRail,"display","none"),l(t),r.css(e.scrollbarXRail,"display",""),r.css(e.scrollbarYRail,"display",""))}},{"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-geometry":19}]},{},[1]); \ No newline at end of file
diff --git a/web/templates/channel.html b/web/templates/channel.html
index aabd01ecb..c15cea178 100644
--- a/web/templates/channel.html
+++ b/web/templates/channel.html
@@ -25,8 +25,6 @@
<div id="new_channel_modal"></div>
<div id="post_deleted_modal"></div>
<div id="channel_notifications_modal"></div>
- <div id="channel_members_modal"></div>
- <div id="channel_invite_modal"></div>
<div id="team_members_modal"></div>
<div id="direct_channel_modal"></div>
<div id="channel_info_modal"></div>
diff --git a/web/templates/head.html b/web/templates/head.html
index 24f9862c0..a73e809a7 100644
--- a/web/templates/head.html
+++ b/web/templates/head.html
@@ -85,7 +85,7 @@
!function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="3.0.1";
analytics.load(window.mm_config.SegmentDeveloperKey);
if (window.mm_user) {
- analytics.identify(user.id, {
+ analytics.identify(window.mm_user.id, {
name: window.mm_user.nickname,
email: window.mm_user.email,
createdAt: window.mm_user.create_at,