summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/channel.go2
-rw-r--r--api/import.go57
-rw-r--r--api/post.go3
-rw-r--r--api/slackimport.go244
-rw-r--r--api/team.go70
-rw-r--r--api/user.go3
-rw-r--r--config/config.json3
-rw-r--r--docker/0.6/config_docker.json1
-rw-r--r--docker/dev/README.md43
-rw-r--r--docker/dev/config_docker.json1
-rw-r--r--docker/local/config_docker.json1
-rw-r--r--model/post.go4
-rw-r--r--utils/config.go1
-rw-r--r--utils/mail.go6
-rw-r--r--web/react/.eslintrc287
-rw-r--r--web/react/components/login.jsx2
-rw-r--r--web/react/components/post.jsx3
-rw-r--r--web/react/components/post_info.jsx171
-rw-r--r--web/react/components/post_list.jsx45
-rw-r--r--web/react/components/setting_item_max.jsx4
-rw-r--r--web/react/components/setting_upload.jsx79
-rw-r--r--web/react/components/settings_sidebar.jsx2
-rw-r--r--web/react/components/team_feature_tab.jsx147
-rw-r--r--web/react/components/team_import_tab.jsx68
-rw-r--r--web/react/components/team_settings.jsx181
-rw-r--r--web/react/components/team_settings_modal.jsx42
-rw-r--r--web/react/components/user_settings.jsx1202
-rw-r--r--web/react/components/user_settings_appearance.jsx118
-rw-r--r--web/react/components/user_settings_general.jsx428
-rw-r--r--web/react/components/user_settings_notifications.jsx484
-rw-r--r--web/react/components/user_settings_security.jsx200
-rw-r--r--web/react/package.json27
-rw-r--r--web/react/stores/post_store.jsx22
-rw-r--r--web/react/utils/client.jsx392
-rw-r--r--web/react/utils/constants.jsx5
-rw-r--r--web/react/utils/utils.jsx930
-rw-r--r--web/templates/head.html8
37 files changed, 3085 insertions, 2201 deletions
diff --git a/api/channel.go b/api/channel.go
index be6a8b064..5f3282072 100644
--- a/api/channel.go
+++ b/api/channel.go
@@ -399,7 +399,7 @@ func JoinChannel(c *Context, channelId string, role string) {
}
}
-func JoinDefaultChannels(c *Context, user *model.User, channelRole string) *model.AppError {
+func JoinDefaultChannels(user *model.User, channelRole string) *model.AppError {
// We don't call JoinChannel here since c.Session is not populated on user creation
var err *model.AppError = nil
diff --git a/api/import.go b/api/import.go
new file mode 100644
index 000000000..e3f314d20
--- /dev/null
+++ b/api/import.go
@@ -0,0 +1,57 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api
+
+import (
+ l4g "code.google.com/p/log4go"
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+)
+
+//
+// Import functions are sutible for entering posts and users into the database without
+// some of the usual checks. (IsValid is still run)
+//
+
+func ImportPost(post *model.Post) {
+ post.Hashtags, _ = model.ParseHashtags(post.Message)
+
+ if result := <-Srv.Store.Post().Save(post); result.Err != nil {
+ l4g.Debug("Error saving post. user=" + post.UserId + ", message=" + post.Message)
+ }
+}
+
+func ImportUser(user *model.User) *model.User {
+ user.MakeNonNil()
+ if len(user.Props["theme"]) == 0 {
+ user.AddProp("theme", utils.Cfg.TeamSettings.DefaultThemeColor)
+ }
+
+ if result := <-Srv.Store.User().Save(user); result.Err != nil {
+ l4g.Error("Error saving user. err=%v", result.Err)
+ return nil
+ } else {
+ ruser := result.Data.(*model.User)
+
+ if err := JoinDefaultChannels(ruser, ""); err != nil {
+ l4g.Error("Encountered an issue joining default channels user_id=%s, team_id=%s, err=%v", ruser.Id, ruser.TeamId, err)
+ }
+
+ if cresult := <-Srv.Store.User().VerifyEmail(ruser.Id); cresult.Err != nil {
+ l4g.Error("Failed to set email verified err=%v", cresult.Err)
+ }
+
+ return ruser
+ }
+}
+
+func ImportChannel(channel *model.Channel) *model.Channel {
+ if result := <-Srv.Store.Channel().Save(channel); result.Err != nil {
+ return nil
+ } else {
+ sc := result.Data.(*model.Channel)
+
+ return sc
+ }
+}
diff --git a/api/post.go b/api/post.go
index f6699d181..c013df87f 100644
--- a/api/post.go
+++ b/api/post.go
@@ -696,8 +696,7 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
}
message := model.NewMessage(c.Session.TeamId, post.ChannelId, c.Session.UserId, model.ACTION_POST_DELETED)
- message.Add("post_id", post.Id)
- message.Add("channel_id", post.ChannelId)
+ message.Add("post", post.ToJson())
PublishAndForget(message)
diff --git a/api/slackimport.go b/api/slackimport.go
new file mode 100644
index 000000000..ca3fdf3d1
--- /dev/null
+++ b/api/slackimport.go
@@ -0,0 +1,244 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api
+
+import (
+ "archive/zip"
+ "bytes"
+ l4g "code.google.com/p/log4go"
+ "encoding/json"
+ "github.com/mattermost/platform/model"
+ "io"
+ "mime/multipart"
+ "strconv"
+ "strings"
+)
+
+type SlackChannel struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Members []string `json:"members"`
+ Topic map[string]string `json:"topic"`
+}
+
+type SlackUser struct {
+ Id string `json:"id"`
+ Username string `json:"name"`
+ Profile map[string]string `json:"profile"`
+}
+
+type SlackPost struct {
+ User string `json:"user"`
+ BotId string `json:"bot_id"`
+ BotUsername string `json:"username"`
+ Text string `json:"text"`
+ TimeStamp string `json:"ts"`
+ Type string `json:"type"`
+ SubType string `json:"subtype"`
+ Comment map[string]string `json:"comment"`
+}
+
+func SlackConvertTimeStamp(ts string) int64 {
+ timeString := strings.SplitN(ts, ".", 2)[0]
+
+ timeStamp, err := strconv.ParseInt(timeString, 10, 64)
+ if err != nil {
+ l4g.Warn("Bad timestamp detected")
+ return 1
+ }
+ return timeStamp * 1000 // Convert to milliseconds
+}
+
+func SlackParseChannels(data io.Reader) []SlackChannel {
+ decoder := json.NewDecoder(data)
+
+ var channels []SlackChannel
+ if err := decoder.Decode(&channels); err != nil {
+ return make([]SlackChannel, 0)
+ }
+ return channels
+}
+
+func SlackParseUsers(data io.Reader) []SlackUser {
+ decoder := json.NewDecoder(data)
+
+ var users []SlackUser
+ if err := decoder.Decode(&users); err != nil {
+ return make([]SlackUser, 0)
+ }
+ return users
+}
+
+func SlackParsePosts(data io.Reader) []SlackPost {
+ decoder := json.NewDecoder(data)
+
+ var posts []SlackPost
+ if err := decoder.Decode(&posts); err != nil {
+ return make([]SlackPost, 0)
+ }
+ return posts
+}
+
+func SlackAddUsers(teamId string, slackusers []SlackUser, log *bytes.Buffer) map[string]*model.User {
+ // Log header
+ log.WriteString("\n Users Created\n")
+ log.WriteString("===============\n\n")
+
+ addedUsers := make(map[string]*model.User)
+ for _, sUser := range slackusers {
+ firstName := ""
+ lastName := ""
+ if name, ok := sUser.Profile["first_name"]; ok {
+ firstName = name
+ }
+ if name, ok := sUser.Profile["last_name"]; ok {
+ lastName = name
+ }
+
+ password := model.NewId()
+
+ newUser := model.User{
+ TeamId: teamId,
+ Username: sUser.Username,
+ FirstName: firstName,
+ LastName: lastName,
+ Email: sUser.Profile["email"],
+ Password: password,
+ }
+
+ if mUser := ImportUser(&newUser); mUser != nil {
+ addedUsers[sUser.Id] = mUser
+ log.WriteString("Email, Password: " + newUser.Email + ", " + password + "\n")
+ } else {
+ log.WriteString("Unable to import user: " + sUser.Username)
+ }
+ }
+
+ return addedUsers
+}
+
+func SlackAddPosts(channel *model.Channel, posts []SlackPost, users map[string]*model.User) {
+ for _, sPost := range posts {
+ switch {
+ case sPost.Type == "message" && (sPost.SubType == "" || sPost.SubType == "file_share"):
+ if sPost.User == "" {
+ l4g.Debug("Message without user")
+ continue
+ } else if users[sPost.User] == nil {
+ l4g.Debug("User: " + sPost.User + " does not exist!")
+ continue
+ }
+ newPost := model.Post{
+ UserId: users[sPost.User].Id,
+ ChannelId: channel.Id,
+ Message: sPost.Text,
+ CreateAt: SlackConvertTimeStamp(sPost.TimeStamp),
+ }
+ ImportPost(&newPost)
+ case sPost.Type == "message" && sPost.SubType == "file_comment":
+ if sPost.Comment["user"] == "" {
+ l4g.Debug("Message without user")
+ continue
+ } else if users[sPost.Comment["user"]] == nil {
+ l4g.Debug("User: " + sPost.User + " does not exist!")
+ continue
+ }
+ newPost := model.Post{
+ UserId: users[sPost.Comment["user"]].Id,
+ ChannelId: channel.Id,
+ Message: sPost.Comment["comment"],
+ CreateAt: SlackConvertTimeStamp(sPost.TimeStamp),
+ }
+ ImportPost(&newPost)
+ case sPost.Type == "message" && sPost.SubType == "bot_message":
+ // In the future this will use the "Action Post" spec to post
+ // a message without using a username. For now we just warn that we don't handle this case
+ l4g.Warn("Slack bot posts are not imported yet")
+ default:
+ l4g.Warn("Unsupported post type: " + sPost.Type + ", " + sPost.SubType)
+ }
+ }
+}
+
+func SlackAddChannels(teamId string, slackchannels []SlackChannel, posts map[string][]SlackPost, users map[string]*model.User, log *bytes.Buffer) map[string]*model.Channel {
+ // Write Header
+ log.WriteString("\n Channels Added \n")
+ log.WriteString("=================\n\n")
+
+ addedChannels := make(map[string]*model.Channel)
+ for _, sChannel := range slackchannels {
+ newChannel := model.Channel{
+ TeamId: teamId,
+ Type: model.CHANNEL_OPEN,
+ DisplayName: sChannel.Name,
+ Name: sChannel.Name,
+ Description: sChannel.Topic["value"],
+ }
+ mChannel := ImportChannel(&newChannel)
+ if mChannel == nil {
+ // Maybe it already exists?
+ if result := <-Srv.Store.Channel().GetByName(teamId, sChannel.Name); result.Err != nil {
+ l4g.Debug("Failed to import: %s", newChannel.DisplayName)
+ log.WriteString("Failed to import: " + newChannel.DisplayName + "\n")
+ continue
+ } else {
+ mChannel = result.Data.(*model.Channel)
+ log.WriteString("Merged with existing channel: " + newChannel.DisplayName + "\n")
+ }
+ }
+ log.WriteString(newChannel.DisplayName + "\n")
+ addedChannels[sChannel.Id] = mChannel
+ SlackAddPosts(mChannel, posts[sChannel.Name], users)
+ }
+
+ return addedChannels
+}
+
+func SlackImport(fileData multipart.File, fileSize int64, teamID string) (*model.AppError, *bytes.Buffer) {
+ zipreader, err := zip.NewReader(fileData, fileSize)
+ if err != nil || zipreader.File == nil {
+ return model.NewAppError("SlackImport", "Unable to open zip file", err.Error()), nil
+ }
+
+ // Create log file
+ log := bytes.NewBufferString("Mattermost Slack Import Log\n")
+
+ var channels []SlackChannel
+ var users []SlackUser
+ posts := make(map[string][]SlackPost)
+ for _, file := range zipreader.File {
+ reader, err := file.Open()
+ if err != nil {
+ return model.NewAppError("SlackImport", "Unable to open: "+file.Name, err.Error()), log
+ }
+ if file.Name == "channels.json" {
+ channels = SlackParseChannels(reader)
+ } else if file.Name == "users.json" {
+ users = SlackParseUsers(reader)
+ } else {
+ spl := strings.Split(file.Name, "/")
+ if len(spl) == 2 && strings.HasSuffix(spl[1], ".json") {
+ newposts := SlackParsePosts(reader)
+ channel := spl[0]
+ if _, ok := posts[channel]; ok == false {
+ posts[channel] = newposts
+ } else {
+ posts[channel] = append(posts[channel], newposts...)
+ }
+ }
+
+ }
+ }
+
+ addedUsers := SlackAddUsers(teamID, users, log)
+ SlackAddChannels(teamID, channels, posts, addedUsers, log)
+
+ log.WriteString("\n Notes \n")
+ log.WriteString("=======\n\n")
+
+ log.WriteString("- Some posts may not have been imported because they where not supported by this importer.\n")
+ log.WriteString("- Slack bot posts are currently not supported.\n")
+
+ return nil, log
+}
diff --git a/api/team.go b/api/team.go
index e6b8f4e14..a331e9e34 100644
--- a/api/team.go
+++ b/api/team.go
@@ -4,6 +4,7 @@
package api
import (
+ "bytes"
l4g "code.google.com/p/log4go"
"fmt"
"github.com/gorilla/mux"
@@ -13,6 +14,7 @@ import (
"net/url"
"strconv"
"strings"
+ "time"
)
func InitTeam(r *mux.Router) {
@@ -29,6 +31,7 @@ func InitTeam(r *mux.Router) {
sr.Handle("/update_name", ApiUserRequired(updateTeamDisplayName)).Methods("POST")
sr.Handle("/update_valet_feature", ApiUserRequired(updateValetFeature)).Methods("POST")
sr.Handle("/me", ApiUserRequired(getMyTeam)).Methods("GET")
+ sr.Handle("/import_team", ApiUserRequired(importTeam)).Methods("POST")
}
func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -489,3 +492,70 @@ func getMyTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
}
+
+func importTeam(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !c.HasPermissionsToTeam(c.Session.TeamId, "import") || !c.IsTeamAdmin(c.Session.UserId) {
+ c.Err = model.NewAppError("importTeam", "Only a team admin can import data.", "userId="+c.Session.UserId)
+ c.Err.StatusCode = http.StatusForbidden
+ return
+ }
+
+ if err := r.ParseMultipartForm(10000000); err != nil {
+ c.Err = model.NewAppError("importTeam", "Could not parse multipart form", err.Error())
+ return
+ }
+
+ importFromArray, ok := r.MultipartForm.Value["importFrom"]
+ importFrom := importFromArray[0]
+
+ fileSizeStr, ok := r.MultipartForm.Value["filesize"]
+ if !ok {
+ c.Err = model.NewAppError("importTeam", "Filesize unavilable", "")
+ c.Err.StatusCode = http.StatusBadRequest
+ return
+ }
+
+ fileSize, err := strconv.ParseInt(fileSizeStr[0], 10, 64)
+ if err != nil {
+ c.Err = model.NewAppError("importTeam", "Filesize not an integer", "")
+ c.Err.StatusCode = http.StatusBadRequest
+ return
+ }
+
+ fileInfoArray, ok := r.MultipartForm.File["file"]
+ if !ok {
+ c.Err = model.NewAppError("importTeam", "No file under 'file' in request", "")
+ c.Err.StatusCode = http.StatusBadRequest
+ return
+ }
+
+ if len(fileInfoArray) <= 0 {
+ c.Err = model.NewAppError("importTeam", "Empty array under 'file' in request", "")
+ c.Err.StatusCode = http.StatusBadRequest
+ return
+ }
+
+ fileInfo := fileInfoArray[0]
+
+ fileData, err := fileInfo.Open()
+ defer fileData.Close()
+ if err != nil {
+ c.Err = model.NewAppError("importTeam", "Could not open file", err.Error())
+ c.Err.StatusCode = http.StatusBadRequest
+ return
+ }
+
+ var log *bytes.Buffer
+ switch importFrom {
+ case "slack":
+ var err *model.AppError
+ if err, log = SlackImport(fileData, fileSize, c.Session.TeamId); err != nil {
+ c.Err = err
+ c.Err.StatusCode = http.StatusBadRequest
+ }
+ }
+
+ w.Header().Set("Content-Disposition", "attachment; filename=MattermostImportLog.txt")
+ w.Header().Set("Content-Type", "application/octet-stream")
+ http.ServeContent(w, r, "MattermostImportLog.txt", time.Now(), bytes.NewReader(log.Bytes()))
+}
diff --git a/api/user.go b/api/user.go
index 2e71ddfc6..05ccd03e8 100644
--- a/api/user.go
+++ b/api/user.go
@@ -181,12 +181,13 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User {
if result := <-Srv.Store.User().Save(user); result.Err != nil {
c.Err = result.Err
+ l4g.Error("Filae err=%v", result.Err)
return nil
} else {
ruser := result.Data.(*model.User)
// Soft error if there is an issue joining the default channels
- if err := JoinDefaultChannels(c, ruser, channelRole); err != nil {
+ if err := JoinDefaultChannels(ruser, channelRole); err != nil {
l4g.Error("Encountered an issue joining default channels user_id=%s, team_id=%s, err=%v", ruser.Id, ruser.TeamId, err)
}
diff --git a/config/config.json b/config/config.json
index e7134cba5..f1f3ba22c 100644
--- a/config/config.json
+++ b/config/config.json
@@ -73,7 +73,8 @@
"SMTPUsername": "",
"SMTPPassword": "",
"SMTPServer": "",
- "UseTLS": false,
+ "UseTLS": false,
+ "UseStartTLS": false,
"FeedbackEmail": "",
"FeedbackName": "",
"ApplePushServer": "",
diff --git a/docker/0.6/config_docker.json b/docker/0.6/config_docker.json
index 128dc1274..157120b99 100644
--- a/docker/0.6/config_docker.json
+++ b/docker/0.6/config_docker.json
@@ -64,6 +64,7 @@
"SMTPPassword": "",
"SMTPServer": "",
"UseTLS": false,
+ "UseStartTLS": false,
"FeedbackEmail": "",
"FeedbackName": "",
"ApplePushServer": "",
diff --git a/docker/dev/README.md b/docker/dev/README.md
new file mode 100644
index 000000000..a4b6fb9bb
--- /dev/null
+++ b/docker/dev/README.md
@@ -0,0 +1,43 @@
+**Mattermost Alpha**
+**Team Communication Service**
+**Development Build**
+
+
+About Mattermost
+================
+
+Mattermost is an open-source team communication service. It brings team messaging and file sharing into one place, accessible across PCs and phones, with archiving and search.
+
+
+
+Installing Mattermost
+=====================
+
+You're installing "Mattermost Alpha", a pre-released version providing an early look at what we're building. While the core team runs this version internally, it's not recommended for production since we can't guarantee API stability or backwards compatibility.
+
+That said, any issues at all, please let us know on the Mattermost forum.
+
+Notes:
+- For Alpha, Docker is intentionally setup as a single container, since production deployment is not yet recommended.
+
+Local Machine Setup (Docker)
+-----------------------------
+
+To run an instance of the latest version of mattermost on your local machine you can run:
+
+`docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform`
+
+To update this image to the latest version you can run:
+
+`docker pull mattermost/platform`
+
+To run an instance of the latest code from the master branch on GitHub you can run:
+
+`docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:dev`
+
+
+License
+-------
+
+Mattermost is licensed under an "Apache-wrapped AGPL" model inspired by MongoDB. Similar to MongoDB, you can run and link to the system using Configuration Files and Admin Tools licensed under Apache, version 2.0, as described in the LICENSE file, as an explicit exception to the terms of the GNU Affero General Public License (AGPL) that applies to most of the remaining source files. See individual files for details.
+
diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json
index cd612c7fe..d336300ca 100644
--- a/docker/dev/config_docker.json
+++ b/docker/dev/config_docker.json
@@ -64,6 +64,7 @@
"SMTPPassword": "",
"SMTPServer": "",
"UseTLS": false,
+ "UseStartTLS": false,
"FeedbackEmail": "",
"FeedbackName": "",
"ApplePushServer": "",
diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json
index cd612c7fe..d336300ca 100644
--- a/docker/local/config_docker.json
+++ b/docker/local/config_docker.json
@@ -64,6 +64,7 @@
"SMTPPassword": "",
"SMTPServer": "",
"UseTLS": false,
+ "UseStartTLS": false,
"FeedbackEmail": "",
"FeedbackName": "",
"ApplePushServer": "",
diff --git a/model/post.go b/model/post.go
index 0c035d4e7..f8a3032a3 100644
--- a/model/post.go
+++ b/model/post.go
@@ -120,7 +120,9 @@ func (o *Post) PreSave() {
o.OriginalId = ""
- o.CreateAt = GetMillis()
+ if o.CreateAt <= 0 {
+ o.CreateAt = GetMillis()
+ }
o.UpdateAt = o.CreateAt
if o.Props == nil {
diff --git a/utils/config.go b/utils/config.go
index a3944f670..536d0d802 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -85,6 +85,7 @@ type EmailSettings struct {
SMTPPassword string
SMTPServer string
UseTLS bool
+ UseStartTLS bool
FeedbackEmail string
FeedbackName string
ApplePushServer string
diff --git a/utils/mail.go b/utils/mail.go
index d152b2669..f02e21253 100644
--- a/utils/mail.go
+++ b/utils/mail.go
@@ -73,6 +73,12 @@ func newSMTPClient(conn net.Conn) (*smtp.Client, *model.AppError) {
if err = c.Auth(auth); err != nil {
return nil, model.NewAppError("SendMail", "Failed to authenticate on SMTP server", err.Error())
}
+ } else if Cfg.EmailSettings.UseStartTLS {
+ tlsconfig := &tls.Config{
+ InsecureSkipVerify: true,
+ ServerName: host,
+ }
+ c.StartTLS(tlsconfig)
}
return c, nil
}
diff --git a/web/react/.eslintrc b/web/react/.eslintrc
index d8b36f6ca..cdf96905b 100644
--- a/web/react/.eslintrc
+++ b/web/react/.eslintrc
@@ -1,139 +1,160 @@
{
- "ecmaFeatures": {
- "jsx": true,
- "blockBindings": true,
- "modules": true
- },
- "plugins": [
- "react"
- ],
- "env": {
- "browser": true,
- "node": true,
- "jquery": true,
- "es6": true
- },
- "globals": {
- "React": false
- },
- "rules": {
- "comma-dangle": [2, "never"],
- "no-cond-assign": [2, "except-parens"],
- "no-console": 1,
- "no-constant-condition": 1,
- "no-debugger": 1,
- "no-dupe-args": 2,
- "no-dupe-keys": 2,
- "no-duplicate-case": 2,
- "no-empty": 1,
- "no-ex-assign": 1,
- "no-extra-semi": 2,
- "no-func-assign": 1,
- "no-inner-declarations": 0,
- "no-invalid-regexp": 2,
- "no-irregular-whitespace": 2,
- "no-unreachable": 2,
- "valid-typeof": 2,
- "no-unexpected-multiline": 2,
+ "extends": "eslint:recommended",
+ "ecmaFeatures": {
+ "jsx": true,
+ "blockBindings": true,
+ "modules": true,
+ "classes": true
+ },
+ "plugins": [
+ "react"
+ ],
+ "env": {
+ "browser": true,
+ "node": true,
+ "jquery": true,
+ "es6": true
+ },
+ "globals": {
+ "React": false
+ },
+ "rules": {
+ "comma-dangle": [2, "never"],
+ "no-cond-assign": [2, "except-parens"],
+ "no-console": 1,
+ "no-constant-condition": 1,
+ "no-debugger": 1,
+ "no-dupe-args": 2,
+ "no-dupe-keys": 2,
+ "no-duplicate-case": 2,
+ "no-empty": 1,
+ "no-ex-assign": 1,
+ "no-extra-semi": 2,
+ "no-func-assign": 1,
+ "no-inner-declarations": 0,
+ "no-invalid-regexp": 2,
+ "no-irregular-whitespace": 2,
+ "no-unreachable": 2,
+ "valid-typeof": 2,
+ "no-unexpected-multiline": 2,
- "block-scoped-var": 1,
- "complexity": [1, 8],
- "consistent-return": 2,
- "curly": [2, "all"],
- "dot-notation": 2,
- "dot-location": [2, "object"],
- "eqeqeq": [2, "smart"],
- "guard-for-in": 1,
- "no-alert": 1,
- "no-caller": 2,
- "no-div-regex": 1,
- "no-else-return": 1,
- "no-eval": 2,
- "no-extend-native": 2,
- "no-floating-decimal": 2,
- "no-labels": 2,
- "no-lone-blocks": 1,
- "no-multi-spaces": [2, { "exceptions": { "Property": false } }],
- "no-multi-str": 0,
- "no-param-reassign": 2,
- "no-process-env": 2,
- "no-redeclare": 2,
- "no-return-assign": [2, "always"],
- "no-script-url": 2,
- "no-self-compare": 2,
- "no-sequences": 2,
- "no-throw-literal": 2,
- "no-unused-expressions": 2,
- "no-void": 2,
- "no-warning-comments": 0,
- "no-with": 2,
- "radix": 2,
- "vars-on-top": 0,
- "wrap-iife": [2, "outside"],
- "yoda": [2, "never", {"exceptRange": false, "onlyEquality": false}],
+ "block-scoped-var": 1,
+ "complexity": [1, 8],
+ "consistent-return": 2,
+ "curly": [2, "all"],
+ "dot-notation": 2,
+ "dot-location": [2, "object"],
+ "eqeqeq": [2, "smart"],
+ "guard-for-in": 1,
+ "no-alert": 1,
+ "no-array-constructor": 2,
+ "no-caller": 2,
+ "no-div-regex": 1,
+ "no-else-return": 1,
+ "no-eval": 2,
+ "no-extend-native": 2,
+ "no-extra-bind": 2,
+ "no-floating-decimal": 2,
+ "no-implied-eval": 2,
+ "no-iterator": 2,
+ "no-labels": 2,
+ "no-lone-blocks": 1,
+ "no-loop-func": 2,
+ "no-multi-spaces": [2, { "exceptions": { "Property": false } }],
+ "no-multi-str": 0,
+ "no-native-reassign": 2,
+ "no-new": 2,
+ "no-new-func": 2,
+ "no-new-object": 2,
+ "no-new-wrappers": 2,
+ "no-octal-escape": 2,
+ "no-param-reassign": 2,
+ "no-process-env": 2,
+ "no-process-exit": 2,
+ "no-proto": 2,
+ "no-redeclare": 2,
+ "no-return-assign": [2, "always"],
+ "no-script-url": 2,
+ "no-self-compare": 2,
+ "no-sequences": 2,
+ "no-throw-literal": 2,
+ "no-unused-expressions": 2,
+ "no-undef-init": 2,
+ "no-void": 2,
+ "no-warning-comments": 0,
+ "no-with": 2,
+ "radix": 2,
+ "vars-on-top": 0,
+ "wrap-iife": [2, "outside"],
+ "yoda": [2, "never", {"exceptRange": false, "onlyEquality": false}],
- "no-undefined": 2,
- "no-shadow": [2, {"hoist": "functions"}],
- "no-unused-vars": [2, {"vars": "all", "args": "all"}],
- "no-use-before-define": [2, "nofunc"],
+ "no-undefined": 2,
+ "no-shadow": [2, {"hoist": "functions"}],
+ "no-shadow-restricted-names": 2,
+ "no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
+ "no-use-before-define": [2, "nofunc"],
- // Style
- "array-bracket-spacing": [2, "never"],
- "brace-style": [2, "1tbs", { "allowSingleLine": false }],
- "camelcase": [2, {"properties": "always"}],
- "comma-spacing": [2, {"before": false, "after": true}],
- "comma-style": [2, "last"],
- "computed-property-spacing": [2, "never"],
- "consistent-this": [2, "self"],
- "func-names": 2,
- "func-style": [2, "declaration"],
- "indent": [2, 4, {"indentSwitchCase": false}],
- "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
- "lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }],
- "linebreak-style": 2,
- "new-cap": 2,
- "new-parens": 2,
- "no-lonely-if": 2,
- "no-mixed-spaces-and-tabs": 2,
- "no-multiple-empty-lines": [2, {"max": 1}],
- "no-spaced-func": 2,
- "no-ternary": 2,
- "no-trailing-spaces": [2, { "skipBlankLines": false }],
- "no-underscore-dangle": 2,
- "no-unneeded-ternary": 2,
- "object-curly-spacing": [2, "never"],
- "one-var": [2, "never"],
- "operator-linebreak": [2, "after"],
- "padded-blocks": [2, "never"],
- "quote-props": [2, "as-needed"],
- "quotes": [2, "single", "avoid-escape"],
- "semi-spacing": [2, {"before": false, "after": true}],
- "semi": [2, "always"],
- "space-after-keywords": [2, "always"],
- "space-before-blocks": [2, "always"],
- "space-before-function-paren": [2, "never"],
- "space-in-parens": [2, "never"],
- "space-infix-ops": 2,
- "space-return-throw-case": 2,
- "space-unary-ops": [2, { "words": true, "nonwords": false }],
- "wrap-regex": 2,
+ // Style
+ "array-bracket-spacing": [2, "never"],
+ "brace-style": [2, "1tbs", { "allowSingleLine": false }],
+ "camelcase": [2, {"properties": "always"}],
+ "comma-spacing": [2, {"before": false, "after": true}],
+ "comma-style": [2, "last"],
+ "computed-property-spacing": [2, "never"],
+ "consistent-this": [2, "self"],
+ "func-names": 2,
+ "func-style": [2, "declaration"],
+ "indent": [2, 4, {"SwitchCase": 0}],
+ "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
+ "lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }],
+ "linebreak-style": 2,
+ "new-cap": 2,
+ "new-parens": 2,
+ "no-lonely-if": 2,
+ "no-mixed-spaces-and-tabs": 2,
+ "no-multiple-empty-lines": [2, {"max": 1}],
+ "no-spaced-func": 2,
+ "no-ternary": 2,
+ "no-trailing-spaces": [2, { "skipBlankLines": false }],
+ "no-underscore-dangle": 2,
+ "no-unneeded-ternary": 2,
+ "object-curly-spacing": [2, "never"],
+ "one-var": [2, "never"],
+ "operator-linebreak": [2, "after"],
+ "padded-blocks": [2, "never"],
+ "quote-props": [2, "as-needed"],
+ "quotes": [2, "single", "avoid-escape"],
+ "semi-spacing": [2, {"before": false, "after": true}],
+ "semi": [2, "always"],
+ "space-after-keywords": [2, "always"],
+ "space-before-blocks": [2, "always"],
+ "space-before-function-paren": [2, "never"],
+ "space-in-parens": [2, "never"],
+ "space-infix-ops": 2,
+ "space-return-throw-case": 2,
+ "space-unary-ops": [2, { "words": true, "nonwords": false }],
+ "wrap-regex": 2,
- // React Specific
- "react/display-name": [2, { "acceptTranspilerName": true }],
- "react/jsx-boolean-value": [2, "always"],
- "react/jsx-curly-spacing": [2, "never"],
- "react/jsx-no-duplicate-props": [2, { "ignoreCase": false }],
- "react/jsx-no-undef": 2,
- "react/jsx-quotes": [2, "single", "avoid-escape"],
- "react/jsx-uses-react": 2,
- "react/jsx-uses-vars": 2,
- "react/no-danger": 0,
- "react/no-did-mount-set-state": 2,
- "react/no-did-update-set-state": 2,
- "react/no-multi-comp": 2,
- "react/no-unknown-property": 2,
- "react/prop-types": 2,
- "react/sort-comp": 0,
- "react/wrap-multilines": 2
- }
+ // React Specific
+ "react/display-name": [2, { "acceptTranspilerName": true }],
+ "react/jsx-boolean-value": [2, "always"],
+ "react/jsx-curly-spacing": [2, "never"],
+ "react/jsx-max-props-per-line": [2, { "maximum": 1 }],
+ // SOON "react/jsx-indent-props": [2, 4],
+ "react/jsx-no-duplicate-props": [2, { "ignoreCase": false }],
+ "react/jsx-no-literals": 0,
+ "react/jsx-no-undef": 2,
+ "react/jsx-quotes": [2, "single", "avoid-escape"],
+ "react/jsx-uses-react": 2,
+ "react/jsx-uses-vars": 2,
+ "react/no-danger": 0,
+ "react/no-did-mount-set-state": 2,
+ "react/no-did-update-set-state": 2,
+ "react/no-multi-comp": 2,
+ "react/no-unknown-property": 2,
+ "react/prop-types": 2,
+ "react/sort-comp": 0,
+ "react/self-closing-comp": 2,
+ "react/wrap-multilines": 2
+ }
}
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index f9eacf094..b61ea931e 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -56,7 +56,7 @@ module.exports = React.createClass({
},
function loginFailed(err) {
if (err.message === 'Login failed because email address has not been verified') {
- window.location.href = '/verify_email?name=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email);
+ window.location.href = '/verify_email?teamname=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email);
return;
}
state.serverError = err.message;
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index b798dc7ca..7bc6a8c01 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -3,7 +3,6 @@
var PostHeader = require('./post_header.jsx');
var PostBody = require('./post_body.jsx');
-var PostInfo = require('./post_info.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx');
var UserStore = require('../stores/user_store.jsx');
@@ -13,6 +12,8 @@ var client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var ActionTypes = Constants.ActionTypes;
+var PostInfo = require('./post_info.jsx');
+
module.exports = React.createClass({
displayName: "Post",
handleCommentClick: function(e) {
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index f6ab0ed8a..c96a04c7c 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -6,68 +6,145 @@ var utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx');
-module.exports = React.createClass({
- getInitialState: function() {
- return { };
- },
- render: function() {
+export default class PostInfo extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ }
+ shouldShowComment(state, type, isOwner) {
+ if (state === Constants.POST_FAILED || state === Constants.POST_LOADING) {
+ return false;
+ }
+ return isOwner || (this.props.allowReply === 'true' && type !== 'Comment');
+ }
+ createDropdown() {
var post = this.props.post;
var isOwner = UserStore.getCurrentId() === post.user_id;
var isAdmin = UserStore.getCurrentUser().roles.indexOf('admin') > -1;
+ if (post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING || post.state === Constants.POST_DELETED) {
+ return '';
+ }
+
var type = 'Post';
if (post.root_id && post.root_id.length > 0) {
type = 'Comment';
}
- var comments = '';
- var lastCommentClass = ' comment-icon__container__hide';
- if (this.props.isLastComment) {
- lastCommentClass = ' comment-icon__container__show';
+ if (!this.shouldShowComment(post.state, type, isOwner)) {
+ return '';
}
- if (this.props.commentCount >= 1 && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) {
- comments = <a href='#' className={'comment-icon__container theme' + lastCommentClass} onClick={this.props.handleCommentClick}><span className='comment-icon' dangerouslySetInnerHTML={{__html: Constants.COMMENT_ICON}} />{this.props.commentCount}</a>;
+ var dropdownContents = [];
+ var dataComments = 0;
+ if (type === 'Post') {
+ dataComments = this.props.commentCount;
}
- var showDropdown = isOwner || (this.props.allowReply === 'true' && type !== 'Comment');
- if (post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING) {
- showDropdown = false;
+ if (isOwner) {
+ dropdownContents.push(
+ <li role='presentation'>
+ <a
+ href='#'
+ role='menuitem'
+ data-toggle='modal'
+ data-target='#edit_post'
+ data-title={type}
+ data-message={post.message}
+ data-postid={post.id}
+ data-channelid={post.channel_id}
+ data-comments={dataComments}
+ >
+ Edit
+ </a>
+ </li>
+ );
}
- var dropdownContents = [];
- var dropdown;
- if (showDropdown) {
- var dataComments = 0;
- if (type === 'Post') {
- dataComments = this.props.commentCount;
- }
-
- if (isOwner) {
- dropdownContents.push(<li role='presentation'><a href='#' role='menuitem' data-toggle='modal' data-target='#edit_post' data-title={type} data-message={post.message} data-postid={post.id} data-channelid={post.channel_id} data-comments={dataComments}>Edit</a></li>);
- }
+ if (isOwner || isAdmin) {
+ dropdownContents.push(
+ <li role='presentation'>
+ <a
+ href='#'
+ role='menuitem'
+ data-toggle='modal'
+ data-target='#delete_post'
+ data-title={type}
+ data-postid={post.id}
+ data-channelid={post.channel_id}
+ data-comments={dataComments}
+ >
+ Delete
+ </a>
+ </li>
+ );
+ }
- if (isOwner || isAdmin) {
- dropdownContents.push(<li role='presentation'><a href='#' role='menuitem' data-toggle='modal' data-target='#delete_post' data-title={type} data-postid={post.id} data-channelid={post.channel_id} data-comments={dataComments}>Delete</a></li>);
- }
+ if (this.props.allowReply === 'true') {
+ dropdownContents.push(
+ <li role='presentation'>
+ <a
+ className='reply-link theme'
+ href='#'
+ onClick={this.props.handleCommentClick}
+ >
+ Reply
+ </a>
+ </li>
+ );
+ }
- if (this.props.allowReply === 'true') {
- dropdownContents.push(<li role='presentation'><a className='reply-link theme' href='#' onClick={this.props.handleCommentClick}>Reply</a></li>);
- }
+ return (
+ <div>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ data-toggle='dropdown'
+ aria-expanded='false'
+ />
+ <ul
+ className='dropdown-menu'
+ role='menu'
+ >
+ {dropdownContents}
+ </ul>
+ </div>
+ );
+ }
+ render() {
+ var post = this.props.post;
+ var comments = '';
+ var lastCommentClass = ' comment-icon__container__hide';
+ if (this.props.isLastComment) {
+ lastCommentClass = ' comment-icon__container__show';
+ }
- dropdown = (
- <div>
- <a href='#' className='dropdown-toggle theme' type='button' data-toggle='dropdown' aria-expanded='false' />
- <ul className='dropdown-menu' role='menu'>
- {dropdownContents}
- </ul>
- </div>
+ if (this.props.commentCount >= 1 && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) {
+ comments = (
+ <a
+ href='#'
+ className={'comment-icon__container theme' + lastCommentClass}
+ onClick={this.props.handleCommentClick}
+ >
+ <span
+ className='comment-icon'
+ dangerouslySetInnerHTML={{__html: Constants.COMMENT_ICON}}
+ />
+ {this.props.commentCount}
+ </a>
);
}
+ var dropdown = this.createDropdown();
+
return (
<ul className='post-header post-info'>
- <li className='post-header-col'><time className='post-profile-time'>{utils.displayDateTime(post.create_at)}</time></li>
+ <li className='post-header-col'>
+ <time className='post-profile-time'>
+ {utils.displayDateTime(post.create_at)}
+ </time>
+ </li>
<li className='post-header-col post-header__reply'>
<div className='dropdown'>
{dropdown}
@@ -77,4 +154,18 @@ module.exports = React.createClass({
</ul>
);
}
-});
+}
+
+PostInfo.defaultProps = {
+ post: null,
+ commentCount: 0,
+ isLastComment: false,
+ allowReply: false
+};
+PostInfo.propTypes = {
+ post: React.PropTypes.object,
+ commentCount: React.PropTypes.number,
+ isLastComment: React.PropTypes.bool,
+ allowReply: React.PropTypes.string,
+ handleCommentClick: React.PropTypes.func
+};
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 060b8555b..8b60f0251 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -23,12 +23,31 @@ function getStateFromStores() {
}
var postList = PostStore.getCurrentPosts();
+ var deletedPosts = PostStore.getUnseenDeletedPosts(channel.id);
+
+ if (deletedPosts && Object.keys(deletedPosts).length > 0) {
+ for (var pid in deletedPosts) {
+ postList.posts[pid] = deletedPosts[pid];
+ postList.order.unshift(pid);
+ }
+
+ postList.order.sort(function postSort(a, b) {
+ if (postList.posts[a].create_at > postList.posts[b].create_at) {
+ return -1;
+ }
+ if (postList.posts[a].create_at < postList.posts[b].create_at) {
+ return 1;
+ }
+ return 0;
+ });
+ }
+
var pendingPostList = PostStore.getPendingPosts(channel.id);
if (pendingPostList) {
postList.order = pendingPostList.order.concat(postList.order);
- for (var pid in pendingPostList.posts) {
- postList.posts[pid] = pendingPostList.posts[pid];
+ for (var ppid in pendingPostList.posts) {
+ postList.posts[ppid] = pendingPostList.posts[ppid];
}
}
@@ -88,7 +107,6 @@ module.exports = React.createClass({
$('.modal-body').css('max-height', $(window).height() * 0.7);
});
- // Timeout exists for the DOM to fully render before making changes
var self = this;
$(window).resize(function resize() {
$(postHolder).perfectScrollbar('update');
@@ -185,6 +203,7 @@ module.exports = React.createClass({
}
}
if (this.state.channel.id !== newState.channel.id) {
+ PostStore.clearUnseenDeletedPosts(this.state.channel.id);
this.scrolledToNew = false;
}
this.setState(newState);
@@ -220,23 +239,19 @@ module.exports = React.createClass({
activeRootPostId = activeRoot.id;
}
- if (this.state.channel.id === msg.channel_id) {
- postList = this.state.postList;
- if (!(msg.props.post_id in this.state.postList.posts)) {
- return;
- }
+ post = JSON.parse(msg.props.post);
+ postList = this.state.postList;
+
+ PostStore.storeUnseenDeletedPost(post);
- delete postList.posts[msg.props.post_id];
- var index = postList.order.indexOf(msg.props.post_id);
+ if (postList.posts[post.id]) {
+ delete postList.posts[post.id];
+ var index = postList.order.indexOf(post.id);
if (index > -1) {
postList.order.splice(index, 1);
}
- this.setState({postList: postList});
-
PostStore.storePosts(msg.channel_id, postList);
- } else {
- AsyncClient.getPosts(true, msg.channel_id);
}
if (activeRootPostId === msg.props.post_id && UserStore.getCurrentId() !== msg.user_id) {
@@ -318,7 +333,7 @@ module.exports = React.createClass({
var lastViewed = Number.MAX_VALUE;
if (ChannelStore.getCurrentMember() != null) {
- lastViewed = ChannelStore.getCurrentMember().lastViewed_at;
+ lastViewed = ChannelStore.getCurrentMember().last_viewed_at;
}
if (this.state.postList != null) {
diff --git a/web/react/components/setting_item_max.jsx b/web/react/components/setting_item_max.jsx
index 49eb58773..d3d386534 100644
--- a/web/react/components/setting_item_max.jsx
+++ b/web/react/components/setting_item_max.jsx
@@ -3,7 +3,7 @@
module.exports = React.createClass({
render: function() {
- var client_error = this.props.client_error ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.client_error }</label></div> : null;
+ var clientError = this.props.clientError ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.clientError }</label></div> : null;
var server_error = this.props.server_error ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.server_error }</label></div> : null;
var inputs = this.props.inputs;
@@ -19,7 +19,7 @@ module.exports = React.createClass({
<li className="setting-list-item">
<hr />
{ server_error }
- { client_error }
+ { clientError }
{ this.props.submit ? <a className="btn btn-sm btn-primary" onClick={this.props.submit}>Submit</a> : "" }
<a className="btn btn-sm theme" href="#" onClick={this.props.updateSection}>Cancel</a>
</li>
diff --git a/web/react/components/setting_upload.jsx b/web/react/components/setting_upload.jsx
new file mode 100644
index 000000000..870710850
--- /dev/null
+++ b/web/react/components/setting_upload.jsx
@@ -0,0 +1,79 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+module.exports = React.createClass({
+ displayName: 'Setting Upload',
+ propTypes: {
+ title: React.PropTypes.string.isRequired,
+ submit: React.PropTypes.func.isRequired,
+ fileTypesAccepted: React.PropTypes.string.isRequired,
+ clientError: React.PropTypes.string,
+ serverError: React.PropTypes.string
+ },
+ getInitialState: function() {
+ return {
+ clientError: this.props.clientError,
+ serverError: this.props.serverError
+ };
+ },
+ componentWillReceiveProps: function() {
+ this.setState({
+ clientError: this.props.clientError,
+ serverError: this.props.serverError
+ });
+ },
+ doFileSelect: function(e) {
+ e.preventDefault();
+ this.setState({
+ clientError: '',
+ serverError: ''
+ });
+ },
+ doSubmit: function(e) {
+ e.preventDefault();
+ var inputnode = this.refs.uploadinput.getDOMNode();
+ if (inputnode.files && inputnode.files[0]) {
+ this.props.submit(inputnode.files[0]);
+ } else {
+ this.setState({clientError: 'No file selected.'});
+ }
+ },
+ doCancel: function(e) {
+ e.preventDefault();
+ this.refs.uploadinput.getDOMNode().value = '';
+ this.setState({
+ clientError: '',
+ serverError: ''
+ });
+ },
+ render: function() {
+ var clientError = null;
+ if (this.state.clientError) {
+ clientError = (
+ <div className='form-group has-error'><label className='control-label'>{this.state.clientError}</label></div>
+ );
+ }
+ var serverError = null;
+ if (this.state.serverError) {
+ serverError = (
+ <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>
+ );
+ }
+ return (
+ <ul className='section-max'>
+ <li className='col-xs-12 section-title'>{this.props.title}</li>
+ <li className='col-xs-offset-3 col-xs-8'>
+ <ul className='setting-list'>
+ <li className='setting-list-item'>
+ {serverError}
+ {clientError}
+ <span className='btn btn-sm btn-primary btn-file sel-btn'>SelectFile<input ref='uploadinput' accept={this.props.fileTypesAccepted} type='file' onChange={this.onFileSelect}/></span>
+ <a className={'btn btn-sm btn-primary'} onClick={this.doSubmit}>Import</a>
+ <a className='btn btn-sm theme' href='#' onClick={this.doCancel}>Cancel</a>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ );
+ }
+});
diff --git a/web/react/components/settings_sidebar.jsx b/web/react/components/settings_sidebar.jsx
index b4d291622..d8091ec28 100644
--- a/web/react/components/settings_sidebar.jsx
+++ b/web/react/components/settings_sidebar.jsx
@@ -15,7 +15,7 @@ module.exports = React.createClass({
<div className="">
<ul className="nav nav-pills nav-stacked">
{this.props.tabs.map(function(tab) {
- return <li key={tab.name+'_li'} className={self.props.activeTab == tab.name ? 'active' : ''}><a key={tab.name + '_a'} href="#" onClick={function(){self.updateTab(tab.name);}}><i key={tab.name+'_i'} className={tab.icon}></i>{tab.ui_name}</a></li>
+ return <li key={tab.name+'_li'} className={self.props.activeTab == tab.name ? 'active' : ''}><a key={tab.name + '_a'} href="#" onClick={function(){self.updateTab(tab.name);}}><i key={tab.name+'_i'} className={tab.icon}></i>{tab.uiName}</a></li>
})}
</ul>
</div>
diff --git a/web/react/components/team_feature_tab.jsx b/web/react/components/team_feature_tab.jsx
new file mode 100644
index 000000000..ee0bfa874
--- /dev/null
+++ b/web/react/components/team_feature_tab.jsx
@@ -0,0 +1,147 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var SettingItemMin = require('./setting_item_min.jsx');
+var SettingItemMax = require('./setting_item_max.jsx');
+
+var client = require('../utils/client.jsx');
+var AsyncClient = require('../utils/async_client.jsx');
+
+module.exports = React.createClass({
+ displayName: 'Feature Tab',
+ propTypes: {
+ updateSection: React.PropTypes.func.isRequired,
+ team: React.PropTypes.object.isRequired,
+ activeSection: React.PropTypes.string.isRequired
+ },
+ submitValetFeature: function() {
+ var data = {};
+ data.allowValet = this.state.allowValet;
+
+ client.updateValetFeature(data,
+ function() {
+ this.props.updateSection('');
+ AsyncClient.getMyTeam();
+ }.bind(this),
+ function(err) {
+ var state = this.getInitialState();
+ state.serverError = err;
+ this.setState(state);
+ }.bind(this)
+ );
+ },
+ handleValetRadio: function(val) {
+ this.setState({allowValet: val});
+ this.refs.wrapper.getDOMNode().focus();
+ },
+ componentWillReceiveProps: function(newProps) {
+ var team = newProps.team;
+
+ var allowValet = 'false';
+ if (team && team.allowValet) {
+ allowValet = 'true';
+ }
+
+ this.setState({allowValet: allowValet});
+ },
+ getInitialState: function() {
+ var team = this.props.team;
+
+ var allowValet = 'false';
+ if (team && team.allowValet) {
+ allowValet = 'true';
+ }
+
+ return {allowValet: allowValet};
+ },
+ onUpdateSection: function() {
+ if (this.props.activeSection === 'valet') {
+ self.props.updateSection('valet');
+ } else {
+ self.props.updateSection('');
+ }
+ },
+ render: function() {
+ var clientError = null;
+ var serverError = null;
+ if (this.state.clientError) {
+ clientError = this.state.clientError;
+ }
+ if (this.state.serverError) {
+ serverError = this.state.serverError;
+ }
+
+ var valetSection;
+ var self = this;
+
+ if (this.props.activeSection === 'valet') {
+ var valetActive = ['', ''];
+ if (this.state.allowValet === 'false') {
+ valetActive[1] = 'active';
+ } else {
+ valetActive[0] = 'active';
+ }
+
+ var inputs = [];
+
+ function valetActivate() {
+ self.handleValetRadio('true');
+ }
+
+ function valetDeactivate() {
+ self.handleValetRadio('false');
+ }
+
+ inputs.push(
+ <div>
+ <div className='btn-group' data-toggle='buttons-radio'>
+ <button className={'btn btn-default ' + valetActive[0]} onClick={valetActivate}>On</button>
+ <button className={'btn btn-default ' + valetActive[1]} onClick={valetDeactivate}>Off</button>
+ </div>
+ <div><br/>Valet is a preview feature for enabling a non-user account limited to basic member permissions that can be manipulated by 3rd parties.<br/><br/>IMPORTANT: The preview version of Valet should not be used without a secure connection and a trusted 3rd party, since user credentials are used to connect. OAuth2 will be used in the final release.</div>
+ </div>
+ );
+
+ valetSection = (
+ <SettingItemMax
+ title='Valet (Preview - EXPERTS ONLY)'
+ inputs={inputs}
+ submit={this.submitValetFeature}
+ serverError={serverError}
+ clientError={clientError}
+ updateSection={this.onUpdateSection}
+ />
+ );
+ } else {
+ var describe = '';
+ if (this.state.allowValet === 'false') {
+ describe = 'Off';
+ } else {
+ describe = 'On';
+ }
+
+ valetSection = (
+ <SettingItemMin
+ title='Valet (Preview - EXPERTS ONLY)'
+ describe={describe}
+ updateSection={this.onUpdateSection}
+ />
+ );
+ }
+
+ return (
+ <div>
+ <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' ref='title'><i className='modal-back'></i>Feature Settings</h4>
+ </div>
+ <div ref='wrapper' className='user-settings'>
+ <h3 className='tab-header'>Feature Settings</h3>
+ <div className='divider-dark first'/>
+ {valetSection}
+ <div className='divider-dark'/>
+ </div>
+ </div>
+ );
+ }
+});
diff --git a/web/react/components/team_import_tab.jsx b/web/react/components/team_import_tab.jsx
new file mode 100644
index 000000000..131add999
--- /dev/null
+++ b/web/react/components/team_import_tab.jsx
@@ -0,0 +1,68 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var utils = require('../utils/utils.jsx');
+var SettingUpload = require('./setting_upload.jsx');
+
+module.exports = React.createClass({
+ displayName: 'Import Tab',
+ getInitialState: function() {
+ return {status: 'ready', link: ''};
+ },
+ onImportFailure: function() {
+ this.setState({status: 'fail', link: ''});
+ },
+ onImportSuccess: function(data) {
+ this.setState({status: 'done', link: 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(data)});
+ },
+ doImportSlack: function(file) {
+ this.setState({status: 'in-progress', link: ''});
+ utils.importSlack(file, this.onImportSuccess, this.onImportFailure);
+ },
+ render: function() {
+ var uploadSection = (
+ <SettingUpload
+ title='Import from Slack'
+ submit={this.doImportSlack}
+ fileTypesAccepted='.zip'/>
+ );
+
+ var messageSection;
+ switch (this.state.status) {
+ case 'ready':
+ messageSection = '';
+ break;
+ case 'in-progress':
+ messageSection = (
+ <p>Importing...</p>
+ );
+ break;
+ case 'done':
+ messageSection = (
+ <p>Import sucessfull: <a href={this.state.link} download='MattermostImportSummery.txt'>View Summery</a></p>
+ );
+ break;
+ case 'fail':
+ messageSection = (
+ <p>Import failure: <a href={this.state.link} download='MattermostImportSummery.txt'>View Summery</a></p>
+ );
+ break;
+ }
+
+ return (
+ <div>
+ <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' ref='title'><i className='modal-back'></i>Import</h4>
+ </div>
+ <div ref='wrapper' className='user-settings'>
+ <h3 className='tab-header'>Import</h3>
+ <div className='divider-dark first'/>
+ {uploadSection}
+ {messageSection}
+ <div className='divider-dark'/>
+ </div>
+ </div>
+ );
+ }
+});
diff --git a/web/react/components/team_settings.jsx b/web/react/components/team_settings.jsx
index 3bbb5e892..94d536651 100644
--- a/web/react/components/team_settings.jsx
+++ b/web/react/components/team_settings.jsx
@@ -1,161 +1,62 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserStore = require('../stores/user_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
-var SettingItemMin = require('./setting_item_min.jsx');
-var SettingItemMax = require('./setting_item_max.jsx');
-var SettingPicture = require('./setting_picture.jsx');
+var ImportTab = require('./team_import_tab.jsx');
+var FeatureTab = require('./team_feature_tab.jsx');
var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var Constants = require('../utils/constants.jsx');
-
-var FeatureTab = React.createClass({
- submitValetFeature: function() {
- data = {};
- data['allow_valet'] = this.state.allow_valet;
-
- client.updateValetFeature(data,
- function(data) {
- this.props.updateSection("");
- AsyncClient.getMyTeam();
- }.bind(this),
- function(err) {
- state = this.getInitialState();
- state.server_error = err;
- this.setState(state);
- }.bind(this)
- );
- },
- handleValetRadio: function(val) {
- this.setState({ allow_valet: val });
- this.refs.wrapper.getDOMNode().focus();
- },
- componentWillReceiveProps: function(newProps) {
- var team = newProps.team;
-
- var allow_valet = "false";
- if (team && team.allow_valet) {
- allow_valet = "true";
- }
-
- this.setState({ allow_valet: allow_valet });
- },
- getInitialState: function() {
- var team = this.props.team;
-
- var allow_valet = "false";
- if (team && team.allow_valet) {
- allow_valet = "true";
- }
-
- return { allow_valet: allow_valet };
- },
- render: function() {
- var team = this.props.team;
-
- var client_error = this.state.client_error ? this.state.client_error : null;
- var server_error = this.state.server_error ? this.state.server_error : null;
-
- var valetSection;
- var self = this;
-
- if (this.props.activeSection === 'valet') {
- var valetActive = ["",""];
- if (this.state.allow_valet === "false") {
- valetActive[1] = "active";
- } else {
- valetActive[0] = "active";
- }
-
- var inputs = [];
-
- inputs.push(
- <div>
- <div className="btn-group" data-toggle="buttons-radio">
- <button className={"btn btn-default "+valetActive[0]} onClick={function(){self.handleValetRadio("true")}}>On</button>
- <button className={"btn btn-default "+valetActive[1]} onClick={function(){self.handleValetRadio("false")}}>Off</button>
- </div>
- <div><br/>Valet is a preview feature for enabling a non-user account limited to basic member permissions that can be manipulated by 3rd parties.<br/><br/>IMPORTANT: The preview version of Valet should not be used without a secure connection and a trusted 3rd party, since user credentials are used to connect. OAuth2 will be used in the final release.</div>
- </div>
- );
-
- valetSection = (
- <SettingItemMax
- title="Valet (Preview - EXPERTS ONLY)"
- inputs={inputs}
- submit={this.submitValetFeature}
- server_error={server_error}
- client_error={client_error}
- updateSection={function(e){self.props.updateSection("");e.preventDefault();}}
- />
- );
- } else {
- var describe = "";
- if (this.state.allow_valet === "false") {
- describe = "Off";
- } else {
- describe = "On";
- }
-
- valetSection = (
- <SettingItemMin
- title="Valet (Preview - EXPERTS ONLY)"
- describe={describe}
- updateSection={function(){self.props.updateSection("valet");}}
- />
- );
- }
-
- return (
- <div>
- <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" ref="title"><i className="modal-back"></i>Feature Settings</h4>
- </div>
- <div ref="wrapper" className="user-settings">
- <h3 className="tab-header">Feature Settings</h3>
- <div className="divider-dark first"/>
- {valetSection}
- <div className="divider-dark"/>
- </div>
- </div>
- );
- }
-});
-
module.exports = React.createClass({
+ displayName: 'Team Settings',
+ propTypes: {
+ activeTab: React.PropTypes.string.isRequired,
+ activeSection: React.PropTypes.string.isRequired,
+ updateSection: React.PropTypes.func.isRequired
+ },
componentDidMount: function() {
- TeamStore.addChangeListener(this._onChange);
+ TeamStore.addChangeListener(this.onChange);
},
componentWillUnmount: function() {
- TeamStore.removeChangeListener(this._onChange);
+ TeamStore.removeChangeListener(this.onChange);
},
- _onChange: function () {
+ onChange: function() {
var team = TeamStore.getCurrent();
if (!utils.areStatesEqual(this.state.team, team)) {
- this.setState({ team: team });
+ this.setState({team: team});
}
},
getInitialState: function() {
- return { team: TeamStore.getCurrent() };
+ return {team: TeamStore.getCurrent()};
},
render: function() {
- if (this.props.activeTab === 'general') {
- return (
- <div>
- </div>
- );
- } else if (this.props.activeTab === 'feature') {
- return (
- <div>
- <FeatureTab team={this.state.team} activeSection={this.props.activeSection} updateSection={this.props.updateSection} />
- </div>
- );
- } else {
- return <div/>;
+ var result;
+ switch (this.props.activeTab) {
+ case 'general':
+ result = (
+ <div>
+ </div>
+ );
+ break;
+ case 'feature':
+ result = (
+ <div>
+ <FeatureTab team={this.state.team} activeSection={this.props.activeSection} updateSection={this.props.updateSection} />
+ </div>
+ );
+ break;
+ case 'import':
+ result = (
+ <div>
+ <ImportTab team={this.state.team} activeSection={this.props.activeSection} updateSection={this.props.updateSection} />
+ </div>
+ );
+ break;
+ default:
+ result = (
+ <div/>
+ );
+ break;
}
+ return result;
}
});
diff --git a/web/react/components/team_settings_modal.jsx b/web/react/components/team_settings_modal.jsx
index b1c38fd16..c9f479a22 100644
--- a/web/react/components/team_settings_modal.jsx
+++ b/web/react/components/team_settings_modal.jsx
@@ -5,50 +5,52 @@ var SettingsSidebar = require('./settings_sidebar.jsx');
var TeamSettings = require('./team_settings.jsx');
module.exports = React.createClass({
+ displayName: 'Team Settings Modal',
componentDidMount: function() {
- $('body').on('click', '.modal-back', function(){
+ $('body').on('click', '.modal-back', function onClick() {
$(this).closest('.modal-dialog').removeClass('display--content');
});
- $('body').on('click', '.modal-header .close', function(){
- setTimeout(function() {
+ $('body').on('click', '.modal-header .close', function onClick() {
+ setTimeout(function removeContent() {
$('.modal-dialog.display--content').removeClass('display--content');
}, 500);
});
},
updateTab: function(tab) {
- this.setState({ active_tab: tab });
+ this.setState({activeTab: tab});
},
updateSection: function(section) {
- this.setState({ active_section: section });
+ this.setState({activeSection: section});
},
getInitialState: function() {
- return { active_tab: "feature", active_section: "" };
+ return {activeTab: 'feature', activeSection: ''};
},
render: function() {
var tabs = [];
- tabs.push({name: "feature", ui_name: "Features", icon: "glyphicon glyphicon-wrench"});
+ tabs.push({name: 'feature', uiName: 'Features', icon: 'glyphicon glyphicon-wrench'});
+ tabs.push({name: 'import', uiName: 'Import', icon: 'glyphicon glyphicon-upload'});
return (
- <div className="modal fade" ref="modal" id="team_settings" role="dialog" tabIndex="-1" aria-hidden="true">
- <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">&times;</span></button>
- <h4 className="modal-title" ref="title">Team Settings</h4>
+ <div className='modal fade' ref='modal' id='team_settings' role='dialog' tabIndex='-1' aria-hidden='true'>
+ <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'>&times;</span></button>
+ <h4 className='modal-title' ref='title'>Team Settings</h4>
</div>
- <div className="modal-body">
- <div className="settings-table">
- <div className="settings-links">
+ <div className='modal-body'>
+ <div className='settings-table'>
+ <div className='settings-links'>
<SettingsSidebar
tabs={tabs}
- activeTab={this.state.active_tab}
+ activeTab={this.state.activeTab}
updateTab={this.updateTab}
/>
</div>
- <div className="settings-content minimize-settings">
+ <div className='settings-content minimize-settings'>
<TeamSettings
- activeTab={this.state.active_tab}
- activeSection={this.state.active_section}
+ activeTab={this.state.activeTab}
+ activeSection={this.state.activeSection}
updateSection={this.updateSection}
/>
</div>
diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx
index 8f29bbe57..9b0e906c5 100644
--- a/web/react/components/user_settings.jsx
+++ b/web/react/components/user_settings.jsx
@@ -2,1206 +2,34 @@
// See License.txt for license information.
var UserStore = require('../stores/user_store.jsx');
-var SettingItemMin = require('./setting_item_min.jsx');
-var SettingItemMax = require('./setting_item_max.jsx');
-var SettingPicture = require('./setting_picture.jsx');
-var client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
var utils = require('../utils/utils.jsx');
-var Constants = require('../utils/constants.jsx');
-var assign = require('object-assign');
-
-function getNotificationsStateFromStores() {
- var user = UserStore.getCurrentUser();
- var soundNeeded = !utils.isBrowserFirefox();
- var sound = (!user.notify_props || user.notify_props.desktop_sound == undefined) ? "true" : user.notify_props.desktop_sound;
- var desktop = (!user.notify_props || user.notify_props.desktop == undefined) ? "all" : user.notify_props.desktop;
- var email = (!user.notify_props || user.notify_props.email == undefined) ? "true" : user.notify_props.email;
-
- var username_key = false;
- var mention_key = false;
- var custom_keys = "";
- var first_name_key = false;
- var all_key = false;
- var channel_key = false;
-
- if (user.notify_props) {
- if (user.notify_props.mention_keys !== undefined) {
- var keys = user.notify_props.mention_keys.split(',');
-
- if (keys.indexOf(user.username) !== -1) {
- username_key = true;
- keys.splice(keys.indexOf(user.username), 1);
- } else {
- username_key = false;
- }
-
- if (keys.indexOf('@'+user.username) !== -1) {
- mention_key = true;
- keys.splice(keys.indexOf('@'+user.username), 1);
- } else {
- mention_key = false;
- }
-
- custom_keys = keys.join(',');
- }
-
- if (user.notify_props.first_name !== undefined) {
- first_name_key = user.notify_props.first_name === "true";
- }
-
- if (user.notify_props.all !== undefined) {
- all_key = user.notify_props.all === "true";
- }
-
- if (user.notify_props.channel !== undefined) {
- channel_key = user.notify_props.channel === "true";
- }
- }
-
- return { notify_level: desktop, enable_email: email, soundNeeded: soundNeeded, enable_sound: sound, username_key: username_key, mention_key: mention_key, custom_keys: custom_keys, custom_keys_checked: custom_keys.length > 0, first_name_key: first_name_key, all_key: all_key, channel_key: channel_key };
-}
-
-
-var NotificationsTab = React.createClass({
- handleSubmit: function() {
- data = {}
- data["user_id"] = this.props.user.id;
- data["email"] = this.state.enable_email;
- data["desktop_sound"] = this.state.enable_sound;
- data["desktop"] = this.state.notify_level;
-
- var mention_keys = [];
- if (this.state.username_key) mention_keys.push(this.props.user.username);
- if (this.state.mention_key) mention_keys.push('@'+this.props.user.username);
-
- var string_keys = mention_keys.join(',');
- if (this.state.custom_keys.length > 0 && this.state.custom_keys_checked) {
- string_keys += ',' + this.state.custom_keys;
- }
-
- data["mention_keys"] = string_keys;
- data["first_name"] = this.state.first_name_key ? "true" : "false";
- data["all"] = this.state.all_key ? "true" : "false";
- data["channel"] = this.state.channel_key ? "true" : "false";
-
- client.updateUserNotifyProps(data,
- function(data) {
- this.props.updateSection("");
- AsyncClient.getMe();
- }.bind(this),
- function(err) {
- this.setState({ server_error: err.message });
- }.bind(this)
- );
- },
- handleClose: function() {
- $(this.getDOMNode()).find(".form-control").each(function() {
- this.value = "";
- });
-
- this.setState(assign({},getNotificationsStateFromStores(),{server_error: null}));
-
- this.props.updateTab('general');
- },
- componentDidMount: function() {
- UserStore.addChangeListener(this._onChange);
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
- },
- componentWillUnmount: function() {
- UserStore.removeChangeListener(this._onChange);
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- this.props.updateSection('');
- },
- _onChange: function() {
- var newState = getNotificationsStateFromStores();
- if (!utils.areStatesEqual(newState, this.state)) {
- this.setState(newState);
- }
- },
- getInitialState: function() {
- return getNotificationsStateFromStores();
- },
- handleNotifyRadio: function(notifyLevel) {
- this.setState({ notify_level: notifyLevel });
- this.refs.wrapper.getDOMNode().focus();
- },
- handleEmailRadio: function(enableEmail) {
- this.setState({ enable_email: enableEmail });
- this.refs.wrapper.getDOMNode().focus();
- },
- handleSoundRadio: function(enableSound) {
- this.setState({ enable_sound: enableSound });
- this.refs.wrapper.getDOMNode().focus();
- },
- updateUsernameKey: function(val) {
- this.setState({ username_key: val });
- },
- updateMentionKey: function(val) {
- this.setState({ mention_key: val });
- },
- updateFirstNameKey: function(val) {
- this.setState({ first_name_key: val });
- },
- updateAllKey: function(val) {
- this.setState({ all_key: val });
- },
- updateChannelKey: function(val) {
- this.setState({ channel_key: val });
- },
- updateCustomMentionKeys: function() {
- var checked = this.refs.customcheck.getDOMNode().checked;
-
- if (checked) {
- var text = this.refs.custommentions.getDOMNode().value;
-
- // remove all spaces and split string into individual keys
- this.setState({ custom_keys: text.replace(/ /g, ''), custom_keys_checked: true });
- } else {
- this.setState({ custom_keys: "", custom_keys_checked: false });
- }
- },
- onCustomChange: function() {
- this.refs.customcheck.getDOMNode().checked = true;
- this.updateCustomMentionKeys();
- },
- render: function() {
- var server_error = this.state.server_error ? this.state.server_error : null;
-
- var self = this;
-
- var user = this.props.user;
-
- var desktopSection;
- if (this.props.activeSection === 'desktop') {
- var notifyActive = [false, false, false];
- if (this.state.notify_level === "mention") {
- notifyActive[1] = true;
- } else if (this.state.notify_level === "none") {
- notifyActive[2] = true;
- } else {
- notifyActive[0] = true;
- }
-
- var inputs = [];
-
- inputs.push(
- <div>
- <div className="radio">
- <label>
- <input type="radio" checked={notifyActive[0]} onClick={function(){self.handleNotifyRadio("all")}}>For all activity</input>
- </label>
- <br/>
- </div>
- <div className="radio">
- <label>
- <input type="radio" checked={notifyActive[1]} onClick={function(){self.handleNotifyRadio("mention")}}>Only for mentions and private messages</input>
- </label>
- <br/>
- </div>
- <div className="radio">
- <label>
- <input type="radio" checked={notifyActive[2]} onClick={function(){self.handleNotifyRadio("none")}}>Never</input>
- </label>
- </div>
- </div>
- );
-
- desktopSection = (
- <SettingItemMax
- title="Send desktop notifications"
- inputs={inputs}
- submit={this.handleSubmit}
- server_error={server_error}
- updateSection={function(e){self.props.updateSection("");e.preventDefault();}}
- />
- );
- } else {
- var describe = "";
- if (this.state.notify_level === "mention") {
- describe = "Only for mentions and private messages";
- } else if (this.state.notify_level === "none") {
- describe = "Never";
- } else {
- describe = "For all activity";
- }
-
- desktopSection = (
- <SettingItemMin
- title="Send desktop notifications"
- describe={describe}
- updateSection={function(){self.props.updateSection("desktop");}}
- />
- );
- }
-
- var soundSection;
- if (this.props.activeSection === 'sound' && this.state.soundNeeded) {
- var soundActive = ["",""];
- if (this.state.enable_sound === "false") {
- soundActive[1] = "active";
- } else {
- soundActive[0] = "active";
- }
-
- var inputs = [];
-
- inputs.push(
- <div>
- <div className="btn-group" data-toggle="buttons-radio">
- <button className={"btn btn-default "+soundActive[0]} onClick={function(){self.handleSoundRadio("true")}}>On</button>
- <button className={"btn btn-default "+soundActive[1]} onClick={function(){self.handleSoundRadio("false")}}>Off</button>
- </div>
- </div>
- );
-
- soundSection = (
- <SettingItemMax
- title="Desktop notification sounds"
- inputs={inputs}
- submit={this.handleSubmit}
- server_error={server_error}
- updateSection={function(e){self.props.updateSection("");e.preventDefault();}}
- />
- );
- } else {
- var describe = "";
- if (!this.state.soundNeeded) {
- describe = "Please configure notification sounds in your browser settings"
- } else if (this.state.enable_sound === "false") {
- describe = "Off";
- } else {
- describe = "On";
- }
-
- soundSection = (
- <SettingItemMin
- title="Desktop notification sounds"
- describe={describe}
- updateSection={function(){self.props.updateSection("sound");}}
- disableOpen = {!this.state.soundNeeded}
- />
- );
- }
-
- var emailSection;
- if (this.props.activeSection === 'email') {
- var emailActive = ["",""];
- if (this.state.enable_email === "false") {
- emailActive[1] = "active";
- } else {
- emailActive[0] = "active";
- }
-
- var inputs = [];
-
- inputs.push(
- <div>
- <div className="btn-group" data-toggle="buttons-radio">
- <button className={"btn btn-default "+emailActive[0]} onClick={function(){self.handleEmailRadio("true")}}>On</button>
- <button className={"btn btn-default "+emailActive[1]} onClick={function(){self.handleEmailRadio("false")}}>Off</button>
- </div>
- <div><br/>{"Email notifications are sent for mentions and private messages after you have been away from " + config.SiteName + " for 5 minutes."}</div>
- </div>
- );
-
- emailSection = (
- <SettingItemMax
- title="Email notifications"
- inputs={inputs}
- submit={this.handleSubmit}
- server_error={server_error}
- updateSection={function(e){self.props.updateSection("");e.preventDefault();}}
- />
- );
- } else {
- var describe = "";
- if (this.state.enable_email === "false") {
- describe = "Off";
- } else {
- describe = "On";
- }
-
- emailSection = (
- <SettingItemMin
- title="Email notifications"
- describe={describe}
- updateSection={function(){self.props.updateSection("email");}}
- />
- );
- }
-
- var keysSection;
- if (this.props.activeSection === 'keys') {
- var inputs = [];
-
- if (user.first_name) {
- inputs.push(
- <div>
- <div className="checkbox">
- <label>
- <input type="checkbox" checked={this.state.first_name_key} onChange={function(e){self.updateFirstNameKey(e.target.checked);}}>{'Your case sensitive first name "' + user.first_name + '"'}</input>
- </label>
- </div>
- </div>
- );
- }
-
- inputs.push(
- <div>
- <div className="checkbox">
- <label>
- <input type="checkbox" checked={this.state.username_key} onChange={function(e){self.updateUsernameKey(e.target.checked);}}>{'Your non-case sensitive username "' + user.username + '"'}</input>
- </label>
- </div>
- </div>
- );
-
- inputs.push(
- <div>
- <div className="checkbox">
- <label>
- <input type="checkbox" checked={this.state.mention_key} onChange={function(e){self.updateMentionKey(e.target.checked);}}>{'Your username mentioned "@' + user.username + '"'}</input>
- </label>
- </div>
- </div>
- );
-
- inputs.push(
- <div>
- <div className="checkbox">
- <label>
- <input type="checkbox" checked={this.state.all_key} onChange={function(e){self.updateAllKey(e.target.checked);}}>{'Team-wide mentions "@all"'}</input>
- </label>
- </div>
- </div>
- );
-
- inputs.push(
- <div>
- <div className="checkbox">
- <label>
- <input type="checkbox" checked={this.state.channel_key} onChange={function(e){self.updateChannelKey(e.target.checked);}}>{'Channel-wide mentions "@channel"'}</input>
- </label>
- </div>
- </div>
- );
-
- inputs.push(
- <div>
- <div className="checkbox">
- <label>
- <input ref="customcheck" type="checkbox" checked={this.state.custom_keys_checked} onChange={this.updateCustomMentionKeys}>{'Other non-case sensitive words, separated by commas:'}</input>
- </label>
- </div>
- <input ref="custommentions" className="form-control mentions-input" type="text" defaultValue={this.state.custom_keys} onChange={this.onCustomChange} />
- </div>
- );
-
- keysSection = (
- <SettingItemMax
- title="Words that trigger mentions"
- inputs={inputs}
- submit={this.handleSubmit}
- server_error={server_error}
- updateSection={function(e){self.props.updateSection("");e.preventDefault();}}
- />
- );
- } else {
- var keys = [];
- if (this.state.first_name_key) keys.push(user.first_name);
- if (this.state.username_key) keys.push(user.username);
- if (this.state.mention_key) keys.push('@'+user.username);
- if (this.state.all_key) keys.push('@all');
- if (this.state.channel_key) keys.push('@channel');
- if (this.state.custom_keys.length > 0) keys = keys.concat(this.state.custom_keys.split(','));
-
- var describe = "";
- for (var i = 0; i < keys.length; i++) {
- describe += '"' + keys[i] + '", ';
- }
-
- if (describe.length > 0) {
- describe = describe.substring(0, describe.length - 2);
- } else {
- describe = "No words configured";
- }
-
- keysSection = (
- <SettingItemMin
- title="Words that trigger mentions"
- describe={describe}
- updateSection={function(){self.props.updateSection("keys");}}
- />
- );
- }
-
- return (
- <div>
- <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" ref="title"><i className="modal-back"></i>Notifications</h4>
- </div>
- <div ref="wrapper" className="user-settings">
- <h3 className="tab-header">Notifications</h3>
- <div className="divider-dark first"/>
- {desktopSection}
- <div className="divider-light"/>
- {soundSection}
- <div className="divider-light"/>
- {emailSection}
- <div className="divider-light"/>
- {keysSection}
- <div className="divider-dark"/>
- </div>
- </div>
-
- );
- }
-});
-
-var SecurityTab = React.createClass({
- submitPassword: function(e) {
- e.preventDefault();
-
- var user = this.props.user;
- var currentPassword = this.state.currentPassword;
- var newPassword = this.state.newPassword;
- var confirmPassword = this.state.confirmPassword;
-
- if (currentPassword === '') {
- this.setState({passwordError: 'Please enter your current password', serverError: ''});
- return;
- }
-
- if (newPassword.length < 5) {
- this.setState({passwordError: 'New passwords must be at least 5 characters', serverError: ''});
- return;
- }
-
- if (newPassword !== confirmPassword) {
- this.setState({passwordError: 'The new passwords you entered do not match', serverError: ''});
- return;
- }
-
- var data = {};
- data.user_id = user.id;
- data.current_password = currentPassword;
- data.new_password = newPassword;
-
- client.updatePassword(data,
- function() {
- this.props.updateSection('');
- AsyncClient.getMe();
- this.setState({currentPassword: '', newPassword: '', confirmPassword: ''});
- }.bind(this),
- function(err) {
- var state = this.getInitialState();
- if (err.message) {
- state.serverError = err.message;
- } else {
- state.serverError = err;
- }
- state.passwordError = '';
- this.setState(state);
- }.bind(this)
- );
- },
- updateCurrentPassword: function(e) {
- this.setState({currentPassword: e.target.value});
- },
- updateNewPassword: function(e) {
- this.setState({newPassword: e.target.value});
- },
- updateConfirmPassword: function(e) {
- this.setState({confirmPassword: e.target.value});
- },
- handleHistoryOpen: function() {
- this.setState({willReturn: true});
- $("#user_settings").modal('hide');
- },
- handleDevicesOpen: function() {
- this.setState({willReturn: true});
- $("#user_settings").modal('hide');
- },
- handleClose: function() {
- $(this.getDOMNode()).find('.form-control').each(function() {
- this.value = '';
- });
- this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
-
- if (!this.state.willReturn) {
- this.props.updateTab('general');
- } else {
- this.setState({willReturn: false});
- }
- },
- componentDidMount: function() {
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
- },
- componentWillUnmount: function() {
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- this.props.updateSection('');
- },
- getInitialState: function() {
- return {currentPassword: '', newPassword: '', confirmPassword: '', willReturn: false};
- },
- render: function() {
- var serverError = this.state.serverError ? this.state.serverError : null;
- var passwordError = this.state.passwordError ? this.state.passwordError : null;
-
- var updateSectionStatus;
- var passwordSection;
- var self = this;
- if (this.props.activeSection === 'password') {
- var inputs = [];
- var submit = null;
-
- if (this.props.user.auth_service === '') {
- inputs.push(
- <div className='form-group'>
- <label className='col-sm-5 control-label'>Current Password</label>
- <div className='col-sm-7'>
- <input className='form-control' type='password' onChange={this.updateCurrentPassword} value={this.state.currentPassword}/>
- </div>
- </div>
- );
- inputs.push(
- <div className='form-group'>
- <label className='col-sm-5 control-label'>New Password</label>
- <div className='col-sm-7'>
- <input className='form-control' type='password' onChange={this.updateNewPassword} value={this.state.newPassword}/>
- </div>
- </div>
- );
- inputs.push(
- <div className='form-group'>
- <label className='col-sm-5 control-label'>Retype New Password</label>
- <div className='col-sm-7'>
- <input className='form-control' type='password' onChange={this.updateConfirmPassword} value={this.state.confirmPassword}/>
- </div>
- </div>
- );
-
- submit = this.submitPassword;
- } else {
- inputs.push(
- <div className='form-group'>
- <label className='col-sm-12'>Log in occurs through GitLab. Please see your GitLab account settings page to update your password.</label>
- </div>
- );
- }
-
- updateSectionStatus = function(e) {
- self.props.updateSection('');
- self.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
- e.preventDefault();
- };
-
- passwordSection = (
- <SettingItemMax
- title='Password'
- inputs={inputs}
- submit={submit}
- server_error={serverError}
- client_error={passwordError}
- updateSection={updateSectionStatus}
- />
- );
- } else {
- var describe;
- if (this.props.user.auth_service === '') {
- var d = new Date(this.props.user.last_password_update);
- var hour = d.getHours() % 12 ? String(d.getHours() % 12) : '12';
- var min = d.getMinutes() < 10 ? '0' + d.getMinutes() : String(d.getMinutes());
- var timeOfDay = d.getHours() >= 12 ? ' pm' : ' am';
- describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay;
- } else {
- describe = 'Log in done through GitLab';
- }
-
- updateSectionStatus = function() {
- self.props.updateSection('password');
- };
-
- passwordSection = (
- <SettingItemMin
- title='Password'
- describe={describe}
- updateSection={updateSectionStatus}
- />
- );
- }
-
- return (
- <div>
- <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' ref='title'><i className='modal-back'></i>Security Settings</h4>
- </div>
- <div className='user-settings'>
- <h3 className='tab-header'>Security Settings</h3>
- <div className='divider-dark first'/>
- {passwordSection}
- <div className='divider-dark'/>
- <br></br>
- <a data-toggle='modal' className='security-links theme' data-target='#access-history' href='#' onClick={this.handleHistoryOpen}><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}><i className='fa fa-globe'></i>View and Logout of Active Sessions</a>
- </div>
- </div>
- );
- }
-});
-
-var GeneralTab = React.createClass({
- submitActive: false,
- submitUsername: function(e) {
- e.preventDefault();
-
- var user = this.props.user;
- var username = this.state.username.trim();
-
- var usernameError = utils.isValidUsername(username);
- if (usernameError === 'Cannot use a reserved word as a username.') {
- this.setState({clientError: 'This username is reserved, please choose a new one.'});
- return;
- } else if (usernameError) {
- this.setState({clientError: "Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'."});
- return;
- }
-
- if (user.username === username) {
- this.setState({clientError: 'You must submit a new username'});
- return;
- }
-
- user.username = username;
-
- this.submitUser(user);
- },
- submitNickname: function(e) {
- e.preventDefault();
-
- var user = UserStore.getCurrentUser();
- var nickname = this.state.nickname.trim();
-
- if (user.nickname === nickname) {
- this.setState({clientError: 'You must submit a new nickname'});
- return;
- }
-
- user.nickname = nickname;
-
- this.submitUser(user);
- },
- submitName: function(e) {
- e.preventDefault();
-
- var user = UserStore.getCurrentUser();
- var firstName = this.state.firstName.trim();
- var lastName = this.state.lastName.trim();
-
- if (user.first_name === firstName && user.last_name === lastName) {
- this.setState({clientError: 'You must submit a new first or last name'});
- return;
- }
-
- user.first_name = firstName;
- user.last_name = lastName;
-
- this.submitUser(user);
- },
- submitEmail: function(e) {
- e.preventDefault();
-
- var user = UserStore.getCurrentUser();
- var email = this.state.email.trim().toLowerCase();
-
- if (user.email === email) {
- return;
- }
-
- if (email === '' || !utils.isEmail(email)) {
- this.setState({emailError: 'Please enter a valid email address'});
- return;
- }
-
- user.email = email;
-
- this.submitUser(user);
- },
- submitUser: function(user) {
- client.updateUser(user,
- function() {
- this.updateSection('');
- AsyncClient.getMe();
- }.bind(this),
- function(err) {
- var state = this.getInitialState();
- if (err.message) {
- state.serverError = err.message;
- } else {
- state.serverError = err;
- }
- this.setState(state);
- }.bind(this)
- );
- },
- submitPicture: function(e) {
- e.preventDefault();
-
- if (!this.state.picture) {
- return;
- }
-
- if (!this.submitActive) {
- return;
- }
-
- var picture = this.state.picture;
-
- if (picture.type !== 'image/jpeg' && picture.type !== 'image/png') {
- this.setState({clientError: 'Only JPG or PNG images may be used for profile pictures'});
- return;
- }
-
- var formData = new FormData();
- formData.append('image', picture, picture.name);
- this.setState({loadingPicture: true});
-
- client.uploadProfileImage(formData,
- function() {
- this.submitActive = false;
- AsyncClient.getMe();
- window.location.reload();
- }.bind(this),
- function(err) {
- var state = this.getInitialState();
- state.serverError = err;
- this.setState(state);
- }.bind(this)
- );
- },
- updateUsername: function(e) {
- this.setState({username: e.target.value});
- },
- updateFirstName: function(e) {
- this.setState({firstName: e.target.value});
- },
- updateLastName: function(e) {
- this.setState({lastName: e.target.value});
- },
- updateNickname: function(e) {
- this.setState({nickname: e.target.value});
- },
- updateEmail: function(e) {
- this.setState({email: e.target.value});
- },
- updatePicture: function(e) {
- if (e.target.files && e.target.files[0]) {
- this.setState({picture: e.target.files[0]});
-
- this.submitActive = true;
- this.setState({clientError: null});
- } else {
- this.setState({picture: null});
- }
- },
- updateSection: function(section) {
- this.setState({clientError: ''});
- this.submitActive = false;
- this.props.updateSection(section);
- },
- handleClose: function() {
- $(this.getDOMNode()).find('.form-control').each(function() {
- this.value = '';
- });
-
- this.setState(assign({}, this.getInitialState(), {clientError: null, serverError: null, emailError: null}));
- this.props.updateSection('');
- },
- componentDidMount: function() {
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
- },
- componentWillUnmount: function() {
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- },
- getInitialState: function() {
- var user = this.props.user;
-
- return {username: user.username, firstName: user.first_name, lastName: user.last_name, nickname: user.nickname,
- email: user.email, picture: null, loadingPicture: false};
- },
- render: function() {
- var user = this.props.user;
-
- var clientError = null;
- if (this.state.clientError) {
- clientError = this.state.clientError;
- }
- var serverError = null;
- if (this.state.serverError) {
- serverError = this.state.serverError;
- }
- var emailError = null;
- if (this.state.emailError) {
- emailError = this.state.emailError;
- }
-
- var nameSection;
- var self = this;
- var inputs = [];
-
- if (this.props.activeSection === 'name') {
- inputs.push(
- <div className='form-group'>
- <label className='col-sm-5 control-label'>First Name</label>
- <div className='col-sm-7'>
- <input className='form-control' type='text' onChange={this.updateFirstName} value={this.state.firstName}/>
- </div>
- </div>
- );
-
- inputs.push(
- <div className='form-group'>
- <label className='col-sm-5 control-label'>Last Name</label>
- <div className='col-sm-7'>
- <input className='form-control' type='text' onChange={this.updateLastName} value={this.state.lastName}/>
- </div>
- </div>
- );
-
- nameSection = (
- <SettingItemMax
- title='Full Name'
- inputs={inputs}
- submit={this.submitName}
- server_error={serverError}
- client_error={clientError}
- updateSection={function(e) {
- self.updateSection('');
- e.preventDefault();
- }}
- />
- );
- } else {
- var fullName = '';
-
- if (user.first_name && user.last_name) {
- fullName = user.first_name + ' ' + user.last_name;
- } else if (user.first_name) {
- fullName = user.first_name;
- } else if (user.last_name) {
- fullName = user.last_name;
- }
-
- nameSection = (
- <SettingItemMin
- title='Full Name'
- describe={fullName}
- updateSection={function() {
- self.updateSection('name');
- }}
- />
- );
- }
-
- var nicknameSection;
- if (this.props.activeSection === 'nickname') {
- inputs.push(
- <div className='form-group'>
- <label className='col-sm-5 control-label'>{utils.isMobile() ? '' : 'Nickname'}</label>
- <div className='col-sm-7'>
- <input className='form-control' type='text' onChange={this.updateNickname} value={this.state.nickname}/>
- </div>
- </div>
- );
-
- nicknameSection = (
- <SettingItemMax
- title='Nickname'
- inputs={inputs}
- submit={this.submitNickname}
- server_error={serverError}
- client_error={clientError}
- updateSection={function(e) {
- self.updateSection('');
- e.preventDefault();
- }}
- />
- );
- } else {
- nicknameSection = (
- <SettingItemMin
- title='Nickname'
- describe={UserStore.getCurrentUser().nickname}
- updateSection={function() {
- self.updateSection('nickname');
- }}
- />
- );
- }
-
- var usernameSection;
- if (this.props.activeSection === 'username') {
- inputs.push(
- <div className='form-group'>
- <label className='col-sm-5 control-label'>{utils.isMobile() ? '' : 'Username'}</label>
- <div className='col-sm-7'>
- <input className='form-control' type='text' onChange={this.updateUsername} value={this.state.username}/>
- </div>
- </div>
- );
-
- usernameSection = (
- <SettingItemMax
- title='Username'
- inputs={inputs}
- submit={this.submitUsername}
- server_error={serverError}
- client_error={clientError}
- updateSection={function(e) {
- self.updateSection('');
- e.preventDefault();
- }}
- />
- );
- } else {
- usernameSection = (
- <SettingItemMin
- title='Username'
- describe={UserStore.getCurrentUser().username}
- updateSection={function() {
- self.updateSection('username');
- }}
- />
- );
- }
- var emailSection;
- if (this.props.activeSection === 'email') {
- inputs.push(
- <div className='form-group'>
- <label className='col-sm-5 control-label'>Primary Email</label>
- <div className='col-sm-7'>
- <input className='form-control' type='text' onChange={this.updateEmail} value={this.state.email}/>
- </div>
- </div>
- );
-
- emailSection = (
- <SettingItemMax
- title='Email'
- inputs={inputs}
- submit={this.submitEmail}
- server_error={serverError}
- client_error={emailError}
- updateSection={function(e) {
- self.updateSection('');
- e.preventDefault();
- }}
- />
- );
- } else {
- emailSection = (
- <SettingItemMin
- title='Email'
- describe={UserStore.getCurrentUser().email}
- updateSection={function() {
- self.updateSection('email');
- }}
- />
- );
- }
-
- var pictureSection;
- if (this.props.activeSection === 'picture') {
- pictureSection = (
- <SettingPicture
- title='Profile Picture'
- submit={this.submitPicture}
- src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update}
- server_error={serverError}
- client_error={clientError}
- updateSection={function(e) {
- self.updateSection('');
- e.preventDefault();
- }}
- picture={this.state.picture}
- pictureChange={this.updatePicture}
- submitActive={this.submitActive}
- loadingPicture={this.state.loadingPicture}
- />
- );
- } else {
- var minMessage = 'Click \'Edit\' to upload an image.';
- if (user.last_picture_update) {
- minMessage = 'Image last updated ' + utils.displayDate(user.last_picture_update);
- }
- pictureSection = (
- <SettingItemMin
- title='Profile Picture'
- describe={minMessage}
- updateSection={function() {
- self.updateSection('picture');
- }}
- />
- );
- }
- return (
- <div>
- <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' ref='title'><i className='modal-back'></i>General Settings</h4>
- </div>
- <div className='user-settings'>
- <h3 className='tab-header'>General Settings</h3>
- <div className='divider-dark first'/>
- {nameSection}
- <div className='divider-light'/>
- {usernameSection}
- <div className='divider-light'/>
- {nicknameSection}
- <div className='divider-light'/>
- {emailSection}
- <div className='divider-light'/>
- {pictureSection}
- <div className='divider-dark'/>
- </div>
- </div>
- );
- }
-});
-
-var AppearanceTab = React.createClass({
- submitTheme: function(e) {
- e.preventDefault();
- var user = UserStore.getCurrentUser();
- if (!user.props) user.props = {};
- user.props.theme = this.state.theme;
-
- client.updateUser(user,
- function(data) {
- this.props.updateSection("");
- window.location.reload();
- }.bind(this),
- function(err) {
- state = this.getInitialState();
- state.server_error = err;
- this.setState(state);
- }.bind(this)
- );
- },
- updateTheme: function(e) {
- var hex = utils.rgb2hex(e.target.style.backgroundColor);
- this.setState({ theme: hex.toLowerCase() });
- },
- handleClose: function() {
- this.setState({server_error: null});
- this.props.updateTab('general');
- },
- componentDidMount: function() {
- if (this.props.activeSection === "theme") {
- $(this.refs[this.state.theme].getDOMNode()).addClass('active-border');
- }
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
- },
- componentDidUpdate: function() {
- if (this.props.activeSection === "theme") {
- $('.color-btn').removeClass('active-border');
- $(this.refs[this.state.theme].getDOMNode()).addClass('active-border');
- }
- },
- componentWillUnmount: function() {
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- this.props.updateSection('');
- },
- getInitialState: function() {
- var user = UserStore.getCurrentUser();
- var theme = config.ThemeColors != null ? config.ThemeColors[0] : "#2389d7";
- if (user.props && user.props.theme) {
- theme = user.props.theme;
- }
- return { theme: theme.toLowerCase() };
- },
- render: function() {
- var server_error = this.state.server_error ? this.state.server_error : null;
-
-
- var themeSection;
- var self = this;
-
- if (config.ThemeColors != null) {
- if (this.props.activeSection === 'theme') {
- var theme_buttons = [];
-
- for (var i = 0; i < config.ThemeColors.length; i++) {
- theme_buttons.push(<button ref={config.ThemeColors[i]} type="button" className="btn btn-lg color-btn" style={{backgroundColor: config.ThemeColors[i]}} onClick={this.updateTheme} />);
- }
-
- var inputs = [];
-
- inputs.push(
- <li className="setting-list-item">
- <div className="btn-group" data-toggle="buttons-radio">
- { theme_buttons }
- </div>
- </li>
- );
-
- themeSection = (
- <SettingItemMax
- title="Theme Color"
- inputs={inputs}
- submit={this.submitTheme}
- server_error={server_error}
- updateSection={function(e){self.props.updateSection("");e.preventDefault;}}
- />
- );
- } else {
- themeSection = (
- <SettingItemMin
- title="Theme Color"
- describe={this.state.theme}
- updateSection={function(){self.props.updateSection("theme");}}
- />
- );
- }
- }
-
- return (
- <div>
- <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" ref="title"><i className="modal-back"></i>Appearance Settings</h4>
- </div>
- <div className="user-settings">
- <h3 className="tab-header">Appearance Settings</h3>
- <div className="divider-dark first"/>
- {themeSection}
- <div className="divider-dark"/>
- </div>
- </div>
- );
- }
-});
+var NotificationsTab = require('./user_settings_notifications.jsx');
+var SecurityTab = require('./user_settings_security.jsx');
+var GeneralTab = require('./user_settings_general.jsx');
+var AppearanceTab = require('./user_settings_appearance.jsx');
module.exports = React.createClass({
displayName: 'UserSettings',
+ propTypes: {
+ activeTab: React.PropTypes.string,
+ activeSection: React.PropTypes.string,
+ updateSection: React.PropTypes.func,
+ updateTab: React.PropTypes.func
+ },
componentDidMount: function() {
- UserStore.addChangeListener(this._onChange);
+ UserStore.addChangeListener(this.onListenerChange);
},
componentWillUnmount: function() {
- UserStore.removeChangeListener(this._onChange);
+ UserStore.removeChangeListener(this.onListenerChange);
},
- _onChange: function () {
+ onListenerChange: function () {
var user = UserStore.getCurrentUser();
if (!utils.areStatesEqual(this.state.user, user)) {
- this.setState({ user: user });
+ this.setState({user: user});
}
},
getInitialState: function() {
- return { user: UserStore.getCurrentUser() };
+ return {user: UserStore.getCurrentUser()};
},
render: function() {
if (this.props.activeTab === 'general') {
diff --git a/web/react/components/user_settings_appearance.jsx b/web/react/components/user_settings_appearance.jsx
new file mode 100644
index 000000000..0a17f1687
--- /dev/null
+++ b/web/react/components/user_settings_appearance.jsx
@@ -0,0 +1,118 @@
+var UserStore = require('../stores/user_store.jsx');
+var SettingItemMin = require('./setting_item_min.jsx');
+var SettingItemMax = require('./setting_item_max.jsx');
+var client = require('../utils/client.jsx');
+var utils = require('../utils/utils.jsx');
+
+module.exports = React.createClass({
+ submitTheme: function(e) {
+ e.preventDefault();
+ var user = UserStore.getCurrentUser();
+ if (!user.props) user.props = {};
+ user.props.theme = this.state.theme;
+
+ client.updateUser(user,
+ function(data) {
+ this.props.updateSection("");
+ window.location.reload();
+ }.bind(this),
+ function(err) {
+ state = this.getInitialState();
+ state.server_error = err;
+ this.setState(state);
+ }.bind(this)
+ );
+ },
+ updateTheme: function(e) {
+ var hex = utils.rgb2hex(e.target.style.backgroundColor);
+ this.setState({ theme: hex.toLowerCase() });
+ },
+ handleClose: function() {
+ this.setState({server_error: null});
+ this.props.updateTab('general');
+ },
+ componentDidMount: function() {
+ if (this.props.activeSection === "theme") {
+ $(this.refs[this.state.theme].getDOMNode()).addClass('active-border');
+ }
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
+ },
+ componentDidUpdate: function() {
+ if (this.props.activeSection === "theme") {
+ $('.color-btn').removeClass('active-border');
+ $(this.refs[this.state.theme].getDOMNode()).addClass('active-border');
+ }
+ },
+ componentWillUnmount: function() {
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
+ this.props.updateSection('');
+ },
+ getInitialState: function() {
+ var user = UserStore.getCurrentUser();
+ var theme = config.ThemeColors != null ? config.ThemeColors[0] : "#2389d7";
+ if (user.props && user.props.theme) {
+ theme = user.props.theme;
+ }
+ return { theme: theme.toLowerCase() };
+ },
+ render: function() {
+ var server_error = this.state.server_error ? this.state.server_error : null;
+
+
+ var themeSection;
+ var self = this;
+
+ if (config.ThemeColors != null) {
+ if (this.props.activeSection === 'theme') {
+ var theme_buttons = [];
+
+ for (var i = 0; i < config.ThemeColors.length; i++) {
+ theme_buttons.push(<button ref={config.ThemeColors[i]} type="button" className="btn btn-lg color-btn" style={{backgroundColor: config.ThemeColors[i]}} onClick={this.updateTheme} />);
+ }
+
+ var inputs = [];
+
+ inputs.push(
+ <li className="setting-list-item">
+ <div className="btn-group" data-toggle="buttons-radio">
+ { theme_buttons }
+ </div>
+ </li>
+ );
+
+ themeSection = (
+ <SettingItemMax
+ title="Theme Color"
+ inputs={inputs}
+ submit={this.submitTheme}
+ server_error={server_error}
+ updateSection={function(e){self.props.updateSection("");e.preventDefault;}}
+ />
+ );
+ } else {
+ themeSection = (
+ <SettingItemMin
+ title="Theme Color"
+ describe={this.state.theme}
+ updateSection={function(){self.props.updateSection("theme");}}
+ />
+ );
+ }
+ }
+
+ return (
+ <div>
+ <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" ref="title"><i className="modal-back"></i>Appearance Settings</h4>
+ </div>
+ <div className="user-settings">
+ <h3 className="tab-header">Appearance Settings</h3>
+ <div className="divider-dark first"/>
+ {themeSection}
+ <div className="divider-dark"/>
+ </div>
+ </div>
+ );
+ }
+});
diff --git a/web/react/components/user_settings_general.jsx b/web/react/components/user_settings_general.jsx
new file mode 100644
index 000000000..5e7bbcb51
--- /dev/null
+++ b/web/react/components/user_settings_general.jsx
@@ -0,0 +1,428 @@
+var UserStore = require('../stores/user_store.jsx');
+var SettingItemMin = require('./setting_item_min.jsx');
+var SettingItemMax = require('./setting_item_max.jsx');
+var SettingPicture = require('./setting_picture.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');
+
+module.exports = React.createClass({
+ displayName: 'GeneralTab',
+ submitActive: false,
+ submitUsername: function(e) {
+ e.preventDefault();
+
+ var user = this.props.user;
+ var username = this.state.username.trim();
+
+ var usernameError = utils.isValidUsername(username);
+ if (usernameError === 'Cannot use a reserved word as a username.') {
+ this.setState({clientError: 'This username is reserved, please choose a new one.'});
+ return;
+ } else if (usernameError) {
+ this.setState({clientError: "Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'."});
+ return;
+ }
+
+ if (user.username === username) {
+ this.setState({clientError: 'You must submit a new username'});
+ return;
+ }
+
+ user.username = username;
+
+ this.submitUser(user);
+ },
+ submitNickname: function(e) {
+ e.preventDefault();
+
+ var user = UserStore.getCurrentUser();
+ var nickname = this.state.nickname.trim();
+
+ if (user.nickname === nickname) {
+ this.setState({clientError: 'You must submit a new nickname'});
+ return;
+ }
+
+ user.nickname = nickname;
+
+ this.submitUser(user);
+ },
+ submitName: function(e) {
+ e.preventDefault();
+
+ var user = UserStore.getCurrentUser();
+ var firstName = this.state.firstName.trim();
+ var lastName = this.state.lastName.trim();
+
+ if (user.first_name === firstName && user.last_name === lastName) {
+ this.setState({clientError: 'You must submit a new first or last name'});
+ return;
+ }
+
+ user.first_name = firstName;
+ user.last_name = lastName;
+
+ this.submitUser(user);
+ },
+ submitEmail: function(e) {
+ e.preventDefault();
+
+ var user = UserStore.getCurrentUser();
+ var email = this.state.email.trim().toLowerCase();
+
+ if (user.email === email) {
+ return;
+ }
+
+ if (email === '' || !utils.isEmail(email)) {
+ this.setState({emailError: 'Please enter a valid email address'});
+ return;
+ }
+
+ user.email = email;
+
+ this.submitUser(user);
+ },
+ submitUser: function(user) {
+ client.updateUser(user,
+ function() {
+ this.updateSection('');
+ AsyncClient.getMe();
+ }.bind(this),
+ function(err) {
+ var state = this.getInitialState();
+ if (err.message) {
+ state.serverError = err.message;
+ } else {
+ state.serverError = err;
+ }
+ this.setState(state);
+ }.bind(this)
+ );
+ },
+ submitPicture: function(e) {
+ e.preventDefault();
+
+ if (!this.state.picture) {
+ return;
+ }
+
+ if (!this.submitActive) {
+ return;
+ }
+
+ var picture = this.state.picture;
+
+ if (picture.type !== 'image/jpeg' && picture.type !== 'image/png') {
+ this.setState({clientError: 'Only JPG or PNG images may be used for profile pictures'});
+ return;
+ }
+
+ var formData = new FormData();
+ formData.append('image', picture, picture.name);
+ this.setState({loadingPicture: true});
+
+ client.uploadProfileImage(formData,
+ function() {
+ this.submitActive = false;
+ AsyncClient.getMe();
+ window.location.reload();
+ }.bind(this),
+ function(err) {
+ var state = this.getInitialState();
+ state.serverError = err;
+ this.setState(state);
+ }.bind(this)
+ );
+ },
+ updateUsername: function(e) {
+ this.setState({username: e.target.value});
+ },
+ updateFirstName: function(e) {
+ this.setState({firstName: e.target.value});
+ },
+ updateLastName: function(e) {
+ this.setState({lastName: e.target.value});
+ },
+ updateNickname: function(e) {
+ this.setState({nickname: e.target.value});
+ },
+ updateEmail: function(e) {
+ this.setState({email: e.target.value});
+ },
+ updatePicture: function(e) {
+ if (e.target.files && e.target.files[0]) {
+ this.setState({picture: e.target.files[0]});
+
+ this.submitActive = true;
+ this.setState({clientError: null});
+ } else {
+ this.setState({picture: null});
+ }
+ },
+ updateSection: function(section) {
+ this.setState(assign({}, this.getInitialState(), {clientError: ''}));
+ this.submitActive = false;
+ this.props.updateSection(section);
+ },
+ handleClose: function() {
+ $(this.getDOMNode()).find('.form-control').each(function() {
+ this.value = '';
+ });
+
+ this.setState(assign({}, this.getInitialState(), {clientError: null, serverError: null, emailError: null}));
+ this.props.updateSection('');
+ },
+ componentDidMount: function() {
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
+ },
+ componentWillUnmount: function() {
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
+ },
+ getInitialState: function() {
+ var user = this.props.user;
+
+ return {username: user.username, firstName: user.first_name, lastName: user.last_name, nickname: user.nickname,
+ email: user.email, picture: null, loadingPicture: false};
+ },
+ render: function() {
+ var user = this.props.user;
+
+ var clientError = null;
+ if (this.state.clientError) {
+ clientError = this.state.clientError;
+ }
+ var serverError = null;
+ if (this.state.serverError) {
+ serverError = this.state.serverError;
+ }
+ var emailError = null;
+ if (this.state.emailError) {
+ emailError = this.state.emailError;
+ }
+
+ var nameSection;
+ var self = this;
+ var inputs = [];
+
+ if (this.props.activeSection === 'name') {
+ inputs.push(
+ <div className='form-group'>
+ <label className='col-sm-5 control-label'>First Name</label>
+ <div className='col-sm-7'>
+ <input className='form-control' type='text' onChange={this.updateFirstName} value={this.state.firstName}/>
+ </div>
+ </div>
+ );
+
+ inputs.push(
+ <div className='form-group'>
+ <label className='col-sm-5 control-label'>Last Name</label>
+ <div className='col-sm-7'>
+ <input className='form-control' type='text' onChange={this.updateLastName} value={this.state.lastName}/>
+ </div>
+ </div>
+ );
+
+ nameSection = (
+ <SettingItemMax
+ title='Full Name'
+ inputs={inputs}
+ submit={this.submitName}
+ server_error={serverError}
+ client_error={clientError}
+ updateSection={function(e) {
+ self.updateSection('');
+ e.preventDefault();
+ }}
+ />
+ );
+ } else {
+ var fullName = '';
+
+ if (user.first_name && user.last_name) {
+ fullName = user.first_name + ' ' + user.last_name;
+ } else if (user.first_name) {
+ fullName = user.first_name;
+ } else if (user.last_name) {
+ fullName = user.last_name;
+ }
+
+ nameSection = (
+ <SettingItemMin
+ title='Full Name'
+ describe={fullName}
+ updateSection={function() {
+ self.updateSection('name');
+ }}
+ />
+ );
+ }
+
+ var nicknameSection;
+ if (this.props.activeSection === 'nickname') {
+ inputs.push(
+ <div className='form-group'>
+ <label className='col-sm-5 control-label'>{utils.isMobile() ? '' : 'Nickname'}</label>
+ <div className='col-sm-7'>
+ <input className='form-control' type='text' onChange={this.updateNickname} value={this.state.nickname}/>
+ </div>
+ </div>
+ );
+
+ nicknameSection = (
+ <SettingItemMax
+ title='Nickname'
+ inputs={inputs}
+ submit={this.submitNickname}
+ server_error={serverError}
+ client_error={clientError}
+ updateSection={function(e) {
+ self.updateSection('');
+ e.preventDefault();
+ }}
+ />
+ );
+ } else {
+ nicknameSection = (
+ <SettingItemMin
+ title='Nickname'
+ describe={UserStore.getCurrentUser().nickname}
+ updateSection={function() {
+ self.updateSection('nickname');
+ }}
+ />
+ );
+ }
+
+ var usernameSection;
+ if (this.props.activeSection === 'username') {
+ inputs.push(
+ <div className='form-group'>
+ <label className='col-sm-5 control-label'>{utils.isMobile() ? '' : 'Username'}</label>
+ <div className='col-sm-7'>
+ <input className='form-control' type='text' onChange={this.updateUsername} value={this.state.username}/>
+ </div>
+ </div>
+ );
+
+ usernameSection = (
+ <SettingItemMax
+ title='Username'
+ inputs={inputs}
+ submit={this.submitUsername}
+ server_error={serverError}
+ client_error={clientError}
+ updateSection={function(e) {
+ self.updateSection('');
+ e.preventDefault();
+ }}
+ />
+ );
+ } else {
+ usernameSection = (
+ <SettingItemMin
+ title='Username'
+ describe={UserStore.getCurrentUser().username}
+ updateSection={function() {
+ self.updateSection('username');
+ }}
+ />
+ );
+ }
+ var emailSection;
+ if (this.props.activeSection === 'email') {
+ inputs.push(
+ <div className='form-group'>
+ <label className='col-sm-5 control-label'>Primary Email</label>
+ <div className='col-sm-7'>
+ <input className='form-control' type='text' onChange={this.updateEmail} value={this.state.email}/>
+ </div>
+ </div>
+ );
+
+ emailSection = (
+ <SettingItemMax
+ title='Email'
+ inputs={inputs}
+ submit={this.submitEmail}
+ server_error={serverError}
+ client_error={emailError}
+ updateSection={function(e) {
+ self.updateSection('');
+ e.preventDefault();
+ }}
+ />
+ );
+ } else {
+ emailSection = (
+ <SettingItemMin
+ title='Email'
+ describe={UserStore.getCurrentUser().email}
+ updateSection={function() {
+ self.updateSection('email');
+ }}
+ />
+ );
+ }
+
+ var pictureSection;
+ if (this.props.activeSection === 'picture') {
+ pictureSection = (
+ <SettingPicture
+ title='Profile Picture'
+ submit={this.submitPicture}
+ src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update}
+ server_error={serverError}
+ client_error={clientError}
+ updateSection={function(e) {
+ self.updateSection('');
+ e.preventDefault();
+ }}
+ picture={this.state.picture}
+ pictureChange={this.updatePicture}
+ submitActive={this.submitActive}
+ loadingPicture={this.state.loadingPicture}
+ />
+ );
+ } else {
+ var minMessage = 'Click \'Edit\' to upload an image.';
+ if (user.last_picture_update) {
+ minMessage = 'Image last updated ' + utils.displayDate(user.last_picture_update);
+ }
+ pictureSection = (
+ <SettingItemMin
+ title='Profile Picture'
+ describe={minMessage}
+ updateSection={function() {
+ self.updateSection('picture');
+ }}
+ />
+ );
+ }
+ return (
+ <div>
+ <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' ref='title'><i className='modal-back'></i>General Settings</h4>
+ </div>
+ <div className='user-settings'>
+ <h3 className='tab-header'>General Settings</h3>
+ <div className='divider-dark first'/>
+ {nameSection}
+ <div className='divider-light'/>
+ {usernameSection}
+ <div className='divider-light'/>
+ {nicknameSection}
+ <div className='divider-light'/>
+ {emailSection}
+ <div className='divider-light'/>
+ {pictureSection}
+ <div className='divider-dark'/>
+ </div>
+ </div>
+ );
+ }
+});
diff --git a/web/react/components/user_settings_notifications.jsx b/web/react/components/user_settings_notifications.jsx
new file mode 100644
index 000000000..33ae01eaa
--- /dev/null
+++ b/web/react/components/user_settings_notifications.jsx
@@ -0,0 +1,484 @@
+var UserStore = require('../stores/user_store.jsx');
+var SettingItemMin = require('./setting_item_min.jsx');
+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();
+ var soundNeeded = !utils.isBrowserFirefox();
+
+ var sound = 'true';
+ if (user.notify_props && user.notify_props.desktop_sound) {
+ sound = user.notify_props.desktop_sound;
+ }
+ var desktop = 'all';
+ if (user.notify_props && user.notify_props.desktop) {
+ desktop = user.notify_props.desktop;
+ }
+ var email = 'true';
+ if (user.notify_props && user.notify_props.email) {
+ email = user.notify_props.email;
+ }
+
+ var usernameKey = false;
+ var mentionKey = false;
+ var customKeys = '';
+ var firstNameKey = false;
+ var allKey = false;
+ var channelKey = false;
+
+ if (user.notify_props) {
+ if (user.notify_props.mention_keys) {
+ var keys = user.notify_props.mention_keys.split(',');
+
+ if (keys.indexOf(user.username) !== -1) {
+ usernameKey = true;
+ keys.splice(keys.indexOf(user.username), 1);
+ } else {
+ usernameKey = false;
+ }
+
+ if (keys.indexOf('@' + user.username) !== -1) {
+ mentionKey = true;
+ keys.splice(keys.indexOf('@' + user.username), 1);
+ } else {
+ mentionKey = false;
+ }
+
+ customKeys = keys.join(',');
+ }
+
+ if (user.notify_props.first_name) {
+ firstNameKey = user.notify_props.first_name === 'true';
+ }
+
+ if (user.notify_props.all) {
+ allKey = user.notify_props.all === 'true';
+ }
+
+ if (user.notify_props.channel) {
+ channelKey = user.notify_props.channel === 'true';
+ }
+ }
+
+ return {notifyLevel: desktop, enableEmail: email, soundNeeded: soundNeeded, enableSound: sound, usernameKey: usernameKey, mentionKey: mentionKey, customKeys: customKeys, customKeysChecked: customKeys.length > 0, firstNameKey: firstNameKey, allKey: allKey, channelKey: channelKey};
+}
+
+module.exports = React.createClass({
+ displayName: 'NotificationsTab',
+ propTypes: {
+ user: React.PropTypes.object,
+ updateSection: React.PropTypes.func,
+ updateTab: React.PropTypes.func,
+ activeSection: React.PropTypes.string,
+ activeTab: React.PropTypes.string
+ },
+ handleSubmit: function() {
+ var data = {};
+ data.user_id = this.props.user.id;
+ data.email = this.state.enableEmail;
+ data.desktop_sound = this.state.enableSound;
+ data.desktop = this.state.notifyLevel;
+
+ var mentionKeys = [];
+ if (this.state.usernameKey) {
+ mentionKeys.push(this.props.user.username);
+ }
+ if (this.state.mentionKey) {
+ mentionKeys.push('@' + this.props.user.username);
+ }
+
+ var stringKeys = mentionKeys.join(',');
+ if (this.state.customKeys.length > 0 && this.state.customKeysChecked) {
+ stringKeys += ',' + this.state.customKeys;
+ }
+
+ data.mention_keys = stringKeys;
+ data.first_name = this.state.firstNameKey.toString();
+ data.all = this.state.allKey.toString();
+ data.channel = this.state.channelKey.toString();
+
+ client.updateUserNotifyProps(data,
+ function success() {
+ this.props.updateSection('');
+ AsyncClient.getMe();
+ }.bind(this),
+ function failure(err) {
+ this.setState({serverError: err.message});
+ }.bind(this)
+ );
+ },
+ handleClose: function() {
+ $(this.getDOMNode()).find('.form-control').each(function clearField() {
+ this.value = '';
+ });
+
+ this.setState(assign({}, getNotificationsStateFromStores(), {serverError: null}));
+
+ this.props.updateTab('general');
+ },
+ updateSection: function(section) {
+ this.setState(this.getInitialState());
+ this.props.updateSection(section);
+ },
+ componentDidMount: function() {
+ UserStore.addChangeListener(this.onListenerChange);
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
+ },
+ componentWillUnmount: function() {
+ UserStore.removeChangeListener(this.onListenerChange);
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
+ this.props.updateSection('');
+ },
+ onListenerChange: function() {
+ var newState = getNotificationsStateFromStores();
+ if (!utils.areStatesEqual(newState, this.state)) {
+ this.setState(newState);
+ }
+ },
+ getInitialState: function() {
+ return getNotificationsStateFromStores();
+ },
+ handleNotifyRadio: function(notifyLevel) {
+ this.setState({notifyLevel: notifyLevel});
+ this.refs.wrapper.getDOMNode().focus();
+ },
+ handleEmailRadio: function(enableEmail) {
+ this.setState({enableEmail: enableEmail});
+ this.refs.wrapper.getDOMNode().focus();
+ },
+ handleSoundRadio: function(enableSound) {
+ this.setState({enableSound: enableSound});
+ this.refs.wrapper.getDOMNode().focus();
+ },
+ updateUsernameKey: function(val) {
+ this.setState({usernameKey: val});
+ },
+ updateMentionKey: function(val) {
+ this.setState({mentionKey: val});
+ },
+ updateFirstNameKey: function(val) {
+ this.setState({firstNameKey: val});
+ },
+ updateAllKey: function(val) {
+ this.setState({allKey: val});
+ },
+ updateChannelKey: function(val) {
+ this.setState({channelKey: val});
+ },
+ updateCustomMentionKeys: function() {
+ var checked = this.refs.customcheck.getDOMNode().checked;
+
+ if (checked) {
+ var text = this.refs.custommentions.getDOMNode().value;
+
+ // remove all spaces and split string into individual keys
+ this.setState({customKeys: text.replace(/ /g, ''), customKeysChecked: true});
+ } else {
+ this.setState({customKeys: '', customKeysChecked: false});
+ }
+ },
+ onCustomChange: function() {
+ this.refs.customcheck.getDOMNode().checked = true;
+ this.updateCustomMentionKeys();
+ },
+ render: function() {
+ var serverError = null;
+ if (this.state.serverError) {
+ serverError = this.state.serverError;
+ }
+
+ var self = this;
+
+ var user = this.props.user;
+
+ var desktopSection;
+ if (this.props.activeSection === 'desktop') {
+ var notifyActive = [false, false, false];
+ if (this.state.notifyLevel === 'mention') {
+ notifyActive[1] = true;
+ } else if (this.state.notifyLevel === 'none') {
+ notifyActive[2] = true;
+ } else {
+ notifyActive[0] = true;
+ }
+
+ var inputs = [];
+
+ inputs.push(
+ <div>
+ <div className='radio'>
+ <label>
+ <input type='radio' checked={notifyActive[0]} onClick={function(){self.handleNotifyRadio('all')}}>For all activity</input>
+ </label>
+ <br/>
+ </div>
+ <div className='radio'>
+ <label>
+ <input type='radio' checked={notifyActive[1]} onClick={function(){self.handleNotifyRadio('mention')}}>Only for mentions and private messages</input>
+ </label>
+ <br/>
+ </div>
+ <div className='radio'>
+ <label>
+ <input type='radio' checked={notifyActive[2]} onClick={function(){self.handleNotifyRadio('none')}}>Never</input>
+ </label>
+ </div>
+ </div>
+ );
+
+ desktopSection = (
+ <SettingItemMax
+ title='Send desktop notifications'
+ inputs={inputs}
+ submit={this.handleSubmit}
+ server_error={serverError}
+ updateSection={function(e){self.updateSection('');e.preventDefault();}}
+ />
+ );
+ } else {
+ var describe = '';
+ if (this.state.notifyLevel === 'mention') {
+ describe = 'Only for mentions and private messages';
+ } else if (this.state.notifyLevel === 'none') {
+ describe = 'Never';
+ } else {
+ describe = 'For all activity';
+ }
+
+ desktopSection = (
+ <SettingItemMin
+ title='Send desktop notifications'
+ describe={describe}
+ updateSection={function(){self.updateSection('desktop');}}
+ />
+ );
+ }
+
+ var soundSection;
+ if (this.props.activeSection === 'sound' && this.state.soundNeeded) {
+ var soundActive = ['', ''];
+ if (this.state.enableSound === 'false') {
+ soundActive[1] = 'active';
+ } else {
+ soundActive[0] = 'active';
+ }
+
+ var inputs = [];
+
+ inputs.push(
+ <div>
+ <div className='btn-group' data-toggle='buttons-radio'>
+ <button className={'btn btn-default '+soundActive[0]} onClick={function(){self.handleSoundRadio('true')}}>On</button>
+ <button className={'btn btn-default '+soundActive[1]} onClick={function(){self.handleSoundRadio('false')}}>Off</button>
+ </div>
+ </div>
+ );
+
+ soundSection = (
+ <SettingItemMax
+ title='Desktop notification sounds'
+ inputs={inputs}
+ submit={this.handleSubmit}
+ server_error={serverError}
+ updateSection={function(e){self.updateSection('');e.preventDefault();}}
+ />
+ );
+ } else {
+ var describe = '';
+ if (!this.state.soundNeeded) {
+ describe = 'Please configure notification sounds in your browser settings'
+ } else if (this.state.enableSound === 'false') {
+ describe = 'Off';
+ } else {
+ describe = 'On';
+ }
+
+ soundSection = (
+ <SettingItemMin
+ title='Desktop notification sounds'
+ describe={describe}
+ updateSection={function(){self.updateSection('sound');}}
+ disableOpen = {!this.state.soundNeeded}
+ />
+ );
+ }
+
+ var emailSection;
+ if (this.props.activeSection === 'email') {
+ var emailActive = ['',''];
+ if (this.state.enableEmail === 'false') {
+ emailActive[1] = 'active';
+ } else {
+ emailActive[0] = 'active';
+ }
+
+ var inputs = [];
+
+ inputs.push(
+ <div>
+ <div className='btn-group' data-toggle='buttons-radio'>
+ <button className={'btn btn-default '+emailActive[0]} onClick={function(){self.handleEmailRadio('true')}}>On</button>
+ <button className={'btn btn-default '+emailActive[1]} onClick={function(){self.handleEmailRadio('false')}}>Off</button>
+ </div>
+ <div><br/>{'Email notifications are sent for mentions and private messages after you have been away from ' + config.SiteName + ' for 5 minutes.'}</div>
+ </div>
+ );
+
+ emailSection = (
+ <SettingItemMax
+ title='Email notifications'
+ inputs={inputs}
+ submit={this.handleSubmit}
+ server_error={serverError}
+ updateSection={function(e){self.updateSection('');e.preventDefault();}}
+ />
+ );
+ } else {
+ var describe = '';
+ if (this.state.enableEmail === 'false') {
+ describe = 'Off';
+ } else {
+ describe = 'On';
+ }
+
+ emailSection = (
+ <SettingItemMin
+ title='Email notifications'
+ describe={describe}
+ updateSection={function(){self.updateSection('email');}}
+ />
+ );
+ }
+
+ var keysSection;
+ if (this.props.activeSection === 'keys') {
+ var inputs = [];
+
+ if (user.first_name) {
+ inputs.push(
+ <div>
+ <div className='checkbox'>
+ <label>
+ <input type='checkbox' checked={this.state.firstNameKey} onChange={function(e){self.updateFirstNameKey(e.target.checked);}}>{'Your case sensitive first name "' + user.first_name + '"'}</input>
+ </label>
+ </div>
+ </div>
+ );
+ }
+
+ inputs.push(
+ <div>
+ <div className='checkbox'>
+ <label>
+ <input type='checkbox' checked={this.state.usernameKey} onChange={function(e){self.updateUsernameKey(e.target.checked);}}>{'Your non-case sensitive username "' + user.username + '"'}</input>
+ </label>
+ </div>
+ </div>
+ );
+
+ inputs.push(
+ <div>
+ <div className='checkbox'>
+ <label>
+ <input type='checkbox' checked={this.state.mentionKey} onChange={function(e){self.updateMentionKey(e.target.checked);}}>{'Your username mentioned "@' + user.username + '"'}</input>
+ </label>
+ </div>
+ </div>
+ );
+
+ inputs.push(
+ <div>
+ <div className='checkbox'>
+ <label>
+ <input type='checkbox' checked={this.state.allKey} onChange={function(e){self.updateAllKey(e.target.checked);}}>{'Team-wide mentions "@all"'}</input>
+ </label>
+ </div>
+ </div>
+ );
+
+ inputs.push(
+ <div>
+ <div className='checkbox'>
+ <label>
+ <input type='checkbox' checked={this.state.channelKey} onChange={function(e){self.updateChannelKey(e.target.checked);}}>{'Channel-wide mentions "@channel"'}</input>
+ </label>
+ </div>
+ </div>
+ );
+
+ inputs.push(
+ <div>
+ <div className='checkbox'>
+ <label>
+ <input ref='customcheck' type='checkbox' checked={this.state.customKeysChecked} onChange={this.updateCustomMentionKeys}>{'Other non-case sensitive words, separated by commas:'}</input>
+ </label>
+ </div>
+ <input ref='custommentions' className='form-control mentions-input' type='text' defaultValue={this.state.customKeys} onChange={this.onCustomChange} />
+ </div>
+ );
+
+ keysSection = (
+ <SettingItemMax
+ title='Words that trigger mentions'
+ inputs={inputs}
+ submit={this.handleSubmit}
+ server_error={serverError}
+ updateSection={function(e){self.updateSection('');e.preventDefault();}}
+ />
+ );
+ } else {
+ var keys = [];
+ if (this.state.firstNameKey) keys.push(user.first_name);
+ if (this.state.usernameKey) keys.push(user.username);
+ if (this.state.mentionKey) keys.push('@'+user.username);
+ if (this.state.allKey) keys.push('@all');
+ if (this.state.channelKey) keys.push('@channel');
+ if (this.state.customKeys.length > 0) keys = keys.concat(this.state.customKeys.split(','));
+
+ var describe = '';
+ for (var i = 0; i < keys.length; i++) {
+ describe += '"' + keys[i] + '", ';
+ }
+
+ if (describe.length > 0) {
+ describe = describe.substring(0, describe.length - 2);
+ } else {
+ describe = 'No words configured';
+ }
+
+ keysSection = (
+ <SettingItemMin
+ title='Words that trigger mentions'
+ describe={describe}
+ updateSection={function(){self.updateSection('keys');}}
+ />
+ );
+ }
+
+ return (
+ <div>
+ <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' ref='title'><i className='modal-back'></i>Notifications</h4>
+ </div>
+ <div ref='wrapper' className='user-settings'>
+ <h3 className='tab-header'>Notifications</h3>
+ <div className='divider-dark first'/>
+ {desktopSection}
+ <div className='divider-light'/>
+ {soundSection}
+ <div className='divider-light'/>
+ {emailSection}
+ <div className='divider-light'/>
+ {keysSection}
+ <div className='divider-dark'/>
+ </div>
+ </div>
+
+ );
+ }
+});
diff --git a/web/react/components/user_settings_security.jsx b/web/react/components/user_settings_security.jsx
new file mode 100644
index 000000000..568d3fe99
--- /dev/null
+++ b/web/react/components/user_settings_security.jsx
@@ -0,0 +1,200 @@
+var SettingItemMin = require('./setting_item_min.jsx');
+var SettingItemMax = require('./setting_item_max.jsx');
+var client = require('../utils/client.jsx');
+var AsyncClient = require('../utils/async_client.jsx');
+var Constants = require('../utils/constants.jsx');
+
+module.exports = React.createClass({
+ displayName: 'SecurityTab',
+ submitPassword: function(e) {
+ e.preventDefault();
+
+ var user = this.props.user;
+ var currentPassword = this.state.currentPassword;
+ var newPassword = this.state.newPassword;
+ var confirmPassword = this.state.confirmPassword;
+
+ if (currentPassword === '') {
+ this.setState({passwordError: 'Please enter your current password', serverError: ''});
+ return;
+ }
+
+ if (newPassword.length < 5) {
+ this.setState({passwordError: 'New passwords must be at least 5 characters', serverError: ''});
+ return;
+ }
+
+ if (newPassword !== confirmPassword) {
+ this.setState({passwordError: 'The new passwords you entered do not match', serverError: ''});
+ return;
+ }
+
+ var data = {};
+ data.user_id = user.id;
+ data.current_password = currentPassword;
+ data.new_password = newPassword;
+
+ client.updatePassword(data,
+ function() {
+ this.props.updateSection('');
+ AsyncClient.getMe();
+ this.setState({currentPassword: '', newPassword: '', confirmPassword: ''});
+ }.bind(this),
+ function(err) {
+ var state = this.getInitialState();
+ if (err.message) {
+ state.serverError = err.message;
+ } else {
+ state.serverError = err;
+ }
+ state.passwordError = '';
+ this.setState(state);
+ }.bind(this)
+ );
+ },
+ updateCurrentPassword: function(e) {
+ this.setState({currentPassword: e.target.value});
+ },
+ updateNewPassword: function(e) {
+ this.setState({newPassword: e.target.value});
+ },
+ updateConfirmPassword: function(e) {
+ this.setState({confirmPassword: e.target.value});
+ },
+ handleHistoryOpen: function() {
+ this.setState({willReturn: true});
+ $("#user_settings").modal('hide');
+ },
+ handleDevicesOpen: function() {
+ this.setState({willReturn: true});
+ $("#user_settings").modal('hide');
+ },
+ handleClose: function() {
+ $(this.getDOMNode()).find('.form-control').each(function() {
+ this.value = '';
+ });
+ this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
+
+ if (!this.state.willReturn) {
+ this.props.updateTab('general');
+ } else {
+ this.setState({willReturn: false});
+ }
+ },
+ componentDidMount: function() {
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
+ },
+ componentWillUnmount: function() {
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
+ this.props.updateSection('');
+ },
+ getInitialState: function() {
+ return {currentPassword: '', newPassword: '', confirmPassword: '', willReturn: false};
+ },
+ render: function() {
+ var serverError = this.state.serverError ? this.state.serverError : null;
+ var passwordError = this.state.passwordError ? this.state.passwordError : null;
+
+ var updateSectionStatus;
+ var passwordSection;
+ var self = this;
+ if (this.props.activeSection === 'password') {
+ var inputs = [];
+ var submit = null;
+
+ if (this.props.user.auth_service === '') {
+ inputs.push(
+ <div className='form-group'>
+ <label className='col-sm-5 control-label'>Current Password</label>
+ <div className='col-sm-7'>
+ <input className='form-control' type='password' onChange={this.updateCurrentPassword} value={this.state.currentPassword}/>
+ </div>
+ </div>
+ );
+ inputs.push(
+ <div className='form-group'>
+ <label className='col-sm-5 control-label'>New Password</label>
+ <div className='col-sm-7'>
+ <input className='form-control' type='password' onChange={this.updateNewPassword} value={this.state.newPassword}/>
+ </div>
+ </div>
+ );
+ inputs.push(
+ <div className='form-group'>
+ <label className='col-sm-5 control-label'>Retype New Password</label>
+ <div className='col-sm-7'>
+ <input className='form-control' type='password' onChange={this.updateConfirmPassword} value={this.state.confirmPassword}/>
+ </div>
+ </div>
+ );
+
+ submit = this.submitPassword;
+ } else {
+ inputs.push(
+ <div className='form-group'>
+ <label className='col-sm-12'>Log in occurs through GitLab. Please see your GitLab account settings page to update your password.</label>
+ </div>
+ );
+ }
+
+ updateSectionStatus = function(e) {
+ self.props.updateSection('');
+ self.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
+ e.preventDefault();
+ };
+
+ passwordSection = (
+ <SettingItemMax
+ title='Password'
+ inputs={inputs}
+ submit={submit}
+ server_error={serverError}
+ client_error={passwordError}
+ updateSection={updateSectionStatus}
+ />
+ );
+ } else {
+ var describe;
+ if (this.props.user.auth_service === '') {
+ var d = new Date(this.props.user.last_password_update);
+ var hour = d.getHours() % 12 ? String(d.getHours() % 12) : '12';
+ var min = d.getMinutes() < 10 ? '0' + d.getMinutes() : String(d.getMinutes());
+ var timeOfDay = d.getHours() >= 12 ? ' pm' : ' am';
+ describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay;
+ } else {
+ describe = 'Log in done through GitLab';
+ }
+
+ updateSectionStatus = function() {
+ self.props.updateSection('password');
+ };
+
+ passwordSection = (
+ <SettingItemMin
+ title='Password'
+ describe={describe}
+ updateSection={updateSectionStatus}
+ />
+ );
+ }
+
+ return (
+ <div>
+ <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' ref='title'><i className='modal-back'></i>Security Settings</h4>
+ </div>
+ <div className='user-settings'>
+ <h3 className='tab-header'>Security Settings</h3>
+ <div className='divider-dark first'/>
+ {passwordSection}
+ <div className='divider-dark'/>
+ <br></br>
+ <a data-toggle='modal' className='security-links theme' data-target='#access-history' href='#' onClick={this.handleHistoryOpen}><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}><i className='fa fa-globe'></i>View and Logout of Active Sessions</a>
+ </div>
+ </div>
+ );
+ }
+});
diff --git a/web/react/package.json b/web/react/package.json
index 2bba29e2b..c930c4db6 100644
--- a/web/react/package.json
+++ b/web/react/package.json
@@ -3,22 +3,21 @@
"version": "0.0.1",
"private": true,
"dependencies": {
- "flux": "^2.0.0",
- "keymirror": "~0.1.0",
- "object-assign": "^1.0.0",
- "react": "^0.12.0",
- "autolinker": "^0.15.2",
+ "autolinker": "^0.18.1",
+ "flux": "^2.1.1",
+ "keymirror": "^0.1.1",
+ "object-assign": "^3.0.0",
+ "react": "^0.13.3",
"react-zeroclipboard-mixin": "^0.1.0"
},
"devDependencies": {
- "browserify": "^6.2.0",
- "envify": "^3.0.0",
- "jest-cli": "~0.1.17",
- "reactify": "^0.15.2",
- "uglify-js": "~2.4.15",
- "watchify": "^2.1.1",
- "eslint": "^0.24.1",
- "eslint-plugin-react": "^3.0.0"
+ "browserify": "^11.0.1",
+ "envify": "^3.4.0",
+ "babelify": "^6.1.3",
+ "uglify-js": "^2.4.24",
+ "watchify": "^3.3.1",
+ "eslint": "^1.1.0",
+ "eslint-plugin-react": "^3.2.3"
},
"scripts": {
"start": "watchify --extension=jsx -o ../static/js/bundle.js -v -d ./**/*.jsx",
@@ -28,7 +27,7 @@
},
"browserify": {
"transform": [
- "reactify",
+ ["babelify", {"blacklist": ["strict"]}],
"envify"
]
},
diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx
index 3e4fde30a..2fffb17d0 100644
--- a/web/react/stores/post_store.jsx
+++ b/web/react/stores/post_store.jsx
@@ -172,6 +172,28 @@ var PostStore = assign({}, EventEmitter.prototype, {
getPendingPosts: function(channelId) {
return BrowserStore.getItem('pending_posts_' + channelId);
},
+ storeUnseenDeletedPost: function(post) {
+ var posts = this.getUnseenDeletedPosts(post.channel_id);
+
+ if (!posts) {
+ posts = {};
+ }
+
+ post.message = '(message deleted)';
+ post.state = Constants.POST_DELETED;
+
+ posts[post.id] = post;
+ this.storeUnseenDeletedPosts(post.channel_id, posts);
+ },
+ storeUnseenDeletedPosts: function(channelId, posts) {
+ BrowserStore.setItem('deleted_posts_' + channelId, posts);
+ },
+ getUnseenDeletedPosts: function(channelId) {
+ return BrowserStore.getItem('deleted_posts_' + channelId);
+ },
+ clearUnseenDeletedPosts: function(channelId) {
+ BrowserStore.setItem('deleted_posts_' + channelId, {});
+ },
removePendingPost: function(channelId, pendingPostId) {
this._removePendingPost(channelId, pendingPostId);
this.emitChange();
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index da0b74081..103292abf 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -1,4 +1,3 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var BrowserStore = require('../stores/browser_store.jsx');
@@ -14,73 +13,73 @@ module.exports.trackPage = function() {
global.window.analytics.page();
};
-function handleError(method_name, xhr, status, err) {
- var _LTracker = global.window._LTracker || [];
+function handleError(methodName, xhr, status, err) {
+ var LTracker = global.window.LTracker || [];
var e = null;
try {
e = JSON.parse(xhr.responseText);
- }
- catch(parse_error) {
+ } catch(parseError) {
+ e = null;
}
- var msg = "";
+ var msg = '';
if (e) {
- msg = "error in " + method_name + " msg=" + e.message + " detail=" + e.detailed_error + " rid=" + e.request_id;
- }
- else {
- msg = "error in " + method_name + " status=" + status + " statusCode=" + xhr.status + " err=" + err;
+ msg = 'error in ' + methodName + ' msg=' + e.message + ' detail=' + e.detailed_error + ' rid=' + e.request_id;
+ } else {
+ msg = 'error in ' + methodName + ' status=' + status + ' statusCode=' + xhr.status + ' err=' + err;
- if (xhr.status === 0)
- e = { message: "There appears to be a problem with your internet connection" };
- else
- e = { message: "We received an unexpected status code from the server (" + xhr.status + ")"};
+ if (xhr.status === 0) {
+ e = {message: 'There appears to be a problem with your internet connection'};
+ } else {
+ e = {message: 'We received an unexpected status code from the server (' + xhr.status + ')'};
+ }
}
- console.error(msg)
- console.error(e);
- _LTracker.push(msg);
+ console.error(msg); //eslint-disable-line no-console
+ console.error(e); //eslint-disable-line no-console
+ LTracker.push(msg);
- module.exports.track('api', 'api_weberror', method_name, 'message', msg);
+ module.exports.track('api', 'api_weberror', methodName, 'message', msg);
- if (xhr.status == 401) {
- if (window.location.href.indexOf("/channels") === 0) {
- window.location.pathname = '/login?redirect=' + encodeURIComponent(window.location.pathname+window.location.search);
+ if (xhr.status === 401) {
+ if (window.location.href.indexOf('/channels') === 0) {
+ window.location.pathname = '/login?redirect=' + encodeURIComponent(window.location.pathname + window.location.search);
} else {
var teamURL = window.location.href.split('/channels')[0];
- window.location.href = teamURL + '/login?redirect=' + encodeURIComponent(window.location.pathname+window.location.search);
+ window.location.href = teamURL + '/login?redirect=' + encodeURIComponent(window.location.pathname + window.location.search);
}
}
return e;
}
-module.exports.createTeamFromSignup = function(team_signup, success, error) {
+module.exports.createTeamFromSignup = function(teamSignup, success, error) {
$.ajax({
- url: "/api/v1/teams/create_from_signup",
+ url: '/api/v1/teams/create_from_signup',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
- data: JSON.stringify(team_signup),
+ data: JSON.stringify(teamSignup),
success: success,
- error: function(xhr, status, err) {
- e = handleError("createTeamFromSignup", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('createTeamFromSignup', xhr, status, err);
error(e);
}
});
};
-module.exports.createUser = function(user, data, email_hash, success, error) {
+module.exports.createUser = function(user, data, emailHash, success, error) {
$.ajax({
- url: "/api/v1/users/create?d=" + encodeURIComponent(data) + "&h=" + encodeURIComponent(email_hash),
+ url: '/api/v1/users/create?d=' + encodeURIComponent(data) + '&h=' + encodeURIComponent(emailHash),
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(user),
success: success,
- error: function(xhr, status, err) {
- e = handleError("createUser", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('createUser', xhr, status, err);
error(e);
}
});
@@ -90,14 +89,14 @@ module.exports.createUser = function(user, data, email_hash, success, error) {
module.exports.updateUser = function(user, success, error) {
$.ajax({
- url: "/api/v1/users/update",
+ url: '/api/v1/users/update',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(user),
success: success,
- error: function(xhr, status, err) {
- e = handleError("updateUser", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('updateUser', xhr, status, err);
error(e);
}
});
@@ -107,14 +106,14 @@ module.exports.updateUser = function(user, success, error) {
module.exports.updatePassword = function(data, success, error) {
$.ajax({
- url: "/api/v1/users/newpassword",
+ url: '/api/v1/users/newpassword',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success: success,
- error: function(xhr, status, err) {
- e = handleError("newPassword", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('newPassword', xhr, status, err);
error(e);
}
});
@@ -124,14 +123,14 @@ module.exports.updatePassword = function(data, success, error) {
module.exports.updateUserNotifyProps = function(data, success, error) {
$.ajax({
- url: "/api/v1/users/update_notify",
+ url: '/api/v1/users/update_notify',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success: success,
- error: function(xhr, status, err) {
- e = handleError("updateUserNotifyProps", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('updateUserNotifyProps', xhr, status, err);
error(e);
}
});
@@ -139,14 +138,14 @@ module.exports.updateUserNotifyProps = function(data, success, error) {
module.exports.updateRoles = function(data, success, error) {
$.ajax({
- url: "/api/v1/users/update_roles",
+ url: '/api/v1/users/update_roles',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success: success,
- error: function(xhr, status, err) {
- e = handleError("updateRoles", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('updateRoles', xhr, status, err);
error(e);
}
});
@@ -155,19 +154,19 @@ module.exports.updateRoles = function(data, success, error) {
};
module.exports.updateActive = function(userId, active, success, error) {
- var data = {};
- data["user_id"] = userId;
- data["active"] = "" + active;
-
+ var data = {};
+ data.user_id = userId;
+ data.active = '' + active;
+
$.ajax({
- url: "/api/v1/users/update_active",
+ url: '/api/v1/users/update_active',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success: success,
- error: function(xhr, status, err) {
- e = handleError("updateActive", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('updateActive', xhr, status, err);
error(e);
}
});
@@ -177,14 +176,14 @@ module.exports.updateActive = function(userId, active, success, error) {
module.exports.sendPasswordReset = function(data, success, error) {
$.ajax({
- url: "/api/v1/users/send_password_reset",
+ url: '/api/v1/users/send_password_reset',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success: success,
- error: function(xhr, status, err) {
- e = handleError("sendPasswordReset", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('sendPasswordReset', xhr, status, err);
error(e);
}
});
@@ -194,14 +193,14 @@ module.exports.sendPasswordReset = function(data, success, error) {
module.exports.resetPassword = function(data, success, error) {
$.ajax({
- url: "/api/v1/users/reset_password",
+ url: '/api/v1/users/reset_password',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success: success,
- error: function(xhr, status, err) {
- e = handleError("resetPassword", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('resetPassword', xhr, status, err);
error(e);
}
});
@@ -213,24 +212,24 @@ module.exports.logout = function() {
module.exports.track('api', 'api_users_logout');
var currentTeamUrl = TeamStore.getCurrentTeamUrl();
BrowserStore.clear();
- window.location.href = currentTeamUrl + "/logout";
+ window.location.href = currentTeamUrl + '/logout';
};
module.exports.loginByEmail = function(name, email, password, success, error) {
$.ajax({
- url: "/api/v1/users/login",
+ url: '/api/v1/users/login',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify({name: name, email: email, password: password}),
- success: function(data, textStatus, xhr) {
+ success: function onSuccess(data, textStatus, xhr) {
module.exports.track('api', 'api_users_login_success', data.team_id, 'email', data.email);
success(data, textStatus, xhr);
},
- error: function(xhr, status, err) {
+ error: function onError(xhr, status, err) {
module.exports.track('api', 'api_users_login_fail', window.getSubDomain(), 'email', email);
- e = handleError("loginByEmail", xhr, status, err);
+ var e = handleError('loginByEmail', xhr, status, err);
error(e);
}
});
@@ -238,14 +237,14 @@ module.exports.loginByEmail = function(name, email, password, success, error) {
module.exports.revokeSession = function(altId, success, error) {
$.ajax({
- url: "/api/v1/users/revoke_session",
+ url: '/api/v1/users/revoke_session',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify({id: altId}),
success: success,
- error: function(xhr, status, err) {
- e = handleError("revokeSession", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('revokeSession', xhr, status, err);
error(e);
}
});
@@ -253,13 +252,13 @@ module.exports.revokeSession = function(altId, success, error) {
module.exports.getSessions = function(userId, success, error) {
$.ajax({
- url: "/api/v1/users/"+userId+"/sessions",
+ url: '/api/v1/users/' + userId + '/sessions',
dataType: 'json',
contentType: 'application/json',
type: 'GET',
success: success,
- error: function(xhr, status, err) {
- e = handleError("getSessions", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('getSessions', xhr, status, err);
error(e);
}
});
@@ -267,13 +266,13 @@ module.exports.getSessions = function(userId, success, error) {
module.exports.getAudits = function(userId, success, error) {
$.ajax({
- url: "/api/v1/users/"+userId+"/audits",
+ url: '/api/v1/users/' + userId + '/audits',
dataType: 'json',
contentType: 'application/json',
type: 'GET',
success: success,
- error: function(xhr, status, err) {
- e = handleError("getAudits", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('getAudits', xhr, status, err);
error(e);
}
});
@@ -281,10 +280,9 @@ module.exports.getAudits = function(userId, success, error) {
module.exports.getMeSynchronous = function(success, error) {
var currentUser = null;
-
$.ajax({
async: false,
- url: "/api/v1/users/me",
+ url: '/api/v1/users/me',
dataType: 'json',
contentType: 'application/json',
type: 'GET',
@@ -294,14 +292,14 @@ module.exports.getMeSynchronous = function(success, error) {
success(data, textStatus, xhr);
}
},
- error: function(xhr, status, err) {
+ error: function onError(xhr, status, err) {
var ieChecker = window.navigator.userAgent; // This and the condition below is used to check specifically for browsers IE10 & 11 to suppress a 200 'OK' error from appearing on login
- if (xhr.status != 200 || !(ieChecker.indexOf("Trident/7.0") > 0 || ieChecker.indexOf("Trident/6.0") > 0)) {
+ if (xhr.status !== 200 || !(ieChecker.indexOf('Trident/7.0') > 0 || ieChecker.indexOf('Trident/6.0') > 0)) {
if (error) {
- e = handleError('getMeSynchronous', xhr, status, err);
+ var e = handleError('getMeSynchronous', xhr, status, err);
error(e);
- };
- };
+ }
+ }
}
});
@@ -310,14 +308,14 @@ module.exports.getMeSynchronous = function(success, error) {
module.exports.inviteMembers = function(data, success, error) {
$.ajax({
- url: "/api/v1/teams/invite_members",
+ url: '/api/v1/teams/invite_members',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success: success,
- error: function(xhr, status, err) {
- e = handleError("inviteMembers", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('inviteMembers', xhr, status, err);
error(e);
}
});
@@ -327,14 +325,14 @@ module.exports.inviteMembers = function(data, success, error) {
module.exports.updateTeamDisplayName = function(data, success, error) {
$.ajax({
- url: "/api/v1/teams/update_name",
+ url: '/api/v1/teams/update_name',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success: success,
- error: function(xhr, status, err) {
- e = handleError("updateTeamDisplayName", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('updateTeamDisplayName', xhr, status, err);
error(e);
}
});
@@ -344,14 +342,14 @@ module.exports.updateTeamDisplayName = function(data, success, error) {
module.exports.signupTeam = function(email, success, error) {
$.ajax({
- url: "/api/v1/teams/signup",
+ url: '/api/v1/teams/signup',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify({email: email}),
success: success,
- error: function(xhr, status, err) {
- e = handleError("singupTeam", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('singupTeam', xhr, status, err);
error(e);
}
});
@@ -361,14 +359,14 @@ module.exports.signupTeam = function(email, success, error) {
module.exports.createTeam = function(team, success, error) {
$.ajax({
- url: "/api/v1/teams/create",
+ url: '/api/v1/teams/create',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(team),
success: success,
- error: function(xhr, status, err) {
- e = handleError("createTeam", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('createTeam', xhr, status, err);
error(e);
}
});
@@ -376,14 +374,14 @@ module.exports.createTeam = function(team, success, error) {
module.exports.findTeamByName = function(teamName, success, error) {
$.ajax({
- url: "/api/v1/teams/find_team_by_name",
+ url: '/api/v1/teams/find_team_by_name',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify({name: teamName}),
success: success,
- error: function(xhr, status, err) {
- e = handleError("findTeamByName", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('findTeamByName', xhr, status, err);
error(e);
}
});
@@ -391,14 +389,14 @@ module.exports.findTeamByName = function(teamName, success, error) {
module.exports.findTeamsSendEmail = function(email, success, error) {
$.ajax({
- url: "/api/v1/teams/email_teams",
+ url: '/api/v1/teams/email_teams',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify({email: email}),
success: success,
- error: function(xhr, status, err) {
- e = handleError("findTeamsSendEmail", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('findTeamsSendEmail', xhr, status, err);
error(e);
}
});
@@ -408,14 +406,14 @@ module.exports.findTeamsSendEmail = function(email, success, error) {
module.exports.findTeams = function(email, success, error) {
$.ajax({
- url: "/api/v1/teams/find_teams",
+ url: '/api/v1/teams/find_teams',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify({email: email}),
success: success,
- error: function(xhr, status, err) {
- e = handleError("findTeams", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('findTeams', xhr, status, err);
error(e);
}
});
@@ -423,14 +421,14 @@ module.exports.findTeams = function(email, success, error) {
module.exports.createChannel = function(channel, success, error) {
$.ajax({
- url: "/api/v1/channels/create",
+ url: '/api/v1/channels/create',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(channel),
success: success,
- error: function(xhr, status, err) {
- e = handleError("createChannel", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('createChannel', xhr, status, err);
error(e);
}
});
@@ -457,14 +455,14 @@ module.exports.createDirectChannel = function(channel, userId, success, error) {
module.exports.updateChannel = function(channel, success, error) {
$.ajax({
- url: "/api/v1/channels/update",
+ url: '/api/v1/channels/update',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(channel),
success: success,
- error: function(xhr, status, err) {
- e = handleError("updateChannel", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('updateChannel', xhr, status, err);
error(e);
}
});
@@ -474,14 +472,14 @@ module.exports.updateChannel = function(channel, success, error) {
module.exports.updateChannelDesc = function(data, success, error) {
$.ajax({
- url: "/api/v1/channels/update_desc",
+ url: '/api/v1/channels/update_desc',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success: success,
- error: function(xhr, status, err) {
- e = handleError("updateChannelDesc", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('updateChannelDesc', xhr, status, err);
error(e);
}
});
@@ -491,14 +489,14 @@ module.exports.updateChannelDesc = function(data, success, error) {
module.exports.updateNotifyLevel = function(data, success, error) {
$.ajax({
- url: "/api/v1/channels/update_notify_level",
+ url: '/api/v1/channels/update_notify_level',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success: success,
- error: function(xhr, status, err) {
- e = handleError("updateNotifyLevel", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('updateNotifyLevel', xhr, status, err);
error(e);
}
});
@@ -506,13 +504,13 @@ module.exports.updateNotifyLevel = function(data, success, error) {
module.exports.joinChannel = function(id, success, error) {
$.ajax({
- url: "/api/v1/channels/" + id + "/join",
+ url: '/api/v1/channels/' + id + '/join',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
success: success,
- error: function(xhr, status, err) {
- e = handleError("joinChannel", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('joinChannel', xhr, status, err);
error(e);
}
});
@@ -522,13 +520,13 @@ module.exports.joinChannel = function(id, success, error) {
module.exports.leaveChannel = function(id, success, error) {
$.ajax({
- url: "/api/v1/channels/" + id + "/leave",
+ url: '/api/v1/channels/' + id + '/leave',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
success: success,
- error: function(xhr, status, err) {
- e = handleError("leaveChannel", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('leaveChannel', xhr, status, err);
error(e);
}
});
@@ -538,13 +536,13 @@ module.exports.leaveChannel = function(id, success, error) {
module.exports.deleteChannel = function(id, success, error) {
$.ajax({
- url: "/api/v1/channels/" + id + "/delete",
+ url: '/api/v1/channels/' + id + '/delete',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
success: success,
- error: function(xhr, status, err) {
- e = handleError("deleteChannel", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('deleteChannel', xhr, status, err);
error(e);
}
});
@@ -554,13 +552,13 @@ module.exports.deleteChannel = function(id, success, error) {
module.exports.updateLastViewedAt = function(channelId, success, error) {
$.ajax({
- url: "/api/v1/channels/" + channelId + "/update_last_viewed_at",
+ url: '/api/v1/channels/' + channelId + '/update_last_viewed_at',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
success: success,
- error: function(xhr, status, err) {
- e = handleError("updateLastViewedAt", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('updateLastViewedAt', xhr, status, err);
error(e);
}
});
@@ -573,7 +571,7 @@ function getChannels(success, error) {
type: 'GET',
success: success,
ifModified: true,
- error: function(xhr, status, err) {
+ error: function onError(xhr, status, err) {
var e = handleError('getChannels', xhr, status, err);
error(e);
}
@@ -583,12 +581,12 @@ module.exports.getChannels = getChannels;
module.exports.getChannel = function(id, success, error) {
$.ajax({
- url: "/api/v1/channels/" + id + "/",
+ url: '/api/v1/channels/' + id + '/',
dataType: 'json',
type: 'GET',
success: success,
- error: function(xhr, status, err) {
- e = handleError("getChannel", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('getChannel', xhr, status, err);
error(e);
}
});
@@ -598,13 +596,13 @@ module.exports.getChannel = function(id, success, error) {
module.exports.getMoreChannels = function(success, error) {
$.ajax({
- url: "/api/v1/channels/more",
+ url: '/api/v1/channels/more',
dataType: 'json',
type: 'GET',
success: success,
ifModified: true,
- error: function(xhr, status, err) {
- e = handleError("getMoreChannels", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('getMoreChannels', xhr, status, err);
error(e);
}
});
@@ -617,7 +615,7 @@ function getChannelCounts(success, error) {
type: 'GET',
success: success,
ifModified: true,
- error: function(xhr, status, err) {
+ error: function onError(xhr, status, err) {
var e = handleError('getChannelCounts', xhr, status, err);
error(e);
}
@@ -627,12 +625,12 @@ module.exports.getChannelCounts = getChannelCounts;
module.exports.getChannelExtraInfo = function(id, success, error) {
$.ajax({
- url: "/api/v1/channels/" + id + "/extra_info",
+ url: '/api/v1/channels/' + id + '/extra_info',
dataType: 'json',
type: 'GET',
success: success,
- error: function(xhr, status, err) {
- e = handleError("getChannelExtraInfo", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('getChannelExtraInfo', xhr, status, err);
error(e);
}
});
@@ -640,14 +638,14 @@ module.exports.getChannelExtraInfo = function(id, success, error) {
module.exports.executeCommand = function(channelId, command, suggest, success, error) {
$.ajax({
- url: "/api/v1/command",
+ url: '/api/v1/command',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
- data: JSON.stringify({channelId: channelId, command: command, suggest: "" + suggest}),
+ data: JSON.stringify({channelId: channelId, command: command, suggest: '' + suggest}),
success: success,
- error: function(xhr, status, err) {
- e = handleError("executeCommand", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('executeCommand', xhr, status, err);
error(e);
}
});
@@ -655,18 +653,14 @@ module.exports.executeCommand = function(channelId, command, suggest, success, e
module.exports.getPosts = function(channelId, offset, limit, success, error, complete) {
$.ajax({
- url: "/api/v1/channels/" + channelId + "/posts/" + offset + "/" + limit,
+ url: '/api/v1/channels/' + channelId + '/posts/' + offset + '/' + limit,
dataType: 'json',
type: 'GET',
ifModified: true,
success: success,
- error: function(xhr, status, err) {
- try {
- e = handleError("getPosts", xhr, status, err);
- error(e);
- } catch(er) {
- console.error(er);
- }
+ error: function onError(xhr, status, err) {
+ var e = handleError('getPosts', xhr, status, err);
+ error(e);
},
complete: complete
});
@@ -674,13 +668,13 @@ module.exports.getPosts = function(channelId, offset, limit, success, error, com
module.exports.getPost = function(channelId, postId, success, error) {
$.ajax({
- url: "/api/v1/channels/" + channelId + "/post/" + postId,
+ url: '/api/v1/channels/' + channelId + '/post/' + postId,
dataType: 'json',
type: 'GET',
ifModified: false,
success: success,
- error: function(xhr, status, err) {
- e = handleError("getPost", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('getPost', xhr, status, err);
error(e);
}
});
@@ -688,13 +682,13 @@ module.exports.getPost = function(channelId, postId, success, error) {
module.exports.search = function(terms, success, error) {
$.ajax({
- url: "/api/v1/posts/search",
+ url: '/api/v1/posts/search',
dataType: 'json',
type: 'GET',
- data: {"terms": terms},
+ data: {terms: terms},
success: success,
- error: function(xhr, status, err) {
- e = handleError("search", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('search', xhr, status, err);
error(e);
}
});
@@ -704,13 +698,13 @@ module.exports.search = function(terms, success, error) {
module.exports.deletePost = function(channelId, id, success, error) {
$.ajax({
- url: "/api/v1/channels/" + channelId + "/post/" + id + "/delete",
+ url: '/api/v1/channels/' + channelId + '/post/' + id + '/delete',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
success: success,
- error: function(xhr, status, err) {
- e = handleError("deletePost", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('deletePost', xhr, status, err);
error(e);
}
});
@@ -720,14 +714,14 @@ module.exports.deletePost = function(channelId, id, success, error) {
module.exports.createPost = function(post, channel, success, error) {
$.ajax({
- url: "/api/v1/channels/"+ post.channel_id + "/create",
+ url: '/api/v1/channels/' + post.channel_id + '/create',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(post),
success: success,
- error: function(xhr, status, err) {
- e = handleError("createPost", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('createPost', xhr, status, err);
error(e);
}
});
@@ -740,20 +734,20 @@ module.exports.createPost = function(post, channel, success, error) {
// channel_type: channel.type,
// length: post.message.length,
// files: (post.filenames || []).length,
- // mentions: (post.message.match("/<mention>/g") || []).length
+ // mentions: (post.message.match('/<mention>/g') || []).length
// });
};
module.exports.updatePost = function(post, success, error) {
$.ajax({
- url: "/api/v1/channels/"+ post.channel_id + "/update",
+ url: '/api/v1/channels/' + post.channel_id + '/update',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(post),
success: success,
- error: function(xhr, status, err) {
- e = handleError("updatePost", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('updatePost', xhr, status, err);
error(e);
}
});
@@ -763,14 +757,14 @@ module.exports.updatePost = function(post, success, error) {
module.exports.addChannelMember = function(id, data, success, error) {
$.ajax({
- url: "/api/v1/channels/" + id + "/add",
+ url: '/api/v1/channels/' + id + '/add',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success: success,
- error: function(xhr, status, err) {
- e = handleError("addChannelMember", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('addChannelMember', xhr, status, err);
error(e);
}
});
@@ -780,14 +774,14 @@ module.exports.addChannelMember = function(id, data, success, error) {
module.exports.removeChannelMember = function(id, data, success, error) {
$.ajax({
- url: "/api/v1/channels/" + id + "/remove",
+ url: '/api/v1/channels/' + id + '/remove',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success: success,
- error: function(xhr, status, err) {
- e = handleError("removeChannelMember", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('removeChannelMember', xhr, status, err);
error(e);
}
});
@@ -797,14 +791,14 @@ module.exports.removeChannelMember = function(id, data, success, error) {
module.exports.getProfiles = function(success, error) {
$.ajax({
- url: "/api/v1/users/profiles",
+ url: '/api/v1/users/profiles',
dataType: 'json',
contentType: 'application/json',
type: 'GET',
success: success,
ifModified: true,
- error: function(xhr, status, err) {
- e = handleError("getProfiles", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('getProfiles', xhr, status, err);
error(e);
}
});
@@ -812,16 +806,16 @@ module.exports.getProfiles = function(success, error) {
module.exports.uploadFile = function(formData, success, error) {
var request = $.ajax({
- url: "/api/v1/files/upload",
+ url: '/api/v1/files/upload',
type: 'POST',
data: formData,
cache: false,
contentType: false,
processData: false,
success: success,
- error: function(xhr, status, err) {
+ error: function onError(xhr, status, err) {
if (err !== 'abort') {
- e = handleError("uploadFile", xhr, status, err);
+ var e = handleError('uploadFile', xhr, status, err);
error(e);
}
}
@@ -834,13 +828,13 @@ module.exports.uploadFile = function(formData, success, error) {
module.exports.getPublicLink = function(data, success, error) {
$.ajax({
- url: "/api/v1/files/get_public_link",
+ url: '/api/v1/files/get_public_link',
dataType: 'json',
type: 'POST',
data: JSON.stringify(data),
success: success,
- error: function(xhr, status, err) {
- e = handleError("getPublicLink", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('getPublicLink', xhr, status, err);
error(e);
}
});
@@ -848,15 +842,31 @@ module.exports.getPublicLink = function(data, success, error) {
module.exports.uploadProfileImage = function(imageData, success, error) {
$.ajax({
- url: "/api/v1/users/newimage",
+ url: '/api/v1/users/newimage',
type: 'POST',
data: imageData,
cache: false,
contentType: false,
processData: false,
success: success,
- error: function(xhr, status, err) {
- e = handleError("uploadProfileImage", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('uploadProfileImage', xhr, status, err);
+ error(e);
+ }
+ });
+};
+
+module.exports.importSlack = function(fileData, success, error) {
+ $.ajax({
+ url: '/api/v1/teams/import_team',
+ type: 'POST',
+ data: fileData,
+ cache: false,
+ contentType: false,
+ processData: false,
+ success: success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('importTeam', xhr, status, err);
error(e);
}
});
@@ -864,13 +874,13 @@ module.exports.uploadProfileImage = function(imageData, success, error) {
module.exports.getStatuses = function(success, error) {
$.ajax({
- url: "/api/v1/users/status",
+ url: '/api/v1/users/status',
dataType: 'json',
contentType: 'application/json',
type: 'GET',
success: success,
- error: function(xhr, status, err) {
- e = handleError("getStatuses", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('getStatuses', xhr, status, err);
error(e);
}
});
@@ -878,13 +888,13 @@ module.exports.getStatuses = function(success, error) {
module.exports.getMyTeam = function(success, error) {
$.ajax({
- url: "/api/v1/teams/me",
+ url: '/api/v1/teams/me',
dataType: 'json',
type: 'GET',
success: success,
ifModified: true,
- error: function(xhr, status, err) {
- e = handleError("getMyTeam", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('getMyTeam', xhr, status, err);
error(e);
}
});
@@ -892,14 +902,14 @@ module.exports.getMyTeam = function(success, error) {
module.exports.updateValetFeature = function(data, success, error) {
$.ajax({
- url: "/api/v1/teams/update_valet_feature",
+ url: '/api/v1/teams/update_valet_feature',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success: success,
- error: function(xhr, status, err) {
- e = handleError("updateValetFeature", xhr, status, err);
+ error: function onError(xhr, status, err) {
+ var e = handleError('updateValetFeature', xhr, status, err);
error(e);
}
});
@@ -914,10 +924,10 @@ function getConfig(success, error) {
type: 'GET',
ifModified: true,
success: success,
- error: function(xhr, status, err) {
+ error: function onError(xhr, status, err) {
var e = handleError('getConfig', xhr, status, err);
error(e);
}
});
-};
+}
module.exports.getConfig = getConfig;
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 41b02c8d6..8239a4a69 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -63,8 +63,9 @@ module.exports = {
GOOGLE_SERVICE: 'google',
POST_CHUNK_SIZE: 60,
MAX_POST_CHUNKS: 3,
- POST_LOADING: "loading",
- POST_FAILED: "failed",
+ POST_LOADING: 'loading',
+ POST_FAILED: 'failed',
+ POST_DELETED: 'deleted',
RESERVED_TEAM_NAMES: [
"www",
"web",
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 618cc1557..c2d250e74 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var ChannelStore = require('../stores/channel_store.jsx')
+var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -11,8 +11,8 @@ var client = require('./client.jsx');
var Autolinker = require('autolinker');
module.exports.isEmail = function(email) {
- var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
- return regex.test(email);
+ var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
+ return regex.test(email);
};
module.exports.cleanUpUrlable = function(input) {
@@ -23,174 +23,187 @@ module.exports.cleanUpUrlable = function(input) {
};
module.exports.isTestDomain = function() {
-
- if ((/^localhost/).test(window.location.hostname))
+ if ((/^localhost/).test(window.location.hostname)) {
return true;
+ }
- if ((/^dockerhost/).test(window.location.hostname))
+ if ((/^dockerhost/).test(window.location.hostname)) {
return true;
+ }
- if ((/^test/).test(window.location.hostname))
+ if ((/^test/).test(window.location.hostname)) {
return true;
+ }
- if ((/^127.0./).test(window.location.hostname))
+ if ((/^127.0./).test(window.location.hostname)) {
return true;
+ }
- if ((/^192.168./).test(window.location.hostname))
+ if ((/^192.168./).test(window.location.hostname)) {
return true;
+ }
- if ((/^10./).test(window.location.hostname))
+ if ((/^10./).test(window.location.hostname)) {
return true;
+ }
- if ((/^176./).test(window.location.hostname))
+ if ((/^176./).test(window.location.hostname)) {
return true;
+ }
return false;
};
-var getSubDomain = function() {
-
- if (module.exports.isTestDomain())
- return "";
+function getSubDomain() {
+ if (module.exports.isTestDomain()) {
+ return '';
+ }
- if ((/^www/).test(window.location.hostname))
- return "";
+ if ((/^www/).test(window.location.hostname)) {
+ return '';
+ }
- if ((/^beta/).test(window.location.hostname))
- return "";
+ if ((/^beta/).test(window.location.hostname)) {
+ return '';
+ }
- if ((/^ci/).test(window.location.hostname))
- return "";
+ if ((/^ci/).test(window.location.hostname)) {
+ return '';
+ }
- var parts = window.location.hostname.split(".");
+ var parts = window.location.hostname.split('.');
- if (parts.length != 3)
- return "";
+ if (parts.length !== 3) {
+ return '';
+ }
- return parts[0];
+ return parts[0];
}
global.window.getSubDomain = getSubDomain;
module.exports.getSubDomain = getSubDomain;
module.exports.getDomainWithOutSub = function() {
+ var parts = window.location.host.split('.');
- var parts = window.location.host.split(".");
+ if (parts.length === 1) {
+ if (parts[0].indexOf('dockerhost') > -1) {
+ return 'dockerhost:8065';
+ }
- if (parts.length == 1) {
- if (parts[0].indexOf("dockerhost") > -1) {
- return "dockerhost:8065";
- }
- else {
- return "localhost:8065";
+ return 'localhost:8065';
}
- }
- return parts[1] + "." + parts[2];
-}
+ return parts[1] + '.' + parts[2];
+};
module.exports.getCookie = function(name) {
- var value = "; " + document.cookie;
- var parts = value.split("; " + name + "=");
- if (parts.length == 2) return parts.pop().split(";").shift();
-}
-
+ var value = '; ' + document.cookie;
+ var parts = value.split('; ' + name + '=');
+ if (parts.length === 2) {
+ return parts.pop().split(';').shift();
+ }
+};
module.exports.notifyMe = function(title, body, channel) {
- if ("Notification" in window && Notification.permission !== 'denied') {
- Notification.requestPermission(function (permission) {
- if (Notification.permission !== permission) {
- Notification.permission = permission;
- }
-
- if (permission === "granted") {
- var notification = new Notification(title,
- { body: body, tag: body, icon: '/static/images/icon50x50.gif' }
- );
- notification.onclick = function() {
- window.focus();
- if (channel) {
- module.exports.switchChannel(channel);
- } else {
- window.location.href = "/";
- }
- };
- setTimeout(function(){
- notification.close();
- }, 5000);
- }
- });
- }
-}
+ if ('Notification' in window && Notification.permission !== 'denied') {
+ Notification.requestPermission(function onRequestPermission(permission) {
+ if (Notification.permission !== permission) {
+ Notification.permission = permission;
+ }
+
+ if (permission === 'granted') {
+ var notification = new Notification(title, {body: body, tag: body, icon: '/static/images/icon50x50.gif'});
+ notification.onclick = function onClick() {
+ window.focus();
+ if (channel) {
+ module.exports.switchChannel(channel);
+ } else {
+ window.location.href = '/';
+ }
+ };
+ setTimeout(function closeNotificationOnTimeout() {
+ notification.close();
+ }, 5000);
+ }
+ });
+ }
+};
module.exports.ding = function() {
if (!module.exports.isBrowserFirefox()) {
var audio = new Audio('/static/images/ding.mp3');
audio.play();
}
-}
+};
module.exports.getUrlParameter = function(sParam) {
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
- for (var i = 0; i < sURLVariables.length; i++)
- {
+ for (var i = 0; i < sURLVariables.length; i++) {
var sParameterName = sURLVariables[i].split('=');
- if (sParameterName[0] == sParam)
- {
+ if (sParameterName[0] === sParam) {
return sParameterName[1];
}
}
return null;
-}
+};
module.exports.getDateForUnixTicks = function(ticks) {
- return new Date(ticks)
-}
+ return new Date(ticks);
+};
module.exports.displayDate = function(ticks) {
- var d = new Date(ticks);
- var m_names = new Array("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December");
+ var d = new Date(ticks);
+ var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
- return m_names[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear();
-}
+ return monthNames[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear();
+};
module.exports.displayTime = function(ticks) {
- var d = new Date(ticks);
- var hours = d.getHours();
- var minutes = d.getMinutes();
- var ampm = hours >= 12 ? "PM" : "AM";
- hours = hours % 12;
- hours = hours ? hours : "12"
- minutes = minutes > 9 ? minutes : '0'+minutes
- return hours + ":" + minutes + " " + ampm
-}
+ var d = new Date(ticks);
+ var hours = d.getHours();
+ var minutes = d.getMinutes();
-module.exports.displayDateTime = function(ticks) {
- var seconds = Math.floor((Date.now() - ticks) / 1000)
+ var ampm = 'AM';
+ if (hours >= 12) {
+ ampm = 'AM';
+ }
- interval = Math.floor(seconds / 3600);
+ hours = hours % 12;
+ if (!hours) {
+ hours = '12';
+ }
+ if (minutes <= 9) {
+ minutes = 0 + minutes;
+ }
+ return hours + ':' + minutes + ' ' + ampm;
+};
- if (interval > 24) {
- return this.displayTime(ticks)
- }
+module.exports.displayDateTime = function(ticks) {
+ var seconds = Math.floor((Date.now() - ticks) / 1000);
- if (interval > 1) {
- return interval + " hours ago";
- }
+ var interval = Math.floor(seconds / 3600);
- if (interval == 1) {
- return interval + " hour ago";
- }
+ if (interval > 24) {
+ return this.displayTime(ticks);
+ }
- interval = Math.floor(seconds / 60);
- if (interval > 1) {
- return interval + " minutes ago";
- }
+ if (interval > 1) {
+ return interval + ' hours ago';
+ }
- return "1 minute ago";
+ if (interval === 1) {
+ return interval + ' hour ago';
+ }
-}
+ interval = Math.floor(seconds / 60);
+ if (interval > 1) {
+ return interval + ' minutes ago';
+ }
+
+ return '1 minute ago';
+};
module.exports.displayCommentDateTime = function(ticks) {
return module.exports.displayDate(ticks) + ' ' + module.exports.displayTime(ticks);
@@ -199,68 +212,135 @@ module.exports.displayCommentDateTime = function(ticks) {
// returns Unix timestamp in milliseconds
module.exports.getTimestamp = function() {
return Date.now();
-}
+};
-var testUrlMatch = function(text) {
+function testUrlMatch(text) {
var urlMatcher = new Autolinker.matchParser.MatchParser({
- urls: true,
- emails: false,
- twitter: false,
- phone: false,
- hashtag: false,
+ urls: true,
+ emails: false,
+ twitter: false,
+ phone: false,
+ hashtag: false
});
var result = [];
- var replaceFn = function(match) {
- var linkData = {};
- var matchText = match.getMatchedText();
+ function replaceFn(match) {
+ var linkData = {};
+ var matchText = match.getMatchedText();
- linkData.text = matchText;
- linkData.link = matchText.trim().indexOf("http") !== 0 ? "http://" + matchText : matchText;
+ linkData.text = matchText;
+ if (matchText.trim().indexOf('http') !== 0) {
+ linkData.link = 'http://' + matchText;
+ } else {
+ linkData.link = matchText;
+ }
- result.push(linkData);
+ result.push(linkData);
}
- urlMatcher.replace(text,replaceFn,this);
+ urlMatcher.replace(text, replaceFn, this);
return result;
}
module.exports.extractLinks = function(text) {
- var repRegex = new RegExp("<br>", "g");
- var matches = testUrlMatch(text.replace(repRegex, "\n"));
+ var repRegex = new RegExp('<br>', 'g');
+ var matches = testUrlMatch(text.replace(repRegex, '\n'));
- if (!matches.length) return { "links": null, "text": text };
+ if (!matches.length) {
+ return {links: null, text: text};
+ }
var links = [];
for (var i = 0; i < matches.length; i++) {
links.push(matches[i].link);
}
- return { "links": links, "text": text };
-}
+ return {links: links, text: text};
+};
module.exports.escapeRegExp = function(string) {
- return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
+ return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
+};
+
+function getYoutubeEmbed(link) {
+ var regex = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/;
+
+ var youtubeId = link.trim().match(regex)[1];
+
+ function onClick(e) {
+ var div = $(e.target).closest('.video-thumbnail__container')[0];
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('src',
+ 'https://www.youtube.com/embed/' +
+ div.id +
+ '?autoplay=1&autohide=1&border=0&wmode=opaque&enablejsapi=1');
+ iframe.setAttribute('width', '480px');
+ iframe.setAttribute('height', '360px');
+ iframe.setAttribute('type', 'text/html');
+ iframe.setAttribute('frameborder', '0');
+
+ div.parentNode.replaceChild(iframe, div);
+ }
+
+ function success(data) {
+ if (!data.items.length || !data.items[0].snippet) {
+ return;
+ }
+ var metadata = data.items[0].snippet;
+ $('.video-uploader.' + youtubeId).html(metadata.channelTitle);
+ $('.video-title.' + youtubeId).find('a').html(metadata.title);
+ $('.post-list-holder-by-time').scrollTop($('.post-list-holder-by-time')[0].scrollHeight);
+ $('.post-list-holder-by-time').perfectScrollbar('update');
+ }
+
+ if (config.GoogleDeveloperKey) {
+ $.ajax({
+ async: true,
+ url: 'https://www.googleapis.com/youtube/v3/videos',
+ type: 'GET',
+ data: {part: 'snippet', id: youtubeId, key:config.GoogleDeveloperKey},
+ success: success
+ });
+ }
+
+ return (
+ <div className='post-comment'>
+ <h4 className='video-type'>YouTube</h4>
+ <h4 className={'video-uploader ' + youtubeId}></h4>
+ <h4 className={'video-title ' + youtubeId}><a href={link}></a></h4>
+ <div className='video-div embed-responsive-item' id={youtubeId} onClick={onClick}>
+ <div className='embed-responsive embed-responsive-4by3 video-div__placeholder'>
+ <div id={youtubeId} className='video-thumbnail__container'>
+ <img className='video-thumbnail' src={'https://i.ytimg.com/vi/' + youtubeId + '/hqdefault.jpg'}/>
+ <div className='block'>
+ <span className='play-button'><span></span></span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
}
module.exports.getEmbed = function(link) {
-
var ytRegex = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/;
var match = link.trim().match(ytRegex);
- if (match && match[1].length==11){
- return getYoutubeEmbed(link);
+ if (match && match[1].length === 11) {
+ return getYoutubeEmbed(link);
}
// Generl embed feature turned off for now
- return;
+ return '';
+ // NEEDS REFACTORING WHEN TURNED BACK ON
+ /*
var id = parseInt((Math.random() * 1000000) + 1);
$.ajax({
type: 'GET',
- url: "https://query.yahooapis.com/v1/public/yql",
+ url: 'https://query.yahooapis.com/v1/public/yql',
data: {
- q: "select * from html where url=\""+link+"\" and xpath='html/head'",
- format: "json"
+ q: 'select * from html where url="' + link + "\" and xpath='html/head'",
+ format: 'json'
},
async: true
}).done(function(data) {
@@ -270,9 +350,9 @@ module.exports.getEmbed = function(link) {
var headerData = data.query.results.head;
- var description = ""
+ var description = ''
for(var i = 0; i < headerData.meta.length; i++) {
- if(headerData.meta[i].name && (headerData.meta[i].name === "description" || headerData.meta[i].name === "Description")){
+ if(headerData.meta[i].name && (headerData.meta[i].name === 'description' || headerData.meta[i].name === 'Description')){
description = headerData.meta[i].content;
break;
}
@@ -283,79 +363,20 @@ module.exports.getEmbed = function(link) {
})
return (
- <div className="post-comment">
- <div className={"web-embed-data"}>
- <p className={"embed-title " + id} />
- <p className={"embed-description " + id} />
- <p className={"embed-link " + id}>{link}</p>
- </div>
- </div>
- );
-}
-
-var getYoutubeEmbed = function(link) {
- var regex = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/;
-
- var youtubeId = link.trim().match(regex)[1];
-
- var onclick = function(e) {
- var div = $(e.target).closest('.video-thumbnail__container')[0];
- var iframe = document.createElement("iframe");
- iframe.setAttribute("src",
- "https://www.youtube.com/embed/" + div.id
- + "?autoplay=1&autohide=1&border=0&wmode=opaque&enablejsapi=1");
- iframe.setAttribute("width", "480px");
- iframe.setAttribute("height", "360px");
- iframe.setAttribute("type", "text/html");
- iframe.setAttribute("frameborder", "0");
-
- div.parentNode.replaceChild(iframe, div);
- };
-
- var success = function(data) {
- if(!data.items.length || !data.items[0].snippet) {
- return;
- }
- var metadata = data.items[0].snippet;
- $('.video-uploader.'+youtubeId).html(metadata.channelTitle);
- $('.video-title.'+youtubeId).find('a').html(metadata.title);
- $(".post-list-holder-by-time").scrollTop($(".post-list-holder-by-time")[0].scrollHeight);
- $(".post-list-holder-by-time").perfectScrollbar('update');
- };
-
- if(config.GoogleDeveloperKey) {
- $.ajax({
- async: true,
- url: "https://www.googleapis.com/youtube/v3/videos",
- type: 'GET',
- data: {part:"snippet", id:youtubeId, key:config.GoogleDeveloperKey},
- success: success
- });
- }
-
- return (
- <div className="post-comment">
- <h4 className="video-type">YouTube</h4>
- <h4 className={"video-uploader "+youtubeId}></h4>
- <h4 className={"video-title "+youtubeId}><a href={link}></a></h4>
- <div className="video-div embed-responsive-item" id={youtubeId} onClick={onclick}>
- <div className="embed-responsive embed-responsive-4by3 video-div__placeholder">
- <div id={youtubeId} className="video-thumbnail__container">
- <img className="video-thumbnail" src={"https://i.ytimg.com/vi/" + youtubeId + "/hqdefault.jpg"}/>
- <div className="block">
- <span className="play-button"><span></span></span>
- </div>
- </div>
- </div>
+ <div className='post-comment'>
+ <div className={'web-embed-data'}>
+ <p className={'embed-title ' + id} />
+ <p className={'embed-description ' + id} />
+ <p className={'embed-link ' + id}>{link}</p>
</div>
</div>
);
-
-}
+ */
+};
module.exports.areStatesEqual = function(state1, state2) {
return JSON.stringify(state1) === JSON.stringify(state2);
-}
+};
module.exports.replaceHtmlEntities = function(text) {
var tagsToReplace = {
@@ -363,12 +384,15 @@ module.exports.replaceHtmlEntities = function(text) {
'&lt;': '<',
'&gt;': '>'
};
+ var newtext = text;
for (var tag in tagsToReplace) {
- var regex = new RegExp(tag, "g");
- text = text.replace(regex, tagsToReplace[tag]);
+ if ({}.hasOwnProperty.call(tagsToReplace, tag)) {
+ var regex = new RegExp(tag, 'g');
+ newtext = newtext.replace(regex, tagsToReplace[tag]);
+ }
}
- return text;
-}
+ return newtext;
+};
module.exports.insertHtmlEntities = function(text) {
var tagsToReplace = {
@@ -376,12 +400,15 @@ module.exports.insertHtmlEntities = function(text) {
'<': '&lt;',
'>': '&gt;'
};
+ var newtext = text;
for (var tag in tagsToReplace) {
- var regex = new RegExp(tag, "g");
- text = text.replace(regex, tagsToReplace[tag]);
+ if ({}.hasOwnProperty.call(tagsToReplace, tag)) {
+ var regex = new RegExp(tag, 'g');
+ newtext = newtext.replace(regex, tagsToReplace[tag]);
+ }
}
- return text;
-}
+ return newtext;
+};
module.exports.searchForTerm = function(term) {
AppDispatcher.handleServerAction({
@@ -389,192 +416,192 @@ module.exports.searchForTerm = function(term) {
term: term,
do_search: true
});
-}
+};
-var oldExplicitMentionRegex = /(?:<mention>)([\s\S]*?)(?:<\/mention>)/g;
var puncStartRegex = /^((?![@#])\W)+/g;
var puncEndRegex = /(\W)+$/g;
module.exports.textToJsx = function(text, options) {
if (options && options['singleline']) {
- var repRegex = new RegExp("\n", "g");
- text = text.replace(repRegex, " ");
+ var repRegex = new RegExp('\n', 'g');
+ text = text.replace(repRegex, ' ');
}
- var searchTerm = ""
+ var searchTerm = ''
if (options && options['searchTerm']) {
searchTerm = options['searchTerm'].toLowerCase()
}
- var mentionClass = "mention-highlight";
+ var mentionClass = 'mention-highlight';
if (options && options['noMentionHighlight']) {
- mentionClass = "";
+ mentionClass = '';
}
var inner = [];
// Function specific regex
- var hashRegex = /^href="#[^"]+"|(#[A-Za-z]+[A-Za-z0-9_\-]*[A-Za-z0-9])$/g;
+ var hashRegex = /^href="#[^']+"|(#[A-Za-z]+[A-Za-z0-9_\-]*[A-Za-z0-9])$/g;
var implicitKeywords = UserStore.getCurrentMentionKeys();
- var lines = text.split("\n");
+ var lines = text.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
- var words = line.split(" ");
- var highlightSearchClass = "";
+ var words = line.split(' ');
+ var highlightSearchClass = '';
for (var z = 0; z < words.length; z++) {
var word = words[z];
var trimWord = word.replace(puncStartRegex, '').replace(puncEndRegex, '').trim();
var mentionRegex = /^(?:@)([a-z0-9_]+)$/gi; // looks loop invariant but a weird JS bug needs it to be redefined here
var explicitMention = mentionRegex.exec(trimWord);
- if ((trimWord.toLowerCase().indexOf(searchTerm) > -1 || word.toLowerCase().indexOf(searchTerm) > -1) && searchTerm != "") {
+ if ((trimWord.toLowerCase().indexOf(searchTerm) > -1 || word.toLowerCase().indexOf(searchTerm) > -1) && searchTerm != '') {
- highlightSearchClass = " search-highlight";
+ highlightSearchClass = ' search-highlight';
}
if (explicitMention &&
(UserStore.getProfileByUsername(explicitMention[1]) ||
- Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1))
- {
- var name = explicitMention[1];
- // do both a non-case sensitive and case senstive check
- var mClass = implicitKeywords.indexOf('@'+name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@'+name) !== -1 ? mentionClass : "";
+ Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1))
+ {
+ var name = explicitMention[1];
+ // do both a non-case sensitive and case senstive check
+ var mClass = implicitKeywords.indexOf('@'+name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@'+name) !== -1 ? mentionClass : '';
- var suffix = word.match(puncEndRegex);
- var prefix = word.match(puncStartRegex);
+ var suffix = word.match(puncEndRegex);
+ var prefix = word.match(puncStartRegex);
- if (searchTerm === name) {
- highlightSearchClass = " search-highlight";
- }
+ if (searchTerm === name) {
+ highlightSearchClass = ' search-highlight';
+ }
- inner.push(<span key={name+i+z+"_span"}>{prefix}<a className={mClass + highlightSearchClass + " mention-link"} key={name+i+z+"_link"} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>@{name}</a>{suffix} </span>);
- } else if (testUrlMatch(word).length) {
- var match = testUrlMatch(word)[0];
- var link = match.link;
+ inner.push(<span key={name+i+z+'_span'}>{prefix}<a className={mClass + highlightSearchClass + ' mention-link'} key={name+i+z+'_link'} href='#' onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>@{name}</a>{suffix} </span>);
+ } else if (testUrlMatch(word).length) {
+ var match = testUrlMatch(word)[0];
+ var link = match.link;
- var prefix = word.substring(0,word.indexOf(match.text));
- var suffix = word.substring(word.indexOf(match.text)+match.text.length);
+ var prefix = word.substring(0,word.indexOf(match.text));
+ var suffix = word.substring(word.indexOf(match.text)+match.text.length);
- inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_link"} className={"theme" + highlightSearchClass} target="_blank" href={link}>{match.text}</a>{suffix} </span>);
+ inner.push(<span key={word+i+z+'_span'}>{prefix}<a key={word+i+z+'_link'} className={'theme' + highlightSearchClass} target='_blank' href={link}>{match.text}</a>{suffix} </span>);
- } else if (trimWord.match(hashRegex)) {
- var suffix = word.match(puncEndRegex);
- var prefix = word.match(puncStartRegex);
- var mClass = implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1 ? mentionClass : "";
+ } else if (trimWord.match(hashRegex)) {
+ var suffix = word.match(puncEndRegex);
+ var prefix = word.match(puncStartRegex);
+ var mClass = implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1 ? mentionClass : '';
- if (searchTerm === trimWord.substring(1).toLowerCase() || searchTerm === trimWord.toLowerCase()) {
- highlightSearchClass = " search-highlight";
- }
+ if (searchTerm === trimWord.substring(1).toLowerCase() || searchTerm === trimWord.toLowerCase()) {
+ highlightSearchClass = ' search-highlight';
+ }
- inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_hash"} className={"theme " + mClass + highlightSearchClass} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a>{suffix} </span>);
+ inner.push(<span key={word+i+z+'_span'}>{prefix}<a key={word+i+z+'_hash'} className={'theme ' + mClass + highlightSearchClass} href='#' onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a>{suffix} </span>);
- } else if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
- var suffix = word.match(puncEndRegex);
- var prefix = word.match(puncStartRegex);
+ } else if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
+ var suffix = word.match(puncEndRegex);
+ var prefix = word.match(puncStartRegex);
- if (trimWord.charAt(0) === '@') {
- if (searchTerm === trimWord.substring(1).toLowerCase()) {
- highlightSearchClass = " search-highlight";
+ if (trimWord.charAt(0) === '@') {
+ if (searchTerm === trimWord.substring(1).toLowerCase()) {
+ highlightSearchClass = ' search-highlight';
+ }
+ inner.push(<span key={word+i+z+'_span'} key={name+i+z+'_span'}>{prefix}<a className={mentionClass + highlightSearchClass} key={name+i+z+'_link'} href='#'>{trimWord}</a>{suffix} </span>);
+ } else {
+ inner.push(<span key={word+i+z+'_span'}>{prefix}<span className={mentionClass + highlightSearchClass}>{module.exports.replaceHtmlEntities(trimWord)}</span>{suffix} </span>);
}
- inner.push(<span key={word+i+z+"_span"} key={name+i+z+"_span"}>{prefix}<a className={mentionClass + highlightSearchClass} key={name+i+z+"_link"} href="#">{trimWord}</a>{suffix} </span>);
+
+ } else if (word === '') {
+ // if word is empty dont include a span
} else {
- inner.push(<span key={word+i+z+"_span"}>{prefix}<span className={mentionClass + highlightSearchClass}>{module.exports.replaceHtmlEntities(trimWord)}</span>{suffix} </span>);
+ inner.push(<span key={word+i+z+'_span'}><span className={highlightSearchClass}>{module.exports.replaceHtmlEntities(word)}</span> </span>);
}
-
- } else if (word === "") {
- // if word is empty dont include a span
- } else {
- inner.push(<span key={word+i+z+"_span"}><span className={highlightSearchClass}>{module.exports.replaceHtmlEntities(word)}</span> </span>);
- }
- highlightSearchClass = "";
+ highlightSearchClass = '';
}
if (i != lines.length-1)
- inner.push(<br key={"br_"+i+z}/>);
+ inner.push(<br key={'br_'+i+z}/>);
}
return inner;
}
-module.exports.getFileType = function(ext) {
- ext = ext.toLowerCase();
+module.exports.getFileType = function(extin) {
+ var ext = extin.toLowerCase();
if (Constants.IMAGE_TYPES.indexOf(ext) > -1) {
- return "image";
+ return 'image';
}
if (Constants.AUDIO_TYPES.indexOf(ext) > -1) {
- return "audio";
+ return 'audio';
}
if (Constants.VIDEO_TYPES.indexOf(ext) > -1) {
- return "video";
+ return 'video';
}
if (Constants.SPREADSHEET_TYPES.indexOf(ext) > -1) {
- return "spreadsheet";
+ return 'spreadsheet';
}
if (Constants.CODE_TYPES.indexOf(ext) > -1) {
- return "code";
+ return 'code';
}
if (Constants.WORD_TYPES.indexOf(ext) > -1) {
- return "word";
+ return 'word';
}
if (Constants.EXCEL_TYPES.indexOf(ext) > -1) {
- return "excel";
+ return 'excel';
}
if (Constants.PDF_TYPES.indexOf(ext) > -1) {
- return "pdf";
+ return 'pdf';
}
if (Constants.PATCH_TYPES.indexOf(ext) > -1) {
- return "patch";
+ return 'patch';
}
- return "other";
+ return 'other';
};
-module.exports.getPreviewImagePathForFileType = function(fileType) {
- fileType = fileType.toLowerCase();
+module.exports.getPreviewImagePathForFileType = function(fileTypeIn) {
+ var fileType = fileTypeIn.toLowerCase();
var icon;
if (fileType in Constants.ICON_FROM_TYPE) {
icon = Constants.ICON_FROM_TYPE[fileType];
} else {
- icon = Constants.ICON_FROM_TYPE["other"];
+ icon = Constants.ICON_FROM_TYPE.other;
}
- return "/static/images/icons/" + icon + ".png";
+ return '/static/images/icons/' + icon + '.png';
};
-module.exports.getIconClassName = function(fileType) {
- fileType = fileType.toLowerCase();
+module.exports.getIconClassName = function(fileTypeIn) {
+ var fileType = fileTypeIn.toLowerCase();
- if (fileType in Constants.ICON_FROM_TYPE)
+ if (fileType in Constants.ICON_FROM_TYPE) {
return Constants.ICON_FROM_TYPE[fileType];
+ }
- return "glyphicon-file";
-}
+ return 'glyphicon-file';
+};
module.exports.splitFileLocation = function(fileLocation) {
var fileSplit = fileLocation.split('.');
- var ext = "";
+ var ext = '';
if (fileSplit.length > 1) {
ext = fileSplit[fileSplit.length - 1];
fileSplit.splice(fileSplit.length - 1, 1);
}
var filePath = fileSplit.join('.');
- var filename = filePath.split('/')[filePath.split('/').length-1];
+ var filename = filePath.split('/')[filePath.split('/').length - 1];
- return {'ext': ext, 'name': filename, 'path': filePath};
-}
+ return {ext: ext, name: filename, path: filePath};
+};
// Asynchronously gets the size of a file by requesting its headers. If successful, it calls the
// provided callback with the file size in bytes as the argument.
@@ -582,10 +609,10 @@ module.exports.getFileSize = function(url, callback) {
var request = new XMLHttpRequest();
request.open('HEAD', url, true);
- request.onreadystatechange = function() {
- if (request.readyState == 4 && request.status == 200) {
+ request.onreadystatechange = function onReadyStateChange() {
+ if (request.readyState === 4 && request.status === 200) {
if (callback) {
- callback(parseInt(request.getResponseHeader("content-length")));
+ callback(parseInt(request.getResponseHeader('content-length'), 10));
}
}
};
@@ -594,155 +621,154 @@ module.exports.getFileSize = function(url, callback) {
};
module.exports.toTitleCase = function(str) {
- return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
-}
+ function doTitleCase(txt) {
+ return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
+ }
+ return str.replace(/\w\S*/g, doTitleCase);
+};
module.exports.changeCss = function(className, classValue) {
// we need invisible container to store additional css definitions
var cssMainContainer = $('#css-modifier-container');
- if (cssMainContainer.length == 0) {
- var cssMainContainer = $('<div id="css-modifier-container"></div>');
- cssMainContainer.hide();
- cssMainContainer.appendTo($('body'));
+ if (cssMainContainer.length === 0) {
+ var cssMainContainer2 = $('<div id="css-modifier-container"></div>');
+ cssMainContainer2.hide();
+ cssMainContainer2.appendTo($('body'));
}
// and we need one div for each class
- classContainer = cssMainContainer.find('div[data-class="' + className + '"]');
- if (classContainer.length == 0) {
+ var classContainer = cssMainContainer.find('div[data-class="' + className + '"]');
+ if (classContainer.length === 0) {
classContainer = $('<div data-class="' + className + '"></div>');
classContainer.appendTo(cssMainContainer);
}
// append additional style
classContainer.html('<style>' + className + ' {' + classValue + '}</style>');
-}
+};
-module.exports.rgb2hex = function(rgb) {
- if (/^#[0-9A-F]{6}$/i.test(rgb)) return rgb;
+module.exports.rgb2hex = function(rgbIn) {
+ if (/^#[0-9A-F]{6}$/i.test(rgbIn)) {
+ return rgbIn;
+ }
- rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
+ var rgb = rgbIn.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
function hex(x) {
- return ("0" + parseInt(x).toString(16)).slice(-2);
+ return ('0' + parseInt(x, 10).toString(16)).slice(-2);
}
- return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
-}
+ return '#' + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
+};
module.exports.placeCaretAtEnd = function(el) {
el.focus();
- if (typeof window.getSelection != "undefined"
- && typeof document.createRange != "undefined") {
+ if (typeof window.getSelection != 'undefined' && typeof document.createRange != 'undefined') {
var range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
- } else if (typeof document.body.createTextRange != "undefined") {
+ } else if (typeof document.body.createTextRange != 'undefined') {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(false);
textRange.select();
}
-}
+};
module.exports.getCaretPosition = function(el) {
if (el.selectionStart) {
- return el.selectionStart;
+ return el.selectionStart;
} else if (document.selection) {
- el.focus();
+ el.focus();
- var r = document.selection.createRange();
- if (r == null) {
- return 0;
- }
+ var r = document.selection.createRange();
+ if (r == null) {
+ return 0;
+ }
- var re = el.createTextRange(),
- rc = re.duplicate();
- re.moveToBookmark(r.getBookmark());
- rc.setEndPoint('EndToStart', re);
+ var re = el.createTextRange();
+ var rc = re.duplicate();
+ re.moveToBookmark(r.getBookmark());
+ rc.setEndPoint('EndToStart', re);
- return rc.text.length;
+ return rc.text.length;
}
return 0;
-}
+};
module.exports.setSelectionRange = function(input, selectionStart, selectionEnd) {
- if (input.setSelectionRange) {
- input.focus();
- input.setSelectionRange(selectionStart, selectionEnd);
- }
- else if (input.createTextRange) {
- var range = input.createTextRange();
- range.collapse(true);
- range.moveEnd('character', selectionEnd);
- range.moveStart('character', selectionStart);
- range.select();
- }
-}
+ if (input.setSelectionRange) {
+ input.focus();
+ input.setSelectionRange(selectionStart, selectionEnd);
+ } else if (input.createTextRange) {
+ var range = input.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', selectionEnd);
+ range.moveStart('character', selectionStart);
+ range.select();
+ }
+};
-module.exports.setCaretPosition = function (input, pos) {
- module.exports.setSelectionRange(input, pos, pos);
-}
+module.exports.setCaretPosition = function(input, pos) {
+ module.exports.setSelectionRange(input, pos, pos);
+};
module.exports.getSelectedText = function(input) {
- var selectedText;
- if (document.selection != undefined) {
- input.focus();
- var sel = document.selection.createRange();
- selectedText = sel.text;
- } else if (input.selectionStart != undefined) {
- var startPos = input.selectionStart;
- var endPos = input.selectionEnd;
- selectedText = input.value.substring(startPos, endPos)
- }
-
- return selectedText;
-}
-
-module.exports.isValidUsername = function (name) {
-
- var error = ""
- if (!name) {
- error = "This field is required";
+ var selectedText;
+ if (typeof document.selection !== 'undefined') {
+ input.focus();
+ var sel = document.selection.createRange();
+ selectedText = sel.text;
+ } else if (typeof input.selectionStart !== 'undefined') {
+ var startPos = input.selectionStart;
+ var endPos = input.selectionEnd;
+ selectedText = input.value.substring(startPos, endPos);
}
- else if (name.length < 3 || name.length > 15)
- {
- error = "Must be between 3 and 15 characters";
- }
+ return selectedText;
+};
- else if (!/^[a-z0-9\.\-\_]+$/.test(name))
- {
+module.exports.isValidUsername = function(name) {
+ var error = '';
+ if (!name) {
+ error = 'This field is required';
+ } else if (name.length < 3 || name.length > 15) {
+ error = 'Must be between 3 and 15 characters';
+ } else if (!(/^[a-z0-9\.\-\_]+$/).test(name)) {
error = "Must contain only lowercase letters, numbers, and the symbols '.', '-', and '_'.";
- }
-
- else if (!/[a-z]/.test(name.charAt(0)))
- {
- error = "First character must be a letter.";
- }
-
- else
- {
+ } else if (!(/[a-z]/).test(name.charAt(0))) {
+ error = 'First character must be a letter.';
+ } else {
var lowerName = name.toLowerCase().trim();
- for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++)
- {
- if (lowerName === Constants.RESERVED_USERNAMES[i])
- {
- error = "Cannot use a reserved word as a username.";
+ for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++) {
+ if (lowerName === Constants.RESERVED_USERNAMES[i]) {
+ error = 'Cannot use a reserved word as a username.';
break;
}
}
}
return error;
+};
+
+function updateTabTitle(name) {
+ document.title = name + ' ' + document.title.substring(document.title.lastIndexOf('-'));
+}
+module.exports.updateTabTitle = updateTabTitle;
+
+function updateAddressBar(channelName) {
+ var teamURL = window.location.href.split('/channels')[0];
+ history.replaceState('data', '', teamURL + '/channels/' + channelName);
}
+module.exports.updateAddressBar = updateAddressBar;
function switchChannel(channel, teammateName) {
AppDispatcher.handleViewAction({
- type: ActionTypes.CLICK_CHANNEL,
- name: channel.name,
- id: channel.id
+ type: ActionTypes.CLICK_CHANNEL,
+ name: channel.name,
+ id: channel.id
});
updateAddressBar(channel.name);
@@ -766,99 +792,104 @@ function switchChannel(channel, teammateName) {
}
module.exports.switchChannel = switchChannel;
-function updateTabTitle(name) {
- document.title = name + ' ' + document.title.substring(document.title.lastIndexOf('-'));
-}
-module.exports.updateTabTitle = updateTabTitle;
-
-function updateAddressBar(channelName) {
- var teamURL = window.location.href.split('/channels')[0];
- history.replaceState('data', '', teamURL + '/channels/' + channelName);
-}
-module.exports.updateAddressBar = updateAddressBar;
-
module.exports.isMobile = function() {
- return screen.width <= 768;
-}
+ return screen.width <= 768;
+};
module.exports.isComment = function(post) {
if ('root_id' in post) {
- return post.root_id != "";
+ return post.root_id !== '';
}
return false;
-}
+};
-module.exports.getDirectTeammate = function(channel_id) {
- var userIds = ChannelStore.get(channel_id).name.split('__');
+module.exports.getDirectTeammate = function(channelId) {
+ var userIds = ChannelStore.get(channelId).name.split('__');
var curUserId = UserStore.getCurrentId();
var teammate = {};
- if(userIds.length != 2 || userIds.indexOf(curUserId) === -1) {
+ if (userIds.length !== 2 || userIds.indexOf(curUserId) === -1) {
return teammate;
}
for (var idx in userIds) {
- if(userIds[idx] !== curUserId) {
+ if (userIds[idx] !== curUserId) {
teammate = UserStore.getProfile(userIds[idx]);
break;
}
}
return teammate;
-}
+};
Image.prototype.load = function(url, progressCallback) {
- var thisImg = this;
+ var self = this;
var xmlHTTP = new XMLHttpRequest();
xmlHTTP.open('GET', url, true);
xmlHTTP.responseType = 'arraybuffer';
- xmlHTTP.onload = function(e) {
- var h = xmlHTTP.getAllResponseHeaders(),
- m = h.match( /^Content-Type\:\s*(.*?)$/mi ),
- mimeType = m[ 1 ] || 'image/png';
+ xmlHTTP.onload = function onLoad() {
+ var h = xmlHTTP.getAllResponseHeaders();
+ var m = h.match(/^Content-Type\:\s*(.*?)$/mi);
+ var mimeType = m[1] || 'image/png';
- var blob = new Blob([this.response], { type: mimeType });
- thisImg.src = window.URL.createObjectURL(blob);
+ var blob = new Blob([this.response], {type: mimeType});
+ self.src = window.URL.createObjectURL(blob);
};
- xmlHTTP.onprogress = function(e) {
- parseInt(thisImg.completedPercentage = (e.loaded / e.total) * 100);
- if (progressCallback) progressCallback();
+ xmlHTTP.onprogress = function onprogress(e) {
+ parseInt(self.completedPercentage = (e.loaded / e.total) * 100, 10);
+ if (progressCallback) {
+ progressCallback();
+ }
};
- xmlHTTP.onloadstart = function() {
- thisImg.completedPercentage = 0;
+ xmlHTTP.onloadstart = function onloadstart() {
+ self.completedPercentage = 0;
};
xmlHTTP.send();
};
Image.prototype.completedPercentage = 0;
-module.exports.changeColor =function(col, amt) {
-
+module.exports.changeColor = function(colourIn, amt) {
var usePound = false;
+ var col = colourIn;
- if (col[0] == "#") {
+ if (col[0] === '#') {
col = col.slice(1);
usePound = true;
}
- var num = parseInt(col,16);
+ var num = parseInt(col, 16);
var r = (num >> 16) + amt;
- if (r > 255) r = 255;
- else if (r < 0) r = 0;
+ if (r > 255) {
+ r = 255;
+ } else if (r < 0) {
+ r = 0;
+ }
var b = ((num >> 8) & 0x00FF) + amt;
- if (b > 255) b = 255;
- else if (b < 0) b = 0;
+ if (b > 255) {
+ b = 255;
+ } else if (b < 0) {
+ b = 0;
+ }
var g = (num & 0x0000FF) + amt;
- if (g > 255) g = 255;
- else if (g < 0) g = 0;
+ if (g > 255) {
+ g = 255;
+ } else if (g < 0) {
+ g = 0;
+ }
+
+ var pound = '#';
+ if (!usePound) {
+ pound = '';
+ }
- return (usePound?"#":"") + String("000000" + (g | (b << 8) | (r << 16)).toString(16)).slice(-6);
+ return pound + String('000000' + (g | (b << 8) | (r << 16)).toString(16)).slice(-6);
};
module.exports.changeOpacity = function(oldColor, opacity) {
@@ -877,53 +908,55 @@ module.exports.changeOpacity = function(oldColor, opacity) {
module.exports.getFullName = function(user) {
if (user.first_name && user.last_name) {
- return user.first_name + " " + user.last_name;
+ return user.first_name + ' ' + user.last_name;
} else if (user.first_name) {
return user.first_name;
} else if (user.last_name) {
return user.last_name;
- } else {
- return "";
}
+
+ return '';
};
module.exports.getDisplayName = function(user) {
if (user.nickname && user.nickname.trim().length > 0) {
return user.nickname;
- } else {
- var fullName = module.exports.getFullName(user);
+ }
+ var fullName = module.exports.getFullName(user);
- if (fullName) {
- return fullName;
- } else {
- return user.username;
- }
+ if (fullName) {
+ return fullName;
}
+
+ return user.username;
};
//IE10 does not set window.location.origin automatically so this must be called instead when using it
module.exports.getWindowLocationOrigin = function() {
var windowLocationOrigin = window.location.origin;
if (!windowLocationOrigin) {
- windowLocationOrigin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
+ windowLocationOrigin = window.location.protocol + '//' + window.location.hostname;
+ if (window.location.port) {
+ windowLocationOrigin += ':' + window.location.port;
+ }
}
return windowLocationOrigin;
-}
+};
-// Converts a file size in bytes into a human-readable string of the form "123MB".
+// Converts a file size in bytes into a human-readable string of the form '123MB'.
module.exports.fileSizeToString = function(bytes) {
// it's unlikely that we'll have files bigger than this
if (bytes > 1024 * 1024 * 1024 * 1024) {
- return Math.floor(bytes / (1024 * 1024 * 1024 * 1024)) + "TB";
+ return Math.floor(bytes / (1024 * 1024 * 1024 * 1024)) + 'TB';
} else if (bytes > 1024 * 1024 * 1024) {
- return Math.floor(bytes / (1024 * 1024 * 1024)) + "GB";
+ return Math.floor(bytes / (1024 * 1024 * 1024)) + 'GB';
} else if (bytes > 1024 * 1024) {
- return Math.floor(bytes / (1024 * 1024)) + "MB";
+ return Math.floor(bytes / (1024 * 1024)) + 'MB';
} else if (bytes > 1024) {
- return Math.floor(bytes / 1024) + "KB";
- } else {
- return bytes + "B";
+ return Math.floor(bytes / 1024) + 'KB';
}
+
+ return bytes + 'B';
};
// Converts a filename (like those attached to Post objects) to a url that can be used to retrieve attachments from the server.
@@ -931,10 +964,10 @@ module.exports.getFileUrl = function(filename) {
var url = filename;
// This is a temporary patch to fix issue with old files using absolute paths
- if (url.indexOf("/api/v1/files/get") != -1) {
- url = filename.split("/api/v1/files/get")[1];
+ if (url.indexOf('/api/v1/files/get') !== -1) {
+ url = filename.split('/api/v1/files/get')[1];
}
- url = module.exports.getWindowLocationOrigin() + "/api/v1/files/get" + url;
+ url = module.exports.getWindowLocationOrigin() + '/api/v1/files/get' + url;
return url;
};
@@ -950,7 +983,7 @@ module.exports.generateId = function() {
// implementation taken from http://stackoverflow.com/a/2117523
var id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
- id = id.replace(/[xy]/g, function(c) {
+ id = id.replace(/[xy]/g, function replaceRandom(c) {
var r = Math.floor(Math.random() * 16);
var v;
@@ -982,3 +1015,12 @@ module.exports.getUserIdFromChannelName = function(channel) {
return otherUserId;
};
+
+module.exports.importSlack = function(file, success, error) {
+ var formData = new FormData();
+ formData.append('file', file, file.name);
+ formData.append('filesize', file.size);
+ formData.append('importFrom', 'slack');
+
+ client.importSlack(formData, success, error);
+};
diff --git a/web/templates/head.html b/web/templates/head.html
index dd5e9f46e..5448b09ed 100644
--- a/web/templates/head.html
+++ b/web/templates/head.html
@@ -56,11 +56,11 @@
<script>
if (config.LogglyWriteKey != null && config.LogglyWriteKey !== "") {
- var _LTracker = _LTracker || [];
- window._LTracker = _LTracker;
- _LTracker.push({'logglyKey': config.LogglyWriteKey, 'sendConsoleErrors' : config.LogglyConsoleErrors });
+ var LTracker = LTracker || [];
+ window.LTracker = LTracker;
+ LTracker.push({'logglyKey': config.LogglyWriteKey, 'sendConsoleErrors' : config.LogglyConsoleErrors });
} else {
- window._LTracker = [];
+ window.LTracker = [];
console.warn("config.js missing LogglyWriteKey, Loggly analytics is not reporting");
}
</script>