Merge remote-tracking branch 'upstream/master' into PLT-637

Conflicts:
	web/react/utils/constants.jsx
Этот коммит содержится в:
Stas Vovk
2015-10-16 14:21:51 +03:00
родитель 2bd81ff379 50a8e2bd4f
Коммит 79fcb98084
133 изменённых файлов: 15051 добавлений и 17638 удалений

Просмотреть файл

@@ -1,10 +1,14 @@
# Mattermost Changelog # Mattermost Changelog
## UNDER DEVELOPMENT - Release v1.1.0 ## UNDER DEVELOPMENT Release v1.2.0
The "UNDER DEVELOPMENT" section of the Mattermost changelog appears in the product's `master` branch to note key changes committed to master and are on their way to the next stable release. When a stable release is pushed the "UNDER DEVELOPMENT" heading is removed from the final changelog of the release. The "UNDER DEVELOPMENT" section of the Mattermost changelog appears in the product's `master` branch to note key changes committed to master and are on their way to the next stable release. When a stable release is pushed the "UNDER DEVELOPMENT" heading is removed from the final changelog of the release.
- **Final release anticipated:** 2015-10-16 - **Final release anticipated:** 2015-11-16
## Release v1.1.0
Released: 2015-10-16
### Release Highlights ### Release Highlights
@@ -44,6 +48,11 @@ Messaging and Notifications
- Fixed bug where System Administrator did not have Team Administrator permissions - Fixed bug where System Administrator did not have Team Administrator permissions
- Fixed bug causing scrolling to jump when the right hand sidebar opened and closed - Fixed bug causing scrolling to jump when the right hand sidebar opened and closed
### Known Issues
- Slack import is unstable due to change in Slack export format
- Uploading a .flac file breaks the file previewer on iOS
### Contributors ### Contributors
Many thanks to our external contributors. In no particular order: Many thanks to our external contributors. In no particular order:

Просмотреть файл

