From 009df5bad5f55c7e4c698f9dab8420d00a7ae71e Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Mon, 22 Jun 2015 08:35:24 -0400 Subject: [PATCH 01/84] fixes mm-1318 only allow 5 files to be uploaded at a time --- web/react/components/create_comment.jsx | 28 ++++++++++++-------- web/react/components/create_post.jsx | 34 +++++++++++++++---------- web/react/components/file_upload.jsx | 18 ++++++------- web/react/utils/constants.jsx | 1 + 4 files changed, 49 insertions(+), 32 deletions(-) diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 9bcbad0796..bb70271159 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -112,13 +112,28 @@ module.exports = React.createClass({ return { messageText: '', uploadsInProgress: 0, previews: [], submitting: false }; }, setUploads: function(val) { - var num = this.state.uploadsInProgress + val; - this.setState({uploadsInProgress: num}); + var oldInProgress = this.state.uploadsInProgress + var newInProgress = oldInProgress + val; + + if (newInProgress + this.state.previews.length > Constants.MAX_UPLOAD_FILES) { + newInProgress = Constants.MAX_UPLOAD_FILES - this.state.previews.length; + this.setState({limit_error: "Uploads limited to " + Constants.MAX_UPLOAD_FILES + " files maximum. Please use additional comments for more files."}); + } else { + this.setState({limit_error: null}); + } + + var numToUpload = newInProgress - oldInProgress; + if (numToUpload <= 0) return 0; + + this.setState({uploadsInProgress: newInProgress}); + + return numToUpload; }, render: function() { var server_error = this.state.server_error ?
: null; var post_error = this.state.post_error ? : null; + var limit_error = this.state.limit_error ?
: null; var preview =
; if (this.state.previews.length > 0 || this.state.uploadsInProgress > 0) { @@ -129,13 +144,6 @@ module.exports = React.createClass({ uploadsInProgress={this.state.uploadsInProgress} /> ); } - var limit_previews = "" - if (this.state.previews.length > 5) { - limit_previews =
- } - if (this.state.previews.length > 20) { - limit_previews =
- } return (
@@ -159,7 +167,7 @@ module.exports = React.createClass({ { post_error } { server_error } - { limit_previews } + { limit_error }
{ preview } diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index a534e495d9..e7cd661fb1 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -51,7 +51,7 @@ module.exports = React.createClass({ false, function(data) { PostStore.storeDraft(data.channel_id, user_id, null); - this.setState({ messageText: '', submitting: false, post_error: null, previews: [], server_error: null }); + this.setState({ messageText: '', submitting: false, post_error: null, previews: [], server_error: null, limit_error: null }); if (data.goto_location.length > 0) { window.location.href = data.goto_location; @@ -71,7 +71,7 @@ module.exports = React.createClass({ client.createPost(post, ChannelStore.getCurrent(), function(data) { PostStore.storeDraft(data.channel_id, data.user_id, null); - this.setState({ messageText: '', submitting: false, post_error: null, previews: [], server_error: null }); + this.setState({ messageText: '', submitting: false, post_error: null, previews: [], server_error: null, limit_error: null }); this.resizePostHolder(); AsyncClient.getPosts(true); @@ -207,21 +207,36 @@ module.exports = React.createClass({ return { channel_id: ChannelStore.getCurrentId(), messageText: messageText, uploadsInProgress: 0, previews: previews, submitting: false, initialText: messageText }; }, setUploads: function(val) { - var num = this.state.uploadsInProgress + val; + var oldInProgress = this.state.uploadsInProgress + var newInProgress = oldInProgress + val; + + if (newInProgress + this.state.previews.length > Constants.MAX_UPLOAD_FILES) { + newInProgress = Constants.MAX_UPLOAD_FILES - this.state.previews.length; + this.setState({limit_error: "Uploads limited to " + Constants.MAX_UPLOAD_FILES + " files maximum. Please use additional posts for more files."}); + } else { + this.setState({limit_error: null}); + } + + var numToUpload = newInProgress - oldInProgress; + if (numToUpload <= 0) return 0; + var draft = PostStore.getCurrentDraft(); if (!draft) { draft = {} draft['message'] = ''; draft['previews'] = []; } - draft['uploadsInProgress'] = num; + draft['uploadsInProgress'] = newInProgress; PostStore.storeCurrentDraft(draft); - this.setState({uploadsInProgress: num}); + this.setState({uploadsInProgress: newInProgress}); + + return numToUpload; }, render: function() { var server_error = this.state.server_error ?
: null; var post_error = this.state.post_error ? : null; + var limit_error = this.state.limit_error ?
: null; var preview =
; if (this.state.previews.length > 0 || this.state.uploadsInProgress > 0) { @@ -232,13 +247,6 @@ module.exports = React.createClass({ uploadsInProgress={this.state.uploadsInProgress} /> ); } - var limit_previews = "" - if (this.state.previews.length > 5) { - limit_previews =
- } - if (this.state.previews.length > 20) { - limit_previews =
- } return ( @@ -260,7 +268,7 @@ module.exports = React.createClass({
{ post_error } { server_error } - { limit_previews } + { limit_error } { preview }
diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index c03a61c63c..f2429f17ea 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -12,18 +12,18 @@ module.exports = React.createClass({ this.props.onUploadError(null); - //This looks redundant, but must be done this way due to - //setState being an asynchronous call + // This looks redundant, but must be done this way due to + // setState being an asynchronous call var numFiles = 0; - for(var i = 0; i < files.length && i <= 20 ; i++) { + for(var i = 0; i < files.length && i < Constants.MAX_UPLOAD_FILES; i++) { if (files[i].size <= Constants.MAX_FILE_SIZE) { numFiles++; } } - this.props.setUploads(numFiles); + var numToUpload = this.props.setUploads(numFiles); - for (var i = 0; i < files.length && i <= 20; i++) { + for (var i = 0; i < files.length && i < numToUpload; i++) { if (files[i].size > Constants.MAX_FILE_SIZE) { this.props.onUploadError("Files must be no more than " + Constants.MAX_FILE_SIZE/1000000 + " MB"); continue; @@ -70,8 +70,8 @@ module.exports = React.createClass({ self.props.onUploadError(null); - //This looks redundant, but must be done this way due to - //setState being an asynchronous call + // This looks redundant, but must be done this way due to + // setState being an asynchronous call var items = e.clipboardData.items; var numItems = 0; if (items) { @@ -87,9 +87,9 @@ module.exports = React.createClass({ } } - self.props.setUploads(numItems); + var numToUpload = self.props.setUploads(numItems); - for (var i = 0; i < items.length; i++) { + for (var i = 0; i < items.length && i < numToUpload; i++) { if (items[i].type.indexOf("image") !== -1) { var file = items[i].getAsFile(); diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index deb07409b2..6d129106b1 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -45,6 +45,7 @@ module.exports = { PATCH_TYPES: ['patch'], ICON_FROM_TYPE: {'audio': 'audio', 'video': 'video', 'spreadsheet': 'ppt', 'pdf': 'pdf', 'code': 'code' , 'word': 'word' , 'excel': 'excel' , 'patch': 'patch', 'other': 'generic'}, MAX_DISPLAY_FILES: 5, + MAX_UPLOAD_FILES: 5, MAX_FILE_SIZE: 50000000, // 50 MB DEFAULT_CHANNEL: 'town-square', POST_CHUNK_SIZE: 60, From 94390236fd75c2b1f70519bc2e44f70e00b8a81b Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Mon, 22 Jun 2015 12:25:46 -0700 Subject: [PATCH 02/84] Unescaped escape characters in the subject line of emails so that all characters appear properly --- utils/mail.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/mail.go b/utils/mail.go index 2fb7f801d5..3cd37ffef8 100644 --- a/utils/mail.go +++ b/utils/mail.go @@ -11,6 +11,7 @@ import ( "net" "net/mail" "net/smtp" + "html" ) func CheckMailSettings() *model.AppError { @@ -84,7 +85,7 @@ func SendMail(to, subject, body string) *model.AppError { headers := make(map[string]string) headers["From"] = fromMail.String() headers["To"] = toMail.String() - headers["Subject"] = subject + headers["Subject"] = html.UnescapeString(subject) headers["MIME-version"] = "1.0" headers["Content-Type"] = "text/html" From dea39c7b64fa1cb21e955561bd3f9086f1c4cfda Mon Sep 17 00:00:00 2001 From: Haiko Schol Date: Wed, 24 Jun 2015 14:21:27 +0200 Subject: [PATCH 03/84] Fix typo in function name --- api/web_socket_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/web_socket_test.go b/api/web_socket_test.go index c7b612cde9..15bc3baeb9 100644 --- a/api/web_socket_test.go +++ b/api/web_socket_test.go @@ -119,7 +119,7 @@ func TestSocket(t *testing.T) { } -func TestZZWebScoketTearDown(t *testing.T) { +func TestZZWebSocketTearDown(t *testing.T) { // *IMPORTANT* - Kind of hacky // This should be the last function in any test file // that calls Setup() From 34d688ca72d8ef13b83987cc3b3d9b93ab989d9f Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Wed, 24 Jun 2015 16:29:45 +0200 Subject: [PATCH 04/84] SQL store: use authenticated encryption Data were encrypted using AES-CFB, with a properly randomized IV, but without any authenticators. This allows the data to be tampered with, without being noticed by the application. This diff slightly changes the encryption/decryption functions in sql_store.go to add a HMAC-SHA256 authenticator to encrypted messages. Two keys are derived from AtRestEncryptKey: the first half of SHA512(AtRestEncryptKey) for the block cipher and the second half for the MAC. This can be changed to a KDF if needed. The decryption function also checks that base64 decoding actually worked, and that the ciphertext is long enough to include the IV and the MAC. Unfortunately, it breaks backward compatibility. But if such a change has to be made, it has to be made early. --- store/sql_store.go | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/store/sql_store.go b/store/sql_store.go index a2deea6bab..bef8b48670 100644 --- a/store/sql_store.go +++ b/store/sql_store.go @@ -7,6 +7,9 @@ import ( l4g "code.google.com/p/log4go" "crypto/aes" "crypto/cipher" + "crypto/hmac" + "crypto/sha256" + "crypto/sha512" crand "crypto/rand" dbsql "database/sql" "encoding/base64" @@ -327,20 +330,26 @@ func encrypt(key []byte, text string) (string, error) { } plaintext := []byte(text) + skey := sha512.Sum512(key) + ekey, akey := skey[:32], skey[32:] - block, err := aes.NewCipher(key) + block, err := aes.NewCipher(ekey) if err != nil { return "", err } - ciphertext := make([]byte, aes.BlockSize+len(plaintext)) + macfn := hmac.New(sha256.New, akey) + ciphertext := make([]byte, aes.BlockSize+macfn.Size()+len(plaintext)) iv := ciphertext[:aes.BlockSize] if _, err := io.ReadFull(crand.Reader, iv); err != nil { return "", err } stream := cipher.NewCFBEncrypter(block, iv) - stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) + stream.XORKeyStream(ciphertext[aes.BlockSize+macfn.Size():], plaintext) + macfn.Write(ciphertext[aes.BlockSize+macfn.Size():]) + mac := macfn.Sum(nil) + copy(ciphertext[aes.BlockSize:aes.BlockSize+macfn.Size()], mac) return base64.URLEncoding.EncodeToString(ciphertext), nil } @@ -351,9 +360,26 @@ func decrypt(key []byte, cryptoText string) (string, error) { return "{}", nil } - ciphertext, _ := base64.URLEncoding.DecodeString(cryptoText) + ciphertext, err := base64.URLEncoding.DecodeString(cryptoText) + if err != nil { + return "", err + } - block, err := aes.NewCipher(key) + skey := sha512.Sum512(key) + ekey, akey := skey[:32], skey[32:] + macfn := hmac.New(sha256.New, akey) + if len(ciphertext) < aes.BlockSize+macfn.Size() { + return "", errors.New("short ciphertext") + } + + macfn.Write(ciphertext[aes.BlockSize+macfn.Size():]) + expectedMac := macfn.Sum(nil) + mac := ciphertext[aes.BlockSize:aes.BlockSize+macfn.Size()] + if hmac.Equal(expectedMac, mac) != true { + return "", errors.New("Incorrect MAC for the given ciphertext") + } + + block, err := aes.NewCipher(ekey) if err != nil { return "", err } @@ -362,7 +388,7 @@ func decrypt(key []byte, cryptoText string) (string, error) { return "", errors.New("ciphertext too short") } iv := ciphertext[:aes.BlockSize] - ciphertext = ciphertext[aes.BlockSize:] + ciphertext = ciphertext[aes.BlockSize+macfn.Size():] stream := cipher.NewCFBDecrypter(block, iv) From afda53615ad39d92950c1bf6351aeab30e1e2014 Mon Sep 17 00:00:00 2001 From: adamenger Date: Wed, 24 Jun 2015 11:34:53 -0500 Subject: [PATCH 05/84] IAM not AIM --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c2b0fbf2d8..638cbed9ba 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ AWS Elastic Beanstalk Setup (Docker) 2. Select "Create New Application" from the top right. 3. Name the application and press next 4. Select "Create a web server" environment. - 5. If asked, select create and AIM role and instance profile and press next. + 5. If asked, select create and IAM role and instance profile and press next. 6. For predefined configuration select docker. For environment type select single instance. 7. For application source, select upload your own and upload Dockerrun.aws.json from docker/Dockerrun.aws.json. Everything else may be left at default. 8. Select an environment name, this is how you will refer to your environment. Make sure the URL is available then press next. From e7900b016df0e0cbffebe4f772ac56464132ece7 Mon Sep 17 00:00:00 2001 From: Andrii Bubis Date: Wed, 24 Jun 2015 10:02:53 -0700 Subject: [PATCH 06/84] Update README.md --- README.md | 48 ++++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index c2b0fbf2d8..0433e8d97b 100644 --- a/README.md +++ b/README.md @@ -35,36 +35,32 @@ Local Machine Setup (Docker) ### Ubuntu ### 1. Follow the instructions at https://docs.docker.com/installation/ubuntulinux/ or use the summery below. -`sudo apt-get update` +``` bash +sudo apt-get update +sudo apt-get install wget +wget -qO- https://get.docker.com/ | sh +sudo usermod -aG docker +sudo service docker start +newgrp docker +``` -`sudo apt-get install wget` - -`wget -qO- https://get.docker.com/ | sh` - -`sudo usermod -aG docker ` - -`sudo service docker start` - -`newgrp docker` - -2. Run `docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:helium +2. Run `docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:helium` 3. When docker is done fetching the image, open http://localhost:8065/ in your browser ### Arch ### -1. Install docker using the following commands - -`pacman -S docker` - -`systemctl enable docker.service` - -`systemctl start docker.service` - -`gpasswd -a docker` - -`newgrp docker` - -2. docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:helium -3. When docker is done fetching the image, open http://localhost:8065/ in your browser +1. Install docker using the following commands: +``` bash +pacman -S docker +systemctl enable docker.service +systemctl start docker.service +gpasswd -a docker +newgrp docker +``` +2. Start docker container: +``` bash +docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:helium +``` +3. When docker is done fetching the image, open http://localhost:8065/ in your browser. ### Notes ### If your ISP blocks port 25 then you may install locally but email will not be sent. From 5db72e682be18c1bc89f2317bcbb0e1ab9e709f2 Mon Sep 17 00:00:00 2001 From: Andrii Bubis Date: Wed, 24 Jun 2015 10:11:20 -0700 Subject: [PATCH 07/84] Fixed list numbers --- README.md | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 0433e8d97b..fc0186380b 100644 --- a/README.md +++ b/README.md @@ -35,31 +35,35 @@ Local Machine Setup (Docker) ### Ubuntu ### 1. Follow the instructions at https://docs.docker.com/installation/ubuntulinux/ or use the summery below. -``` bash -sudo apt-get update -sudo apt-get install wget -wget -qO- https://get.docker.com/ | sh -sudo usermod -aG docker -sudo service docker start -newgrp docker -``` + ``` bash + sudo apt-get update + sudo apt-get install wget + wget -qO- https://get.docker.com/ | sh + sudo usermod -aG docker + sudo service docker start + newgrp docker + ``` 2. Run `docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:helium` 3. When docker is done fetching the image, open http://localhost:8065/ in your browser ### Arch ### 1. Install docker using the following commands: -``` bash -pacman -S docker -systemctl enable docker.service -systemctl start docker.service -gpasswd -a docker -newgrp docker -``` + + ``` bash + pacman -S docker + systemctl enable docker.service + systemctl start docker.service + gpasswd -a docker + newgrp docker + ``` + 2. Start docker container: -``` bash -docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:helium -``` + + ``` bash + docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:helium + ``` + 3. When docker is done fetching the image, open http://localhost:8065/ in your browser. ### Notes ### From 47322b9324e7fffa7c3457a80a67713dd430ab6c Mon Sep 17 00:00:00 2001 From: Andrii Bubis Date: Wed, 24 Jun 2015 10:20:11 -0700 Subject: [PATCH 08/84] Fixed typos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fc0186380b..4c06319534 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Local Machine Setup (Docker) 6. When docker is done fetching the image, open http://dockerhost:8065/ in your browser ### Ubuntu ### -1. Follow the instructions at https://docs.docker.com/installation/ubuntulinux/ or use the summery below. +1. Follow the instructions at https://docs.docker.com/installation/ubuntulinux/ or use the summary below. ``` bash sudo apt-get update @@ -107,7 +107,7 @@ AWS Elastic Beanstalk Setup (Docker) 18. Modify an existing CNAME record set or create a new one with the name * and the value of the domain you copied in step 1.13. 19. Save the record set -3. Set the enviroment variable "MATTERMOST\_DOMAIN" to the domain you mapped above (example.com not www.example.com) +3. Set the environment variable "MATTERMOST\_DOMAIN" to the domain you mapped above (example.com not www.example.com) 20. Return the Elastic Beanstalk from the AWS console. 21. Select the environment you created. 22. Select configuration from the sidebar. From 92391121e6febc4d80320742e51fc67af773d904 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Thu, 25 Jun 2015 12:41:48 -0400 Subject: [PATCH 09/84] updating readme --- README.md | 17 +++++++++++++++++ config/config_docker.json | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c2b0fbf2d8..3f5c60574d 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,17 @@ If you wish to remove mattermost-dev use the following commands 1. `docker stop mattermost-dev` 2. `docker rm -v mattermost-dev` +If you wish to gain access to the container use the folowwing commands +1. `docker exec -ti mattermost-dev /bin/bash` + +We've updated the config file to skip email verification. You can pull the latest docker container or run the following to skip email verification +1. `docker exec -ti mattermost-dev /bin/bash` +2. `cd src/github.com/mattermost/platform/config` +3. `vi config_docker.json` +4. `Edit "Mode" : "prod", -> "Mode" : "dev"` +5. `docker stop mattermost-dev` +6. `docker start mattermost-dev` + AWS Elastic Beanstalk Setup (Docker) ------------------------------------ @@ -119,6 +130,11 @@ AWS Elastic Beanstalk Setup (Docker) 26. Return to the dashboard on the sidebar and wait for beanstalk update the environment. 27. Try it out by entering the domain you mapped into your browser. +Contributing +------------ + +To contribute to this open source project please review the Mattermost Contribution Guidelines at http://www.mattermost.org/contribute-to-mattermost/. + License ------- @@ -126,3 +142,4 @@ Most Mattermost source files are made available under the terms of the GNU Affer As an exception, Admin Tools and Configuration Files are are made available under the terms of the Apache License, version 2.0. See LICENSE.txt for more information. + diff --git a/config/config_docker.json b/config/config_docker.json index 6936f619a7..85f0d9c73b 100644 --- a/config/config_docker.json +++ b/config/config_docker.json @@ -10,7 +10,7 @@ "ServiceSettings": { "SiteName": "Mattermost", "Domain": "", - "Mode" : "prod", + "Mode" : "dev", "AllowTesting" : false, "UseSSL": false, "Port": "80", From f5c2129fd59a2018d02905cb320c75e0f5983afc Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Thu, 25 Jun 2015 12:51:22 -0400 Subject: [PATCH 10/84] updating readme --- README.md | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 3f5c60574d..89fad28cea 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Installing the Mattermost You're installing "Mattermost Preview", a pre-released 0.50 version intended for an early look at what we're building. While SpinPunch runs this version internally, it's not recommended for production deployments since we can't guarantee API stability or backwards compatibility until our 1.0 version release. -That said, any issues at all, please let us know on the Mattermost forum at: http://bit.ly/1MY1kul +That said, any issues at all, please let us know on the Mattermost forum at: http://discourse.mattermost.org Local Machine Setup (Docker) ----------------------------- @@ -80,18 +80,9 @@ If you wish to remove mattermost-dev use the following commands 1. `docker stop mattermost-dev` 2. `docker rm -v mattermost-dev` -If you wish to gain access to the container use the folowwing commands +If you wish to gain access to the container use the following commands 1. `docker exec -ti mattermost-dev /bin/bash` -We've updated the config file to skip email verification. You can pull the latest docker container or run the following to skip email verification -1. `docker exec -ti mattermost-dev /bin/bash` -2. `cd src/github.com/mattermost/platform/config` -3. `vi config_docker.json` -4. `Edit "Mode" : "prod", -> "Mode" : "dev"` -5. `docker stop mattermost-dev` -6. `docker start mattermost-dev` - - AWS Elastic Beanstalk Setup (Docker) ------------------------------------ From f2401dd91f7e0ae005e1efddd1132d91bf4dad02 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Fri, 26 Jun 2015 03:07:20 +0500 Subject: [PATCH 11/84] MM-1184 - Updating image preview --- web/sass-files/sass/partials/_modal.scss | 13 +++++++------ web/sass-files/sass/partials/_responsive.scss | 1 - 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss index 43dbdc077c..22013aa4fd 100644 --- a/web/sass-files/sass/partials/_modal.scss +++ b/web/sass-files/sass/partials/_modal.scss @@ -129,15 +129,16 @@ width:100%; margin: 0 auto; .image-wrapper { - padding: 4px; background: #FFF; position: relative; max-width: 80%; - min-height: 280px; min-width: 280px; - @include border-radius(4px); + @include border-radius(3px); display: table; margin: 0 auto; + &:hover { + @include border-radius(3px 3px 0 0); + } &:hover .modal-close { @include opacity(1); } @@ -217,10 +218,11 @@ } .modal-button-bar { position:absolute; - bottom:0px; + bottom:-40px; left:0px; right:0px; - background-color:rgba(0, 0, 0, 0.8); + background-color: #222; + @include border-radius(0 0 3px 3px); @include opacity(0); -webkit-transition: opacity 0.6s; -moz-transition: opacity 0.6s; @@ -228,7 +230,6 @@ transition: opacity 0.6s; line-height: 40px; padding: 0 10px; - margin: 4px; &.footer--show { @include opacity(1); } diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index bed2f6324c..3949fc064a 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -538,7 +538,6 @@ .modal { .modal-image { .image-wrapper { - padding-bottom: 40px; .modal-close { @include opacity(1); } From febb6e3b656f0a5e48c928f26d071d2f1baf5b33 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Fri, 26 Jun 2015 23:19:29 +0500 Subject: [PATCH 12/84] mm-1256 - Improving view modal and close modal button --- web/react/components/channel_info_modal.jsx | 15 +++++++++--- web/sass-files/sass/partials/_modal.scss | 23 +++++++++++++++++-- web/sass-files/sass/partials/_responsive.scss | 11 ++++----- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/web/react/components/channel_info_modal.jsx b/web/react/components/channel_info_modal.jsx index 191297ce4f..18addb52fe 100644 --- a/web/react/components/channel_info_modal.jsx +++ b/web/react/components/channel_info_modal.jsx @@ -35,9 +35,18 @@ module.exports = React.createClass({

{channel.display_name}

-

Channel Name: {channel.display_name}

-

Channel Handle: {channel.name}

-

Channel ID: {channel.id}

+
+
Channel Name:
+
{channel.display_name}
+
+
+
Channel Handle:
+
{channel.name}
+
+
+
Channel ID:
+
{channel.id}
+
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss index 22013aa4fd..9009dd768c 100644 --- a/web/sass-files/sass/partials/_modal.scss +++ b/web/sass-files/sass/partials/_modal.scss @@ -1,9 +1,17 @@ +.modal-body { + padding: 20px 15px; +} .modal { &.image_modal { .modal-backdrop.in { @include opacity(0.7); } } + .info__label { + font-weight: bold; + text-align: right; + padding-right: 0; + } .remove__member { float: right; } @@ -29,7 +37,7 @@ border-radius: 0; background: $primary-color; color: #FFF; - padding: 15px 15px 11px; + padding: 15px 15px 11px; border: none; min-height: 56px; @include clearfix; @@ -41,11 +49,22 @@ margin: 0; } button.close { - margin-top: 0; + margin: -2px -2px 0 0; color: #fff; @include opacity(1); z-index: 5; + width: 30px; + height: 30px; + line-height: 30px; + @include single-transition(all, 0.25s, ease-in); position: relative; + &:hover { + background: rgba(0, 0, 0, 0.1); + } + span { + position: relative; + top: -1px; + } } .btn { margin-right: 10px; diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index 3949fc064a..509c764b31 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -231,17 +231,14 @@ } } .modal { + .info__label { + text-align: left; + padding-bottom: 5px; + } .modal-header { - padding-left: 20px; - padding-right: 20px; .modal-action { margin-top: 10px; } - button.close { - width: 35px; - height: 32px; - margin: -5px -10px 0; - } .modal-title { float: none; } From 0104bf822c70eef7793b8a31b78d5c72c0f1514e Mon Sep 17 00:00:00 2001 From: test Date: Fri, 26 Jun 2015 11:07:22 -0700 Subject: [PATCH 13/84] gui: fix mentions dropdown --- web/react/components/mention.jsx | 4 ++-- web/sass-files/sass/partials/_mentions.scss | 25 ++++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/web/react/components/mention.jsx b/web/react/components/mention.jsx index ba758688b3..86a4231387 100644 --- a/web/react/components/mention.jsx +++ b/web/react/components/mention.jsx @@ -8,8 +8,8 @@ module.exports = React.createClass({ render: function() { return (
- - @{this.props.username}{this.props.name} + + @{this.props.username}{this.props.name}
); } diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss index 11cd4e9e45..cb6ff16c55 100644 --- a/web/sass-files/sass/partials/_mentions.scss +++ b/web/sass-files/sass/partials/_mentions.scss @@ -3,21 +3,19 @@ background: $primary-color; position: relative; z-index: 10; - padding-bottom: 1px; + padding-bottom: 2px; @include border-radius(3px); - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - box-sizing: border-box; } .mentions--top { position: absolute; - z-index:99999; + z-index: 1040; .mentions-box { position:absolute; background-color:#fff; - border:1px solid #ddd; - overflow:scroll; + border: $border-gray; + overflow-x: hidden; + overflow-y: scroll; bottom:0; } } @@ -29,10 +27,10 @@ height:37px; padding:2px; z-index:101; -} - -.mentions-name:hover { - background-color:#e8eaed; + cursor: pointer; + &:hover { + background-color:#e8eaed; + } } .mentions-text { @@ -46,6 +44,11 @@ border-radius: 10%; } +.mention-fullname { + color: grey; + padding-left: 10px; +} + .mention-highlight { background-color:#fff2bb; } From 9e7ba4aa5dec9882e6fcf747b464cd8d5cee1e98 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Sat, 27 Jun 2015 00:29:37 +0500 Subject: [PATCH 14/84] Removing relative from span --- web/sass-files/sass/partials/_modal.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss index 9009dd768c..4427cb7ddf 100644 --- a/web/sass-files/sass/partials/_modal.scss +++ b/web/sass-files/sass/partials/_modal.scss @@ -62,8 +62,7 @@ background: rgba(0, 0, 0, 0.1); } span { - position: relative; - top: -1px; + line-height: 10px; } } .btn { From f25cdbdb08c11fc9cd3e6bbf0748b67d2635ff47 Mon Sep 17 00:00:00 2001 From: ralder Date: Fri, 26 Jun 2015 15:58:34 -0700 Subject: [PATCH 15/84] gui: fix broken mailto link in user-popover --- web/react/components/user_profile.jsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx index 8ffad737d8..648960471a 100644 --- a/web/react/components/user_profile.jsx +++ b/web/react/components/user_profile.jsx @@ -10,8 +10,7 @@ function getStateFromStores(userId) { if (profile == null) { return { profile: { id: "0", username: "..."} }; - } - else { + } else { return { profile: profile }; } } @@ -54,12 +53,11 @@ module.exports = React.createClass({ var name = this.props.overwriteName ? this.props.overwriteName : this.state.profile.username; - var data_content = "" - data_content += "" + var data_content = ""; if (!config.ShowEmail) { - data_content += "
Email not shared
"; + data_content += "
Email not shared
"; } else { - data_content += ""; + data_content += ""; } return ( From e1714eaa3f3363c0e38c947c2cc438b201a16a02 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Sun, 28 Jun 2015 04:18:24 +0500 Subject: [PATCH 16/84] Updating settings UI --- web/react/components/setting_item_max.jsx | 2 +- web/react/components/user_settings.jsx | 30 +++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/web/react/components/setting_item_max.jsx b/web/react/components/setting_item_max.jsx index 03f05b0cf2..b8b667e1a7 100644 --- a/web/react/components/setting_item_max.jsx +++ b/web/react/components/setting_item_max.jsx @@ -13,7 +13,7 @@ module.exports = React.createClass({
  • {this.props.title}
    • -
    • +
    • {inputs}
    • diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx index 7d542a8b7e..147973fb79 100644 --- a/web/react/components/user_settings.jsx +++ b/web/react/components/user_settings.jsx @@ -155,7 +155,7 @@ var NotificationsTab = React.createClass({ var inputs = []; inputs.push( -
      +
    • +
    • { theme_buttons }
      From ce74e4a7288c1edded736f1315beb8b1909a4641 Mon Sep 17 00:00:00 2001 From: ralder Date: Mon, 29 Jun 2015 01:39:31 +0300 Subject: [PATCH 17/84] fix pr #82 mention list inside modal --- web/sass-files/sass/partials/_mentions.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss index cb6ff16c55..86fe400c9f 100644 --- a/web/sass-files/sass/partials/_mentions.scss +++ b/web/sass-files/sass/partials/_mentions.scss @@ -9,7 +9,7 @@ .mentions--top { position: absolute; - z-index: 1040; + z-index: 1060; .mentions-box { position:absolute; background-color:#fff; From 2cd5f3629ff2410127a8fdc5df92fb16f52fa457 Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Sun, 28 Jun 2015 19:25:39 -0700 Subject: [PATCH 18/84] Fixed typo in team domain look-up page --- config/config.json | 6 +++--- web/react/components/login.jsx | 2 +- web/react/utils/utils.jsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/config.json b/config/config.json index e38f1701a1..90ec9540ec 100644 --- a/config/config.json +++ b/config/config.json @@ -8,9 +8,9 @@ "FileLocation": "" }, "ServiceSettings": { - "SiteName": "Mattermost", - "Domain": "xxxxxxmustbefilledin.com", - "Mode" : "dev", + "SiteName": "Battlehouse", + "Domain": "battlehouse.com", + "Mode" : "prod", "AllowTesting" : false, "UseSSL": false, "Port": "8065", diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index 85df5f797c..3b6f96c2d4 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -37,7 +37,7 @@ var FindTeamDomain = React.createClass({ window.location.href = window.location.protocol + "//" + domain + "." + utils.getDomainWithOutSub(); } else { - this.state.server_error = "We couldn't find your " + strings.TeamPlural + "."; + this.state.server_error = "We couldn't find your " + strings.Team + "."; this.setState(this.state); } }.bind(this), diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 75c583c8fc..128d9d1e70 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -25,7 +25,7 @@ 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)) @@ -44,7 +44,7 @@ module.exports.isTestDomain = function() { return true; if ((/^176./).test(window.location.hostname)) - return true; + return true;*/ return false; }; From e621950364c93365fad4dbbbb6e94360d2fce809 Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Sun, 28 Jun 2015 19:38:19 -0700 Subject: [PATCH 19/84] Removed test code --- config/config.json | 6 +++--- web/react/utils/utils.jsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/config.json b/config/config.json index 90ec9540ec..e38f1701a1 100644 --- a/config/config.json +++ b/config/config.json @@ -8,9 +8,9 @@ "FileLocation": "" }, "ServiceSettings": { - "SiteName": "Battlehouse", - "Domain": "battlehouse.com", - "Mode" : "prod", + "SiteName": "Mattermost", + "Domain": "xxxxxxmustbefilledin.com", + "Mode" : "dev", "AllowTesting" : false, "UseSSL": false, "Port": "8065", diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 128d9d1e70..75c583c8fc 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -25,7 +25,7 @@ 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)) @@ -44,7 +44,7 @@ module.exports.isTestDomain = function() { return true; if ((/^176./).test(window.location.hostname)) - return true;*/ + return true; return false; }; From 2b4888d062d65546325470d2d9181f4a66a2fb00 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Mon, 22 Jun 2015 14:23:59 -0400 Subject: [PATCH 20/84] fixes mm-1316 improves channel notifications UI and updates channellist etag --- model/channel_list.go | 6 + model/channel_member.go | 5 + store/sql_channel_store.go | 12 +- web/react/components/channel_header.jsx | 30 +-- .../components/channel_notifications.jsx | 208 ++++++++++++++---- 5 files changed, 185 insertions(+), 76 deletions(-) diff --git a/model/channel_list.go b/model/channel_list.go index 088dbea2a1..09f14a9862 100644 --- a/model/channel_list.go +++ b/model/channel_list.go @@ -53,6 +53,12 @@ func (o *ChannelList) Etag() string { t = member.LastViewedAt id = v.Id } + + if member.LastUpdateAt > t { + t = member.LastUpdateAt + id = v.Id + } + } } diff --git a/model/channel_member.go b/model/channel_member.go index 720ac4c42d..50f51304bc 100644 --- a/model/channel_member.go +++ b/model/channel_member.go @@ -25,6 +25,7 @@ type ChannelMember struct { MsgCount int64 `json:"msg_count"` MentionCount int64 `json:"mention_count"` NotifyLevel string `json:"notify_level"` + LastUpdateAt int64 `json:"last_update_at"` } func (o *ChannelMember) ToJson() string { @@ -70,6 +71,10 @@ func (o *ChannelMember) IsValid() *AppError { return nil } +func (o *ChannelMember) PreSave() { + o.LastUpdateAt = GetMillis() +} + func IsChannelNotifyLevelValid(notifyLevel string) bool { return notifyLevel == CHANNEL_NOTIFY_ALL || notifyLevel == CHANNEL_NOTIFY_MENTION || notifyLevel == CHANNEL_NOTIFY_NONE || notifyLevel == CHANNEL_NOTIFY_QUIET } diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go index 592657c1c3..0a1ea23fe9 100644 --- a/store/sql_channel_store.go +++ b/store/sql_channel_store.go @@ -37,6 +37,7 @@ func NewSqlChannelStore(sqlStore *SqlStore) ChannelStore { } func (s SqlChannelStore) UpgradeSchemaIfNeeded() { + s.CreateColumnIfNotExists("ChannelMembers", "LastUpdateAt", "NotifyLevel", "bigint(20)", "0") } func (s SqlChannelStore) CreateIndexesIfNotExists() { @@ -273,6 +274,7 @@ func (s SqlChannelStore) SaveMember(member *model.ChannelMember) StoreChannel { go func() { result := StoreResult{} + member.PreSave() if result.Err = member.IsValid(); result.Err != nil { storeChannel <- result return @@ -484,7 +486,8 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelId string, userId string) Sto SET ChannelMembers.MentionCount = 0, ChannelMembers.MsgCount = Channels.TotalMsgCount, - ChannelMembers.LastViewedAt = Channels.LastPostAt + ChannelMembers.LastViewedAt = Channels.LastPostAt, + ChannelMembers.LastUpdateAt = Channels.LastPostAt WHERE Channels.Id = ChannelMembers.ChannelId AND UserId = ? @@ -533,15 +536,18 @@ func (s SqlChannelStore) UpdateNotifyLevel(channelId, userId, notifyLevel string go func() { result := StoreResult{} + updateAt := model.GetMillis() + _, err := s.GetMaster().Exec( `UPDATE ChannelMembers SET - NotifyLevel = ? + NotifyLevel = ?, + LastUpdateAt = ? WHERE UserId = ? AND ChannelId = ?`, - notifyLevel, userId, channelId) + notifyLevel, updateAt, userId, channelId) if err != nil { result.Err = model.NewAppError("SqlChannelStore.UpdateNotifyLevel", "We couldn't update the notify level", "channel_id="+channelId+", user_id="+userId+", "+err.Error()) } diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index ade58a10a3..428d3ed818 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -15,17 +15,8 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -function getExtraInfoStateFromStores() { - return { - extra_info: ChannelStore.getCurrentExtraInfo() - }; -} - var ExtraMembers = React.createClass({ componentDidMount: function() { - ChannelStore.addExtraInfoChangeListener(this._onChange); - ChannelStore.addChangeListener(this._onChange); - var originalLeave = $.fn.popover.Constructor.prototype.leave; $.fn.popover.Constructor.prototype.leave = function(obj) { var self = obj instanceof this.constructor ? obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type); @@ -49,25 +40,12 @@ var ExtraMembers = React.createClass({ }); }, - componentWillUnmount: function() { - ChannelStore.removeExtraInfoChangeListener(this._onChange); - ChannelStore.removeChangeListener(this._onChange); - }, - _onChange: function() { - var newState = getExtraInfoStateFromStores(); - if (!utils.areStatesEqual(newState, this.state)) { - this.setState(newState); - } - }, - getInitialState: function() { - return getExtraInfoStateFromStores(); - }, render: function() { - var count = this.state.extra_info.members.length == 0 ? "-" : this.state.extra_info.members.length; - count = this.state.extra_info.members.length > 19 ? "20+" : count; + var count = this.props.members.length == 0 ? "-" : this.props.members.length; + count = this.props.members.length > 19 ? "20+" : count; var data_content = ""; - this.state.extra_info.members.forEach(function(m) { + this.props.members.forEach(function(m) { data_content += "
      " + m.username + "
      "; }); @@ -228,7 +206,7 @@ module.exports = React.createClass({ {channelTitle} } - + { searchForm }
      diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx index 085536a0aa..fa9ab42ae8 100644 --- a/web/react/components/channel_notifications.jsx +++ b/web/react/components/channel_notifications.jsx @@ -1,6 +1,8 @@ // 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 utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); @@ -9,26 +11,50 @@ var ChannelStore = require('../stores/channel_store.jsx'); module.exports = React.createClass({ componentDidMount: function() { + ChannelStore.addChangeListener(this._onChange); + var self = this; $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { var button = e.relatedTarget; var channel_id = button.dataset.channelid; var notifyLevel = ChannelStore.getMember(channel_id).notify_level; - self.setState({ notify_level: notifyLevel, title: button.dataset.title, channel_id: channel_id }); + var quietMode = false; + if (notifyLevel === "quiet") quietMode = true; + self.setState({ notify_level: notifyLevel, quiet_mode: quietMode, title: button.dataset.title, channel_id: channel_id }); }); }, - getInitialState: function() { - return { notify_level: "", title: "", channel_id: "" }; + componentWillUnmount: function() { + ChannelStore.removeChangeListener(this._onChange); }, - handleUpdate: function(e) { + _onChange: function() { + if (!this.state.channel_id) return; + var notifyLevel = ChannelStore.getMember(this.state.channel_id).notify_level; + var quietMode = false; + if (notifyLevel === "quiet") quietMode = true; + + var newState = this.state; + newState.notify_level = notifyLevel; + newState.quiet_mode = quietMode; + + if (!utils.areStatesEqual(this.state, newState)) { + this.setState(newState); + } + }, + updateSection: function(section) { + this.setState({ activeSection: section }); + }, + getInitialState: function() { + return { notify_level: "", title: "", channel_id: "", activeSection: "" }; + }, + handleUpdate: function() { var channel_id = this.state.channel_id; - var notify_level = this.state.notify_level; + var notify_level = this.state.quiet_mode ? "quiet" : this.state.notify_level; var data = {}; data["channel_id"] = channel_id; data["user_id"] = UserStore.getCurrentId(); - data["notify_level"] = this.state.notify_level; + data["notify_level"] = notify_level; if (!data["notify_level"] || data["notify_level"].length === 0) return; @@ -37,7 +63,7 @@ module.exports = React.createClass({ var member = ChannelStore.getMember(channel_id); member.notify_level = notify_level; ChannelStore.setChannelMember(member); - $(this.refs.modal.getDOMNode()).modal('hide'); + this.updateSection(""); }.bind(this), function(err) { this.setState({ server_error: err.message }); @@ -45,42 +71,138 @@ module.exports = React.createClass({ ); }, handleRadioClick: function(notifyLevel) { - this.setState({ notify_level: notifyLevel }); + this.setState({ notify_level: notifyLevel, quiet_mode: false }); this.refs.modal.getDOMNode().focus(); }, - handleQuietToggle: function() { - if (this.state.notify_level === "quiet") { - this.setState({ notify_level: "none" }); - this.refs.modal.getDOMNode().focus(); - } else { - this.setState({ notify_level: "quiet" }); - this.refs.modal.getDOMNode().focus(); - } + handleQuietToggle: function(quietMode) { + this.setState({ notify_level: "none", quiet_mode: quietMode }); + this.refs.modal.getDOMNode().focus(); }, render: function() { var server_error = this.state.server_error ?
      : null; - var allActive = ""; - var mentionActive = ""; - var noneActive = ""; - var quietActive = ""; - var desktopHidden = ""; + var self = this; - if (this.state.notify_level === "quiet") { - desktopHidden = "hidden"; - quietActive = "active"; - } else if (this.state.notify_level === "mention") { - mentionActive = "active"; - } else if (this.state.notify_level === "none") { - noneActive = "active"; + var desktopSection; + if (this.state.activeSection === 'desktop') { + var notifyActive = [false, false, false]; + if (this.state.notify_level === "mention") { + notifyActive[1] = true; + } else if (this.state.notify_level === "all") { + notifyActive[0] = true; + } else { + notifyActive[2] = true; + } + + var inputs = []; + + inputs.push( +
      +
      + +
      +
      +
      + +
      +
      +
      + +
      +
      + ); + + desktopSection = ( + + ); } else { - allActive = "active"; + var describe = ""; + if (this.state.notify_level === "mention") { + describe = "Only for mentions"; + } else if (this.state.notify_level === "all") { + describe = "For all activity"; + } else { + describe = "Never"; + } + + desktopSection = ( + + ); + } + + var quietSection; + if (this.state.activeSection === 'quiet') { + var quietActive = ["",""]; + if (this.state.quiet_mode) { + quietActive[0] = "active"; + } else { + quietActive[1] = "active"; + } + + var inputs = []; + + inputs.push( +
      +
      + + +
      +
      + ); + + inputs.push( +
      +
      + Enabling quiet mode will turn off desktop notifications and only mark the channel as unread if you have been mentioned. +
      + ); + + quietSection = ( + + ); + } else { + var describe = ""; + if (this.state.quiet_mode) { + describe = "On"; + } else { + describe = "Off"; + } + + quietSection = ( + + ); } var self = this; return (