@@ -106,11 +106,12 @@ travis:
mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js
mv $(DIST_PATH)/web/static/js/libs.min.js $(DIST_PATH)/web/static/js/libs-$(BUILD_NUMBER).min.js mv $(DIST_PATH)/web/static/js/libs.min.js $(DIST_PATH)/web/static/js/libs-$(BUILD_NUMBER).min.js
@sed -i'.bak' 's|react-with-addons-0.13.3.js|react-with-addons-0.13.3.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|react-0.14.0.js|react-0.14.0.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|jquery-1.11.1.js|jquery-1.11.1.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|react-dom-0.14.0.js|react-dom-0.14.0.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|jquery-2.1.4.js|jquery-2.1.4.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|bootstrap-3.3.5.js|bootstrap-3.3.5.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|bootstrap-3.3.5.js|bootstrap-3.3.5.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|react-bootstrap-0.25.1.js|react-bootstrap-0.25.1.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|react-bootstrap-0.27.1.js|react-bootstrap-0.27.1.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|perfect-scrollbar.js|perfect-scrollbar.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|perfect-scrollbar-0.6.7.jquery.js|perfect-scrollbar-0.6.7.jquery.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|bundle.js|bundle-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|bundle.js|bundle-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|libs.min.js|libs-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|libs.min.js|libs-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html
rm $(DIST_PATH)/web/templates/*.bak rm $(DIST_PATH)/web/templates/*.bak
@@ -290,11 +291,12 @@ dist: install
mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js
mv $(DIST_PATH)/web/static/js/libs.min.js $(DIST_PATH)/web/static/js/libs-$(BUILD_NUMBER).min.js mv $(DIST_PATH)/web/static/js/libs.min.js $(DIST_PATH)/web/static/js/libs-$(BUILD_NUMBER).min.js
@sed -i'.bak' 's|react-with-addons-0.13.3.js|react-with-addons-0.13.3.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|react-0.14.0.js|react-0.14.0.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|jquery-1.11.1.js|jquery-1.11.1.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|react-dom-0.14.0.js|react-dom-0.14.0.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|jquery-2.1.4.js|jquery-2.1.4.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|bootstrap-3.3.5.js|bootstrap-3.3.5.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|bootstrap-3.3.5.js|bootstrap-3.3.5.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|react-bootstrap-0.25.1.js|react-bootstrap-0.25.1.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|react-bootstrap-0.27.1.js|react-bootstrap-0.27.1.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|perfect-scrollbar.js|perfect-scrollbar.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|perfect-scrollbar-0.6.7.jquery.js|perfect-scrollbar-0.6.7.jquery.min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|bundle.js|bundle-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|bundle.js|bundle-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html
@sed -i'.bak' 's|libs.min.js|libs-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|libs.min.js|libs-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html
rm $(DIST_PATH)/web/templates/*.bak rm $(DIST_PATH)/web/templates/*.bak

Просмотреть файл

@@ -19,6 +19,8 @@ func Setup() {
StartServer() StartServer()
InitApi() InitApi()
Client = model.NewClient("http://localhost" + utils.Cfg.ServiceSettings.ListenAddress) Client = model.NewClient("http://localhost" + utils.Cfg.ServiceSettings.ListenAddress)
Srv.Store.MarkSystemRanUnitTests()
} }
} }

Просмотреть файл

@@ -8,15 +8,17 @@ Get up and running quickly with Docker-based install
- [AWS Elastic Beanstalk Setup](install/Amazon-Elastic-Beanstalk.md) - [AWS Elastic Beanstalk Setup](install/Amazon-Elastic-Beanstalk.md)
- [Docker Single Container Preview Setup](install/Docker-Single-Container.md) - [Docker Single Container Preview Setup](install/Docker-Single-Container.md)
- [SMTP Email Setup](install/SMTP-Email-Setup.md) - [SMTP Email Setup](install/SMTP-Email-Setup.md)
- [System Console Settings](install/Configuration-Settings.md)
#### Production Installation #### Production Installation
Set up Mattermost in your data center Set up Mattermost in your data center
- [Software and Hardware Requirements](install/Requirements.md) - [Software and Hardware Requirements](install/Requirements.md)
- [Production Ubuntu Setup](install/Production-Ubuntu.md) - [Production Ubuntu Setup](install/Production-Ubuntu.md)
- [SMTP Email Setup](install/SMTP-Email-Setup.md) - [SMTP Email Setup](install/SMTP-Email-Setup.md)
- [System Console Settings](install/Configuration-Settings.md)
#### Configuration and Management #### Configuration and Management
- Configuration Settings Overview - [System Console Settings](install/Configuration-Settings.md)
- [GitLab SSO Configuration](integrations/Single-Sign-On/Gitlab.md) - [GitLab SSO Configuration](integrations/Single-Sign-On/Gitlab.md)
- [Mattermost Release Numbering Scheme](install/Release-Numbering.md) - [Mattermost Release Numbering Scheme](install/Release-Numbering.md)

Просмотреть файл

@@ -61,7 +61,7 @@ Any issues? Please let us know on our forums at: http://forum.mattermost.org
3. Reload your bashrc 3. Reload your bashrc
`source ~/.bashrc` `source ~/.bashrc`
6. Install Node.js 6. Install Node.js
1. Download the newest version of the Node.js sources from https://nodejs.org/download/ 1. Download the newest version of the Node.js sources from https://nodejs.org/en/download/
2. Extract the contents of the package and cd into the extracted files 2. Extract the contents of the package and cd into the extracted files
3. Compile and install Node.js 3. Compile and install Node.js
`./configure` `./configure`

Просмотреть файл

@@ -1,22 +1,29 @@
### Slack Import (Beta) ### Slack Import
*Note: As a SaaS service, Slack is able to change its export format quickly. If you encounter issues not mentioned in the documentation below, please let us know by [filing an issue](https://github.com/mattermost/platform/issues).* *Note: As a proprietary SaaS service, Slack is able to change its export format quickly and without notice. If you encounter issues not mentioned in the documentation below, please alert the product team by [filing an issue](https://github.com/mattermost/platform/issues).*
#### Usage #### Usage
The Slack Import feature in Mattermost is in "Beta" and focus is on supporting migration of teams of less than 100 registered users. The feature can be accessed from by Team Administrators and Team Owners via the `Team Settings -> Import` menu option. The Slack Import feature in Mattermost is in "Beta" and focus is on supporting migration of teams of less than 100 registered users. To use:
- **It is highly recommended that you test Slack import before applying it to an instance intended for production.** If you use Docker, you can spin up a test instance in one line (`docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform`). If you don't use Docker, there are [step-by-step instructions to install Mattermost in preview mode in less than 5 minutes](../install/Docker-Single-Container.md). 1. Generate a Slack "Export" file from **Slack > Team Settings > Import/Export Data > Export > Start Export**
Mattermost currently supports the processing of an "Export" file from Slack containing account information and public channel archives from a Slack team. 2. In Mattermost go to **Team Settings > Import > Import from Slack**. _Team Owner_ or _Team Administrator_ role is required to access this menu option.
- Emails and usernames from Slack are used to create new Mattermost accounts, connected to messages history in imported Slack channels. Users can activate these accounts and by going to the Password Reset screen in Mattermost to set new credentials. 3. Click **Select file** to upload Slack export file and click **Import**.
- Once logged in, users will have access to previous Slack messages shared in public channels, now imported to Mattermost.
#### Limitations: 4. Emails and usernames from Slack are used to create new Mattermost accounts
5. Slack users can activate their new Mattermost accounts by using Mattermost's Password Reset screen with their email addresses from Slack to set new passwords for their Mattermost accounts
6. Once logged in, the Mattermost users will have access to previous Slack messages in the public channels imported from Slack.
**It is highly recommended that you test Slack import before applying it to an instance intended for production.** If you use Docker, you can spin up a test instance in one line (`docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform`). If you don't use Docker, there are [step-by-step instructions](../install/Docker-Single-Container.md) to install Mattermost in preview mode in less than 5 minutes.
#### Notes:
- Newly added markdown suppport in Slack's Posts 2.0 feature announced on September 28, 2015 is not yet supported. - Newly added markdown suppport in Slack's Posts 2.0 feature announced on September 28, 2015 is not yet supported.
- Slack does not export files or images your team has stored in Slack's database. Mattermost will provide links to the location of your assets in Slack's web UI. - Slack does not export files or images your team has stored in Slack's database. Mattermost will provide links to the location of your assets in Slack's web UI.
- Slack does not export any content from private groups or direct messages that your team has stored in Slack's database. - Slack does not export any content from private groups or direct messages that your team has stored in Slack's database.
- The Beta release of Slack Import does not offer pre-checks or roll-back and will not import Slack accounts with username or email address collisions with existing Mattermost accounts. Also, Slack channel names with underscores will not import. Also, mentions do not yet resolve as Mattermost usernames (still shows Slack ID). - In Beta, Slack accounts with username or email address collisions with existing Mattermost accounts will not import and mentions do not resolve as Mattermost usernames (still shows Slack ID). No pre-check or roll-back is currently offered.

Просмотреть файл

@@ -28,7 +28,7 @@
## Set up Mattermost Server ## Set up Mattermost Server
1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.2 1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.2
1. Download the latest Mattermost Server by typing: 1. Download the latest Mattermost Server by typing:
* ``` wget https://github.com/mattermost/platform/releases/download/v1.0.0/mattermost.tar.gz``` * ``` wget https://github.com/mattermost/platform/releases/download/v1.1.0/mattermost.tar.gz```
1. Unzip the Mattermost Server by typing: 1. Unzip the Mattermost Server by typing:
* ``` tar -xvzf mattermost.tar.gz``` * ``` tar -xvzf mattermost.tar.gz```
1. For the sake of making this guide simple we located the files at `/home/ubuntu/mattermost`. In the future we will give guidance for storing under `/opt`. 1. For the sake of making this guide simple we located the files at `/home/ubuntu/mattermost`. In the future we will give guidance for storing under `/opt`.

Просмотреть файл

@@ -1,50 +1,46 @@
## SMTP Email Setup ## SMTP Email Setup
In some product evaluation setups email is intentionally bypassed using a `ByPassEmail=true` option. This option allows account creation and system operation without having to set up an email service (e.g. no email verification is required for account creation). This also means neither email notifications nor password reset by email are available. In product evaluation setups with single-container Docker instances, email is intentionally disabled. This allows account creation and system operation without having to set up email, but it also means email notification and password reset functionality aren't available.
To enable email, turn this option off by setting `ByPassEmail=false` and configuring an SMTP email service as follows: ### How to enable email
1. **Set up an SMTP email sending service.** (If you already have credentials for a SMTP server you can skip this step.) To enable email, configure an SMTP email service as follows:
1. [Setup Amazon Simple Email Service](https://console.aws.amazon.com/ses)
2. From the `SMTP Settings` menu click `Create My SMTP Credentials` 1. **Set up an SMTP email sending service** (if you don't yet have an SMTP service with credentials)
3. Copy the `Server Name`, `Port`, `SMTP Username`, and `SMTP Password` 1. Any SMTP email service can be used, you just need the following information: `Server Name`, `Port`, `SMTP Username`, and `SMTP Password`.
4. From the `Domains` menu setup and verify a new domain. It it also a good practice to enable `Generate DKIM Settings` for this domain. 2. If you don't have an SMTP service, here are simple instructions to set one up with [Amazon Simple Email Service (SES)](https://aws.amazon.com/ses/):
5. Choose an email address like `feedback@example.com` for Mattermost to send emails from. 2. Go to [Amazon SES console](https://console.aws.amazon.com/ses) then `SMTP Settings > Create My SMTP Credentials`
6. Test sending an email from `feedback@example.com` by clicking the `Send a Test Email` button and verify everything appears to be working correctly. 3. Copy the `Server Name`, `Port`, `SMTP Username`, and `SMTP Password` for Step 2 below.
2. **Modify the Mattermost configuration file config.json or config_docker.json with the SMTP information.** 4. From the `Domains` menu set up and verify a new domain, then enable `Generate DKIM Settings` for the domain.
1. If you're running Mattermost on Amazon Beanstalk you can shell into the instance with the following commands 5. Choose an sender address like `mattermost@example.com` and click `Send a Test Email` to verify setup is working correctly.
2. `ssh ec2-user@[domain for the docker instance]` 2. **Configure SMTP settings**
3. `sudo gpasswd -a ec2-user docker` 1. Open the **System Console** by logging into an existing team and accessing "System Console" from the main menu.
4. Retrieve the name of the container with `sudo docker ps` 1. Alternatively, if a team doesn't yet exist, go to `http://dockerhost:8065/` in your browser, create a team, then from the main menu click **System Console**
5. `sudo docker exec -ti container_name /bin/bash` 2. Go to the **Email Settings** tab and configure the following:
3. **Edit the config file `vi /config_docker.json` with the settings you captured from the step above.** 1. **Allow Sign Up With Email:** `true`
1. See an example below and notice `ByPassEmail` has been set to `false` 2. **Send Email Notifications:** `true`
``` bash 3. **Require Email Verification:** `true`
"EmailSettings": { 4. **Notification Display Name:** Display name on email account sending notifications
"ByPassEmail" : false, 5. **Notification Email Address:** Email address displayed on email account used to send notifications
"SMTPUsername": "AKIADTOVBGERKLCBV", 6. **SMTP Username**: `SMTP Username` from Step 1
"SMTPPassword": "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY", 7. **SMTP Password**: `SMTP Password` from Step 1
"SMTPServer": "email-smtp.us-east-1.amazonaws.com:465", 8. **SMTP Server**: `SMTP Server` from Step 1
"UseTLS": true, 9. **SMTP Port**: `SMTP Port` from Step 1
"FeedbackEmail": "feedback@example.com", 10. **Connection Security**: `TLS (Recommended)`
"FeedbackName": "Feedback", 11. Then click **Save**
"ApplePushServer": "",
"ApplePushCertPublic": "", 3. **Restart Mattermost**
"ApplePushCertPrivate": "" 1. Use `ps -A` to find the process ID ("pid") for service named `platform` and stop it using `kill [pid]`
} 2. The service should restart automatically. Run `ps -A` to verify the `platform` is running again
``` 3. Use the reset password page (E.g. _example.com/teamname/reset_password_) to test that email is now working by entering your email and clicking **Reset my password**.
4. **Restart Mattermost** 4. Note: The next time users log out, or when their session tokens expire, each will be required to verify their email address.
1. Find the process id with `ps -A` and look for the process named `platform`
2. Kill the process `kill pid`
3. The service should restart automatically. Verify the Mattermost service is running with `ps -A`
4. Current logged in users will not be affected, but upon logging out or session expiration users will be required to verify their email address.
### Troubleshooting SMTP ### Troubleshooting SMTP
If you have issues with your SMTP install, from your Mattermost team site go to the main menu and open `System Console > Logs` to look for error messages related to your setup. You can do a search for the error code to narrow down the issue. Sometimes ISPs require nuanced setups for SMTP and error codes can hint at how to make the proper adjustments. If you have issues with your SMTP install, from your Mattermost team site go to the main menu and open **System Console -> Logs** to look for error messages related to your setup. You can do a search for the error code to narrow down the issue. Sometimes ISPs require nuanced setups for SMTP and error codes can hint at how to make the proper adjustments.
For example, if `System Console > Logs` has an error code reading: For example, if **System Console -> Logs** has an error code reading:
``` ```
Connection unsuccessful: Failed to add to email address - 554 5.7.1 <unknown[IP-ADDRESS]>: Client host rejected: Access denied Connection unsuccessful: Failed to add to email address - 554 5.7.1 <unknown[IP-ADDRESS]>: Client host rejected: Access denied

Просмотреть файл

@@ -3,5 +3,5 @@
#### Important notes #### Important notes
1. **DO NOT manipulate the Mattermost database** 1. **DO NOT manipulate the Mattermost database**
- In particular, DO NOT delete data from the database, as this will most likely crash Mattermost in strange ways. Mattermost is designed to archive content continously and generally assumes data is never deleted. - In particular, DO NOT delete data from the database, as Mattermost is designed to stop working if data integrity has been compromised. The system is designed to archive content continously and generally assumes data is never deleted.

Просмотреть файл

@@ -84,16 +84,16 @@ func securityAndDiagnosticsJob() {
if *utils.Cfg.ServiceSettings.EnableSecurityFixAlert { if *utils.Cfg.ServiceSettings.EnableSecurityFixAlert {
if result := <-api.Srv.Store.System().Get(); result.Err == nil { if result := <-api.Srv.Store.System().Get(); result.Err == nil {
props := result.Data.(model.StringMap) props := result.Data.(model.StringMap)
lastSecurityTime, _ := strconv.ParseInt(props["LastSecurityTime"], 10, 0) lastSecurityTime, _ := strconv.ParseInt(props[model.SYSTEM_LAST_SECURITY_TIME], 10, 0)
currentTime := model.GetMillis() currentTime := model.GetMillis()
if (currentTime - lastSecurityTime) > 1000*60*60*24*1 { if (currentTime - lastSecurityTime) > 1000*60*60*24*1 {
l4g.Debug("Checking for security update from Mattermost") l4g.Debug("Checking for security update from Mattermost")
id := props["DiagnosticId"] id := props[model.SYSTEM_DIAGNOSTIC_ID]
if len(id) == 0 { if len(id) == 0 {
id = model.NewId() id = model.NewId()
systemId := &model.System{Name: "DiagnosticId", Value: id} systemId := &model.System{Name: model.SYSTEM_DIAGNOSTIC_ID, Value: id}
<-api.Srv.Store.System().Save(systemId) <-api.Srv.Store.System().Save(systemId)
} }
@@ -104,7 +104,13 @@ func securityAndDiagnosticsJob() {
v.Set(utils.PROP_DIAGNOSTIC_OS, runtime.GOOS) v.Set(utils.PROP_DIAGNOSTIC_OS, runtime.GOOS)
v.Set(utils.PROP_DIAGNOSTIC_CATEGORY, utils.VAL_DIAGNOSTIC_CATEGORY_DEFAULT) v.Set(utils.PROP_DIAGNOSTIC_CATEGORY, utils.VAL_DIAGNOSTIC_CATEGORY_DEFAULT)
systemSecurityLastTime := &model.System{Name: "LastSecurityTime", Value: strconv.FormatInt(currentTime, 10)} if len(props[model.SYSTEM_RAN_UNIT_TESTS]) > 0 {
v.Set(utils.PROP_DIAGNOSTIC_UNIT_TESTS, "1")
} else {
v.Set(utils.PROP_DIAGNOSTIC_UNIT_TESTS, "0")
}
systemSecurityLastTime := &model.System{Name: model.SYSTEM_LAST_SECURITY_TIME, Value: strconv.FormatInt(currentTime, 10)}
if lastSecurityTime == 0 { if lastSecurityTime == 0 {
<-api.Srv.Store.System().Save(systemSecurityLastTime) <-api.Srv.Store.System().Save(systemSecurityLastTime)
} else { } else {

Просмотреть файл

@@ -8,6 +8,12 @@ import (
"io" "io"
) )
const (
SYSTEM_DIAGNOSTIC_ID = "DiagnosticId"
SYSTEM_RAN_UNIT_TESTS = "RanUnitTests"
SYSTEM_LAST_SECURITY_TIME = "LastSecurityTime"
)
type System struct { type System struct {
Name string `json:"name"` Name string `json:"name"`
Value string `json:"value"` Value string `json:"value"`

Просмотреть файл

@@ -16,17 +16,18 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/go-gorp/gorp"
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"io" "io"
sqltrace "log" sqltrace "log"
"math/rand" "math/rand"
"os" "os"
"strings" "strings"
"time" "time"
"github.com/go-gorp/gorp"
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
) )
type SqlStore struct { type SqlStore struct {
@@ -179,6 +180,17 @@ func (ss SqlStore) GetCurrentSchemaVersion() string {
return version return version
} }
func (ss SqlStore) MarkSystemRanUnitTests() {
if result := <-ss.System().Get(); result.Err == nil {
props := result.Data.(model.StringMap)
unitTests := props[model.SYSTEM_RAN_UNIT_TESTS]
if len(unitTests) == 0 {
systemTests := &model.System{Name: model.SYSTEM_RAN_UNIT_TESTS, Value: "1"}
<-ss.System().Save(systemTests)
}
}
}
func (ss SqlStore) DoesTableExist(tableName string) bool { func (ss SqlStore) DoesTableExist(tableName string) bool {
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES { if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
count, err := ss.GetMaster().SelectInt( count, err := ss.GetMaster().SelectInt(

Просмотреть файл

@@ -4,10 +4,11 @@
package store package store
import ( import (
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"strings" "strings"
"testing" "testing"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
) )
var store Store var store Store
@@ -16,6 +17,8 @@ func Setup() {
if store == nil { if store == nil {
utils.LoadConfig("config.json") utils.LoadConfig("config.json")
store = NewSqlStore() store = NewSqlStore()
store.MarkSystemRanUnitTests()
} }
} }

Просмотреть файл

@@ -38,6 +38,7 @@ type Store interface {
System() SystemStore System() SystemStore
Webhook() WebhookStore Webhook() WebhookStore
Preference() PreferenceStore Preference() PreferenceStore
MarkSystemRanUnitTests()
Close() Close()
} }

Просмотреть файл

@@ -19,6 +19,7 @@ const (
PROP_DIAGNOSTIC_OS = "os" PROP_DIAGNOSTIC_OS = "os"
PROP_DIAGNOSTIC_USER_COUNT = "uc" PROP_DIAGNOSTIC_USER_COUNT = "uc"
PROP_DIAGNOSTIC_ACTIVE_USER_COUNT = "auc" PROP_DIAGNOSTIC_ACTIVE_USER_COUNT = "auc"
PROP_DIAGNOSTIC_UNIT_TESTS = "ut"
) )
func SendDiagnostic(values url.Values) { func SendDiagnostic(values url.Values) {

Просмотреть файл

@@ -19,6 +19,7 @@
}, },
"globals": { "globals": {
"React": false, "React": false,
"ReactDOM": false,
"ReactBootstrap": false "ReactBootstrap": false
}, },
"rules": { "rules": {

Просмотреть файл

@@ -2,6 +2,7 @@
// See License.txt for license information. // See License.txt for license information.
var UserStore = require('../stores/user_store.jsx'); var UserStore = require('../stores/user_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var AsyncClient = require('../utils/async_client.jsx'); var AsyncClient = require('../utils/async_client.jsx');
var LoadingScreen = require('./loading_screen.jsx'); var LoadingScreen = require('./loading_screen.jsx');
var Utils = require('../utils/utils.jsx'); var Utils = require('../utils/utils.jsx');
@@ -14,8 +15,10 @@ export default class AccessHistoryModal extends React.Component {
this.handleMoreInfo = this.handleMoreInfo.bind(this); this.handleMoreInfo = this.handleMoreInfo.bind(this);
this.onHide = this.onHide.bind(this); this.onHide = this.onHide.bind(this);
this.onShow = this.onShow.bind(this); this.onShow = this.onShow.bind(this);
this.formatAuditInfo = this.formatAuditInfo.bind(this);
this.handleRevokedSession = this.handleRevokedSession.bind(this);
let state = this.getStateFromStoresForAudits(); const state = this.getStateFromStoresForAudits();
state.moreInfo = []; state.moreInfo = [];
this.state = state; this.state = state;
@@ -34,9 +37,9 @@ export default class AccessHistoryModal extends React.Component {
} }
componentDidMount() { componentDidMount() {
UserStore.addAuditsChangeListener(this.onAuditChange); UserStore.addAuditsChangeListener(this.onAuditChange);
$(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow); $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow);
$(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide);
} }
componentWillUnmount() { componentWillUnmount() {
UserStore.removeAuditsChangeListener(this.onAuditChange); UserStore.removeAuditsChangeListener(this.onAuditChange);
@@ -52,23 +55,269 @@ export default class AccessHistoryModal extends React.Component {
newMoreInfo[index] = true; newMoreInfo[index] = true;
this.setState({moreInfo: newMoreInfo}); this.setState({moreInfo: newMoreInfo});
} }
handleRevokedSession(sessionId) {
return 'The session with id ' + sessionId + ' was revoked';
}
formatAuditInfo(currentAudit) {
const currentActionURL = currentAudit.action.replace(/\/api\/v[1-9]/, '');
let currentAuditDesc = '';
if (currentActionURL.indexOf('/channels') === 0) {
const channelInfo = currentAudit.extra_info.split(' ');
const channelNameField = channelInfo[0].split('=');
let channelURL = '';
let channelObj;
let channelName = '';
if (channelNameField.indexOf('name') >= 0) {
channelURL = channelNameField[channelNameField.indexOf('name') + 1];
channelObj = ChannelStore.getByName(channelURL);
if (channelObj) {
channelName = channelObj.display_name;
} else {
channelName = channelURL;
}
}
switch (currentActionURL) {
case '/channels/create':
currentAuditDesc = 'Created the ' + channelName + ' channel/group';
break;
case '/channels/create_direct':
currentAuditDesc = 'Established a direct message channel with ' + Utils.getDirectTeammate(channelObj.id).username;
break;
case '/channels/update':
currentAuditDesc = 'Updated the ' + channelName + ' channel/group name';
break;
case '/channels/update_desc':
currentAuditDesc = 'Updated the ' + channelName + ' channel/group description';
break;
default:
let userIdField = [];
let userId = '';
let username = '';
if (channelInfo[1]) {
userIdField = channelInfo[1].split('=');
if (userIdField.indexOf('user_id') >= 0) {
userId = userIdField[userIdField.indexOf('user_id') + 1];
username = UserStore.getProfile(userId).username;
}
}
if (/\/channels\/[A-Za-z0-9]+\/delete/.test(currentActionURL)) {
currentAuditDesc = 'Deleted the channel/group with the URL ' + channelURL;
} else if (/\/channels\/[A-Za-z0-9]+\/add/.test(currentActionURL)) {
currentAuditDesc = 'Added ' + username + ' to the ' + channelName + ' channel/group';
} else if (/\/channels\/[A-Za-z0-9]+\/remove/.test(currentActionURL)) {
currentAuditDesc = 'Removed ' + username + ' from the ' + channelName + ' channel/group';
}
break;
}
} else if (currentActionURL.indexOf('/oauth') === 0) {
const oauthInfo = currentAudit.extra_info.split(' ');
switch (currentActionURL) {
case '/oauth/register':
const clientIdField = oauthInfo[0].split('=');
if (clientIdField[0] === 'client_id') {
currentAuditDesc = 'Attempted to register a new OAuth Application with ID ' + clientIdField[1];
}
break;
case '/oauth/allow':
if (oauthInfo[0] === 'attempt') {
currentAuditDesc = 'Attempted to allow a new OAuth service access';
} else if (oauthInfo[0] === 'success') {
currentAuditDesc = 'Successfully gave a new OAuth service access';
} else if (oauthInfo[0] === 'fail - redirect_uri did not match registered callback') {
currentAuditDesc = 'Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback';
}
break;
case '/oauth/access_token':
if (oauthInfo[0] === 'attempt') {
currentAuditDesc = 'Attempted to get an OAuth access token';
} else if (oauthInfo[0] === 'success') {
currentAuditDesc = 'Successfully added a new OAuth service';
} else {
const oauthTokenFailure = oauthInfo[0].split('-');
if (oauthTokenFailure[0].trim() === 'fail' && oauthTokenFailure[1]) {
currentAuditDesc = 'Failed to get an OAuth access token - ' + oauthTokenFailure[1].trim();
}
}
break;
default:
break;
}
} else if (currentActionURL.indexOf('/users') === 0) {
const userInfo = currentAudit.extra_info.split(' ');
switch (currentActionURL) {
case '/users/login':
if (userInfo[0] === 'attempt') {
currentAuditDesc = 'Attempted to login';
} else if (userInfo[0] === 'success') {
currentAuditDesc = 'Successfully logged in';
} else if (userInfo[0]) {
currentAuditDesc = 'FAILED login attempt';
}
break;
case '/users/revoke_session':
currentAuditDesc = this.handleRevokedSession(userInfo[0].split('=')[1]);
break;
case '/users/newimage':
currentAuditDesc = 'Updated your profile picture';
break;
case '/users/update':
currentAuditDesc = 'Updated the general settings of your account';
break;
case '/users/newpassword':
if (userInfo[0] === 'attempted') {
currentAuditDesc = 'Attempted to change password';
} else if (userInfo[0] === 'completed') {
currentAuditDesc = 'Successfully changed password';
} else if (userInfo[0] === 'failed - tried to update user password who was logged in through oauth') {
currentAuditDesc = 'Failed to change password - tried to update user password who was logged in through oauth';
}
break;
case '/users/update_roles':
const userRoles = userInfo[0].split('=')[1];
currentAuditDesc = 'Updated user role(s) to ';
if (userRoles.trim()) {
currentAuditDesc += userRoles;
} else {
currentAuditDesc += 'member';
}
break;
case '/users/update_active':
const updateType = userInfo[0].split('=')[0];
const updateField = userInfo[0].split('=')[1];
/* Either describes account activation/deactivation or a revoked session as part of an account deactivation */
if (updateType === 'active') {
if (updateField === 'true') {
currentAuditDesc = 'Account made active';
} else if (updateField === 'false') {
currentAuditDesc = 'Account made inactive';
}
const actingUserInfo = userInfo[1].split('=');
if (actingUserInfo[0] === 'session_user') {
const actingUser = UserStore.getProfile(actingUserInfo[1]);
const currentUser = UserStore.getCurrentUser();
if (currentUser && actingUser && (Utils.isAdmin(currentUser.roles) || Utils.isSystemAdmin(currentUser.roles))) {
currentAuditDesc += ' by ' + actingUser.username;
} else if (currentUser && actingUser) {
currentAuditDesc += ' by an admin';
}
}
} else if (updateType === 'session_id') {
currentAuditDesc = this.handleRevokedSession(updateField);
}
break;
case '/users/send_password_reset':
currentAuditDesc = 'Sent an email to ' + userInfo[0].split('=')[1] + ' to reset your password';
break;
case '/users/reset_password':
if (userInfo[0] === 'attempt') {
currentAuditDesc = 'Attempted to reset password';
} else if (userInfo[0] === 'success') {
currentAuditDesc = 'Successfully reset password';
}
break;
case '/users/update_notify':
currentAuditDesc = 'Updated your global notification settings';
break;
default:
break;
}
} else if (currentActionURL.indexOf('/hooks') === 0) {
const webhookInfo = currentAudit.extra_info.split(' ');
switch (currentActionURL) {
case '/hooks/incoming/create':
if (webhookInfo[0] === 'attempt') {
currentAuditDesc = 'Attempted to create a webhook';
} else if (webhookInfo[0] === 'success') {
currentAuditDesc = 'Successfully created a webhook';
} else if (webhookInfo[0] === 'fail - bad channel permissions') {
currentAuditDesc = 'Failed to create a webhook - bad channel permissions';
}
break;
case '/hooks/incoming/delete':
if (webhookInfo[0] === 'attempt') {
currentAuditDesc = 'Attempted to delete a webhook';
} else if (webhookInfo[0] === 'success') {
currentAuditDesc = 'Successfully deleted a webhook';
} else if (webhookInfo[0] === 'fail - inappropriate conditions') {
currentAuditDesc = 'Failed to delete a webhook - inappropriate conditions';
}
break;
default:
break;
}
} else {
switch (currentActionURL) {
case '/logout':
currentAuditDesc = 'Logged out of your account';
break;
case '/verify_email':
currentAuditDesc = 'Sucessfully verified your email address';
break;
default:
break;
}
}
/* If all else fails... */
if (!currentAuditDesc) {
/* Currently not called anywhere */
if (currentAudit.extra_info.indexOf('revoked_all=') >= 0) {
currentAuditDesc = 'Revoked all current sessions for the team';
} else {
let currentActionDesc = '';
if (currentActionURL && currentActionURL.lastIndexOf('/') !== -1) {
currentActionDesc = currentActionURL.substring(currentActionURL.lastIndexOf('/') + 1).replace('_', ' ');
currentActionDesc = Utils.toTitleCase(currentActionDesc);
}
let currentExtraInfoDesc = '';
if (currentAudit.extra_info) {
currentExtraInfoDesc = currentAudit.extra_info;
if (currentExtraInfoDesc.indexOf('=') !== -1) {
currentExtraInfoDesc = currentExtraInfoDesc.substring(currentExtraInfoDesc.indexOf('=') + 1);
}
}
currentAuditDesc = currentActionDesc + ' ' + currentExtraInfoDesc;
}
}
const currentDate = new Date(currentAudit.create_at);
const currentAuditInfo = currentDate.toDateString() + ' - ' + currentDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute: '2-digit'}) + ' | ' + currentAuditDesc;
return currentAuditInfo;
}
render() { render() {
var accessList = []; var accessList = [];
var currentHistoryDate = null;
for (var i = 0; i < this.state.audits.length; i++) { for (var i = 0; i < this.state.audits.length; i++) {
var currentAudit = this.state.audits[i]; const currentAudit = this.state.audits[i];
var newHistoryDate = new Date(currentAudit.create_at); const currentAuditInfo = this.formatAuditInfo(currentAudit);
var newDate = null;
if (!currentHistoryDate || currentHistoryDate.toLocaleDateString() !== newHistoryDate.toLocaleDateString()) {
currentHistoryDate = newHistoryDate;
newDate = (<div> {currentHistoryDate.toDateString()} </div>);
}
if (!currentAudit.session_id && currentAudit.action.search('/users/login') !== -1) {
currentAudit.session_id = 'N/A (Login attempt)';
}
var moreInfo = ( var moreInfo = (
<a <a
@@ -76,15 +325,27 @@ export default class AccessHistoryModal extends React.Component {
className='theme' className='theme'
onClick={this.handleMoreInfo.bind(this, i)} onClick={this.handleMoreInfo.bind(this, i)}
> >
More info {'More info'}
</a> </a>
); );
if (this.state.moreInfo[i]) { if (this.state.moreInfo[i]) {
if (!currentAudit.session_id) {
currentAudit.session_id = 'N/A';
if (currentAudit.action.search('/users/login') >= 0) {
if (currentAudit.extra_info === 'attempt') {
currentAudit.session_id += ' (Login attempt)';
} else {
currentAudit.session_id += ' (Login failure)';
}
}
}
moreInfo = ( moreInfo = (
<div> <div>
<div>{'IP: ' + currentAudit.ip_address}</div>
<div>{'Session ID: ' + currentAudit.session_id}</div> <div>{'Session ID: ' + currentAudit.session_id}</div>
<div>{'URL: ' + currentAudit.action.replace(/\/api\/v[1-9]/, '')}</div>
</div> </div>
); );
} }
@@ -99,11 +360,9 @@ export default class AccessHistoryModal extends React.Component {
key={'accessHistoryEntryKey' + i} key={'accessHistoryEntryKey' + i}
className='access-history__table' className='access-history__table'
> >
<div className='access__date'>{newDate}</div>
<div className='access__report'> <div className='access__report'>
<div className='report__time'>{newHistoryDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute: '2-digit'})}</div> <div className='report__time'>{currentAuditInfo}</div>
<div className='report__info'> <div className='report__info'>
<div>{'IP: ' + currentAudit.ip_address}</div>
{moreInfo} {moreInfo}
</div> </div>
{divider} {divider}
@@ -138,13 +397,13 @@ export default class AccessHistoryModal extends React.Component {
data-dismiss='modal' data-dismiss='modal'
aria-label='Close' aria-label='Close'
> >
<span aria-hidden='true'>&times;</span> <span aria-hidden='true'>{'×'}</span>
</button> </button>
<h4 <h4
className='modal-title' className='modal-title'
id='myModalLabel' id='myModalLabel'
> >
Access History {'Access History'}
</h4> </h4>
</div> </div>
<div <div

Просмотреть файл

@@ -56,9 +56,9 @@ export default class ActivityLogModal extends React.Component {
} }
componentDidMount() { componentDidMount() {
UserStore.addSessionsChangeListener(this.onListenerChange); UserStore.addSessionsChangeListener(this.onListenerChange);
$(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow); $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow);
$(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide);
} }
componentWillUnmount() { componentWillUnmount() {
UserStore.removeSessionsChangeListener(this.onListenerChange); UserStore.removeSessionsChangeListener(this.onListenerChange);

Просмотреть файл

@@ -27,7 +27,7 @@ export default class AdminNavbarDropdown extends React.Component {
} }
componentDidMount() { componentDidMount() {
$(React.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => { $(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => {
this.blockToggle = true; this.blockToggle = true;
setTimeout(() => { setTimeout(() => {
this.blockToggle = false; this.blockToggle = false;
@@ -36,7 +36,7 @@ export default class AdminNavbarDropdown extends React.Component {
} }
componentWillUnmount() { componentWillUnmount() {
$(React.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown'); $(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown');
} }
render() { render() {

Просмотреть файл

@@ -100,6 +100,7 @@ export default class AdminSidebar extends React.Component {
className='menu-icon--right menu__close' className='menu-icon--right menu__close'
onClick={this.removeTeam.bind(this, team.id)} onClick={this.removeTeam.bind(this, team.id)}
style={{cursor: 'pointer'}} style={{cursor: 'pointer'}}
title='Remove team from sidebar menu'
> >
{'x'} {'x'}
</span> </span>
@@ -233,7 +234,10 @@ export default class AdminSidebar extends React.Component {
href='#' href='#'
onClick={this.showTeamSelect} onClick={this.showTeamSelect}
> >
<i className='fa fa-plus'></i> <i
className='fa fa-plus'
title='Add team to sidebar menu'
></i>
</a> </a>
</span> </span>
</h4> </h4>

Просмотреть файл

@@ -41,28 +41,28 @@ export default class EmailSettings extends React.Component {
buildConfig() { buildConfig() {
var config = this.props.config; var config = this.props.config;
config.EmailSettings.EnableSignUpWithEmail = React.findDOMNode(this.refs.allowSignUpWithEmail).checked; config.EmailSettings.EnableSignUpWithEmail = ReactDOM.findDOMNode(this.refs.allowSignUpWithEmail).checked;
config.EmailSettings.SendEmailNotifications = React.findDOMNode(this.refs.sendEmailNotifications).checked; config.EmailSettings.SendEmailNotifications = ReactDOM.findDOMNode(this.refs.sendEmailNotifications).checked;
config.EmailSettings.RequireEmailVerification = React.findDOMNode(this.refs.requireEmailVerification).checked; config.EmailSettings.RequireEmailVerification = ReactDOM.findDOMNode(this.refs.requireEmailVerification).checked;
config.EmailSettings.SendEmailNotifications = React.findDOMNode(this.refs.sendEmailNotifications).checked; config.EmailSettings.SendEmailNotifications = ReactDOM.findDOMNode(this.refs.sendEmailNotifications).checked;
config.EmailSettings.FeedbackName = React.findDOMNode(this.refs.feedbackName).value.trim(); config.EmailSettings.FeedbackName = ReactDOM.findDOMNode(this.refs.feedbackName).value.trim();
config.EmailSettings.FeedbackEmail = React.findDOMNode(this.refs.feedbackEmail).value.trim(); config.EmailSettings.FeedbackEmail = ReactDOM.findDOMNode(this.refs.feedbackEmail).value.trim();
config.EmailSettings.SMTPServer = React.findDOMNode(this.refs.SMTPServer).value.trim(); config.EmailSettings.SMTPServer = ReactDOM.findDOMNode(this.refs.SMTPServer).value.trim();
config.EmailSettings.SMTPPort = React.findDOMNode(this.refs.SMTPPort).value.trim(); config.EmailSettings.SMTPPort = ReactDOM.findDOMNode(this.refs.SMTPPort).value.trim();
config.EmailSettings.SMTPUsername = React.findDOMNode(this.refs.SMTPUsername).value.trim(); config.EmailSettings.SMTPUsername = ReactDOM.findDOMNode(this.refs.SMTPUsername).value.trim();
config.EmailSettings.SMTPPassword = React.findDOMNode(this.refs.SMTPPassword).value.trim(); config.EmailSettings.SMTPPassword = ReactDOM.findDOMNode(this.refs.SMTPPassword).value.trim();
config.EmailSettings.ConnectionSecurity = React.findDOMNode(this.refs.ConnectionSecurity).value.trim(); config.EmailSettings.ConnectionSecurity = ReactDOM.findDOMNode(this.refs.ConnectionSecurity).value.trim();
config.EmailSettings.InviteSalt = React.findDOMNode(this.refs.InviteSalt).value.trim(); config.EmailSettings.InviteSalt = ReactDOM.findDOMNode(this.refs.InviteSalt).value.trim();
if (config.EmailSettings.InviteSalt === '') { if (config.EmailSettings.InviteSalt === '') {
config.EmailSettings.InviteSalt = crypto.randomBytes(256).toString('base64').substring(0, 32); config.EmailSettings.InviteSalt = crypto.randomBytes(256).toString('base64').substring(0, 32);
React.findDOMNode(this.refs.InviteSalt).value = config.EmailSettings.InviteSalt; ReactDOM.findDOMNode(this.refs.InviteSalt).value = config.EmailSettings.InviteSalt;
} }
config.EmailSettings.PasswordResetSalt = React.findDOMNode(this.refs.PasswordResetSalt).value.trim(); config.EmailSettings.PasswordResetSalt = ReactDOM.findDOMNode(this.refs.PasswordResetSalt).value.trim();
if (config.EmailSettings.PasswordResetSalt === '') { if (config.EmailSettings.PasswordResetSalt === '') {
config.EmailSettings.PasswordResetSalt = crypto.randomBytes(256).toString('base64').substring(0, 32); config.EmailSettings.PasswordResetSalt = crypto.randomBytes(256).toString('base64').substring(0, 32);
React.findDOMNode(this.refs.PasswordResetSalt).value = config.EmailSettings.PasswordResetSalt; ReactDOM.findDOMNode(this.refs.PasswordResetSalt).value = config.EmailSettings.PasswordResetSalt;
} }
return config; return config;
@@ -70,14 +70,14 @@ export default class EmailSettings extends React.Component {
handleGenerateInvite(e) { handleGenerateInvite(e) {
e.preventDefault(); e.preventDefault();
React.findDOMNode(this.refs.InviteSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); ReactDOM.findDOMNode(this.refs.InviteSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32);
var s = {saveNeeded: true, serverError: this.state.serverError}; var s = {saveNeeded: true, serverError: this.state.serverError};
this.setState(s); this.setState(s);
} }
handleGenerateReset(e) { handleGenerateReset(e) {
e.preventDefault(); e.preventDefault();
React.findDOMNode(this.refs.PasswordResetSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); ReactDOM.findDOMNode(this.refs.PasswordResetSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32);
var s = {saveNeeded: true, serverError: this.state.serverError}; var s = {saveNeeded: true, serverError: this.state.serverError};
this.setState(s); this.setState(s);
} }
@@ -447,7 +447,7 @@ export default class EmailSettings extends React.Component {
</div> </div>
<div className='help-text'> <div className='help-text'>
<button <button
className='btn' className='btn btn-default'
onClick={this.handleTestConnection} onClick={this.handleTestConnection}
disabled={!this.state.sendEmailNotifications} disabled={!this.state.sendEmailNotifications}
id='connection-button' id='connection-button'
@@ -482,7 +482,7 @@ export default class EmailSettings extends React.Component {
<p className='help-text'>{'32-character salt added to signing of email invites. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p> <p className='help-text'>{'32-character salt added to signing of email invites. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p>
<div className='help-text'> <div className='help-text'>
<button <button
className='btn' className='btn btn-default'
onClick={this.handleGenerateInvite} onClick={this.handleGenerateInvite}
disabled={!this.state.sendEmailNotifications} disabled={!this.state.sendEmailNotifications}
> >
@@ -513,7 +513,7 @@ export default class EmailSettings extends React.Component {
<p className='help-text'>{'32-character salt added to signing of password reset emails. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p> <p className='help-text'>{'32-character salt added to signing of password reset emails. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p>
<div className='help-text'> <div className='help-text'>
<button <button
className='btn' className='btn btn-default'
onClick={this.handleGenerateReset} onClick={this.handleGenerateReset}
disabled={!this.state.sendEmailNotifications} disabled={!this.state.sendEmailNotifications}
> >

Просмотреть файл

@@ -37,12 +37,12 @@ export default class GitLabSettings extends React.Component {
$('#save-button').button('loading'); $('#save-button').button('loading');
var config = this.props.config; var config = this.props.config;
config.GitLabSettings.Enable = React.findDOMNode(this.refs.Enable).checked; config.GitLabSettings.Enable = ReactDOM.findDOMNode(this.refs.Enable).checked;
config.GitLabSettings.Secret = React.findDOMNode(this.refs.Secret).value.trim(); config.GitLabSettings.Secret = ReactDOM.findDOMNode(this.refs.Secret).value.trim();
config.GitLabSettings.Id = React.findDOMNode(this.refs.Id).value.trim(); config.GitLabSettings.Id = ReactDOM.findDOMNode(this.refs.Id).value.trim();
config.GitLabSettings.AuthEndpoint = React.findDOMNode(this.refs.AuthEndpoint).value.trim(); config.GitLabSettings.AuthEndpoint = ReactDOM.findDOMNode(this.refs.AuthEndpoint).value.trim();
config.GitLabSettings.TokenEndpoint = React.findDOMNode(this.refs.TokenEndpoint).value.trim(); config.GitLabSettings.TokenEndpoint = ReactDOM.findDOMNode(this.refs.TokenEndpoint).value.trim();
config.GitLabSettings.UserApiEndpoint = React.findDOMNode(this.refs.UserApiEndpoint).value.trim(); config.GitLabSettings.UserApiEndpoint = ReactDOM.findDOMNode(this.refs.UserApiEndpoint).value.trim();
Client.saveConfig( Client.saveConfig(
config, config,
@@ -116,12 +116,14 @@ export default class GitLabSettings extends React.Component {
<p className='help-text'> <p className='help-text'>
{'When true, Mattermost allows team creation and account signup using GitLab OAuth.'} <br/> {'When true, Mattermost allows team creation and account signup using GitLab OAuth.'} <br/>
</p> </p>
<ol className='help-text'> <div className='help-text'>
<li>{'Log in to your GitLab account and go to Applications -> Profile Settings.'}</li> <ol>
<li>{'Enter Redirect URIs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete". '}</li> <li>{'Log in to your GitLab account and go to Applications -> Profile Settings.'}</li>
<li>{'Then use "Secret" and "Id" fields from GitLab to complete the options below.'}</li> <li>{'Enter Redirect URIs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete". '}</li>
<li>{'Complete the Endpoint URLs below. '}</li> <li>{'Then use "Secret" and "Id" fields from GitLab to complete the options below.'}</li>
</ol> <li>{'Complete the Endpoint URLs below. '}</li>
</ol>
</div>
</div> </div>
</div> </div>
@@ -258,7 +260,7 @@ export default class GitLabSettings extends React.Component {
} }
//config.GitLabSettings.Scope = React.findDOMNode(this.refs.Scope).value.trim(); //config.GitLabSettings.Scope = ReactDOM.findDOMNode(this.refs.Scope).value.trim();
// <div className='form-group'> // <div className='form-group'>
// <label // <label
// className='control-label col-sm-4' // className='control-label col-sm-4'

Просмотреть файл

@@ -24,7 +24,7 @@ export default class FileSettings extends React.Component {
var s = {saveNeeded: true, serverError: this.state.serverError}; var s = {saveNeeded: true, serverError: this.state.serverError};
if (action === 'DriverName') { if (action === 'DriverName') {
s.DriverName = React.findDOMNode(this.refs.DriverName).value; s.DriverName = ReactDOM.findDOMNode(this.refs.DriverName).value;
} }
this.setState(s); this.setState(s);
@@ -32,7 +32,7 @@ export default class FileSettings extends React.Component {
handleGenerate(e) { handleGenerate(e) {
e.preventDefault(); e.preventDefault();
React.findDOMNode(this.refs.PublicLinkSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32);
var s = {saveNeeded: true, serverError: this.state.serverError}; var s = {saveNeeded: true, serverError: this.state.serverError};
this.setState(s); this.setState(s);
} }
@@ -42,62 +42,62 @@ export default class FileSettings extends React.Component {
$('#save-button').button('loading'); $('#save-button').button('loading');
var config = this.props.config; var config = this.props.config;
config.FileSettings.DriverName = React.findDOMNode(this.refs.DriverName).value; config.FileSettings.DriverName = ReactDOM.findDOMNode(this.refs.DriverName).value;
config.FileSettings.Directory = React.findDOMNode(this.refs.Directory).value; config.FileSettings.Directory = ReactDOM.findDOMNode(this.refs.Directory).value;
config.FileSettings.AmazonS3AccessKeyId = React.findDOMNode(this.refs.AmazonS3AccessKeyId).value; config.FileSettings.AmazonS3AccessKeyId = ReactDOM.findDOMNode(this.refs.AmazonS3AccessKeyId).value;
config.FileSettings.AmazonS3SecretAccessKey = React.findDOMNode(this.refs.AmazonS3SecretAccessKey).value; config.FileSettings.AmazonS3SecretAccessKey = ReactDOM.findDOMNode(this.refs.AmazonS3SecretAccessKey).value;
config.FileSettings.AmazonS3Bucket = React.findDOMNode(this.refs.AmazonS3Bucket).value; config.FileSettings.AmazonS3Bucket = ReactDOM.findDOMNode(this.refs.AmazonS3Bucket).value;
config.FileSettings.AmazonS3Region = React.findDOMNode(this.refs.AmazonS3Region).value; config.FileSettings.AmazonS3Region = ReactDOM.findDOMNode(this.refs.AmazonS3Region).value;
config.FileSettings.EnablePublicLink = React.findDOMNode(this.refs.EnablePublicLink).checked; config.FileSettings.EnablePublicLink = ReactDOM.findDOMNode(this.refs.EnablePublicLink).checked;
config.FileSettings.PublicLinkSalt = React.findDOMNode(this.refs.PublicLinkSalt).value.trim(); config.FileSettings.PublicLinkSalt = ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value.trim();
if (config.FileSettings.PublicLinkSalt === '') { if (config.FileSettings.PublicLinkSalt === '') {
config.FileSettings.PublicLinkSalt = crypto.randomBytes(256).toString('base64').substring(0, 32); config.FileSettings.PublicLinkSalt = crypto.randomBytes(256).toString('base64').substring(0, 32);
React.findDOMNode(this.refs.PublicLinkSalt).value = config.FileSettings.PublicLinkSalt; ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value = config.FileSettings.PublicLinkSalt;
} }
var thumbnailWidth = 120; var thumbnailWidth = 120;
if (!isNaN(parseInt(React.findDOMNode(this.refs.ThumbnailWidth).value, 10))) { if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailWidth).value, 10))) {
thumbnailWidth = parseInt(React.findDOMNode(this.refs.ThumbnailWidth).value, 10); thumbnailWidth = parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailWidth).value, 10);
} }
config.FileSettings.ThumbnailWidth = thumbnailWidth; config.FileSettings.ThumbnailWidth = thumbnailWidth;
React.findDOMNode(this.refs.ThumbnailWidth).value = thumbnailWidth; ReactDOM.findDOMNode(this.refs.ThumbnailWidth).value = thumbnailWidth;
var thumbnailHeight = 100; var thumbnailHeight = 100;
if (!isNaN(parseInt(React.findDOMNode(this.refs.ThumbnailHeight).value, 10))) { if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailHeight).value, 10))) {
thumbnailHeight = parseInt(React.findDOMNode(this.refs.ThumbnailHeight).value, 10); thumbnailHeight = parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailHeight).value, 10);
} }
config.FileSettings.ThumbnailHeight = thumbnailHeight; config.FileSettings.ThumbnailHeight = thumbnailHeight;
React.findDOMNode(this.refs.ThumbnailHeight).value = thumbnailHeight; ReactDOM.findDOMNode(this.refs.ThumbnailHeight).value = thumbnailHeight;
var previewWidth = 1024; var previewWidth = 1024;
if (!isNaN(parseInt(React.findDOMNode(this.refs.PreviewWidth).value, 10))) { if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.PreviewWidth).value, 10))) {
previewWidth = parseInt(React.findDOMNode(this.refs.PreviewWidth).value, 10); previewWidth = parseInt(ReactDOM.findDOMNode(this.refs.PreviewWidth).value, 10);
} }
config.FileSettings.PreviewWidth = previewWidth; config.FileSettings.PreviewWidth = previewWidth;
React.findDOMNode(this.refs.PreviewWidth).value = previewWidth; ReactDOM.findDOMNode(this.refs.PreviewWidth).value = previewWidth;
var previewHeight = 0; var previewHeight = 0;
if (!isNaN(parseInt(React.findDOMNode(this.refs.PreviewHeight).value, 10))) { if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.PreviewHeight).value, 10))) {
previewHeight = parseInt(React.findDOMNode(this.refs.PreviewHeight).value, 10); previewHeight = parseInt(ReactDOM.findDOMNode(this.refs.PreviewHeight).value, 10);
} }
config.FileSettings.PreviewHeight = previewHeight; config.FileSettings.PreviewHeight = previewHeight;
React.findDOMNode(this.refs.PreviewHeight).value = previewHeight; ReactDOM.findDOMNode(this.refs.PreviewHeight).value = previewHeight;
var profileWidth = 128; var profileWidth = 128;
if (!isNaN(parseInt(React.findDOMNode(this.refs.ProfileWidth).value, 10))) { if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ProfileWidth).value, 10))) {
profileWidth = parseInt(React.findDOMNode(this.refs.ProfileWidth).value, 10); profileWidth = parseInt(ReactDOM.findDOMNode(this.refs.ProfileWidth).value, 10);
} }
config.FileSettings.ProfileWidth = profileWidth; config.FileSettings.ProfileWidth = profileWidth;
React.findDOMNode(this.refs.ProfileWidth).value = profileWidth; ReactDOM.findDOMNode(this.refs.ProfileWidth).value = profileWidth;
var profileHeight = 128; var profileHeight = 128;
if (!isNaN(parseInt(React.findDOMNode(this.refs.ProfileHeight).value, 10))) { if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ProfileHeight).value, 10))) {
profileHeight = parseInt(React.findDOMNode(this.refs.ProfileHeight).value, 10); profileHeight = parseInt(ReactDOM.findDOMNode(this.refs.ProfileHeight).value, 10);
} }
config.FileSettings.ProfileHeight = profileHeight; config.FileSettings.ProfileHeight = profileHeight;
React.findDOMNode(this.refs.ProfileHeight).value = profileHeight; ReactDOM.findDOMNode(this.refs.ProfileHeight).value = profileHeight;
Client.saveConfig( Client.saveConfig(
config, config,

Просмотреть файл

@@ -46,12 +46,12 @@ export default class LogSettings extends React.Component {
$('#save-button').button('loading'); $('#save-button').button('loading');
var config = this.props.config; var config = this.props.config;
config.LogSettings.EnableConsole = React.findDOMNode(this.refs.consoleEnable).checked; config.LogSettings.EnableConsole = ReactDOM.findDOMNode(this.refs.consoleEnable).checked;
config.LogSettings.ConsoleLevel = React.findDOMNode(this.refs.consoleLevel).value; config.LogSettings.ConsoleLevel = ReactDOM.findDOMNode(this.refs.consoleLevel).value;
config.LogSettings.EnableFile = React.findDOMNode(this.refs.fileEnable).checked; config.LogSettings.EnableFile = ReactDOM.findDOMNode(this.refs.fileEnable).checked;
config.LogSettings.FileLevel = React.findDOMNode(this.refs.fileLevel).value; config.LogSettings.FileLevel = ReactDOM.findDOMNode(this.refs.fileLevel).value;
config.LogSettings.FileLocation = React.findDOMNode(this.refs.fileLocation).value.trim(); config.LogSettings.FileLocation = ReactDOM.findDOMNode(this.refs.fileLocation).value.trim();
config.LogSettings.FileFormat = React.findDOMNode(this.refs.fileFormat).value.trim(); config.LogSettings.FileFormat = ReactDOM.findDOMNode(this.refs.fileFormat).value.trim();
Client.saveConfig( Client.saveConfig(
config, config,

Просмотреть файл

@@ -28,8 +28,8 @@ export default class PrivacySettings extends React.Component {
$('#save-button').button('loading'); $('#save-button').button('loading');
var config = this.props.config; var config = this.props.config;
config.PrivacySettings.ShowEmailAddress = React.findDOMNode(this.refs.ShowEmailAddress).checked; config.PrivacySettings.ShowEmailAddress = ReactDOM.findDOMNode(this.refs.ShowEmailAddress).checked;
config.PrivacySettings.ShowFullName = React.findDOMNode(this.refs.ShowFullName).checked; config.PrivacySettings.ShowFullName = ReactDOM.findDOMNode(this.refs.ShowFullName).checked;
Client.saveConfig( Client.saveConfig(
config, config,

Просмотреть файл

@@ -46,23 +46,23 @@ export default class RateSettings extends React.Component {
$('#save-button').button('loading'); $('#save-button').button('loading');
var config = this.props.config; var config = this.props.config;
config.RateLimitSettings.EnableRateLimiter = React.findDOMNode(this.refs.EnableRateLimiter).checked; config.RateLimitSettings.EnableRateLimiter = ReactDOM.findDOMNode(this.refs.EnableRateLimiter).checked;
config.RateLimitSettings.VaryByRemoteAddr = React.findDOMNode(this.refs.VaryByRemoteAddr).checked; config.RateLimitSettings.VaryByRemoteAddr = ReactDOM.findDOMNode(this.refs.VaryByRemoteAddr).checked;
config.RateLimitSettings.VaryByHeader = React.findDOMNode(this.refs.VaryByHeader).value.trim(); config.RateLimitSettings.VaryByHeader = ReactDOM.findDOMNode(this.refs.VaryByHeader).value.trim();
var PerSec = 10; var PerSec = 10;
if (!isNaN(parseInt(React.findDOMNode(this.refs.PerSec).value, 10))) { if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.PerSec).value, 10))) {
PerSec = parseInt(React.findDOMNode(this.refs.PerSec).value, 10); PerSec = parseInt(ReactDOM.findDOMNode(this.refs.PerSec).value, 10);
} }
config.RateLimitSettings.PerSec = PerSec; config.RateLimitSettings.PerSec = PerSec;
React.findDOMNode(this.refs.PerSec).value = PerSec; ReactDOM.findDOMNode(this.refs.PerSec).value = PerSec;
var MemoryStoreSize = 10000; var MemoryStoreSize = 10000;
if (!isNaN(parseInt(React.findDOMNode(this.refs.MemoryStoreSize).value, 10))) { if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MemoryStoreSize).value, 10))) {
MemoryStoreSize = parseInt(React.findDOMNode(this.refs.MemoryStoreSize).value, 10); MemoryStoreSize = parseInt(ReactDOM.findDOMNode(this.refs.MemoryStoreSize).value, 10);
} }
config.RateLimitSettings.MemoryStoreSize = MemoryStoreSize; config.RateLimitSettings.MemoryStoreSize = MemoryStoreSize;
React.findDOMNode(this.refs.MemoryStoreSize).value = MemoryStoreSize; ReactDOM.findDOMNode(this.refs.MemoryStoreSize).value = MemoryStoreSize;
Client.saveConfig( Client.saveConfig(
config, config,

Просмотреть файл

@@ -18,7 +18,7 @@ export default class ResetPasswordModal extends React.Component {
doSubmit(e) { doSubmit(e) {
e.preventDefault(); e.preventDefault();
var password = React.findDOMNode(this.refs.password).value; var password = ReactDOM.findDOMNode(this.refs.password).value;
if (!password || password.length < 5) { if (!password || password.length < 5) {
this.setState({serverError: 'Please enter at least 5 characters.'}); this.setState({serverError: 'Please enter at least 5 characters.'});
@@ -34,7 +34,7 @@ export default class ResetPasswordModal extends React.Component {
Client.resetPassword(data, Client.resetPassword(data,
() => { () => {
this.props.onModalSubmit(React.findDOMNode(this.refs.password).value); this.props.onModalSubmit(ReactDOM.findDOMNode(this.refs.password).value);
}, },
(err) => { (err) => {
this.setState({serverError: err.message}); this.setState({serverError: err.message});

Просмотреть файл

@@ -13,7 +13,7 @@ export default class SelectTeamModal extends React.Component {
doSubmit(e) { doSubmit(e) {
e.preventDefault(); e.preventDefault();
this.props.onModalSubmit(React.findDOMNode(this.refs.team).value); this.props.onModalSubmit(ReactDOM.findDOMNode(this.refs.team).value);
} }
doCancel() { doCancel() {
this.props.onModalDismissed(); this.props.onModalDismissed();

Просмотреть файл

@@ -27,28 +27,28 @@ export default class ServiceSettings extends React.Component {
$('#save-button').button('loading'); $('#save-button').button('loading');
var config = this.props.config; var config = this.props.config;
config.ServiceSettings.ListenAddress = React.findDOMNode(this.refs.ListenAddress).value.trim(); config.ServiceSettings.ListenAddress = ReactDOM.findDOMNode(this.refs.ListenAddress).value.trim();
if (config.ServiceSettings.ListenAddress === '') { if (config.ServiceSettings.ListenAddress === '') {
config.ServiceSettings.ListenAddress = ':8065'; config.ServiceSettings.ListenAddress = ':8065';
React.findDOMNode(this.refs.ListenAddress).value = config.ServiceSettings.ListenAddress; ReactDOM.findDOMNode(this.refs.ListenAddress).value = config.ServiceSettings.ListenAddress;
} }
config.ServiceSettings.SegmentDeveloperKey = React.findDOMNode(this.refs.SegmentDeveloperKey).value.trim(); config.ServiceSettings.SegmentDeveloperKey = ReactDOM.findDOMNode(this.refs.SegmentDeveloperKey).value.trim();
config.ServiceSettings.GoogleDeveloperKey = React.findDOMNode(this.refs.GoogleDeveloperKey).value.trim(); config.ServiceSettings.GoogleDeveloperKey = ReactDOM.findDOMNode(this.refs.GoogleDeveloperKey).value.trim();
config.ServiceSettings.EnableIncomingWebhooks = React.findDOMNode(this.refs.EnableIncomingWebhooks).checked; config.ServiceSettings.EnableIncomingWebhooks = ReactDOM.findDOMNode(this.refs.EnableIncomingWebhooks).checked;
config.ServiceSettings.EnablePostUsernameOverride = React.findDOMNode(this.refs.EnablePostUsernameOverride).checked; config.ServiceSettings.EnablePostUsernameOverride = ReactDOM.findDOMNode(this.refs.EnablePostUsernameOverride).checked;
config.ServiceSettings.EnablePostIconOverride = React.findDOMNode(this.refs.EnablePostIconOverride).checked; config.ServiceSettings.EnablePostIconOverride = ReactDOM.findDOMNode(this.refs.EnablePostIconOverride).checked;
config.ServiceSettings.EnableTesting = React.findDOMNode(this.refs.EnableTesting).checked; config.ServiceSettings.EnableTesting = ReactDOM.findDOMNode(this.refs.EnableTesting).checked;
config.ServiceSettings.EnableSecurityFixAlert = React.findDOMNode(this.refs.EnableSecurityFixAlert).checked; config.ServiceSettings.EnableSecurityFixAlert = ReactDOM.findDOMNode(this.refs.EnableSecurityFixAlert).checked;
//config.ServiceSettings.EnableOAuthServiceProvider = React.findDOMNode(this.refs.EnableOAuthServiceProvider).checked; //config.ServiceSettings.EnableOAuthServiceProvider = ReactDOM.findDOMNode(this.refs.EnableOAuthServiceProvider).checked;
var MaximumLoginAttempts = 10; var MaximumLoginAttempts = 10;
if (!isNaN(parseInt(React.findDOMNode(this.refs.MaximumLoginAttempts).value, 10))) { if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaximumLoginAttempts).value, 10))) {
MaximumLoginAttempts = parseInt(React.findDOMNode(this.refs.MaximumLoginAttempts).value, 10); MaximumLoginAttempts = parseInt(ReactDOM.findDOMNode(this.refs.MaximumLoginAttempts).value, 10);
} }
config.ServiceSettings.MaximumLoginAttempts = MaximumLoginAttempts; config.ServiceSettings.MaximumLoginAttempts = MaximumLoginAttempts;
React.findDOMNode(this.refs.MaximumLoginAttempts).value = MaximumLoginAttempts; ReactDOM.findDOMNode(this.refs.MaximumLoginAttempts).value = MaximumLoginAttempts;
Client.saveConfig( Client.saveConfig(
config, config,

Просмотреть файл

@@ -29,27 +29,27 @@ export default class SqlSettings extends React.Component {
$('#save-button').button('loading'); $('#save-button').button('loading');
var config = this.props.config; var config = this.props.config;
config.SqlSettings.Trace = React.findDOMNode(this.refs.Trace).checked; config.SqlSettings.Trace = ReactDOM.findDOMNode(this.refs.Trace).checked;
config.SqlSettings.AtRestEncryptKey = React.findDOMNode(this.refs.AtRestEncryptKey).value.trim(); config.SqlSettings.AtRestEncryptKey = ReactDOM.findDOMNode(this.refs.AtRestEncryptKey).value.trim();
if (config.SqlSettings.AtRestEncryptKey === '') { if (config.SqlSettings.AtRestEncryptKey === '') {
config.SqlSettings.AtRestEncryptKey = crypto.randomBytes(256).toString('base64').substring(0, 32); config.SqlSettings.AtRestEncryptKey = crypto.randomBytes(256).toString('base64').substring(0, 32);
React.findDOMNode(this.refs.AtRestEncryptKey).value = config.SqlSettings.AtRestEncryptKey; ReactDOM.findDOMNode(this.refs.AtRestEncryptKey).value = config.SqlSettings.AtRestEncryptKey;
} }
var MaxOpenConns = 10; var MaxOpenConns = 10;
if (!isNaN(parseInt(React.findDOMNode(this.refs.MaxOpenConns).value, 10))) { if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaxOpenConns).value, 10))) {
MaxOpenConns = parseInt(React.findDOMNode(this.refs.MaxOpenConns).value, 10); MaxOpenConns = parseInt(ReactDOM.findDOMNode(this.refs.MaxOpenConns).value, 10);
} }
config.SqlSettings.MaxOpenConns = MaxOpenConns; config.SqlSettings.MaxOpenConns = MaxOpenConns;
React.findDOMNode(this.refs.MaxOpenConns).value = MaxOpenConns; ReactDOM.findDOMNode(this.refs.MaxOpenConns).value = MaxOpenConns;
var MaxIdleConns = 10; var MaxIdleConns = 10;
if (!isNaN(parseInt(React.findDOMNode(this.refs.MaxIdleConns).value, 10))) { if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaxIdleConns).value, 10))) {
MaxIdleConns = parseInt(React.findDOMNode(this.refs.MaxIdleConns).value, 10); MaxIdleConns = parseInt(ReactDOM.findDOMNode(this.refs.MaxIdleConns).value, 10);
} }
config.SqlSettings.MaxIdleConns = MaxIdleConns; config.SqlSettings.MaxIdleConns = MaxIdleConns;
React.findDOMNode(this.refs.MaxIdleConns).value = MaxIdleConns; ReactDOM.findDOMNode(this.refs.MaxIdleConns).value = MaxIdleConns;
Client.saveConfig( Client.saveConfig(
config, config,
@@ -79,7 +79,7 @@ export default class SqlSettings extends React.Component {
return; return;
} }
React.findDOMNode(this.refs.AtRestEncryptKey).value = crypto.randomBytes(256).toString('base64').substring(0, 32); ReactDOM.findDOMNode(this.refs.AtRestEncryptKey).value = crypto.randomBytes(256).toString('base64').substring(0, 32);
var s = {saveNeeded: true, serverError: this.state.serverError}; var s = {saveNeeded: true, serverError: this.state.serverError};
this.setState(s); this.setState(s);
} }
@@ -220,7 +220,7 @@ export default class SqlSettings extends React.Component {
<p className='help-text'>{'32-character salt available to encrypt and decrypt sensitive fields in database.'}</p> <p className='help-text'>{'32-character salt available to encrypt and decrypt sensitive fields in database.'}</p>
<div className='help-text'> <div className='help-text'>
<button <button
className='help-link' className='btn btn-default'
onClick={this.handleGenerate} onClick={this.handleGenerate}
> >
{'Re-Generate'} {'Re-Generate'}

Просмотреть файл

@@ -27,17 +27,17 @@ export default class TeamSettings extends React.Component {
$('#save-button').button('loading'); $('#save-button').button('loading');
var config = this.props.config; var config = this.props.config;
config.TeamSettings.SiteName = React.findDOMNode(this.refs.SiteName).value.trim(); config.TeamSettings.SiteName = ReactDOM.findDOMNode(this.refs.SiteName).value.trim();
config.TeamSettings.RestrictCreationToDomains = React.findDOMNode(this.refs.RestrictCreationToDomains).value.trim(); config.TeamSettings.RestrictCreationToDomains = ReactDOM.findDOMNode(this.refs.RestrictCreationToDomains).value.trim();
config.TeamSettings.EnableTeamCreation = React.findDOMNode(this.refs.EnableTeamCreation).checked; config.TeamSettings.EnableTeamCreation = ReactDOM.findDOMNode(this.refs.EnableTeamCreation).checked;
config.TeamSettings.EnableUserCreation = React.findDOMNode(this.refs.EnableUserCreation).checked; config.TeamSettings.EnableUserCreation = ReactDOM.findDOMNode(this.refs.EnableUserCreation).checked;
var MaxUsersPerTeam = 50; var MaxUsersPerTeam = 50;
if (!isNaN(parseInt(React.findDOMNode(this.refs.MaxUsersPerTeam).value, 10))) { if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaxUsersPerTeam).value, 10))) {
MaxUsersPerTeam = parseInt(React.findDOMNode(this.refs.MaxUsersPerTeam).value, 10); MaxUsersPerTeam = parseInt(ReactDOM.findDOMNode(this.refs.MaxUsersPerTeam).value, 10);
} }
config.TeamSettings.MaxUsersPerTeam = MaxUsersPerTeam; config.TeamSettings.MaxUsersPerTeam = MaxUsersPerTeam;
React.findDOMNode(this.refs.MaxUsersPerTeam).value = MaxUsersPerTeam; ReactDOM.findDOMNode(this.refs.MaxUsersPerTeam).value = MaxUsersPerTeam;
Client.saveConfig( Client.saveConfig(
config, config,

Просмотреть файл

@@ -29,7 +29,7 @@ export default class ChangeUrlModal extends React.Component {
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (this.props.show === true && prevProps.show === false) { if (this.props.show === true && prevProps.show === false) {
React.findDOMNode(this.refs.urlinput).select(); ReactDOM.findDOMNode(this.refs.urlinput).select();
} }
} }
onURLChanged(e) { onURLChanged(e) {
@@ -60,7 +60,7 @@ export default class ChangeUrlModal extends React.Component {
doSubmit(e) { doSubmit(e) {
e.preventDefault(); e.preventDefault();
const url = React.findDOMNode(this.refs.urlinput).value; const url = ReactDOM.findDOMNode(this.refs.urlinput).value;
const cleanedURL = Utils.cleanUpUrlable(url); const cleanedURL = Utils.cleanUpUrlable(url);
if (cleanedURL !== url || url.length < 2 || url.indexOf('__') > -1) { if (cleanedURL !== url || url.length < 2 || url.indexOf('__') > -1) {
this.setState({urlError: this.getURLError(url)}); this.setState({urlError: this.getURLError(url)});

Просмотреть файл

@@ -17,6 +17,9 @@ const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
const Constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
const ActionTypes = Constants.ActionTypes; const ActionTypes = Constants.ActionTypes;
const Popover = ReactBootstrap.Popover;
const OverlayTrigger = ReactBootstrap.OverlayTrigger;
export default class ChannelHeader extends React.Component { export default class ChannelHeader extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@@ -110,7 +113,21 @@ export default class ChannelHeader extends React.Component {
} }
const channel = this.state.channel; const channel = this.state.channel;
const popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>); const popoverContent = (
<Popover
id='hader-popover'
bStyle='info'
bSize='large'
placement='bottom'
className='description'
onMouseOver={() => this.refs.descriptionOverlay.show()}
onMouseOut={() => this.refs.descriptionOverlay.hide()}
>
<MessageWrapper
message={channel.description}
/>
</Popover>
);
let channelTitle = channel.display_name; let channelTitle = channel.display_name;
const currentId = UserStore.getCurrentId(); const currentId = UserStore.getCurrentId();
const isAdmin = Utils.isAdmin(this.state.memberChannel.roles) || Utils.isAdmin(this.state.memberTeam.roles); const isAdmin = Utils.isAdmin(this.state.memberChannel.roles) || Utils.isAdmin(this.state.memberTeam.roles);
@@ -301,75 +318,82 @@ export default class ChannelHeader extends React.Component {
return ( return (
<table className='channel-header alt'> <table className='channel-header alt'>
<tr> <tbody>
<th> <tr>
<div className='channel-header__info'> <th>
<div className='dropdown'> <div className='channel-header__info'>
<div className='dropdown'>
<a
href='#'
className='dropdown-toggle theme'
type='button'
id='channel_header_dropdown'
data-toggle='dropdown'
aria-expanded='true'
>
<strong className='heading'>{channelTitle} </strong>
<span className='glyphicon glyphicon-chevron-down header-dropdown__icon' />
</a>
<ul
className='dropdown-menu'
role='menu'
aria-labelledby='channel_header_dropdown'
>
{dropdownContents}
</ul>
</div>
<OverlayTrigger
trigger={['hover', 'focus']}
placement='bottom'
overlay={popoverContent}
ref='descriptionOverlay'
>
<div
onClick={TextFormatting.handleClick}
className='description'
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(channel.description, {singleline: true, mentionHighlight: false})}}
/>
</OverlayTrigger>
</div>
</th>
<th>
<PopoverListMembers
members={this.state.users}
channelId={channel.id}
/>
</th>
<th className='search-bar__container'><NavbarSearchBox /></th>
<th>
<div className='dropdown channel-header__links'>
<a <a
href='#' href='#'
className='dropdown-toggle theme' className='dropdown-toggle theme'
type='button' type='button'
id='channel_header_dropdown' id='channel_header_right_dropdown'
data-toggle='dropdown' data-toggle='dropdown'
aria-expanded='true' aria-expanded='true'
> >
<strong className='heading'>{channelTitle} </strong> <span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
<span className='glyphicon glyphicon-chevron-down header-dropdown__icon' />
</a> </a>
<ul <ul
className='dropdown-menu' className='dropdown-menu dropdown-menu-right'
role='menu' role='menu'
aria-labelledby='channel_header_dropdown' aria-labelledby='channel_header_right_dropdown'
> >
{dropdownContents} <li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.searchMentions}
>
Recent Mentions
</a>
</li>
</ul> </ul>
</div> </div>
<div </th>
data-toggle='popover' </tr>
data-content={popoverContent} </tbody>
className='description'
onClick={TextFormatting.handleClick}
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(channel.description, {singleline: true, mentionHighlight: false})}}
/>
</div>
</th>
<th>
<PopoverListMembers
members={this.state.users}
channelId={channel.id}
/>
</th>
<th className='search-bar__container'><NavbarSearchBox /></th>
<th>
<div className='dropdown channel-header__links'>
<a
href='#'
className='dropdown-toggle theme'
type='button'
id='channel_header_right_dropdown'
data-toggle='dropdown'
aria-expanded='true'
>
<span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
</a>
<ul
className='dropdown-menu dropdown-menu-right'
role='menu'
aria-labelledby='channel_header_right_dropdown'
>
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.searchMentions}
>
Recent Mentions
</a>
</li>
</ul>
</div>
</th>
</tr>
</table> </table>
); );
} }

Просмотреть файл

@@ -15,7 +15,7 @@ export default class CommandList extends React.Component {
componentDidMount() { componentDidMount() {
var self = this; var self = this;
if (this.refs.modal) { if (this.refs.modal) {
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) { $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) {
var button = e.relatedTarget; var button = e.relatedTarget;
self.setState({channel_id: $(button).attr('data-channelid')}); self.setState({channel_id: $(button).attr('data-channelid')});
}); });

Просмотреть файл

@@ -56,8 +56,8 @@ export default class ChannelInviteModal extends React.Component {
}; };
} }
componentDidMount() { componentDidMount() {
$(React.findDOMNode(this)).on('hidden.bs.modal', this.onHide); $(ReactDOM.findDOMNode(this)).on('hidden.bs.modal', this.onHide);
$(React.findDOMNode(this)).on('show.bs.modal', this.onShow); $(ReactDOM.findDOMNode(this)).on('show.bs.modal', this.onShow);
ChannelStore.addExtraInfoChangeListener(this.onListenerChange); ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
ChannelStore.addChangeListener(this.onListenerChange); ChannelStore.addChangeListener(this.onListenerChange);

Просмотреть файл

@@ -75,15 +75,6 @@ export default class ChannelLoader extends React.Component {
Utils.applyTheme(Constants.THEMES.default); Utils.applyTheme(Constants.THEMES.default);
} }
/* Setup global mouse events */
$('body').on('click', function hidePopover(e) {
$('[data-toggle="popover"]').each(function eachPopover() {
if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
$(this).popover('hide');
}
});
});
$('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) { $('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) {
if (ev.type === 'mouseenter') { if (ev.type === 'mouseenter') {
$(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after'); $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after');

Просмотреть файл

@@ -74,9 +74,9 @@ export default class ChannelMembers extends React.Component {
componentDidMount() { componentDidMount() {
ChannelStore.addExtraInfoChangeListener(this.onChange); ChannelStore.addExtraInfoChangeListener(this.onChange);
ChannelStore.addChangeListener(this.onChange); ChannelStore.addChangeListener(this.onChange);
$(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide);
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
} }
componentWillUnmount() { componentWillUnmount() {
ChannelStore.removeExtraInfoChangeListener(this.onChange); ChannelStore.removeExtraInfoChangeListener(this.onChange);

Просмотреть файл

@@ -51,7 +51,7 @@ export default class ChannelNotifications extends React.Component {
componentDidMount() { componentDidMount() {
ChannelStore.addChangeListener(this.onListenerChange); ChannelStore.addChangeListener(this.onListenerChange);
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
} }
componentWillUnmount() { componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange); ChannelStore.removeChangeListener(this.onListenerChange);
@@ -104,7 +104,7 @@ export default class ChannelNotifications extends React.Component {
} }
handleUpdateNotifyLevel(notifyLevel) { handleUpdateNotifyLevel(notifyLevel) {
this.setState({notifyLevel}); this.setState({notifyLevel});
React.findDOMNode(this.refs.modal).focus(); ReactDOM.findDOMNode(this.refs.modal).focus();
} }
createNotifyLevelSection(serverError) { createNotifyLevelSection(serverError) {
var handleUpdateSection; var handleUpdateSection;
@@ -266,7 +266,7 @@ export default class ChannelNotifications extends React.Component {
handleUpdateMarkUnreadLevel(markUnreadLevel) { handleUpdateMarkUnreadLevel(markUnreadLevel) {
this.setState({markUnreadLevel}); this.setState({markUnreadLevel});
React.findDOMNode(this.refs.modal).focus(); ReactDOM.findDOMNode(this.refs.modal).focus();
} }
createMarkUnreadLevelSection(serverError) { createMarkUnreadLevelSection(serverError) {

Просмотреть файл

@@ -46,7 +46,9 @@ export default class CreateComment extends React.Component {
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
if (prevState.uploadsInProgress < this.state.uploadsInProgress) { if (prevState.uploadsInProgress < this.state.uploadsInProgress) {
$('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight); $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight);
$('.post-right__scroll').perfectScrollbar('update'); if ($(window).width() > 768) {
$('.post-right__scroll').perfectScrollbar('update');
}
} }
} }
handleSubmit(e) { handleSubmit(e) {
@@ -126,7 +128,7 @@ export default class CreateComment extends React.Component {
commentMsgKeyPress(e) { commentMsgKeyPress(e) {
if (e.which === 13 && !e.shiftKey && !e.altKey) { if (e.which === 13 && !e.shiftKey && !e.altKey) {
e.preventDefault(); e.preventDefault();
React.findDOMNode(this.refs.textbox).blur(); ReactDOM.findDOMNode(this.refs.textbox).blur();
this.handleSubmit(e); this.handleSubmit(e);
} }
@@ -189,7 +191,7 @@ export default class CreateComment extends React.Component {
handleTextDrop(text) { handleTextDrop(text) {
const newText = this.state.messageText + text; const newText = this.state.messageText + text;
this.handleUserInput(newText); this.handleUserInput(newText);
Utils.setCaretPosition(React.findDOMNode(this.refs.textbox.refs.message), newText.length); Utils.setCaretPosition(ReactDOM.findDOMNode(this.refs.textbox.refs.message), newText.length);
} }
removePreview(id) { removePreview(id) {
let previews = this.state.previews; let previews = this.state.previews;
@@ -255,6 +257,17 @@ export default class CreateComment extends React.Component {
postFooterClassName += ' has-error'; postFooterClassName += ' has-error';
} }
let uploadsInProgressText = null;
if (this.state.uploadsInProgress.length > 0) {
uploadsInProgressText = (
<span
className='pull-right post-right-comments-upload-in-progress'
>
{this.state.uploadsInProgress.length === 1 ? 'File uploading' : 'Files uploading'}
</span>
);
}
return ( return (
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<div className='post-create'> <div className='post-create'>
@@ -295,6 +308,7 @@ export default class CreateComment extends React.Component {
value='Add Comment' value='Add Comment'
onClick={this.handleSubmit} onClick={this.handleSubmit}
/> />
{uploadsInProgressText}
{postError} {postError}
{serverError} {serverError}
</div> </div>

Просмотреть файл

@@ -16,6 +16,7 @@ const Utils = require('../utils/utils.jsx');
const Constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
const ActionTypes = Constants.ActionTypes; const ActionTypes = Constants.ActionTypes;
const KeyCodes = Constants.KeyCodes;
export default class CreatePost extends React.Component { export default class CreatePost extends React.Component {
constructor(props) { constructor(props) {
@@ -35,6 +36,7 @@ export default class CreatePost extends React.Component {
this.removePreview = this.removePreview.bind(this); this.removePreview = this.removePreview.bind(this);
this.onChange = this.onChange.bind(this); this.onChange = this.onChange.bind(this);
this.getFileCount = this.getFileCount.bind(this); this.getFileCount = this.getFileCount.bind(this);
this.handleArrowUp = this.handleArrowUp.bind(this);
PostStore.clearDraftUploads(); PostStore.clearDraftUploads();
@@ -172,9 +174,9 @@ export default class CreatePost extends React.Component {
} }
} }
postMsgKeyPress(e) { postMsgKeyPress(e) {
if (e.which === 13 && !e.shiftKey && !e.altKey) { if (e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
e.preventDefault(); e.preventDefault();
React.findDOMNode(this.refs.textbox).blur(); ReactDOM.findDOMNode(this.refs.textbox).blur();
this.handleSubmit(e); this.handleSubmit(e);
} }
@@ -192,7 +194,7 @@ export default class CreatePost extends React.Component {
PostStore.storeCurrentDraft(draft); PostStore.storeCurrentDraft(draft);
} }
resizePostHolder() { resizePostHolder() {
const height = $(window).height() - $(React.findDOMNode(this.refs.topDiv)).height() - 50; const height = $(window).height() - $(ReactDOM.findDOMNode(this.refs.topDiv)).height() - 50;
$('.post-list-holder-by-time').css('height', `${height}px`); $('.post-list-holder-by-time').css('height', `${height}px`);
$(window).trigger('resize'); $(window).trigger('resize');
if ($(window).width() > 960) { if ($(window).width() > 960) {
@@ -243,7 +245,7 @@ export default class CreatePost extends React.Component {
handleTextDrop(text) { handleTextDrop(text) {
const newText = this.state.messageText + text; const newText = this.state.messageText + text;
this.handleUserInput(newText); this.handleUserInput(newText);
Utils.setCaretPosition(React.findDOMNode(this.refs.textbox.refs.message), newText.length); Utils.setCaretPosition(ReactDOM.findDOMNode(this.refs.textbox.refs.message), newText.length);
} }
removePreview(id) { removePreview(id) {
const previews = Object.assign([], this.state.previews); const previews = Object.assign([], this.state.previews);
@@ -292,6 +294,27 @@ export default class CreatePost extends React.Component {
const draft = PostStore.getDraft(channelId); const draft = PostStore.getDraft(channelId);
return draft.previews.length + draft.uploadsInProgress.length; return draft.previews.length + draft.uploadsInProgress.length;
} }
handleArrowUp(e) {
if (e.keyCode === KeyCodes.UP && this.state.messageText === '') {
e.preventDefault();
const channelId = ChannelStore.getCurrentId();
const lastPost = PostStore.getCurrentUsersLatestPost(channelId);
if (!lastPost) {
return;
}
var type = (lastPost.root_id && lastPost.root_id.length > 0) ? 'Comment' : 'Post';
AppDispatcher.handleViewAction({
type: ActionTypes.RECIEVED_EDIT_POST,
refocusId: '#post_textbox',
title: type,
message: lastPost.message,
postId: lastPost.id,
channelId: lastPost.channel_id
});
}
}
render() { render() {
let serverError = null; let serverError = null;
if (this.state.serverError) { if (this.state.serverError) {
@@ -336,6 +359,7 @@ export default class CreatePost extends React.Component {
<Textbox <Textbox
onUserInput={this.handleUserInput} onUserInput={this.handleUserInput}
onKeyPress={this.postMsgKeyPress} onKeyPress={this.postMsgKeyPress}
onKeyDown={this.handleArrowUp}
onHeightChange={this.resizePostHolder} onHeightChange={this.resizePostHolder}
messageText={this.state.messageText} messageText={this.state.messageText}
createMessage='Write a message...' createMessage='Write a message...'

Просмотреть файл

@@ -41,7 +41,7 @@ export default class DeleteChannelModal extends React.Component {
}); });
} }
componentDidMount() { componentDidMount() {
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
} }
render() { render() {
const channel = ChannelStore.getCurrent(); const channel = ChannelStore.getCurrent();

Просмотреть файл

@@ -73,7 +73,7 @@ export default class DeletePostModal extends React.Component {
this.setState(newState); this.setState(newState);
} }
componentDidMount() { componentDidMount() {
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
PostStore.addSelectedPostChangeListener(this.onListenerChange); PostStore.addSelectedPostChangeListener(this.onListenerChange);
} }
componentWillUnmount() { componentWillUnmount() {

Просмотреть файл

@@ -34,7 +34,7 @@ export default class EditChannelModal extends React.Component {
function handleUpdateSuccess() { function handleUpdateSuccess() {
this.setState({serverError: ''}); this.setState({serverError: ''});
AsyncClient.getChannel(this.state.channelId); AsyncClient.getChannel(this.state.channelId);
$(React.findDOMNode(this.refs.modal)).modal('hide'); $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
}.bind(this), }.bind(this),
function handleUpdateError(err) { function handleUpdateError(err) {
if (err.message === 'Invalid channel_description parameter') { if (err.message === 'Invalid channel_description parameter') {
@@ -56,11 +56,11 @@ export default class EditChannelModal extends React.Component {
this.setState({description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''}); this.setState({description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''});
} }
componentDidMount() { componentDidMount() {
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
$(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
} }
componentWillUnmount() { componentWillUnmount() {
$(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose); $(ReactDOM.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose);
} }
render() { render() {
var serverError = null; var serverError = null;

Просмотреть файл

@@ -5,6 +5,7 @@ var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx'); var AsyncClient = require('../utils/async_client.jsx');
var Textbox = require('./textbox.jsx'); var Textbox = require('./textbox.jsx');
var BrowserStore = require('../stores/browser_store.jsx'); var BrowserStore = require('../stores/browser_store.jsx');
var PostStore = require('../stores/post_store.jsx');
export default class EditPostModal extends React.Component { export default class EditPostModal extends React.Component {
constructor() { constructor() {
@@ -14,6 +15,7 @@ export default class EditPostModal extends React.Component {
this.handleEditInput = this.handleEditInput.bind(this); this.handleEditInput = this.handleEditInput.bind(this);
this.handleEditKeyPress = this.handleEditKeyPress.bind(this); this.handleEditKeyPress = this.handleEditKeyPress.bind(this);
this.handleUserInput = this.handleUserInput.bind(this); this.handleUserInput = this.handleUserInput.bind(this);
this.handleEditPostEvent = this.handleEditPostEvent.bind(this);
this.state = {editText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: ''}; this.state = {editText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: ''};
} }
@@ -35,16 +37,15 @@ export default class EditPostModal extends React.Component {
Client.updatePost(updatedPost, Client.updatePost(updatedPost,
function success() { function success() {
AsyncClient.getPosts(this.state.channel_id); AsyncClient.getPosts(updatedPost.channel_id);
window.scrollTo(0, 0); window.scrollTo(0, 0);
}.bind(this), },
function error(err) { function error(err) {
AsyncClient.dispatchError(err, 'updatePost'); AsyncClient.dispatchError(err, 'updatePost');
} }
); );
$('#edit_post').modal('hide'); $('#edit_post').modal('hide');
$(this.state.refocusId).focus();
} }
handleEditInput(editMessage) { handleEditInput(editMessage) {
this.setState({editText: editMessage}); this.setState({editText: editMessage});
@@ -52,28 +53,57 @@ export default class EditPostModal extends React.Component {
handleEditKeyPress(e) { handleEditKeyPress(e) {
if (e.which === 13 && !e.shiftKey && !e.altKey) { if (e.which === 13 && !e.shiftKey && !e.altKey) {
e.preventDefault(); e.preventDefault();
React.findDOMNode(this.refs.editbox).blur(); ReactDOM.findDOMNode(this.refs.editbox).blur();
this.handleEdit(e); this.handleEdit(e);
} }
} }
handleUserInput(e) { handleUserInput(e) {
this.setState({editText: e.target.value}); this.setState({editText: e.target.value});
} }
handleEditPostEvent(options) {
this.setState({
editText: options.message || '',
title: options.title || '',
post_id: options.postId || '',
channel_id: options.channelId || '',
comments: options.comments || 0,
refocusId: options.refocusId || ''
});
$(React.findDOMNode(this.refs.modal)).modal('show');
}
componentDidMount() { componentDidMount() {
var self = this; var self = this;
$(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function onHidden() { $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function onHidden() {
self.setState({editText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: ''}); self.setState({editText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: ''});
}); });
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function onShow(e) { $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', function onShow(e) {
var button = e.relatedTarget; var button = e.relatedTarget;
if (!button) {
return;
}
self.setState({editText: $(button).attr('data-message'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments'), refocusId: $(button).attr('data-refoucsid')}); self.setState({editText: $(button).attr('data-message'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments'), refocusId: $(button).attr('data-refoucsid')});
}); });
$(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function onShown() { $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', function onShown() {
self.refs.editbox.resize(); self.refs.editbox.resize();
$('#edit_textbox').get(0).focus();
}); });
$(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', function onShown() {
if (self.state.refocusId !== '') {
setTimeout(() => {
$(self.state.refocusId).get(0).focus();
});
}
});
PostStore.addEditPostListener(this.handleEditPostEvent);
}
componentWillUnmount() {
PostStore.removeEditPostListener(this.handleEditPostEvent);
} }
render() { render() {
var error = (<div className='form-group'><br /></div>); var error = (<div className='form-group'><br /></div>);

Просмотреть файл

@@ -39,11 +39,9 @@ export default class EmailVerify extends React.Component {
return ( return (
<div className='col-sm-12'> <div className='col-sm-12'>
<div className='panel panel-default verify_panel'> <div className='signup-team__container'>
<div className='panel-heading'> <h3>{title}</h3>
<h3 className='panel-title'>{title}</h3> <div>
</div>
<div className='panel-body'>
{body} {body}
{resend} {resend}
{resendConfirm} {resendConfirm}

Просмотреть файл

@@ -43,7 +43,7 @@ export default class FileAttachment extends React.Component {
return function loader() { return function loader() {
$(this).remove(); $(this).remove();
if (name in self.refs) { if (name in self.refs) {
var imgDiv = React.findDOMNode(self.refs[name]); var imgDiv = ReactDOM.findDOMNode(self.refs[name]);
$(imgDiv).removeClass('post__load'); $(imgDiv).removeClass('post__load');
$(imgDiv).addClass('post__image'); $(imgDiv).addClass('post__image');
@@ -82,7 +82,7 @@ export default class FileAttachment extends React.Component {
if (nextState.fileSize !== this.state.fileSize) { if (nextState.fileSize !== this.state.fileSize) {
if (this.refs.fileSize) { if (this.refs.fileSize) {
// update the UI element to display the file size without re-rendering the whole component // update the UI element to display the file size without re-rendering the whole component
React.findDOMNode(this.refs.fileSize).innerHTML = utils.fileSizeToString(nextState.fileSize); ReactDOM.findDOMNode(this.refs.fileSize).innerHTML = utils.fileSizeToString(nextState.fileSize);
return false; return false;
} }

Просмотреть файл

@@ -86,7 +86,7 @@ export default class FileUpload extends React.Component {
} }
handleChange() { handleChange() {
var element = $(React.findDOMNode(this.refs.fileInput)); var element = $(ReactDOM.findDOMNode(this.refs.fileInput));
this.uploadFiles(element.prop('files')); this.uploadFiles(element.prop('files'));
@@ -115,7 +115,7 @@ export default class FileUpload extends React.Component {
} }
componentDidMount() { componentDidMount() {
var inputDiv = React.findDOMNode(this.refs.input); var inputDiv = ReactDOM.findDOMNode(this.refs.input);
var self = this; var self = this;
if (this.props.postType === 'post') { if (this.props.postType === 'post') {

Просмотреть файл

@@ -17,7 +17,7 @@ export default class FindTeam extends React.Component {
var state = { }; var state = { };
var email = React.findDOMNode(this.refs.email).value.trim().toLowerCase(); var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!email || !utils.isEmail(email)) { if (!email || !utils.isEmail(email)) {
state.email_error = 'Please enter a valid email address'; state.email_error = 'Please enter a valid email address';
this.setState(state); this.setState(state);

Просмотреть файл

@@ -22,12 +22,12 @@ export default class GetLinkModal extends React.Component {
} }
componentDidMount() { componentDidMount() {
if (this.refs.modal) { if (this.refs.modal) {
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
$(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', this.onHide); $(ReactDOM.findDOMNode(this.refs.modal)).on('hide.bs.modal', this.onHide);
} }
} }
handleClick() { handleClick() {
var copyTextarea = $(React.findDOMNode(this.refs.textarea)); var copyTextarea = $(ReactDOM.findDOMNode(this.refs.textarea));
copyTextarea.select(); copyTextarea.select();
try { try {

Просмотреть файл

@@ -36,7 +36,7 @@ export default class InviteMemberModal extends React.Component {
var notEmpty = false; var notEmpty = false;
for (var i = 0; i < self.state.inviteIds.length; i++) { for (var i = 0; i < self.state.inviteIds.length; i++) {
var index = self.state.inviteIds[i]; var index = self.state.inviteIds[i];
if (React.findDOMNode(self.refs['email' + index]).value.trim() !== '') { if (ReactDOM.findDOMNode(self.refs['email' + index]).value.trim() !== '') {
notEmpty = true; notEmpty = true;
break; break;
} }
@@ -69,7 +69,7 @@ export default class InviteMemberModal extends React.Component {
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
var index = inviteIds[i]; var index = inviteIds[i];
var invite = {}; var invite = {};
invite.email = React.findDOMNode(this.refs['email' + index]).value.trim(); invite.email = ReactDOM.findDOMNode(this.refs['email' + index]).value.trim();
if (!invite.email || !utils.isEmail(invite.email)) { if (!invite.email || !utils.isEmail(invite.email)) {
emailErrors[index] = 'Please enter a valid email address'; emailErrors[index] = 'Please enter a valid email address';
valid = false; valid = false;
@@ -77,9 +77,9 @@ export default class InviteMemberModal extends React.Component {
emailErrors[index] = ''; emailErrors[index] = '';
} }
invite.firstName = React.findDOMNode(this.refs['first_name' + index]).value.trim(); invite.firstName = ReactDOM.findDOMNode(this.refs['first_name' + index]).value.trim();
invite.lastName = React.findDOMNode(this.refs['last_name' + index]).value.trim(); invite.lastName = ReactDOM.findDOMNode(this.refs['last_name' + index]).value.trim();
invites.push(invite); invites.push(invite);
} }
@@ -95,8 +95,8 @@ export default class InviteMemberModal extends React.Component {
Client.inviteMembers(data, Client.inviteMembers(data,
function success() { function success() {
$(React.findDOMNode(this.refs.modal)).attr('data-confirm', 'true'); $(ReactDOM.findDOMNode(this.refs.modal)).attr('data-confirm', 'true');
$(React.findDOMNode(this.refs.modal)).modal('hide'); $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
}.bind(this), }.bind(this),
function fail(err) { function fail(err) {
if (err.message === 'This person is already on your team') { if (err.message === 'This person is already on your team') {
@@ -110,8 +110,8 @@ export default class InviteMemberModal extends React.Component {
} }
componentDidUpdate() { componentDidUpdate() {
$(React.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200); $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200);
$(React.findDOMNode(this.refs.modalBody)).css('overflow-y', 'scroll'); $(ReactDOM.findDOMNode(this.refs.modalBody)).css('overflow-y', 'scroll');
} }
addInviteFields() { addInviteFields() {
@@ -126,9 +126,9 @@ export default class InviteMemberModal extends React.Component {
for (var i = 0; i < inviteIds.length; i++) { for (var i = 0; i < inviteIds.length; i++) {
var index = inviteIds[i]; var index = inviteIds[i];
React.findDOMNode(this.refs['email' + index]).value = ''; ReactDOM.findDOMNode(this.refs['email' + index]).value = '';
React.findDOMNode(this.refs['first_name' + index]).value = ''; ReactDOM.findDOMNode(this.refs['first_name' + index]).value = '';
React.findDOMNode(this.refs['last_name' + index]).value = ''; ReactDOM.findDOMNode(this.refs['last_name' + index]).value = '';
} }
this.setState({ this.setState({

Просмотреть файл

@@ -25,14 +25,14 @@ export default class Login extends React.Component {
return; return;
} }
const email = React.findDOMNode(this.refs.email).value.trim(); const email = ReactDOM.findDOMNode(this.refs.email).value.trim();
if (!email) { if (!email) {
state.serverError = 'An email is required'; state.serverError = 'An email is required';
this.setState(state); this.setState(state);
return; return;
} }
const password = React.findDOMNode(this.refs.password).value.trim(); const password = ReactDOM.findDOMNode(this.refs.password).value.trim();
if (!password) { if (!password) {
state.serverError = 'A password is required'; state.serverError = 'A password is required';
this.setState(state); this.setState(state);

Просмотреть файл

@@ -61,7 +61,7 @@ export default class MentionList extends React.Component {
} }
onClick(e) { onClick(e) {
if (!($('#' + this.props.id).is(e.target) || $('#' + this.props.id).has(e.target).length || if (!($('#' + this.props.id).is(e.target) || $('#' + this.props.id).has(e.target).length ||
('mentionlist' in this.refs && $(React.findDOMNode(this.refs.mentionlist)).has(e.target).length))) { ('mentionlist' in this.refs && $(ReactDOM.findDOMNode(this.refs.mentionlist)).has(e.target).length))) {
this.setState({mentionText: '-1'}); this.setState({mentionText: '-1'});
} }
} }

Просмотреть файл

@@ -31,12 +31,12 @@ export default class MoreChannels extends React.Component {
} }
componentDidMount() { componentDidMount() {
ChannelStore.addMoreChangeListener(this.onListenerChange); ChannelStore.addMoreChangeListener(this.onListenerChange);
$(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function shown() { $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', function shown() {
asyncClient.getMoreChannels(true); asyncClient.getMoreChannels(true);
}); });
var self = this; var self = this;
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) { $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) {
var button = e.relatedTarget; var button = e.relatedTarget;
self.setState({channelType: $(button).attr('data-channeltype')}); self.setState({channelType: $(button).attr('data-channeltype')});
}); });
@@ -54,7 +54,7 @@ export default class MoreChannels extends React.Component {
this.setState({joiningChannel: channelIndex}); this.setState({joiningChannel: channelIndex});
client.joinChannel(channel.id, client.joinChannel(channel.id,
function joinSuccess() { function joinSuccess() {
$(React.findDOMNode(this.refs.modal)).modal('hide'); $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
asyncClient.getChannel(channel.id); asyncClient.getChannel(channel.id);
utils.switchChannel(channel); utils.switchChannel(channel);
this.setState({joiningChannel: -1}); this.setState({joiningChannel: -1});
@@ -65,7 +65,7 @@ export default class MoreChannels extends React.Component {
); );
} }
handleNewChannel() { handleNewChannel() {
$(React.findDOMNode(this.refs.modal)).modal('hide'); $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
this.setState({showNewChannelModal: true}); this.setState({showNewChannelModal: true});
} }
render() { render() {

Просмотреть файл

@@ -1,133 +1,273 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
var TeamStore = require('../stores/team_store.jsx'); const AsyncClient = require('../utils/async_client.jsx');
var Client = require('../utils/client.jsx'); const ChannelStore = require('../stores/channel_store.jsx');
var Constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
var AsyncClient = require('../utils/async_client.jsx'); const Client = require('../utils/client.jsx');
var PreferenceStore = require('../stores/preference_store.jsx'); const Modal = ReactBootstrap.Modal;
var utils = require('../utils/utils.jsx'); const PreferenceStore = require('../stores/preference_store.jsx');
const TeamStore = require('../stores/team_store.jsx');
const UserStore = require('../stores/user_store.jsx');
const Utils = require('../utils/utils.jsx');
export default class MoreDirectChannels extends React.Component { export default class MoreDirectChannels extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {channels: [], loadingDMChannel: -1}; this.handleFilterChange = this.handleFilterChange.bind(this);
this.handleHide = this.handleHide.bind(this);
this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
this.handleUserChange = this.handleUserChange.bind(this);
this.createRowForUser = this.createRowForUser.bind(this);
this.state = {
users: this.getUsersFromStore(),
filter: '',
loadingDMChannel: -1
};
}
getUsersFromStore() {
const currentId = UserStore.getCurrentId();
const profiles = UserStore.getProfiles();
const users = [];
for (const id in profiles) {
if (id !== currentId) {
users.push(profiles[id]);
}
}
users.sort((a, b) => a.username.localeCompare(b.username));
return users;
} }
componentDidMount() { componentDidMount() {
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', (e) => { UserStore.addChangeListener(this.handleUserChange);
var button = e.relatedTarget;
this.setState({channels: $(button).data('channels')}); // eslint-disable-line react/no-did-mount-set-state
});
} }
handleJoinDirectChannel(channel) { componentWillUnmount() {
const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, channel.teammate_id, 'true'); UserStore.addChangeListener(this.handleUserChange);
}
handleFilterChange() {
const filter = ReactDOM.findDOMNode(this.refs.filter).value;
if (filter !== this.state.filter) {
this.setState({filter});
}
}
handleHide() {
if (this.props.onModalDismissed) {
this.props.onModalDismissed();
}
this.setState({filter: ''});
}
handleShowDirectChannel(teammate, e) {
if (this.state.loadingDMChannel !== -1) {
return;
}
e.preventDefault();
const channelName = Utils.getDirectChannelName(UserStore.getCurrentId(), teammate.id);
let channel = ChannelStore.getByName(channelName);
const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, teammate.id, 'true');
AsyncClient.savePreferences([preference]); AsyncClient.savePreferences([preference]);
if (channel) {
Utils.switchChannel(channel);
this.handleHide();
} else {
this.setState({loadingDMChannel: teammate.id});
channel = {
name: channelName,
last_post_at: 0,
total_msg_count: 0,
type: 'D',
display_name: teammate.username,
teammate_id: teammate.id,
status: UserStore.getStatus(teammate.id)
};
Client.createDirectChannel(
channel,
teammate.id,
(data) => {
this.setState({loadingDMChannel: -1});
AsyncClient.getChannel(data.id);
Utils.switchChannel(data);
this.handleHide();
},
() => {
this.setState({loadingDMChannel: -1});
window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channelName;
}
);
}
}
handleUserChange() {
this.setState({users: this.getUsersFromStore()});
}
createRowForUser(user) {
const details = [];
const fullName = Utils.getFullName(user);
if (fullName) {
details.push(
<span
key={`${user.id}__full-name`}
className='full-name'
>
{fullName}
</span>
);
}
if (user.nickname) {
const separator = fullName ? ' - ' : '';
details.push(
<span
key={`${user.nickname}__nickname`}
className='nickname'
>
{separator + user.nickname}
</span>
);
}
let joinButton;
if (this.state.loadingDMChannel === user.id) {
joinButton = (
<img
className='channel-loading-gif'
src='/static/images/load.gif'
/>
);
} else {
joinButton = (
<button
type='button'
className='btn btn-primary btn-message'
onClick={this.handleShowDirectChannel.bind(this, user)}
>
{'Message'}
</button>
);
}
return (
<li
key={user.id}
className='direct-channel'
>
<div className='col-xs-1 image-div'>
<img
className='profile-image'
src={`/api/v1/users/${user.id}/image?time=${user.update_at}`}
/>
</div>
<div className='col-xs-9'>
<div className='username'>
{user.username}
</div>
<div>
{details}
</div>
</div>
<div className='col-xs-2 btn-div'>
{joinButton}
</div>
</li>
);
} }
render() { render() {
var directMessageItems = this.state.channels.map((channel, index) => { if (!this.props.show) {
var badge = ''; return null;
var titleClass = ''; }
var handleClick = null;
if (channel.fake) { let users = this.state.users;
// It's a direct message channel that doesn't exist yet so let's create it now if (this.state.filter !== '') {
var otherUserId = utils.getUserIdFromChannelName(channel); users = users.filter((user) => {
return user.username.indexOf(this.state.filter) !== -1 ||
user.first_name.indexOf(this.state.filter) !== -1 ||
user.last_name.indexOf(this.state.filter) !== -1 ||
user.nickname.indexOf(this.state.filter) !== -1;
});
}
if (this.state.loadingDMChannel === index) { const userEntries = users.map(this.createRowForUser);
badge = (
<img
className='channel-loading-gif pull-right'
src='/static/images/load.gif'
/>
);
}
if (this.state.loadingDMChannel === -1) { if (userEntries.length === 0) {
handleClick = (e) => { userEntries.push(<li key='no-users-found'>{'No users found :('}</li>);
e.preventDefault(); }
this.setState({loadingDMChannel: index});
this.handleJoinDirectChannel(channel);
Client.createDirectChannel(channel, otherUserId, let memberString = 'Member';
(data) => { if (users.length !== 1) {
$(React.findDOMNode(this.refs.modal)).modal('hide'); memberString += 's';
this.setState({loadingDMChannel: -1}); }
AsyncClient.getChannel(data.id);
utils.switchChannel(data);
},
() => {
this.setState({loadingDMChannel: -1});
window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
}
);
};
}
} else {
if (channel.unread) {
badge = <span className='badge pull-right small'>{channel.unread}</span>;
titleClass = 'unread-title';
}
handleClick = (e) => { let count;
e.preventDefault(); if (users.length === this.state.users.length) {
this.handleJoinDirectChannel(channel); count = `${users.length} ${memberString}`;
utils.switchChannel(channel); } else {
$(React.findDOMNode(this.refs.modal)).modal('hide'); count = `${users.length} ${memberString} of ${this.state.users.length} Total`;
}; }
}
return (
<li key={channel.name}>
<a
className={'sidebar-channel ' + titleClass}
href='#'
onClick={handleClick}
>{badge}{channel.display_name}</a>
</li>
);
});
return ( return (
<div <Modal
className='modal fade' className='modal-direct-channels'
id='more_direct_channels' show={this.props.show}
ref='modal' bsSize='large'
tabIndex='-1' onHide={this.handleHide}
role='dialog'
aria-hidden='true'
> >
<div className='modal-dialog'> <Modal.Header closeButton={true}>
<div className='modal-content'> <Modal.Title>{'More Direct Messages'}</Modal.Title>
<div className='modal-header'> </Modal.Header>
<button <Modal.Body>
type='button' <div>
className='close' <input
data-dismiss='modal' ref='filter'
> className='form-control filter-textbox'
<span aria-hidden='true'>{'×'}</span> placeholder='Search members'
<span className='sr-only'>{'Close'}</span> onInput={this.handleFilterChange}
</button> style={{width: '200px', display: 'inline'}}
<h4 className='modal-title'>{'More Direct Messages'}</h4> />
</div> <span className='member-count pull-right'>{count}</span>
<div className='modal-body'>
<ul className='nav nav-pills nav-stacked'>
{directMessageItems}
</ul>
</div>
<div className='modal-footer'>
<button
type='button'
className='btn btn-default'
data-dismiss='modal'
>{'Close'}</button>
</div>
</div> </div>
</div> <ul className='user-list'>
</div> {userEntries}
</ul>
</Modal.Body>
<Modal.Footer>
<button
type='button'
className='btn btn-default'
onClick={this.handleHide}
>
{'Close'}
</button>
</Modal.Footer>
</Modal>
); );
} }
} }
MoreDirectChannels.propTypes = {
show: React.PropTypes.bool.isRequired,
onModalDismissed: React.PropTypes.func
};

Просмотреть файл

@@ -14,6 +14,9 @@ var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes; var ActionTypes = Constants.ActionTypes;
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Popover = ReactBootstrap.Popover;
var OverlayTrigger = ReactBootstrap.OverlayTrigger;
export default class Navbar extends React.Component { export default class Navbar extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@@ -36,12 +39,6 @@ export default class Navbar extends React.Component {
ChannelStore.addChangeListener(this.onChange); ChannelStore.addChangeListener(this.onChange);
ChannelStore.addExtraInfoChangeListener(this.onChange); ChannelStore.addExtraInfoChangeListener(this.onChange);
$('.inner__wrap').click(this.hideSidebars); $('.inner__wrap').click(this.hideSidebars);
$('body').on('click.infopopover', function handlePopoverClick(e) {
if ($(e.target).attr('data-toggle') !== 'popover' && $(e.target).parents('.popover.in').length === 0) {
$('.info-popover').popover('hide');
}
});
} }
componentWillUnmount() { componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChange); ChannelStore.removeChangeListener(this.onChange);
@@ -224,11 +221,15 @@ export default class Navbar extends React.Component {
return ( return (
<div className='navbar-brand'> <div className='navbar-brand'>
<div className='dropdown'> <div className='dropdown'>
<div <OverlayTrigger
data-toggle='popover' trigger='click'
data-content={popoverContent} placement='bottom'
className='description info-popover' overlay={popoverContent}
/> className='description'
rootClose={true}
>
<div className='description info-popover'/>
</OverlayTrigger>
<a <a
href='#' href='#'
className='dropdown-toggle theme' className='dropdown-toggle theme'
@@ -330,11 +331,17 @@ export default class Navbar extends React.Component {
var isDirect = false; var isDirect = false;
if (channel) { if (channel) {
popoverContent = React.renderToString( popoverContent = (
<MessageWrapper <Popover
message={channel.description} bsStyle='info'
options={{singleline: true, mentionHighlight: false}} placement='bottom'
/> id='description-popover'
>
<MessageWrapper
message={channel.description}
options={{singleline: true, mentionHighlight: false}}
/>
</Popover>
); );
isAdmin = Utils.isAdmin(this.state.member.roles); isAdmin = Utils.isAdmin(this.state.member.roles);
@@ -354,19 +361,28 @@ export default class Navbar extends React.Component {
} }
if (channel.description.length === 0) { if (channel.description.length === 0) {
popoverContent = React.renderToString( popoverContent = (
<div> <Popover
No channel description yet. <br/> bsStyle='info'
<a placement='bottom'
href='#' id='description-popover'
data-toggle='modal' >
data-desc={channel.description} <div>
data-title={channel.display_name} {'No channel description yet.'}
data-channelid={channel.id} <br/>
data-target='#edit_channel' <a
> href='#'
Click here data-toggle='modal'
</a> to add one.</div> data-desc={channel.description}
data-title={channel.display_name}
data-channelid={channel.id}
data-target='#edit_channel'
>
{'Click here'}
</a>
{' to add one.'}
</div>
</Popover>
); );
} }
} }

Просмотреть файл

@@ -40,7 +40,7 @@ export default class NavbarDropdown extends React.Component {
UserStore.addTeamsChangeListener(this.onListenerChange); UserStore.addTeamsChangeListener(this.onListenerChange);
TeamStore.addChangeListener(this.onListenerChange); TeamStore.addChangeListener(this.onListenerChange);
$(React.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => { $(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => {
this.blockToggle = true; this.blockToggle = true;
setTimeout(() => { setTimeout(() => {
this.blockToggle = false; this.blockToggle = false;
@@ -51,7 +51,7 @@ export default class NavbarDropdown extends React.Component {
UserStore.removeTeamsChangeListener(this.onListenerChange); UserStore.removeTeamsChangeListener(this.onListenerChange);
TeamStore.removeChangeListener(this.onListenerChange); TeamStore.removeChangeListener(this.onListenerChange);
$(React.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown'); $(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown');
} }
onListenerChange() { onListenerChange() {
var newState = getStateFromStores(); var newState = getStateFromStores();

Просмотреть файл

@@ -25,7 +25,7 @@ export default class NewChannelModal extends React.Component {
handleSubmit(e) { handleSubmit(e) {
e.preventDefault(); e.preventDefault();
const displayName = React.findDOMNode(this.refs.display_name).value.trim(); const displayName = ReactDOM.findDOMNode(this.refs.display_name).value.trim();
if (displayName.length < 1) { if (displayName.length < 1) {
this.setState({displayNameError: 'This field is required'}); this.setState({displayNameError: 'This field is required'});
return; return;
@@ -35,8 +35,8 @@ export default class NewChannelModal extends React.Component {
} }
handleChange() { handleChange() {
const newData = { const newData = {
displayName: React.findDOMNode(this.refs.display_name).value, displayName: ReactDOM.findDOMNode(this.refs.display_name).value,
description: React.findDOMNode(this.refs.channel_desc).value description: ReactDOM.findDOMNode(this.refs.channel_desc).value
}; };
this.props.onDataChanged(newData); this.props.onDataChanged(newData);
} }

Просмотреть файл

@@ -15,7 +15,7 @@ export default class PasswordResetForm extends React.Component {
e.preventDefault(); e.preventDefault();
var state = {}; var state = {};
var password = React.findDOMNode(this.refs.password).value.trim(); var password = ReactDOM.findDOMNode(this.refs.password).value.trim();
if (!password || password.length < 5) { if (!password || password.length < 5) {
state.error = 'Please enter at least 5 characters.'; state.error = 'Please enter at least 5 characters.';
this.setState(state); this.setState(state);

Просмотреть файл

@@ -16,7 +16,7 @@ export default class PasswordResetSendLink extends React.Component {
e.preventDefault(); e.preventDefault();
var state = {}; var state = {};
var email = React.findDOMNode(this.refs.email).value.trim().toLowerCase(); var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!email || !Utils.isEmail(email)) { if (!email || !Utils.isEmail(email)) {
state.error = 'Please enter a valid email address.'; state.error = 'Please enter a valid email address.';
this.setState(state); this.setState(state);
@@ -33,7 +33,7 @@ export default class PasswordResetSendLink extends React.Component {
client.sendPasswordReset(data, client.sendPasswordReset(data,
function passwordResetSent() { function passwordResetSent() {
this.setState({error: null, updateText: <p>A password reset link has been sent to <b>{email}</b> for your <b>{this.props.teamDisplayName}</b> team on {window.location.hostname}.</p>, moreUpdateText: 'Please check your inbox.'}); this.setState({error: null, updateText: <p>A password reset link has been sent to <b>{email}</b> for your <b>{this.props.teamDisplayName}</b> team on {window.location.hostname}.</p>, moreUpdateText: 'Please check your inbox.'});
$(React.findDOMNode(this.refs.reset_form)).hide(); $(ReactDOM.findDOMNode(this.refs.reset_form)).hide();
}.bind(this), }.bind(this),
function passwordResetFailedToSend(err) { function passwordResetFailedToSend(err) {
this.setState({error: err.message, update_text: null, moreUpdateText: null}); this.setState({error: err.message, update_text: null, moreUpdateText: null});

Просмотреть файл

@@ -2,6 +2,8 @@
// See License.txt for license information. // See License.txt for license information.
var UserStore = require('../stores/user_store.jsx'); var UserStore = require('../stores/user_store.jsx');
var Popover = ReactBootstrap.Popover;
var OverlayTrigger = ReactBootstrap.OverlayTrigger;
export default class PopoverListMembers extends React.Component { export default class PopoverListMembers extends React.Component {
componentDidMount() { componentDidMount() {
@@ -24,16 +26,9 @@ export default class PopoverListMembers extends React.Component {
}); });
} }
}; };
$('#member_popover').popover({placement: 'bottom', trigger: 'click', html: true});
$('body').on('click', function onClick(e) {
if (e.target.parentNode && $(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) {
$('#member_popover').popover('hide');
}
});
} }
render() { render() {
let popoverHtml = ''; let popoverHtml = [];
let count = 0; let count = 0;
let countText = '-'; let countText = '-';
const members = this.props.members; const members = this.props.members;
@@ -46,7 +41,7 @@ export default class PopoverListMembers extends React.Component {
members.forEach(function addMemberElement(m) { members.forEach(function addMemberElement(m) {
if (teamMembers[m.username] && teamMembers[m.username].delete_at <= 0) { if (teamMembers[m.username] && teamMembers[m.username].delete_at <= 0) {
popoverHtml += `<div class='text--nowrap'>${m.username}</div>`; popoverHtml.push(<div className='text--nowrap'>{m.username}</div>);
count++; count++;
} }
}); });
@@ -59,15 +54,14 @@ export default class PopoverListMembers extends React.Component {
} }
return ( return (
<div <OverlayTrigger
id='member_popover' trigger='click'
data-toggle='popover' placement='bottom'
data-content={popoverHtml} rootClose='true'
data-original-title='Members' overlay={<Popover title='Members'>{popoverHtml}</Popover>}
> >
<div <div id='member_popover'>
id='member_tooltip' <div>
>
{countText} {countText}
<span <span
className='fa fa-user' className='fa fa-user'
@@ -75,6 +69,7 @@ export default class PopoverListMembers extends React.Component {
/> />
</div> </div>
</div> </div>
</OverlayTrigger>
); );
} }
} }

Просмотреть файл

@@ -37,7 +37,7 @@ export default class PostBody extends React.Component {
} }
parseEmojis() { parseEmojis() {
twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE}); twemoji.parse(ReactDOM.findDOMNode(this), {size: Constants.EMOJI_SIZE});
} }
componentDidMount() { componentDidMount() {

Просмотреть файл

@@ -15,7 +15,7 @@ export default class PostDeletedModal extends React.Component {
this.state = {}; this.state = {};
} }
componentDidMount() { componentDidMount() {
$(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', () => { $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', () => {
this.handleClose(); this.handleClose();
}); });
} }

Просмотреть файл

@@ -109,7 +109,7 @@ export default class PostList extends React.Component {
PreferenceStore.addChangeListener(this.onTimeChange); PreferenceStore.addChangeListener(this.onTimeChange);
SocketStore.addChangeListener(this.onSocketChange); SocketStore.addChangeListener(this.onSocketChange);
const postHolder = $(React.findDOMNode(this.refs.postlist)); const postHolder = $(ReactDOM.findDOMNode(this.refs.postlist));
$(window).resize(() => { $(window).resize(() => {
this.resize(); this.resize();
@@ -135,7 +135,7 @@ export default class PostList extends React.Component {
$('.top-visible-post').removeClass('top-visible-post'); $('.top-visible-post').removeClass('top-visible-post');
$(React.findDOMNode(this.refs.postlistcontent)).children().each(function select() { $(ReactDOM.findDOMNode(this.refs.postlistcontent)).children().each(function select() {
if ($(this).position().top + $(this).height() / 2 > 0) { if ($(this).position().top + $(this).height() / 2 > 0) {
$(this).addClass('top-visible-post'); $(this).addClass('top-visible-post');
return false; return false;
@@ -161,7 +161,7 @@ export default class PostList extends React.Component {
PreferenceStore.removeChangeListener(this.onTimeChange); PreferenceStore.removeChangeListener(this.onTimeChange);
$('body').off('click.userpopover'); $('body').off('click.userpopover');
$(window).off('resize'); $(window).off('resize');
var postHolder = $(React.findDOMNode(this.refs.postlist)); var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist));
postHolder.off('scroll'); postHolder.off('scroll');
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
@@ -217,7 +217,7 @@ export default class PostList extends React.Component {
} }
} }
componentWillUpdate() { componentWillUpdate() {
var postHolder = $(React.findDOMNode(this.refs.postlist)); var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist));
this.prevScrollTop = postHolder.scrollTop(); this.prevScrollTop = postHolder.scrollTop();
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
@@ -228,7 +228,7 @@ export default class PostList extends React.Component {
} }
} }
resize() { resize() {
const postHolder = $(React.findDOMNode(this.refs.postlist)); const postHolder = $(ReactDOM.findDOMNode(this.refs.postlist));
if ($('#create_post').length > 0) { if ($('#create_post').length > 0) {
const height = $(window).height() - $('#create_post').height() - $('#error_bar').outerHeight() - 50; const height = $(window).height() - $('#create_post').height() - $('#error_bar').outerHeight() - 50;
postHolder.css('height', height + 'px'); postHolder.css('height', height + 'px');
@@ -236,12 +236,12 @@ export default class PostList extends React.Component {
} }
scrollTo(val) { scrollTo(val) {
this.isUserScroll = false; this.isUserScroll = false;
var postHolder = $(React.findDOMNode(this.refs.postlist)); var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist));
postHolder[0].scrollTop = val; postHolder[0].scrollTop = val;
} }
scrollToBottom(force) { scrollToBottom(force) {
this.isUserScroll = false; this.isUserScroll = false;
var postHolder = $(React.findDOMNode(this.refs.postlist)); var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist));
if ($('#new_message_' + this.props.channelId)[0] && !this.userHasSeenNew && !force) { if ($('#new_message_' + this.props.channelId)[0] && !this.userHasSeenNew && !force) {
$('#new_message_' + this.props.channelId)[0].scrollIntoView(); $('#new_message_' + this.props.channelId)[0].scrollIntoView();
} else { } else {
@@ -599,14 +599,14 @@ export default class PostList extends React.Component {
var order = this.state.postList.order; var order = this.state.postList.order;
var channelId = this.props.channelId; var channelId = this.props.channelId;
$(React.findDOMNode(this.refs.loadmore)).text('Retrieving more messages...'); $(ReactDOM.findDOMNode(this.refs.loadmore)).text('Retrieving more messages...');
Client.getPostsPage( Client.getPostsPage(
channelId, channelId,
order.length, order.length,
Constants.POST_CHUNK_SIZE, Constants.POST_CHUNK_SIZE,
function success(data) { function success(data) {
$(React.findDOMNode(this.refs.loadmore)).text('Load more messages'); $(ReactDOM.findDOMNode(this.refs.loadmore)).text('Load more messages');
this.gotMorePosts = true; this.gotMorePosts = true;
this.setState({numToDisplay: this.state.numToDisplay + Constants.POST_CHUNK_SIZE}); this.setState({numToDisplay: this.state.numToDisplay + Constants.POST_CHUNK_SIZE});
@@ -631,7 +631,7 @@ export default class PostList extends React.Component {
Client.getProfiles(); Client.getProfiles();
}.bind(this), }.bind(this),
function fail(err) { function fail(err) {
$(React.findDOMNode(this.refs.loadmore)).text('Load more messages'); $(ReactDOM.findDOMNode(this.refs.loadmore)).text('Load more messages');
AsyncClient.dispatchError(err, 'getPosts'); AsyncClient.dispatchError(err, 'getPosts');
}.bind(this) }.bind(this)
); );

Просмотреть файл

@@ -14,7 +14,7 @@ export default class RegisterAppModal extends React.Component {
this.state = {clientId: '', clientSecret: '', saved: false}; this.state = {clientId: '', clientSecret: '', saved: false};
} }
componentDidMount() { componentDidMount() {
$(React.findDOMNode(this)).on('hide.bs.modal', this.onHide); $(ReactDOM.findDOMNode(this)).on('hide.bs.modal', this.onHide);
} }
register() { register() {
var state = this.state; var state = this.state;
@@ -22,7 +22,7 @@ export default class RegisterAppModal extends React.Component {
var app = {}; var app = {};
var name = this.refs.name.getDOMNode().value; var name = this.refs.name.value;
if (!name || name.length === 0) { if (!name || name.length === 0) {
state.nameError = 'Application name must be filled in.'; state.nameError = 'Application name must be filled in.';
this.setState(state); this.setState(state);
@@ -31,7 +31,7 @@ export default class RegisterAppModal extends React.Component {
state.nameError = null; state.nameError = null;
app.name = name; app.name = name;
var homepage = this.refs.homepage.getDOMNode().value; var homepage = this.refs.homepage.value;
if (!homepage || homepage.length === 0) { if (!homepage || homepage.length === 0) {
state.homepageError = 'Homepage must be filled in.'; state.homepageError = 'Homepage must be filled in.';
this.setState(state); this.setState(state);
@@ -40,10 +40,10 @@ export default class RegisterAppModal extends React.Component {
state.homepageError = null; state.homepageError = null;
app.homepage = homepage; app.homepage = homepage;
var desc = this.refs.desc.getDOMNode().value; var desc = this.refs.desc.value;
app.description = desc; app.description = desc;
var rawCallbacks = this.refs.callback.getDOMNode().value.trim(); var rawCallbacks = this.refs.callback.value.trim();
if (!rawCallbacks || rawCallbacks.length === 0) { if (!rawCallbacks || rawCallbacks.length === 0) {
state.callbackError = 'At least one callback URL must be filled in.'; state.callbackError = 'At least one callback URL must be filled in.';
this.setState(state); this.setState(state);
@@ -73,7 +73,7 @@ export default class RegisterAppModal extends React.Component {
this.setState({clientId: '', clientSecret: '', saved: false}); this.setState({clientId: '', clientSecret: '', saved: false});
} }
save() { save() {
this.setState({saved: this.refs.save.getDOMNode().checked}); this.setState({saved: this.refs.save.checked});
} }
render() { render() {
var nameError; var nameError;

Просмотреть файл

@@ -37,13 +37,13 @@ export default class RemovedFromChannelModal extends React.Component {
} }
componentDidMount() { componentDidMount() {
$(React.findDOMNode(this)).on('show.bs.modal', this.handleShow); $(ReactDOM.findDOMNode(this)).on('show.bs.modal', this.handleShow);
$(React.findDOMNode(this)).on('hidden.bs.modal', this.handleClose); $(ReactDOM.findDOMNode(this)).on('hidden.bs.modal', this.handleClose);
} }
componentWillUnmount() { componentWillUnmount() {
$(React.findDOMNode(this)).off('show.bs.modal', this.handleShow); $(ReactDOM.findDOMNode(this)).off('show.bs.modal', this.handleShow);
$(React.findDOMNode(this)).off('hidden.bs.modal', this.handleClose); $(ReactDOM.findDOMNode(this)).off('hidden.bs.modal', this.handleClose);
} }
render() { render() {

Просмотреть файл

@@ -76,13 +76,13 @@ export default class RenameChannelModal extends React.Component {
Client.updateChannel(channel, Client.updateChannel(channel,
function handleUpdateSuccess() { function handleUpdateSuccess() {
$(React.findDOMNode(this.refs.modal)).modal('hide'); $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
AsyncClient.getChannel(channel.id); AsyncClient.getChannel(channel.id);
Utils.updateAddressBar(channel.name); Utils.updateAddressBar(channel.name);
React.findDOMNode(this.refs.displayName).value = ''; ReactDOM.findDOMNode(this.refs.displayName).value = '';
React.findDOMNode(this.refs.channelName).value = ''; ReactDOM.findDOMNode(this.refs.channelName).value = '';
}.bind(this), }.bind(this),
function handleUpdateError(err) { function handleUpdateError(err) {
state.serverError = err.message; state.serverError = err.message;
@@ -92,15 +92,15 @@ export default class RenameChannelModal extends React.Component {
); );
} }
onNameChange() { onNameChange() {
this.setState({channelName: React.findDOMNode(this.refs.channelName).value}); this.setState({channelName: ReactDOM.findDOMNode(this.refs.channelName).value});
} }
onDisplayNameChange() { onDisplayNameChange() {
this.setState({displayName: React.findDOMNode(this.refs.displayName).value}); this.setState({displayName: ReactDOM.findDOMNode(this.refs.displayName).value});
} }
displayNameKeyUp() { displayNameKeyUp() {
const displayName = React.findDOMNode(this.refs.displayName).value.trim(); const displayName = ReactDOM.findDOMNode(this.refs.displayName).value.trim();
const channelName = Utils.cleanUpUrlable(displayName); const channelName = Utils.cleanUpUrlable(displayName);
React.findDOMNode(this.refs.channelName).value = channelName; ReactDOM.findDOMNode(this.refs.channelName).value = channelName;
this.setState({channelName: channelName}); this.setState({channelName: channelName});
} }
handleClose() { handleClose() {
@@ -119,11 +119,11 @@ export default class RenameChannelModal extends React.Component {
this.setState({displayName: button.attr('data-display'), channelName: button.attr('data-name'), channelId: button.attr('data-channelid')}); this.setState({displayName: button.attr('data-display'), channelName: button.attr('data-name'), channelId: button.attr('data-channelid')});
} }
componentDidMount() { componentDidMount() {
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.handleShow); $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.handleShow);
$(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
} }
componentWillUnmount() { componentWillUnmount() {
$(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose); $(ReactDOM.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose);
} }
render() { render() {
let displayNameError = null; let displayNameError = null;

Просмотреть файл

@@ -55,7 +55,7 @@ export default class RhsComment extends React.Component {
this.forceUpdate(); this.forceUpdate();
} }
parseEmojis() { parseEmojis() {
twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE}); twemoji.parse(ReactDOM.findDOMNode(this), {size: Constants.EMOJI_SIZE});
} }
componentDidMount() { componentDidMount() {
this.parseEmojis(); this.parseEmojis();

Просмотреть файл

@@ -19,7 +19,7 @@ export default class RhsRootPost extends React.Component {
this.state = {}; this.state = {};
} }
parseEmojis() { parseEmojis() {
twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE}); twemoji.parse(ReactDOM.findDOMNode(this), {size: Constants.EMOJI_SIZE});
} }
componentDidMount() { componentDidMount() {
this.parseEmojis(); this.parseEmojis();

Просмотреть файл

@@ -55,8 +55,6 @@ export default class RhsThread extends React.Component {
if ($('.post-right__scroll')[0]) { if ($('.post-right__scroll')[0]) {
$('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight); $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight);
} }
$('.post-right__scroll').perfectScrollbar('update');
this.resize(); this.resize();
} }
componentWillUnmount() { componentWillUnmount() {
@@ -113,8 +111,10 @@ export default class RhsThread extends React.Component {
var height = $(window).height() - $('#error_bar').outerHeight() - 100; var height = $(window).height() - $('#error_bar').outerHeight() - 100;
$('.post-right__scroll').css('height', height + 'px'); $('.post-right__scroll').css('height', height + 'px');
$('.post-right__scroll').scrollTop(100000); $('.post-right__scroll').scrollTop(100000);
$('.post-right__scroll').perfectScrollbar(); if ($(window).width() > 768) {
$('.post-right__scroll').perfectScrollbar('update'); $('.post-right__scroll').perfectScrollbar();
$('.post-right__scroll').perfectScrollbar('update');
}
} }
render() { render() {
var postList = this.state.postList; var postList = this.state.postList;

Просмотреть файл

@@ -90,7 +90,7 @@ export default class SearchBar extends React.Component {
function success(data) { function success(data) {
this.setState({isSearching: false}); this.setState({isSearching: false});
if (utils.isMobile()) { if (utils.isMobile()) {
React.findDOMNode(this.refs.search).value = ''; ReactDOM.findDOMNode(this.refs.search).value = '';
} }
AppDispatcher.handleServerAction({ AppDispatcher.handleServerAction({

Просмотреть файл

@@ -56,7 +56,9 @@ export default class SearchResults extends React.Component {
var height = $(window).height() - $('#error_bar').outerHeight() - 100; var height = $(window).height() - $('#error_bar').outerHeight() - 100;
$('#search-items-container').css('height', height + 'px'); $('#search-items-container').css('height', height + 'px');
$('#search-items-container').scrollTop(0); $('#search-items-container').scrollTop(0);
$('#search-items-container').perfectScrollbar(); if ($(window).width() > 768) {
$('#search-items-container').perfectScrollbar();
}
} }
render() { render() {

Просмотреть файл

@@ -12,7 +12,7 @@ export default class SettingPicture extends React.Component {
if (file) { if (file) {
var reader = new FileReader(); var reader = new FileReader();
var img = React.findDOMNode(this.refs.image); var img = ReactDOM.findDOMNode(this.refs.image);
reader.onload = function load(e) { reader.onload = function load(e) {
$(img).attr('src', e.target.result); $(img).attr('src', e.target.result);
}; };

Просмотреть файл

@@ -37,7 +37,7 @@ export default class SettingsUpload extends React.Component {
doSubmit(e) { doSubmit(e) {
e.preventDefault(); e.preventDefault();
var inputnode = React.findDOMNode(this.refs.uploadinput); var inputnode = ReactDOM.findDOMNode(this.refs.uploadinput);
if (inputnode.files && inputnode.files[0]) { if (inputnode.files && inputnode.files[0]) {
this.props.submit(inputnode.files[0]); this.props.submit(inputnode.files[0]);
} else { } else {

Просмотреть файл

@@ -8,6 +8,7 @@ const Client = require('../utils/client.jsx');
const Constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
const PreferenceStore = require('../stores/preference_store.jsx'); const PreferenceStore = require('../stores/preference_store.jsx');
const NewChannelFlow = require('./new_channel_flow.jsx'); const NewChannelFlow = require('./new_channel_flow.jsx');
const MoreDirectChannels = require('./more_direct_channels.jsx');
const SearchBox = require('./search_bar.jsx'); const SearchBox = require('./search_bar.jsx');
const SidebarHeader = require('./sidebar_header.jsx'); const SidebarHeader = require('./sidebar_header.jsx');
const SocketStore = require('../stores/socket_store.jsx'); const SocketStore = require('../stores/socket_store.jsx');
@@ -33,12 +34,19 @@ export default class Sidebar extends React.Component {
this.onResize = this.onResize.bind(this); this.onResize = this.onResize.bind(this);
this.updateUnreadIndicators = this.updateUnreadIndicators.bind(this); this.updateUnreadIndicators = this.updateUnreadIndicators.bind(this);
this.handleLeaveDirectChannel = this.handleLeaveDirectChannel.bind(this); this.handleLeaveDirectChannel = this.handleLeaveDirectChannel.bind(this);
this.showNewChannelModal = this.showNewChannelModal.bind(this);
this.hideNewChannelModal = this.hideNewChannelModal.bind(this);
this.showMoreDirectChannelsModal = this.showMoreDirectChannelsModal.bind(this);
this.hideMoreDirectChannelsModal = this.hideMoreDirectChannelsModal.bind(this);
this.createChannelElement = this.createChannelElement.bind(this); this.createChannelElement = this.createChannelElement.bind(this);
this.isLeaving = new Map(); this.isLeaving = new Map();
const state = this.getStateFromStores(); const state = this.getStateFromStores();
state.modal = ''; state.newChannelModalType = '';
state.showMoreDirectChannelsModal = false;
state.loadingDMChannel = -1; state.loadingDMChannel = -1;
this.state = state; this.state = state;
@@ -47,10 +55,11 @@ export default class Sidebar extends React.Component {
const members = ChannelStore.getAllMembers(); const members = ChannelStore.getAllMembers();
var teamMemberMap = UserStore.getActiveOnlyProfiles(); var teamMemberMap = UserStore.getActiveOnlyProfiles();
var currentId = ChannelStore.getCurrentId(); var currentId = ChannelStore.getCurrentId();
const currentUserId = UserStore.getCurrentId();
var teammates = []; var teammates = [];
for (var id in teamMemberMap) { for (var id in teamMemberMap) {
if (id === UserStore.getCurrentId()) { if (id === currentUserId) {
continue; continue;
} }
teammates.push(teamMemberMap[id]); teammates.push(teamMemberMap[id]);
@@ -58,22 +67,16 @@ export default class Sidebar extends React.Component {
const preferences = PreferenceStore.getPreferences(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW); const preferences = PreferenceStore.getPreferences(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
// Create lists of all read and unread direct channels
var visibleDirectChannels = []; var visibleDirectChannels = [];
var hiddenDirectChannels = []; var hiddenDirectChannelCount = 0;
for (var i = 0; i < teammates.length; i++) { for (var i = 0; i < teammates.length; i++) {
const teammate = teammates[i]; const teammate = teammates[i];
if (teammate.id === UserStore.getCurrentId()) { if (teammate.id === currentUserId) {
continue; continue;
} }
var channelName = ''; const channelName = Utils.getDirectChannelName(currentUserId, teammate.id);
if (teammate.id > UserStore.getCurrentId()) {
channelName = UserStore.getCurrentId() + '__' + teammate.id;
} else {
channelName = teammate.id + '__' + UserStore.getCurrentId();
}
let forceShow = false; let forceShow = false;
let channel = ChannelStore.getByName(channelName); let channel = ChannelStore.getByName(channelName);
@@ -106,19 +109,18 @@ export default class Sidebar extends React.Component {
visibleDirectChannels.push(channel); visibleDirectChannels.push(channel);
} else { } else {
hiddenDirectChannels.push(channel); hiddenDirectChannelCount += 1;
} }
} }
visibleDirectChannels.sort(this.sortChannelsByDisplayName); visibleDirectChannels.sort(this.sortChannelsByDisplayName);
hiddenDirectChannels.sort(this.sortChannelsByDisplayName);
return { return {
activeId: currentId, activeId: currentId,
channels: ChannelStore.getAll(), channels: ChannelStore.getAll(),
members, members,
visibleDirectChannels, visibleDirectChannels,
hiddenDirectChannels hiddenDirectChannelCount
}; };
} }
@@ -132,7 +134,9 @@ export default class Sidebar extends React.Component {
AsyncClient.getDirectChannelPreferences(); AsyncClient.getDirectChannelPreferences();
$('.nav-pills__container').perfectScrollbar(); if ($(window).width() > 768) {
$('.nav-pills__container').perfectScrollbar();
}
this.updateTitle(); this.updateTitle();
this.updateUnreadIndicators(); this.updateUnreadIndicators();
@@ -280,13 +284,13 @@ export default class Sidebar extends React.Component {
this.updateUnreadIndicators(); this.updateUnreadIndicators();
} }
updateUnreadIndicators() { updateUnreadIndicators() {
const container = $(React.findDOMNode(this.refs.container)); const container = $(ReactDOM.findDOMNode(this.refs.container));
var showTopUnread = false; var showTopUnread = false;
var showBottomUnread = false; var showBottomUnread = false;
if (this.firstUnreadChannel) { if (this.firstUnreadChannel) {
var firstUnreadElement = $(React.findDOMNode(this.refs[this.firstUnreadChannel])); var firstUnreadElement = $(ReactDOM.findDOMNode(this.refs[this.firstUnreadChannel]));
if (firstUnreadElement.position().top + firstUnreadElement.height() < 0) { if (firstUnreadElement.position().top + firstUnreadElement.height() < 0) {
showTopUnread = true; showTopUnread = true;
@@ -294,7 +298,7 @@ export default class Sidebar extends React.Component {
} }
if (this.lastUnreadChannel) { if (this.lastUnreadChannel) {
var lastUnreadElement = $(React.findDOMNode(this.refs[this.lastUnreadChannel])); var lastUnreadElement = $(ReactDOM.findDOMNode(this.refs[this.lastUnreadChannel]));
if (lastUnreadElement.position().top > container.height()) { if (lastUnreadElement.position().top > container.height()) {
showBottomUnread = true; showBottomUnread = true;
@@ -336,6 +340,20 @@ export default class Sidebar extends React.Component {
return a.display_name.localeCompare(b.display_name); return a.display_name.localeCompare(b.display_name);
} }
showNewChannelModal(type) {
this.setState({newChannelModalType: type});
}
hideNewChannelModal() {
this.setState({newChannelModalType: ''});
}
showMoreDirectChannelsModal() {
this.setState({showDirectChannelsModal: true});
}
hideMoreDirectChannelsModal() {
this.setState({showDirectChannelsModal: false});
}
createChannelElement(channel, index, arr, handleClose) { createChannelElement(channel, index, arr, handleClose) {
var members = this.state.members; var members = this.state.members;
var activeId = this.state.activeId; var activeId = this.state.activeId;
@@ -532,25 +550,21 @@ export default class Sidebar extends React.Component {
head.appendChild(link); head.appendChild(link);
var directMessageMore = null; var directMessageMore = null;
if (this.state.hiddenDirectChannels.length > 0) { if (this.state.hiddenDirectChannelCount > 0) {
directMessageMore = ( directMessageMore = (
<li key='more'> <li key='more'>
<a <a
key={`more${this.state.hiddenDirectChannels.length}`}
href='#' href='#'
data-toggle='modal' onClick={this.showMoreDirectChannelsModal}
className='nav-more'
data-target='#more_direct_channels'
data-channels={JSON.stringify(this.state.hiddenDirectChannels)}
> >
{'More (' + this.state.hiddenDirectChannels.length + ')'} {'More (' + this.state.hiddenDirectChannelCount + ')'}
</a> </a>
</li> </li>
); );
} }
let showChannelModal = false; let showChannelModal = false;
if (this.state.modal !== '') { if (this.state.newChannelModalType !== '') {
showChannelModal = true; showChannelModal = true;
} }
@@ -561,9 +575,14 @@ export default class Sidebar extends React.Component {
<div> <div>
<NewChannelFlow <NewChannelFlow
show={showChannelModal} show={showChannelModal}
channelType={this.state.modal} channelType={this.state.newChannelModalType}
onModalDismissed={() => this.setState({modal: ''})} onModalDismissed={this.hideNewChannelModal}
/> />
<MoreDirectChannels
show={this.state.showDirectChannelsModal}
onModalDismissed={this.hideMoreDirectChannelsModal}
/>
<SidebarHeader <SidebarHeader
teamDisplayName={this.props.teamDisplayName} teamDisplayName={this.props.teamDisplayName}
teamName={this.props.teamName} teamName={this.props.teamName}
@@ -599,7 +618,7 @@ export default class Sidebar extends React.Component {
<a <a
className='add-channel-btn' className='add-channel-btn'
href='#' href='#'
onClick={() => this.setState({modal: 'O'})} onClick={this.showNewChannelModal.bind(this, 'O')}
> >
{'+'} {'+'}
</a> </a>
@@ -632,7 +651,7 @@ export default class Sidebar extends React.Component {
<a <a
className='add-channel-btn' className='add-channel-btn'
href='#' href='#'
onClick={() => this.setState({modal: 'P'})} onClick={this.showNewChannelModal.bind(this, 'P')}
> >
{'+'} {'+'}
</a> </a>

Просмотреть файл

@@ -28,7 +28,7 @@ export default class SignupUserComplete extends React.Component {
handleSubmit(e) { handleSubmit(e) {
e.preventDefault(); e.preventDefault();
const providedEmail = React.findDOMNode(this.refs.email).value.trim(); const providedEmail = ReactDOM.findDOMNode(this.refs.email).value.trim();
if (!providedEmail) { if (!providedEmail) {
this.setState({nameError: '', emailError: 'This field is required', passwordError: ''}); this.setState({nameError: '', emailError: 'This field is required', passwordError: ''});
return; return;
@@ -39,7 +39,7 @@ export default class SignupUserComplete extends React.Component {
return; return;
} }
const providedUsername = React.findDOMNode(this.refs.name).value.trim().toLowerCase(); const providedUsername = ReactDOM.findDOMNode(this.refs.name).value.trim().toLowerCase();
if (!providedUsername) { if (!providedUsername) {
this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''}); this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''});
return; return;
@@ -59,7 +59,7 @@ export default class SignupUserComplete extends React.Component {
return; return;
} }
const providedPassword = React.findDOMNode(this.refs.password).value.trim(); const providedPassword = ReactDOM.findDOMNode(this.refs.password).value.trim();
if (!providedPassword || providedPassword.length < 5) { if (!providedPassword || providedPassword.length < 5) {
this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''}); this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''});
return; return;

Просмотреть файл

@@ -44,11 +44,11 @@ export default class TeamMembers extends React.Component {
UserStore.addChangeListener(this.onChange); UserStore.addChangeListener(this.onChange);
var self = this; var self = this;
$(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function show() { $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function show() {
self.setState({render_members: false}); self.setState({render_members: false});
}); });
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function hide() { $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', function hide() {
self.setState({render_members: true}); self.setState({render_members: true});
}); });
} }

Просмотреть файл

@@ -21,7 +21,7 @@ export default class TeamSignupDisplayNamePage extends React.Component {
submitNext(e) { submitNext(e) {
e.preventDefault(); e.preventDefault();
var displayName = React.findDOMNode(this.refs.name).value.trim(); var displayName = ReactDOM.findDOMNode(this.refs.name).value.trim();
if (!displayName) { if (!displayName) {
this.setState({nameError: 'This field is required'}); this.setState({nameError: 'This field is required'});
return; return;

Просмотреть файл

@@ -13,10 +13,10 @@ export default class TeamSignupEmailItem extends React.Component {
this.state = {}; this.state = {};
} }
getValue() { getValue() {
return React.findDOMNode(this.refs.email).value.trim(); return ReactDOM.findDOMNode(this.refs.email).value.trim();
} }
validate(teamEmail) { validate(teamEmail) {
const email = React.findDOMNode(this.refs.email).value.trim().toLowerCase(); const email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!email) { if (!email) {
return true; return true;

Просмотреть файл

@@ -22,7 +22,7 @@ export default class TeamSignupPasswordPage extends React.Component {
submitNext(e) { submitNext(e) {
e.preventDefault(); e.preventDefault();
var password = React.findDOMNode(this.refs.password).value.trim(); var password = ReactDOM.findDOMNode(this.refs.password).value.trim();
if (!password || password.length < 5) { if (!password || password.length < 5) {
this.setState({passwordError: 'Please enter at least 5 characters'}); this.setState({passwordError: 'Please enter at least 5 characters'});
return; return;

Просмотреть файл

@@ -23,7 +23,7 @@ export default class TeamSignupUrlPage extends React.Component {
submitNext(e) { submitNext(e) {
e.preventDefault(); e.preventDefault();
const name = React.findDOMNode(this.refs.name).value.trim(); const name = ReactDOM.findDOMNode(this.refs.name).value.trim();
if (!name) { if (!name) {
this.setState({nameError: 'This field is required'}); this.setState({nameError: 'This field is required'});
return; return;

Просмотреть файл

@@ -21,7 +21,7 @@ export default class TeamSignupUsernamePage extends React.Component {
submitNext(e) { submitNext(e) {
e.preventDefault(); e.preventDefault();
var name = React.findDOMNode(this.refs.name).value.trim().toLowerCase(); var name = ReactDOM.findDOMNode(this.refs.name).value.trim().toLowerCase();
var usernameError = Utils.isValidUsername(name); var usernameError = Utils.isValidUsername(name);
if (usernameError === 'Cannot use a reserved word as a username.') { if (usernameError === 'Cannot use a reserved word as a username.') {

Просмотреть файл

@@ -36,7 +36,7 @@ export default class TeamSignupWelcomePage extends React.Component {
var state = {useDiff: true, serverError: ''}; var state = {useDiff: true, serverError: ''};
var email = React.findDOMNode(this.refs.email).value.trim().toLowerCase(); var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!email || !Utils.isEmail(email)) { if (!email || !Utils.isEmail(email)) {
state.emailError = 'Please enter a valid email address'; state.emailError = 'Please enter a valid email address';
this.setState(state); this.setState(state);

Просмотреть файл

@@ -17,7 +17,7 @@ export default class EmailSignUpPage extends React.Component {
var team = {}; var team = {};
var state = {serverError: ''}; var state = {serverError: ''};
team.email = React.findDOMNode(this.refs.email).value.trim().toLowerCase(); team.email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!team.email || !Utils.isEmail(team.email)) { if (!team.email || !Utils.isEmail(team.email)) {
state.emailError = 'Please enter a valid email address'; state.emailError = 'Please enter a valid email address';
state.inValid = true; state.inValid = true;

Просмотреть файл

@@ -54,7 +54,7 @@ export default class SSOSignUpPage extends React.Component {
); );
} }
nameChange() { nameChange() {
this.setState({name: React.findDOMNode(this.refs.teamname).value.trim()}); this.setState({name: ReactDOM.findDOMNode(this.refs.teamname).value.trim()});
} }
render() { render() {
var nameError = null; var nameError = null;

Просмотреть файл

@@ -9,6 +9,7 @@ const ErrorStore = require('../stores/error_store.jsx');
const Utils = require('../utils/utils.jsx'); const Utils = require('../utils/utils.jsx');
const Constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
const ActionTypes = Constants.ActionTypes; const ActionTypes = Constants.ActionTypes;
const KeyCodes = Constants.KeyCodes;
export default class Textbox extends React.Component { export default class Textbox extends React.Component {
constructor(props) { constructor(props) {
@@ -83,7 +84,7 @@ export default class Textbox extends React.Component {
componentDidUpdate() { componentDidUpdate() {
if (this.caret >= 0) { if (this.caret >= 0) {
Utils.setCaretPosition(React.findDOMNode(this.refs.message), this.caret); Utils.setCaretPosition(ReactDOM.findDOMNode(this.refs.message), this.caret);
this.caret = -1; this.caret = -1;
} }
if (this.doProcessMentions) { if (this.doProcessMentions) {
@@ -97,7 +98,7 @@ export default class Textbox extends React.Component {
if (!this.addedMention) { if (!this.addedMention) {
this.checkForNewMention(nextProps.messageText); this.checkForNewMention(nextProps.messageText);
} }
const text = React.findDOMNode(this.refs.message).value; const text = ReactDOM.findDOMNode(this.refs.message).value;
if (nextProps.channelId !== this.props.channelId || nextProps.messageText !== text) { if (nextProps.channelId !== this.props.channelId || nextProps.messageText !== text) {
this.doProcessMentions = true; this.doProcessMentions = true;
} }
@@ -117,11 +118,11 @@ export default class Textbox extends React.Component {
} }
handleChange() { handleChange() {
this.props.onUserInput(React.findDOMNode(this.refs.message).value); this.props.onUserInput(ReactDOM.findDOMNode(this.refs.message).value);
} }
handleKeyPress(e) { handleKeyPress(e) {
const text = React.findDOMNode(this.refs.message).value; const text = ReactDOM.findDOMNode(this.refs.message).value;
if (!this.refs.commands.isEmpty() && text.indexOf('/') === 0 && e.which === 13) { if (!this.refs.commands.isEmpty() && text.indexOf('/') === 0 && e.which === 13) {
this.refs.commands.addFirstCommand(); this.refs.commands.addFirstCommand();
@@ -130,7 +131,7 @@ export default class Textbox extends React.Component {
} }
if (!this.doProcessMentions) { if (!this.doProcessMentions) {
const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message)); const caret = Utils.getCaretPosition(ReactDOM.findDOMNode(this.refs.message));
const preText = text.substring(0, caret); const preText = text.substring(0, caret);
const lastSpace = preText.lastIndexOf(' '); const lastSpace = preText.lastIndexOf(' ');
const lastAt = preText.lastIndexOf('@'); const lastAt = preText.lastIndexOf('@');
@@ -144,17 +145,19 @@ export default class Textbox extends React.Component {
} }
handleKeyDown(e) { handleKeyDown(e) {
if (Utils.getSelectedText(React.findDOMNode(this.refs.message)) !== '') { if (Utils.getSelectedText(ReactDOM.findDOMNode(this.refs.message)) !== '') {
this.doProcessMentions = true; this.doProcessMentions = true;
} }
if (e.keyCode === 8) { if (e.keyCode === KeyCodes.BACKSPACE) {
this.handleBackspace(e); this.handleBackspace(e);
} else if (this.props.onKeyDown) {
this.props.onKeyDown(e);
} }
} }
handleBackspace() { handleBackspace() {
const text = React.findDOMNode(this.refs.message).value; const text = ReactDOM.findDOMNode(this.refs.message).value;
if (text.indexOf('/') === 0) { if (text.indexOf('/') === 0) {
this.refs.commands.getSuggestedCommands(text.substring(0, text.length - 1)); this.refs.commands.getSuggestedCommands(text.substring(0, text.length - 1));
} }
@@ -163,7 +166,7 @@ export default class Textbox extends React.Component {
return; return;
} }
const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message)); const caret = Utils.getCaretPosition(ReactDOM.findDOMNode(this.refs.message));
const preText = text.substring(0, caret); const preText = text.substring(0, caret);
const lastSpace = preText.lastIndexOf(' '); const lastSpace = preText.lastIndexOf(' ');
const lastAt = preText.lastIndexOf('@'); const lastAt = preText.lastIndexOf('@');
@@ -174,7 +177,7 @@ export default class Textbox extends React.Component {
} }
checkForNewMention(text) { checkForNewMention(text) {
const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message)); const caret = Utils.getCaretPosition(ReactDOM.findDOMNode(this.refs.message));
const preText = text.substring(0, caret); const preText = text.substring(0, caret);
@@ -201,7 +204,7 @@ export default class Textbox extends React.Component {
} }
addMention(name) { addMention(name) {
const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message)); const caret = Utils.getCaretPosition(ReactDOM.findDOMNode(this.refs.message));
const text = this.props.messageText; const text = this.props.messageText;
@@ -224,14 +227,14 @@ export default class Textbox extends React.Component {
} }
addCommand(cmd) { addCommand(cmd) {
const elm = React.findDOMNode(this.refs.message); const elm = ReactDOM.findDOMNode(this.refs.message);
elm.value = cmd; elm.value = cmd;
this.handleChange(); this.handleChange();
} }
resize() { resize() {
const e = React.findDOMNode(this.refs.message); const e = ReactDOM.findDOMNode(this.refs.message);
const w = React.findDOMNode(this.refs.wrapper); const w = ReactDOM.findDOMNode(this.refs.wrapper);
const prevHeight = $(e).height(); const prevHeight = $(e).height();
@@ -259,14 +262,14 @@ export default class Textbox extends React.Component {
} }
handleFocus() { handleFocus() {
const elm = React.findDOMNode(this.refs.message); const elm = ReactDOM.findDOMNode(this.refs.message);
if (elm.title === elm.value) { if (elm.title === elm.value) {
elm.value = ''; elm.value = '';
} }
} }
handleBlur() { handleBlur() {
const elm = React.findDOMNode(this.refs.message); const elm = ReactDOM.findDOMNode(this.refs.message);
if (elm.value === '') { if (elm.value === '') {
elm.value = elm.title; elm.value = elm.title;
} }
@@ -318,5 +321,6 @@ Textbox.propTypes = {
onUserInput: React.PropTypes.func.isRequired, onUserInput: React.PropTypes.func.isRequired,
onKeyPress: React.PropTypes.func.isRequired, onKeyPress: React.PropTypes.func.isRequired,
onHeightChange: React.PropTypes.func, onHeightChange: React.PropTypes.func,
createMessage: React.PropTypes.string.isRequired createMessage: React.PropTypes.string.isRequired,
onKeyDown: React.PropTypes.func
}; };

Просмотреть файл

@@ -3,6 +3,8 @@
var Utils = require('../utils/utils.jsx'); var Utils = require('../utils/utils.jsx');
var UserStore = require('../stores/user_store.jsx'); var UserStore = require('../stores/user_store.jsx');
var Popover = ReactBootstrap.Popover;
var OverlayTrigger = ReactBootstrap.OverlayTrigger;
var id = 0; var id = 0;
@@ -32,7 +34,6 @@ export default class UserProfile extends React.Component {
componentDidMount() { componentDidMount() {
UserStore.addChangeListener(this.onChange); UserStore.addChangeListener(this.onChange);
if (!this.props.disablePopover) { if (!this.props.disablePopover) {
$('#profile_' + this.uniqueId).popover({placement: 'right', container: 'body', trigger: 'click hover', html: true, delay: {show: 200, hide: 100}});
$('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'}); $('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'});
} }
} }
@@ -62,23 +63,46 @@ export default class UserProfile extends React.Component {
return <div>{name}</div>; return <div>{name}</div>;
} }
var dataContent = '<img class="user-popover__image" src="/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at + '" height="128" width="128" />'; var dataContent = [];
dataContent.push(
<img className='user-popover__image'
src={'/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at}
height='128'
width='128'
/>
);
if (!global.window.config.ShowEmailAddress === 'true') { if (!global.window.config.ShowEmailAddress === 'true') {
dataContent += '<div class="text-nowrap">Email not shared</div>'; dataContent.push(<div className='text-nowrap'>{'Email not shared'}</div>);
} else { } else {
dataContent += '<div data-toggle="tooltip" title="' + this.state.profile.email + '"><a href="mailto:' + this.state.profile.email + '" class="text-nowrap text-lowercase user-popover__email">' + this.state.profile.email + '</a></div>'; dataContent.push(
<div
data-toggle='tooltip'
title="' + this.state.profile.email + '"
>
<a
href="mailto:' + this.state.profile.email + '"
className='text-nowrap text-lowercase user-popover__email'
>
{this.state.profile.email}
</a>
</div>
);
} }
return ( return (
<OverlayTrigger
trigger='click'
placement='right'
rootClose='true'
overlay={<Popover title={this.state.profile.username}>{dataContent}</Popover>}
>
<div <div
className='user-popover' className='user-popover'
id={'profile_' + this.uniqueId} id={'profile_' + this.uniqueId}
data-toggle='popover'
data-content={dataContent}
data-original-title={this.state.profile.username}
> >
{name} {name}
</div> </div>
</OverlayTrigger>
); );
} }
} }

Просмотреть файл

@@ -35,7 +35,7 @@ export default class ImportThemeModal extends React.Component {
handleSubmit(e) { handleSubmit(e) {
e.preventDefault(); e.preventDefault();
const text = React.findDOMNode(this.refs.input).value; const text = ReactDOM.findDOMNode(this.refs.input).value;
if (!this.isInputValid(text)) { if (!this.isInputValid(text)) {
this.setState({inputError: 'Invalid format, please try copying and pasting in again.'}); this.setState({inputError: 'Invalid format, please try copying and pasting in again.'});

Просмотреть файл

@@ -29,14 +29,14 @@ export default class UserSettingsAppearance extends React.Component {
UserStore.addChangeListener(this.onChange); UserStore.addChangeListener(this.onChange);
if (this.props.activeSection === 'theme') { if (this.props.activeSection === 'theme') {
$(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); $(ReactDOM.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
} }
$('#user_settings').on('hidden.bs.modal', this.handleClose); $('#user_settings').on('hidden.bs.modal', this.handleClose);
} }
componentDidUpdate() { componentDidUpdate() {
if (this.props.activeSection === 'theme') { if (this.props.activeSection === 'theme') {
$('.color-btn').removeClass('active-border'); $('.color-btn').removeClass('active-border');
$(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); $(ReactDOM.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
} }
} }
componentWillUnmount() { componentWillUnmount() {

Просмотреть файл

@@ -211,7 +211,7 @@ export default class UserSettingsGeneralTab extends React.Component {
this.props.updateSection(section); this.props.updateSection(section);
} }
handleClose() { handleClose() {
$(React.findDOMNode(this)).find('.form-control').each(function clearForms() { $(ReactDOM.findDOMNode(this)).find('.form-control').each(function clearForms() {
this.value = ''; this.value = '';
}); });

Просмотреть файл

@@ -129,7 +129,7 @@ export default class NotificationsTab extends React.Component {
); );
} }
handleClose() { handleClose() {
$(React.findDOMNode(this)).find('.form-control').each(function clearField() { $(ReactDOM.findDOMNode(this)).find('.form-control').each(function clearField() {
this.value = ''; this.value = '';
}); });
@@ -158,15 +158,15 @@ export default class NotificationsTab extends React.Component {
} }
handleNotifyRadio(notifyLevel) { handleNotifyRadio(notifyLevel) {
this.setState({notifyLevel: notifyLevel}); this.setState({notifyLevel: notifyLevel});
React.findDOMNode(this.refs.wrapper).focus(); ReactDOM.findDOMNode(this.refs.wrapper).focus();
} }
handleEmailRadio(enableEmail) { handleEmailRadio(enableEmail) {
this.setState({enableEmail: enableEmail}); this.setState({enableEmail: enableEmail});
React.findDOMNode(this.refs.wrapper).focus(); ReactDOM.findDOMNode(this.refs.wrapper).focus();
} }
handleSoundRadio(enableSound) { handleSoundRadio(enableSound) {
this.setState({enableSound: enableSound}); this.setState({enableSound: enableSound});
React.findDOMNode(this.refs.wrapper).focus(); ReactDOM.findDOMNode(this.refs.wrapper).focus();
} }
updateUsernameKey(val) { updateUsernameKey(val) {
this.setState({usernameKey: val}); this.setState({usernameKey: val});
@@ -184,10 +184,10 @@ export default class NotificationsTab extends React.Component {
this.setState({channelKey: val}); this.setState({channelKey: val});
} }
updateCustomMentionKeys() { updateCustomMentionKeys() {
var checked = React.findDOMNode(this.refs.customcheck).checked; var checked = ReactDOM.findDOMNode(this.refs.customcheck).checked;
if (checked) { if (checked) {
var text = React.findDOMNode(this.refs.custommentions).value; var text = ReactDOM.findDOMNode(this.refs.custommentions).value;
// remove all spaces and split string into individual keys // remove all spaces and split string into individual keys
this.setState({customKeys: text.replace(/ /g, ''), customKeysChecked: true}); this.setState({customKeys: text.replace(/ /g, ''), customKeysChecked: true});
@@ -196,7 +196,7 @@ export default class NotificationsTab extends React.Component {
} }
} }
onCustomChange() { onCustomChange() {
React.findDOMNode(this.refs.customcheck).checked = true; ReactDOM.findDOMNode(this.refs.customcheck).checked = true;
this.updateCustomMentionKeys(); this.updateCustomMentionKeys();
} }
render() { render() {

Просмотреть файл

@@ -82,7 +82,7 @@ export default class SecurityTab extends React.Component {
$('#user_settings').modal('hide'); $('#user_settings').modal('hide');
} }
handleClose() { handleClose() {
$(React.findDOMNode(this)).find('.form-control').each(function resetValue() { $(ReactDOM.findDOMNode(this)).find('.form-control').each(function resetValue() {
this.value = ''; this.value = '';
}); });
this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null}); this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});

Просмотреть файл

@@ -77,7 +77,7 @@ export default class ViewImageModal extends React.Component {
} }
onModalHidden() { onModalHidden() {
if (this.refs.video) { if (this.refs.video) {
var video = React.findDOMNode(this.refs.video); var video = ReactDOM.findDOMNode(this.refs.video);
video.pause(); video.pause();
video.currentTime = 0; video.currentTime = 0;
} }

Просмотреть файл

@@ -6,17 +6,17 @@ var SelectTeamModal = require('../components/admin_console/select_team_modal.jsx
var AdminController = require('../components/admin_console/admin_controller.jsx'); var AdminController = require('../components/admin_console/admin_controller.jsx');
export function setupAdminConsolePage() { export function setupAdminConsolePage() {
React.render( ReactDOM.render(
<AdminController />, <AdminController />,
document.getElementById('admin_controller') document.getElementById('admin_controller')
); );
React.render( ReactDOM.render(
<SelectTeamModal />, <SelectTeamModal />,
document.getElementById('select_team_modal') document.getElementById('select_team_modal')
); );
React.render( ReactDOM.render(
<ErrorBar/>, <ErrorBar/>,
document.getElementById('error_bar') document.getElementById('error_bar')
); );

Просмотреть файл

@@ -4,7 +4,7 @@
var Authorize = require('../components/authorize.jsx'); var Authorize = require('../components/authorize.jsx');
function setupAuthorizePage(props) { function setupAuthorizePage(props) {
React.render( ReactDOM.render(
<Authorize <Authorize
teamName={props.TeamName} teamName={props.TeamName}
appName={props.AppName} appName={props.AppName}

Просмотреть файл

@@ -24,7 +24,6 @@ var TeamSettingsModal = require('../components/team_settings_modal.jsx');
var ChannelMembersModal = require('../components/channel_members.jsx'); var ChannelMembersModal = require('../components/channel_members.jsx');
var ChannelInviteModal = require('../components/channel_invite_modal.jsx'); var ChannelInviteModal = require('../components/channel_invite_modal.jsx');
var TeamMembersModal = require('../components/team_members.jsx'); var TeamMembersModal = require('../components/team_members.jsx');
var DirectChannelModal = require('../components/more_direct_channels.jsx');
var ErrorBar = require('../components/error_bar.jsx'); var ErrorBar = require('../components/error_bar.jsx');
var ErrorStore = require('../stores/error_store.jsx'); var ErrorStore = require('../stores/error_store.jsx');
var ChannelLoader = require('../components/channel_loader.jsx'); var ChannelLoader = require('../components/channel_loader.jsx');
@@ -56,22 +55,22 @@ function setupChannelPage(props) {
}); });
// ChannelLoader must be rendered first // ChannelLoader must be rendered first
React.render( ReactDOM.render(
<ChannelLoader/>, <ChannelLoader/>,
document.getElementById('channel_loader') document.getElementById('channel_loader')
); );
React.render( ReactDOM.render(
<ErrorBar/>, <ErrorBar/>,
document.getElementById('error_bar') document.getElementById('error_bar')
); );
React.render( ReactDOM.render(
<Navbar teamDisplayName={props.TeamDisplayName} />, <Navbar teamDisplayName={props.TeamDisplayName} />,
document.getElementById('navbar') document.getElementById('navbar')
); );
React.render( ReactDOM.render(
<Sidebar <Sidebar
teamDisplayName={props.TeamDisplayName} teamDisplayName={props.TeamDisplayName}
teamName={props.TeamName} teamName={props.TeamName}
@@ -80,117 +79,112 @@ function setupChannelPage(props) {
document.getElementById('sidebar-left') document.getElementById('sidebar-left')
); );
React.render( ReactDOM.render(
<GetLinkModal />, <GetLinkModal />,
document.getElementById('get_link_modal') document.getElementById('get_link_modal')
); );
React.render( ReactDOM.render(
<UserSettingsModal />, <UserSettingsModal />,
document.getElementById('user_settings_modal') document.getElementById('user_settings_modal')
); );
React.render( ReactDOM.render(
<ImportThemeModal />, <ImportThemeModal />,
document.getElementById('import_theme_modal') document.getElementById('import_theme_modal')
); );
React.render( ReactDOM.render(
<TeamSettingsModal teamDisplayName={props.TeamDisplayName} />, <TeamSettingsModal teamDisplayName={props.TeamDisplayName} />,
document.getElementById('team_settings_modal') document.getElementById('team_settings_modal')
); );
React.render( ReactDOM.render(
<TeamMembersModal teamDisplayName={props.TeamDisplayName} />, <TeamMembersModal teamDisplayName={props.TeamDisplayName} />,
document.getElementById('team_members_modal') document.getElementById('team_members_modal')
); );
React.render( ReactDOM.render(
<MemberInviteModal teamType={props.TeamType} />, <MemberInviteModal teamType={props.TeamType} />,
document.getElementById('invite_member_modal') document.getElementById('invite_member_modal')
); );
React.render( ReactDOM.render(
<ChannelHeader />, <ChannelHeader />,
document.getElementById('channel-header') document.getElementById('channel-header')
); );
React.render( ReactDOM.render(
<EditChannelModal />, <EditChannelModal />,
document.getElementById('edit_channel_modal') document.getElementById('edit_channel_modal')
); );
React.render( ReactDOM.render(
<DeleteChannelModal />, <DeleteChannelModal />,
document.getElementById('delete_channel_modal') document.getElementById('delete_channel_modal')
); );
React.render( ReactDOM.render(
<RenameChannelModal />, <RenameChannelModal />,
document.getElementById('rename_channel_modal') document.getElementById('rename_channel_modal')
); );
React.render( ReactDOM.render(
<ChannelNotificationsModal />, <ChannelNotificationsModal />,
document.getElementById('channel_notifications_modal') document.getElementById('channel_notifications_modal')
); );
React.render( ReactDOM.render(
<ChannelMembersModal />, <ChannelMembersModal />,
document.getElementById('channel_members_modal') document.getElementById('channel_members_modal')
); );
React.render( ReactDOM.render(
<ChannelInviteModal />, <ChannelInviteModal />,
document.getElementById('channel_invite_modal') document.getElementById('channel_invite_modal')
); );
React.render( ReactDOM.render(
<ChannelInfoModal />, <ChannelInfoModal />,
document.getElementById('channel_info_modal') document.getElementById('channel_info_modal')
); );
React.render( ReactDOM.render(
<MoreChannelsModal />, <MoreChannelsModal />,
document.getElementById('more_channels_modal') document.getElementById('more_channels_modal')
); );
React.render( ReactDOM.render(
<DirectChannelModal />,
document.getElementById('direct_channel_modal')
);
React.render(
<PostListContainer />, <PostListContainer />,
document.getElementById('post-list') document.getElementById('post-list')
); );
React.render( ReactDOM.render(
<EditPostModal />, <EditPostModal />,
document.getElementById('edit_post_modal') document.getElementById('edit_post_modal')
); );
React.render( ReactDOM.render(
<DeletePostModal />, <DeletePostModal />,
document.getElementById('delete_post_modal') document.getElementById('delete_post_modal')
); );
React.render( ReactDOM.render(
<PostDeletedModal />, <PostDeletedModal />,
document.getElementById('post_deleted_modal') document.getElementById('post_deleted_modal')
); );
React.render( ReactDOM.render(
<CreatePost />, <CreatePost />,
document.getElementById('post-create') document.getElementById('post-create')
); );
React.render( ReactDOM.render(
<SidebarRight />, <SidebarRight />,
document.getElementById('sidebar-right') document.getElementById('sidebar-right')
); );
React.render( ReactDOM.render(
<SidebarRightMenu <SidebarRightMenu
teamDisplayName={props.TeamDisplayName} teamDisplayName={props.TeamDisplayName}
teamType={props.TeamType} teamType={props.TeamType}
@@ -198,44 +192,44 @@ function setupChannelPage(props) {
document.getElementById('sidebar-menu') document.getElementById('sidebar-menu')
); );
React.render( ReactDOM.render(
<MentionList id='post_textbox' />, <MentionList id='post_textbox' />,
document.getElementById('post_mention_tab') document.getElementById('post_mention_tab')
); );
React.render( ReactDOM.render(
<MentionList id='reply_textbox' />, <MentionList id='reply_textbox' />,
document.getElementById('reply_mention_tab') document.getElementById('reply_mention_tab')
); );
React.render( ReactDOM.render(
<MentionList id='edit_textbox' />, <MentionList id='edit_textbox' />,
document.getElementById('edit_mention_tab') document.getElementById('edit_mention_tab')
); );
React.render( ReactDOM.render(
<AccessHistoryModal />, <AccessHistoryModal />,
document.getElementById('access_history_modal') document.getElementById('access_history_modal')
); );
React.render( ReactDOM.render(
<ActivityLogModal />, <ActivityLogModal />,
document.getElementById('activity_log_modal') document.getElementById('activity_log_modal')
); );
React.render( ReactDOM.render(
<RemovedFromChannelModal />, <RemovedFromChannelModal />,
document.getElementById('removed_from_channel_modal') document.getElementById('removed_from_channel_modal')
); );
React.render( ReactDOM.render(
<FileUploadOverlay <FileUploadOverlay
overlayType='center' overlayType='center'
/>, />,
document.getElementById('file_upload_overlay') document.getElementById('file_upload_overlay')
); );
React.render( ReactDOM.render(
<RegisterAppModal />, <RegisterAppModal />,
document.getElementById('register_app_modal') document.getElementById('register_app_modal')
); );

Просмотреть файл

@@ -4,7 +4,7 @@
var FindTeam = require('../components/find_team.jsx'); var FindTeam = require('../components/find_team.jsx');
function setupFindTeamPage() { function setupFindTeamPage() {
React.render( ReactDOM.render(
<FindTeam />, <FindTeam />,
document.getElementById('find-team') document.getElementById('find-team')
); );

Просмотреть файл

@@ -4,7 +4,7 @@
var Login = require('../components/login.jsx'); var Login = require('../components/login.jsx');
function setupLoginPage(props) { function setupLoginPage(props) {
React.render( ReactDOM.render(
<Login <Login
teamDisplayName={props.TeamDisplayName} teamDisplayName={props.TeamDisplayName}
teamName={props.TeamName} teamName={props.TeamName}

Просмотреть файл

@@ -4,7 +4,7 @@
var PasswordReset = require('../components/password_reset.jsx'); var PasswordReset = require('../components/password_reset.jsx');
function setupPasswordResetPage(props) { function setupPasswordResetPage(props) {
React.render( ReactDOM.render(
<PasswordReset <PasswordReset
isReset={props.IsReset} isReset={props.IsReset}
teamDisplayName={props.TeamDisplayName} teamDisplayName={props.TeamDisplayName}

Просмотреть файл

@@ -4,7 +4,7 @@
var SignupTeam = require('../components/signup_team.jsx'); var SignupTeam = require('../components/signup_team.jsx');
function setupSignupTeamPage() { function setupSignupTeamPage() {
React.render( ReactDOM.render(
<SignupTeam />, <SignupTeam />,
document.getElementById('signup-team') document.getElementById('signup-team')
); );

Просмотреть файл

@@ -4,7 +4,7 @@
var SignupTeamComplete = require('../components/signup_team_complete.jsx'); var SignupTeamComplete = require('../components/signup_team_complete.jsx');
function setupSignupTeamCompletePage(props) { function setupSignupTeamCompletePage(props) {
React.render( ReactDOM.render(
<SignupTeamComplete <SignupTeamComplete
email={props.Email} email={props.Email}
hash={props.Hash} hash={props.Hash}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше