summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md235
-rw-r--r--doc/README.md5
-rw-r--r--doc/config/smtp-email-setup.md38
-rw-r--r--doc/install/aws-ebs-setup.md27
-rw-r--r--doc/install/requirements.md (renamed from requirements.md)0
-rw-r--r--doc/install/single-container-install.md120
-rw-r--r--web/react/components/access_history_modal.jsx108
-rw-r--r--web/react/components/activity_log_modal.jsx179
-rw-r--r--web/react/components/channel_header.jsx436
-rw-r--r--web/react/components/channel_loader.jsx45
-rw-r--r--web/react/components/channel_members.jsx280
-rw-r--r--web/react/components/channel_notifications.jsx152
-rw-r--r--web/react/components/confirm_modal.jsx83
-rw-r--r--web/react/components/create_comment.jsx213
-rw-r--r--web/react/components/create_post.jsx267
-rw-r--r--web/react/components/delete_channel_modal.jsx127
-rw-r--r--web/react/components/delete_post_modal.jsx148
-rw-r--r--web/react/components/edit_channel_modal.jsx181
-rw-r--r--web/react/components/edit_post_modal.jsx145
-rw-r--r--web/react/components/email_verify.jsx65
-rw-r--r--web/react/components/error_bar.jsx80
-rw-r--r--web/react/components/file_attachment.jsx188
-rw-r--r--web/react/components/file_attachment_list.jsx56
-rw-r--r--web/react/components/file_preview.jsx106
-rw-r--r--web/react/components/file_upload_overlay.jsx14
-rw-r--r--web/react/components/get_link_modal.jsx141
-rw-r--r--web/react/components/loading_screen.jsx37
-rw-r--r--web/react/components/login.jsx46
-rw-r--r--web/react/components/member_list_item.jsx118
-rw-r--r--web/react/components/member_list_team.jsx131
-rw-r--r--web/react/components/member_list_team_item.jsx203
-rw-r--r--web/react/components/mention.jsx61
-rw-r--r--web/react/components/mention_list.jsx140
-rw-r--r--web/react/components/message_wrapper.jsx27
-rw-r--r--web/react/components/navbar.jsx436
-rw-r--r--web/react/components/navbar_dropdown.jsx209
-rw-r--r--web/react/components/new_channel.jsx119
-rw-r--r--web/react/components/notify_counts.jsx29
-rw-r--r--web/react/components/password_reset.jsx164
-rw-r--r--web/react/components/password_reset_form.jsx100
-rw-r--r--web/react/components/password_reset_send_link.jsx98
-rw-r--r--web/react/components/popover_list_members.jsx80
-rw-r--r--web/react/components/post.jsx120
-rw-r--r--web/react/components/post_body.jsx127
-rw-r--r--web/react/components/post_deleted_modal.jsx73
-rw-r--r--web/react/components/post_header.jsx41
-rw-r--r--web/react/components/rename_channel_modal.jsx286
-rw-r--r--web/react/components/rhs_comment.jsx12
-rw-r--r--web/react/components/rhs_header_post.jsx8
-rw-r--r--web/react/components/search_bar.jsx126
-rw-r--r--web/react/components/setting_item_max.jsx67
-rw-r--r--web/react/components/setting_item_min.jsx35
-rw-r--r--web/react/components/settings_sidebar.jsx60
-rw-r--r--web/react/components/sidebar.jsx574
-rw-r--r--web/react/components/sidebar_header.jsx154
-rw-r--r--web/react/components/sidebar_right.jsx92
-rw-r--r--web/react/components/signup_team.jsx22
-rw-r--r--web/react/components/signup_team_complete.jsx106
-rw-r--r--web/react/components/signup_user_complete.jsx131
-rw-r--r--web/react/components/team_feature_tab.jsx116
-rw-r--r--web/react/components/team_general_tab.jsx37
-rw-r--r--web/react/components/team_settings.jsx121
-rw-r--r--web/react/components/team_settings_modal.jsx116
-rw-r--r--web/react/components/team_signup_allowed_domains_page.jsx96
-rw-r--r--web/react/components/team_signup_display_name_page.jsx68
-rw-r--r--web/react/components/team_signup_email_item.jsx60
-rw-r--r--web/react/components/team_signup_password_page.jsx93
-rw-r--r--web/react/components/team_signup_send_invites_page.jsx11
-rw-r--r--web/react/components/team_signup_url_page.jsx111
-rw-r--r--web/react/components/team_signup_username_page.jsx79
-rw-r--r--web/react/components/team_signup_welcome_page.jsx106
-rw-r--r--web/react/components/team_signup_with_email.jsx18
-rw-r--r--web/react/components/textbox.jsx282
-rw-r--r--web/react/components/user_profile.jsx104
-rw-r--r--web/react/components/user_settings_appearance.jsx168
-rw-r--r--web/react/components/user_settings_general.jsx43
-rw-r--r--web/react/components/user_settings_modal.jsx86
-rw-r--r--web/react/components/user_settings_notifications.jsx100
-rw-r--r--web/react/components/user_settings_security.jsx180
-rw-r--r--web/react/components/view_image.jsx292
-rw-r--r--web/react/dispatcher/app_dispatcher.jsx30
-rw-r--r--web/react/pages/channel.jsx31
-rw-r--r--web/react/pages/find_team.jsx6
-rw-r--r--web/react/pages/home.jsx9
-rw-r--r--web/react/pages/login.jsx11
-rw-r--r--web/react/pages/password_reset.jsx12
-rw-r--r--web/react/pages/signup_team.jsx6
-rw-r--r--web/react/pages/signup_team_complete.jsx13
-rw-r--r--web/react/pages/signup_user_complete.jsx15
-rw-r--r--web/react/pages/verify.jsx5
-rw-r--r--web/react/stores/browser_store.jsx80
-rw-r--r--web/react/stores/channel_store.jsx256
-rw-r--r--web/react/stores/config_store.jsx65
-rw-r--r--web/react/stores/error_store.jsx81
-rw-r--r--web/react/stores/post_store.jsx387
-rw-r--r--web/react/stores/socket_store.jsx67
-rw-r--r--web/react/stores/team_store.jsx97
-rw-r--r--web/react/stores/user_store.jsx336
-rw-r--r--web/react/utils/async_client.jsx293
-rw-r--r--web/react/utils/client.jsx297
-rw-r--r--web/react/utils/constants.jsx182
-rw-r--r--web/react/utils/utils.jsx484
102 files changed, 7670 insertions, 4851 deletions
diff --git a/README.md b/README.md
index 1dd9620d6..b469754ea 100644
--- a/README.md
+++ b/README.md
@@ -1,214 +1,55 @@
-**Mattermost Alpha**
-**Team Communication Service**
-**Development Build**
+# Mattermost is an open source, on-prem Slack Alternative
+Mattermost modernizes team communication without locking in your data to a single provider.
-About Mattermost
-================
+Offer your end users messaging and file sharing across PCs and phones with archiving and instant search--without losing control of your data.
-Mattermost is an open-source team communication service. It brings team messaging and file sharing into one place, accessible across PCs and phones, with archiving and search.
+## Features
-Learn More
-==========
-- Ask the core team anything at: http://forum.mattermost.org
-- Share feature requests and upvotes: http://www.mattermost.org/feature-requests/
-- File bugs: http://www.mattermost.org/filing-issues/
-- Make a pull request: http://www.mattermost.org/contribute-to-mattermost/
+### Sharing Messaging and Files
+- Send messages and comments across public, private and 1-1 channels
+- Personalize notifications for unreads and mentions by channel and keyword
+- Share files and images internally and externally
-Installing Mattermost
-=====================
+### Archiving and Search
-You're installing "Mattermost Alpha", a pre-released version providing an early look at what we're building. While the core team runs this version internally, it's not recommended for production since we can't guarantee API stability or backwards compatibility.
+- Search archives across channels for historical messages and comments
+- Use #hashtags to tag and recall messages, threads and files
+- View recent mentions of your name and custom search terms
-That said, any issues at all, please let us know on the Mattermost forum at: http://forum.mattermost.org
+### Anywhere Access
-Notes:
-- For Alpha, Docker is intentionally setup as a single container, since production deployment is not yet recommended.
+- Use Mattermost from web-enabled PCs and phones
+- Attach sound, video and image files from mobile devices
+- Define team-specific color themes across your devices
-Local Machine Setup (Docker)
------------------------------
+## Learn More
-### Mac OSX ###
+- [Product Vision and Target Audiences](http://www.mattermost.org/vision/) - What we're solving and for whom are we building
+- [Mattermost Forum](http://forum.mattermost.org/) - For technical questions and answers
+- [Issue Tracker](http://www.mattermost.org/filing-issues/) - For reporting bugs
+- [Feature Ideas Forum](http://www.mattermost.org/feature-requests/) - For sharing ideas for future versions
+- [Contributuion Guidelines](http://www.mattermost.org/contribute-to-mattermost/) - For contributing code or feedback to the project
-1. Install Boot2Docker using instructions at: http://docs.docker.com/installation/mac/
- 1. Start Boot2Docker from the command line and run: `boot2docker init eval “$(boot2docker shellinit)”`
-2. Get your Docker IP address with: `boot2docker ip`
-3. Use `sudo nano /etc/hosts` to add `<Docker IP> dockerhost` to your /etc/hosts file
-4. Run: `boot2docker shellinit` and copy the export statements to your ~/.bash\_profile by running `sudo nano ~/.bash_profile`. Then run: `source ~/.bash_profile`
-5. Run: `docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform`
-6. When docker is done fetching the image, open http://dockerhost:8065/ in your browser.
+Follow us on Twitter at [@MattermostHQ](https://twitter.com/mattermosthq).
-### Ubuntu ###
-1. Follow the instructions at https://docs.docker.com/installation/ubuntulinux/ or use the summary below:
+# Installing Mattermost
- ``` bash
- sudo apt-get update
- sudo apt-get install wget
- wget -qO- https://get.docker.com/ | sh
- sudo usermod -aG docker <username>
- sudo service docker start
- newgrp docker
- ```
+Depending on your needs, there are multiple ways to install Mattermost:
-2. Start docker container:
+### Product Evaluation
- ``` bash
- docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform
- ```
-
-3. When docker is done fetching the image, open http://localhost:8065/ in your browser.
-
-### Arch ###
-1. Install Docker using the following commands:
-
- ``` bash
- pacman -S docker
- systemctl enable docker.service
- systemctl start docker.service
- gpasswd -a <username> docker
- newgrp docker
- ```
-
-2. Start Docker container:
-
- ``` bash
- docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform
- ```
-
-3. When Docker is done fetching the image, open http://localhost:8065/ in your browser.
-
-### Additional Notes ###
-- If you want to work with the latest master from the repository (i.e. not a stable release) you can run the cmd:
-
- ``` bash
- docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:dev
- ```
-
-- Instructions on how to update your Docker image are found below.
-
-- If you wish to remove mattermost-dev use:
-
- ``` bash
- docker stop mattermost-dev
- docker rm -v mattermost-dev
- ```
-
-- If you wish to gain access to a shell on the container use:
-
- ``` bash
- docker exec -ti mattermost-dev /bin/bash
- ```
-
-AWS Elastic Beanstalk Setup (Docker)
-------------------------------------
-
-1. Create a new Elastic Beanstalk Docker application using the [Dockerrun.aws.zip](docker/0.6/Dockerrun.aws//Dockerrun.aws.zip) file provided.
- 1. From the AWS console select Elastic Beanstalk.
- 2. Select "Create New Application" from the top right.
- 3. Name the application and press next.
- 4. Select "Create a web server" environment.
- 5. If asked, select create an IAM role and instance profile and press next.
- 6. For predefined configuration select under Generic: Docker. For environment type select single instance.
- 7. For application source, select upload your own and upload Dockerrun.aws.zip from [Dockerrun.aws.zip](docker/0.6/Dockerrun.aws//Dockerrun.aws.zip). Everything else may be left at default.
- 8. Select an environment name, this is how you will refer to your environment. Make sure the URL is available then press next.
- 9. The options on the additional resources page may be left at default unless you wish to change them. Press Next.
- 10. On the configuration details place. Select an instance type of t2.small or larger.
- 11. You can set the configuration details as you please but they may be left at their defaults. When you are done press next.
- 12. Environment tags my be left blank. Press next.
- 13. You will be asked to review your information. Press Launch.
-
-4. Try it out!
- 14. Wait for beanstalk to update the environment.
- 15. Try it out by entering the domain of the form \*.elasticbeanstalk.com found at the top of the dashboard into your browser. You can also map your own domain if you wish.
-
-Configuration Settings
-----------------------
-
-There are a few configuration settings you might want to adjust when setting up your instance of Mattermost. You can edit them in [config/config.json](config/config.json) or [docker/0.6/config_docker.json](docker/0.6/config_docker.json) if you're running a Docker instance.
-
-* *EmailSettings*:*ByPassEmail* - If this is set to true, then users on the system will not need to verify their email addresses when signing up. In addition, no emails will ever be sent.
-* *ServiceSettings*:*UseLocalStorage* - If this is set to true, then your Mattermost server will store uploaded files in the storage directory specified by *StorageDirectory*. *StorageDirectory* must be set if *UseLocalStorage* is set to true.
-* *ServiceSettings*:*StorageDirectory* - The file path where files will be stored locally if *UseLocalStorage* is set to true. The operating system user that is running the Mattermost application must have read and write privileges to this directory.
-* *AWSSettings*:*S3*\* - If *UseLocalStorage* is set to false, and the S3 settings are configured here, then Mattermost will store files in the provided S3 bucket.
-
-Email Setup (Optional)
-----------------------
-
-1. Setup an email sending service. If you already have credentials for a SMTP server you can skip this step.
- 1. [Setup Amazon Simple Email Service](https://console.aws.amazon.com/ses)
- 2. From the `SMTP Settings` menu click `Create My SMTP Credentials`
- 3. Copy the `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.
- 5. Choose an email address like `feedback@example.com` for Mattermost to send emails from.
- 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.
-2. Modify the Mattermost configuration file config.json or config_docker.json with the SMTP information.
- 1. If you're running Mattermost on Amazon Beanstalk you can shell into the instance with the following commands
- 2. `ssh ec2-user@[domain for the docker instance]`
- 3. `sudo gpasswd -a ec2-user docker`
- 4. Retrieve the name of the container with `sudo docker ps`
- 5. `sudo docker exec -ti container_name /bin/bash`
-2. Edit the config file `vi /config_docker.json` with the settings you captured from the step above. See an example below and notice `ByPassEmail` has been set to `false`
-
-``` bash
-"EmailSettings": {
- "ByPassEmail" : false,
- "SMTPUsername": "AKIADTOVBGERKLCBV",
- "SMTPPassword": "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY",
- "SMTPServer": "email-smtp.us-east-1.amazonaws.com:465",
- "UseTLS": true,
- "FeedbackEmail": "feedback@example.com",
- "FeedbackName": "Feedback",
- "ApplePushServer": "",
- "ApplePushCertPublic": "",
- "ApplePushCertPrivate": ""
-}
-```
-
-3. Restart Mattermost
- 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.
-
-Upgrading Mattermost Preview
-----------------------------
-
-### Docker ###
-To upgrade your Docker image to a preview of the latest stable release (NOTE: this will erase all data in the Docker container, including the database):
-
-1. Stop your Docker container by running:
-
- ``` bash
- docker stop mattermost-dev
- ```
-2. Delete your Docker container by running:
-
- ``` bash
- docker rm mattermost-dev
- ```
-3. Update your Docker image by running:
-
- ``` bash
- docker pull mattermost/platform
- ```
-4. Start your Docker container by running:
-
- ``` bash
- docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform
- ```
-
-To upgrade to the latest development build on master from the repository replace `mattermost/platform` with `mattermost/platform:dev` in the instructions 3) and 4) above.
-
-Contributing
-------------
-
-To contribute to this open source project please review the [Mattermost Contribution Guidelines]( http://www.mattermost.org/contribute-to-mattermost/).
-
-To setup your machine for development of mattermost see: [Developer Machine Setup](scripts/README_DEV.md).
-
-License
--------
-
-Mattermost is licensed under an "Apache-wrapped AGPL" model inspired by MongoDB. Similar to MongoDB, you can run and link to the system using Configuration Files and Admin Tools licensed under Apache, version 2.0, as described in the LICENSE file, as an explicit exception to the terms of the GNU Affero General Public License (AGPL) that applies to most of the remaining source files. See individual files for details.
+- [Local Machine Install and Upgrade with Docker](doc/install/single-container-install.md) - Explore product functionality using a single-container Docker install on a local machine, including Mac OSX, Ubuntu, or Arch Linux). Optionally set up email and upgrade your instance using DockerHub.
+- [AWS EBS Install and Upgrade with Docker](doc/install/aws-ebs-setup.md) - Explore product functionality using a single-container Docker install for Amazon Web Services Elastic Beanstalk. Optionally set up email and upgrade your instance using DockerHub.
+
+### Development
+
+- [Developer Machine Setup](scripts/README_DEV.md) - Setup your local machine development environment using Docker on Mac OSX or Ubuntu.
+
+### Production Deployment
+
+- [GitLab Mattermost Production Installation](https://about.gitlab.com/downloads/) - Install Mattermost for production environments bundled with GitLab, a leading open source Git repository, using an omnibus package for Ubuntu 12.04, Ubuntu 14.04, Debian 7, Debian 8, and CentOS 6 (and RedHat/Oracle/Scientific Linux 6), CentOS 7 (and RedHat/Oracle/Scientific Linux 7).
+
+Any issues at all, please let us know on the Mattermost forum at: http://forum.mattermost.org
diff --git a/doc/README.md b/doc/README.md
new file mode 100644
index 000000000..5195327b2
--- /dev/null
+++ b/doc/README.md
@@ -0,0 +1,5 @@
+# Documentation
+
+## Administrator Documentation
+
+- [GitLab SSO Configuration](integrations/sso/gitlab-sso.md) - Configure OAuth2 Single-Sign-On for GitLab.
diff --git a/doc/config/smtp-email-setup.md b/doc/config/smtp-email-setup.md
new file mode 100644
index 000000000..b90d78919
--- /dev/null
+++ b/doc/config/smtp-email-setup.md
@@ -0,0 +1,38 @@
+
+## SMTP Email Setup
+
+The following instructions maybe used when SMTP email is not setup as part of the installation process.
+
+1. Setup an email sending service. If you already have credentials for a SMTP server you can skip this step.
+ 1. [Setup Amazon Simple Email Service](https://console.aws.amazon.com/ses)
+ 2. From the `SMTP Settings` menu click `Create My SMTP Credentials`
+ 3. Copy the `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.
+ 5. Choose an email address like `feedback@example.com` for Mattermost to send emails from.
+ 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.
+2. Modify the Mattermost configuration file config.json or config_docker.json with the SMTP information.
+ 1. If you're running Mattermost on Amazon Beanstalk you can shell into the instance with the following commands
+ 2. `ssh ec2-user@[domain for the docker instance]`
+ 3. `sudo gpasswd -a ec2-user docker`
+ 4. Retrieve the name of the container with `sudo docker ps`
+ 5. `sudo docker exec -ti container_name /bin/bash`
+3. Edit the config file `vi /config_docker.json` with the settings you captured from the step above. See an example below and notice `ByPassEmail` has been set to `false`
+``` bash
+"EmailSettings": {
+ "ByPassEmail" : false,
+ "SMTPUsername": "AKIADTOVBGERKLCBV",
+ "SMTPPassword": "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY",
+ "SMTPServer": "email-smtp.us-east-1.amazonaws.com:465",
+ "UseTLS": true,
+ "FeedbackEmail": "feedback@example.com",
+ "FeedbackName": "Feedback",
+ "ApplePushServer": "",
+ "ApplePushCertPublic": "",
+ "ApplePushCertPrivate": ""
+}
+```
+4. Restart Mattermost
+ 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.
diff --git a/doc/install/aws-ebs-setup.md b/doc/install/aws-ebs-setup.md
new file mode 100644
index 000000000..452cfcb4f
--- /dev/null
+++ b/doc/install/aws-ebs-setup.md
@@ -0,0 +1,27 @@
+
+## AWS Elastic Beanstalk Setup (Docker)
+
+1. Create a new Elastic Beanstalk Docker application using the [Dockerrun.aws.zip](docker/0.6/Dockerrun.aws//Dockerrun.aws.zip) file provided.
+ 1. From the AWS console select Elastic Beanstalk.
+ 2. Select "Create New Application" from the top right.
+ 3. Name the application and press next.
+ 4. Select "Create a web server" environment.
+ 5. If asked, select create an IAM role and instance profile and press next.
+ 6. For predefined configuration select under Generic: Docker. For environment type select single instance.
+ 7. For application source, select upload your own and upload Dockerrun.aws.zip from [Dockerrun.aws.zip](docker/0.6/Dockerrun.aws//Dockerrun.aws.zip). Everything else may be left at default.
+ 8. Select an environment name, this is how you will refer to your environment. Make sure the URL is available then press next.
+ 9. The options on the additional resources page may be left at default unless you wish to change them. Press Next.
+ 10. On the configuration details place. Select an instance type of t2.small or larger.
+ 11. You can set the configuration details as you please but they may be left at their defaults. When you are done press next.
+ 12. Environment tags my be left blank. Press next.
+ 13. You will be asked to review your information. Press Launch.
+
+4. Try it out!
+ 14. Wait for beanstalk to update the environment.
+ 15. Try it out by entering the domain of the form \*.elasticbeanstalk.com found at the top of the dashboard into your browser. You can also map your own domain if you wish.
+
+
+ ### (Recommended) Enable Email
+ The default single-container Docker instance for Mattermost is designed for product evaluation, and sets `ByPassEmail=true` so the product can run without enabling email, when doing so maybe difficult.
+
+ To see the product's full functionality, [enabling SMTP email is recommended](doc/config/smtp-email-setup.md).
diff --git a/requirements.md b/doc/install/requirements.md
index cc0d1833d..cc0d1833d 100644
--- a/requirements.md
+++ b/doc/install/requirements.md
diff --git a/doc/install/single-container-install.md b/doc/install/single-container-install.md
new file mode 100644
index 000000000..3e307ca74
--- /dev/null
+++ b/doc/install/single-container-install.md
@@ -0,0 +1,120 @@
+# Single Container Installation and Upgrade
+
+The following install instructions are for single-container installs of Mattermost using Docker for exploring product functionality and upgrading to newer versions.
+
+Local Machine Setup (Docker)
+-----------------------------
+
+### Mac OSX ###
+
+1. Install Boot2Docker using instructions at: http://docs.docker.com/installation/mac/
+ 1. Start Boot2Docker from the command line and run: `boot2docker init eval “$(boot2docker shellinit)”`
+2. Get your Docker IP address with: `boot2docker ip`
+3. Use `sudo nano /etc/hosts` to add `<Docker IP> dockerhost` to your /etc/hosts file
+4. Run: `boot2docker shellinit` and copy the export statements to your ~/.bash\_profile by running `sudo nano ~/.bash_profile`. Then run: `source ~/.bash_profile`
+5. Run: `docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform`
+6. When docker is done fetching the image, open http://dockerhost:8065/ in your browser.
+
+### Ubuntu ###
+1. Follow the instructions at https://docs.docker.com/installation/ubuntulinux/ or use the summary below:
+
+ ``` bash
+ sudo apt-get update
+ sudo apt-get install wget
+ wget -qO- https://get.docker.com/ | sh
+ sudo usermod -aG docker <username>
+ sudo service docker start
+ newgrp docker
+ ```
+
+2. Start docker container:
+
+ ``` bash
+ docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform
+ ```
+
+3. When docker is done fetching the image, open http://localhost:8065/ in your browser.
+
+### Arch ###
+1. Install Docker using the following commands:
+
+ ``` bash
+ pacman -S docker
+ systemctl enable docker.service
+ systemctl start docker.service
+ gpasswd -a <username> docker
+ newgrp docker
+ ```
+
+2. Start Docker container:
+
+ ``` bash
+ docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform
+ ```
+
+3. When Docker is done fetching the image, open http://localhost:8065/ in your browser.
+
+### Additional Notes ###
+- If you want to work with the latest master from the repository (i.e. not a stable release) you can run the cmd:
+
+ ``` bash
+ docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:dev
+ ```
+
+- Instructions on how to update your Docker image are found below.
+
+- If you wish to remove mattermost-dev use:
+
+ ``` bash
+ docker stop mattermost-dev
+ docker rm -v mattermost-dev
+ ```
+
+- If you wish to gain access to a shell on the container use:
+
+ ``` bash
+ docker exec -ti mattermost-dev /bin/bash
+ ```
+
+## Configuration Settings
+
+There are a few configuration settings you might want to adjust when setting up your instance of Mattermost. You can edit them in [config/config.json](config/config.json) or [docker/0.6/config_docker.json](docker/0.6/config_docker.json) if you're running a Docker instance.
+
+* *EmailSettings*:*ByPassEmail* - If this is set to true, then users on the system will not need to verify their email addresses when signing up. In addition, no emails will ever be sent.
+* *ServiceSettings*:*UseLocalStorage* - If this is set to true, then your Mattermost server will store uploaded files in the storage directory specified by *StorageDirectory*. *StorageDirectory* must be set if *UseLocalStorage* is set to true.
+* *ServiceSettings*:*StorageDirectory* - The file path where files will be stored locally if *UseLocalStorage* is set to true. The operating system user that is running the Mattermost application must have read and write privileges to this directory.
+* *AWSSettings*:*S3*\* - If *UseLocalStorage* is set to false, and the S3 settings are configured here, then Mattermost will store files in the provided S3 bucket.
+
+### (Recommended) Enable Email
+
+The default single-container Docker instance for Mattermost is designed for product evaluation, and sets `ByPassEmail=true` so the product can run without enabling email, when doing so maybe difficult.
+
+To see the product's full functionality, [enabling SMTP email is recommended](doc/config/smtp-email-setup.md).
+
+## Upgrading Mattermost
+
+### Docker ###
+To upgrade your Docker image to a preview of the latest stable release (NOTE: this will erase all data in the Docker container, including the database):
+
+1. Stop your Docker container by running:
+
+ ``` bash
+ docker stop mattermost-dev
+ ```
+2. Delete your Docker container by running:
+
+ ``` bash
+ docker rm mattermost-dev
+ ```
+3. Update your Docker image by running:
+
+ ``` bash
+ docker pull mattermost/platform
+ ```
+4. Start your Docker container by running:
+
+ ``` bash
+ docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform
+ ```
+
+To upgrade to the latest development build on master from the repository replace `mattermost/platform` with `mattermost/platform:dev` in the instructions 3) and 4) above.
diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx
index a19e5c16e..9c8e7c6c3 100644
--- a/web/react/components/access_history_modal.jsx
+++ b/web/react/components/access_history_modal.jsx
@@ -4,48 +4,49 @@
var UserStore = require('../stores/user_store.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var LoadingScreen = require('./loading_screen.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
-function getStateFromStoresForAudits() {
- return {
- audits: UserStore.getAudits()
- };
-}
+export default class AccessHistoryModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onAuditChange = this.onAuditChange.bind(this);
+ this.handleMoreInfo = this.handleMoreInfo.bind(this);
-module.exports = React.createClass({
- displayName: 'AccessHistoryModal',
- componentDidMount: function() {
- UserStore.addAuditsChangeListener(this.onListenerChange);
- $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function() {
+ this.state = this.getStateFromStoresForAudits();
+ this.state.moreInfo = [];
+ }
+ getStateFromStoresForAudits() {
+ return {
+ audits: UserStore.getAudits()
+ };
+ }
+ componentDidMount() {
+ UserStore.addAuditsChangeListener(this.onAuditChange);
+ $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function show() {
AsyncClient.getAudits();
});
- var self = this;
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function() {
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function hide() {
$('#user_settings').modal('show');
- self.setState({moreInfo: []});
- });
- },
- componentWillUnmount: function() {
- UserStore.removeAuditsChangeListener(this.onListenerChange);
- },
- onListenerChange: function() {
- var newState = getStateFromStoresForAudits();
- if (!utils.areStatesEqual(newState.audits, this.state.audits)) {
+ this.setState({moreInfo: []});
+ }.bind(this));
+ }
+ componentWillUnmount() {
+ UserStore.removeAuditsChangeListener(this.onAuditChange);
+ }
+ onAuditChange() {
+ var newState = this.getStateFromStoresForAudits();
+ if (!Utils.areStatesEqual(newState.audits, this.state.audits)) {
this.setState(newState);
}
- },
- handleMoreInfo: function(index) {
+ }
+ handleMoreInfo(index) {
var newMoreInfo = this.state.moreInfo;
newMoreInfo[index] = true;
this.setState({moreInfo: newMoreInfo});
- },
- getInitialState: function() {
- var initialState = getStateFromStoresForAudits();
- initialState.moreInfo = [];
- return initialState;
- },
- render: function() {
+ }
+ render() {
var accessList = [];
var currentHistoryDate = null;
@@ -63,7 +64,16 @@ module.exports = React.createClass({
currentAudit.session_id = 'N/A (Login attempt)';
}
- var moreInfo = (<a href='#' className='theme' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>);
+ var moreInfo = (
+ <a
+ href='#'
+ className='theme'
+ onClick={this.handleMoreInfo.bind(this, i)}
+ >
+ More info
+ </a>
+ );
+
if (this.state.moreInfo[i]) {
moreInfo = (
<div>
@@ -75,7 +85,7 @@ module.exports = React.createClass({
var divider = null;
if (i < this.state.audits.length - 1) {
- divider = (<div className='divider-light'></div>)
+ divider = (<div className='divider-light'></div>);
}
accessList[i] = (
@@ -102,14 +112,36 @@ module.exports = React.createClass({
return (
<div>
- <div className='modal fade' ref='modal' id='access-history' tabIndex='-1' role='dialog' aria-hidden='true'>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='access-history'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
<div className='modal-dialog modal-lg'>
<div className='modal-content'>
<div className='modal-header'>
- <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
- <h4 className='modal-title' id='myModalLabel'>Access History</h4>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4
+ className='modal-title'
+ id='myModalLabel'
+ >
+ Access History
+ </h4>
</div>
- <div ref='modalBody' className='modal-body'>
+ <div
+ ref='modalBody'
+ className='modal-body'
+ >
{content}
</div>
</div>
@@ -118,4 +150,4 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index 1192a72bc..2a83b3c40 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -1,102 +1,104 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserStore = require('../stores/user_store.jsx');
-var Client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var LoadingScreen = require('./loading_screen.jsx');
-var utils = require('../utils/utils.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const LoadingScreen = require('./loading_screen.jsx');
+const Utils = require('../utils/utils.jsx');
-function getStateFromStoresForSessions() {
- return {
- sessions: UserStore.getSessions(),
- serverError: null,
- clientError: null
- };
-}
+export default class ActivityLogModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitRevoke = this.submitRevoke.bind(this);
+ this.onListenerChange = this.onListenerChange.bind(this);
+ this.handleMoreInfo = this.handleMoreInfo.bind(this);
-module.exports = React.createClass({
- displayName: 'ActivityLogModal',
- submitRevoke: function(altId) {
+ this.state = this.getStateFromStores();
+ this.state.moreInfo = [];
+ }
+ getStateFromStores() {
+ return {
+ sessions: UserStore.getSessions(),
+ serverError: null,
+ clientError: null
+ };
+ }
+ submitRevoke(altId) {
Client.revokeSession(altId,
- function(data) {
+ function handleRevokeSuccess() {
AsyncClient.getSessions();
- }.bind(this),
- function(err) {
- var state = getStateFromStoresForSessions();
+ },
+ function handleRevokeError(err) {
+ let state = this.getStateFromStores();
state.serverError = err;
this.setState(state);
}.bind(this)
);
- },
- componentDidMount: function() {
+ }
+ componentDidMount() {
UserStore.addSessionsChangeListener(this.onListenerChange);
- $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) {
+ $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function handleShow() {
AsyncClient.getSessions();
});
- var self = this;
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function handleHide() {
$('#user_settings').modal('show');
- self.setState({moreInfo: []});
- });
- },
- componentWillUnmount: function() {
+ this.setState({moreInfo: []});
+ }.bind(this));
+ }
+ componentWillUnmount() {
UserStore.removeSessionsChangeListener(this.onListenerChange);
- },
- onListenerChange: function() {
- var newState = getStateFromStoresForSessions();
- if (!utils.areStatesEqual(newState.sessions, this.state.sessions)) {
+ }
+ onListenerChange() {
+ const newState = this.getStateFromStores();
+ if (!Utils.areStatesEqual(newState.sessions, this.state.sessions)) {
this.setState(newState);
}
- },
- handleMoreInfo: function(index) {
- var newMoreInfo = this.state.moreInfo;
+ }
+ handleMoreInfo(index) {
+ let newMoreInfo = this.state.moreInfo;
newMoreInfo[index] = true;
this.setState({moreInfo: newMoreInfo});
- },
- getInitialState: function() {
- var initialState = getStateFromStoresForSessions();
- initialState.moreInfo = [];
- return initialState;
- },
- render: function() {
- var activityList = [];
- var serverError = this.state.serverError;
-
- // Squash any false-y value for server error into null
- if (!serverError) {
- serverError = null;
- }
+ }
+ render() {
+ let activityList = [];
- for (var i = 0; i < this.state.sessions.length; i++) {
- var currentSession = this.state.sessions[i];
- var lastAccessTime = new Date(currentSession.last_activity_at);
- var firstAccessTime = new Date(currentSession.create_at);
- var devicePicture = '';
+ for (let i = 0; i < this.state.sessions.length; i++) {
+ const currentSession = this.state.sessions[i];
+ const lastAccessTime = new Date(currentSession.last_activity_at);
+ const firstAccessTime = new Date(currentSession.create_at);
+ let devicePicture = '';
if (currentSession.props.platform === 'Windows') {
devicePicture = 'fa fa-windows';
- }
- else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') {
+ } else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') {
devicePicture = 'fa fa-apple';
- }
- else if (currentSession.props.platform === 'Linux') {
+ } else if (currentSession.props.platform === 'Linux') {
devicePicture = 'fa fa-linux';
}
- var moreInfo;
+ let moreInfo;
if (this.state.moreInfo[i]) {
moreInfo = (
<div>
- <div>{'First time active: ' + firstAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div>
- <div>{'OS: ' + currentSession.props.os}</div>
- <div>{'Browser: ' + currentSession.props.browser}</div>
- <div>{'Session ID: ' + currentSession.alt_id}</div>
+ <div>{`First time active: ${firstAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
+ <div>{`OS: ${currentSession.props.os}`}</div>
+ <div>{`Browser: ${currentSession.props.browser}`}</div>
+ <div>{`Session ID: ${currentSession.alt_id}`}</div>
</div>
);
} else {
- moreInfo = (<a className='theme' href='#' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>);
+ moreInfo = (
+ <a
+ className='theme'
+ href='#'
+ onClick={this.handleMoreInfo.bind(this, i)}
+ >
+ More info
+ </a>
+ );
}
activityList[i] = (
@@ -104,33 +106,62 @@ module.exports = React.createClass({
<div className='activity-log__report'>
<div className='report__platform'><i className={devicePicture} />{currentSession.props.platform}</div>
<div className='report__info'>
- <div>{'Last activity: ' + lastAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div>
+ <div>{`Last activity: ${lastAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
{moreInfo}
</div>
</div>
- <div className='activity-log__action'><button onClick={this.submitRevoke.bind(this, currentSession.alt_id)} className='btn btn-primary'>Logout</button></div>
+ <div className='activity-log__action'>
+ <button
+ onClick={this.submitRevoke.bind(this, currentSession.alt_id)}
+ className='btn btn-primary'
+ >
+ Logout
+ </button>
+ </div>
</div>
);
}
- var content;
+ let content;
if (this.state.sessions.loading) {
- content = (<LoadingScreen />);
+ content = <LoadingScreen />;
} else {
- content = (<form role='form'>{activityList}</form>);
+ content = <form role='form'>{activityList}</form>;
}
return (
<div>
- <div className='modal fade' ref='modal' id='activity-log' tabIndex='-1' role='dialog' aria-hidden='true'>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='activity-log'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
<div className='modal-dialog modal-lg'>
<div className='modal-content'>
<div className='modal-header'>
- <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
- <h4 className='modal-title' id='myModalLabel'>Active Sessions</h4>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4
+ className='modal-title'
+ id='myModalLabel'
+ >
+ Active Sessions
+ </h4>
</div>
<p className='session-help-text'>Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session.</p>
- <div ref='modalBody' className='modal-body'>
+ <div
+ ref='modalBody'
+ className='modal-body'
+ >
{content}
</div>
</div>
@@ -139,4 +170,4 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 0254d0e82..87b9cab04 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -1,139 +1,85 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var ChannelStore = require('../stores/channel_store.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var PostStore = require('../stores/post_store.jsx');
-var SocketStore = require('../stores/socket_store.jsx');
-var NavbarSearchBox = require('./search_bar.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var Client = require('../utils/client.jsx');
-var utils = require('../utils/utils.jsx');
-var MessageWrapper = require('./message_wrapper.jsx');
-
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
-
-var PopoverListMembers = React.createClass({
- componentDidMount: function() {
- var originalLeave = $.fn.popover.Constructor.prototype.leave;
- $.fn.popover.Constructor.prototype.leave = function(obj) {
- var selfObj;
- if (obj instanceof this.constructor) {
- selfObj = obj;
- } else {
- selfObj = $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type);
- }
- originalLeave.call(this, obj);
-
- if (obj.currentTarget && selfObj.$tip) {
- selfObj.$tip.one('mouseenter', function() {
- clearTimeout(selfObj.timeout);
- selfObj.$tip.one('mouseleave', function() {
- $.fn.popover.Constructor.prototype.leave.call(selfObj, selfObj);
- });
- });
- }
- };
+const ChannelStore = require('../stores/channel_store.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const PostStore = require('../stores/post_store.jsx');
+const SocketStore = require('../stores/socket_store.jsx');
+const NavbarSearchBox = require('./search_bar.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const Client = require('../utils/client.jsx');
+const Utils = require('../utils/utils.jsx');
+const MessageWrapper = require('./message_wrapper.jsx');
+const PopoverListMembers = require('./popover_list_members.jsx');
- $('#member_popover').popover({placement: 'bottom', trigger: 'click', html: true});
- $('body').on('click', function(e) {
- if ($(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) {
- $('#member_popover').popover('hide');
- }
- });
- },
-
- render: function() {
- var popoverHtml = '';
- var members = this.props.members;
- var count;
- if (members.length > 20) {
- count = '20+';
- } else {
- count = members.length || '-';
- }
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
- if (members) {
- members.sort(function(a, b) {
- return a.username.localeCompare(b.username);
- });
+export default class ChannelHeader extends React.Component {
+ constructor(props) {
+ super(props);
- members.forEach(function(m) {
- popoverHtml += "<div class='text--nowrap'>" + m.username + '</div>';
- });
- }
+ this.onListenerChange = this.onListenerChange.bind(this);
+ this.onSocketChange = this.onSocketChange.bind(this);
+ this.handleLeave = this.handleLeave.bind(this);
+ this.searchMentions = this.searchMentions.bind(this);
- return (
- <div id='member_popover' data-toggle='popover' data-content={popoverHtml} data-original-title='Members' >
- <div id='member_tooltip' data-placement='left' data-toggle='tooltip' title='View Channel Members'>
- {count} <span className='glyphicon glyphicon-user' aria-hidden='true'></span>
- </div>
- </div>
- );
+ this.state = this.getStateFromStores();
}
-});
-
-function getStateFromStores() {
- return {
- channel: ChannelStore.getCurrent(),
- memberChannel: ChannelStore.getCurrentMember(),
- memberTeam: UserStore.getCurrentUser(),
- users: ChannelStore.getCurrentExtraInfo().members,
- searchVisible: PostStore.getSearchResults() != null
- };
-}
-
-module.exports = React.createClass({
- displayName: 'ChannelHeader',
- componentDidMount: function() {
+ getStateFromStores() {
+ return {
+ channel: ChannelStore.getCurrent(),
+ memberChannel: ChannelStore.getCurrentMember(),
+ memberTeam: UserStore.getCurrentUser(),
+ users: ChannelStore.getCurrentExtraInfo().members,
+ searchVisible: PostStore.getSearchResults() !== null
+ };
+ }
+ componentDidMount() {
ChannelStore.addChangeListener(this.onListenerChange);
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
PostStore.addSearchChangeListener(this.onListenerChange);
UserStore.addChangeListener(this.onListenerChange);
SocketStore.addChangeListener(this.onSocketChange);
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange);
ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
PostStore.removeSearchChangeListener(this.onListenerChange);
UserStore.addChangeListener(this.onListenerChange);
- },
- onListenerChange: function() {
- var newState = getStateFromStores();
- if (!utils.areStatesEqual(newState, this.state)) {
+ }
+ onListenerChange() {
+ const newState = this.getStateFromStores();
+ if (!Utils.areStatesEqual(newState, this.state)) {
this.setState(newState);
}
$('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover click', html: true, delay: {show: 500, hide: 500}});
- },
- onSocketChange: function(msg) {
+ }
+ onSocketChange(msg) {
if (msg.action === 'new_user') {
AsyncClient.getChannelExtraInfo(true);
}
- },
- getInitialState: function() {
- return getStateFromStores();
- },
- handleLeave: function() {
+ }
+ handleLeave() {
Client.leaveChannel(this.state.channel.id,
- function() {
- var townsquare = ChannelStore.getByName('town-square');
- utils.switchChannel(townsquare);
+ function handleLeaveSuccess() {
+ const townsquare = ChannelStore.getByName('town-square');
+ Utils.switchChannel(townsquare);
},
- function(err) {
+ function handleLeaveError(err) {
AsyncClient.dispatchError(err, 'handleLeave');
}
);
- },
- searchMentions: function(e) {
+ }
+ searchMentions(e) {
e.preventDefault();
- var user = UserStore.getCurrentUser();
+ const user = UserStore.getCurrentUser();
- var terms = '';
+ let terms = '';
if (user.notify_props && user.notify_props.mention_keys) {
- var termKeys = UserStore.getCurrentMentionKeys();
+ let termKeys = UserStore.getCurrentMentionKeys();
if (user.notify_props.all === 'true' && termKeys.indexOf('@all') !== -1) {
termKeys.splice(termKeys.indexOf('@all'), 1);
}
@@ -149,23 +95,23 @@ module.exports = React.createClass({
do_search: true,
is_mention_search: true
});
- },
- render: function() {
- if (this.state.channel == null) {
+ }
+ render() {
+ if (this.state.channel === null) {
return null;
}
- var channel = this.state.channel;
- var description = utils.textToJsx(channel.description, {singleline: true, noMentionHighlight: true});
- var popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>);
- var channelTitle = channel.display_name;
- var currentId = UserStore.getCurrentId();
- var isAdmin = this.state.memberChannel.roles.indexOf('admin') > -1 || this.state.memberTeam.roles.indexOf('admin') > -1;
- var isDirect = (this.state.channel.type === 'D');
+ const channel = this.state.channel;
+ const description = Utils.textToJsx(channel.description, {singleline: true, noMentionHighlight: true});
+ const popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>);
+ let channelTitle = channel.display_name;
+ const currentId = UserStore.getCurrentId();
+ const isAdmin = this.state.memberChannel.roles.indexOf('admin') > -1 || this.state.memberTeam.roles.indexOf('admin') > -1;
+ const isDirect = (this.state.channel.type === 'D');
if (isDirect) {
if (this.state.users.length > 1) {
- var contact;
+ let contact;
if (this.state.users[0].id === currentId) {
contact = this.state.users[1];
} else {
@@ -175,64 +121,244 @@ module.exports = React.createClass({
}
}
- var channelTerm = 'Channel';
+ let channelTerm = 'Channel';
if (channel.type === 'P') {
channelTerm = 'Group';
}
+ let dropdownContents = [];
+ if (!isDirect) {
+ dropdownContents.push(
+ <li
+ key='view_info'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ data-toggle='modal'
+ data-target='#channel_info'
+ data-channelid={channel.id}
+ href='#'
+ >
+ View Info
+ </a>
+ </li>
+ );
+
+ if (!ChannelStore.isDefault(channel)) {
+ dropdownContents.push(
+ <li
+ key='add_members'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ data-toggle='modal'
+ data-target='#channel_invite'
+ href='#'
+ >
+ Add Members
+ </a>
+ </li>
+ );
+
+ if (isAdmin) {
+ dropdownContents.push(
+ <li
+ key='manage_members'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ data-toggle='modal'
+ data-target='#channel_members'
+ href='#'
+ >
+ Manage Members
+ </a>
+ </li>
+ );
+ }
+ }
+
+ dropdownContents.push(
+ <li
+ key='set_channel_description'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#edit_channel'
+ data-desc={channel.description}
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ >
+ Set {channelTerm} Description...
+ </a>
+ </li>
+ );
+ dropdownContents.push(
+ <li
+ key='notification_preferences'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#channel_notifications'
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ >
+ Notification Preferences
+ </a>
+ </li>
+ );
+
+ if (!ChannelStore.isDefault(channel)) {
+ if (isAdmin) {
+ dropdownContents.push(
+ <li
+ key='rename_channel'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#rename_channel'
+ data-display={channel.display_name}
+ data-name={channel.name}
+ data-channelid={channel.id}
+ >
+ Rename {channelTerm}...
+ </a>
+ </li>
+ );
+ dropdownContents.push(
+ <li
+ key='delete_channel'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#delete_channel'
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ >
+ Delete {channelTerm}...
+ </a>
+ </li>
+ );
+ }
+
+ dropdownContents.push(
+ <li
+ key='leave_channel'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleLeave}
+ >
+ Leave {channelTerm}
+ </a>
+ </li>
+ );
+ }
+ } else {
+ dropdownContents.push(
+ <li
+ key='edit_description_direct'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#edit_channel'
+ data-desc={channel.description}
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ >
+ Set Channel Description...
+ </a>
+ </li>
+ );
+ }
+
return (
<table className='channel-header alt'>
<tr>
<th>
<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'>
+ <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'></span>
+ <span className='glyphicon glyphicon-chevron-down header-dropdown__icon' />
</a>
- {!isDirect ?
- <ul className='dropdown-menu' role='menu' aria-labelledby='channel_header_dropdown'>
- <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_info' data-channelid={channel.id} href='#'>View Info</a></li>
- {!ChannelStore.isDefault(channel) ?
- <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_invite' href='#'>Add Members</a></li>
- : null
- }
- {isAdmin && !ChannelStore.isDefault(channel) ?
- <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_members' href='#'>Manage Members</a></li>
- : null
- }
- <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set {channelTerm} Description...</a></li>
- <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#channel_notifications' data-title={channel.display_name} data-channelid={channel.id}>Notification Preferences</a></li>
- {isAdmin && !ChannelStore.isDefault(channel) ?
- <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#rename_channel' data-display={channel.display_name} data-name={channel.name} data-channelid={channel.id}>Rename {channelTerm}...</a></li>
- : null
- }
- {isAdmin && !ChannelStore.isDefault(channel) ?
- <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#delete_channel' data-title={channel.display_name} data-channelid={channel.id}>Delete {channelTerm}...</a></li>
- : null
- }
- {!ChannelStore.isDefault(channel) ?
- <li role='presentation'><a role='menuitem' href='#' onClick={this.handleLeave}>Leave {channelTerm}</a></li>
- : null
- }
- </ul>
- :
- <ul className='dropdown-menu' role='menu' aria-labelledby='channel_header_dropdown'>
- <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li>
+ <ul
+ className='dropdown-menu'
+ role='menu'
+ aria-labelledby='channel_header_dropdown'
+ >
+ {dropdownContents}
</ul>
- }
</div>
- <div data-toggle='popover' data-content={popoverContent} className='description'>{description}</div>
+ <div
+ data-toggle='popover'
+ data-content={popoverContent}
+ className='description'
+ >
+ {description}
+ </div>
</div>
</th>
- <th><PopoverListMembers members={this.state.users} channelId={channel.id} /></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>
+ <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>
@@ -240,4 +366,4 @@ module.exports = React.createClass({
</table>
);
}
-});
+}
diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx
index 0fa433383..8e8ed3f73 100644
--- a/web/react/components/channel_loader.jsx
+++ b/web/react/components/channel_loader.jsx
@@ -10,13 +10,18 @@ var SocketStore = require('../stores/socket_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var PostStore = require('../stores/post_store.jsx');
var UserStore = require('../stores/user_store.jsx');
-var Constants = require('../utils/constants.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
-module.exports = React.createClass({
- componentDidMount: function() {
+export default class ChannelLoader extends React.Component {
+ constructor(props) {
+ super(props);
+ this.onSocketChange = this.onSocketChange.bind(this);
+
+ this.state = {};
+ }
+ componentDidMount() {
/* Initial aysnc loads */
AsyncClient.getMe();
AsyncClient.getPosts(ChannelStore.getCurrentId());
@@ -60,32 +65,32 @@ module.exports = React.createClass({
var user = UserStore.getCurrentUser();
if (user.props && user.props.theme) {
- utils.changeCss('div.theme', 'background-color:' + user.props.theme + ';');
- utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme + ';');
- utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme + ';');
- utils.changeCss('.mention', 'background: ' + user.props.theme + ';');
- utils.changeCss('.mention-link', 'color: ' + user.props.theme + ';');
- utils.changeCss('@media(max-width: 768px){.search-bar__container', 'background: ' + user.props.theme + ';}');
- utils.changeCss('.search-item-container:hover', 'background: ' + utils.changeOpacity(user.props.theme, 0.05) + ';');
+ Utils.changeCss('div.theme', 'background-color:' + user.props.theme + ';');
+ Utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme + ';');
+ Utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme + ';');
+ Utils.changeCss('.mention', 'background: ' + user.props.theme + ';');
+ Utils.changeCss('.mention-link', 'color: ' + user.props.theme + ';');
+ Utils.changeCss('@media(max-width: 768px){.search-bar__container', 'background: ' + user.props.theme + ';}');
+ Utils.changeCss('.search-item-container:hover', 'background: ' + Utils.changeOpacity(user.props.theme, 0.05) + ';');
}
if (user.props.theme !== '#000000' && user.props.theme !== '#585858') {
- utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, -10) + ';');
- utils.changeCss('a.theme', 'color:' + user.props.theme + '; fill:' + user.props.theme + '!important;');
+ Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, -10) + ';');
+ Utils.changeCss('a.theme', 'color:' + user.props.theme + '; fill:' + user.props.theme + '!important;');
} else if (user.props.theme === '#000000') {
- utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, +50) + ';');
+ Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +50) + ';');
$('.team__header').addClass('theme--black');
} else if (user.props.theme === '#585858') {
- utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, +10) + ';');
+ Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +10) + ';');
$('.team__header').addClass('theme--gray');
}
- },
- onSocketChange: function(msg) {
+ }
+ onSocketChange(msg) {
if (msg && msg.user_id && msg.user_id !== UserStore.getCurrentId()) {
UserStore.setStatus(msg.user_id, 'online');
}
- },
- render: function() {
+ }
+ render() {
return <div/>;
}
-});
+}
diff --git a/web/react/components/channel_members.jsx b/web/react/components/channel_members.jsx
index db4bec400..04fa2c7a2 100644
--- a/web/react/components/channel_members.jsx
+++ b/web/react/components/channel_members.jsx
@@ -1,154 +1,200 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserStore = require('../stores/user_store.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var MemberList = require('./member_list.jsx');
-var client = require('../utils/client.jsx');
-var utils = require('../utils/utils.jsx');
-
-function getStateFromStores() {
- var users = UserStore.getActiveOnlyProfiles();
- var member_list = ChannelStore.getCurrentExtraInfo().members;
-
- var nonmember_list = [];
- for (var id in users) {
- var found = false;
- for (var i = 0; i < member_list.length; i++) {
- if (member_list[i].id === id) {
- found = true;
- break;
+const UserStore = require('../stores/user_store.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const MemberList = require('./member_list.jsx');
+const Client = require('../utils/client.jsx');
+const Utils = require('../utils/utils.jsx');
+
+export default class ChannelMembers extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getStateFromStores = this.getStateFromStores.bind(this);
+ this.onChange = this.onChange.bind(this);
+ this.handleRemove = this.handleRemove.bind(this);
+
+ this.state = this.getStateFromStores();
+ }
+ getStateFromStores() {
+ const users = UserStore.getActiveOnlyProfiles();
+ let memberList = ChannelStore.getCurrentExtraInfo().members;
+
+ let nonmemberList = [];
+ for (let id in users) {
+ if (users.hasOwnProperty(id)) {
+ let found = false;
+ for (let i = 0; i < memberList.length; i++) {
+ if (memberList[i].id === id) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ nonmemberList.push(users[id]);
+ }
}
}
- if (!found) {
- nonmember_list.push(users[id]);
+
+ function compareByUsername(a, b) {
+ if (a.username < b.username) {
+ return -1;
+ } else if (a.username > b.username) {
+ return 1;
+ }
+
+ return 0;
}
- }
- member_list.sort(function(a,b) {
- if (a.username < b.username) return -1;
- if (a.username > b.username) return 1;
- return 0;
- });
-
- nonmember_list.sort(function(a,b) {
- if (a.username < b.username) return -1;
- if (a.username > b.username) return 1;
- return 0;
- });
-
- var channel_name = ChannelStore.getCurrent() ? ChannelStore.getCurrent().display_name : "";
-
- return {
- nonmember_list: nonmember_list,
- member_list: member_list,
- channel_name: channel_name
- };
-}
+ memberList.sort(compareByUsername);
+ nonmemberList.sort(compareByUsername);
+
+ const channel = ChannelStore.getCurrent();
+ let channelName = '';
+ if (channel) {
+ channelName = channel.display_name;
+ }
-module.exports = React.createClass({
- componentDidMount: function() {
- ChannelStore.addExtraInfoChangeListener(this._onChange);
- ChannelStore.addChangeListener(this._onChange);
- var self = this;
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
- self.setState({ render_members: false });
- });
-
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
- self.setState({ render_members: true });
- });
- },
- componentWillUnmount: function() {
- ChannelStore.removeExtraInfoChangeListener(this._onChange);
- ChannelStore.removeChangeListener(this._onChange);
- },
- _onChange: function() {
- var new_state = getStateFromStores();
- if (!utils.areStatesEqual(this.state, new_state)) {
- this.setState(new_state);
+ return {
+ nonmemberList: nonmemberList,
+ memberList: memberList,
+ channelName: channelName
+ };
+ }
+ componentDidMount() {
+ ChannelStore.addExtraInfoChangeListener(this.onChange);
+ ChannelStore.addChangeListener(this.onChange);
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function handleHide() {
+ this.setState({renderMembers: false});
+ }.bind(this));
+
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow() {
+ this.setState({renderMembers: true});
+ }.bind(this));
+ }
+ componentWillUnmount() {
+ ChannelStore.removeExtraInfoChangeListener(this.onChange);
+ ChannelStore.removeChangeListener(this.onChange);
+ }
+ onChange() {
+ const newState = this.getStateFromStores();
+ if (!Utils.areStatesEqual(this.state, newState)) {
+ this.setState(newState);
}
- },
- handleRemove: function(user_id) {
+ }
+ handleRemove(userId) {
// Make sure the user is a member of the channel
- var member_list = this.state.member_list;
- var found = false;
- for (var i = 0; i < member_list.length; i++) {
- if (member_list[i].id === user_id) {
+ let memberList = this.state.memberList;
+ let found = false;
+ for (let i = 0; i < memberList.length; i++) {
+ if (memberList[i].id === userId) {
found = true;
break;
}
}
- if (!found) { return };
+ if (!found) {
+ return;
+ }
- var data = {};
- data['user_id'] = user_id;
+ let data = {};
+ data.user_id = userId;
- client.removeChannelMember(ChannelStore.getCurrentId(), data,
- function(data) {
- var old_member;
- for (var i = 0; i < member_list.length; i++) {
- if (user_id === member_list[i].id) {
- old_member = member_list[i];
- member_list.splice(i, 1);
+ Client.removeChannelMember(ChannelStore.getCurrentId(), data,
+ function handleRemoveSuccess() {
+ let oldMember;
+ for (let i = 0; i < memberList.length; i++) {
+ if (userId === memberList[i].id) {
+ oldMember = memberList[i];
+ memberList.splice(i, 1);
break;
}
}
- var nonmember_list = this.state.nonmember_list;
- if (old_member) {
- nonmember_list.push(old_member);
+ let nonmemberList = this.state.nonmemberList;
+ if (oldMember) {
+ nonmemberList.push(oldMember);
}
- this.setState({ member_list: member_list, nonmember_list: nonmember_list });
+ this.setState({memberList: memberList, nonmemberList: nonmemberList});
AsyncClient.getChannelExtraInfo(true);
}.bind(this),
- function(err) {
- this.setState({ invite_error: err.message });
+ function handleRemoveError(err) {
+ this.setState({inviteError: err.message});
}.bind(this)
);
- },
- getInitialState: function() {
- return getStateFromStores();
- },
- render: function() {
- var currentMember = ChannelStore.getCurrentMember();
- var isAdmin = false;
+ }
+ render() {
+ const currentMember = ChannelStore.getCurrentMember();
+ let isAdmin = false;
if (currentMember) {
- isAdmin = currentMember.roles.indexOf("admin") > -1 || UserStore.getCurrentUser().roles.indexOf("admin") > -1;
+ isAdmin = currentMember.roles.indexOf('admin') > -1 || UserStore.getCurrentUser().roles.indexOf('admin') > -1;
+ }
+
+ var memberList = null;
+ if (this.state.renderMembers) {
+ memberList = (
+ <MemberList
+ memberList={this.state.memberList}
+ isAdmin={isAdmin}
+ handleRemove={this.handleRemove}
+ />
+ );
}
return (
- <div className="modal fade" ref="modal" id="channel_members" tabIndex="-1" role="dialog" aria-hidden="true">
- <div className="modal-dialog">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
- <h4 className="modal-title"><span className="name">{this.state.channel_name}</span> Members</h4>
- <a className="btn btn-md btn-primary" data-toggle="modal" data-target="#channel_invite"><i className="glyphicon glyphicon-envelope"/> Add New Members</a>
- </div>
- <div ref="modalBody" className="modal-body">
- <div className="col-sm-12">
- <div className="team-member-list">
- { this.state.render_members ?
- <MemberList
- memberList={this.state.member_list}
- isAdmin={isAdmin}
- handleRemove={this.handleRemove}
- />
- : "" }
+ <div
+ className='modal fade'
+ ref='modal'
+ id='channel_members'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>×</span>
+ </button>
+ <h4 className='modal-title'><span className='name'>{this.state.channelName}</span> Members</h4>
+ <a
+ className='btn btn-md btn-primary'
+ data-toggle='modal'
+ data-target='#channel_invite'
+ >
+ <i className='glyphicon glyphicon-envelope'/> Add New Members
+ </a>
+ </div>
+ <div
+ ref='modalBody'
+ className='modal-body'
+ >
+ <div className='col-sm-12'>
+ <div className='team-member-list'>
+ {memberList}
+ </div>
</div>
</div>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >
+ Close
+ </button>
+ </div>
</div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
- </div>
- </div>
- </div>
+ </div>
</div>
-
);
}
-});
+}
diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx
index 884bad9d2..173646597 100644
--- a/web/react/components/channel_notifications.jsx
+++ b/web/react/components/channel_notifications.jsx
@@ -4,26 +4,29 @@
var SettingItemMin = require('./setting_item_min.jsx');
var SettingItemMax = require('./setting_item_max.jsx');
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
+var Utils = require('../utils/utils.jsx');
+var Client = require('../utils/client.jsx');
var UserStore = require('../stores/user_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
export default class ChannelNotifications extends React.Component {
constructor(props) {
super(props);
+
this.onListenerChange = this.onListenerChange.bind(this);
this.updateSection = this.updateSection.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
this.handleRadioClick = this.handleRadioClick.bind(this);
this.handleQuietToggle = this.handleQuietToggle.bind(this);
+ this.createDesktopSection = this.createDesktopSection.bind(this);
+ this.createQuietSection = this.createQuietSection.bind(this);
+
this.state = {notifyLevel: '', title: '', channelId: '', activeSection: ''};
}
componentDidMount() {
ChannelStore.addChangeListener(this.onListenerChange);
- var self = this;
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function showModal(e) {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function showModal(e) {
var button = e.relatedTarget;
var channelId = button.getAttribute('data-channelid');
@@ -34,8 +37,8 @@ export default class ChannelNotifications extends React.Component {
quietMode = true;
}
- self.setState({notifyLevel: notifyLevel, quietMode: quietMode, title: button.getAttribute('data-title'), channelId: channelId});
- });
+ this.setState({notifyLevel: notifyLevel, quietMode: quietMode, title: button.getAttribute('data-title'), channelId: channelId});
+ }.bind(this));
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange);
@@ -55,7 +58,7 @@ export default class ChannelNotifications extends React.Component {
newState.notifyLevel = notifyLevel;
newState.quietMode = quietMode;
- if (!utils.areStatesEqual(this.state, newState)) {
+ if (!Utils.areStatesEqual(this.state, newState)) {
this.setState(newState);
}
}
@@ -78,7 +81,7 @@ export default class ChannelNotifications extends React.Component {
return;
}
- client.updateNotifyLevel(data,
+ Client.updateNotifyLevel(data,
function success() {
var member = ChannelStore.getMember(channelId);
member.notify_level = notifyLevel;
@@ -92,25 +95,15 @@ export default class ChannelNotifications extends React.Component {
}
handleRadioClick(notifyLevel) {
this.setState({notifyLevel: notifyLevel, quietMode: false});
- this.refs.modal.getDOMNode().focus();
+ React.findDOMNode(this.refs.modal).focus();
}
handleQuietToggle(quietMode) {
this.setState({notifyLevel: 'none', quietMode: quietMode});
- this.refs.modal.getDOMNode().focus();
+ React.findDOMNode(this.refs.modal).focus();
}
- render() {
- var serverError = null;
- if (this.state.serverError) {
- serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
- }
-
- var self = this;
- var describe = '';
- var inputs = [];
-
+ createDesktopSection(serverError) {
var handleUpdateSection;
- var desktopSection;
if (this.state.activeSection === 'desktop') {
var notifyActive = [false, false, false];
if (this.state.notifyLevel === 'mention') {
@@ -121,6 +114,8 @@ export default class ChannelNotifications extends React.Component {
notifyActive[2] = true;
}
+ var inputs = [];
+
inputs.push(
<div>
<div className='radio'>
@@ -128,7 +123,7 @@ export default class ChannelNotifications extends React.Component {
<input
type='radio'
checked={notifyActive[0]}
- onChange={self.handleRadioClick.bind(this, 'all')}
+ onChange={this.handleRadioClick.bind(this, 'all')}
>
For all activity
</input>
@@ -140,7 +135,7 @@ export default class ChannelNotifications extends React.Component {
<input
type='radio'
checked={notifyActive[1]}
- onChange={self.handleRadioClick.bind(this, 'mention')}
+ onChange={this.handleRadioClick.bind(this, 'mention')}
>
Only for mentions
</input>
@@ -152,7 +147,7 @@ export default class ChannelNotifications extends React.Component {
<input
type='radio'
checked={notifyActive[2]}
- onChange={self.handleRadioClick.bind(this, 'none')}
+ onChange={this.handleRadioClick.bind(this, 'none')}
>
Never
</input>
@@ -162,12 +157,12 @@ export default class ChannelNotifications extends React.Component {
);
handleUpdateSection = function updateSection(e) {
- self.updateSection('');
- self.onListenerChange();
+ this.updateSection('');
+ this.onListenerChange();
e.preventDefault();
- };
+ }.bind(this);
- desktopSection = (
+ return (
<SettingItemMax
title='Send desktop notifications'
inputs={inputs}
@@ -176,30 +171,32 @@ export default class ChannelNotifications extends React.Component {
updateSection={handleUpdateSection}
/>
);
- } else {
- if (this.state.notifyLevel === 'mention') {
- describe = 'Only for mentions';
- } else if (this.state.notifyLevel === 'all') {
- describe = 'For all activity';
- } else {
- describe = 'Never';
- }
-
- handleUpdateSection = function updateSection(e) {
- self.updateSection('desktop');
- e.preventDefault();
- };
+ }
- desktopSection = (
- <SettingItemMin
- title='Send desktop notifications'
- describe={describe}
- updateSection={handleUpdateSection}
- />
- );
+ var describe;
+ if (this.state.notifyLevel === 'mention') {
+ describe = 'Only for mentions';
+ } else if (this.state.notifyLevel === 'all') {
+ describe = 'For all activity';
+ } else {
+ describe = 'Never';
}
- var quietSection;
+ handleUpdateSection = function updateSection(e) {
+ this.updateSection('desktop');
+ e.preventDefault();
+ }.bind(this);
+
+ return (
+ <SettingItemMin
+ title='Send desktop notifications'
+ describe={describe}
+ updateSection={handleUpdateSection}
+ />
+ );
+ }
+ createQuietSection(serverError) {
+ var handleUpdateSection;
if (this.state.activeSection === 'quiet') {
var quietActive = [false, false];
if (this.state.quietMode) {
@@ -208,6 +205,8 @@ export default class ChannelNotifications extends React.Component {
quietActive[1] = true;
}
+ var inputs = [];
+
inputs.push(
<div>
<div className='radio'>
@@ -215,7 +214,7 @@ export default class ChannelNotifications extends React.Component {
<input
type='radio'
checked={quietActive[0]}
- onChange={self.handleQuietToggle.bind(this, true)}
+ onChange={this.handleQuietToggle.bind(this, true)}
>
On
</input>
@@ -227,7 +226,7 @@ export default class ChannelNotifications extends React.Component {
<input
type='radio'
checked={quietActive[1]}
- onChange={self.handleQuietToggle.bind(this, false)}
+ onChange={this.handleQuietToggle.bind(this, false)}
>
Off
</input>
@@ -245,12 +244,12 @@ export default class ChannelNotifications extends React.Component {
);
handleUpdateSection = function updateSection(e) {
- self.updateSection('');
- self.onListenerChange();
+ this.updateSection('');
+ this.onListenerChange();
e.preventDefault();
- };
+ }.bind(this);
- quietSection = (
+ return (
<SettingItemMax
title='Quiet mode'
inputs={inputs}
@@ -259,27 +258,38 @@ export default class ChannelNotifications extends React.Component {
updateSection={handleUpdateSection}
/>
);
+ }
+
+ var describe;
+ if (this.state.quietMode) {
+ describe = 'On';
} else {
- if (this.state.quietMode) {
- describe = 'On';
- } else {
- describe = 'Off';
- }
+ describe = 'Off';
+ }
- handleUpdateSection = function updateSection(e) {
- self.updateSection('quiet');
- e.preventDefault();
- };
+ handleUpdateSection = function updateSection(e) {
+ this.updateSection('quiet');
+ e.preventDefault();
+ }.bind(this);
- quietSection = (
- <SettingItemMin
- title='Quiet mode'
- describe={describe}
- updateSection={handleUpdateSection}
- />
- );
+ return (
+ <SettingItemMin
+ title='Quiet mode'
+ describe={describe}
+ updateSection={handleUpdateSection}
+ />
+ );
+ }
+ render() {
+ var serverError = null;
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
+ var desktopSection = this.createDesktopSection(serverError);
+
+ var quietSection = this.createQuietSection(serverError);
+
return (
<div
className='modal fade'
diff --git a/web/react/components/confirm_modal.jsx b/web/react/components/confirm_modal.jsx
index 3be13cf9b..cb3b9c5e3 100644
--- a/web/react/components/confirm_modal.jsx
+++ b/web/react/components/confirm_modal.jsx
@@ -1,31 +1,70 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-module.exports = React.createClass({
- handleConfirm: function() {
- $('#'+this.props.parent_id).attr('data-confirm', 'true');
- $('#'+this.props.parent_id).modal('hide');
- $('#'+this.props.id).modal('hide');
- },
- render: function() {
+export default class ConfirmModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleConfirm = this.handleConfirm.bind(this);
+
+ this.state = {};
+ }
+ handleConfirm() {
+ $('#' + this.props.parent_id).attr('data-confirm', 'true');
+ $('#' + this.props.parent_id).modal('hide');
+ $('#' + this.props.id).modal('hide');
+ }
+ render() {
return (
- <div className="modal fade" id={this.props.id} tabIndex="-1" role="dialog" aria-hidden="true">
- <div className="modal-dialog">
- <div className="modal-content">
- <div className="modal-header">
- <h4 className="modal-title">{this.props.title}</h4>
- </div>
- <div className="modal-body">
- {this.props.message}
- </div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button>
- <button onClick={this.handleConfirm} type="button" className="btn btn-primary">{this.props.confirm_button}</button>
+ <div
+ className='modal fade'
+ id={this.props.id}
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <h4 className='modal-title'>{this.props.title}</h4>
+ </div>
+ <div className='modal-body'>
+ {this.props.message}
+ </div>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >
+ Cancel
+ </button>
+ <button
+ onClick={this.handleConfirm}
+ type='button'
+ className='btn btn-primary'
+ >
+ {this.props.confirm_button}
+ </button>
+ </div>
</div>
- </div>
- </div>
+ </div>
</div>
);
}
-});
+}
+ConfirmModal.defaultProps = {
+ parent_id: '',
+ id: '',
+ title: '',
+ message: '',
+ confirm_button: ''
+};
+ConfirmModal.propTypes = {
+ parent_id: React.PropTypes.string,
+ id: React.PropTypes.string,
+ title: React.PropTypes.string,
+ message: React.PropTypes.string,
+ confirm_button: React.PropTypes.string
+};
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index c2b7e222f..c2fc0dcf3 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -1,24 +1,48 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var SocketStore = require('../stores/socket_store.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var PostStore = require('../stores/post_store.jsx');
-var Textbox = require('./textbox.jsx');
-var MsgTyping = require('./msg_typing.jsx');
-var FileUpload = require('./file_upload.jsx');
-var FilePreview = require('./file_preview.jsx');
-var utils = require('../utils/utils.jsx');
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
-
-module.exports = React.createClass({
- lastTime: 0,
- handleSubmit: function(e) {
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const SocketStore = require('../stores/socket_store.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const PostStore = require('../stores/post_store.jsx');
+const Textbox = require('./textbox.jsx');
+const MsgTyping = require('./msg_typing.jsx');
+const FileUpload = require('./file_upload.jsx');
+const FilePreview = require('./file_preview.jsx');
+const Utils = require('../utils/utils.jsx');
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
+
+export default class CreateComment extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.lastTime = 0;
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.commentMsgKeyPress = this.commentMsgKeyPress.bind(this);
+ this.handleUserInput = this.handleUserInput.bind(this);
+ this.handleUploadStart = this.handleUploadStart.bind(this);
+ this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this);
+ this.handleUploadError = this.handleUploadError.bind(this);
+ this.removePreview = this.removePreview.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.getFileCount = this.getFileCount.bind(this);
+
+ PostStore.clearCommentDraftUploads();
+
+ const draft = PostStore.getCommentDraft(this.props.rootId);
+ this.state = {
+ messageText: draft.message,
+ uploadsInProgress: draft.uploadsInProgress,
+ previews: draft.previews,
+ submitting: false
+ };
+ }
+ handleSubmit(e) {
e.preventDefault();
if (this.state.uploadsInProgress.length > 0) {
@@ -29,7 +53,7 @@ module.exports = React.createClass({
return;
}
- var post = {};
+ let post = {};
post.filenames = [];
post.message = this.state.messageText;
@@ -38,30 +62,30 @@ module.exports = React.createClass({
}
if (post.message.length > Constants.CHARACTER_LIMIT) {
- this.setState({postError: 'Comment length must be less than ' + Constants.CHARACTER_LIMIT + ' characters.'});
+ this.setState({postError: `Comment length must be less than ${Constants.CHARACTER_LIMIT} characters.`});
return;
}
- var user_id = UserStore.getCurrentId();
+ const userId = UserStore.getCurrentId();
post.channel_id = this.props.channelId;
post.root_id = this.props.rootId;
post.parent_id = this.props.rootId;
post.filenames = this.state.previews;
- var time = utils.getTimestamp();
- post.pending_post_id = user_id + ':'+ time;
- post.user_id = user_id;
+ const time = Utils.getTimestamp();
+ post.pending_post_id = `${userId}:${time}`;
+ post.user_id = userId;
post.create_at = time;
PostStore.storePendingPost(post);
PostStore.storeCommentDraft(this.props.rootId, null);
- client.createPost(post, ChannelStore.getCurrent(),
- function(data) {
+ Client.createPost(post, ChannelStore.getCurrent(),
+ function handlePostSuccess(data) {
AsyncClient.getPosts(this.props.channelId);
- var channel = ChannelStore.get(this.props.channelId);
- var member = ChannelStore.getMember(this.props.channelId);
+ const channel = ChannelStore.get(this.props.channelId);
+ let member = ChannelStore.getMember(this.props.channelId);
member.msg_count = channel.total_msg_count;
member.last_viewed_at = Date.now();
ChannelStore.setChannelMember(member);
@@ -71,8 +95,8 @@ module.exports = React.createClass({
post: data
});
}.bind(this),
- function(err) {
- var state = {};
+ function handlePostError(err) {
+ let state = {};
if (err.message === 'Invalid RootId parameter') {
if ($('#post_deleted').length > 0) {
@@ -90,76 +114,76 @@ module.exports = React.createClass({
);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
- },
- commentMsgKeyPress: function(e) {
+ }
+ commentMsgKeyPress(e) {
if (e.which === 13 && !e.shiftKey && !e.altKey) {
e.preventDefault();
- this.refs.textbox.getDOMNode().blur();
+ React.findDOMNode(this.refs.textbox).blur();
this.handleSubmit(e);
}
- var t = Date.now();
+ const t = Date.now();
if ((t - this.lastTime) > 5000) {
- SocketStore.sendMessage({channel_id: this.props.channelId, action: 'typing', props: {'parent_id': this.props.rootId}});
+ SocketStore.sendMessage({channel_id: this.props.channelId, action: 'typing', props: {parent_id: this.props.rootId}});
this.lastTime = t;
}
- },
- handleUserInput: function(messageText) {
- var draft = PostStore.getCommentDraft(this.props.rootId);
+ }
+ handleUserInput(messageText) {
+ let draft = PostStore.getCommentDraft(this.props.rootId);
draft.message = messageText;
PostStore.storeCommentDraft(this.props.rootId, draft);
$('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight);
$('.post-right__scroll').perfectScrollbar('update');
this.setState({messageText: messageText});
- },
- handleUploadStart: function(clientIds, channelId) {
- var draft = PostStore.getCommentDraft(this.props.rootId);
+ }
+ handleUploadStart(clientIds) {
+ let draft = PostStore.getCommentDraft(this.props.rootId);
- draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(clientIds);
+ draft.uploadsInProgress = draft.uploadsInProgress.concat(clientIds);
PostStore.storeCommentDraft(this.props.rootId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress']});
- },
- handleFileUploadComplete: function(filenames, clientIds, channelId) {
- var draft = PostStore.getCommentDraft(this.props.rootId);
+ this.setState({uploadsInProgress: draft.uploadsInProgress});
+ }
+ handleFileUploadComplete(filenames, clientIds) {
+ let draft = PostStore.getCommentDraft(this.props.rootId);
// remove each finished file from uploads
- for (var i = 0; i < clientIds.length; i++) {
- var index = draft['uploadsInProgress'].indexOf(clientIds[i]);
+ for (let i = 0; i < clientIds.length; i++) {
+ const index = draft.uploadsInProgress.indexOf(clientIds[i]);
if (index !== -1) {
- draft['uploadsInProgress'].splice(index, 1);
+ draft.uploadsInProgress.splice(index, 1);
}
}
- draft['previews'] = draft['previews'].concat(filenames);
+ draft.previews = draft.previews.concat(filenames);
PostStore.storeCommentDraft(this.props.rootId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']});
- },
- handleUploadError: function(err, clientId) {
+ this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
+ }
+ handleUploadError(err, clientId) {
if (clientId !== -1) {
- var draft = PostStore.getCommentDraft(this.props.rootId);
+ let draft = PostStore.getCommentDraft(this.props.rootId);
- var index = draft['uploadsInProgress'].indexOf(clientId);
+ const index = draft.uploadsInProgress.indexOf(clientId);
if (index !== -1) {
- draft['uploadsInProgress'].splice(index, 1);
+ draft.uploadsInProgress.splice(index, 1);
}
PostStore.storeCommentDraft(this.props.rootId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err});
+ this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err});
} else {
this.setState({serverError: err});
}
- },
- removePreview: function(id) {
- var previews = this.state.previews;
- var uploadsInProgress = this.state.uploadsInProgress;
+ }
+ removePreview(id) {
+ let previews = this.state.previews;
+ let uploadsInProgress = this.state.uploadsInProgress;
// id can either be the path of an uploaded file or the client id of an in progress upload
- var index = previews.indexOf(id);
+ let index = previews.indexOf(id);
if (index !== -1) {
previews.splice(index, 1);
} else {
@@ -171,30 +195,24 @@ module.exports = React.createClass({
}
}
- var draft = PostStore.getCommentDraft(this.props.rootId);
+ let draft = PostStore.getCommentDraft(this.props.rootId);
draft.previews = previews;
draft.uploadsInProgress = uploadsInProgress;
PostStore.storeCommentDraft(this.props.rootId, draft);
this.setState({previews: previews, uploadsInProgress: uploadsInProgress});
- },
- getInitialState: function() {
- PostStore.clearCommentDraftUploads();
-
- var draft = PostStore.getCommentDraft(this.props.rootId);
- return {messageText: draft['message'], uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews'], submitting: false};
- },
- componentWillReceiveProps: function(newProps) {
+ }
+ componentWillReceiveProps(newProps) {
if (newProps.rootId !== this.props.rootId) {
- var draft = PostStore.getCommentDraft(newProps.rootId);
- this.setState({messageText: draft['message'], uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']});
+ const draft = PostStore.getCommentDraft(newProps.rootId);
+ this.setState({messageText: draft.message, uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
}
- },
- getFileCount: function(channelId) {
+ }
+ getFileCount() {
return this.state.previews.length + this.state.uploadsInProgress.length;
- },
- render: function() {
- var serverError = null;
+ }
+ render() {
+ let serverError = null;
if (this.state.serverError) {
serverError = (
<div className='form-group has-error'>
@@ -203,22 +221,23 @@ module.exports = React.createClass({
);
}
- var postError = null;
+ let postError = null;
if (this.state.postError) {
postError = <label className='control-label'>{this.state.postError}</label>;
}
- var preview = null;
+ let preview = null;
if (this.state.previews.length > 0 || this.state.uploadsInProgress.length > 0) {
preview = (
<FilePreview
files={this.state.previews}
onRemove={this.removePreview}
- uploadsInProgress={this.state.uploadsInProgress} />
+ uploadsInProgress={this.state.uploadsInProgress}
+ />
);
}
- var postFooterClassName = 'post-create-footer';
+ let postFooterClassName = 'post-create-footer';
if (postError) {
postFooterClassName += ' has-error';
}
@@ -226,7 +245,10 @@ module.exports = React.createClass({
return (
<form onSubmit={this.handleSubmit}>
<div className='post-create'>
- <div id={this.props.rootId} className='post-create-body comment-create-body'>
+ <div
+ id={this.props.rootId}
+ className='post-create-body comment-create-body'
+ >
<Textbox
onUserInput={this.handleUserInput}
onKeyPress={this.commentMsgKeyPress}
@@ -234,7 +256,8 @@ module.exports = React.createClass({
createMessage='Add a comment...'
initialText=''
id='reply_textbox'
- ref='textbox' />
+ ref='textbox'
+ />
<FileUpload
ref='fileUpload'
getFileCount={this.getFileCount}
@@ -242,11 +265,20 @@ module.exports = React.createClass({
onFileUpload={this.handleFileUploadComplete}
onUploadError={this.handleUploadError}
postType='comment'
- channelId={this.props.channelId} />
+ channelId={this.props.channelId}
+ />
</div>
- <MsgTyping channelId={this.props.channelId} parentId={this.props.rootId} />
+ <MsgTyping
+ channelId={this.props.channelId}
+ parentId={this.props.rootId}
+ />
<div className={postFooterClassName}>
- <input type='button' className='btn btn-primary comment-btn pull-right' value='Add Comment' onClick={this.handleSubmit} />
+ <input
+ type='button'
+ className='btn btn-primary comment-btn pull-right'
+ value='Add Comment'
+ onClick={this.handleSubmit}
+ />
{postError}
{serverError}
</div>
@@ -255,4 +287,9 @@ module.exports = React.createClass({
</form>
);
}
-});
+}
+
+CreateComment.propTypes = {
+ channelId: React.PropTypes.string.isRequired,
+ rootId: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index b9142223f..ce4ebac9e 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -1,33 +1,68 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var PostStore = require('../stores/post_store.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var SocketStore = require('../stores/socket_store.jsx');
-var MsgTyping = require('./msg_typing.jsx');
-var Textbox = require('./textbox.jsx');
-var FileUpload = require('./file_upload.jsx');
-var FilePreview = require('./file_preview.jsx');
-var utils = require('../utils/utils.jsx');
-
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
-
-module.exports = React.createClass({
- displayName: 'CreatePost',
- lastTime: 0,
- handleSubmit: function(e) {
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
+const PostStore = require('../stores/post_store.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const SocketStore = require('../stores/socket_store.jsx');
+const MsgTyping = require('./msg_typing.jsx');
+const Textbox = require('./textbox.jsx');
+const FileUpload = require('./file_upload.jsx');
+const FilePreview = require('./file_preview.jsx');
+const Utils = require('../utils/utils.jsx');
+
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
+
+export default class CreatePost extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.lastTime = 0;
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.postMsgKeyPress = this.postMsgKeyPress.bind(this);
+ this.handleUserInput = this.handleUserInput.bind(this);
+ this.resizePostHolder = this.resizePostHolder.bind(this);
+ this.handleUploadStart = this.handleUploadStart.bind(this);
+ this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this);
+ this.handleUploadError = this.handleUploadError.bind(this);
+ this.removePreview = this.removePreview.bind(this);
+ this.onChange = this.onChange.bind(this);
+ this.getFileCount = this.getFileCount.bind(this);
+
+ PostStore.clearDraftUploads();
+
+ const draft = PostStore.getCurrentDraft();
+ let previews = [];
+ let messageText = '';
+ let uploadsInProgress = [];
+ if (draft && draft.previews && draft.message) {
+ previews = draft.previews;
+ messageText = draft.message;
+ uploadsInProgress = draft.uploadsInProgress;
+ }
+
+ this.state = {
+ channelId: ChannelStore.getCurrentId(),
+ messageText: messageText,
+ uploadsInProgress: uploadsInProgress,
+ previews: previews,
+ submitting: false,
+ initialText: messageText
+ };
+ }
+ handleSubmit(e) {
e.preventDefault();
if (this.state.uploadsInProgress.length > 0 || this.state.submitting) {
return;
}
- var post = {};
+ let post = {};
post.filenames = [];
post.message = this.state.messageText;
@@ -36,18 +71,18 @@ module.exports = React.createClass({
}
if (post.message.length > Constants.CHARACTER_LIMIT) {
- this.setState({postError: 'Post length must be less than ' + Constants.CHARACTER_LIMIT + ' characters.'});
+ this.setState({postError: `Post length must be less than ${Constants.CHARACTER_LIMIT} characters.`});
return;
}
this.setState({submitting: true, serverError: null});
if (post.message.indexOf('/') === 0) {
- client.executeCommand(
+ Client.executeCommand(
this.state.channelId,
post.message,
false,
- function(data) {
+ function handleCommandSuccess(data) {
PostStore.storeDraft(data.channel_id, null);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
@@ -55,8 +90,8 @@ module.exports = React.createClass({
window.location.href = data.goto_location;
}
}.bind(this),
- function(err) {
- var state = {};
+ function handleCommandError(err) {
+ let state = {};
state.serverError = err.message;
state.submitting = false;
this.setState(state);
@@ -66,26 +101,26 @@ module.exports = React.createClass({
post.channel_id = this.state.channelId;
post.filenames = this.state.previews;
- var time = utils.getTimestamp();
- var userId = UserStore.getCurrentId();
- post.pending_post_id = userId + ':' + time;
+ const time = Utils.getTimestamp();
+ const userId = UserStore.getCurrentId();
+ post.pending_post_id = `${userId}:${time}`;
post.user_id = userId;
post.create_at = time;
post.root_id = this.state.rootId;
post.parent_id = this.state.parentId;
- var channel = ChannelStore.get(this.state.channelId);
+ const channel = ChannelStore.get(this.state.channelId);
PostStore.storePendingPost(post);
PostStore.storeDraft(channel.id, null);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
- client.createPost(post, channel,
- function(data) {
+ Client.createPost(post, channel,
+ function handlePostSuccess(data) {
this.resizePostHolder();
AsyncClient.getPosts();
- var member = ChannelStore.getMember(channel.id);
+ let member = ChannelStore.getMember(channel.id);
member.msg_count = channel.total_msg_count;
member.last_viewed_at = Date.now();
ChannelStore.setChannelMember(member);
@@ -95,8 +130,8 @@ module.exports = React.createClass({
post: data
});
}.bind(this),
- function(err) {
- var state = {};
+ function handlePostError(err) {
+ let state = {};
if (err.message === 'Invalid RootId parameter') {
if ($('#post_deleted').length > 0) {
@@ -113,83 +148,83 @@ module.exports = React.createClass({
}.bind(this)
);
}
- },
- componentDidUpdate: function() {
+ }
+ componentDidUpdate() {
this.resizePostHolder();
- },
- postMsgKeyPress: function(e) {
+ }
+ postMsgKeyPress(e) {
if (e.which === 13 && !e.shiftKey && !e.altKey) {
e.preventDefault();
- this.refs.textbox.getDOMNode().blur();
+ React.findDOMNode(this.refs.textbox).blur();
this.handleSubmit(e);
}
- var t = Date.now();
+ const t = Date.now();
if ((t - this.lastTime) > 5000) {
- SocketStore.sendMessage({channel_id: this.state.channelId, action: 'typing', props: {'parent_id': ''}, state: {}});
+ SocketStore.sendMessage({channel_id: this.state.channelId, action: 'typing', props: {parent_id: ''}, state: {}});
this.lastTime = t;
}
- },
- handleUserInput: function(messageText) {
+ }
+ handleUserInput(messageText) {
this.resizePostHolder();
this.setState({messageText: messageText});
- var draft = PostStore.getCurrentDraft();
- draft['message'] = messageText;
+ let draft = PostStore.getCurrentDraft();
+ draft.message = messageText;
PostStore.storeCurrentDraft(draft);
- },
- resizePostHolder: function() {
- var height = $(window).height() - $(this.refs.topDiv.getDOMNode()).height() - $('#error_bar').outerHeight() - 50;
- $('.post-list-holder-by-time').css('height', height + 'px');
+ }
+ resizePostHolder() {
+ const height = $(window).height() - $(React.findDOMNode(this.refs.topDiv)).height() - $('#error_bar').outerHeight() - 50;
+ $('.post-list-holder-by-time').css('height', `${height}px`);
$(window).trigger('resize');
- },
- handleUploadStart: function(clientIds, channelId) {
- var draft = PostStore.getDraft(channelId);
+ }
+ handleUploadStart(clientIds, channelId) {
+ let draft = PostStore.getDraft(channelId);
- draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(clientIds);
+ draft.uploadsInProgress = draft.uploadsInProgress.concat(clientIds);
PostStore.storeDraft(channelId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress']});
- },
- handleFileUploadComplete: function(filenames, clientIds, channelId) {
- var draft = PostStore.getDraft(channelId);
+ this.setState({uploadsInProgress: draft.uploadsInProgress});
+ }
+ handleFileUploadComplete(filenames, clientIds, channelId) {
+ let draft = PostStore.getDraft(channelId);
// remove each finished file from uploads
- for (var i = 0; i < clientIds.length; i++) {
- var index = draft['uploadsInProgress'].indexOf(clientIds[i]);
+ for (let i = 0; i < clientIds.length; i++) {
+ const index = draft.uploadsInProgress.indexOf(clientIds[i]);
if (index !== -1) {
- draft['uploadsInProgress'].splice(index, 1);
+ draft.uploadsInProgress.splice(index, 1);
}
}
- draft['previews'] = draft['previews'].concat(filenames);
+ draft.previews = draft.previews.concat(filenames);
PostStore.storeDraft(channelId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']});
- },
- handleUploadError: function(err, clientId) {
+ this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
+ }
+ handleUploadError(err, clientId) {
if (clientId !== -1) {
- var draft = PostStore.getDraft(this.state.channelId);
+ let draft = PostStore.getDraft(this.state.channelId);
- var index = draft['uploadsInProgress'].indexOf(clientId);
+ const index = draft.uploadsInProgress.indexOf(clientId);
if (index !== -1) {
- draft['uploadsInProgress'].splice(index, 1);
+ draft.uploadsInProgress.splice(index, 1);
}
PostStore.storeDraft(this.state.channelId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err});
+ this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err});
} else {
this.setState({serverError: err});
}
- },
- removePreview: function(id) {
- var previews = this.state.previews;
- var uploadsInProgress = this.state.uploadsInProgress;
+ }
+ removePreview(id) {
+ let previews = this.state.previews;
+ let uploadsInProgress = this.state.uploadsInProgress;
// id can either be the path of an uploaded file or the client id of an in progress upload
- var index = previews.indexOf(id);
+ let index = previews.indexOf(id);
if (index !== -1) {
previews.splice(index, 1);
} else {
@@ -201,28 +236,28 @@ module.exports = React.createClass({
}
}
- var draft = PostStore.getCurrentDraft();
- draft['previews'] = previews;
- draft['uploadsInProgress'] = uploadsInProgress;
+ let draft = PostStore.getCurrentDraft();
+ draft.previews = previews;
+ draft.uploadsInProgress = uploadsInProgress;
PostStore.storeCurrentDraft(draft);
this.setState({previews: previews, uploadsInProgress: uploadsInProgress});
- },
- componentDidMount: function() {
- ChannelStore.addChangeListener(this._onChange);
+ }
+ componentDidMount() {
+ ChannelStore.addChangeListener(this.onChange);
this.resizePostHolder();
- },
- componentWillUnmount: function() {
- ChannelStore.removeChangeListener(this._onChange);
- },
- _onChange: function() {
- var channelId = ChannelStore.getCurrentId();
+ }
+ componentWillUnmount() {
+ ChannelStore.removeChangeListener(this.onChange);
+ }
+ onChange() {
+ const channelId = ChannelStore.getCurrentId();
if (this.state.channelId !== channelId) {
- var draft = PostStore.getCurrentDraft();
+ let draft = PostStore.getCurrentDraft();
- var previews = [];
- var messageText = '';
- var uploadsInProgress = [];
+ let previews = [];
+ let messageText = '';
+ let uploadsInProgress = [];
if (draft && draft.previews && draft.message) {
previews = draft.previews;
messageText = draft.message;
@@ -231,33 +266,17 @@ module.exports = React.createClass({
this.setState({channelId: channelId, messageText: messageText, initialText: messageText, submitting: false, serverError: null, postError: null, previews: previews, uploadsInProgress: uploadsInProgress});
}
- },
- getInitialState: function() {
- PostStore.clearDraftUploads();
-
- var draft = PostStore.getCurrentDraft();
- var previews = [];
- var messageText = '';
- var uploadsInProgress = [];
- if (draft && draft.previews && draft.message) {
- previews = draft.previews;
- messageText = draft.message;
- uploadsInProgress = draft.uploadsInProgress;
- }
-
- return {channelId: ChannelStore.getCurrentId(), messageText: messageText, uploadsInProgress: uploadsInProgress, previews: previews, submitting: false, initialText: messageText};
- },
- getFileCount: function(channelId) {
+ }
+ getFileCount(channelId) {
if (channelId === this.state.channelId) {
return this.state.previews.length + this.state.uploadsInProgress.length;
- } else {
- var draft = PostStore.getDraft(channelId);
-
- return draft['previews'].length + draft['uploadsInProgress'].length;
}
- },
- render: function() {
- var serverError = null;
+
+ const draft = PostStore.getDraft(channelId);
+ return draft.previews.length + draft.uploadsInProgress.length;
+ }
+ render() {
+ let serverError = null;
if (this.state.serverError) {
serverError = (
<div className='has-error'>
@@ -266,12 +285,12 @@ module.exports = React.createClass({
);
}
- var postError = null;
+ let postError = null;
if (this.state.postError) {
postError = <label className='control-label'>{this.state.postError}</label>;
}
- var preview = null;
+ let preview = null;
if (this.state.previews.length > 0 || this.state.uploadsInProgress.length > 0) {
preview = (
<FilePreview
@@ -281,13 +300,18 @@ module.exports = React.createClass({
);
}
- var postFooterClassName = 'post-create-footer';
+ let postFooterClassName = 'post-create-footer';
if (postError) {
postFooterClassName += ' has-error';
}
return (
- <form id='create_post' ref='topDiv' role='form' onSubmit={this.handleSubmit}>
+ <form
+ id='create_post'
+ ref='topDiv'
+ role='form'
+ onSubmit={this.handleSubmit}
+ >
<div className='post-create'>
<div className='post-create-body'>
<Textbox
@@ -311,10 +335,13 @@ module.exports = React.createClass({
{postError}
{serverError}
{preview}
- <MsgTyping channelId={this.state.channelId} parentId=''/>
+ <MsgTyping
+ channelId={this.state.channelId}
+ parentId=''
+ />
</div>
</div>
</form>
);
}
-});
+}
diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx
index 589737271..4efb9cb23 100644
--- a/web/react/components/delete_channel_modal.jsx
+++ b/web/react/components/delete_channel_modal.jsx
@@ -1,58 +1,99 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var Client =require('../utils/client.jsx');
-var AsyncClient =require('../utils/async_client.jsx');
-var ChannelStore =require('../stores/channel_store.jsx')
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
-module.exports = React.createClass({
- handleDelete: function(e) {
- if (this.state.channel_id.length != 26) return;
+export default class DeleteChannelModal extends React.Component {
+ constructor(props) {
+ super(props);
- Client.deleteChannel(this.state.channel_id,
- function(data) {
+ this.handleDelete = this.handleDelete.bind(this);
+
+ this.state = {
+ title: '',
+ channelId: ''
+ };
+ }
+ handleDelete() {
+ if (this.state.channelId.length !== 26) {
+ return;
+ }
+
+ Client.deleteChannel(this.state.channelId,
+ function handleDeleteSuccess() {
AsyncClient.getChannels(true);
window.location.href = '/';
- }.bind(this),
- function(err) {
- AsyncClient.dispatchError(err, "handleDelete");
- }.bind(this)
+ },
+ function handleDeleteError(err) {
+ AsyncClient.dispatchError(err, 'handleDelete');
+ }
);
- },
- componentDidMount: function() {
- var self = this;
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
+ }
+ componentDidMount() {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) {
var button = $(e.relatedTarget);
- self.setState({ title: button.attr('data-title'), channel_id: button.attr('data-channelid') });
- });
- },
- getInitialState: function() {
- return { title: "", channel_id: "" };
- },
- render: function() {
-
- var channelType = ChannelStore.getCurrent() && ChannelStore.getCurrent().type === 'P' ? "private group" : "channel"
+ this.setState({
+ title: button.attr('data-title'),
+ channelId: button.attr('data-channelid')
+ });
+ }.bind(this));
+ }
+ render() {
+ const channel = ChannelStore.getCurrent();
+ let channelType = 'channel';
+ if (channel && channel.type === 'P') {
+ channelType = 'private group';
+ }
return (
- <div className="modal fade" ref="modal" id="delete_channel" role="dialog" tabIndex="-1" aria-hidden="true">
- <div className="modal-dialog">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title">Confirm DELETE Channel</h4>
- </div>
- <div className="modal-body">
- <p>
- Are you sure you wish to delete the {this.state.title} {channelType}?
- </p>
- </div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" className="btn btn-danger" data-dismiss="modal" onClick={this.handleDelete}>Delete</button>
- </div>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='delete_channel'
+ role='dialog'
+ tabIndex='-1'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4 className='modal-title'>Confirm DELETE Channel</h4>
+ </div>
+ <div className='modal-body'>
+ <p>
+ Are you sure you wish to delete the {this.state.title} {channelType}?
+ </p>
+ </div>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >
+ Cancel
+ </button>
+ <button
+ type='button'
+ className='btn btn-danger'
+ data-dismiss='modal'
+ onClick={this.handleDelete}
+ >
+ Delete
+ </button>
+ </div>
+ </div>
</div>
- </div>
</div>
);
}
-});
+}
diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx
index 55d6f509c..075f9c742 100644
--- a/web/react/components/delete_post_modal.jsx
+++ b/web/react/components/delete_post_modal.jsx
@@ -4,20 +4,28 @@
var Client = require('../utils/client.jsx');
var PostStore = require('../stores/post_store.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-module.exports = React.createClass({
- handleDelete: function(e) {
- Client.deletePost(this.state.channel_id, this.state.post_id,
- function(data) {
- var selected_list = this.state.selectedList;
- if (selected_list && selected_list.order && selected_list.order.length > 0) {
- var selected_post = selected_list.posts[selected_list.order[0]];
- if ((selected_post.id === this.state.post_id && this.state.title === "Post") || selected_post.root_id === this.state.post_id) {
+export default class DeletePostModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleDelete = this.handleDelete.bind(this);
+ this.onListenerChange = this.onListenerChange.bind(this);
+
+ this.state = {title: '', postId: '', channelId: '', selectedList: PostStore.getSelectedPost(), comments: 0};
+ }
+ handleDelete() {
+ Client.deletePost(this.state.channelId, this.state.postId,
+ function deleteSuccess() {
+ var selectedList = this.state.selectedList;
+ if (selectedList && selectedList.order && selectedList.order.length > 0) {
+ var selectedPost = selectedList.posts[selectedList.order[0]];
+ if ((selectedPost.id === this.state.postId && this.state.title === 'Post') || selectedPost.root_id === this.state.postId) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_SEARCH,
results: null
@@ -27,14 +35,14 @@ module.exports = React.createClass({
type: ActionTypes.RECIEVED_POST_SELECTED,
results: null
});
- } else if (selected_post.id === this.state.post_id && this.state.title === "Comment") {
- if (selected_post.root_id && selected_post.root_id.length > 0 && selected_list.posts[selected_post.root_id]) {
- selected_list.order = [selected_post.root_id];
- delete selected_list.posts[selected_post.id];
+ } else if (selectedPost.id === this.state.postId && this.state.title === 'Comment') {
+ if (selectedPost.root_id && selectedPost.root_id.length > 0 && selectedList.posts[selectedPost.root_id]) {
+ selectedList.order = [selectedPost.root_id];
+ delete selectedList.posts[selectedPost.id];
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_POST_SELECTED,
- post_list: selected_list
+ post_list: selectedList
});
AppDispatcher.handleServerAction({
@@ -44,67 +52,97 @@ module.exports = React.createClass({
}
}
}
- PostStore.removePost(this.state.post_id, this.state.channel_id);
- AsyncClient.getPosts(this.state.channel_id);
+ PostStore.removePost(this.state.postId, this.state.channelId);
+ AsyncClient.getPosts(this.state.channelId);
}.bind(this),
- function(err) {
- AsyncClient.dispatchError(err, "deletePost");
- }.bind(this)
+ function deleteFailed(err) {
+ AsyncClient.dispatchError(err, 'deletePost');
+ }
);
- },
- componentDidMount: function() {
- var self = this;
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
+ }
+ componentDidMount() {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function freshOpen(e) {
var newState = {};
- if(BrowserStore.getItem('edit_state_transfer')) {
+ if (BrowserStore.getItem('edit_state_transfer')) {
newState = BrowserStore.getItem('edit_state_transfer');
BrowserStore.removeItem('edit_state_transfer');
} else {
var button = e.relatedTarget;
- newState = { title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments') };
+ newState = {title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), postId: $(button).attr('data-postid'), comments: $(button).attr('data-comments')};
}
- self.setState(newState);
- });
- PostStore.addSelectedPostChangeListener(this._onChange);
- },
- componentWillUnmount: function() {
- PostStore.removeSelectedPostChangeListener(this._onChange);
- },
- _onChange: function() {
+ this.setState(newState);
+ }.bind(this));
+ PostStore.addSelectedPostChangeListener(this.onListenerChange);
+ }
+ componentWillUnmount() {
+ PostStore.removeSelectedPostChangeListener(this.onListenerChange);
+ }
+ onListenerChange() {
var newList = PostStore.getSelectedPost();
- if (!utils.areStatesEqual(this.state.selectedList, newList)) {
- this.setState({ selectedList: newList });
+ if (!Utils.areStatesEqual(this.state.selectedList, newList)) {
+ this.setState({selectedList: newList});
+ }
+ }
+ render() {
+ var error = null;
+ if (this.state.error) {
+ error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
+ }
+
+ var commentWarning = '';
+ if (this.state.comments > 0) {
+ commentWarning = 'This post has ' + this.state.comments + ' comment(s) on it.';
}
- },
- getInitialState: function() {
- return { title: "", post_id: "", channel_id: "", selectedList: PostStore.getSelectedPost(), comments: 0 };
- },
- render: function() {
- var error = this.state.error ? <div className='form-group has-error'><label className='control-label'>{ this.state.error }</label></div> : null;
return (
- <div className="modal fade" id="delete_post" ref="modal" role="dialog" tabIndex="-1" aria-hidden="true">
- <div className="modal-dialog modal-push-down">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title">Confirm {this.state.title} Delete</h4>
+ <div
+ className='modal fade'
+ id='delete_post'
+ ref='modal'
+ role='dialog'
+ tabIndex='-1'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog modal-push-down'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4 className='modal-title'>Confirm {this.state.title} Delete</h4>
</div>
- <div className="modal-body">
+ <div className='modal-body'>
Are you sure you want to delete the {this.state.title.toLowerCase()}?
<br/>
<br/>
- { this.state.comments > 0 ?
- "This post has " + this.state.comments + " comment(s) on it."
- : "" }
+ {commentWarning}
</div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" className="btn btn-danger" data-dismiss="modal" onClick={this.handleDelete}>Delete</button>
+ {error}
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >
+ Cancel
+ </button>
+ <button
+ type='button'
+ className='btn btn-danger'
+ data-dismiss='modal'
+ onClick={this.handleDelete}
+ >
+ Delete
+ </button>
</div>
</div>
</div>
</div>
);
}
-});
+}
diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx
index 76f0c2c4d..e93bab431 100644
--- a/web/react/components/edit_channel_modal.jsx
+++ b/web/react/components/edit_channel_modal.jsx
@@ -1,79 +1,142 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var Client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
-module.exports = React.createClass({
- handleEdit: function(e) {
+export default class EditChannelModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleEdit = this.handleEdit.bind(this);
+ this.handleUserInput = this.handleUserInput.bind(this);
+ this.handleClose = this.handleClose.bind(this);
+
+ this.state = {
+ description: '',
+ title: '',
+ channelId: '',
+ serverError: ''
+ };
+ }
+ handleEdit() {
var data = {};
- data["channel_id"] = this.state.channel_id;
- if (data["channel_id"].length !== 26) return;
- data["channel_description"] = this.state.description.trim();
+ data.channel_id = this.state.channelId;
+
+ if (data.channel_id.length !== 26) {
+ return;
+ }
+
+ data.channel_description = this.state.description.trim();
Client.updateChannelDesc(data,
- function(data) {
- this.setState({ server_error: "" });
- AsyncClient.getChannel(this.state.channel_id);
- $(this.refs.modal.getDOMNode()).modal('hide');
+ function handleUpdateSuccess() {
+ this.setState({serverError: ''});
+ AsyncClient.getChannel(this.state.channelId);
+ $(React.findDOMNode(this.refs.modal)).modal('hide');
}.bind(this),
- function(err) {
- if (err.message === "Invalid channel_description parameter") {
- this.setState({ server_error: "This description is too long, please enter a shorter one" });
- }
- else {
- this.setState({ server_error: err.message });
+ function handleUpdateError(err) {
+ if (err.message === 'Invalid channel_description parameter') {
+ this.setState({serverError: 'This description is too long, please enter a shorter one'});
+ } else {
+ this.setState({serverError: err.message});
}
}.bind(this)
);
- },
- handleUserInput: function(e) {
- this.setState({ description: e.target.value });
- },
- handleClose: function() {
- this.setState({description: "", server_error: ""});
- },
- componentDidMount: function() {
- var self = this;
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
- var button = e.relatedTarget;
- self.setState({ description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), server_error: "" });
- });
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', this.handleClose)
- },
- componentWillUnmount: function() {
- $(this.refs.modal.getDOMNode()).off('hidden.bs.modal', this.handleClose)
- },
- getInitialState: function() {
- return { description: "", title: "", channel_id: "" };
- },
- render: function() {
- var server_error = this.state.server_error ? <div className='form-group has-error'><br/><label className='control-label'>{ this.state.server_error }</label></div> : null;
+ }
+ handleUserInput(e) {
+ this.setState({description: e.target.value});
+ }
+ handleClose() {
+ this.setState({description: '', serverError: ''});
+ }
+ componentDidMount() {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) {
+ const button = e.relatedTarget;
+ this.setState({description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''});
+ }.bind(this));
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
+ }
+ componentWillUnmount() {
+ $(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose);
+ }
+ render() {
+ var serverError = null;
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><br/><label className='control-label'>{this.state.serverError}</label></div>;
+ }
- var editTitle = <h4 className='modal-title' ref='title'>Edit Description</h4>;
+ var editTitle = (
+ <h4
+ className='modal-title'
+ ref='title'
+ >
+ Edit Description
+ </h4>
+ );
if (this.state.title) {
- editTitle = <h4 className='modal-title' ref='title'>Edit Description for <span className='name'>{this.state.title}</span></h4>;
+ editTitle = (
+ <h4
+ className='modal-title'
+ ref='title'
+ >
+ Edit Description for <span className='name'>{this.state.title}</span>
+ </h4>
+ );
}
return (
- <div className="modal fade" ref="modal" id="edit_channel" role="dialog" tabIndex="-1" aria-hidden="true">
- <div className="modal-dialog">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- {editTitle}
- </div>
- <div className="modal-body">
- <textarea className="form-control no-resize" rows="6" ref="channelDesc" maxLength="1024" value={this.state.description} onChange={this.handleUserInput}></textarea>
- { server_error }
- </div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" className="btn btn-primary" onClick={this.handleEdit}>Save</button>
- </div>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='edit_channel'
+ role='dialog'
+ tabIndex='-1'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ {editTitle}
+ </div>
+ <div className='modal-body'>
+ <textarea
+ className='form-control no-resize'
+ rows='6'
+ ref='channelDesc'
+ maxLength='1024'
+ value={this.state.description}
+ onChange={this.handleUserInput}
+ />
+ {serverError}
+ </div>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >
+ Cancel
+ </button>
+ <button
+ type='button'
+ className='btn btn-primary'
+ onClick={this.handleEdit}
+ >
+ Save
+ </button>
+ </div>
+ </div>
</div>
- </div>
</div>
);
}
-});
+}
diff --git a/web/react/components/edit_post_modal.jsx b/web/react/components/edit_post_modal.jsx
index 1c5a1ed5e..fef60c715 100644
--- a/web/react/components/edit_post_modal.jsx
+++ b/web/react/components/edit_post_modal.jsx
@@ -3,13 +3,21 @@
var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
-var Constants = require('../utils/constants.jsx');
-var utils = require('../utils/utils.jsx');
var Textbox = require('./textbox.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
-module.exports = React.createClass({
- handleEdit: function(e) {
+export default class EditPostModal extends React.Component {
+ constructor() {
+ super();
+
+ this.handleEdit = this.handleEdit.bind(this);
+ this.handleEditInput = this.handleEditInput.bind(this);
+ this.handleEditKeyPress = this.handleEditKeyPress.bind(this);
+ this.handleUserInput = this.handleUserInput.bind(this);
+
+ this.state = {editText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: ''};
+ }
+ handleEdit() {
var updatedPost = {};
updatedPost.message = this.state.editText.trim();
@@ -17,8 +25,8 @@ module.exports = React.createClass({
var tempState = this.state;
delete tempState.editText;
BrowserStore.setItem('edit_state_transfer', tempState);
- $("#edit_post").modal('hide');
- $("#delete_post").modal('show');
+ $('#edit_post').modal('hide');
+ $('#delete_post').modal('show');
return;
}
@@ -26,79 +34,102 @@ module.exports = React.createClass({
updatedPost.channel_id = this.state.channel_id;
Client.updatePost(updatedPost,
- function(data) {
+ function success() {
AsyncClient.getPosts(this.state.channel_id);
window.scrollTo(0, 0);
}.bind(this),
- function(err) {
- AsyncClient.dispatchError(err, "updatePost");
- }.bind(this)
+ function error(err) {
+ AsyncClient.dispatchError(err, 'updatePost');
+ }
);
- $("#edit_post").modal('hide');
+ $('#edit_post').modal('hide');
$(this.state.refocusId).focus();
- },
- handleEditInput: function(editMessage) {
+ }
+ handleEditInput(editMessage) {
this.setState({editText: editMessage});
- },
- handleEditKeyPress: function(e) {
- if (e.which == 13 && !e.shiftKey && !e.altKey) {
+ }
+ handleEditKeyPress(e) {
+ if (e.which === 13 && !e.shiftKey && !e.altKey) {
e.preventDefault();
- this.refs.editbox.getDOMNode().blur();
+ React.findDOMNode(this.refs.editbox).blur();
this.handleEdit(e);
}
- },
- handleUserInput: function(e) {
- this.setState({ editText: e.target.value });
- },
- componentDidMount: function() {
+ }
+ handleUserInput(e) {
+ this.setState({editText: e.target.value});
+ }
+ componentDidMount() {
var self = this;
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
- self.setState({editText: "", title: "", channel_id: "", post_id: "", comments: 0, refocusId: "", error: ''});
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function onHidden() {
+ self.setState({editText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: ''});
});
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function onShow(e) {
var button = e.relatedTarget;
- 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')});
});
- $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function(e) {
+ $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function onShown() {
self.refs.editbox.resize();
});
- },
- getInitialState: function() {
- return { editText: "", title: "", post_id: "", channel_id: "", comments: 0, refocusId: "" };
- },
- render: function() {
- var error = this.state.error ? <div className='form-group has-error'><br /><label className='control-label'>{ this.state.error }</label></div> : <div className='form-group'><br /></div>;
+ }
+ render() {
+ var error = (<div className='form-group'><br /></div>);
+ if (this.state.error) {
+ error = (<div className='form-group has-error'><br /><label className='control-label'>{this.state.error}</label></div>);
+ }
return (
- <div className="modal fade edit-modal" ref="modal" id="edit_post" role="dialog" tabIndex="-1" aria-hidden="true">
- <div className="modal-dialog modal-push-down">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close" onClick={this.handleEditClose}><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title">Edit {this.state.title}</h4>
- </div>
- <div className="edit-modal-body modal-body">
- <Textbox
- onUserInput={this.handleEditInput}
- onKeyPress={this.handleEditKeyPress}
- messageText={this.state.editText}
- createMessage="Edit the post..."
- id="edit_textbox"
- ref="editbox"
- />
- { error }
- </div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" className="btn btn-primary" onClick={this.handleEdit}>Save</button>
- </div>
+ <div
+ className='modal fade edit-modal'
+ ref='modal'
+ id='edit_post'
+ role='dialog'
+ tabIndex='-1'
+ aria-hidden='true' >
+ <div className='modal-dialog modal-push-down'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ onClick={this.handleEditClose}>
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4 className='modal-title'>Edit {this.state.title}</h4>
+ </div>
+ <div className='edit-modal-body modal-body'>
+ <Textbox
+ onUserInput={this.handleEditInput}
+ onKeyPress={this.handleEditKeyPress}
+ messageText={this.state.editText}
+ createMessage='Edit the post...'
+ id='edit_textbox'
+ ref='editbox'
+ />
+ {error}
+ </div>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal' >
+ Cancel
+ </button>
+ <button
+ type='button'
+ className='btn btn-primary'
+ onClick={this.handleEdit}>
+ Save
+ </button>
+ </div>
+ </div>
</div>
- </div>
</div>
);
}
-});
+}
diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx
index 678eb9928..f2e91dd98 100644
--- a/web/react/components/email_verify.jsx
+++ b/web/react/components/email_verify.jsx
@@ -1,35 +1,58 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-module.exports = React.createClass({
- handleResend: function() {
- window.location.href = window.location.href + "&resend=true"
- },
- render: function() {
- var title = "";
- var body = "";
- var resend = "";
- if (this.props.isVerified === "true") {
- title = config.SiteName + " Email Verified";
- body = <p>Your email has been verified! Click <a href={this.props.teamURL + "?email=" + this.props.userEmail}>here</a> to log in.</p>;
+export default class EmailVerify extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleResend = this.handleResend.bind(this);
+
+ this.state = {};
+ }
+ handleResend() {
+ window.location.href = window.location.href + '&resend=true';
+ }
+ render() {
+ var title = '';
+ var body = '';
+ var resend = '';
+ if (this.props.isVerified === 'true') {
+ title = config.SiteName + ' Email Verified';
+ body = <p>Your email has been verified! Click <a href={this.props.teamURL + '?email=' + this.props.userEmail}>here</a> to log in.</p>;
} else {
- title = config.SiteName + " Email Not Verified";
+ title = config.SiteName + ' Email Not Verified';
body = <p>Please verify your email address. Check your inbox for an email.</p>;
- resend = <button onClick={this.handleResend} className="btn btn-primary">Resend Email</button>
+ resend = (<button
+ onClick={this.handleResend}
+ className='btn btn-primary'
+ >
+ Resend Email
+ </button>);
}
return (
- <div className="col-sm-offset-4 col-sm-4">
- <div className="panel panel-default">
- <div className="panel-heading">
- <h3 className="panel-title">{ title }</h3>
+ <div className='col-sm-offset-4 col-sm-4'>
+ <div className='panel panel-default'>
+ <div className='panel-heading'>
+ <h3 className='panel-title'>{title}</h3>
</div>
- <div className="panel-body">
- { body }
- { resend }
+ <div className='panel-body'>
+ {body}
+ {resend}
</div>
</div>
</div>
);
}
-});
+}
+
+EmailVerify.defaultProps = {
+ isVerified: 'false',
+ teamURL: '',
+ userEmail: ''
+};
+EmailVerify.propTypes = {
+ isVerified: React.PropTypes.string,
+ teamURL: React.PropTypes.string,
+ userEmail: React.PropTypes.string
+};
diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx
index f7514a009..95f3e572c 100644
--- a/web/react/components/error_bar.jsx
+++ b/web/react/components/error_bar.jsx
@@ -7,32 +7,40 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-function getStateFromStores() {
- var error = ErrorStore.getLastError();
- if (error && error.message !== "There appears to be a problem with your internet connection") {
- return { message: error.message };
- } else {
- return { message: null };
- }
-}
+export default class ErrorBar extends React.Component {
+ constructor() {
+ super();
-module.exports = React.createClass({
- displayName: 'ErrorBar',
+ this.onErrorChange = this.onErrorChange.bind(this);
+ this.handleClose = this.handleClose.bind(this);
- componentDidMount: function() {
- ErrorStore.addChangeListener(this._onChange);
+ this.state = this.getStateFromStores();
+ if (this.state.message) {
+ setTimeout(this.handleClose, 10000);
+ }
+ }
+ getStateFromStores() {
+ var error = ErrorStore.getLastError();
+ if (!error || error.message === 'There appears to be a problem with your internet connection') {
+ return {message: null};
+ }
+
+ return {message: error.message};
+ }
+ componentDidMount() {
+ ErrorStore.addChangeListener(this.onErrorChange);
$('body').css('padding-top', $(React.findDOMNode(this)).outerHeight());
- $(window).resize(function() {
+ $(window).resize(function onResize() {
if (this.state.message) {
$('body').css('padding-top', $(React.findDOMNode(this)).outerHeight());
}
}.bind(this));
- },
- componentWillUnmount: function() {
- ErrorStore.removeChangeListener(this._onChange);
- },
- _onChange: function() {
- var newState = getStateFromStores();
+ }
+ componentWillUnmount() {
+ ErrorStore.removeChangeListener(this.onErrorChange);
+ }
+ onErrorChange() {
+ var newState = this.getStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
if (newState.message) {
setTimeout(this.handleClose, 10000);
@@ -40,9 +48,11 @@ module.exports = React.createClass({
this.setState(newState);
}
- },
- handleClose: function(e) {
- if (e) e.preventDefault();
+ }
+ handleClose(e) {
+ if (e) {
+ e.preventDefault();
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_ERROR,
@@ -50,24 +60,22 @@ module.exports = React.createClass({
});
$('body').css('padding-top', '0');
- },
- getInitialState: function() {
- var state = getStateFromStores();
- if (state.message) {
- setTimeout(this.handleClose, 10000);
- }
- return state;
- },
- render: function() {
+ }
+ render() {
if (this.state.message) {
return (
- <div className="error-bar">
+ <div className='error-bar'>
<span>{this.state.message}</span>
- <a href="#" className="error-bar__close" onClick={this.handleClose}>&times;</a>
+ <a
+ href='#'
+ className='error-bar__close'
+ onClick={this.handleClose}>
+ &times;
+ </a>
</div>
);
- } else {
- return <div/>;
}
+
+ return <div/>;
}
-}); \ No newline at end of file
+}
diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx
index 45e6c5e28..d07afbc2b 100644
--- a/web/react/components/file_attachment.jsx
+++ b/web/react/components/file_attachment.jsx
@@ -5,31 +5,24 @@ var utils = require('../utils/utils.jsx');
var Client = require('../utils/client.jsx');
var Constants = require('../utils/constants.jsx');
-module.exports = React.createClass({
- displayName: "FileAttachment",
- canSetState: false,
- propTypes: {
- // a list of file pathes displayed by the parent FileAttachmentList
- filename: React.PropTypes.string.isRequired,
- // the index of this attachment preview in the parent FileAttachmentList
- index: React.PropTypes.number.isRequired,
- // the identifier of the modal dialog used to preview files
- modalId: React.PropTypes.string.isRequired,
- // handler for when the thumbnail is clicked
- handleImageClick: React.PropTypes.func
- },
- getInitialState: function() {
- return {fileSize: -1};
- },
- componentDidMount: function() {
+export default class FileAttachment extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.loadFiles = this.loadFiles.bind(this);
+
+ this.canSetState = false;
+ this.state = {fileSize: -1};
+ }
+ componentDidMount() {
this.loadFiles();
- },
- componentDidUpdate: function(prevProps) {
+ }
+ componentDidUpdate(prevProps) {
if (this.props.filename !== prevProps.filename) {
this.loadFiles();
}
- },
- loadFiles: function() {
+ }
+ loadFiles() {
this.canSetState = true;
var filename = this.props.filename;
@@ -39,91 +32,92 @@ module.exports = React.createClass({
var type = utils.getFileType(fileInfo.ext);
// This is a temporary patch to fix issue with old files using absolute paths
- if (fileInfo.path.indexOf("/api/v1/files/get") != -1) {
- fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1];
+ if (fileInfo.path.indexOf('/api/v1/files/get') !== -1) {
+ fileInfo.path = fileInfo.path.split('/api/v1/files/get')[1];
}
- fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path;
-
- if (type === "image") {
- var self = this;
- $('<img/>').attr('src', fileInfo.path+'_thumb.jpg').load(function(path, name){ return function() {
- $(this).remove();
- if (name in self.refs) {
- var imgDiv = self.refs[name].getDOMNode();
-
- $(imgDiv).removeClass('post__load');
- $(imgDiv).addClass('post__image');
-
- var width = this.width || $(this).width();
- var height = this.height || $(this).height();
-
- if (width < Constants.THUMBNAIL_WIDTH
- && height < Constants.THUMBNAIL_HEIGHT) {
- $(imgDiv).addClass('small');
- } else {
- $(imgDiv).addClass('normal');
+ fileInfo.path = utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path;
+
+ if (type === 'image') {
+ var self = this; // Need this reference since we use the given "this"
+ $('<img/>').attr('src', fileInfo.path + '_thumb.jpg').load(function loadWrapper(path, name) {
+ return function loader() {
+ $(this).remove();
+ if (name in self.refs) {
+ var imgDiv = React.findDOMNode(self.refs[name]);
+
+ $(imgDiv).removeClass('post__load');
+ $(imgDiv).addClass('post__image');
+
+ var width = this.width || $(this).width();
+ var height = this.height || $(this).height();
+
+ if (width < Constants.THUMBNAIL_WIDTH &&
+ height < Constants.THUMBNAIL_HEIGHT) {
+ $(imgDiv).addClass('small');
+ } else {
+ $(imgDiv).addClass('normal');
+ }
+
+ var re1 = new RegExp(' ', 'g');
+ var re2 = new RegExp('\\(', 'g');
+ var re3 = new RegExp('\\)', 'g');
+ var url = path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29');
+ $(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg)');
}
-
- var re1 = new RegExp(' ', 'g');
- var re2 = new RegExp('\\(', 'g');
- var re3 = new RegExp('\\)', 'g');
- var url = path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29');
- $(imgDiv).css('background-image', 'url('+url+'_thumb.jpg)');
- }
- }}(fileInfo.path, filename));
+ }; }(fileInfo.path, filename));
}
}
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
// keep track of when this component is mounted so that we can asynchronously change state without worrying about whether or not we're mounted
this.canSetState = false;
- },
- shouldComponentUpdate: function(nextProps, nextState) {
+ }
+ shouldComponentUpdate(nextProps, nextState) {
if (!utils.areStatesEqual(nextProps, this.props)) {
return true;
}
// the only time this object should update is when it receives an updated file size which we can usually handle without re-rendering
- if (nextState.fileSize != this.state.fileSize) {
+ if (nextState.fileSize !== this.state.fileSize) {
if (this.refs.fileSize) {
// update the UI element to display the file size without re-rendering the whole component
- this.refs.fileSize.getDOMNode().innerHTML = utils.fileSizeToString(nextState.fileSize);
+ React.findDOMNode(this.refs.fileSize).innerHTML = utils.fileSizeToString(nextState.fileSize);
return false;
- } else {
- // we can't find the element that should hold the file size so we must not have rendered yet
- return true;
}
- } else {
+
+ // we can't find the element that should hold the file size so we must not have rendered yet
return true;
}
- },
- render: function() {
+
+ return true;
+ }
+ render() {
var filename = this.props.filename;
var fileInfo = utils.splitFileLocation(filename);
var type = utils.getFileType(fileInfo.ext);
var thumbnail;
- if (type === "image") {
- thumbnail = <div ref={filename} className="post__load" style={{backgroundImage: 'url(/static/images/load.gif)'}}/>;
+ if (type === 'image') {
+ thumbnail = (<div
+ ref={filename}
+ className='post__load'
+ style={{backgroundImage: 'url(/static/images/load.gif)'}} />);
} else {
- thumbnail = <div className={"file-icon "+utils.getIconClassName(type)}/>;
+ thumbnail = <div className={'file-icon ' + utils.getIconClassName(type)}/>;
}
- var fileSizeString = "";
+ var fileSizeString = '';
if (this.state.fileSize < 0) {
- var self = this;
-
Client.getFileInfo(
filename,
- function(data) {
- if (self.canSetState) {
- self.setState({fileSize: parseInt(data["size"], 10)});
+ function success(data) {
+ if (this.canSetState) {
+ this.setState({fileSize: parseInt(data.size, 10)});
}
- },
- function(err) {
- }
+ }.bind(this),
+ function error() {}
);
} else {
fileSizeString = utils.fileSizeToString(this.state.fileSize);
@@ -132,25 +126,51 @@ module.exports = React.createClass({
var filenameString = decodeURIComponent(utils.getFileName(filename));
var trimmedFilename;
if (filenameString.length > 35) {
- trimmedFilename = filenameString.substring(0, Math.min(35, filenameString.length)) + "...";
+ trimmedFilename = filenameString.substring(0, Math.min(35, filenameString.length)) + '...';
} else {
trimmedFilename = filenameString;
}
return (
- <div className="post-image__column" key={filename}>
- <a className="post-image__thumbnail" href="#" onClick={this.props.handleImageClick}
- data-img-id={this.props.index} data-toggle="modal" data-target={"#" + this.props.modalId }>
+ <div
+ className='post-image__column'
+ key={filename}>
+ <a className='post-image__thumbnail'
+ href='#'
+ onClick={this.props.handleImageClick}
+ data-img-id={this.props.index}
+ data-toggle='modal'
+ data-target={'#' + this.props.modalId} >
{thumbnail}
</a>
- <div className="post-image__details">
- <div data-toggle="tooltip" title={filenameString} className="post-image__name">{trimmedFilename}</div>
+ <div className='post-image__details'>
+ <div
+ data-toggle='tooltip'
+ title={filenameString}
+ className='post-image__name' >
+ {trimmedFilename}
+ </div>
<div>
- <span className="post-image__type">{fileInfo.ext.toUpperCase()}</span>
- <span className="post-image__size">{fileSizeString}</span>
+ <span className='post-image__type'>{fileInfo.ext.toUpperCase()}</span>
+ <span className='post-image__size'>{fileSizeString}</span>
</div>
</div>
</div>
);
}
-});
+}
+
+FileAttachment.propTypes = {
+
+ // a list of file pathes displayed by the parent FileAttachmentList
+ filename: React.PropTypes.string.isRequired,
+
+ // the index of this attachment preview in the parent FileAttachmentList
+ index: React.PropTypes.number.isRequired,
+
+ // the identifier of the modal dialog used to preview files
+ modalId: React.PropTypes.string.isRequired,
+
+ // handler for when the thumbnail is clicked
+ handleImageClick: React.PropTypes.func
+};
diff --git a/web/react/components/file_attachment_list.jsx b/web/react/components/file_attachment_list.jsx
index df4424d03..33643de73 100644
--- a/web/react/components/file_attachment_list.jsx
+++ b/web/react/components/file_attachment_list.jsx
@@ -5,33 +5,30 @@ var ViewImageModal = require('./view_image.jsx');
var FileAttachment = require('./file_attachment.jsx');
var Constants = require('../utils/constants.jsx');
-module.exports = React.createClass({
- displayName: "FileAttachmentList",
- propTypes: {
- // a list of file pathes displayed by this
- filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
- // the identifier of the modal dialog used to preview files
- modalId: React.PropTypes.string.isRequired,
- // the channel that this is part of
- channelId: React.PropTypes.string,
- // the user that owns the post that this is attached to
- userId: React.PropTypes.string
- },
- getInitialState: function() {
- return {startImgId: 0};
- },
- render: function() {
+export default class FileAttachmentList extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {startImgId: 0};
+ }
+ render() {
var filenames = this.props.filenames;
var modalId = this.props.modalId;
var postFiles = [];
for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) {
- postFiles.push(<FileAttachment key={i} filename={filenames[i]} index={i} modalId={modalId} handleImageClick={this.handleImageClick} />);
+ postFiles.push(
+ <FileAttachment
+ key={i}
+ filename={filenames[i]}
+ index={i}
+ modalId={modalId}
+ handleImageClick={this.handleImageClick} />
+ );
}
return (
<div>
- <div className="post-image__columns">
+ <div className='post-image__columns'>
{postFiles}
</div>
<ViewImageModal
@@ -42,8 +39,23 @@ module.exports = React.createClass({
filenames={filenames} />
</div>
);
- },
- handleImageClick: function(e) {
- this.setState({startImgId: parseInt($(e.target.parentNode).attr('data-img-id'))});
}
-});
+ handleImageClick(e) {
+ this.setState({startImgId: parseInt($(e.target.parentNode).attr('data-img-id'), 10)});
+ }
+}
+
+FileAttachmentList.propTypes = {
+
+ // a list of file pathes displayed by this
+ filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
+
+ // the identifier of the modal dialog used to preview files
+ modalId: React.PropTypes.string.isRequired,
+
+ // the channel that this is part of
+ channelId: React.PropTypes.string,
+
+ // the user that owns the post that this is attached to
+ userId: React.PropTypes.string
+};
diff --git a/web/react/components/file_preview.jsx b/web/react/components/file_preview.jsx
index d1b2f734a..33382a439 100644
--- a/web/react/components/file_preview.jsx
+++ b/web/react/components/file_preview.jsx
@@ -1,14 +1,17 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserStore = require('../stores/user_store.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var client = require('../utils/client.jsx');
-var utils = require('../utils/utils.jsx');
-var Constants = require('../utils/constants.jsx');
+var Utils = require('../utils/utils.jsx');
-module.exports = React.createClass({
- handleRemove: function(e) {
+export default class FilePreview extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleRemove = this.handleRemove.bind(this);
+
+ this.state = {};
+ }
+ handleRemove(e) {
var previewDiv = e.target.parentNode.parentNode;
if (previewDiv.hasAttribute('data-filename')) {
@@ -16,51 +19,96 @@ module.exports = React.createClass({
} else if (previewDiv.hasAttribute('data-client-id')) {
this.props.onRemove(previewDiv.getAttribute('data-client-id'));
}
- },
- render: function() {
+ }
+ render() {
var previews = [];
- this.props.files.forEach(function(filename) {
-
+ this.props.files.forEach(function setupPreview(fullFilename) {
+ var filename = fullFilename;
var originalFilename = filename;
var filenameSplit = filename.split('.');
- var ext = filenameSplit[filenameSplit.length-1];
- var type = utils.getFileType(ext);
+ var ext = filenameSplit[filenameSplit.length - 1];
+ var type = Utils.getFileType(ext);
+
// This is a temporary patch to fix issue with old files using absolute paths
- if (filename.indexOf("/api/v1/files/get") != -1) {
- filename = filename.split("/api/v1/files/get")[1];
+
+ if (filename.indexOf('/api/v1/files/get') !== -1) {
+ filename = filename.split('/api/v1/files/get')[1];
}
- filename = utils.getWindowLocationOrigin() + "/api/v1/files/get" + filename;
+ filename = Utils.getWindowLocationOrigin() + '/api/v1/files/get' + filename;
- if (type === "image") {
+ if (type === 'image') {
previews.push(
- <div key={filename} className="preview-div" data-filename={originalFilename}>
- <img className="preview-img" src={filename}/>
- <a className="remove-preview" onClick={this.handleRemove}><i className="glyphicon glyphicon-remove"/></a>
+ <div
+ key={filename}
+ className='preview-div'
+ data-filename={originalFilename}
+ >
+ <img
+ className='preview-img'
+ src={filename}
+ />
+ <a
+ className='remove-preview'
+ onClick={this.handleRemove}
+ >
+ <i className='glyphicon glyphicon-remove'/>
+ </a>
</div>
);
} else {
previews.push(
- <div key={filename} className="preview-div custom-file" data-filename={originalFilename}>
- <div className={"file-icon "+utils.getIconClassName(type)}/>
- <a className="remove-preview" onClick={this.handleRemove}><i className="glyphicon glyphicon-remove"/></a>
+ <div
+ key={filename}
+ className='preview-div custom-file'
+ data-filename={originalFilename}
+ >
+ <div className={'file-icon ' + Utils.getIconClassName(type)}/>
+ <a
+ className='remove-preview'
+ onClick={this.handleRemove}
+ >
+ <i className='glyphicon glyphicon-remove'/>
+ </a>
</div>
);
}
}.bind(this));
- this.props.uploadsInProgress.forEach(function(clientId) {
+ this.props.uploadsInProgress.forEach(function addUploadsInProgress(clientId) {
previews.push(
- <div className="preview-div" data-client-id={clientId}>
- <img className="spinner" src="/static/images/load.gif"/>
- <a className="remove-preview" onClick={this.handleRemove}><i className="glyphicon glyphicon-remove"/></a>
+ <div
+ key={clientId}
+ className='preview-div'
+ data-client-id={clientId}
+ >
+ <img
+ className='spinner'
+ src='/static/images/load.gif'
+ />
+ <a
+ className='remove-preview'
+ onClick={this.handleRemove}
+ >
+ <i className='glyphicon glyphicon-remove'/>
+ </a>
</div>
);
}.bind(this));
return (
- <div className="preview-container">
+ <div className='preview-container'>
{previews}
</div>
);
}
-});
+}
+
+FilePreview.defaultProps = {
+ files: null,
+ uploadsInProgress: null
+};
+FilePreview.propTypes = {
+ onRemove: React.PropTypes.func.isRequired,
+ files: React.PropTypes.array,
+ uploadsInProgress: React.PropTypes.array
+};
diff --git a/web/react/components/file_upload_overlay.jsx b/web/react/components/file_upload_overlay.jsx
index f35556371..265924206 100644
--- a/web/react/components/file_upload_overlay.jsx
+++ b/web/react/components/file_upload_overlay.jsx
@@ -1,12 +1,8 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-module.exports = React.createClass({
- displayName: 'FileUploadOverlay',
- propTypes: {
- overlayType: React.PropTypes.string
- },
- render: function() {
+export default class FileUploadOverlay extends React.Component {
+ render() {
var overlayClass = 'file-overlay hidden';
if (this.props.overlayType === 'right') {
overlayClass += ' right-file-overlay';
@@ -23,4 +19,8 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
+
+FileUploadOverlay.propTypes = {
+ overlayType: React.PropTypes.string
+};
diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx
index 3b10926f5..fc32d946b 100644
--- a/web/react/components/get_link_modal.jsx
+++ b/web/react/components/get_link_modal.jsx
@@ -2,69 +2,114 @@
// See License.txt for license information.
var UserStore = require('../stores/user_store.jsx');
-var ZeroClipboardMixin = require('react-zeroclipboard-mixin');
-ZeroClipboardMixin.ZeroClipboard.config({
- swfPath: '../../static/flash/ZeroClipboard.swf'
-});
+export default class GetLinkModal extends React.Component {
+ constructor(props) {
+ super(props);
-module.exports = React.createClass({
- displayName: 'GetLinkModal',
- zeroclipboardElementsSelector: '[data-copy-btn]',
- mixins: [ZeroClipboardMixin],
- componentDidMount: function() {
- var self = this;
+ this.handleClick = this.handleClick.bind(this);
+
+ this.state = {copiedLink: false};
+ }
+ componentDidMount() {
if (this.refs.modal) {
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
- var button = e.relatedTarget;
- self.setState({title: $(button).attr('data-title'), value: $(button).attr('data-value')});
- });
- $(this.refs.modal.getDOMNode()).on('hide.bs.modal', function() {
- self.setState({copiedLink: false});
- });
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) {
+ var button = e.relatedTarget;
+ this.setState({title: $(button).attr('data-title'), value: $(button).attr('data-value')});
+ }.bind(this));
+ $(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', function hide() {
+ this.setState({copiedLink: false});
+ }.bind(this));
+ }
+ }
+ handleClick() {
+ var copyTextarea = $(React.findDOMNode(this.refs.textarea));
+ copyTextarea.select();
+
+ try {
+ var successful = document.execCommand('copy');
+ if (successful) {
+ this.setState({copiedLink: true});
+ } else {
+ this.setState({copiedLink: false});
+ }
+ } catch (err) {
+ this.setState({copiedLink: false});
}
- },
- getInitialState: function() {
- return {copiedLink: false};
- },
- handleClick: function() {
- this.setState({copiedLink: true});
- },
- render: function() {
+ }
+ render() {
var currentUser = UserStore.getCurrentUser();
var copyLinkConfirm = null;
if (this.state.copiedLink) {
- copyLinkConfirm = <p className='alert alert-success copy-link-confirm'><i className="fa fa-check"></i> Link copied to clipboard.</p>;
+ copyLinkConfirm = <p className='alert alert-success copy-link-confirm'><i className='fa fa-check'></i> Link copied to clipboard.</p>;
}
if (currentUser != null) {
return (
- <div className='modal fade' ref='modal' id='get_link' tabIndex='-1' role='dialog' aria-hidden='true'>
- <div className='modal-dialog'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
- <h4 className='modal-title' id='myModalLabel'>{this.state.title} Link</h4>
- </div>
- <div className='modal-body'>
- <p>
- Send {strings.Team + 'mates'} the link below for them to sign-up to this {strings.Team} site.
- <br /><br />
- Be careful not to share this link publicly, since anyone with the link can join your {strings.Team}.
- </p>
- <textarea className='form-control no-resize' readOnly='true' value={this.state.value}></textarea>
- </div>
- <div className='modal-footer'>
- <button type='button' className='btn btn-default' data-dismiss='modal'>Close</button>
- <button data-copy-btn='true' type='button' className='btn btn-primary pull-left' onClick={this.handleClick} data-clipboard-text={this.state.value}>Copy Link</button>
- {copyLinkConfirm}
+ <div
+ className='modal fade'
+ ref='modal'
+ id='get_link'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4
+ className='modal-title'
+ id='myModalLabel'
+ >
+ {this.state.title} Link
+ </h4>
+ </div>
+ <div className='modal-body'>
+ <p>
+ Send {strings.Team + 'mates'} the link below for them to sign-up to this {strings.Team} site.
+ <br /><br />
+ Be careful not to share this link publicly, since anyone with the link can join your {strings.Team}.
+ </p>
+ <textarea
+ className='form-control no-resize'
+ readOnly='true'
+ ref='textarea'
+ value={this.state.value}
+ />
+ </div>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >
+ Close
+ </button>
+ <button
+ data-copy-btn='true'
+ type='button'
+ className='btn btn-primary pull-left'
+ onClick={this.handleClick}
+ data-clipboard-text={this.state.value}
+ >
+ Copy Link
+ </button>
+ {copyLinkConfirm}
+ </div>
</div>
- </div>
- </div>
+ </div>
</div>
);
}
return <div/>;
}
-});
+}
diff --git a/web/react/components/loading_screen.jsx b/web/react/components/loading_screen.jsx
index 5905e519b..b0f42ce86 100644
--- a/web/react/components/loading_screen.jsx
+++ b/web/react/components/loading_screen.jsx
@@ -1,24 +1,31 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-module.exports = React.createClass({
- displayName: "LoadingScreen",
- propTypes: {
- position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit'])
- },
- getDefaultProps: function() {
- return { position: 'relative' };
- },
- render: function() {
+export default class LoadingScreen extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ }
+ render() {
return (
- <div className="loading-screen" style={{position: this.props.position}}>
- <div className="loading__content">
+ <div
+ className='loading-screen'
+ style={{position: this.props.position}}
+ >
+ <div className='loading__content'>
<h3>Loading</h3>
- <div className="round round-1"></div>
- <div className="round round-2"></div>
- <div className="round round-3"></div>
+ <div className='round round-1'></div>
+ <div className='round round-2'></div>
+ <div className='round round-3'></div>
</div>
</div>
);
}
-});
+}
+
+LoadingScreen.defaultProps = {
+ position: 'relative'
+};
+LoadingScreen.propTypes = {
+ position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit'])
+};
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index 28dd64c39..f87e77ff7 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -1,11 +1,11 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var BrowserStore = require('../stores/browser_store.jsx');
-var Constants = require('../utils/constants.jsx');
+const Utils = require('../utils/utils.jsx');
+const Client = require('../utils/client.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const BrowserStore = require('../stores/browser_store.jsx');
+const Constants = require('../utils/constants.jsx');
export default class Login extends React.Component {
constructor(props) {
@@ -17,23 +17,23 @@ export default class Login extends React.Component {
}
handleSubmit(e) {
e.preventDefault();
- var state = {};
+ let state = {};
- var name = this.props.teamName;
+ const name = this.props.teamName;
if (!name) {
state.serverError = 'Bad team name';
this.setState(state);
return;
}
- var email = this.refs.email.getDOMNode().value.trim();
+ const email = React.findDOMNode(this.refs.email).value.trim();
if (!email) {
state.serverError = 'An email is required';
this.setState(state);
return;
}
- var password = this.refs.password.getDOMNode().value.trim();
+ const password = React.findDOMNode(this.refs.password).value.trim();
if (!password) {
state.serverError = 'A password is required';
this.setState(state);
@@ -49,12 +49,12 @@ export default class Login extends React.Component {
state.serverError = '';
this.setState(state);
- client.loginByEmail(name, email, password,
+ Client.loginByEmail(name, email, password,
function loggedIn(data) {
UserStore.setCurrentUser(data);
UserStore.setLastEmail(email);
- var redirect = utils.getUrlParameter('redirect');
+ const redirect = Utils.getUrlParameter('redirect');
if (redirect) {
window.location.href = decodeURIComponent(redirect);
} else {
@@ -73,31 +73,31 @@ export default class Login extends React.Component {
);
}
render() {
- var serverError;
+ let serverError;
if (this.state.serverError) {
serverError = <label className='control-label'>{this.state.serverError}</label>;
}
- var priorEmail = UserStore.getLastEmail();
+ let priorEmail = UserStore.getLastEmail();
- var emailParam = utils.getUrlParameter('email');
+ const emailParam = Utils.getUrlParameter('email');
if (emailParam) {
priorEmail = decodeURIComponent(emailParam);
}
- var teamDisplayName = this.props.teamDisplayName;
- var teamName = this.props.teamName;
+ const teamDisplayName = this.props.teamDisplayName;
+ const teamName = this.props.teamName;
- var focusEmail = false;
- var focusPassword = false;
+ let focusEmail = false;
+ let focusPassword = false;
if (priorEmail !== '') {
focusPassword = true;
} else {
focusEmail = true;
}
- var authServices = JSON.parse(this.props.authServices);
+ const authServices = JSON.parse(this.props.authServices);
- var loginMessage = [];
+ let loginMessage = [];
if (authServices.indexOf(Constants.GITLAB_SERVICE) !== -1) {
loginMessage.push(
<a
@@ -110,12 +110,12 @@ export default class Login extends React.Component {
);
}
- var errorClass = '';
+ let errorClass = '';
if (serverError) {
errorClass = ' has-error';
}
- var emailSignup;
+ let emailSignup;
if (authServices.indexOf(Constants.EMAIL_SERVICE) !== -1) {
emailSignup = (
<div>
@@ -163,7 +163,7 @@ export default class Login extends React.Component {
);
}
- var forgotPassword;
+ let forgotPassword;
if (emailSignup) {
forgotPassword = (
<div className='form-group'>
diff --git a/web/react/components/member_list_item.jsx b/web/react/components/member_list_item.jsx
index a5279909b..d244939f5 100644
--- a/web/react/components/member_list_item.jsx
+++ b/web/react/components/member_list_item.jsx
@@ -1,64 +1,116 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
-module.exports = React.createClass({
- displayName: 'MemberListItem',
- handleInvite: function(e) {
+export default class MemberListItem extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleInvite = this.handleInvite.bind(this);
+ this.handleRemove = this.handleRemove.bind(this);
+ this.handleMakeAdmin = this.handleMakeAdmin.bind(this);
+ }
+ handleInvite(e) {
e.preventDefault();
this.props.handleInvite(this.props.member.id);
- },
- handleRemove: function(e) {
+ }
+ handleRemove(e) {
e.preventDefault();
this.props.handleRemove(this.props.member.id);
- },
- handleMakeAdmin: function(e) {
+ }
+ handleMakeAdmin(e) {
e.preventDefault();
this.props.handleMakeAdmin(this.props.member.id);
- },
- render: function() {
-
+ }
+ render() {
var member = this.props.member;
var isAdmin = this.props.isAdmin;
- var isMemberAdmin = member.roles.indexOf("admin") > -1;
+ var isMemberAdmin = member.roles.indexOf('admin') > -1;
var timestamp = UserStore.getCurrentUser().update_at;
var invite;
if (member.invited && this.props.handleInvite) {
- invite = <span className="member-role">Added</span>;
+ invite = <span className='member-role'>Added</span>;
} else if (this.props.handleInvite) {
- invite = <a onClick={this.handleInvite} className="btn btn-sm btn-primary member-invite"><i className="glyphicon glyphicon-envelope"/> Add</a>;
- } else if (isAdmin && !isMemberAdmin && (member.id != UserStore.getCurrentId())) {
+ invite = (
+ <a
+ onClick={this.handleInvite}
+ className='btn btn-sm btn-primary member-invite'>
+ <i className='glyphicon glyphicon-envelope'/> Add
+ </a>
+ );
+ } else if (isAdmin && !isMemberAdmin && (member.id !== UserStore.getCurrentId())) {
var self = this;
+
+ let makeAdminOption = null;
+ if (makeAdminOption) {
+ makeAdminOption = (
+ <li role='presentation'>
+ <a
+ href=''
+ role='menuitem'
+ onClick={self.handleMakeAdmin}>Make Admin
+ </a>
+ </li>);
+ }
+
+ let handleRemoveOption = null;
+ if (handleRemoveOption) {
+ handleRemoveOption = (
+ <li role='presentation'>
+ <a
+ href=''
+ role='menuitem'
+ onClick={self.handleRemove}>Remove Member
+ </a>
+ </li>);
+ }
+
invite = (
- <div className="dropdown member-drop">
- <a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true">
- <span className="text-capitalize">{member.roles || 'Member'} </span>
- <span className="caret"></span>
+ <div className='dropdown member-drop'>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ id='channel_header_dropdown'
+ data-toggle='dropdown'
+ aria-expanded='true' >
+ <span className='text-capitalize'>{member.roles || 'Member'} </span>
+ <span className='caret'></span>
</a>
- <ul className="dropdown-menu member-menu" role="menu" aria-labelledby="channel_header_dropdown">
- { this.props.handleMakeAdmin ?
- <li role="presentation"><a href="" role="menuitem" onClick={self.handleMakeAdmin}>Make Admin</a></li>
- : null }
- { this.props.handleRemove ?
- <li role="presentation"><a href="" role="menuitem" onClick={self.handleRemove}>Remove Member</a></li>
- : null }
+ <ul
+ className='dropdown-menu member-menu'
+ role='menu'
+ aria-labelledby='channel_header_dropdown'>
+ {makeAdminOption}
+ {handleRemoveOption}
</ul>
</div>
);
} else {
- invite = <div className="member-role text-capitalize">{member.roles || 'Member'}<span className="caret hidden"></span></div>;
+ invite = <div className='member-role text-capitalize'>{member.roles || 'Member'}<span className='caret hidden'></span></div>;
}
return (
- <div className="row member-div">
- <img className="post-profile-img pull-left" src={"/api/v1/users/" + member.id + "/image?time=" + timestamp} height="36" width="36" />
- <span className="member-name">{member.username}</span>
- <span className="member-email">{member.email}</span>
- { invite }
+ <div className='row member-div'>
+ <img
+ className='post-profile-img pull-left'
+ src={'/api/v1/users/' + member.id + '/image?time=' + timestamp}
+ height='36'
+ width='36' />
+ <span className='member-name'>{member.username}</span>
+ <span className='member-email'>{member.email}</span>
+ {invite}
</div>
);
}
-});
+}
+
+MemberListItem.propTypes = {
+ handleInvite: React.PropTypes.func,
+ handleRemove: React.PropTypes.func,
+ handleMakeAdmin: React.PropTypes.func,
+ member: React.PropTypes.object,
+ isAdmin: React.PropTypes.bool
+};
diff --git a/web/react/components/member_list_team.jsx b/web/react/components/member_list_team.jsx
index cb48e5cc5..064330c8d 100644
--- a/web/react/components/member_list_team.jsx
+++ b/web/react/components/member_list_team.jsx
@@ -1,122 +1,27 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var ChannelStore = require('../stores/channel_store.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var Client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var utils = require('../utils/utils.jsx');
-
-var MemberListTeamItem = React.createClass({
- handleMakeMember: function() {
- var data = {};
- data["user_id"] = this.props.user.id;
- data["new_roles"] = "";
-
- Client.updateRoles(data,
- function(data) {
- AsyncClient.getProfiles();
- }.bind(this),
- function(err) {
- this.setState({ server_error: err.message });
- }.bind(this)
- );
- },
- handleMakeActive: function() {
- Client.updateActive(this.props.user.id, true,
- function(data) {
- AsyncClient.getProfiles();
- }.bind(this),
- function(err) {
- this.setState({ server_error: err.message });
- }.bind(this)
- );
- },
- handleMakeNotActive: function() {
- Client.updateActive(this.props.user.id, false,
- function(data) {
- AsyncClient.getProfiles();
- }.bind(this),
- function(err) {
- this.setState({ server_error: err.message });
- }.bind(this)
- );
- },
- handleMakeAdmin: function() {
- var data = {};
- data["user_id"] = this.props.user.id;
- data["new_roles"] = "admin";
-
- Client.updateRoles(data,
- function(data) {
- AsyncClient.getProfiles();
- }.bind(this),
- function(err) {
- this.setState({ server_error: err.message });
- }.bind(this)
- );
- },
- getInitialState: function() {
- return {};
- },
- render: function() {
- var server_error = this.state.server_error ? <div className="has-error"><label className='has-error control-label'>{this.state.server_error}</label></div> : null;
- var user = this.props.user;
- var currentRoles = "Member";
- var timestamp = UserStore.getCurrentUser().update_at;
-
- if (user.roles.length > 0) {
- currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1);
- }
-
- var email = user.email.length > 0 ? user.email : "";
- var showMakeMember = user.roles == "admin";
- var showMakeAdmin = user.roles == "";
- var showMakeActive = false;
- var showMakeNotActive = true;
-
- if (user.delete_at > 0) {
- currentRoles = "Inactive";
- showMakeMember = false;
- showMakeAdmin = false;
- showMakeActive = true;
- showMakeNotActive = false;
- }
+const MemberListTeamItem = require('./member_list_team_item.jsx');
+
+export default class MemberListTeam extends React.Component {
+ render() {
+ const memberList = this.props.users.map(function makeListItem(user) {
+ return (
+ <MemberListTeamItem
+ key={user.id}
+ user={user}
+ />
+ );
+ }, this);
return (
- <div className="row member-div">
- <img className="post-profile-img pull-left" src={"/api/v1/users/" + user.id + "/image?time=" + timestamp} height="36" width="36" />
- <span className="member-name">{utils.getDisplayName(user)}</span>
- <span className="member-email">{email}</span>
- <div className="dropdown member-drop">
- <a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true">
- <span>{currentRoles} </span>
- <span className="caret"></span>
- </a>
- <ul className="dropdown-menu member-menu" role="menu" aria-labelledby="channel_header_dropdown">
- { showMakeAdmin ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeAdmin}>Make Admin</a></li> : "" }
- { showMakeMember ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeMember}>Make Member</a></li> : "" }
- { showMakeActive ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeActive}>Make Active</a></li> : "" }
- { showMakeNotActive ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeNotActive}>Make Inactive</a></li> : "" }
- </ul>
- </div>
- { server_error }
+ <div className='member-list-holder'>
+ {memberList}
</div>
);
}
-});
+}
-
-module.exports = React.createClass({
- render: function() {
- return (
- <div className="member-list-holder">
- {
- this.props.users.map(function(user) {
- return <MemberListTeamItem key={user.id} user={user} />;
- }, this)
- }
- </div>
- );
- }
-});
+MemberListTeam.propTypes = {
+ users: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
+};
diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx
new file mode 100644
index 000000000..b7e81f843
--- /dev/null
+++ b/web/react/components/member_list_team_item.jsx
@@ -0,0 +1,203 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const UserStore = require('../stores/user_store.jsx');
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const Utils = require('../utils/utils.jsx');
+
+export default class MemberListTeamItem extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleMakeMember = this.handleMakeMember.bind(this);
+ this.handleMakeActive = this.handleMakeActive.bind(this);
+ this.handleMakeNotActive = this.handleMakeNotActive.bind(this);
+ this.handleMakeAdmin = this.handleMakeAdmin.bind(this);
+
+ this.state = {};
+ }
+ handleMakeMember() {
+ const data = {
+ user_id: this.props.user.id,
+ new_roles: ''
+ };
+
+ Client.updateRoles(data,
+ function handleMakeMemberSuccess() {
+ AsyncClient.getProfiles();
+ },
+ function handleMakeMemberError(err) {
+ this.setState({serverError: err.message});
+ }.bind(this)
+ );
+ }
+ handleMakeActive() {
+ Client.updateActive(this.props.user.id, true,
+ function handleMakeActiveSuccess() {
+ AsyncClient.getProfiles();
+ },
+ function handleMakeActiveError(err) {
+ this.setState({serverError: err.message});
+ }.bind(this)
+ );
+ }
+ handleMakeNotActive() {
+ Client.updateActive(this.props.user.id, false,
+ function handleMakeNotActiveSuccess() {
+ AsyncClient.getProfiles();
+ },
+ function handleMakeNotActiveError(err) {
+ this.setState({serverError: err.message});
+ }.bind(this)
+ );
+ }
+ handleMakeAdmin() {
+ const data = {
+ user_id: this.props.user.id,
+ new_roles: 'admin'
+ };
+
+ Client.updateRoles(data,
+ function handleMakeAdminSuccess() {
+ AsyncClient.getProfiles();
+ },
+ function handleMakeAdmitError(err) {
+ this.setState({serverError: err.message});
+ }.bind(this)
+ );
+ }
+ render() {
+ let serverError = null;
+ if (this.state.serverError) {
+ serverError = (
+ <div className='has-error'>
+ <label className='has-error control-label'>{this.state.serverError}</label>
+ </div>
+ );
+ }
+
+ const user = this.props.user;
+ let currentRoles = 'Member';
+ const timestamp = UserStore.getCurrentUser().update_at;
+
+ if (user.roles.length > 0) {
+ currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1);
+ }
+
+ const email = user.email;
+ let showMakeMember = user.roles === 'admin';
+ let showMakeAdmin = user.roles === '';
+ let showMakeActive = false;
+ let showMakeNotActive = true;
+
+ if (user.delete_at > 0) {
+ currentRoles = 'Inactive';
+ showMakeMember = false;
+ showMakeAdmin = false;
+ showMakeActive = true;
+ showMakeNotActive = false;
+ }
+
+ let makeAdmin = null;
+ if (showMakeAdmin) {
+ makeAdmin = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleMakeAdmin}
+ >
+ Make Admin
+ </a>
+ </li>
+ );
+ }
+
+ let makeMember = null;
+ if (showMakeMember) {
+ makeMember = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleMakeMember}
+ >
+ Make Member
+ </a>
+ </li>
+ );
+ }
+
+ let makeActive = null;
+ if (showMakeActive) {
+ makeActive = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleMakeActive}
+ >
+ Make Active
+ </a>
+ </li>
+ );
+ }
+
+ let makeNotActive = null;
+ if (showMakeNotActive) {
+ makeNotActive = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleMakeNotActive}
+ >
+ Make Inactive
+ </a>
+ </li>
+ );
+ }
+
+ return (
+ <div className='row member-div'>
+ <img
+ className='post-profile-img pull-left'
+ src={`/api/v1/users/${user.id}/image?time=${timestamp}`}
+ height='36'
+ width='36'
+ />
+ <span className='member-name'>{Utils.getDisplayName(user)}</span>
+ <span className='member-email'>{email}</span>
+ <div className='dropdown member-drop'>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ id='channel_header_dropdown'
+ data-toggle='dropdown'
+ aria-expanded='true'
+ >
+ <span>{currentRoles} </span>
+ <span className='caret'></span>
+ </a>
+ <ul
+ className='dropdown-menu member-menu'
+ role='menu'
+ aria-labelledby='channel_header_dropdown'
+ >
+ {makeAdmin}
+ {makeMember}
+ {makeActive}
+ {makeNotActive}
+ </ul>
+ </div>
+ {serverError}
+ </div>
+ );
+ }
+}
+
+MemberListTeamItem.propTypes = {
+ user: React.PropTypes.object.isRequired
+};
diff --git a/web/react/components/mention.jsx b/web/react/components/mention.jsx
index 114dc183f..72a2a6251 100644
--- a/web/react/components/mention.jsx
+++ b/web/react/components/mention.jsx
@@ -1,30 +1,57 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserStore = require("../stores/user_store.jsx");
+var UserStore = require('../stores/user_store.jsx');
-module.exports = React.createClass({
- handleClick: function() {
+export default class Mention extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleClick = this.handleClick.bind(this);
+
+ this.state = null;
+ }
+ handleClick() {
this.props.handleClick(this.props.username);
- },
- getInitialState: function() {
- return null;
- },
- render: function() {
- var self = this;
+ }
+ render() {
var icon;
var timestamp = UserStore.getCurrentUser().update_at;
- if (this.props.id === "allmention" || this.props.id === "channelmention") {
- icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>;
+ if (this.props.id === 'allmention' || this.props.id === 'channelmention') {
+ icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>;
} else if (this.props.id != null) {
- icon = <span><img className="mention-img" src={"/api/v1/users/" + this.props.id + "/image?time=" + timestamp}/></span>;
+ icon = (<span><img
+ className='mention-img'
+ src={'/api/v1/users/' + this.props.id + '/image?time=' + timestamp}
+ />
+ </span>);
} else {
- icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>;
+ icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>;
}
return (
- <div className={"mentions-name " + this.props.isFocused} id={this.props.id + "_mentions"} onClick={this.handleClick} onMouseEnter={this.props.handleMouseEnter}>
- <div className="pull-left">{icon}</div>
- <div className="pull-left mention-align"><span>@{this.props.username}</span><span className="mention-fullname">{this.props.secondary_text}</span></div>
+ <div
+ className={'mentions-name ' + this.props.isFocused}
+ id={this.props.id + '_mentions'}
+ onClick={this.handleClick}
+ onMouseEnter={this.props.handleMouseEnter}
+ >
+ <div className='pull-left'>{icon}</div>
+ <div className='pull-left mention-align'><span>@{this.props.username}</span><span className='mention-fullname'>{this.props.secondary_text}</span></div>
</div>
);
}
-});
+}
+
+Mention.defaultProps = {
+ username: '',
+ id: '',
+ isFocused: '',
+ secondary_text: ''
+};
+Mention.propTypes = {
+ handleClick: React.PropTypes.func.isRequired,
+ handleMouseEnter: React.PropTypes.func.isRequired,
+ username: React.PropTypes.string,
+ id: React.PropTypes.string,
+ isFocused: React.PropTypes.string,
+ secondary_text: React.PropTypes.string
+};
diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx
index f562cfb29..afea30161 100644
--- a/web/react/components/mention_list.jsx
+++ b/web/react/components/mention_list.jsx
@@ -14,54 +14,66 @@ var MAX_HEIGHT_LIST = 292;
var MAX_ITEMS_IN_LIST = 25;
var ITEM_HEIGHT = 36;
-module.exports = React.createClass({
- displayName: 'MentionList',
- componentDidMount: function() {
+export default class MentionList extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onListenerChange = this.onListenerChange.bind(this);
+ this.handleClick = this.handleClick.bind(this);
+ this.handleMouseEnter = this.handleMouseEnter.bind(this);
+ this.getSelection = this.getSelection.bind(this);
+ this.addCurrentMention = this.addCurrentMention.bind(this);
+ this.addFirstMention = this.addFirstMention.bind(this);
+ this.isEmpty = this.isEmpty.bind(this);
+ this.scrollToMention = this.scrollToMention.bind(this);
+
+ this.state = {excludeUsers: [], mentionText: '-1', selectedMention: 0, selectedUsername: ''};
+ }
+ componentDidMount() {
PostStore.addMentionDataChangeListener(this.onListenerChange);
- var self = this;
- $('.post-right__scroll').scroll(function(){
- if($('.mentions--top').length){
- $('#reply_mention_tab .mentions--top').css({ bottom: $(window).height() - $('.post-right__scroll #reply_textbox').offset().top });
+ $('.post-right__scroll').scroll(function onScroll() {
+ if ($('.mentions--top').length) {
+ $('#reply_mention_tab .mentions--top').css({bottom: $(window).height() - $('.post-right__scroll #reply_textbox').offset().top});
}
});
$('body').on('keydown.mentionlist', '#' + this.props.id,
- function(e) {
- if (!self.isEmpty() && self.state.mentionText !== '-1' && (e.which === 13 || e.which === 9)) {
+ function onMentionListKey(e) {
+ if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 13 || e.which === 9)) {
e.stopPropagation();
e.preventDefault();
- self.addCurrentMention();
- } else if (!self.isEmpty() && self.state.mentionText !== '-1' && (e.which === 38 || e.which === 40)) {
+ this.addCurrentMention();
+ } else if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 38 || e.which === 40)) {
e.stopPropagation();
e.preventDefault();
if (e.which === 38) {
- if (self.getSelection(self.state.selectedMention - 1)) {
- self.setState({selectedMention: self.state.selectedMention - 1, selectedUsername: self.refs['mention' + (self.state.selectedMention - 1)].props.username});
+ if (this.getSelection(this.state.selectedMention - 1)) {
+ this.setState({selectedMention: this.state.selectedMention - 1, selectedUsername: this.refs['mention' + (this.state.selectedMention - 1)].props.username});
}
} else if (e.which === 40) {
- if (self.getSelection(self.state.selectedMention + 1)) {
- self.setState({selectedMention: self.state.selectedMention + 1, selectedUsername: self.refs['mention' + (self.state.selectedMention + 1)].props.username});
+ if (this.getSelection(this.state.selectedMention + 1)) {
+ this.setState({selectedMention: this.state.selectedMention + 1, selectedUsername: this.refs['mention' + (this.state.selectedMention + 1)].props.username});
}
}
- self.scrollToMention(e.which);
+ this.scrollToMention(e.which);
}
- }
+ }.bind(this)
);
- $(document).click(function(e) {
- if (!($('#' + self.props.id).is(e.target) || $('#' + self.props.id).has(e.target).length ||
- ('mentionlist' in self.refs && $(self.refs.mentionlist.getDOMNode()).has(e.target).length))) {
- self.setState({mentionText: '-1'});
+ $(document).click(function onClick(e) {
+ if (!($('#' + this.props.id).is(e.target) || $('#' + this.props.id).has(e.target).length ||
+ ('mentionlist' in this.refs && $(React.findDOMNode(this.refs.mentionlist)).has(e.target).length))) {
+ this.setState({mentionText: '-1'});
}
- });
- },
- componentWillUnmount: function() {
+ }.bind(this));
+ }
+ componentWillUnmount() {
PostStore.removeMentionDataChangeListener(this.onListenerChange);
$('body').off('keydown.mentionlist', '#' + this.props.id);
- },
- componentDidUpdate: function() {
+ }
+ componentDidUpdate() {
if (this.state.mentionText !== '-1') {
if (this.state.selectedUsername !== '' && (!this.getSelection(this.state.selectedMention) || this.state.selectedUsername !== this.refs['mention' + this.state.selectedMention].props.username)) {
var tempSelectedMention = -1;
@@ -80,8 +92,8 @@ module.exports = React.createClass({
} else if (this.state.selectedMention !== 0) {
this.setState({selectedMention: 0, selectedUsername: ''});
}
- },
- onListenerChange: function(id, mentionText) {
+ }
+ onListenerChange(id, mentionText) {
if (id !== this.props.id) {
return;
}
@@ -92,8 +104,8 @@ module.exports = React.createClass({
}
this.setState(newState);
- },
- handleClick: function(name) {
+ }
+ handleClick(name) {
AppDispatcher.handleViewAction({
type: ActionTypes.RECIEVED_ADD_MENTION,
id: this.props.id,
@@ -101,33 +113,33 @@ module.exports = React.createClass({
});
this.setState({mentionText: '-1'});
- },
- handleMouseEnter: function(listId) {
+ }
+ handleMouseEnter(listId) {
this.setState({selectedMention: listId, selectedUsername: this.refs['mention' + listId].props.username});
- },
- getSelection: function(listId) {
+ }
+ getSelection(listId) {
if (!this.refs['mention' + listId]) {
return false;
}
return true;
- },
- addCurrentMention: function() {
+ }
+ addCurrentMention() {
if (!this.getSelection(this.state.selectedMention)) {
this.addFirstMention();
} else {
this.refs['mention' + this.state.selectedMention].handleClick();
}
- },
- addFirstMention: function() {
+ }
+ addFirstMention() {
if (!this.refs.mention0) {
return;
}
this.refs.mention0.handleClick();
- },
- isEmpty: function() {
+ }
+ isEmpty() {
return (!this.refs.mention0);
- },
- scrollToMention: function(keyPressed) {
+ }
+ scrollToMention(keyPressed) {
var direction;
if (keyPressed === 38) {
direction = 'up';
@@ -145,12 +157,8 @@ module.exports = React.createClass({
$('#mentionsbox').animate({
scrollTop: scrollAmount
}, 75);
- },
- getInitialState: function() {
- return {excludeUsers: [], mentionText: '-1', selectedMention: 0, selectedUsername: ''};
- },
- render: function() {
- var self = this;
+ }
+ render() {
var mentionText = this.state.mentionText;
if (mentionText === '-1') {
return null;
@@ -158,8 +166,10 @@ module.exports = React.createClass({
var profiles = UserStore.getActiveOnlyProfiles();
var users = [];
- for (var id in profiles) {
- users.push(profiles[id]);
+ for (let id in profiles) {
+ if (profiles[id]) {
+ users.push(profiles[id]);
+ }
}
var all = {};
@@ -176,7 +186,7 @@ module.exports = React.createClass({
channel.id = 'channelmention';
users.push(channel);
- users.sort(function(a, b) {
+ users.sort(function sortByUsername(a, b) {
if (a.username < b.username) {
return -1;
}
@@ -185,29 +195,34 @@ module.exports = React.createClass({
}
return 0;
});
- var mentions = {};
+ var mentions = [];
var index = 0;
for (var i = 0; i < users.length && index < MAX_ITEMS_IN_LIST; i++) {
if ((users[i].first_name && users[i].first_name.lastIndexOf(mentionText, 0) === 0) ||
(users[i].last_name && users[i].last_name.lastIndexOf(mentionText, 0) === 0) ||
users[i].username.lastIndexOf(mentionText, 0) === 0) {
+ let isFocused = '';
+ if (this.state.selectedMention === index) {
+ isFocused = 'mentions-focus';
+ }
mentions[index] = (
<Mention
+ key={'mention_key_' + index}
ref={'mention' + index}
username={users[i].username}
secondary_text={Utils.getFullName(users[i])}
id={users[i].id}
listId={index}
- isFocused={this.state.selectedMention === index ? 'mentions-focus' : ''}
- handleMouseEnter={function(value) { return function() { self.handleMouseEnter(value); } }(index)}
+ isFocused={isFocused}
+ handleMouseEnter={this.handleMouseEnter.bind(this, index)}
handleClick={this.handleClick} />
);
index++;
}
}
- var numMentions = Object.keys(mentions).length;
+ var numMentions = mentions.length;
if (numMentions < 1) {
return null;
@@ -223,11 +238,20 @@ module.exports = React.createClass({
};
return (
- <div className='mentions--top' style={style}>
- <div ref='mentionlist' className='mentions-box' id='mentionsbox'>
+ <div
+ className='mentions--top'
+ style={style}>
+ <div
+ ref='mentionlist'
+ className='mentions-box'
+ id='mentionsbox'>
{mentions}
</div>
</div>
);
}
-});
+}
+
+MentionList.propTypes = {
+ id: React.PropTypes.string
+};
diff --git a/web/react/components/message_wrapper.jsx b/web/react/components/message_wrapper.jsx
index 5fc88a61b..bce305853 100644
--- a/web/react/components/message_wrapper.jsx
+++ b/web/react/components/message_wrapper.jsx
@@ -1,17 +1,30 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
-module.exports = React.createClass({
- render: function() {
+export default class MessageWrapper extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ }
+ render() {
if (this.props.message) {
- var inner = utils.textToJsx(this.props.message, this.props.options);
+ var inner = Utils.textToJsx(this.props.message, this.props.options);
return (
<div>{inner}</div>
);
- } else {
- return <div/>
}
+
+ return <div/>;
}
-});
+}
+
+MessageWrapper.defaultProps = {
+ message: null,
+ options: null
+};
+MessageWrapper.propTypes = {
+ message: React.PropTypes.string,
+ options: React.PropTypes.object
+};
diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx
index 06c373e5d..d6cf4f9d6 100644
--- a/web/react/components/navbar.jsx
+++ b/web/react/components/navbar.jsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var client = require('../utils/client.jsx');
+var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var UserStore = require('../stores/user_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
@@ -13,22 +13,27 @@ var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-function getStateFromStores() {
- return {
- channel: ChannelStore.getCurrent(),
- member: ChannelStore.getCurrentMember(),
- users: ChannelStore.getCurrentExtraInfo().members
- };
-}
+export default class Navbar extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onChange = this.onChange.bind(this);
+ this.handleLeave = this.handleLeave.bind(this);
+ this.createCollapseButtons = this.createCollapseButtons.bind(this);
+ this.createDropdown = this.createDropdown.bind(this);
-module.exports = React.createClass({
- displayName: 'Navbar',
- propTypes: {
- teamDisplayName: React.PropTypes.string
- },
- componentDidMount: function() {
- ChannelStore.addChangeListener(this.onListenerChange);
- ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
+ this.state = this.getStateFromStores();
+ }
+ getStateFromStores() {
+ return {
+ channel: ChannelStore.getCurrent(),
+ member: ChannelStore.getCurrentMember(),
+ users: ChannelStore.getCurrentExtraInfo().members
+ };
+ }
+ componentDidMount() {
+ ChannelStore.addChangeListener(this.onChange);
+ ChannelStore.addExtraInfoChangeListener(this.onChange);
$('.inner__wrap').click(this.hideSidebars);
$('body').on('click.infopopover', function handlePopoverClick(e) {
@@ -36,15 +41,15 @@ module.exports = React.createClass({
$('.info-popover').popover('hide');
}
});
- },
- componentWillUnmount: function() {
- ChannelStore.removeChangeListener(this.onListenerChange);
- },
- handleSubmit: function(e) {
+ }
+ componentWillUnmount() {
+ ChannelStore.removeChangeListener(this.onChange);
+ }
+ handleSubmit(e) {
e.preventDefault();
- },
- handleLeave: function() {
- client.leaveChannel(this.state.channel.id,
+ }
+ handleLeave() {
+ Client.leaveChannel(this.state.channel.id,
function success() {
AsyncClient.getChannels(true);
window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square';
@@ -53,8 +58,8 @@ module.exports = React.createClass({
AsyncClient.dispatchError(err, 'handleLeave');
}
);
- },
- hideSidebars: function(e) {
+ }
+ hideSidebars(e) {
var windowWidth = $(window).outerWidth();
if (windowWidth <= 768) {
AppDispatcher.handleServerAction({
@@ -74,32 +79,259 @@ module.exports = React.createClass({
$('.sidebar--menu').removeClass('move--left');
}
}
- },
- toggleLeftSidebar: function() {
+ }
+ toggleLeftSidebar() {
$('.inner__wrap').toggleClass('move--right');
$('.sidebar--left').toggleClass('move--right');
- },
- toggleRightSidebar: function() {
+ }
+ toggleRightSidebar() {
$('.inner__wrap').toggleClass('move--left-small');
$('.sidebar--menu').toggleClass('move--left');
- },
- onListenerChange: function() {
- this.setState(getStateFromStores());
+ }
+ onChange() {
+ this.setState(this.getStateFromStores());
$('#navbar .navbar-brand .description').popover({placement: 'bottom', trigger: 'click', html: true});
- },
- getInitialState: function() {
- return getStateFromStores();
- },
- render: function() {
+ }
+ createDropdown(channel, channelTitle, isAdmin, isDirect, popoverContent) {
+ if (channel) {
+ var viewInfoOption = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ data-toggle='modal'
+ data-target='#channel_info'
+ data-channelid={channel.id}
+ href='#'
+ >
+ View Info
+ </a>
+ </li>
+ );
+
+ var setChannelDescriptionOption = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#edit_channel'
+ data-desc={channel.description}
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ >
+ Set Channel Description...
+ </a>
+ </li>
+ );
+
+ var addMembersOption;
+ var leaveChannelOption;
+ if (!isDirect && !ChannelStore.isDefault(channel)) {
+ addMembersOption = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ data-toggle='modal'
+ data-target='#channel_invite'
+ href='#'
+ >
+ Add Members
+ </a>
+ </li>
+ );
+
+ leaveChannelOption = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleLeave}
+ >
+ Leave Channel
+ </a>
+ </li>
+ );
+ }
+
+ var manageMembersOption;
+ var renameChannelOption;
+ var deleteChannelOption;
+ if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) {
+ manageMembersOption = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ data-toggle='modal'
+ data-target='#channel_members'
+ href='#'
+ >
+ Manage Members
+ </a>
+ </li>
+ );
+
+ renameChannelOption = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#rename_channel'
+ data-display={channel.display_name}
+ data-name={channel.name}
+ data-channelid={channel.id}
+ >
+ Rename Channel...
+ </a>
+ </li>
+ );
+
+ deleteChannelOption = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#delete_channel'
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ >
+ Delete Channel...
+ </a>
+ </li>
+ );
+ }
+
+ var notificationPreferenceOption;
+ if (!isDirect) {
+ notificationPreferenceOption = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#channel_notifications'
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ >
+ Notification Preferences
+ </a>
+ </li>
+ );
+ }
+
+ return (
+ <div className='navbar-brand'>
+ <div className='dropdown'>
+ <div
+ data-toggle='popover'
+ data-content={popoverContent}
+ className='description info-popover'
+ />
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ id='channel_header_dropdown'
+ data-toggle='dropdown'
+ aria-expanded='true'
+ >
+ <span className='heading'>{channelTitle} </span>
+ <span className='glyphicon glyphicon-chevron-down header-dropdown__icon'></span>
+ </a>
+ <ul
+ className='dropdown-menu'
+ role='menu'
+ aria-labelledby='channel_header_dropdown'
+ >
+ {viewInfoOption}
+ {addMembersOption}
+ {manageMembersOption}
+ {setChannelDescriptionOption}
+ {notificationPreferenceOption}
+ {renameChannelOption}
+ {deleteChannelOption}
+ {leaveChannelOption}
+ </ul>
+ </div>
+ </div>
+ );
+ }
+
+ return (
+ <div className='navbar-brand'>
+ <a
+ href='/'
+ className='heading'
+ >
+ {channelTitle}
+ </a>
+ </div>
+ );
+ }
+ createCollapseButtons(currentId) {
+ var buttons = [];
+ if (currentId == null) {
+ buttons.push(
+ <button
+ type='button'
+ className='navbar-toggle'
+ data-toggle='collapse'
+ data-target='#navbar-collapse-1'
+ >
+ <span className='sr-only'>Toggle sidebar</span>
+ <span className='icon-bar'></span>
+ <span className='icon-bar'></span>
+ <span className='icon-bar'></span>
+ </button>
+ );
+ } else {
+ buttons.push(
+ <button
+ type='button'
+ className='navbar-toggle'
+ data-toggle='collapse'
+ data-target='#sidebar-nav'
+ onClick={this.toggleLeftSidebar}
+ >
+ <span className='sr-only'>Toggle sidebar</span>
+ <span className='icon-bar'></span>
+ <span className='icon-bar'></span>
+ <span className='icon-bar'></span>
+ <NotifyCounts />
+ </button>
+ );
+
+ buttons.push(
+ <button
+ type='button'
+ className='navbar-toggle menu-toggle pull-right'
+ data-toggle='collapse'
+ data-target='#sidebar-nav'
+ onClick={this.toggleRightSidebar}
+ >
+ <span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
+ </button>
+ );
+ }
+
+ return buttons;
+ }
+ render() {
var currentId = UserStore.getCurrentId();
- var popoverContent = '';
+ var channel = this.state.channel;
var channelTitle = this.props.teamDisplayName;
+ var popoverContent;
var isAdmin = false;
var isDirect = false;
- var channel = this.state.channel;
if (channel) {
- popoverContent = React.renderToString(<MessageWrapper message={channel.description} options={{singleline: true, noMentionHighlight: true}}/>);
+ popoverContent = React.renderToString(
+ <MessageWrapper
+ message={channel.description}
+ options={{singleline: true, noMentionHighlight: true}}
+ />
+ );
isAdmin = this.state.member.roles.indexOf('admin') > -1;
if (channel.type === 'O') {
@@ -118,110 +350,46 @@ module.exports = React.createClass({
}
if (channel.description.length === 0) {
- popoverContent = React.renderToString(<div>No channel description yet. <br /><a href='#' data-toggle='modal' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id} data-target='#edit_channel'>Click here</a> to add one.</div>);
+ popoverContent = React.renderToString(
+ <div>
+ No channel description yet. <br/>
+ <a
+ href='#'
+ data-toggle='modal'
+ data-desc={channel.description}
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ data-target='#edit_channel'
+ >
+ Click here
+ </a> to add one.</div>
+ );
}
}
- var navbarCollapseButton = null;
- if (currentId == null) {
- navbarCollapseButton = (<button type='button' className='navbar-toggle' data-toggle='collapse' data-target='#navbar-collapse-1'>
- <span className='sr-only'>Toggle sidebar</span>
- <span className='icon-bar'></span>
- <span className='icon-bar'></span>
- <span className='icon-bar'></span>
- </button>);
- }
-
- var sidebarCollapseButton = null;
- if (currentId != null) {
- sidebarCollapseButton = (<button type='button' className='navbar-toggle' data-toggle='collapse' data-target='#sidebar-nav' onClick={this.toggleLeftSidebar}>
- <span className='sr-only'>Toggle sidebar</span>
- <span className='icon-bar'></span>
- <span className='icon-bar'></span>
- <span className='icon-bar'></span>
- <NotifyCounts />
- </button>);
- }
-
- var rightSidebarCollapseButton = null;
- if (currentId != null) {
- rightSidebarCollapseButton = (<button type='button' className='navbar-toggle menu-toggle pull-right' data-toggle='collapse' data-target='#sidebar-nav' onClick={this.toggleRightSidebar}>
- <span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
- </button>);
- }
-
- var channelMenuDropdown = null;
- if (channel) {
- var viewInfoOption = <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_info' data-channelid={channel.id} href='#'>View Info</a></li>
-
- var addMembersOption = null;
- if (!isDirect && !ChannelStore.isDefault(channel)) {
- addMembersOption = <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_invite' href='#'>Add Members</a></li>;
- }
-
- var manageMembersOption = null;
- if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) {
- manageMembersOption = <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_members' href='#'>Manage Members</a></li>;
- }
-
- var setChannelDescriptionOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li>;
-
- var notificationPreferenceOption = null;
- if (!isDirect) {
- notificationPreferenceOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#channel_notifications' data-title={channel.display_name} data-channelid={channel.id}>Notification Preferences</a></li>;
- }
-
- var renameChannelOption = null;
- if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) {
- renameChannelOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#rename_channel' data-display={channel.display_name} data-name={channel.name} data-channelid={channel.id}>Rename Channel...</a></li>;
- }
-
- var deleteChannelOption = null;
- if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) {
- deleteChannelOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#delete_channel' data-title={channel.display_name} data-channelid={channel.id}>Delete Channel...</a></li>;
- }
+ var collapseButtons = this.createCollapseButtons(currentId);
- var leaveChannelOption = null;
- if (!isDirect && !ChannelStore.isDefault(channel)) {
- leaveChannelOption = <li role='presentation'><a role='menuitem' href='#' onClick={this.handleLeave}>Leave Channel</a></li>;
- }
-
- channelMenuDropdown = (<div className='navbar-brand'>
- <div className='dropdown'>
- <div data-toggle='popover' data-content={popoverContent} className='description info-popover'></div>
- <a href='#' className='dropdown-toggle theme' type='button' id='channel_header_dropdown' data-toggle='dropdown' aria-expanded='true'>
- <span className='heading'>{channelTitle} </span>
- <span className='glyphicon glyphicon-chevron-down header-dropdown__icon'></span>
- </a>
- <ul className='dropdown-menu' role='menu' aria-labelledby='channel_header_dropdown'>
- {viewInfoOption}
- {addMembersOption}
- {manageMembersOption}
- {setChannelDescriptionOption}
- {notificationPreferenceOption}
- {renameChannelOption}
- {deleteChannelOption}
- {leaveChannelOption}
- </ul>
- </div>
- </div>);
- } else {
- channelMenuDropdown = (<div className='navbar-brand'>
- <a href='/' className='heading'>{channelTitle}</a>
- </div>);
- }
+ var channelMenuDropdown = this.createDropdown(channel, channelTitle, isAdmin, isDirect, popoverContent);
return (
- <nav className='navbar navbar-default navbar-fixed-top' role='navigation'>
+ <nav
+ className='navbar navbar-default navbar-fixed-top'
+ role='navigation'
+ >
<div className='container-fluid theme'>
<div className='navbar-header'>
- {navbarCollapseButton}
- {sidebarCollapseButton}
- {rightSidebarCollapseButton}
+ {collapseButtons}
{channelMenuDropdown}
</div>
</div>
</nav>
);
}
-});
+}
+
+Navbar.defaultProps = {
+ teamDisplayName: ''
+};
+Navbar.propTypes = {
+ teamDisplayName: React.PropTypes.string
+};
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
new file mode 100644
index 000000000..e818a5c92
--- /dev/null
+++ b/web/react/components/navbar_dropdown.jsx
@@ -0,0 +1,209 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var Utils = require('../utils/utils.jsx');
+var client = require('../utils/client.jsx');
+var UserStore = require('../stores/user_store.jsx');
+var TeamStore = require('../stores/team_store.jsx');
+
+var Constants = require('../utils/constants.jsx');
+
+function getStateFromStores() {
+ return {teams: UserStore.getTeams(), currentTeam: TeamStore.getCurrent()};
+}
+
+export default class NavbarDropdown extends React.Component {
+ constructor(props) {
+ super(props);
+ this.blockToggle = false;
+
+ this.handleLogoutClick = this.handleLogoutClick.bind(this);
+ this.onListenerChange = this.onListenerChange.bind(this);
+
+ this.state = getStateFromStores();
+ }
+ handleLogoutClick(e) {
+ e.preventDefault();
+ client.logout();
+ }
+ componentDidMount() {
+ UserStore.addTeamsChangeListener(this.onListenerChange);
+ TeamStore.addChangeListener(this.onListenerChange);
+
+ $(React.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', function resetDropdown() {
+ this.blockToggle = true;
+ setTimeout(function blockTimeout() {
+ this.blockToggle = false;
+ }.bind(this), 100);
+ }.bind(this));
+ }
+ componentWillUnmount() {
+ UserStore.removeTeamsChangeListener(this.onListenerChange);
+ TeamStore.removeChangeListener(this.onListenerChange);
+
+ $(React.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown');
+ }
+ onListenerChange() {
+ var newState = getStateFromStores();
+ if (!Utils.areStatesEqual(newState, this.state)) {
+ this.setState(newState);
+ }
+ }
+ render() {
+ var teamLink = '';
+ var inviteLink = '';
+ var manageLink = '';
+ var currentUser = UserStore.getCurrentUser();
+ var isAdmin = false;
+ var teamSettings = null;
+
+ if (currentUser != null) {
+ isAdmin = currentUser.roles.indexOf('admin') > -1;
+
+ inviteLink = (<li>
+ <a
+ href='#'
+ data-toggle='modal'
+ data-target='#invite_member'
+ >
+ Invite New Member
+ </a>
+ </li>);
+
+ if (this.props.teamType === 'O') {
+ teamLink = (
+ <li>
+ <a
+ href='#'
+ data-toggle='modal'
+ data-target='#get_link'
+ data-title='Team Invite'
+ data-value={Utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id}
+ >
+ Get Team Invite Link
+ </a>
+ </li>
+ );
+ }
+ }
+
+ if (isAdmin) {
+ manageLink = (<li>
+ <a
+ href='#'
+ data-toggle='modal'
+ data-target='#team_members'
+ >
+ Manage Team
+ </a>
+ </li>);
+ teamSettings = (<li>
+ <a
+ href='#'
+ data-toggle='modal'
+ data-target='#team_settings'
+ >
+ Team Settings
+ </a>
+ </li>);
+ }
+
+ var teams = [];
+
+ teams.push(<li
+ className='divider'
+ key='div'
+ >
+ </li>);
+ if (this.state.teams.length > 1 && this.state.currentTeam) {
+ var curTeamName = this.state.currentTeam.name;
+ this.state.teams.forEach(function listTeams(teamName) {
+ if (teamName !== curTeamName) {
+ teams.push(<li key={teamName}><a href={Utils.getWindowLocationOrigin() + '/' + teamName}>Switch to {teamName}</a></li>);
+ }
+ });
+ }
+ teams.push(<li key='newTeam_li'>
+ <a
+ key='newTeam_a'
+ target='_blank'
+ href={Utils.getWindowLocationOrigin() + '/signup_team'}
+ >
+ Create a New Team
+ </a>
+ </li>);
+
+ return (
+ <ul className='nav navbar-nav navbar-right'>
+ <li
+ ref='dropdown'
+ className='dropdown'
+ >
+ <a
+ href='#'
+ className='dropdown-toggle'
+ data-toggle='dropdown'
+ role='button'
+ aria-expanded='false'
+ >
+ <span
+ className='dropdown__icon'
+ dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}}
+ />
+ </a>
+ <ul
+ className='dropdown-menu'
+ role='menu'
+ >
+ <li>
+ <a
+ href='#'
+ data-toggle='modal'
+ data-target='#user_settings'
+ >
+ Account Settings
+ </a>
+ </li>
+ {teamSettings}
+ {inviteLink}
+ {teamLink}
+ {manageLink}
+ <li>
+ <a
+ href='#'
+ onClick={this.handleLogoutClick}
+ >
+ Logout
+ </a>
+ </li>
+ {teams}
+ <li className='divider'></li>
+ <li>
+ <a
+ target='_blank'
+ href={config.HelpLink}
+ >
+ Help
+ </a>
+ </li>
+ <li>
+ <a
+ target='_blank'
+ href={config.ReportProblemLink}
+ >
+ Report a Problem
+ </a>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ );
+ }
+}
+
+NavbarDropdown.defaultProps = {
+ teamType: ''
+};
+NavbarDropdown.propTypes = {
+ teamType: React.PropTypes.string
+};
diff --git a/web/react/components/new_channel.jsx b/web/react/components/new_channel.jsx
index fc24a7cdc..a02a4c1c0 100644
--- a/web/react/components/new_channel.jsx
+++ b/web/react/components/new_channel.jsx
@@ -5,17 +5,24 @@ var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
var asyncClient = require('../utils/async_client.jsx');
var UserStore = require('../stores/user_store.jsx');
-var TeamStore = require('../stores/team_store.jsx');
-module.exports = React.createClass({
- displayName: 'NewChannelModal',
- handleSubmit: function(e) {
+export default class NewChannelModal extends React.Component {
+ constructor() {
+ super();
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.displayNameKeyUp = this.displayNameKeyUp.bind(this);
+ this.handleClose = this.handleClose.bind(this);
+
+ this.state = {channelType: ''};
+ }
+ handleSubmit(e) {
e.preventDefault();
var channel = {};
var state = {serverError: ''};
- channel.display_name = this.refs.display_name.getDOMNode().value.trim();
+ channel.display_name = React.findDOMNode(this.refs.display_name).value.trim();
if (!channel.display_name) {
state.displayNameError = 'This field is required';
state.inValid = true;
@@ -26,7 +33,7 @@ module.exports = React.createClass({
state.displayNameError = '';
}
- channel.name = this.refs.channel_name.getDOMNode().value.trim();
+ channel.name = React.findDOMNode(this.refs.channel_name).value.trim();
if (!channel.name) {
state.nameError = 'This field is required';
state.inValid = true;
@@ -52,54 +59,51 @@ module.exports = React.createClass({
var cu = UserStore.getCurrentUser();
channel.team_id = cu.team_id;
- channel.description = this.refs.channel_desc.getDOMNode().value.trim();
+ channel.description = React.findDOMNode(this.refs.channel_desc).value.trim();
channel.type = this.state.channelType;
client.createChannel(channel,
- function(data) {
- $(this.refs.modal.getDOMNode()).modal('hide');
+ function success(data) {
+ $(React.findDOMNode(this.refs.modal)).modal('hide');
asyncClient.getChannel(data.id);
utils.switchChannel(data);
- this.refs.display_name.getDOMNode().value = '';
- this.refs.channel_name.getDOMNode().value = '';
- this.refs.channel_desc.getDOMNode().value = '';
+ React.findDOMNode(this.refs.display_name).value = '';
+ React.findDOMNode(this.refs.channel_name).value = '';
+ React.findDOMNode(this.refs.channel_desc).value = '';
}.bind(this),
- function(err) {
+ function error(err) {
state.serverError = err.message;
state.inValid = true;
this.setState(state);
}.bind(this)
);
- },
- displayNameKeyUp: function() {
- var displayName = this.refs.display_name.getDOMNode().value.trim();
+ }
+ displayNameKeyUp() {
+ var displayName = React.findDOMNode(this.refs.display_name).value.trim();
var channelName = utils.cleanUpUrlable(displayName);
- this.refs.channel_name.getDOMNode().value = channelName;
- },
- componentDidMount: function() {
+ React.findDOMNode(this.refs.channel_name).value = channelName;
+ }
+ componentDidMount() {
var self = this;
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function onModalShow(e) {
var button = e.relatedTarget;
self.setState({channelType: $(button).attr('data-channeltype')});
});
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', this.handleClose);
- },
- componentWillUnmount: function() {
- $(this.refs.modal.getDOMNode()).off('hidden.bs.modal', this.handleClose);
- },
- handleClose: function() {
- $(this.getDOMNode()).find('.form-control').each(function clearForms() {
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
+ }
+ componentWillUnmount() {
+ $(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose);
+ }
+ handleClose() {
+ $(React.findDOMNode(this)).find('.form-control').each(function clearForms() {
this.value = '';
});
this.setState({channelType: '', displayNameError: '', nameError: '', serverError: '', inValid: false});
- },
- getInitialState: function() {
- return {channelType: ''};
- },
- render: function() {
+ }
+ render() {
var displayNameError = null;
var nameError = null;
var serverError = null;
@@ -124,11 +128,20 @@ module.exports = React.createClass({
}
return (
- <div className='modal fade' id='new_channel' ref='modal' tabIndex='-1' role='dialog' aria-hidden='true'>
+ <div
+ className='modal fade'
+ id='new_channel'
+ ref='modal'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'>
<div className='modal-dialog'>
<div className='modal-content'>
<div className='modal-header'>
- <button type='button' className='close' data-dismiss='modal'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'>
<span aria-hidden='true'>&times;</span>
<span className='sr-only'>Cancel</span>
</button>
@@ -138,23 +151,49 @@ module.exports = React.createClass({
<div className='modal-body'>
<div className={displayNameClass}>
<label className='control-label'>Display Name</label>
- <input onKeyUp={this.displayNameKeyUp} type='text' ref='display_name' className='form-control' placeholder='Enter display name' maxLength='22' />
+ <input
+ onKeyUp={this.displayNameKeyUp}
+ type='text'
+ ref='display_name'
+ className='form-control'
+ placeholder='Enter display name'
+ maxLength='22' />
{displayNameError}
</div>
<div className={nameClass}>
<label className='control-label'>Handle</label>
- <input type='text' className='form-control' ref='channel_name' placeholder="lowercase alphanumeric's only" maxLength='22' />
+ <input
+ type='text'
+ className='form-control'
+ ref='channel_name'
+ placeholder="lowercase alphanumeric's only"
+ maxLength='22' />
{nameError}
</div>
<div className='form-group'>
<label className='control-label'>Description</label>
- <textarea className='form-control no-resize' ref='channel_desc' rows='3' placeholder='Description' maxLength='1024'></textarea>
+ <textarea
+ className='form-control no-resize'
+ ref='channel_desc'
+ rows='3'
+ placeholder='Description'
+ maxLength='1024' />
</div>
{serverError}
</div>
<div className='modal-footer'>
- <button type='button' className='btn btn-default' data-dismiss='modal'>Cancel</button>
- <button onClick={this.handleSubmit} type='submit' className='btn btn-primary'>Create New {channelTerm}</button>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal' >
+ Cancel
+ </button>
+ <button
+ onClick={this.handleSubmit}
+ type='submit'
+ className='btn btn-primary' >
+ Create New {channelTerm}
+ </button>
</div>
</form>
</div>
@@ -162,4 +201,4 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
diff --git a/web/react/components/notify_counts.jsx b/web/react/components/notify_counts.jsx
index ebc49882b..0b7c41b62 100644
--- a/web/react/components/notify_counts.jsx
+++ b/web/react/components/notify_counts.jsx
@@ -23,27 +23,30 @@ function getCountsStateFromStores() {
return {count: count};
}
-module.exports = React.createClass({
- displayName: 'NotifyCounts',
- componentDidMount: function() {
+export default class NotifyCounts extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onListenerChange = this.onListenerChange.bind(this);
+
+ this.state = getCountsStateFromStores();
+ }
+ componentDidMount() {
ChannelStore.addChangeListener(this.onListenerChange);
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange);
- },
- onListenerChange: function() {
+ }
+ onListenerChange() {
var newState = getCountsStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
this.setState(newState);
}
- },
- getInitialState: function() {
- return getCountsStateFromStores();
- },
- render: function() {
+ }
+ render() {
if (this.state.count) {
return <span className='badge badge-notify'>{this.state.count}</span>;
}
return null;
}
-});
+}
diff --git a/web/react/components/password_reset.jsx b/web/react/components/password_reset.jsx
index b2edea620..399d3b7b9 100644
--- a/web/react/components/password_reset.jsx
+++ b/web/react/components/password_reset.jsx
@@ -1,143 +1,47 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
-var UserStore = require('../stores/user_store.jsx');
+var PasswordResetSendLink = require('./password_reset_send_link.jsx');
+var PasswordResetForm = require('./password_reset_form.jsx');
-SendResetPasswordLink = React.createClass({
- handleSendLink: function(e) {
- e.preventDefault();
- var state = {};
+export default class PasswordReset extends React.Component {
+ constructor(props) {
+ super(props);
- var email = this.refs.email.getDOMNode().value.trim();
- if (!email) {
- state.error = "Please enter a valid email address."
- this.setState(state);
- return;
- }
-
- state.error = null;
- this.setState(state);
-
- data = {};
- data['email'] = email;
- data['name'] = this.props.teamName;
-
- client.sendPasswordReset(data,
- function(data) {
- this.setState({ error: null, update_text: <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>, more_update_text: "Please check your inbox." });
- $(this.refs.reset_form.getDOMNode()).hide();
- }.bind(this),
- function(err) {
- this.setState({ error: err.message, update_text: null, more_update_text: null });
- }.bind(this)
- );
- },
- getInitialState: function() {
- return {};
- },
- render: function() {
- var update_text = this.state.update_text ? <div className="reset-form alert alert-success">{this.state.update_text}{this.state.more_update_text}</div> : null;
- var error = this.state.error ? <div className="form-group has-error"><label className="control-label">{this.state.error}</label></div> : null;
-
- return (
- <div className="col-sm-12">
- <div className="signup-team__container">
- <h3>Password Reset</h3>
- { update_text }
- <form onSubmit={this.handleSendLink} ref="reset_form">
- <p>{"To reset your password, enter the email address you used to sign up for " + this.props.teamDisplayName + "."}</p>
- <div className={error ? 'form-group has-error' : 'form-group'}>
- <input type="text" className="form-control" name="email" ref="email" placeholder="Email" />
- </div>
- { error }
- <button type="submit" className="btn btn-primary">Reset my password</button>
- </form>
- </div>
- </div>
- );
+ this.state = {};
}
-});
-
-ResetPassword = React.createClass({
- handlePasswordReset: function(e) {
- e.preventDefault();
- var state = {};
-
- var password = this.refs.password.getDOMNode().value.trim();
- if (!password || password.length < 5) {
- state.error = "Please enter at least 5 characters."
- this.setState(state);
- return;
- }
-
- state.error = null;
- this.setState(state);
-
- data = {};
- data['new_password'] = password;
- data['hash'] = this.props.hash;
- data['data'] = this.props.data;
- data['name'] = this.props.teamName;
-
- client.resetPassword(data,
- function(data) {
- this.setState({ error: null, update_text: "Your password has been updated successfully." });
- }.bind(this),
- function(err) {
- this.setState({ error: err.message, update_text: null });
- }.bind(this)
- );
- },
- getInitialState: function() {
- return {};
- },
- render: function() {
- var update_text = this.state.update_text ? <div className="form-group"><br/><label className="control-label reset-form">{this.state.update_text} Click <a href={"/" + this.props.teamName + "/login"}>here</a> to log in.</label></div> : null;
- var error = this.state.error ? <div className="form-group has-error"><label className="control-label">{this.state.error}</label></div> : null;
-
- return (
- <div className="col-sm-12">
- <div className="signup-team__container">
- <h3>Password Reset</h3>
- <form onSubmit={this.handlePasswordReset}>
- <p>{"Enter a new password for your " + this.props.teamDisplayName + " " + config.SiteName + " account."}</p>
- <div className={error ? 'form-group has-error' : 'form-group'}>
- <input type="password" className="form-control" name="password" ref="password" placeholder="Password" />
- </div>
- { error }
- <button type="submit" className="btn btn-primary">Change my password</button>
- { update_text }
- </form>
- </div>
- </div>
- );
- }
-});
-
-module.exports = React.createClass({
- getInitialState: function() {
- return {};
- },
- render: function() {
-
- if (this.props.isReset === "false") {
- return (
- <SendResetPasswordLink
- teamDisplayName={this.props.teamDisplayName}
- teamName={this.props.teamName}
- />
- );
- } else {
+ render() {
+ if (this.props.isReset === 'false') {
return (
- <ResetPassword
+ <PasswordResetSendLink
teamDisplayName={this.props.teamDisplayName}
teamName={this.props.teamName}
- hash={this.props.hash}
- data={this.props.data}
/>
);
}
+
+ return (
+ <PasswordResetForm
+ teamDisplayName={this.props.teamDisplayName}
+ teamName={this.props.teamName}
+ hash={this.props.hash}
+ data={this.props.data}
+ />
+ );
}
-});
+}
+
+PasswordReset.defaultProps = {
+ isReset: '',
+ teamName: '',
+ teamDisplayName: '',
+ hash: '',
+ data: ''
+};
+PasswordReset.propTypes = {
+ isReset: React.PropTypes.string,
+ teamName: React.PropTypes.string,
+ teamDisplayName: React.PropTypes.string,
+ hash: React.PropTypes.string,
+ data: React.PropTypes.string
+};
diff --git a/web/react/components/password_reset_form.jsx b/web/react/components/password_reset_form.jsx
new file mode 100644
index 000000000..7acd2d1f7
--- /dev/null
+++ b/web/react/components/password_reset_form.jsx
@@ -0,0 +1,100 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var client = require('../utils/client.jsx');
+
+export default class PasswordResetForm extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handlePasswordReset = this.handlePasswordReset.bind(this);
+
+ this.state = {};
+ }
+ handlePasswordReset(e) {
+ e.preventDefault();
+ var state = {};
+
+ var password = React.findDOMNode(this.refs.password).value.trim();
+ if (!password || password.length < 5) {
+ state.error = 'Please enter at least 5 characters.';
+ this.setState(state);
+ return;
+ }
+
+ state.error = null;
+ this.setState(state);
+
+ var data = {};
+ data.new_password = password;
+ data.hash = this.props.hash;
+ data.data = this.props.data;
+ data.name = this.props.teamName;
+
+ client.resetPassword(data,
+ function resetSuccess() {
+ this.setState({error: null, updateText: 'Your password has been updated successfully.'});
+ }.bind(this),
+ function resetFailure(err) {
+ this.setState({error: err.message, updateText: null});
+ }.bind(this)
+ );
+ }
+ render() {
+ var updateText = null;
+ if (this.state.updateText) {
+ updateText = <div className='form-group'><br/><label className='control-label reset-form'>{this.state.updateText} Click <a href={'/' + this.props.teamName + '/login'}>here</a> to log in.</label></div>;
+ }
+
+ var error = null;
+ if (this.state.error) {
+ error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
+ }
+
+ var formClass = 'form-group';
+ if (error) {
+ formClass += ' has-error';
+ }
+
+ return (
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <h3>Password Reset</h3>
+ <form onSubmit={this.handlePasswordReset}>
+ <p>{'Enter a new password for your ' + this.props.teamDisplayName + ' ' + config.SiteName + ' account.'}</p>
+ <div className={formClass}>
+ <input
+ type='password'
+ className='form-control'
+ name='password'
+ ref='password'
+ placeholder='Password'
+ />
+ </div>
+ {error}
+ <button
+ type='submit'
+ className='btn btn-primary'
+ >
+ Change my password
+ </button>
+ {updateText}
+ </form>
+ </div>
+ </div>
+ );
+ }
+}
+
+PasswordResetForm.defaultProps = {
+ teamName: '',
+ teamDisplayName: '',
+ hash: '',
+ data: ''
+};
+PasswordResetForm.propTypes = {
+ teamName: React.PropTypes.string,
+ teamDisplayName: React.PropTypes.string,
+ hash: React.PropTypes.string,
+ data: React.PropTypes.string
+};
diff --git a/web/react/components/password_reset_send_link.jsx b/web/react/components/password_reset_send_link.jsx
new file mode 100644
index 000000000..1e6cc3607
--- /dev/null
+++ b/web/react/components/password_reset_send_link.jsx
@@ -0,0 +1,98 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var client = require('../utils/client.jsx');
+
+export default class PasswordResetSendLink extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleSendLink = this.handleSendLink.bind(this);
+
+ this.state = {};
+ }
+ handleSendLink(e) {
+ e.preventDefault();
+ var state = {};
+
+ var email = React.findDOMNode(this.refs.email).value.trim();
+ if (!email) {
+ state.error = 'Please enter a valid email address.';
+ this.setState(state);
+ return;
+ }
+
+ state.error = null;
+ this.setState(state);
+
+ var data = {};
+ data.email = email;
+ data.name = this.props.teamName;
+
+ client.sendPasswordReset(data,
+ 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.'});
+ $(React.findDOMNode(this.refs.reset_form)).hide();
+ }.bind(this),
+ function passwordResetFailedToSend(err) {
+ this.setState({error: err.message, update_text: null, moreUpdateText: null});
+ }.bind(this)
+ );
+ }
+ render() {
+ var updateText = null;
+ if (this.state.updateText) {
+ updateText = <div className='reset-form alert alert-success'>{this.state.updateText}{this.state.moreUpdateText}</div>;
+ }
+
+ var error = null;
+ if (this.state.error) {
+ error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
+ }
+
+ var formClass = 'form-group';
+ if (error) {
+ formClass += ' has-error';
+ }
+
+ return (
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <h3>Password Reset</h3>
+ {updateText}
+ <form
+ onSubmit={this.handleSendLink}
+ ref='reset_form'
+ >
+ <p>{'To reset your password, enter the email address you used to sign up for ' + this.props.teamDisplayName + '.'}</p>
+ <div className={formClass}>
+ <input
+ type='text'
+ className='form-control'
+ name='email'
+ ref='email'
+ placeholder='Email'
+ />
+ </div>
+ {error}
+ <button
+ type='submit'
+ className='btn btn-primary'
+ >
+ Reset my password
+ </button>
+ </form>
+ </div>
+ </div>
+ );
+ }
+}
+
+PasswordResetSendLink.defaultProps = {
+ teamName: '',
+ teamDisplayName: ''
+};
+PasswordResetSendLink.propTypes = {
+ teamName: React.PropTypes.string,
+ teamDisplayName: React.PropTypes.string
+};
diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx
new file mode 100644
index 000000000..fb9522afb
--- /dev/null
+++ b/web/react/components/popover_list_members.jsx
@@ -0,0 +1,80 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+export default class PopoverListMembers extends React.Component {
+ componentDidMount() {
+ const originalLeave = $.fn.popover.Constructor.prototype.leave;
+ $.fn.popover.Constructor.prototype.leave = function onLeave(obj) {
+ let selfObj;
+ if (obj instanceof this.constructor) {
+ selfObj = obj;
+ } else {
+ selfObj = $(obj.currentTarget)[this.type](this.getDelegateOptions()).data(`bs.${this.type}`);
+ }
+ originalLeave.call(this, obj);
+
+ if (obj.currentTarget && selfObj.$tip) {
+ selfObj.$tip.one('mouseenter', function onMouseEnter() {
+ clearTimeout(selfObj.timeout);
+ selfObj.$tip.one('mouseleave', function onMouseLeave() {
+ $.fn.popover.Constructor.prototype.leave.call(selfObj, selfObj);
+ });
+ });
+ }
+ };
+
+ $('#member_popover').popover({placement: 'bottom', trigger: 'click', html: true});
+ $('body').on('click', function onClick(e) {
+ if ($(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) {
+ $('#member_popover').popover('hide');
+ }
+ });
+ }
+ render() {
+ let popoverHtml = '';
+ const members = this.props.members;
+ let count;
+ if (members.length > 20) {
+ count = '20+';
+ } else {
+ count = members.length || '-';
+ }
+
+ if (members) {
+ members.sort(function compareByLocal(a, b) {
+ return a.username.localeCompare(b.username);
+ });
+
+ members.forEach(function addMemberElement(m) {
+ popoverHtml += `<div class='text--nowrap'>${m.username}</div>`;
+ });
+ }
+
+ return (
+ <div
+ id='member_popover'
+ data-toggle='popover'
+ data-content={popoverHtml}
+ data-original-title='Members'
+ >
+ <div
+ id='member_tooltip'
+ data-placement='left'
+ data-toggle='tooltip'
+ title='View Channel Members'
+ >
+ {count}
+ <span
+ className='glyphicon glyphicon-user'
+ aria-hidden='true'
+ />
+ </div>
+ </div>
+ );
+ }
+}
+
+PopoverListMembers.propTypes = {
+ members: React.PropTypes.array.isRequired,
+ channelId: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index acc2b51d2..e284f4b6a 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -15,9 +15,17 @@ var utils = require('../utils/utils.jsx');
var PostInfo = require('./post_info.jsx');
-module.exports = React.createClass({
- displayName: 'Post',
- handleCommentClick: function(e) {
+export default class Post extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleCommentClick = this.handleCommentClick.bind(this);
+ this.forceUpdateInfo = this.forceUpdateInfo.bind(this);
+ this.retryPost = this.retryPost.bind(this);
+
+ this.state = {};
+ }
+ handleCommentClick(e) {
e.preventDefault();
var data = {};
@@ -33,31 +41,31 @@ module.exports = React.createClass({
type: ActionTypes.RECIEVED_SEARCH,
results: null
});
- },
- forceUpdateInfo: function() {
+ }
+ forceUpdateInfo() {
this.refs.info.forceUpdate();
this.refs.header.forceUpdate();
- },
- retryPost: function(e) {
+ }
+ retryPost(e) {
e.preventDefault();
var post = this.props.post;
client.createPost(post, post.channel_id,
- function(data) {
+ function success(data) {
AsyncClient.getPosts();
var channel = ChannelStore.get(post.channel_id);
var member = ChannelStore.getMember(post.channel_id);
member.msg_count = channel.total_msg_count;
- member.last_viewed_at = (new Date).getTime();
+ member.last_viewed_at = utils.getTimestamp();
ChannelStore.setChannelMember(member);
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_POST,
post: data
});
- }.bind(this),
- function(err) {
+ },
+ function error() {
post.state = Constants.POST_FAILED;
PostStore.updatePendingPost(post);
this.forceUpdate();
@@ -67,18 +75,15 @@ module.exports = React.createClass({
post.state = Constants.POST_LOADING;
PostStore.updatePendingPost(post);
this.forceUpdate();
- },
- shouldComponentUpdate: function(nextProps) {
+ }
+ shouldComponentUpdate(nextProps) {
if (!utils.areStatesEqual(nextProps.post, this.props.post)) {
return true;
}
return false;
- },
- getInitialState: function() {
- return { };
- },
- render: function() {
+ }
+ render() {
var post = this.props.post;
var parentPost = this.props.parentPost;
var posts = this.props.posts;
@@ -89,19 +94,27 @@ module.exports = React.createClass({
}
var commentCount = 0;
- var commentRootId = parentPost ? post.root_id : post.id;
+ var commentRootId;
+ if (parentPost) {
+ commentRootId = post.root_id;
+ } else {
+ commentRootId = post.id;
+ }
for (var postId in posts) {
- if (posts[postId].root_id == commentRootId) {
+ if (posts[postId].root_id === commentRootId) {
commentCount += 1;
}
}
- var error = this.state.error ? <div className='form-group has-error'><label className='control-label'>{ this.state.error }</label></div> : null;
-
- var rootUser = this.props.sameRoot ? 'same--root' : 'other--root';
+ var rootUser;
+ if (this.props.sameRoot) {
+ rootUser = 'same--root';
+ } else {
+ rootUser = 'other--root';
+ }
var postType = '';
- if (type != 'Post'){
+ if (type !== 'Post') {
postType = 'post--comment';
}
@@ -122,21 +135,60 @@ module.exports = React.createClass({
sameUserClass = 'same--user';
}
+ var profilePic = null;
+ if (this.props.hideProfilePic) {
+ profilePic = (
+ <div className='post-profile-img__container'>
+ <img
+ className='post-profile-img'
+ src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp}
+ height='36'
+ width='36' />
+ </div>
+ );
+ }
+
return (
<div>
- <div id={post.id} className={'post ' + sameUserClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss}>
- { !this.props.hideProfilePic ?
- <div className='post-profile-img__container'>
- <img className='post-profile-img' src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp} height='36' width='36' />
- </div>
- : null }
+ <div
+ id={post.id}
+ className={'post ' + sameUserClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss} >
+ {profilePic}
<div className='post__content'>
- <PostHeader ref='header' post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} isLastComment={this.props.isLastComment} />
- <PostBody post={post} sameRoot={this.props.sameRoot} parentPost={parentPost} posts={posts} handleCommentClick={this.handleCommentClick} retryPost={this.retryPost} />
- <PostInfo ref='info' post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} allowReply='true' />
+ <PostHeader
+ ref='header'
+ post={post}
+ sameRoot={this.props.sameRoot}
+ commentCount={commentCount}
+ handleCommentClick={this.handleCommentClick}
+ isLastComment={this.props.isLastComment} />
+ <PostBody
+ post={post}
+ sameRoot={this.props.sameRoot}
+ parentPost={parentPost}
+ posts={posts}
+ handleCommentClick={this.handleCommentClick}
+ retryPost={this.retryPost} />
+ <PostInfo
+ ref='info'
+ post={post}
+ sameRoot={this.props.sameRoot}
+ commentCount={commentCount}
+ handleCommentClick={this.handleCommentClick}
+ allowReply='true' />
</div>
</div>
</div>
);
}
-});
+}
+
+Post.propTypes = {
+ post: React.PropTypes.object,
+ posts: React.PropTypes.object,
+ parentPost: React.PropTypes.object,
+ sameUser: React.PropTypes.bool,
+ sameRoot: React.PropTypes.bool,
+ hideProfilePic: React.PropTypes.bool,
+ isLastComment: React.PropTypes.bool
+};
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index e5ab5b624..88fb9aec8 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -1,95 +1,140 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var FileAttachmentList = require('./file_attachment_list.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var utils = require('../utils/utils.jsx');
-var Constants = require('../utils/constants.jsx');
+const FileAttachmentList = require('./file_attachment_list.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const Utils = require('../utils/utils.jsx');
+const Constants = require('../utils/constants.jsx');
-module.exports = React.createClass({
- componentWillReceiveProps: function(nextProps) {
- var linkData = utils.extractLinks(nextProps.post.message);
+export default class PostBody extends React.Component {
+ constructor(props) {
+ super(props);
+
+ const linkData = Utils.extractLinks(this.props.post.message);
+ this.state = {links: linkData.links, message: linkData.text};
+ }
+ componentWillReceiveProps(nextProps) {
+ const linkData = Utils.extractLinks(nextProps.post.message);
this.setState({links: linkData.links, message: linkData.text});
- },
- getInitialState: function() {
- var linkData = utils.extractLinks(this.props.post.message);
- return {links: linkData.links, message: linkData.text};
- },
- render: function() {
- var post = this.props.post;
- var filenames = this.props.post.filenames;
- var parentPost = this.props.parentPost;
- var inner = utils.textToJsx(this.state.message);
+ }
+ render() {
+ const post = this.props.post;
+ const filenames = this.props.post.filenames;
+ const parentPost = this.props.parentPost;
+ const inner = Utils.textToJsx(this.state.message);
- var comment = '';
- var reply = '';
- var postClass = '';
+ let comment = '';
+ let postClass = '';
if (parentPost) {
- var profile = UserStore.getProfile(parentPost.user_id);
- var apostrophe = '';
- var name = '...';
+ const profile = UserStore.getProfile(parentPost.user_id);
+
+ let apostrophe = '';
+ let name = '...';
if (profile != null) {
if (profile.username.slice(-1) === 's') {
apostrophe = '\'';
} else {
apostrophe = '\'s';
}
- name = <a className='theme' onClick={function searchName() { utils.searchForTerm(profile.username); }}>{profile.username}</a>;
+ name = (
+ <a
+ className='theme'
+ onClick={Utils.searchForTerm.bind(null, profile.username)}
+ >
+ {profile.username}
+ </a>
+ );
}
- var message = '';
+ let message = '';
if (parentPost.message) {
- message = utils.replaceHtmlEntities(parentPost.message);
+ message = Utils.replaceHtmlEntities(parentPost.message);
} else if (parentPost.filenames.length) {
message = parentPost.filenames[0].split('/').pop();
if (parentPost.filenames.length === 2) {
message += ' plus 1 other file';
} else if (parentPost.filenames.length > 2) {
- message += ' plus ' + (parentPost.filenames.length - 1) + ' other files';
+ message += ` plus ${parentPost.filenames.length - 1} other files`;
}
}
comment = (
<p className='post-link'>
- <span>Commented on {name}{apostrophe} message: <a className='theme' onClick={this.props.handleCommentClick}>{message}</a></span>
+ <span>
+ Commented on {name}{apostrophe} message:
+ <a
+ className='theme'
+ onClick={this.props.handleCommentClick}
+ >
+ {message}
+ </a>
+ </span>
</p>
);
postClass += ' post-comment';
}
- var loading;
+ let loading;
if (post.state === Constants.POST_FAILED) {
postClass += ' post-fail';
- loading = <a className='theme post-retry pull-right' href='#' onClick={this.props.retryPost}>Retry</a>;
+ loading = (
+ <a
+ className='theme post-retry pull-right'
+ href='#'
+ onClick={this.props.retryPost}
+ >
+ Retry
+ </a>
+ );
} else if (post.state === Constants.POST_LOADING) {
postClass += ' post-waiting';
- loading = <img className='post-loading-gif pull-right' src='/static/images/load.gif'/>;
+ loading = (
+ <img
+ className='post-loading-gif pull-right'
+ src='/static/images/load.gif'
+ />
+ );
}
- var embed;
+ let embed;
if (filenames.length === 0 && this.state.links) {
- embed = utils.getEmbed(this.state.links[0]);
+ embed = Utils.getEmbed(this.state.links[0]);
}
- var fileAttachmentHolder = '';
+ let fileAttachmentHolder = '';
if (filenames && filenames.length > 0) {
- fileAttachmentHolder = (<FileAttachmentList
- filenames={filenames}
- modalId={'view_image_modal_' + post.id}
- channelId={post.channel_id}
- userId={post.user_id} />);
+ fileAttachmentHolder = (
+ <FileAttachmentList
+ filenames={filenames}
+ modalId={`view_image_modal_${post.id}`}
+ channelId={post.channel_id}
+ userId={post.user_id}
+ />
+ );
}
return (
<div className='post-body'>
{comment}
- <p key={post.id + '_message'} className={postClass}>{loading}<span>{inner}</span></p>
+ <p
+ key={`${post.id}_message`}
+ className={postClass}
+ >
+ {loading}<span>{inner}</span>
+ </p>
{fileAttachmentHolder}
{embed}
</div>
);
}
-});
+}
+
+PostBody.propTypes = {
+ post: React.PropTypes.object.isRequired,
+ parentPost: React.PropTypes.object,
+ retryPost: React.PropTypes.func.isRequired,
+ handleCommentClick: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/post_deleted_modal.jsx b/web/react/components/post_deleted_modal.jsx
index 83b007bad..d284a9d1b 100644
--- a/web/react/components/post_deleted_modal.jsx
+++ b/web/react/components/post_deleted_modal.jsx
@@ -3,34 +3,61 @@
var UserStore = require('../stores/user_store.jsx');
-module.exports = React.createClass({
- getInitialState: function() {
- return { };
- },
- render: function() {
- var currentUser = UserStore.getCurrentUser()
+export default class PostDeletedModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {};
+ }
+ render() {
+ var currentUser = UserStore.getCurrentUser();
if (currentUser != null) {
return (
- <div className="modal fade" ref="modal" id="post_deleted" tabIndex="-1" role="dialog" aria-hidden="true">
- <div className="modal-dialog">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title" id="myModalLabel">Comment could not be posted</h4>
- </div>
- <div className="modal-body">
- <p>Someone deleted the message on which you tried to post a comment.</p>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='post_deleted'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4
+ className='modal-title'
+ id='myModalLabel'
+ >
+ Comment could not be posted
+ </h4>
+ </div>
+ <div className='modal-body'>
+ <p>Someone deleted the message on which you tried to post a comment.</p>
+ </div>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-primary'
+ data-dismiss='modal'
+ >
+ Okay
+ </button>
+ </div>
</div>
- <div className="modal-footer">
- <button type="button" className="btn btn-primary" data-dismiss="modal">Okay</button>
- </div>
- </div>
- </div>
+ </div>
</div>
);
- } else {
- return <div/>;
}
+
+ return <div/>;
}
-});
+}
diff --git a/web/react/components/post_header.jsx b/web/react/components/post_header.jsx
index 129db6d14..9dc525e03 100644
--- a/web/react/components/post_header.jsx
+++ b/web/react/components/post_header.jsx
@@ -1,23 +1,42 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserProfile = require( './user_profile.jsx' );
+var UserProfile = require('./user_profile.jsx');
var PostInfo = require('./post_info.jsx');
-module.exports = React.createClass({
- getInitialState: function() {
- return { };
- },
- render: function() {
+export default class PostHeader extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ }
+ render() {
var post = this.props.post;
return (
- <ul className="post-header post-header-post">
- <li className="post-header-col post-header__name"><strong><UserProfile userId={post.user_id} /></strong></li>
- <li className="post-info--hidden">
- <PostInfo post={post} commentCount={this.props.commentCount} handleCommentClick={this.props.handleCommentClick} allowReply="true" isLastComment={this.props.isLastComment} />
+ <ul className='post-header post-header-post'>
+ <li className='post-header-col post-header__name'><strong><UserProfile userId={post.user_id} /></strong></li>
+ <li className='post-info--hidden'>
+ <PostInfo
+ post={post}
+ commentCount={this.props.commentCount}
+ handleCommentClick={this.props.handleCommentClick}
+ allowReply='true'
+ isLastComment={this.props.isLastComment}
+ />
</li>
</ul>
);
}
-});
+}
+
+PostHeader.defaultProps = {
+ post: null,
+ commentCount: 0,
+ isLastComment: false
+};
+PostHeader.propTypes = {
+ post: React.PropTypes.object,
+ commentCount: React.PropTypes.number,
+ isLastComment: React.PropTypes.bool,
+ handleCommentClick: React.PropTypes.func
+};
diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx
index 2fe6dd96b..37958b649 100644
--- a/web/react/components/rename_channel_modal.jsx
+++ b/web/react/components/rename_channel_modal.jsx
@@ -1,147 +1,217 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
+const Utils = require('../utils/utils.jsx');
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
-var utils = require('../utils/utils.jsx');
-var Client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var TeamStore = require('../stores/team_store.jsx');
-var Constants = require('../utils/constants.jsx');
+export default class RenameChannelModal extends React.Component {
+ constructor(props) {
+ super(props);
-module.exports = React.createClass({
- handleSubmit: function(e) {
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.onNameChange = this.onNameChange.bind(this);
+ this.onDisplayNameChange = this.onDisplayNameChange.bind(this);
+ this.displayNameKeyUp = this.displayNameKeyUp.bind(this);
+ this.handleClose = this.handleClose.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+
+ this.state = {
+ displayName: '',
+ channelName: '',
+ channelId: '',
+ serverError: '',
+ nameError: '',
+ displayNameError: '',
+ invalid: false
+ };
+ }
+ handleSubmit(e) {
e.preventDefault();
- if (this.state.channel_id.length !== 26) return;
+ if (this.state.channelId.length !== 26) {
+ return;
+ }
- var channel = ChannelStore.get(this.state.channel_id);
- var oldName = channel.name
- var oldDisplayName = channel.display_name
- var state = { server_error: "" };
+ let channel = ChannelStore.get(this.state.channelId);
+ const oldName = channel.name;
+ const oldDisplayName = channel.displayName;
+ let state = {serverError: ''};
- channel.display_name = this.state.display_name.trim();
+ channel.display_name = this.state.displayName.trim();
if (!channel.display_name) {
- state.display_name_error = "This field is required";
- state.inValid = true;
- }
- else if (channel.display_name.length > 22) {
- state.display_name_error = "This field must be less than 22 characters";
- state.inValid = true;
- }
- else {
- state.display_name_error = "";
+ state.displayNameError = 'This field is required';
+ state.invalid = true;
+ } else if (channel.display_name.length > 22) {
+ state.displayNameError = 'This field must be less than 22 characters';
+ state.invalid = true;
+ } else {
+ state.displayNameError = '';
}
- channel.name = this.state.channel_name.trim();
+ channel.name = this.state.channelName.trim();
if (!channel.name) {
- state.name_error = "This field is required";
- state.inValid = true;
- }
- else if(channel.name.length > 22){
- state.name_error = "This field must be less than 22 characters";
- state.inValid = true;
- }
- else {
- var cleaned_name = utils.cleanUpUrlable(channel.name);
- if (cleaned_name != channel.name) {
- state.name_error = "Must be lowercase alphanumeric characters";
- state.inValid = true;
- }
- else {
- state.name_error = "";
+ state.nameError = 'This field is required';
+ state.invalid = true;
+ } else if (channel.name.length > 22) {
+ state.nameError = 'This field must be less than 22 characters';
+ state.invalid = true;
+ } else {
+ let cleanedName = Utils.cleanUpUrlable(channel.name);
+ if (cleanedName !== channel.name) {
+ state.nameError = 'Must be lowercase alphanumeric characters';
+ state.invalid = true;
+ } else {
+ state.nameError = '';
}
}
this.setState(state);
- if (state.inValid)
- return;
-
- if (oldName == channel.name && oldDisplayName == channel.display_name)
+ if (state.invalid || (oldName === channel.name && oldDisplayName === channel.display_name)) {
return;
+ }
Client.updateChannel(channel,
- function(data, text, req) {
- $(this.refs.modal.getDOMNode()).modal('hide');
+ function handleUpdateSuccess() {
+ $(React.findDOMNode(this.refs.modal)).modal('hide');
AsyncClient.getChannel(channel.id);
- utils.updateTabTitle(channel.display_name);
- utils.updateAddressBar(channel.name);
+ Utils.updateTabTitle(channel.display_name);
+ Utils.updateAddressBar(channel.name);
- this.refs.display_name.getDOMNode().value = "";
- this.refs.channel_name.getDOMNode().value = "";
+ React.findDOMNode(this.refs.displayName).value = '';
+ React.findDOMNode(this.refs.channelName).value = '';
}.bind(this),
- function(err) {
- state.server_error = err.message;
- state.inValid = true;
+ function handleUpdateError(err) {
+ state.serverError = err.message;
+ state.invalid = true;
this.setState(state);
}.bind(this)
);
- },
- onNameChange: function() {
- this.setState({ channel_name: this.refs.channel_name.getDOMNode().value })
- },
- onDisplayNameChange: function() {
- this.setState({ display_name: this.refs.display_name.getDOMNode().value })
- },
- displayNameKeyUp: function(e) {
- var display_name = this.refs.display_name.getDOMNode().value.trim();
- var channel_name = utils.cleanUpUrlable(display_name);
- this.refs.channel_name.getDOMNode().value = channel_name;
- this.setState({ channel_name: channel_name })
- },
- handleClose: function() {
- this.setState({display_name: "", channel_name: "", display_name_error: "", server_error: "", name_error: ""});
- },
- componentDidMount: function() {
- var self = this;
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
- var button = $(e.relatedTarget);
- self.setState({ display_name: button.attr('data-display'), channel_name: button.attr('data-name'), channel_id: button.attr('data-channelid') });
- });
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', this.handleClose);
- },
- componentWillUnmount: function() {
- $(this.refs.modal.getDOMNode()).off('hidden.bs.modal', this.handleClose);
- },
- getInitialState: function() {
- return { display_name: "", channel_name: "", channel_id: "" };
- },
- render: function() {
-
- var display_name_error = this.state.display_name_error ? <label className='control-label'>{ this.state.display_name_error }</label> : null;
- var name_error = this.state.name_error ? <label className='control-label'>{ this.state.name_error }</label> : null;
- var server_error = this.state.server_error ? <div className='form-group has-error'><label className='control-label'>{ this.state.server_error }</label></div> : null;
+ }
+ onNameChange() {
+ this.setState({channelName: React.findDOMNode(this.refs.channelName).value});
+ }
+ onDisplayNameChange() {
+ this.setState({displayName: React.findDOMNode(this.refs.displayName).value});
+ }
+ displayNameKeyUp() {
+ const displayName = React.findDOMNode(this.refs.displayName).value.trim();
+ const channelName = Utils.cleanUpUrlable(displayName);
+ React.findDOMNode(this.refs.channelName).value = channelName;
+ this.setState({channelName: channelName});
+ }
+ handleClose() {
+ this.state = {
+ displayName: '',
+ channelName: '',
+ channelId: '',
+ serverError: '',
+ nameError: '',
+ displayNameError: '',
+ invalid: false
+ };
+ }
+ componentDidMount() {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) {
+ const button = $(e.relatedTarget);
+ this.setState({displayName: button.attr('data-display'), channelName: button.attr('data-name'), channelId: button.attr('data-channelid')});
+ }.bind(this));
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
+ }
+ componentWillUnmount() {
+ $(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose);
+ }
+ render() {
+ let displayNameError = null;
+ let displayNameClass = 'form-group';
+ if (this.state.displayNameError) {
+ displayNameError = <label className='control-label'>{this.state.displayNameError}</label>;
+ displayNameClass += ' has-error';
+ }
+
+ let nameError = null;
+ let nameClass = 'form-group';
+ if (this.state.nameError) {
+ nameError = <label className='control-label'>{this.state.nameError}</label>;
+ nameClass += ' has-error';
+ }
+
+ let serverError = null;
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
+ }
return (
- <div className="modal fade" ref="modal" id="rename_channel" tabIndex="-1" role="dialog" aria-hidden="true">
- <div className="modal-dialog">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal">
- <span aria-hidden="true">&times;</span>
- <span className="sr-only">Close</span>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='rename_channel'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ >
+ <span aria-hidden='true'>&times;</span>
+ <span className='sr-only'>Close</span>
</button>
- <h4 className="modal-title">Rename Channel</h4>
+ <h4 className='modal-title'>Rename Channel</h4>
</div>
- <form role="form">
- <div className="modal-body">
- <div className={ this.state.display_name_error ? "form-group has-error" : "form-group" }>
+ <form role='form'>
+ <div className='modal-body'>
+ <div className={displayNameClass}>
<label className='control-label'>Display Name</label>
- <input onKeyUp={this.displayNameKeyUp} onChange={this.onDisplayNameChange} type="text" ref="display_name" className="form-control" placeholder="Enter display name" value={this.state.display_name} maxLength="64" />
- { display_name_error }
+ <input
+ onKeyUp={this.displayNameKeyUp}
+ onChange={this.onDisplayNameChange}
+ type='text'
+ ref='displayName'
+ className='form-control'
+ placeholder='Enter display name'
+ value={this.state.displayName}
+ maxLength='64'
+ />
+ {displayNameError}
</div>
- <div className={ this.state.name_error ? "form-group has-error" : "form-group" }>
+ <div className={nameClass}>
<label className='control-label'>Handle</label>
- <input onChange={this.onNameChange} type="text" className="form-control" ref="channel_name" placeholder="lowercase alphanumeric's only" value={this.state.channel_name} maxLength="64" />
- { name_error }
+ <input
+ onChange={this.onNameChange}
+ type='text'
+ className='form-control'
+ ref='channelName'
+ placeholder='lowercase alphanumeric&#39;s only'
+ value={this.state.channelName}
+ maxLength='64'
+ />
+ {nameError}
</div>
- { server_error }
+ {serverError}
</div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button>
- <button onClick={this.handleSubmit} type="submit" className="btn btn-primary">Save</button>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >
+ Cancel
+ </button>
+ <button
+ onClick={this.handleSubmit}
+ type='submit'
+ className='btn btn-primary'
+ >
+ Save
+ </button>
</div>
</form>
</div>
@@ -149,4 +219,4 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx
index 7df2fed9e..e74ab7f13 100644
--- a/web/react/components/rhs_comment.jsx
+++ b/web/react/components/rhs_comment.jsx
@@ -6,10 +6,10 @@ var ChannelStore = require('../stores/channel_store.jsx');
var UserProfile = require('./user_profile.jsx');
var UserStore = require('../stores/user_store.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx');
var FileAttachmentList = require('./file_attachment_list.jsx');
-var client = require('../utils/client.jsx');
+var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -25,7 +25,7 @@ export default class RhsComment extends React.Component {
e.preventDefault();
var post = this.props.post;
- client.createPost(post, post.channel_id,
+ Client.createPost(post, post.channel_id,
function success(data) {
AsyncClient.getPosts(post.channel_id);
@@ -52,7 +52,7 @@ export default class RhsComment extends React.Component {
this.forceUpdate();
}
shouldComponentUpdate(nextProps) {
- if (!utils.areStatesEqual(nextProps.post, this.props.post)) {
+ if (!Utils.areStatesEqual(nextProps.post, this.props.post)) {
return true;
}
@@ -73,7 +73,7 @@ export default class RhsComment extends React.Component {
type = 'Comment';
}
- var message = utils.textToJsx(post.message);
+ var message = Utils.textToJsx(post.message);
var timestamp = UserStore.getCurrentUser().update_at;
var loading;
@@ -182,7 +182,7 @@ export default class RhsComment extends React.Component {
</li>
<li className='post-header-col'>
<time className='post-right-comment-time'>
- {utils.displayCommentDateTime(post.create_at)}
+ {Utils.displayCommentDateTime(post.create_at)}
</time>
</li>
<li className='post-header-col post-header__reply'>
diff --git a/web/react/components/rhs_header_post.jsx b/web/react/components/rhs_header_post.jsx
index 4cf4231e9..5156ec4d7 100644
--- a/web/react/components/rhs_header_post.jsx
+++ b/web/react/components/rhs_header_post.jsx
@@ -1,9 +1,9 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
export default class RhsHeaderPost extends React.Component {
constructor(props) {
@@ -43,7 +43,7 @@ export default class RhsHeaderPost extends React.Component {
});
}
render() {
- var back;
+ let back;
if (this.props.fromSearch) {
back = (
<a
diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx
index b11b39e9e..8da8231a2 100644
--- a/web/react/components/search_bar.jsx
+++ b/web/react/components/search_bar.jsx
@@ -1,7 +1,6 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-
var client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var PostStore = require('../stores/post_store.jsx');
@@ -10,36 +9,47 @@ var utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-function getSearchTermStateFromStores() {
- var term = PostStore.getSearchTerm() || '';
- return {
- search_term: term
- };
-}
+export default class SearchBar extends React.Component {
+ constructor() {
+ super();
+ this.mounted = false;
-module.exports = React.createClass({
- displayName: 'SearchBar',
- componentDidMount: function() {
- PostStore.addSearchTermChangeListener(this._onChange);
- },
- componentWillUnmount: function() {
- PostStore.removeSearchTermChangeListener(this._onChange);
- },
- _onChange: function(doSearch, isMentionSearch) {
- if (this.isMounted()) {
- var newState = getSearchTermStateFromStores();
+ this.onListenerChange = this.onListenerChange.bind(this);
+ this.handleUserInput = this.handleUserInput.bind(this);
+ this.performSearch = this.performSearch.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+
+ this.state = this.getSearchTermStateFromStores();
+ }
+ getSearchTermStateFromStores() {
+ var term = PostStore.getSearchTerm() || '';
+ return {
+ searchTerm: term
+ };
+ }
+ componentDidMount() {
+ PostStore.addSearchTermChangeListener(this.onListenerChange);
+ this.mounted = true;
+ }
+ componentWillUnmount() {
+ PostStore.removeSearchTermChangeListener(this.onListenerChange);
+ this.mounted = false;
+ }
+ onListenerChange(doSearch, isMentionSearch) {
+ if (this.mounted) {
+ var newState = this.getSearchTermStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
this.setState(newState);
}
if (doSearch) {
- this.performSearch(newState.search_term, isMentionSearch);
+ this.performSearch(newState.searchTerm, isMentionSearch);
}
}
- },
- clearFocus: function(e) {
+ }
+ clearFocus() {
$('.search-bar__container').removeClass('focused');
- },
- handleClose: function(e) {
+ }
+ handleClose(e) {
e.preventDefault();
AppDispatcher.handleServerAction({
@@ -58,23 +68,23 @@ module.exports = React.createClass({
type: ActionTypes.RECIEVED_POST_SELECTED,
results: null
});
- },
- handleUserInput: function(e) {
+ }
+ handleUserInput(e) {
var term = e.target.value;
PostStore.storeSearchTerm(term);
PostStore.emitSearchTermChange(false);
- this.setState({ search_term: term });
- },
- handleUserFocus: function(e) {
+ this.setState({searchTerm: term});
+ }
+ handleUserFocus(e) {
e.target.select();
$('.search-bar__container').addClass('focused');
- },
- performSearch: function(terms, isMentionSearch) {
+ }
+ performSearch(terms, isMentionSearch) {
if (terms.length) {
this.setState({isSearching: true});
client.search(
terms,
- function(data) {
+ function success(data) {
this.setState({isSearching: false});
if (utils.isMobile()) {
React.findDOMNode(this.refs.search).value = '';
@@ -86,38 +96,50 @@ module.exports = React.createClass({
is_mention_search: isMentionSearch
});
}.bind(this),
- function(err) {
+ function error(err) {
this.setState({isSearching: false});
- AsyncClient.dispatchError(err, "search");
+ AsyncClient.dispatchError(err, 'search');
}.bind(this)
);
}
- },
- handleSubmit: function(e) {
+ }
+ handleSubmit(e) {
e.preventDefault();
- this.performSearch(this.state.search_term.trim());
- },
- getInitialState: function() {
- return getSearchTermStateFromStores();
- },
- render: function() {
+ this.performSearch(this.state.searchTerm.trim());
+ }
+ render() {
+ var isSearching = null;
+ if (this.state.isSearching) {
+ isSearching = <span className={'glyphicon glyphicon-refresh glyphicon-refresh-animate'}></span>;
+ }
return (
<div>
- <div className="sidebar__collapse" onClick={this.handleClose}><span className="fa fa-angle-left"></span></div>
- <span onClick={this.clearFocus} className="search__clear">Cancel</span>
- <form role="form" className="search__form relative-div" onSubmit={this.handleSubmit}>
- <span className="glyphicon glyphicon-search sidebar__search-icon"></span>
+ <div
+ className='sidebar__collapse'
+ onClick={this.handleClose} >
+ <span className='fa fa-angle-left'></span>
+ </div>
+ <span
+ className='search__clear'
+ onClick={this.clearFocus}>
+ Cancel
+ </span>
+ <form
+ role='form'
+ className='search__form relative-div'
+ onSubmit={this.handleSubmit}>
+ <span className='glyphicon glyphicon-search sidebar__search-icon'></span>
<input
- type="text"
- ref="search"
- className="form-control search-bar"
- placeholder="Search"
- value={this.state.search_term}
+ type='text'
+ ref='search'
+ className='form-control search-bar'
+ placeholder='Search'
+ value={this.state.searchTerm}
onFocus={this.handleUserFocus}
onChange={this.handleUserInput} />
- {this.state.isSearching ? <span className={"glyphicon glyphicon-refresh glyphicon-refresh-animate"}></span> : null}
+ {isSearching}
</form>
</div>
);
}
-});
+}
diff --git a/web/react/components/setting_item_max.jsx b/web/react/components/setting_item_max.jsx
index b978cdb0c..e67e458af 100644
--- a/web/react/components/setting_item_max.jsx
+++ b/web/react/components/setting_item_max.jsx
@@ -1,33 +1,68 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-module.exports = React.createClass({
- render: function() {
- var clientError = this.props.client_error ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.client_error }</label></div> : null;
- var server_error = this.props.server_error ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.server_error }</label></div> : null;
- var extraInfo = this.props.extraInfo ? this.props.extraInfo : null;
+export default class SettingItemMax extends React.Component {
+ render() {
+ var clientError = null;
+ if (this.props.client_error) {
+ clientError = (<div className='form-group'><label className='col-sm-12 has-error'>{this.props.client_error}</label></div>);
+ }
+
+ var serverError = null;
+ if (this.props.server_error) {
+ serverError = (<div className='form-group'><label className='col-sm-12 has-error'>{this.props.server_error}</label></div>);
+ }
+
+ var extraInfo = null;
+ if (this.props.extraInfo) {
+ extraInfo = this.props.extraInfo;
+ }
+
+ var submit = '';
+ if (this.props.submit) {
+ submit = (<a
+ className='btn btn-sm btn-primary'
+ href='#'
+ onClick={this.props.submit}>
+ Submit</a>);
+ }
var inputs = this.props.inputs;
return (
- <ul className="section-max form-horizontal">
- <li className="col-sm-12 section-title">{this.props.title}</li>
- <li className="col-sm-9 col-sm-offset-3">
- <ul className="setting-list">
- <li className="setting-list-item">
+ <ul className='section-max form-horizontal'>
+ <li className='col-sm-12 section-title'>{this.props.title}</li>
+ <li className='col-sm-9 col-sm-offset-3'>
+ <ul className='setting-list'>
+ <li className='setting-list-item'>
{inputs}
{extraInfo}
</li>
- <li className="setting-list-item">
+ <li className='setting-list-item'>
<hr />
- { server_error }
- { clientError }
- { this.props.submit ? <a className="btn btn-sm btn-primary" href="#" onClick={this.props.submit}>Submit</a> : "" }
- <a className="btn btn-sm theme" href="#" onClick={this.props.updateSection}>Cancel</a>
+ {serverError}
+ {clientError}
+ {submit}
+ <a
+ className='btn btn-sm theme'
+ href='#'
+ onClick={this.props.updateSection} >
+ Cancel
+ </a>
</li>
</ul>
</li>
</ul>
);
}
-});
+}
+
+SettingItemMax.propTypes = {
+ inputs: React.PropTypes.array,
+ client_error: React.PropTypes.string,
+ server_error: React.PropTypes.string,
+ extraInfo: React.PropTypes.element,
+ updateSection: React.PropTypes.func,
+ submit: React.PropTypes.func,
+ title: React.PropTypes.string
+};
diff --git a/web/react/components/setting_item_min.jsx b/web/react/components/setting_item_min.jsx
index 3c87e416e..098729a4f 100644
--- a/web/react/components/setting_item_min.jsx
+++ b/web/react/components/setting_item_min.jsx
@@ -1,19 +1,23 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-module.exports = React.createClass({
- displayName: 'SettingsItemMin',
- propTypes: {
- title: React.PropTypes.string,
- disableOpen: React.PropTypes.bool,
- updateSection: React.PropTypes.func,
- describe: React.PropTypes.string
- },
- render: function() {
- var editButton = '';
+export default class SettingItemMin extends React.Component {
+ render() {
+ let editButton = null;
if (!this.props.disableOpen) {
- editButton = <li className='col-sm-2 section-edit'><a className='section-edit theme' href='#' onClick={this.props.updateSection}>Edit</a></li>;
+ editButton = (
+ <li className='col-sm-2 section-edit'>
+ <a
+ className='section-edit theme'
+ href='#'
+ onClick={this.props.updateSection}
+ >
+ Edit
+ </a>
+ </li>
+ );
}
+
return (
<ul className='section-min'>
<li className='col-sm-10 section-title'>{this.props.title}</li>
@@ -22,4 +26,11 @@ module.exports = React.createClass({
</ul>
);
}
-});
+}
+
+SettingItemMin.propTypes = {
+ title: React.PropTypes.string,
+ disableOpen: React.PropTypes.bool,
+ updateSection: React.PropTypes.func,
+ describe: React.PropTypes.string
+};
diff --git a/web/react/components/settings_sidebar.jsx b/web/react/components/settings_sidebar.jsx
index d8091ec28..e5cbd6e92 100644
--- a/web/react/components/settings_sidebar.jsx
+++ b/web/react/components/settings_sidebar.jsx
@@ -1,24 +1,56 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
+export default class SettingsSidebar extends React.Component {
+ constructor(props) {
+ super(props);
-module.exports = React.createClass({
- displayName:'SettingsSidebar',
- updateTab: function(tab) {
- this.props.updateTab(tab);
+ this.handleClick = this.handleClick.bind(this);
+ }
+ handleClick(tab) {
+ this.props.updateTab(tab.name);
$('.settings-modal').addClass('display--content');
- },
- render: function() {
- var self = this;
+ }
+ render() {
+ let tabList = this.props.tabs.map(function makeTab(tab) {
+ let key = `${tab.name}_li`;
+ let className = '';
+ if (this.props.activeTab === tab.name) {
+ className = 'active';
+ }
+
+ return (
+ <li
+ key={key}
+ className={className}
+ >
+ <a
+ href='#'
+ onClick={this.handleClick.bind(null, tab)}
+ >
+ <i className={tab.icon} />
+ {tab.uiName}
+ </a>
+ </li>
+ );
+ }.bind(this));
+
return (
- <div className="">
- <ul className="nav nav-pills nav-stacked">
- {this.props.tabs.map(function(tab) {
- return <li key={tab.name+'_li'} className={self.props.activeTab == tab.name ? 'active' : ''}><a key={tab.name + '_a'} href="#" onClick={function(){self.updateTab(tab.name);}}><i key={tab.name+'_i'} className={tab.icon}></i>{tab.uiName}</a></li>
- })}
+ <div>
+ <ul className='nav nav-pills nav-stacked'>
+ {tabList}
</ul>
</div>
);
}
-});
+}
+
+SettingsSidebar.propTypes = {
+ tabs: React.PropTypes.arrayOf(React.PropTypes.shape({
+ name: React.PropTypes.string.isRequired,
+ uiName: React.PropTypes.string.isRequired,
+ icon: React.PropTypes.string.isRequired
+ })).isRequired,
+ activeTab: React.PropTypes.string,
+ updateTab: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 5b74165f3..ef23f5bc2 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -8,127 +8,137 @@ var SocketStore = require('../stores/socket_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
var SidebarHeader = require('./sidebar_header.jsx');
var SearchBox = require('./search_bar.jsx');
var Constants = require('../utils/constants.jsx');
-function getStateFromStores() {
- var members = ChannelStore.getAllMembers();
- var teamMemberMap = UserStore.getActiveOnlyProfiles();
- var currentId = ChannelStore.getCurrentId();
+export default class Sidebar extends React.Component {
+ constructor(props) {
+ super(props);
- var teammates = [];
- for (var id in teamMemberMap) {
- if (id === UserStore.getCurrentId()) {
- continue;
- }
- teammates.push(teamMemberMap[id]);
- }
+ this.badgesActive = false;
+ this.firstUnreadChannel = null;
+ this.lastUnreadChannel = null;
- // Create lists of all read and unread direct channels
- var showDirectChannels = [];
- var readDirectChannels = [];
- for (var i = 0; i < teammates.length; i++) {
- var teammate = teammates[i];
+ this.onChange = this.onChange.bind(this);
+ this.onScroll = this.onScroll.bind(this);
+ this.onResize = this.onResize.bind(this);
+ this.updateUnreadIndicators = this.updateUnreadIndicators.bind(this);
+ this.createChannelElement = this.createChannelElement.bind(this);
- if (teammate.id === UserStore.getCurrentId()) {
- continue;
+ this.state = this.getStateFromStores();
+ this.state.loadingDMChannel = -1;
+ }
+ getStateFromStores() {
+ var members = ChannelStore.getAllMembers();
+ var teamMemberMap = UserStore.getActiveOnlyProfiles();
+ var currentId = ChannelStore.getCurrentId();
+
+ var teammates = [];
+ for (var id in teamMemberMap) {
+ if (id === UserStore.getCurrentId()) {
+ continue;
+ }
+ teammates.push(teamMemberMap[id]);
}
- var channelName = '';
- if (teammate.id > UserStore.getCurrentId()) {
- channelName = UserStore.getCurrentId() + '__' + teammate.id;
- } else {
- channelName = teammate.id + '__' + UserStore.getCurrentId();
- }
+ // Create lists of all read and unread direct channels
+ var showDirectChannels = [];
+ var readDirectChannels = [];
+ for (var i = 0; i < teammates.length; i++) {
+ var teammate = teammates[i];
- var channel = ChannelStore.getByName(channelName);
+ if (teammate.id === UserStore.getCurrentId()) {
+ continue;
+ }
- if (channel != null) {
- channel.display_name = teammate.username;
- channel.teammate_username = teammate.username;
+ var channelName = '';
+ if (teammate.id > UserStore.getCurrentId()) {
+ channelName = UserStore.getCurrentId() + '__' + teammate.id;
+ } else {
+ channelName = teammate.id + '__' + UserStore.getCurrentId();
+ }
- channel.status = UserStore.getStatus(teammate.id);
+ var channel = ChannelStore.getByName(channelName);
- var channelMember = members[channel.id];
- var msgCount = channel.total_msg_count - channelMember.msg_count;
- if (msgCount > 0) {
- showDirectChannels.push(channel);
- } else if (currentId === channel.id) {
- showDirectChannels.push(channel);
+ if (channel != null) {
+ channel.display_name = teammate.username;
+ channel.teammate_username = teammate.username;
+
+ channel.status = UserStore.getStatus(teammate.id);
+
+ var channelMember = members[channel.id];
+ var msgCount = channel.total_msg_count - channelMember.msg_count;
+ if (msgCount > 0) {
+ showDirectChannels.push(channel);
+ } else if (currentId === channel.id) {
+ showDirectChannels.push(channel);
+ } else {
+ readDirectChannels.push(channel);
+ }
} else {
- readDirectChannels.push(channel);
+ var tempChannel = {};
+ tempChannel.fake = true;
+ tempChannel.name = channelName;
+ tempChannel.display_name = teammate.username;
+ tempChannel.teammate_username = teammate.username;
+ tempChannel.status = UserStore.getStatus(teammate.id);
+ tempChannel.last_post_at = 0;
+ tempChannel.total_msg_count = 0;
+ tempChannel.type = 'D';
+ readDirectChannels.push(tempChannel);
}
- } else {
- var tempChannel = {};
- tempChannel.fake = true;
- tempChannel.name = channelName;
- tempChannel.display_name = teammate.username;
- tempChannel.teammate_username = teammate.username;
- tempChannel.status = UserStore.getStatus(teammate.id);
- tempChannel.last_post_at = 0;
- tempChannel.total_msg_count = 0;
- tempChannel.type = 'D';
- readDirectChannels.push(tempChannel);
}
- }
- // If we don't have MAX_DMS unread channels, sort the read list by last_post_at
- if (showDirectChannels.length < Constants.MAX_DMS) {
- readDirectChannels.sort(function sortByLastPost(a, b) {
- // sort by last_post_at first
- if (a.last_post_at > b.last_post_at) {
- return -1;
- }
- if (a.last_post_at < b.last_post_at) {
- return 1;
- }
+ // If we don't have MAX_DMS unread channels, sort the read list by last_post_at
+ if (showDirectChannels.length < Constants.MAX_DMS) {
+ readDirectChannels.sort(function sortByLastPost(a, b) {
+ // sort by last_post_at first
+ if (a.last_post_at > b.last_post_at) {
+ return -1;
+ }
+ if (a.last_post_at < b.last_post_at) {
+ return 1;
+ }
- // if last_post_at is equal, sort by name
- if (a.display_name < b.display_name) {
- return -1;
- }
- if (a.display_name > b.display_name) {
- return 1;
+ // if last_post_at is equal, sort by name
+ if (a.display_name < b.display_name) {
+ return -1;
+ }
+ if (a.display_name > b.display_name) {
+ return 1;
+ }
+ return 0;
+ });
+
+ var index = 0;
+ while (showDirectChannels.length < Constants.MAX_DMS && index < readDirectChannels.length) {
+ showDirectChannels.push(readDirectChannels[index]);
+ index++;
}
- return 0;
- });
+ readDirectChannels = readDirectChannels.slice(index);
- var index = 0;
- while (showDirectChannels.length < Constants.MAX_DMS && index < readDirectChannels.length) {
- showDirectChannels.push(readDirectChannels[index]);
- index++;
+ showDirectChannels.sort(function directSort(a, b) {
+ if (a.display_name < b.display_name) {
+ return -1;
+ }
+ if (a.display_name > b.display_name) {
+ return 1;
+ }
+ return 0;
+ });
}
- readDirectChannels = readDirectChannels.slice(index);
- showDirectChannels.sort(function directSort(a, b) {
- if (a.display_name < b.display_name) {
- return -1;
- }
- if (a.display_name > b.display_name) {
- return 1;
- }
- return 0;
- });
+ return {
+ activeId: currentId,
+ channels: ChannelStore.getAll(),
+ members: members,
+ showDirectChannels: showDirectChannels,
+ hideDirectChannels: readDirectChannels
+ };
}
-
- return {
- activeId: currentId,
- channels: ChannelStore.getAll(),
- members: members,
- showDirectChannels: showDirectChannels,
- hideDirectChannels: readDirectChannels
- };
-}
-
-module.exports = React.createClass({
- displayName: 'Sidebar',
- propTypes: {
- teamType: React.PropTypes.string,
- teamDisplayName: React.PropTypes.string
- },
- componentDidMount: function() {
+ componentDidMount() {
ChannelStore.addChangeListener(this.onChange);
UserStore.addChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onChange);
@@ -140,12 +150,12 @@ module.exports = React.createClass({
this.updateUnreadIndicators();
$(window).on('resize', this.onResize);
- },
- componentDidUpdate: function() {
+ }
+ componentDidUpdate() {
this.updateTitle();
this.updateUnreadIndicators();
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
$(window).off('resize', this.onResize);
ChannelStore.removeChangeListener(this.onChange);
@@ -153,14 +163,14 @@ module.exports = React.createClass({
UserStore.removeStatusesChangeListener(this.onChange);
TeamStore.removeChangeListener(this.onChange);
SocketStore.removeChangeListener(this.onSocketChange);
- },
- onChange: function() {
- var newState = getStateFromStores();
- if (!utils.areStatesEqual(newState, this.state)) {
+ }
+ onChange() {
+ var newState = this.getStateFromStores();
+ if (!Utils.areStatesEqual(newState, this.state)) {
this.setState(newState);
}
- },
- onSocketChange: function(msg) {
+ }
+ onSocketChange(msg) {
if (msg.action === 'posted') {
if (ChannelStore.getCurrentId() === msg.channel_id) {
if (window.isActive) {
@@ -208,17 +218,17 @@ module.exports = React.createClass({
if (notifyText.length === 0) {
if (msgProps.image) {
- utils.notifyMe(title, username + ' uploaded an image', channel);
+ Utils.notifyMe(title, username + ' uploaded an image', channel);
} else if (msgProps.otherFile) {
- utils.notifyMe(title, username + ' uploaded a file', channel);
+ Utils.notifyMe(title, username + ' uploaded a file', channel);
} else {
- utils.notifyMe(title, username + ' did something new', channel);
+ Utils.notifyMe(title, username + ' did something new', channel);
}
} else {
- utils.notifyMe(title, username + ' wrote: ' + notifyText, channel);
+ Utils.notifyMe(title, username + ' wrote: ' + notifyText, channel);
}
if (!user.notify_props || user.notify_props.desktop_sound === 'true') {
- utils.ding();
+ Utils.ding();
}
}
} else if (msg.action === 'viewed') {
@@ -243,186 +253,196 @@ module.exports = React.createClass({
}
}
}
- },
- updateTitle: function() {
+ }
+ updateTitle() {
var channel = ChannelStore.getCurrent();
if (channel) {
if (channel.type === 'D') {
- var teammateUsername = utils.getDirectTeammate(channel.id).username;
+ var teammateUsername = Utils.getDirectTeammate(channel.id).username;
document.title = teammateUsername + ' ' + document.title.substring(document.title.lastIndexOf('-'));
} else {
document.title = channel.display_name + ' ' + document.title.substring(document.title.lastIndexOf('-'));
}
}
- },
- onScroll: function() {
+ }
+ onScroll() {
this.updateUnreadIndicators();
- },
- onResize: function() {
+ }
+ onResize() {
this.updateUnreadIndicators();
- },
- updateUnreadIndicators: function() {
- var container = $(this.refs.container.getDOMNode());
+ }
+ updateUnreadIndicators() {
+ var container = $(React.findDOMNode(this.refs.container));
if (this.firstUnreadChannel) {
- var firstUnreadElement = $(this.refs[this.firstUnreadChannel].getDOMNode());
+ var firstUnreadElement = $(React.findDOMNode(this.refs[this.firstUnreadChannel]));
if (firstUnreadElement.position().top + firstUnreadElement.height() < 0) {
- $(this.refs.topUnreadIndicator.getDOMNode()).css('display', 'initial');
+ $(React.findDOMNode(this.refs.topUnreadIndicator)).css('display', 'initial');
} else {
- $(this.refs.topUnreadIndicator.getDOMNode()).css('display', 'none');
+ $(React.findDOMNode(this.refs.topUnreadIndicator)).css('display', 'none');
}
}
if (this.lastUnreadChannel) {
- var lastUnreadElement = $(this.refs[this.lastUnreadChannel].getDOMNode());
+ var lastUnreadElement = $(React.findDOMNode(this.refs[this.lastUnreadChannel]));
if (lastUnreadElement.position().top > container.height()) {
- $(this.refs.bottomUnreadIndicator.getDOMNode()).css('display', 'initial');
+ $(React.findDOMNode(this.refs.bottomUnreadIndicator)).css('display', 'initial');
} else {
- $(this.refs.bottomUnreadIndicator.getDOMNode()).css('display', 'none');
+ $(React.findDOMNode(this.refs.bottomUnreadIndicator)).css('display', 'none');
}
}
- },
- getInitialState: function() {
- var newState = getStateFromStores();
- newState.loadingDMChannel = -1;
-
- return newState;
- },
- render: function() {
+ }
+ createChannelElement(channel, index) {
var members = this.state.members;
var activeId = this.state.activeId;
- var badgesActive = false;
+ var channelMember = members[channel.id];
+ var msgCount;
- // keep track of the first and last unread channels so we can use them to set the unread indicators
- var self = this;
- this.firstUnreadChannel = null;
- this.lastUnreadChannel = null;
-
- function createChannelElement(channel, index) {
- var channelMember = members[channel.id];
- var msgCount;
-
- var linkClass = '';
- if (channel.id === activeId) {
- linkClass = 'active';
- }
+ var linkClass = '';
+ if (channel.id === activeId) {
+ linkClass = 'active';
+ }
- var unread = false;
- if (channelMember) {
- msgCount = channel.total_msg_count - channelMember.msg_count;
- unread = (msgCount > 0 && channelMember.notify_level !== 'quiet') || channelMember.mention_count > 0;
- }
+ var unread = false;
+ if (channelMember) {
+ msgCount = channel.total_msg_count - channelMember.msg_count;
+ unread = (msgCount > 0 && channelMember.notify_level !== 'quiet') || channelMember.mention_count > 0;
+ }
- var titleClass = '';
- if (unread) {
- titleClass = 'unread-title';
+ var titleClass = '';
+ if (unread) {
+ titleClass = 'unread-title';
- if (!self.firstUnreadChannel) {
- self.firstUnreadChannel = channel.name;
- }
- self.lastUnreadChannel = channel.name;
- }
-
- var badge = null;
- if (channelMember) {
- if (channel.type === 'D') {
- // direct message channels show badges for any number of unread posts
- msgCount = channel.total_msg_count - channelMember.msg_count;
- if (msgCount > 0) {
- badge = <span className='badge pull-right small'>{msgCount}</span>;
- badgesActive = true;
- }
- } else if (channelMember.mention_count > 0) {
- // public and private channels only show badges for mentions
- badge = <span className='badge pull-right small'>{channelMember.mention_count}</span>;
- badgesActive = true;
- }
- } else if (self.state.loadingDMChannel === index && channel.type === 'D') {
- badge = <img className='channel-loading-gif pull-right' src='/static/images/load.gif'/>;
+ if (!this.firstUnreadChannel) {
+ this.firstUnreadChannel = channel.name;
}
+ this.lastUnreadChannel = channel.name;
+ }
- // set up status icon for direct message channels
- var status = null;
+ var badge = null;
+ if (channelMember) {
if (channel.type === 'D') {
- var statusIcon = '';
- if (channel.status === 'online') {
- statusIcon = Constants.ONLINE_ICON_SVG;
- } else if (channel.status === 'away') {
- statusIcon = Constants.ONLINE_ICON_SVG;
- } else {
- statusIcon = Constants.OFFLINE_ICON_SVG;
+ // direct message channels show badges for any number of unread posts
+ msgCount = channel.total_msg_count - channelMember.msg_count;
+ if (msgCount > 0) {
+ badge = <span className='badge pull-right small'>{msgCount}</span>;
+ this.badgesActive = true;
}
- status = <span className='status' dangerouslySetInnerHTML={{__html: statusIcon}} />;
+ } else if (channelMember.mention_count > 0) {
+ // public and private channels only show badges for mentions
+ badge = <span className='badge pull-right small'>{channelMember.mention_count}</span>;
+ this.badgesActive = true;
}
+ } else if (this.state.loadingDMChannel === index && channel.type === 'D') {
+ badge = (
+ <img
+ className='channel-loading-gif pull-right'
+ src='/static/images/load.gif'
+ />
+ );
+ }
- // set up click handler to switch channels (or create a new channel for non-existant ones)
- var handleClick = null;
- var href = '#';
- var teamURL = TeamStore.getCurrentTeamUrl();
+ // set up status icon for direct message channels
+ var status = null;
+ if (channel.type === 'D') {
+ var statusIcon = '';
+ if (channel.status === 'online') {
+ statusIcon = Constants.ONLINE_ICON_SVG;
+ } else if (channel.status === 'away') {
+ statusIcon = Constants.ONLINE_ICON_SVG;
+ } else {
+ statusIcon = Constants.OFFLINE_ICON_SVG;
+ }
+ status = (
+ <span
+ className='status'
+ dangerouslySetInnerHTML={{__html: statusIcon}}
+ />
+ );
+ }
- if (!channel.fake) {
+ // set up click handler to switch channels (or create a new channel for non-existant ones)
+ var handleClick = null;
+ var href = '#';
+ var teamURL = TeamStore.getCurrentTeamUrl();
+
+ if (!channel.fake) {
+ handleClick = function clickHandler(e) {
+ e.preventDefault();
+ Utils.switchChannel(channel);
+ };
+ } else if (channel.fake && teamURL) {
+ // It's a direct message channel that doesn't exist yet so let's create it now
+ var otherUserId = Utils.getUserIdFromChannelName(channel);
+
+ if (this.state.loadingDMChannel === -1) {
handleClick = function clickHandler(e) {
e.preventDefault();
- utils.switchChannel(channel);
+ this.setState({loadingDMChannel: index});
+
+ Client.createDirectChannel(channel, otherUserId,
+ function success(data) {
+ this.setState({loadingDMChannel: -1});
+ AsyncClient.getChannel(data.id);
+ Utils.switchChannel(data);
+ },
+ function error() {
+ this.setState({loadingDMChannel: -1});
+ window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
+ }
+ );
};
- } else if (channel.fake && teamURL) {
- // It's a direct message channel that doesn't exist yet so let's create it now
- var otherUserId = utils.getUserIdFromChannelName(channel);
-
- if (self.state.loadingDMChannel === -1) {
- handleClick = function clickHandler(e) {
- e.preventDefault();
- self.setState({loadingDMChannel: index});
-
- Client.createDirectChannel(channel, otherUserId,
- function success(data) {
- self.setState({loadingDMChannel: -1});
- AsyncClient.getChannel(data.id);
- utils.switchChannel(data);
- },
- function error() {
- self.setState({loadingDMChannel: -1});
- window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
- }
- );
- };
- }
}
-
- return (
- <li key={channel.name} ref={channel.name} className={linkClass}>
- <a className={'sidebar-channel ' + titleClass} href={href} onClick={handleClick}>
- {status}
- {channel.display_name}
- {badge}
- </a>
- </li>
- );
}
+ return (
+ <li
+ key={channel.name}
+ ref={channel.name}
+ className={linkClass}
+ >
+ <a
+ className={'sidebar-channel ' + titleClass}
+ href={href}
+ onClick={handleClick}
+ >
+ {status}
+ {channel.display_name}
+ {badge}
+ </a>
+ </li>
+ );
+ }
+ render() {
+ this.badgesActive = false;
+
+ // keep track of the first and last unread channels so we can use them to set the unread indicators
+ this.firstUnreadChannel = null;
+ this.lastUnreadChannel = null;
+
// create elements for all 3 types of channels
var channelItems = this.state.channels.filter(
function filterPublicChannels(channel) {
return channel.type === 'O';
}
- ).map(createChannelElement);
+ ).map(this.createChannelElement);
var privateChannelItems = this.state.channels.filter(
function filterPrivateChannels(channel) {
return channel.type === 'P';
}
- ).map(createChannelElement);
+ ).map(this.createChannelElement);
- var directMessageItems = this.state.showDirectChannels.map(createChannelElement);
+ var directMessageItems = this.state.showDirectChannels.map(this.createChannelElement);
// update the favicon to show if there are any notifications
var link = document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.id = 'favicon';
- if (badgesActive) {
+ if (this.badgesActive) {
link.href = '/static/images/redfavicon.ico';
} else {
link.href = '/static/images/favicon.ico';
@@ -438,7 +458,13 @@ module.exports = React.createClass({
if (this.state.hideDirectChannels.length > 0) {
directMessageMore = (
<li>
- <a href='#' data-toggle='modal' className='nav-more' data-target='#more_direct_channels' data-channels={JSON.stringify(this.state.hideDirectChannels)}>
+ <a
+ href='#'
+ data-toggle='modal'
+ className='nav-more'
+ data-target='#more_direct_channels'
+ data-channels={JSON.stringify(this.state.hideDirectChannels)}
+ >
{'More (' + this.state.hideDirectChannels.length + ')'}
</a>
</li>
@@ -447,21 +473,76 @@ module.exports = React.createClass({
return (
<div>
- <SidebarHeader teamDisplayName={this.props.teamDisplayName} teamType={this.props.teamType} />
+ <SidebarHeader
+ teamDisplayName={this.props.teamDisplayName}
+ teamType={this.props.teamType}
+ />
<SearchBox />
- <div ref='topUnreadIndicator' className='nav-pills__unread-indicator nav-pills__unread-indicator-top' style={{display: 'none'}}>Unread post(s) above</div>
- <div ref='bottomUnreadIndicator' className='nav-pills__unread-indicator nav-pills__unread-indicator-bottom' style={{display: 'none'}}>Unread post(s) below</div>
+ <div
+ ref='topUnreadIndicator'
+ className='nav-pills__unread-indicator nav-pills__unread-indicator-top'
+ style={{display: 'none'}}
+ >
+ Unread post(s) above
+ </div>
+ <div
+ ref='bottomUnreadIndicator'
+ className='nav-pills__unread-indicator nav-pills__unread-indicator-bottom'
+ style={{display: 'none'}}
+ >
+ Unread post(s) below
+ </div>
- <div ref='container' className='nav-pills__container' onScroll={this.onScroll}>
+ <div
+ ref='container'
+ className='nav-pills__container'
+ onScroll={this.onScroll}
+ >
<ul className='nav nav-pills nav-stacked'>
- <li><h4>Channels<a className='add-channel-btn' href='#' data-toggle='modal' data-target='#new_channel' data-channeltype='O'>+</a></h4></li>
+ <li>
+ <h4>
+ Channels
+ <a
+ className='add-channel-btn'
+ href='#'
+ data-toggle='modal'
+ data-target='#new_channel'
+ data-channeltype='O'
+ >
+ +
+ </a>
+ </h4>
+ </li>
{channelItems}
- <li><a href='#' data-toggle='modal' className='nav-more' data-target='#more_channels' data-channeltype='O'>More...</a></li>
+ <li>
+ <a
+ href='#'
+ data-toggle='modal'
+ className='nav-more'
+ data-target='#more_channels'
+ data-channeltype='O'
+ >
+ More...
+ </a>
+ </li>
</ul>
<ul className='nav nav-pills nav-stacked'>
- <li><h4>Private Groups<a className='add-channel-btn' href='#' data-toggle='modal' data-target='#new_channel' data-channeltype='P'>+</a></h4></li>
+ <li>
+ <h4>
+ Private Groups
+ <a
+ className='add-channel-btn'
+ href='#'
+ data-toggle='modal'
+ data-target='#new_channel'
+ data-channeltype='P'
+ >
+ +
+ </a>
+ </h4>
+ </li>
{privateChannelItems}
</ul>
<ul className='nav nav-pills nav-stacked'>
@@ -473,4 +554,13 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
+
+Sidebar.defaultProps = {
+ teamType: '',
+ teamDisplayName: ''
+};
+Sidebar.propTypes = {
+ teamType: React.PropTypes.string,
+ teamDisplayName: React.PropTypes.string
+};
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index af65b7e1d..6e219cc6c 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -1,133 +1,25 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
+var NavbarDropdown = require('./navbar_dropdown.jsx');
var UserStore = require('../stores/user_store.jsx');
-var TeamStore = require('../stores/team_store.jsx');
-var Constants = require('../utils/constants.jsx');
+export default class SidebarHeader extends React.Component {
+ constructor(props) {
+ super(props);
-function getStateFromStores() {
- return {teams: UserStore.getTeams(), currentTeam: TeamStore.getCurrent()};
-}
-
-var NavbarDropdown = React.createClass({
- handleLogoutClick: function(e) {
- e.preventDefault();
- client.logout();
- },
- blockToggle: false,
- componentDidMount: function() {
- UserStore.addTeamsChangeListener(this.onListenerChange);
- TeamStore.addChangeListener(this.onListenerChange);
-
- var self = this;
- $(this.refs.dropdown.getDOMNode()).on('hide.bs.dropdown', function() {
- self.blockToggle = true;
- setTimeout(function() {
- self.blockToggle = false;
- }, 100);
- });
- },
- componentWillUnmount: function() {
- UserStore.removeTeamsChangeListener(this.onListenerChange);
- TeamStore.removeChangeListener(this.onListenerChange);
-
- $(this.refs.dropdown.getDOMNode()).off('hide.bs.dropdown');
- },
- onListenerChange: function() {
- if (this.isMounted()) {
- var newState = getStateFromStores();
- if (!utils.areStatesEqual(newState, this.state)) {
- this.setState(newState);
- }
- }
- },
- getInitialState: function() {
- return getStateFromStores();
- },
- render: function() {
- var teamLink = '';
- var inviteLink = '';
- var manageLink = '';
- var currentUser = UserStore.getCurrentUser();
- var isAdmin = false;
- var teamSettings = null;
-
- if (currentUser != null) {
- isAdmin = currentUser.roles.indexOf('admin') > -1;
-
- inviteLink = (<li> <a href='#' data-toggle='modal' data-target='#invite_member'>Invite New Member</a> </li>);
-
- if (this.props.teamType === 'O') {
- teamLink = (
- <li>
- <a href='#' data-toggle='modal' data-target='#get_link' data-title='Team Invite' data-value={utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id}>Get Team Invite Link</a>
- </li>
- );
- }
- }
-
- if (isAdmin) {
- manageLink = (<li> <a href='#' data-toggle='modal' data-target='#team_members'>Manage Team</a> </li>);
- teamSettings = (<li> <a href='#' data-toggle='modal' data-target='#team_settings'>Team Settings</a> </li>);
- }
+ this.toggleDropdown = this.toggleDropdown.bind(this);
- var teams = [];
-
- teams.push(<li className='divider' key='div'></li>);
- if (this.state.teams.length > 1 && this.state.currentTeam) {
- var curTeamName = this.state.currentTeam.name;
- this.state.teams.forEach(function(teamName) {
- if (teamName !== curTeamName) {
- teams.push(<li key={teamName}><a href={utils.getWindowLocationOrigin() + '/' + teamName}>Switch to {teamName}</a></li>);
- }
- });
- }
- teams.push(<li key='newTeam_li'><a key='newTeam_a' target="_blank" href={utils.getWindowLocationOrigin() + '/signup_team' }>Create a New Team</a></li>);
-
- return (
- <ul className='nav navbar-nav navbar-right'>
- <li ref='dropdown' className='dropdown'>
- <a href='#' className='dropdown-toggle' data-toggle='dropdown' role='button' aria-expanded='false'>
- <span className='dropdown__icon' dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
- </a>
- <ul className='dropdown-menu' role='menu'>
- <li><a href='#' data-toggle='modal' data-target='#user_settings'>Account Settings</a></li>
- {teamSettings}
- {inviteLink}
- {teamLink}
- {manageLink}
- <li><a href='#' onClick={this.handleLogoutClick}>Logout</a></li>
- {teams}
- <li className='divider'></li>
- <li><a target='_blank' href={config.HelpLink}>Help</a></li>
- <li><a target='_blank' href={config.ReportProblemLink}>Report a Problem</a></li>
- </ul>
- </li>
- </ul>
- );
+ this.state = {};
}
-});
-
-module.exports = React.createClass({
- displayName: 'SidebarHeader',
- getDefaultProps: function() {
- return {
- teamDisplayName: config.SiteName
- };
- },
-
- toggleDropdown: function() {
+ toggleDropdown() {
if (this.refs.dropdown.blockToggle) {
this.refs.dropdown.blockToggle = false;
return;
}
$('.team__header').find('.dropdown-toggle').dropdown('toggle');
- },
-
- render: function() {
+ }
+ render() {
var me = UserStore.getCurrentUser();
var profilePicture = null;
@@ -136,20 +28,38 @@ module.exports = React.createClass({
}
if (me.last_picture_update) {
- profilePicture = (<img className='user__picture' src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at} />);
+ profilePicture = (<img
+ className='user__picture'
+ src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at}
+ />);
}
return (
<div className='team__header theme'>
- <a href='#' onClick={this.toggleDropdown}>
+ <a
+ href='#'
+ onClick={this.toggleDropdown}
+ >
{profilePicture}
<div className='header__info'>
<div className='user__name'>{'@' + me.username}</div>
- <div className='team__name'>{this.props.teamDisplayName }</div>
+ <div className='team__name'>{this.props.teamDisplayName}</div>
</div>
</a>
- <NavbarDropdown ref='dropdown' teamType={this.props.teamType} />
+ <NavbarDropdown
+ ref='dropdown'
+ teamType={this.props.teamType}
+ />
</div>
);
}
-});
+}
+
+SidebarHeader.defaultProps = {
+ teamDisplayName: config.SiteName,
+ teamType: ''
+};
+SidebarHeader.propTypes = {
+ teamDisplayName: React.PropTypes.string,
+ teamType: React.PropTypes.string
+};
diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx
index df75e3adf..9aeda6626 100644
--- a/web/react/components/sidebar_right.jsx
+++ b/web/react/components/sidebar_right.jsx
@@ -4,47 +4,49 @@
var SearchResults = require('./search_results.jsx');
var RhsThread = require('./rhs_thread.jsx');
var PostStore = require('../stores/post_store.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
-function getStateFromStores(from_search) {
- return { search_visible: PostStore.getSearchResults() != null, post_right_visible: PostStore.getSelectedPost() != null, is_mention_search: PostStore.getIsMentionSearch() };
+function getStateFromStores() {
+ return {search_visible: PostStore.getSearchResults() != null, post_right_visible: PostStore.getSelectedPost() != null, is_mention_search: PostStore.getIsMentionSearch()};
}
-module.exports = React.createClass({
- componentDidMount: function() {
- PostStore.addSearchChangeListener(this._onSearchChange);
- PostStore.addSelectedPostChangeListener(this._onSelectedChange);
- },
- componentWillUnmount: function() {
- PostStore.removeSearchChangeListener(this._onSearchChange);
- PostStore.removeSelectedPostChangeListener(this._onSelectedChange);
- },
- _onSelectedChange: function(from_search) {
- if (this.isMounted()) {
- var newState = getStateFromStores(from_search);
- newState.from_search = from_search;
- if (!utils.areStatesEqual(newState, this.state)) {
- this.setState(newState);
- }
+export default class SidebarRight extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onSelectedChange = this.onSelectedChange.bind(this);
+ this.onSearchChange = this.onSearchChange.bind(this);
+ this.resize = this.resize.bind(this);
+
+ this.state = getStateFromStores();
+ }
+ componentDidMount() {
+ PostStore.addSearchChangeListener(this.onSearchChange);
+ PostStore.addSelectedPostChangeListener(this.onSelectedChange);
+ }
+ componentWillUnmount() {
+ PostStore.removeSearchChangeListener(this.onSearchChange);
+ PostStore.removeSelectedPostChangeListener(this.onSelectedChange);
+ }
+ onSelectedChange(fromSearch) {
+ var newState = getStateFromStores(fromSearch);
+ newState.from_search = fromSearch;
+ if (!Utils.areStatesEqual(newState, this.state)) {
+ this.setState(newState);
}
- },
- _onSearchChange: function() {
- if (this.isMounted()) {
- var newState = getStateFromStores();
- if (!utils.areStatesEqual(newState, this.state)) {
- this.setState(newState);
- }
+ }
+ onSearchChange() {
+ var newState = getStateFromStores();
+ if (!Utils.areStatesEqual(newState, this.state)) {
+ this.setState(newState);
}
- },
- resize: function() {
+ }
+ resize() {
var postHolder = $('.post-list-holder-by-time');
postHolder[0].scrollTop = postHolder[0].scrollHeight - 224;
- },
- getInitialState: function() {
- return getStateFromStores();
- },
- render: function() {
- if (! (this.state.search_visible || this.state.post_right_visible)) {
+ }
+ render() {
+ if (!(this.state.search_visible || this.state.post_right_visible)) {
$('.inner__wrap').removeClass('move--left').removeClass('move--right');
$('.sidebar--right').removeClass('move--left');
this.resize();
@@ -58,25 +60,27 @@ module.exports = React.createClass({
$('.sidebar--right').addClass('move--left');
$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>');
this.resize();
- setTimeout(function(){
- $('.sidebar__overlay').fadeOut("200", function(){
+ setTimeout(function overlayTimer() {
+ $('.sidebar__overlay').fadeOut('200', function fadeOverlay() {
$(this).remove();
});
- },500)
+ }, 500);
- var content = "";
+ var content = '';
if (this.state.search_visible) {
content = <SearchResults isMentionSearch={this.state.is_mention_search} />;
- }
- else if (this.state.post_right_visible) {
- content = <RhsThread fromSearch={this.state.from_search} isMentionSearch={this.state.is_mention_search} />;
+ } else if (this.state.post_right_visible) {
+ content = (<RhsThread
+ fromSearch={this.state.from_search}
+ isMentionSearch={this.state.is_mention_search}
+ />);
}
return (
- <div className="sidebar-right-container">
- { content }
+ <div className='sidebar-right-container'>
+ {content}
</div>
);
}
-});
+}
diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx
index 13640b1e5..bf08e6508 100644
--- a/web/react/components/signup_team.jsx
+++ b/web/react/components/signup_team.jsx
@@ -1,10 +1,10 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var ChoosePage = require('./team_signup_choose_auth.jsx');
-var EmailSignUpPage = require('./team_signup_with_email.jsx');
-var SSOSignupPage = require('./team_signup_with_sso.jsx');
-var Constants = require('../utils/constants.jsx');
+const ChoosePage = require('./team_signup_choose_auth.jsx');
+const EmailSignUpPage = require('./team_signup_with_email.jsx');
+const SSOSignupPage = require('./team_signup_with_sso.jsx');
+const Constants = require('../utils/constants.jsx');
export default class TeamSignUp extends React.Component {
constructor(props) {
@@ -30,14 +30,14 @@ export default class TeamSignUp extends React.Component {
return <EmailSignUpPage />;
} else if (this.state.page === 'service' && this.state.service !== '') {
return <SSOSignupPage service={this.state.service} />;
- } else {
- return (
- <ChoosePage
- services={this.props.services}
- updatePage={this.updatePage}
- />
- );
}
+
+ return (
+ <ChoosePage
+ services={this.props.services}
+ updatePage={this.updatePage}
+ />
+ );
}
}
diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx
index 756aae638..1d45548da 100644
--- a/web/react/components/signup_team_complete.jsx
+++ b/web/react/components/signup_team_complete.jsx
@@ -10,69 +10,99 @@ var UsernamePage = require('./team_signup_username_page.jsx');
var PasswordPage = require('./team_signup_password_page.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
-module.exports = React.createClass({
- displayName: 'SignupTeamComplete',
- propTypes: {
- hash: React.PropTypes.string,
- email: React.PropTypes.string,
- data: React.PropTypes.string
- },
- updateParent: function(state, skipSet) {
+export default class SignupTeamComplete extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.updateParent = this.updateParent.bind(this);
+
+ var initialState = BrowserStore.getGlobalItem(props.hash);
+
+ if (!initialState) {
+ initialState = {};
+ initialState.wizard = 'welcome';
+ initialState.team = {};
+ initialState.team.email = this.props.email;
+ initialState.team.allowed_domains = '';
+ initialState.invites = [];
+ initialState.invites.push('');
+ initialState.invites.push('');
+ initialState.invites.push('');
+ initialState.user = {};
+ initialState.hash = this.props.hash;
+ initialState.data = this.props.data;
+ }
+
+ this.state = initialState;
+ }
+ updateParent(state, skipSet) {
BrowserStore.setGlobalItem(this.props.hash, state);
if (!skipSet) {
this.setState(state);
}
- },
- getInitialState: function() {
- var props = BrowserStore.getGlobalItem(this.props.hash);
-
- if (!props) {
- props = {};
- props.wizard = 'welcome';
- props.team = {};
- props.team.email = this.props.email;
- props.team.allowed_domains = '';
- props.invites = [];
- props.invites.push('');
- props.invites.push('');
- props.invites.push('');
- props.user = {};
- props.hash = this.props.hash;
- props.data = this.props.data;
- }
-
- return props;
- },
- render: function() {
+ }
+ render() {
if (this.state.wizard === 'welcome') {
- return <WelcomePage state={this.state} updateParent={this.updateParent} />;
+ return (<WelcomePage
+ state={this.state}
+ updateParent={this.updateParent}
+ />);
}
if (this.state.wizard === 'team_display_name') {
- return <TeamDisplayNamePage state={this.state} updateParent={this.updateParent} />;
+ return (<TeamDisplayNamePage
+ state={this.state}
+ updateParent={this.updateParent}
+ />);
}
if (this.state.wizard === 'team_url') {
- return <TeamURLPage state={this.state} updateParent={this.updateParent} />;
+ return (<TeamURLPage
+ state={this.state}
+ updateParent={this.updateParent}
+ />);
}
if (this.state.wizard === 'allowed_domains') {
- return <AllowedDomainsPage state={this.state} updateParent={this.updateParent} />;
+ return (<AllowedDomainsPage
+ state={this.state}
+ updateParent={this.updateParent}
+ />);
}
if (this.state.wizard === 'send_invites') {
- return <SendInivtesPage state={this.state} updateParent={this.updateParent} />;
+ return (<SendInivtesPage
+ state={this.state}
+ updateParent={this.updateParent}
+ />);
}
if (this.state.wizard === 'username') {
- return <UsernamePage state={this.state} updateParent={this.updateParent} />;
+ return (<UsernamePage
+ state={this.state}
+ updateParent={this.updateParent}
+ />);
}
if (this.state.wizard === 'password') {
- return <PasswordPage state={this.state} updateParent={this.updateParent} />;
+ return (<PasswordPage
+ state={this.state}
+ updateParent={this.updateParent}
+ />);
}
return (<div>You've already completed the signup process for this invitation or this invitation has expired.</div>);
}
-});
+}
+
+SignupTeamComplete.defaultProps = {
+ hash: '',
+ email: '',
+ data: ''
+};
+SignupTeamComplete.propTypes = {
+ hash: React.PropTypes.string,
+ email: React.PropTypes.string,
+ data: React.PropTypes.string
+};
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx
index 2080cc191..67e85d4de 100644
--- a/web/react/components/signup_user_complete.jsx
+++ b/web/react/components/signup_user_complete.jsx
@@ -7,11 +7,31 @@ var UserStore = require('../stores/user_store.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
var Constants = require('../utils/constants.jsx');
-module.exports = React.createClass({
- handleSubmit: function(e) {
+export default class SignupUserComplete extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+
+ var initialState = BrowserStore.getGlobalItem(this.props.hash);
+
+ if (!initialState) {
+ initialState = {};
+ initialState.wizard = 'welcome';
+ initialState.user = {};
+ initialState.user.team_id = this.props.teamId;
+ initialState.user.email = this.props.email;
+ initialState.hash = this.props.hash;
+ initialState.data = this.props.data;
+ initialState.original_email = this.props.email;
+ }
+
+ this.state = initialState;
+ }
+ handleSubmit(e) {
e.preventDefault();
- this.state.user.username = this.refs.name.getDOMNode().value.trim();
+ this.state.user.username = React.findDOMNode(this.refs.name).value.trim();
if (!this.state.user.username) {
this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''});
return;
@@ -31,14 +51,14 @@ module.exports = React.createClass({
return;
}
- this.state.user.email = this.refs.email.getDOMNode().value.trim();
+ this.state.user.email = React.findDOMNode(this.refs.email).value.trim();
if (!this.state.user.email) {
this.setState({nameError: '', emailError: 'This field is required', passwordError: ''});
return;
}
- this.state.user.password = this.refs.password.getDOMNode().value.trim();
- if (!this.state.user.password || this.state.user.password .length < 5) {
+ this.state.user.password = React.findDOMNode(this.refs.password).value.trim();
+ if (!this.state.user.password || this.state.user.password .length < 5) {
this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''});
return;
}
@@ -48,11 +68,11 @@ module.exports = React.createClass({
this.state.user.allow_marketing = true;
client.createUser(this.state.user, this.state.data, this.state.hash,
- function(data) {
+ function createUserSuccess() {
client.track('signup', 'signup_user_02_complete');
client.loginByEmail(this.props.teamName, this.state.user.email, this.state.user.password,
- function(data) {
+ function emailLoginSuccess(data) {
UserStore.setLastEmail(this.state.user.email);
UserStore.setCurrentUser(data);
if (this.props.hash > 0) {
@@ -60,7 +80,7 @@ module.exports = React.createClass({
}
window.location.href = '/';
}.bind(this),
- function(err) {
+ function emailLoginFailure(err) {
if (err.message === 'Login failed because email address has not been verified') {
window.location.href = '/verify_email?email=' + encodeURIComponent(this.state.user.email) + '&teamname=' + encodeURIComponent(this.props.teamName);
} else {
@@ -69,28 +89,12 @@ module.exports = React.createClass({
}.bind(this)
);
}.bind(this),
- function(err) {
+ function createUserFailure(err) {
this.setState({serverError: err.message});
}.bind(this)
);
- },
- getInitialState: function() {
- var state = BrowserStore.getGlobalItem(this.props.hash);
-
- if (!state) {
- state = {};
- state.wizard = 'welcome';
- state.user = {};
- state.user.team_id = this.props.teamId;
- state.user.email = this.props.email;
- state.hash = this.props.hash;
- state.data = this.props.data;
- state.original_email = this.props.email;
- }
-
- return state;
- },
- render: function() {
+ }
+ render() {
client.track('signup', 'signup_user_01_welcome');
if (this.state.wizard === 'finished') {
@@ -134,16 +138,24 @@ module.exports = React.createClass({
yourEmailIs = <span>Your email address is {this.state.user.email}. You'll use this address to sign in to {config.SiteName}.</span>;
}
- var emailContainerStyle = "margin--extra";
+ var emailContainerStyle = 'margin--extra';
if (this.state.original_email) {
- emailContainerStyle = "hidden";
+ emailContainerStyle = 'hidden';
}
var email = (
<div className={emailContainerStyle}>
<h5><strong>What's your email address?</strong></h5>
<div className={emailDivStyle}>
- <input type='email' ref='email' className='form-control' defaultValue={this.state.user.email} placeholder='' maxLength='128' autoFocus={true} />
+ <input
+ type='email'
+ ref='email'
+ className='form-control'
+ defaultValue={this.state.user.email}
+ placeholder=''
+ maxLength='128'
+ autoFocus={true}
+ />
{emailError}
</div>
</div>
@@ -155,7 +167,10 @@ module.exports = React.createClass({
var signupMessage = [];
if (authServices.indexOf(Constants.GITLAB_SERVICE) >= 0) {
signupMessage.push(
- <a className='btn btn-custom-login gitlab' href={'/' + this.props.teamName + '/signup/gitlab' + window.location.search}>
+ <a
+ className='btn btn-custom-login gitlab'
+ href={'/' + this.props.teamName + '/signup/gitlab' + window.location.search}
+ >
<span className='icon' />
<span>with GitLab</span>
</a>
@@ -172,7 +187,13 @@ module.exports = React.createClass({
<div className='margin--extra'>
<h5><strong>Choose your username</strong></h5>
<div className={nameDivStyle}>
- <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' />
+ <input
+ type='text'
+ ref='name'
+ className='form-control'
+ placeholder=''
+ maxLength='128'
+ />
{nameError}
<p className='form__hint'>Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'</p>
</div>
@@ -180,12 +201,26 @@ module.exports = React.createClass({
<div className='margin--extra'>
<h5><strong>Choose your password</strong></h5>
<div className={passwordDivStyle}>
- <input type='password' ref='password' className='form-control' placeholder='' maxLength='128' />
+ <input
+ type='password'
+ ref='password'
+ className='form-control'
+ placeholder=''
+ maxLength='128'
+ />
{passwordError}
</div>
</div>
</div>
- <p className='margin--extra'><button type='submit' onClick={this.handleSubmit} className='btn-primary btn'>Create Account</button></p>
+ <p className='margin--extra'>
+ <button
+ type='submit'
+ onClick={this.handleSubmit}
+ className='btn-primary btn'
+ >
+ Create Account
+ </button>
+ </p>
</div>
);
}
@@ -209,7 +244,10 @@ module.exports = React.createClass({
return (
<div>
<form>
- <img className='signup-team-logo' src='/static/images/logo.png' />
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
<h5 className='margin--less'>Welcome to:</h5>
<h2 className='signup-team__name'>{this.props.teamDisplayName}</h2>
<h2 className='signup-team__subdomain'>on {config.SiteName}</h2>
@@ -222,6 +260,23 @@ module.exports = React.createClass({
</div>
);
}
-});
-
+}
+SignupUserComplete.defaultProps = {
+ teamName: '',
+ hash: '',
+ teamId: '',
+ email: '',
+ data: null,
+ authServices: '',
+ teamDisplayName: ''
+};
+SignupUserComplete.propTypes = {
+ teamName: React.PropTypes.string,
+ hash: React.PropTypes.string,
+ teamId: React.PropTypes.string,
+ email: React.PropTypes.string,
+ data: React.PropTypes.string,
+ authServices: React.PropTypes.string,
+ teamDisplayName: React.PropTypes.string
+};
diff --git a/web/react/components/team_feature_tab.jsx b/web/react/components/team_feature_tab.jsx
index 4f8f0b2cf..e5398332e 100644
--- a/web/react/components/team_feature_tab.jsx
+++ b/web/react/components/team_feature_tab.jsx
@@ -4,65 +4,65 @@
var SettingItemMin = require('./setting_item_min.jsx');
var SettingItemMax = require('./setting_item_max.jsx');
-var client = require('../utils/client.jsx');
+var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
-module.exports = React.createClass({
- displayName: 'Feature Tab',
- propTypes: {
- updateSection: React.PropTypes.func.isRequired,
- team: React.PropTypes.object.isRequired,
- activeSection: React.PropTypes.string.isRequired
- },
- submitValetFeature: function() {
+export default class FeatureTab extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitValetFeature = this.submitValetFeature.bind(this);
+ this.handleValetRadio = this.handleValetRadio.bind(this);
+ this.onUpdateSection = this.onUpdateSection.bind(this);
+
+ this.state = {};
+ var team = this.props.team;
+
+ if (team && team.allow_valet) {
+ this.state.allowValet = 'true';
+ } else {
+ this.state.allowValet = 'false';
+ }
+ }
+ componentWillReceiveProps(newProps) {
+ var team = newProps.team;
+
+ var allowValet = 'false';
+ if (team && team.allow_valet) {
+ allowValet = 'true';
+ }
+
+ this.setState({allowValet: allowValet});
+ }
+ submitValetFeature() {
var data = {};
data.allow_valet = this.state.allowValet;
- client.updateValetFeature(data,
- function() {
+ Client.updateValetFeature(data,
+ function success() {
this.props.updateSection('');
AsyncClient.getMyTeam();
}.bind(this),
- function(err) {
+ function fail(err) {
var state = this.getInitialState();
state.serverError = err;
this.setState(state);
}.bind(this)
);
- },
- handleValetRadio: function(val) {
+ }
+ handleValetRadio(val) {
this.setState({allowValet: val});
- this.refs.wrapper.getDOMNode().focus();
- },
- componentWillReceiveProps: function(newProps) {
- var team = newProps.team;
-
- var allowValet = 'false';
- if (team && team.allow_valet) {
- allowValet = 'true';
- }
-
- this.setState({allowValet: allowValet});
- },
- getInitialState: function() {
- var team = this.props.team;
-
- var allowValet = 'false';
- if (team && team.allow_valet) {
- allowValet = 'true';
- }
-
- return {allowValet: allowValet};
- },
- onUpdateSection: function(e) {
+ React.findDOMNode(this.refs.wrapper).focus();
+ }
+ onUpdateSection(e) {
e.preventDefault();
if (this.props.activeSection === 'valet') {
this.props.updateSection('');
} else {
this.props.updateSection('valet');
}
- },
- render: function() {
+ }
+ render() {
var clientError = null;
var serverError = null;
if (this.state.clientError) {
@@ -73,7 +73,6 @@ module.exports = React.createClass({
}
var valetSection;
- var self = this;
if (this.props.activeSection === 'valet') {
var valetActive = [false, false];
@@ -92,7 +91,7 @@ module.exports = React.createClass({
<input
type='radio'
checked={valetActive[0]}
- onChange={self.handleValetRadio.bind(this, 'true')}
+ onChange={this.handleValetRadio.bind(this, 'true')}
>
On
</input>
@@ -104,7 +103,7 @@ module.exports = React.createClass({
<input
type='radio'
checked={valetActive[1]}
- onChange={self.handleValetRadio.bind(this, 'false')}
+ onChange={this.handleValetRadio.bind(this, 'false')}
>
Off
</input>
@@ -145,10 +144,25 @@ module.exports = React.createClass({
return (
<div>
<div className='modal-header'>
- <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
- <h4 className='modal-title' ref='title'><i className='modal-back'></i>Advanced Features</h4>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4
+ className='modal-title'
+ ref='title'
+ >
+ <i className='modal-back'></i>Advanced Features
+ </h4>
</div>
- <div ref='wrapper' className='user-settings'>
+ <div
+ ref='wrapper'
+ className='user-settings'
+ >
<h3 className='tab-header'>Advanced Features</h3>
<div className='divider-dark first'/>
{valetSection}
@@ -157,4 +171,14 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
+
+FeatureTab.defaultProps = {
+ team: {},
+ activeSection: ''
+};
+FeatureTab.propTypes = {
+ updateSection: React.PropTypes.func.isRequired,
+ team: React.PropTypes.object.isRequired,
+ activeSection: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/team_general_tab.jsx b/web/react/components/team_general_tab.jsx
index fd2a22731..2966a8a9a 100644
--- a/web/react/components/team_general_tab.jsx
+++ b/web/react/components/team_general_tab.jsx
@@ -1,11 +1,11 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var SettingItemMin = require('./setting_item_min.jsx');
-var SettingItemMax = require('./setting_item_max.jsx');
+const SettingItemMin = require('./setting_item_min.jsx');
+const SettingItemMax = require('./setting_item_max.jsx');
-var client = require('../utils/client.jsx');
-var utils = require('../utils/utils.jsx');
+const Client = require('../utils/client.jsx');
+const Utils = require('../utils/utils.jsx');
export default class GeneralTab extends React.Component {
constructor(props) {
@@ -21,10 +21,10 @@ export default class GeneralTab extends React.Component {
handleNameSubmit(e) {
e.preventDefault();
- var state = {serverError: '', clientError: ''};
- var valid = true;
+ let state = {serverError: '', clientError: ''};
+ let valid = true;
- var name = this.state.name.trim();
+ const name = this.state.name.trim();
if (!name) {
state.clientError = 'This field is required';
valid = false;
@@ -41,10 +41,10 @@ export default class GeneralTab extends React.Component {
return;
}
- var data = {};
+ let data = {};
data.new_name = name;
- client.updateTeamDisplayName(data,
+ Client.updateTeamDisplayName(data,
function nameChangeSuccess() {
this.props.updateSection('');
$('#team_settings').modal('hide');
@@ -84,8 +84,8 @@ export default class GeneralTab extends React.Component {
this.setState({name: e.target.value});
}
render() {
- var clientError = null;
- var serverError = null;
+ let clientError = null;
+ let serverError = null;
if (this.state.clientError) {
clientError = this.state.clientError;
}
@@ -93,18 +93,21 @@ export default class GeneralTab extends React.Component {
serverError = this.state.serverError;
}
- var nameSection;
+ let nameSection;
if (this.props.activeSection === 'name') {
let inputs = [];
- let teamNameLabel = utils.toTitleCase(strings.Team) + ' Name';
- if (utils.isMobile()) {
+ let teamNameLabel = Utils.toTitleCase(strings.Team) + ' Name';
+ if (Utils.isMobile()) {
teamNameLabel = '';
}
inputs.push(
- <div key='teamNameSetting' className='form-group'>
+ <div
+ key='teamNameSetting'
+ className='form-group'
+ >
<label className='col-sm-5 control-label'>{teamNameLabel}</label>
<div className='col-sm-7'>
<input
@@ -119,7 +122,7 @@ export default class GeneralTab extends React.Component {
nameSection = (
<SettingItemMax
- title={utils.toTitleCase(strings.Team) + ' Name'}
+ title={`${Utils.toTitleCase(strings.Team)} Name`}
inputs={inputs}
submit={this.handleNameSubmit}
server_error={serverError}
@@ -132,7 +135,7 @@ export default class GeneralTab extends React.Component {
nameSection = (
<SettingItemMin
- title={utils.toTitleCase(strings.Team) + ' Name'}
+ title={`${Utils.toTitleCase(strings.Team)} Name`}
describe={describe}
updateSection={this.onUpdateSection}
/>
diff --git a/web/react/components/team_settings.jsx b/web/react/components/team_settings.jsx
index 1a79eef1d..53855fe1c 100644
--- a/web/react/components/team_settings.jsx
+++ b/web/react/components/team_settings.jsx
@@ -5,66 +5,83 @@ var TeamStore = require('../stores/team_store.jsx');
var ImportTab = require('./team_import_tab.jsx');
var FeatureTab = require('./team_feature_tab.jsx');
var GeneralTab = require('./team_general_tab.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
-module.exports = React.createClass({
- displayName: 'Team Settings',
- propTypes: {
- activeTab: React.PropTypes.string.isRequired,
- activeSection: React.PropTypes.string.isRequired,
- updateSection: React.PropTypes.func.isRequired,
- teamDisplayName: React.PropTypes.string.isRequired
- },
- componentDidMount: function() {
+export default class TeamSettings extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onChange = this.onChange.bind(this);
+
+ this.state = {team: TeamStore.getCurrent()};
+ }
+ componentDidMount() {
TeamStore.addChangeListener(this.onChange);
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
TeamStore.removeChangeListener(this.onChange);
- },
- onChange: function() {
+ }
+ onChange() {
var team = TeamStore.getCurrent();
- if (!utils.areStatesEqual(this.state.team, team)) {
+ if (!Utils.areStatesEqual(this.state.team, team)) {
this.setState({team: team});
}
- },
- getInitialState: function() {
- return {team: TeamStore.getCurrent()};
- },
- render: function() {
+ }
+ render() {
var result;
switch (this.props.activeTab) {
- case 'general':
- result = (
- <div>
- <GeneralTab
- team={this.state.team}
- activeSection={this.props.activeSection}
- updateSection={this.props.updateSection}
- teamDisplayName={this.props.teamDisplayName}
- />
- </div>
- );
- break;
- case 'feature':
- result = (
- <div>
- <FeatureTab team={this.state.team} activeSection={this.props.activeSection} updateSection={this.props.updateSection} />
- </div>
- );
- break;
- case 'import':
- result = (
- <div>
- <ImportTab team={this.state.team} activeSection={this.props.activeSection} updateSection={this.props.updateSection} />
- </div>
- );
- break;
- default:
- result = (
- <div/>
- );
- break;
+ case 'general':
+ result = (
+ <div>
+ <GeneralTab
+ team={this.state.team}
+ activeSection={this.props.activeSection}
+ updateSection={this.props.updateSection}
+ teamDisplayName={this.props.teamDisplayName}
+ />
+ </div>
+ );
+ break;
+ case 'feature':
+ result = (
+ <div>
+ <FeatureTab
+ team={this.state.team}
+ activeSection={this.props.activeSection}
+ updateSection={this.props.updateSection}
+ />
+ </div>
+ );
+ break;
+ case 'import':
+ result = (
+ <div>
+ <ImportTab
+ team={this.state.team}
+ activeSection={this.props.activeSection}
+ updateSection={this.props.updateSection}
+ />
+ </div>
+ );
+ break;
+ default:
+ result = (
+ <div/>
+ );
+ break;
}
return result;
}
-});
+}
+
+TeamSettings.defaultProps = {
+ activeTab: '',
+ activeSection: '',
+ teamDisplayName: ''
+};
+TeamSettings.propTypes = {
+ activeTab: React.PropTypes.string.isRequired,
+ activeSection: React.PropTypes.string.isRequired,
+ updateSection: React.PropTypes.func.isRequired,
+ teamDisplayName: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/team_settings_modal.jsx b/web/react/components/team_settings_modal.jsx
index 7e65e8cab..668bf76cf 100644
--- a/web/react/components/team_settings_modal.jsx
+++ b/web/react/components/team_settings_modal.jsx
@@ -1,70 +1,96 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var SettingsSidebar = require('./settings_sidebar.jsx');
-var TeamSettings = require('./team_settings.jsx');
+const SettingsSidebar = require('./settings_sidebar.jsx');
+const TeamSettings = require('./team_settings.jsx');
-module.exports = React.createClass({
- displayName: 'Team Settings Modal',
- propTypes: {
- teamDisplayName: React.PropTypes.string.isRequired
- },
- componentDidMount: function() {
- $('body').on('click', '.modal-back', function onClick() {
+export default class TeamSettingsModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.updateTab = this.updateTab.bind(this);
+ this.updateSection = this.updateSection.bind(this);
+
+ this.state = {
+ activeTab: 'general',
+ activeSection: ''
+ };
+ }
+ componentDidMount() {
+ $('body').on('click', '.modal-back', function handleBackClick() {
$(this).closest('.modal-dialog').removeClass('display--content');
});
- $('body').on('click', '.modal-header .close', function onClick() {
+ $('body').on('click', '.modal-header .close', function handleCloseClick() {
setTimeout(function removeContent() {
$('.modal-dialog.display--content').removeClass('display--content');
}, 500);
});
- },
- updateTab: function(tab) {
+ }
+ updateTab(tab) {
this.setState({activeTab: tab, activeSection: ''});
- },
- updateSection: function(section) {
+ }
+ updateSection(section) {
this.setState({activeSection: section});
- },
- getInitialState: function() {
- return {activeTab: 'general', activeSection: ''};
- },
- render: function() {
- var tabs = [];
+ }
+ render() {
+ let tabs = [];
tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'});
tabs.push({name: 'import', uiName: 'Import', icon: 'glyphicon glyphicon-upload'});
tabs.push({name: 'feature', uiName: 'Advanced', icon: 'glyphicon glyphicon-wrench'});
return (
- <div className='modal fade' ref='modal' id='team_settings' role='dialog' tabIndex='-1' aria-hidden='true'>
- <div className='modal-dialog settings-modal'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
- <h4 className='modal-title' ref='title'>Team Settings</h4>
- </div>
- <div className='modal-body'>
- <div className='settings-table'>
- <div className='settings-links'>
- <SettingsSidebar
- tabs={tabs}
- activeTab={this.state.activeTab}
- updateTab={this.updateTab}
- />
+ <div
+ className='modal fade'
+ ref='modal'
+ id='team_settings'
+ role='dialog'
+ tabIndex='-1'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog settings-modal'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4
+ className='modal-title'
+ ref='title'
+ >
+ Team Settings
+ </h4>
</div>
- <div className='settings-content minimize-settings'>
- <TeamSettings
- activeTab={this.state.activeTab}
- activeSection={this.state.activeSection}
- updateSection={this.updateSection}
- teamDisplayName={this.props.teamDisplayName}
- />
+ <div className='modal-body'>
+ <div className='settings-table'>
+ <div className='settings-links'>
+ <SettingsSidebar
+ tabs={tabs}
+ activeTab={this.state.activeTab}
+ updateTab={this.updateTab}
+ />
+ </div>
+ <div className='settings-content minimize-settings'>
+ <TeamSettings
+ activeTab={this.state.activeTab}
+ activeSection={this.state.activeSection}
+ updateSection={this.updateSection}
+ teamDisplayName={this.props.teamDisplayName}
+ />
+ </div>
+ </div>
</div>
</div>
- </div>
</div>
- </div>
</div>
);
}
-});
+}
+TeamSettingsModal.propTypes = {
+ teamDisplayName: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/team_signup_allowed_domains_page.jsx b/web/react/components/team_signup_allowed_domains_page.jsx
index 90c7ff668..aee5afd23 100644
--- a/web/react/components/team_signup_allowed_domains_page.jsx
+++ b/web/react/components/team_signup_allowed_domains_page.jsx
@@ -1,31 +1,34 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var client = require('../utils/client.jsx');
+var Client = require('../utils/client.jsx');
-module.exports = React.createClass({
- displayName: 'TeamSignupAllowedDomainsPage',
- propTypes: {
- state: React.PropTypes.object,
- updateParent: React.PropTypes.func
- },
- submitBack: function(e) {
+export default class TeamSignupAllowedDomainsPage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitBack = this.submitBack.bind(this);
+ this.submitNext = this.submitNext.bind(this);
+
+ this.state = {};
+ }
+ submitBack(e) {
e.preventDefault();
this.props.state.wizard = 'team_url';
this.props.updateParent(this.props.state);
- },
- submitNext: function(e) {
+ }
+ submitNext(e) {
e.preventDefault();
- if (this.refs.open_network.getDOMNode().checked) {
+ if (React.findDOMNode(this.refs.open_network).checked) {
this.props.state.wizard = 'send_invites';
this.props.state.team.type = 'O';
this.props.updateParent(this.props.state);
return;
}
- if (this.refs.allow.getDOMNode().checked) {
- var name = this.refs.name.getDOMNode().value.trim();
+ if (React.findDOMNode(this.refs.allow).checked) {
+ var name = React.findDOMNode(this.refs.name).value.trim();
var domainRegex = /^\w+\.\w+$/;
if (!name) {
this.setState({nameError: 'This field is required'});
@@ -46,12 +49,9 @@ module.exports = React.createClass({
this.props.state.team.type = 'I';
this.props.updateParent(this.props.state);
}
- },
- getInitialState: function() {
- return {};
- },
- render: function() {
- client.track('signup', 'signup_team_04_allow_domains');
+ }
+ render() {
+ Client.track('signup', 'signup_team_04_allow_domains');
var nameError = null;
var nameDivClass = 'form-group';
@@ -63,11 +63,21 @@ module.exports = React.createClass({
return (
<div>
<form>
- <img className='signup-team-logo' src='/static/images/logo.png' />
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
<h2>Email Domain</h2>
<p>
<div className='checkbox'>
- <label><input type='checkbox' ref='allow' defaultChecked={true} />{' Allow sign up and ' + strings.Team + ' discovery with a ' + strings.Company + ' email address.'}</label>
+ <label>
+ <input
+ type='checkbox'
+ ref='allow'
+ defaultChecked={true}
+ />
+ {' Allow sign up and ' + strings.Team + ' discovery with a ' + strings.Company + ' email address.'}
+ </label>
</div>
</p>
<p>{'Check this box to allow your ' + strings.Team + ' members to sign up using their ' + strings.Company + ' email addresses if you share the same domain--otherwise, you need to invite everyone yourself.'}</p>
@@ -77,7 +87,16 @@ module.exports = React.createClass({
<div className='col-sm-9'>
<div className='input-group'>
<span className='input-group-addon'>@</span>
- <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.allowed_domains} autoFocus={true} onFocus={this.handleFocus}/>
+ <input
+ type='text'
+ ref='name'
+ className='form-control'
+ placeholder=''
+ maxLength='128'
+ defaultValue={this.props.state.team.allowed_domains}
+ autoFocus={true}
+ onFocus={this.handleFocus}
+ />
</div>
</div>
</div>
@@ -86,13 +105,38 @@ module.exports = React.createClass({
<p>To allow signups from multiple domains, separate each with a comma.</p>
<p>
<div className='checkbox'>
- <label><input type='checkbox' ref='open_network' defaultChecked={this.props.state.team.type === 'O'} /> Allow anyone to signup to this domain without an invitation.</label>
+ <label>
+ <input
+ type='checkbox'
+ ref='open_network'
+ defaultChecked={this.props.state.team.type === 'O'}
+ /> Allow anyone to signup to this domain without an invitation.</label>
</div>
</p>
- <button type='button' className='btn btn-default' onClick={this.submitBack}><i className='glyphicon glyphicon-chevron-left'></i> Back</button>&nbsp;
- <button type='submit' className='btn-primary btn' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button>
+ <button
+ type='button'
+ className='btn btn-default'
+ onClick={this.submitBack}
+ >
+ <i className='glyphicon glyphicon-chevron-left'></i> Back
+ </button>&nbsp;
+ <button
+ type='submit'
+ className='btn-primary btn'
+ onClick={this.submitNext}
+ >
+ Next<i className='glyphicon glyphicon-chevron-right'></i>
+ </button>
</form>
</div>
);
}
-});
+}
+
+TeamSignupAllowedDomainsPage.defaultProps = {
+ state: {}
+};
+TeamSignupAllowedDomainsPage.propTypes = {
+ state: React.PropTypes.object,
+ updateParent: React.PropTypes.func
+};
diff --git a/web/react/components/team_signup_display_name_page.jsx b/web/react/components/team_signup_display_name_page.jsx
index b5e93de1b..de756f4d5 100644
--- a/web/react/components/team_signup_display_name_page.jsx
+++ b/web/react/components/team_signup_display_name_page.jsx
@@ -4,21 +4,24 @@
var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
-module.exports = React.createClass({
- displayName: 'TeamSignupDisplayNamePage',
- propTypes: {
- state: React.PropTypes.object,
- updateParent: React.PropTypes.func
- },
- submitBack: function(e) {
+export default class TeamSignupDisplayNamePage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitBack = this.submitBack.bind(this);
+ this.submitNext = this.submitNext.bind(this);
+
+ this.state = {};
+ }
+ submitBack(e) {
e.preventDefault();
this.props.state.wizard = 'welcome';
this.props.updateParent(this.props.state);
- },
- submitNext: function(e) {
+ }
+ submitNext(e) {
e.preventDefault();
- var displayName = this.refs.name.getDOMNode().value.trim();
+ var displayName = React.findDOMNode(this.refs.name).value.trim();
if (!displayName) {
this.setState({nameError: 'This field is required'});
return;
@@ -28,15 +31,12 @@ module.exports = React.createClass({
this.props.state.team.display_name = displayName;
this.props.state.team.name = utils.cleanUpUrlable(displayName);
this.props.updateParent(this.props.state);
- },
- getInitialState: function() {
- return {};
- },
- handleFocus: function(e) {
+ }
+ handleFocus(e) {
e.preventDefault();
e.currentTarget.select();
- },
- render: function() {
+ }
+ render() {
client.track('signup', 'signup_team_02_name');
var nameError = null;
@@ -49,24 +49,48 @@ module.exports = React.createClass({
return (
<div>
<form>
- <img className='signup-team-logo' src='/static/images/logo.png' />
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png' />
<h2>{utils.toTitleCase(strings.Team) + ' Name'}</h2>
<div className={nameDivClass}>
<div className='row'>
<div className='col-sm-9'>
- <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.display_name} autoFocus={true} onFocus={this.handleFocus} />
+ <input
+ type='text'
+ ref='name'
+ className='form-control'
+ placeholder=''
+ maxLength='128'
+ defaultValue={this.props.state.team.display_name}
+ autoFocus={true}
+ onFocus={this.handleFocus} />
</div>
</div>
{nameError}
</div>
<div>{'Name your ' + strings.Team + ' in any language. Your ' + strings.Team + ' name shows in menus and headings.'}</div>
- <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button>
+ <button
+ type='submit'
+ className='btn btn-primary margin--extra'
+ onClick={this.submitNext} >
+ Next<i className='glyphicon glyphicon-chevron-right'></i>
+ </button>
<div className='margin--extra'>
- <a href='#' onClick={this.submitBack}>Back to previous step</a>
+ <a
+ href='#'
+ onClick={this.submitBack}>
+ Back to previous step
+ </a>
</div>
</form>
</div>
);
}
-});
+}
+
+TeamSignupDisplayNamePage.propTypes = {
+ state: React.PropTypes.object,
+ updateParent: React.PropTypes.func
+};
diff --git a/web/react/components/team_signup_email_item.jsx b/web/react/components/team_signup_email_item.jsx
index 11cd17e74..10bb2d69e 100644
--- a/web/react/components/team_signup_email_item.jsx
+++ b/web/react/components/team_signup_email_item.jsx
@@ -1,28 +1,28 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-
-module.exports = React.createClass({
- displayName: 'TeamSignupEmailItem',
- propTypes: {
- focus: React.PropTypes.bool,
- email: React.PropTypes.string
- },
- getInitialState: function() {
- return {};
- },
- getValue: function() {
- return this.refs.email.getDOMNode().value.trim();
- },
- validate: function(teamEmail) {
- var email = this.refs.email.getDOMNode().value.trim().toLowerCase();
+const Utils = require('../utils/utils.jsx');
+
+export default class TeamSignupEmailItem extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getValue = this.getValue.bind(this);
+ this.validate = this.validate.bind(this);
+
+ this.state = {};
+ }
+ getValue() {
+ return React.findDOMNode(this.refs.email).value.trim();
+ }
+ validate(teamEmail) {
+ const email = React.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!email) {
return true;
}
- if (!utils.isEmail(email)) {
+ if (!Utils.isEmail(email)) {
this.state.emailError = 'Please enter a valid email address';
this.setState(this.state);
return false;
@@ -31,13 +31,14 @@ module.exports = React.createClass({
this.setState(this.state);
return false;
}
+
this.state.emailError = '';
this.setState(this.state);
return true;
- },
- render: function() {
- var emailError = null;
- var emailDivClass = 'form-group';
+ }
+ render() {
+ let emailError = null;
+ let emailDivClass = 'form-group';
if (this.state.emailError) {
emailError = <label className='control-label'>{this.state.emailError}</label>;
emailDivClass += ' has-error';
@@ -45,9 +46,22 @@ module.exports = React.createClass({
return (
<div className={emailDivClass}>
- <input autoFocus={this.props.focus} type='email' ref='email' className='form-control' placeholder='Email Address' defaultValue={this.props.email} maxLength='128' />
+ <input
+ autoFocus={this.props.focus}
+ type='email'
+ ref='email'
+ className='form-control'
+ placeholder='Email Address'
+ defaultValue={this.props.email}
+ maxLength='128'
+ />
{emailError}
</div>
);
}
-});
+}
+
+TeamSignupEmailItem.propTypes = {
+ focus: React.PropTypes.bool,
+ email: React.PropTypes.string
+};
diff --git a/web/react/components/team_signup_password_page.jsx b/web/react/components/team_signup_password_page.jsx
index 6b21915f6..f36ec1119 100644
--- a/web/react/components/team_signup_password_page.jsx
+++ b/web/react/components/team_signup_password_page.jsx
@@ -1,24 +1,28 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
+var Client = require('../utils/client.jsx');
+var BrowserStore = require('../stores/browser_store.jsx');
+var UserStore = require('../stores/user_store.jsx');
-module.exports = React.createClass({
- displayName: 'TeamSignupPasswordPage',
- propTypes: {
- state: React.PropTypes.object,
- updateParent: React.PropTypes.func
- },
- submitBack: function(e) {
+export default class TeamSignupPasswordPage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitBack = this.submitBack.bind(this);
+ this.submitNext = this.submitNext.bind(this);
+
+ this.state = {};
+ }
+ submitBack(e) {
e.preventDefault();
this.props.state.wizard = 'username';
this.props.updateParent(this.props.state);
- },
- submitNext: function(e) {
+ }
+ submitNext(e) {
e.preventDefault();
- var password = this.refs.password.getDOMNode().value.trim();
+ var password = React.findDOMNode(this.refs.password).value.trim();
if (!password || password.length < 5) {
this.setState({passwordError: 'Please enter at least 5 characters'});
return;
@@ -31,17 +35,16 @@ module.exports = React.createClass({
teamSignup.user.allow_marketing = true;
delete teamSignup.wizard;
- client.createTeamFromSignup(teamSignup,
+ Client.createTeamFromSignup(teamSignup,
function success() {
- client.track('signup', 'signup_team_08_complete');
+ Client.track('signup', 'signup_team_08_complete');
var props = this.props;
-
- client.loginByEmail(teamSignup.team.name, teamSignup.team.email, teamSignup.user.password,
- function(data) {
+ Client.loginByEmail(teamSignup.team.name, teamSignup.team.email, teamSignup.user.password,
+ function loginSuccess() {
UserStore.setLastEmail(teamSignup.team.email);
- UserStore.setCurrentUser(teamSignup.user);
+ UserStore.setCurrentUser(data);
if (this.props.hash > 0) {
BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'}));
}
@@ -52,7 +55,7 @@ module.exports = React.createClass({
window.location.href = '/';
}.bind(this),
- function(err) {
+ function loginFail(err) {
if (err.message === 'Login failed because email address has not been verified') {
window.location.href = '/verify_email?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name);
} else {
@@ -67,12 +70,9 @@ module.exports = React.createClass({
$('#finish-button').button('reset');
}.bind(this)
);
- },
- getInitialState: function() {
- return {};
- },
- render: function() {
- client.track('signup', 'signup_team_07_password');
+ }
+ render() {
+ Client.track('signup', 'signup_team_07_password');
var passwordError = null;
var passwordDivStyle = 'form-group';
@@ -89,7 +89,10 @@ module.exports = React.createClass({
return (
<div>
<form>
- <img className='signup-team-logo' src='/static/images/logo.png' />
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
<h2 className='margin--less'>Your password</h2>
<h5 className='color--light'>Select a password that you'll use to login with your email address:</h5>
<div className='inner__content margin--extra'>
@@ -99,7 +102,14 @@ module.exports = React.createClass({
<div className='row'>
<div className='col-sm-11'>
<h5><strong>Choose your password</strong></h5>
- <input autoFocus={true} type='password' ref='password' className='form-control' placeholder='' maxLength='128' />
+ <input
+ autoFocus={true}
+ type='password'
+ ref='password'
+ className='form-control'
+ placeholder=''
+ maxLength='128'
+ />
<div className='color--light form__hint'>Passwords must contain 5 to 50 characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.</div>
</div>
</div>
@@ -108,14 +118,37 @@ module.exports = React.createClass({
</div>
</div>
<div className='form-group'>
- <button type='submit' className='btn btn-primary margin--extra' id='finish-button' data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Creating ' + strings.Team + '...'} onClick={this.submitNext}>Finish</button>
+ <button
+ type='submit'
+ className='btn btn-primary margin--extra'
+ id='finish-button'
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Creating ' + strings.Team + '...'}
+ onClick={this.submitNext}
+ >
+ Finish
+ </button>
</div>
<p>By proceeding to create your account and use {config.SiteName}, you agree to our <a href={config.TermsLink}>Terms of Service</a> and <a href={config.PrivacyLink}>Privacy Policy</a>. If you do not agree, you cannot use {config.SiteName}.</p>
<div className='margin--extra'>
- <a href='#' onClick={this.submitBack}>Back to previous step</a>
+ <a
+ href='#'
+ onClick={this.submitBack}
+ >
+ Back to previous step
+ </a>
</div>
</form>
</div>
);
}
-});
+}
+
+TeamSignupPasswordPage.defaultProps = {
+ state: {},
+ hash: ''
+};
+TeamSignupPasswordPage.propTypes = {
+ state: React.PropTypes.object,
+ hash: React.PropTypes.string,
+ updateParent: React.PropTypes.func
+};
diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/team_signup_send_invites_page.jsx
index 646a742ba..6d9b0ec03 100644
--- a/web/react/components/team_signup_send_invites_page.jsx
+++ b/web/react/components/team_signup_send_invites_page.jsx
@@ -2,9 +2,9 @@
// See License.txt for license information.
var EmailItem = require('./team_signup_email_item.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
var ConfigStore = require('../stores/config_store.jsx');
-var client = require('../utils/client.jsx');
+var Client = require('../utils/client.jsx');
export default class TeamSignupSendInvitesPage extends React.Component {
constructor(props) {
@@ -71,7 +71,7 @@ export default class TeamSignupSendInvitesPage extends React.Component {
}
keySubmit(e) {
if (e && e.keyCode === 13) {
- this.submitNext(e)
+ this.submitNext(e);
}
}
componentWillMount() {
@@ -92,7 +92,7 @@ export default class TeamSignupSendInvitesPage extends React.Component {
}
}
render() {
- client.track('signup', 'signup_team_05_send_invites');
+ Client.track('signup', 'signup_team_05_send_invites');
var content = null;
var bottomContent = null;
@@ -165,7 +165,7 @@ export default class TeamSignupSendInvitesPage extends React.Component {
className='signup-team-logo'
src='/static/images/logo.png'
/>
- <h2>{'Invite ' + utils.toTitleCase(strings.Team) + ' Members'}</h2>
+ <h2>{'Invite ' + Utils.toTitleCase(strings.Team) + ' Members'}</h2>
{content}
<div className='form-group'>
<button
@@ -190,6 +190,7 @@ export default class TeamSignupSendInvitesPage extends React.Component {
);
}
}
+
TeamSignupSendInvitesPage.propTypes = {
state: React.PropTypes.object.isRequired,
updateParent: React.PropTypes.func.isRequired
diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/team_signup_url_page.jsx
index beef725e2..2ea6c3680 100644
--- a/web/react/components/team_signup_url_page.jsx
+++ b/web/react/components/team_signup_url_page.jsx
@@ -1,33 +1,37 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
-var constants = require('../utils/constants.jsx');
-
-module.exports = React.createClass({
- displayName: 'TeamSignupURLPage',
- propTypes: {
- state: React.PropTypes.object,
- updateParent: React.PropTypes.func
- },
- submitBack: function(e) {
+const Utils = require('../utils/utils.jsx');
+const Client = require('../utils/client.jsx');
+const Constants = require('../utils/constants.jsx');
+
+export default class TeamSignupUrlPage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitBack = this.submitBack.bind(this);
+ this.submitNext = this.submitNext.bind(this);
+ this.handleFocus = this.handleFocus.bind(this);
+
+ this.state = {nameError: ''};
+ }
+ submitBack(e) {
e.preventDefault();
this.props.state.wizard = 'team_display_name';
this.props.updateParent(this.props.state);
- },
- submitNext: function(e) {
+ }
+ submitNext(e) {
e.preventDefault();
- var name = this.refs.name.getDOMNode().value.trim();
+ const name = React.findDOMNode(this.refs.name).value.trim();
if (!name) {
this.setState({nameError: 'This field is required'});
return;
}
- var cleanedName = utils.cleanUpUrlable(name);
+ const cleanedName = Utils.cleanUpUrlable(name);
- var urlRegex = /^[a-z]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g;
+ const urlRegex = /^[a-z]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g;
if (cleanedName !== name || !urlRegex.test(name)) {
this.setState({nameError: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."});
return;
@@ -36,14 +40,14 @@ module.exports = React.createClass({
return;
}
- for (var index = 0; index < constants.RESERVED_TEAM_NAMES.length; index++) {
- if (cleanedName.indexOf(constants.RESERVED_TEAM_NAMES[index]) === 0) {
+ for (let index = 0; index < Constants.RESERVED_TEAM_NAMES.length; index++) {
+ if (cleanedName.indexOf(Constants.RESERVED_TEAM_NAMES[index]) === 0) {
this.setState({nameError: 'This team name is unavailable'});
return;
}
}
- client.findTeamByName(name,
+ Client.findTeamByName(name,
function success(data) {
if (!data) {
if (config.AllowSignupDomainsWizard) {
@@ -65,55 +69,88 @@ module.exports = React.createClass({
this.setState(this.state);
}.bind(this)
);
- },
- getInitialState: function() {
- return {};
- },
- handleFocus: function(e) {
+ }
+ handleFocus(e) {
e.preventDefault();
e.currentTarget.select();
- },
- render: function() {
+ }
+ render() {
$('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'});
- client.track('signup', 'signup_team_03_url');
+ Client.track('signup', 'signup_team_03_url');
- var nameError = null;
- var nameDivClass = 'form-group';
+ let nameError = null;
+ let nameDivClass = 'form-group';
if (this.state.nameError) {
nameError = <label className='control-label'>{this.state.nameError}</label>;
nameDivClass += ' has-error';
}
+ const title = `${Utils.getWindowLocationOrigin()}/`;
+
return (
<div>
<form>
- <img className='signup-team-logo' src='/static/images/logo.png' />
- <h2>{utils.toTitleCase(strings.Team) + ' URL'}</h2>
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
+ <h2>{`${Utils.toTitleCase(strings.Team)} URL`}</h2>
<div className={nameDivClass}>
<div className='row'>
<div className='col-sm-11'>
<div className='input-group input-group--limit'>
- <span data-toggle='tooltip' title={utils.getWindowLocationOrigin() + '/'} className='input-group-addon'>{utils.getWindowLocationOrigin() + '/'}</span>
- <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.name} autoFocus={true} onFocus={this.handleFocus}/>
+ <span
+ data-toggle='tooltip'
+ title={title}
+ className='input-group-addon'
+ >
+ {title}
+ </span>
+ <input
+ type='text'
+ ref='name'
+ className='form-control'
+ placeholder=''
+ maxLength='128'
+ defaultValue={this.props.state.team.name}
+ autoFocus={true}
+ onFocus={this.handleFocus}
+ />
</div>
</div>
</div>
{nameError}
</div>
- <p>{'Choose the web address of your new ' + strings.Team + ':'}</p>
+ <p>{`Choose the web address of your new ${strings.Team}:`}</p>
<ul className='color--light'>
<li>Short and memorable is best</li>
<li>Use lowercase letters, numbers and dashes</li>
<li>Must start with a letter and can't end in a dash</li>
</ul>
- <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button>
+ <button
+ type='submit'
+ className='btn btn-primary margin--extra'
+ onClick={this.submitNext}
+ >
+ Next<i className='glyphicon glyphicon-chevron-right'></i>
+ </button>
<div className='margin--extra'>
- <a href='#' onClick={this.submitBack}>Back to previous step</a>
+ <a
+ href='#'
+ onClick={this.submitBack}
+ >
+ Back to previous step
+ </a>
</div>
</form>
</div>
);
}
-});
+}
+
+TeamSignupUrlPage.propTypes = {
+ state: React.PropTypes.object,
+ updateParent: React.PropTypes.func
+};
diff --git a/web/react/components/team_signup_username_page.jsx b/web/react/components/team_signup_username_page.jsx
index 56882e6a1..8efcd87e1 100644
--- a/web/react/components/team_signup_username_page.jsx
+++ b/web/react/components/team_signup_username_page.jsx
@@ -1,26 +1,29 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
+var Utils = require('../utils/utils.jsx');
+var Client = require('../utils/client.jsx');
-module.exports = React.createClass({
- displayName: 'TeamSignupUsernamePage',
- propTypes: {
- state: React.PropTypes.object,
- updateParent: React.PropTypes.func
- },
- submitBack: function(e) {
+export default class TeamSignupUsernamePage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitBack = this.submitBack.bind(this);
+ this.submitNext = this.submitNext.bind(this);
+
+ this.state = {};
+ }
+ submitBack(e) {
e.preventDefault();
this.props.state.wizard = 'send_invites';
this.props.updateParent(this.props.state);
- },
- submitNext: function(e) {
+ }
+ submitNext(e) {
e.preventDefault();
- var name = this.refs.name.getDOMNode().value.trim();
+ var name = React.findDOMNode(this.refs.name).value.trim();
- var usernameError = utils.isValidUsername(name);
+ var usernameError = Utils.isValidUsername(name);
if (usernameError === 'Cannot use a reserved word as a username.') {
this.setState({nameError: 'This username is reserved, please choose a new one.'});
return;
@@ -32,12 +35,9 @@ module.exports = React.createClass({
this.props.state.wizard = 'password';
this.props.state.user.username = name;
this.props.updateParent(this.props.state);
- },
- getInitialState: function() {
- return {};
- },
- render: function() {
- client.track('signup', 'signup_team_06_username');
+ }
+ render() {
+ Client.track('signup', 'signup_team_06_username');
var nameError = null;
var nameDivClass = 'form-group';
@@ -49,7 +49,10 @@ module.exports = React.createClass({
return (
<div>
<form>
- <img className='signup-team-logo' src='/static/images/logo.png' />
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
<h2 className='margin--less'>Your username</h2>
<h5 className='color--light'>{'Select a memorable username that makes it easy for ' + strings.Team + 'mates to identify you:'}</h5>
<div className='inner__content margin--extra'>
@@ -57,19 +60,47 @@ module.exports = React.createClass({
<div className='row'>
<div className='col-sm-11'>
<h5><strong>Choose your username</strong></h5>
- <input autoFocus={true} type='text' ref='name' className='form-control' placeholder='' defaultValue={this.props.state.user.username} maxLength='128' />
+ <input
+ autoFocus={true}
+ type='text'
+ ref='name'
+ className='form-control'
+ placeholder=''
+ defaultValue={this.props.state.user.username}
+ maxLength='128'
+ />
<div className='color--light form__hint'>Usernames must begin with a letter and contain 3 to 15 characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'</div>
</div>
</div>
{nameError}
</div>
</div>
- <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button>
+ <button
+ type='submit'
+ className='btn btn-primary margin--extra'
+ onClick={this.submitNext}
+ >
+ Next
+ <i className='glyphicon glyphicon-chevron-right'></i>
+ </button>
<div className='margin--extra'>
- <a href='#' onClick={this.submitBack}>Back to previous step</a>
+ <a
+ href='#'
+ onClick={this.submitBack}
+ >
+ Back to previous step
+ </a>
</div>
</form>
</div>
);
}
-});
+}
+
+TeamSignupUsernamePage.defaultProps = {
+ state: null
+};
+TeamSignupUsernamePage.propTypes = {
+ state: React.PropTypes.object,
+ updateParent: React.PropTypes.func
+};
diff --git a/web/react/components/team_signup_welcome_page.jsx b/web/react/components/team_signup_welcome_page.jsx
index f0c680bd8..e742cba2f 100644
--- a/web/react/components/team_signup_welcome_page.jsx
+++ b/web/react/components/team_signup_welcome_page.jsx
@@ -1,17 +1,22 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
+var Utils = require('../utils/utils.jsx');
+var Client = require('../utils/client.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
-module.exports = React.createClass({
- displayName: 'TeamSignupWelcomePage',
- propTypes: {
- state: React.PropTypes.object,
- updateParent: React.PropTypes.func
- },
- submitNext: function(e) {
+export default class TeamSignupWelcomePage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitNext = this.submitNext.bind(this);
+ this.handleDiffEmail = this.handleDiffEmail.bind(this);
+ this.handleDiffSubmit = this.handleDiffSubmit.bind(this);
+ this.handleKeyPress = this.handleKeyPress.bind(this);
+
+ this.state = {useDiff: false};
+ }
+ submitNext(e) {
if (!BrowserStore.isLocalStorageSupported()) {
this.setState({storageError: 'This service requires local storage to be enabled. Please enable it or exit private browsing.'});
return;
@@ -19,18 +24,18 @@ module.exports = React.createClass({
e.preventDefault();
this.props.state.wizard = 'team_display_name';
this.props.updateParent(this.props.state);
- },
- handleDiffEmail: function(e) {
+ }
+ handleDiffEmail(e) {
e.preventDefault();
this.setState({useDiff: true});
- },
- handleDiffSubmit: function(e) {
+ }
+ handleDiffSubmit(e) {
e.preventDefault();
var state = {useDiff: true, serverError: ''};
- var email = this.refs.email.getDOMNode().value.trim().toLowerCase();
- if (!email || !utils.isEmail(email)) {
+ var email = React.findDOMNode(this.refs.email).value.trim().toLowerCase();
+ if (!email || !Utils.isEmail(email)) {
state.emailError = 'Please enter a valid email address';
this.setState(state);
return;
@@ -41,7 +46,7 @@ module.exports = React.createClass({
}
state.emailError = '';
- client.signupTeam(email,
+ Client.signupTeam(email,
function success(data) {
if (data.follow_link) {
window.location.href = data.follow_link;
@@ -56,23 +61,20 @@ module.exports = React.createClass({
this.setState(this.state);
}.bind(this)
);
- },
- getInitialState: function() {
- return {useDiff: false};
- },
- handleKeyPress: function(event) {
+ }
+ handleKeyPress(event) {
if (event.keyCode === 13) {
this.submitNext(event);
}
- },
- componentWillMount: function() {
+ }
+ componentWillMount() {
document.addEventListener('keyup', this.handleKeyPress, false);
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
document.removeEventListener('keyup', this.handleKeyPress, false);
- },
- render: function() {
- client.track('signup', 'signup_team_01_welcome');
+ }
+ render() {
+ Client.track('signup', 'signup_team_01_welcome');
var storageError = null;
if (this.state.storageError) {
@@ -105,7 +107,10 @@ module.exports = React.createClass({
return (
<div>
<p>
- <img className='signup-team-logo' src='/static/images/logo.png' />
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
<h3 className='sub-heading'>Welcome to:</h3>
<h1 className='margin--top-none'>{config.SiteName}</h1>
</p>
@@ -121,7 +126,14 @@ module.exports = React.createClass({
You can add other administrators later.
</p>
<div className='form-group'>
- <button className='btn-primary btn form-group' type='submit' onClick={this.submitNext}><i className='glyphicon glyphicon-ok'></i>Yes, this address is correct</button>
+ <button
+ className='btn-primary btn form-group'
+ type='submit'
+ onClick={this.submitNext}
+ >
+ <i className='glyphicon glyphicon-ok'></i>
+ Yes, this address is correct
+ </button>
{storageError}
</div>
<hr />
@@ -129,16 +141,42 @@ module.exports = React.createClass({
<div className={emailDivClass}>
<div className='row'>
<div className='col-sm-9'>
- <input type='email' ref='email' className='form-control' placeholder='Email Address' maxLength='128' />
+ <input
+ type='email'
+ ref='email'
+ className='form-control'
+ placeholder='Email Address'
+ maxLength='128'
+ />
</div>
</div>
{emailError}
</div>
{serverError}
- <button className='btn btn-md btn-primary' type='button' onClick={this.handleDiffSubmit}>Use this instead</button>
+ <button
+ className='btn btn-md btn-primary'
+ type='button'
+ onClick={this.handleDiffSubmit}
+ >
+ Use this instead
+ </button>
</div>
- <a href='#' onClick={this.handleDiffEmail} className={differentEmailLinkClass}>Use a different email</a>
+ <a
+ href='#'
+ onClick={this.handleDiffEmail}
+ className={differentEmailLinkClass}
+ >
+ Use a different email
+ </a>
</div>
);
}
-});
+}
+
+TeamSignupWelcomePage.defaultProps = {
+ state: {}
+};
+TeamSignupWelcomePage.propTypes = {
+ updateParent: React.PropTypes.func.isRequired,
+ state: React.PropTypes.object
+};
diff --git a/web/react/components/team_signup_with_email.jsx b/web/react/components/team_signup_with_email.jsx
index c7204880f..c0bbb7da9 100644
--- a/web/react/components/team_signup_with_email.jsx
+++ b/web/react/components/team_signup_with_email.jsx
@@ -1,8 +1,8 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
+const Utils = require('../utils/utils.jsx');
+const Client = require('../utils/client.jsx');
export default class EmailSignUpPage extends React.Component {
constructor() {
@@ -14,11 +14,11 @@ export default class EmailSignUpPage extends React.Component {
}
handleSubmit(e) {
e.preventDefault();
- var team = {};
- var state = {serverError: ''};
+ let team = {};
+ let state = {serverError: ''};
- team.email = this.refs.email.getDOMNode().value.trim().toLowerCase();
- if (!team.email || !utils.isEmail(team.email)) {
+ team.email = React.findDOMNode(this.refs.email).value.trim().toLowerCase();
+ if (!team.email || !Utils.isEmail(team.email)) {
state.emailError = 'Please enter a valid email address';
state.inValid = true;
} else {
@@ -30,12 +30,12 @@ export default class EmailSignUpPage extends React.Component {
return;
}
- client.signupTeam(team.email,
+ Client.signupTeam(team.email,
function success(data) {
if (data.follow_link) {
window.location.href = data.follow_link;
} else {
- window.location.href = '/signup_team_confirm/?email=' + encodeURIComponent(team.email);
+ window.location.href = `/signup_team_confirm/?email=${encodeURIComponent(team.email)}`;
}
},
function fail(err) {
@@ -69,7 +69,7 @@ export default class EmailSignUpPage extends React.Component {
</button>
</div>
<div className='form-group margin--extra-2x'>
- <span><a href='/find_team'>{'Find my ' + strings.Team}</a></span>
+ <span><a href='/find_team'>{`Find my ${strings.Team}`}</a></span>
</div>
</form>
);
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index efd2dd810..0408a262d 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -1,66 +1,93 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var PostStore = require('../stores/post_store.jsx');
-var CommandList = require('./command_list.jsx');
-var ErrorStore = require('../stores/error_store.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-
-var utils = require('../utils/utils.jsx');
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const PostStore = require('../stores/post_store.jsx');
+const CommandList = require('./command_list.jsx');
+const ErrorStore = require('../stores/error_store.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+
+const Utils = require('../utils/utils.jsx');
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
+
+export default class Textbox extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getStateFromStores = this.getStateFromStores.bind(this);
+ this.onListenerChange = this.onListenerChange.bind(this);
+ this.onRecievedError = this.onRecievedError.bind(this);
+ this.onTimerInterrupt = this.onTimerInterrupt.bind(this);
+ this.updateMentionTab = this.updateMentionTab.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleKeyPress = this.handleKeyPress.bind(this);
+ this.handleKeyDown = this.handleKeyDown.bind(this);
+ this.handleBackspace = this.handleBackspace.bind(this);
+ this.checkForNewMention = this.checkForNewMention.bind(this);
+ this.addMention = this.addMention.bind(this);
+ this.addCommand = this.addCommand.bind(this);
+ this.resize = this.resize.bind(this);
+ this.handleFocus = this.handleFocus.bind(this);
+ this.handleBlur = this.handleBlur.bind(this);
+ this.handlePaste = this.handlePaste.bind(this);
+
+ this.state = {
+ mentionText: '-1',
+ mentions: [],
+ connection: '',
+ timerInterrupt: null
+ };
+
+ this.caret = -1;
+ this.addedMention = false;
+ this.doProcessMentions = false;
+ this.mentions = [];
+ }
+ getStateFromStores() {
+ const error = ErrorStore.getLastError();
-function getStateFromStores() {
- var error = ErrorStore.getLastError();
+ if (error) {
+ return {message: error.message};
+ }
- if (error) {
- return {message: error.message};
+ return {message: null};
}
- return {message: null};
-}
-
-module.exports = React.createClass({
- displayName: 'Textbox',
- caret: -1,
- addedMention: false,
- doProcessMentions: false,
- mentions: [],
- componentDidMount: function() {
+ componentDidMount() {
PostStore.addAddMentionListener(this.onListenerChange);
ErrorStore.addChangeListener(this.onRecievedError);
this.resize();
this.updateMentionTab(null);
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
PostStore.removeAddMentionListener(this.onListenerChange);
ErrorStore.removeChangeListener(this.onRecievedError);
- },
- onListenerChange: function(id, username) {
+ }
+ onListenerChange(id, username) {
if (id === this.props.id) {
this.addMention(username);
}
- },
- onRecievedError: function() {
- var errorState = getStateFromStores();
+ }
+ onRecievedError() {
+ const errorState = this.getStateFromStores();
- if (this.state.timerInterrupt != null) {
+ if (this.state.timerInterrupt !== null) {
window.clearInterval(this.state.timerInterrupt);
this.setState({timerInterrupt: null});
}
if (errorState.message === 'There appears to be a problem with your internet connection') {
this.setState({connection: 'bad-connection'});
- var timerInterrupt = window.setInterval(this.onTimerInterrupt, 5000);
+ const timerInterrupt = window.setInterval(this.onTimerInterrupt, 5000);
this.setState({timerInterrupt: timerInterrupt});
} else {
this.setState({connection: ''});
}
- },
- onTimerInterrupt: function() {
- //Since these should only happen when you have no connection and slightly briefly after any
- //performance hit should not matter
+ }
+ onTimerInterrupt() {
+ // Since these should only happen when you have no connection and slightly briefly after any
+ // performance hit should not matter
if (this.state.connection === 'bad-connection') {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_ERROR,
@@ -72,10 +99,10 @@ module.exports = React.createClass({
window.clearInterval(this.state.timerInterrupt);
this.setState({timerInterrupt: null});
- },
- componentDidUpdate: function() {
+ }
+ componentDidUpdate() {
if (this.caret >= 0) {
- utils.setCaretPosition(this.refs.message.getDOMNode(), this.caret);
+ Utils.setCaretPosition(React.findDOMNode(this.refs.message), this.caret);
this.caret = -1;
}
if (this.doProcessMentions) {
@@ -83,40 +110,35 @@ module.exports = React.createClass({
this.doProcessMentions = false;
}
this.resize();
- },
- componentWillReceiveProps: function(nextProps) {
+ }
+ componentWillReceiveProps(nextProps) {
if (!this.addedMention) {
this.checkForNewMention(nextProps.messageText);
}
- var text = this.refs.message.getDOMNode().value;
+ const text = React.findDOMNode(this.refs.message).value;
if (nextProps.channelId !== this.props.channelId || nextProps.messageText !== text) {
this.doProcessMentions = true;
}
this.addedMention = false;
this.refs.commands.getSuggestedCommands(nextProps.messageText);
this.resize();
- },
- getInitialState: function() {
- return {mentionText: '-1', mentions: [], connection: '', timerInterrupt: null};
- },
- updateMentionTab: function(mentionText) {
- var self = this;
-
+ }
+ updateMentionTab(mentionText) {
// using setTimeout so dispatch isn't called during an in progress dispatch
- setTimeout(function() {
+ setTimeout(function updateMentionTabAfterTimeout() {
AppDispatcher.handleViewAction({
type: ActionTypes.RECIEVED_MENTION_DATA,
- id: self.props.id,
+ id: this.props.id,
mention_text: mentionText
});
- }, 1);
- },
- handleChange: function() {
- this.props.onUserInput(this.refs.message.getDOMNode().value);
+ }.bind(this), 1);
+ }
+ handleChange() {
+ this.props.onUserInput(React.findDOMNode(this.refs.message).value);
this.resize();
- },
- handleKeyPress: function(e) {
- var text = this.refs.message.getDOMNode().value;
+ }
+ handleKeyPress(e) {
+ const text = React.findDOMNode(this.refs.message).value;
if (!this.refs.commands.isEmpty() && text.indexOf('/') === 0 && e.which === 13) {
this.refs.commands.addFirstCommand();
@@ -125,10 +147,10 @@ module.exports = React.createClass({
}
if (!this.doProcessMentions) {
- var caret = utils.getCaretPosition(this.refs.message.getDOMNode());
- var preText = text.substring(0, caret);
- var lastSpace = preText.lastIndexOf(' ');
- var lastAt = preText.lastIndexOf('@');
+ const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message));
+ const preText = text.substring(0, caret);
+ const lastSpace = preText.lastIndexOf(' ');
+ const lastAt = preText.lastIndexOf('@');
if (caret > lastAt && lastSpace < lastAt) {
this.doProcessMentions = true;
@@ -136,18 +158,18 @@ module.exports = React.createClass({
}
this.props.onKeyPress(e);
- },
- handleKeyDown: function(e) {
- if (utils.getSelectedText(this.refs.message.getDOMNode()) !== '') {
+ }
+ handleKeyDown(e) {
+ if (Utils.getSelectedText(React.findDOMNode(this.refs.message)) !== '') {
this.doProcessMentions = true;
}
if (e.keyCode === 8) {
this.handleBackspace(e);
}
- },
- handleBackspace: function() {
- var text = this.refs.message.getDOMNode().value;
+ }
+ handleBackspace() {
+ const text = React.findDOMNode(this.refs.message).value;
if (text.indexOf('/') === 0) {
this.refs.commands.getSuggestedCommands(text.substring(0, text.length - 1));
}
@@ -156,21 +178,21 @@ module.exports = React.createClass({
return;
}
- var caret = utils.getCaretPosition(this.refs.message.getDOMNode());
- var preText = text.substring(0, caret);
- var lastSpace = preText.lastIndexOf(' ');
- var lastAt = preText.lastIndexOf('@');
+ const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message));
+ const preText = text.substring(0, caret);
+ const lastSpace = preText.lastIndexOf(' ');
+ const lastAt = preText.lastIndexOf('@');
if (caret > lastAt && (lastSpace > lastAt || lastSpace === -1)) {
this.doProcessMentions = true;
}
- },
- checkForNewMention: function(text) {
- var caret = utils.getCaretPosition(this.refs.message.getDOMNode());
+ }
+ checkForNewMention(text) {
+ const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message));
- var preText = text.substring(0, caret);
+ const preText = text.substring(0, caret);
- var atIndex = preText.lastIndexOf('@');
+ const atIndex = preText.lastIndexOf('@');
// The @ character not typed, so nothing to do.
if (atIndex === -1) {
@@ -178,8 +200,8 @@ module.exports = React.createClass({
return;
}
- var lastCharSpace = preText.lastIndexOf(String.fromCharCode(160));
- var lastSpace = preText.lastIndexOf(' ');
+ const lastCharSpace = preText.lastIndexOf(String.fromCharCode(160));
+ const lastSpace = preText.lastIndexOf(' ');
// If there is a space after the last @, nothing to do.
if (lastSpace > atIndex || lastCharSpace > atIndex) {
@@ -188,43 +210,43 @@ module.exports = React.createClass({
}
// Get the name typed so far.
- var name = preText.substring(atIndex + 1, preText.length).toLowerCase();
+ const name = preText.substring(atIndex + 1, preText.length).toLowerCase();
this.updateMentionTab(name);
- },
- addMention: function(name) {
- var caret = utils.getCaretPosition(this.refs.message.getDOMNode());
+ }
+ addMention(name) {
+ const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message));
- var text = this.props.messageText;
+ const text = this.props.messageText;
- var preText = text.substring(0, caret);
+ const preText = text.substring(0, caret);
- var atIndex = preText.lastIndexOf('@');
+ const atIndex = preText.lastIndexOf('@');
// The @ character not typed, so nothing to do.
if (atIndex === -1) {
return;
}
- var prefix = text.substring(0, atIndex);
- var suffix = text.substring(caret, text.length);
+ const prefix = text.substring(0, atIndex);
+ const suffix = text.substring(caret, text.length);
this.caret = prefix.length + name.length + 2;
this.addedMention = true;
this.doProcessMentions = true;
- this.props.onUserInput(prefix + '@' + name + ' ' + suffix);
- },
- addCommand: function(cmd) {
- var elm = this.refs.message.getDOMNode();
+ this.props.onUserInput(`${prefix}@${name} ${suffix}`);
+ }
+ addCommand(cmd) {
+ const elm = React.findDOMNode(this.refs.message);
elm.value = cmd;
this.handleChange();
- },
- resize: function() {
- var e = this.refs.message.getDOMNode();
- var w = this.refs.wrapper.getDOMNode();
+ }
+ resize() {
+ const e = React.findDOMNode(this.refs.message);
+ const w = React.findDOMNode(this.refs.wrapper);
- var lht = parseInt($(e).css('lineHeight'), 10);
- var lines = e.scrollHeight / lht;
- var mod = 15;
+ const lht = parseInt($(e).css('lineHeight'), 10);
+ const lines = e.scrollHeight / lht;
+ let mod = 15;
if (lines < 2.5 || this.props.messageText === '') {
mod = 30;
@@ -237,28 +259,62 @@ module.exports = React.createClass({
$(e).css({height: 'auto', 'overflow-y': 'scroll'}).height(167);
$(w).css({height: 'auto'}).height(167);
}
- },
- handleFocus: function() {
- var elm = this.refs.message.getDOMNode();
+ }
+ handleFocus() {
+ const elm = React.findDOMNode(this.refs.message);
if (elm.title === elm.value) {
elm.value = '';
}
- },
- handleBlur: function() {
- var elm = this.refs.message.getDOMNode();
+ }
+ handleBlur() {
+ const elm = React.findDOMNode(this.refs.message);
if (elm.value === '') {
elm.value = elm.title;
}
- },
- handlePaste: function() {
+ }
+ handlePaste() {
this.doProcessMentions = true;
- },
- render: function() {
+ }
+ render() {
return (
- <div ref='wrapper' className='textarea-wrapper'>
- <CommandList ref='commands' addCommand={this.addCommand} channelId={this.props.channelId} />
- <textarea id={this.props.id} ref='message' className={'form-control custom-textarea ' + this.state.connection} spellCheck='true' autoComplete='off' autoCorrect='off' rows='1' maxLength={Constants.MAX_POST_LEN} placeholder={this.props.createMessage} value={this.props.messageText} onInput={this.handleChange} onChange={this.handleChange} onKeyPress={this.handleKeyPress} onKeyDown={this.handleKeyDown} onFocus={this.handleFocus} onBlur={this.handleBlur} onPaste={this.handlePaste} />
+ <div
+ ref='wrapper'
+ className='textarea-wrapper'
+ >
+ <CommandList
+ ref='commands'
+ addCommand={this.addCommand}
+ channelId={this.props.channelId}
+ />
+ <textarea
+ id={this.props.id}
+ ref='message'
+ className={`form-control custom-textarea ${this.state.connection}`}
+ spellCheck='true'
+ autoComplete='off'
+ autoCorrect='off'
+ rows='1'
+ maxLength={Constants.MAX_POST_LEN}
+ placeholder={this.props.createMessage}
+ value={this.props.messageText}
+ onInput={this.handleChange}
+ onChange={this.handleChange}
+ onKeyPress={this.handleKeyPress}
+ onKeyDown={this.handleKeyDown}
+ onFocus={this.handleFocus}
+ onBlur={this.handleBlur}
+ onPaste={this.handlePaste}
+ />
</div>
);
}
-});
+}
+
+Textbox.propTypes = {
+ id: React.PropTypes.string.isRequired,
+ channelId: React.PropTypes.string,
+ messageText: React.PropTypes.string.isRequired,
+ onUserInput: React.PropTypes.func.isRequired,
+ onKeyPress: React.PropTypes.func.isRequired,
+ createMessage: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx
index 5c4d26a23..0d10166f3 100644
--- a/web/react/components/user_profile.jsx
+++ b/web/react/components/user_profile.jsx
@@ -1,20 +1,9 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
var UserStore = require('../stores/user_store.jsx');
-function getStateFromStores(userId) {
- var profile = UserStore.getProfile(userId);
-
- if (profile == null) {
- return { profile: { id: "0", username: "..."} };
- } else {
- return { profile: profile };
- }
-}
-
var id = 0;
function nextId() {
@@ -22,49 +11,76 @@ function nextId() {
return id;
}
+export default class UserProfile extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.uniqueId = nextId();
+
+ this.state = this.getStateFromStores(this.props.userId);
+ }
+ getStateFromStores(userId) {
+ var profile = UserStore.getProfile(userId);
+
+ if (profile == null) {
+ return {profile: {id: '0', username: '...'}};
+ }
-module.exports = React.createClass({
- uniqueId: null,
- componentDidMount: function() {
- UserStore.addChangeListener(this._onChange);
- $("#profile_" + this.uniqueId).popover({placement : 'right', container: 'body', trigger: 'hover', html: true, delay: { "show": 200, "hide": 100 }});
- $('body').tooltip( {selector: '[data-toggle=tooltip]', trigger: 'hover click'} );
- },
- componentWillUnmount: function() {
- UserStore.removeChangeListener(this._onChange);
- },
- _onChange: function(id) {
- if (id == this.props.userId) {
- var newState = getStateFromStores(this.props.userId);
- if (!utils.areStatesEqual(newState, this.state)) {
+ return {profile: profile};
+ }
+ componentDidMount() {
+ UserStore.addChangeListener(this.onChange);
+ $('#profile_' + this.uniqueId).popover({placement: 'right', container: 'body', trigger: 'hover', html: true, delay: {show: 200, hide: 100}});
+ $('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'});
+ }
+ componentWillUnmount() {
+ UserStore.removeChangeListener(this.onChange);
+ }
+ onChange(userId) {
+ if (userId === this.props.userId) {
+ var newState = this.getStateFromStores(this.props.userId);
+ if (!Utils.areStatesEqual(newState, this.state)) {
this.setState(newState);
}
}
- },
- componentWillReceiveProps: function(nextProps) {
- if (this.props.userId != nextProps.userId) {
- this.setState(getStateFromStores(nextProps.userId));
+ }
+ componentWillReceiveProps(nextProps) {
+ if (this.props.userId !== nextProps.userId) {
+ this.setState(this.getStateFromStores(nextProps.userId));
+ }
+ }
+ render() {
+ var name = this.state.profile.username;
+ if (this.props.overwriteName) {
+ name = this.props.overwriteName;
}
- },
- getInitialState: function() {
- this.uniqueId = nextId();
- return getStateFromStores(this.props.userId);
- },
- render: function() {
- var name = this.props.overwriteName ? this.props.overwriteName : this.state.profile.username;
-
- var data_content = "<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 = '<img class="user-popover__image" src="/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at + '" height="128" width="128" />';
if (!config.ShowEmail) {
- data_content += "<div class='text-nowrap'>Email not shared</div>";
+ dataContent += '<div class="text-nowrap">Email not shared</div>';
} else {
- data_content += "<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 += '<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>';
}
return (
- <div className="user-popover" id={"profile_" + this.uniqueId} data-toggle="popover" data-content={data_content} data-original-title={this.state.profile.username} >
- { name }
+ <div
+ className='user-popover'
+ id={'profile_' + this.uniqueId}
+ data-toggle='popover'
+ data-content={dataContent}
+ data-original-title={this.state.profile.username}
+ >
+ {name}
</div>
);
}
-});
+}
+
+UserProfile.defaultProps = {
+ userId: '',
+ overwriteName: ''
+};
+UserProfile.propTypes = {
+ userId: React.PropTypes.string,
+ overwriteName: React.PropTypes.string
+};
diff --git a/web/react/components/user_settings_appearance.jsx b/web/react/components/user_settings_appearance.jsx
index ba2d97ea8..878ec42fc 100644
--- a/web/react/components/user_settings_appearance.jsx
+++ b/web/react/components/user_settings_appearance.jsx
@@ -4,100 +4,133 @@
var UserStore = require('../stores/user_store.jsx');
var SettingItemMin = require('./setting_item_min.jsx');
var SettingItemMax = require('./setting_item_max.jsx');
-var client = require('../utils/client.jsx');
-var utils = require('../utils/utils.jsx');
+var Client = require('../utils/client.jsx');
+var Utils = require('../utils/utils.jsx');
-module.exports = React.createClass({
- submitTheme: function(e) {
+export default class UserSettingsAppearance extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitTheme = this.submitTheme.bind(this);
+ this.updateTheme = this.updateTheme.bind(this);
+ this.handleClose = this.handleClose.bind(this);
+
+ this.state = this.getStateFromStores();
+ }
+ getStateFromStores() {
+ var user = UserStore.getCurrentUser();
+ var theme = '#2389d7';
+ if (config.ThemeColors != null) {
+ theme = config.ThemeColors[0];
+ }
+ if (user.props && user.props.theme) {
+ theme = user.props.theme;
+ }
+
+ return {theme: theme.toLowerCase()};
+ }
+ submitTheme(e) {
e.preventDefault();
var user = UserStore.getCurrentUser();
- if (!user.props) user.props = {};
+ if (!user.props) {
+ user.props = {};
+ }
user.props.theme = this.state.theme;
- client.updateUser(user,
- function(data) {
- this.props.updateSection("");
+ Client.updateUser(user,
+ function success() {
+ this.props.updateSection('');
window.location.reload();
}.bind(this),
- function(err) {
- state = this.getInitialState();
- state.server_error = err;
+ function fail(err) {
+ var state = this.getStateFromStores();
+ state.serverError = err;
this.setState(state);
}.bind(this)
);
- },
- updateTheme: function(e) {
- var hex = utils.rgb2hex(e.target.style.backgroundColor);
- this.setState({ theme: hex.toLowerCase() });
- },
- handleClose: function() {
- this.setState({server_error: null});
+ }
+ updateTheme(e) {
+ var hex = Utils.rgb2hex(e.target.style.backgroundColor);
+ this.setState({theme: hex.toLowerCase()});
+ }
+ handleClose() {
+ this.setState({serverError: null});
this.props.updateTab('general');
- },
- componentDidMount: function() {
- if (this.props.activeSection === "theme") {
- $(this.refs[this.state.theme].getDOMNode()).addClass('active-border');
+ }
+ componentDidMount() {
+ if (this.props.activeSection === 'theme') {
+ $(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
}
$('#user_settings').on('hidden.bs.modal', this.handleClose);
- },
- componentDidUpdate: function() {
- if (this.props.activeSection === "theme") {
+ }
+ componentDidUpdate() {
+ if (this.props.activeSection === 'theme') {
$('.color-btn').removeClass('active-border');
- $(this.refs[this.state.theme].getDOMNode()).addClass('active-border');
+ $(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
}
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
$('#user_settings').off('hidden.bs.modal', this.handleClose);
this.props.updateSection('');
- },
- getInitialState: function() {
- var user = UserStore.getCurrentUser();
- var theme = config.ThemeColors != null ? config.ThemeColors[0] : "#2389d7";
- if (user.props && user.props.theme) {
- theme = user.props.theme;
+ }
+ render() {
+ var serverError;
+ if (this.state.serverError) {
+ serverError = this.state.serverError;
}
- return { theme: theme.toLowerCase() };
- },
- render: function() {
- var server_error = this.state.server_error ? this.state.server_error : null;
-
var themeSection;
var self = this;
if (config.ThemeColors != null) {
if (this.props.activeSection === 'theme') {
- var theme_buttons = [];
+ var themeButtons = [];
for (var i = 0; i < config.ThemeColors.length; i++) {
- theme_buttons.push(<button ref={config.ThemeColors[i]} type="button" className="btn btn-lg color-btn" style={{backgroundColor: config.ThemeColors[i]}} onClick={this.updateTheme} />);
+ themeButtons.push(
+ <button
+ ref={config.ThemeColors[i]}
+ type='button'
+ className='btn btn-lg color-btn'
+ style={{backgroundColor: config.ThemeColors[i]}}
+ onClick={this.updateTheme}
+ />
+ );
}
var inputs = [];
inputs.push(
- <li className="setting-list-item">
- <div className="btn-group" data-toggle="buttons-radio">
- { theme_buttons }
+ <li className='setting-list-item'>
+ <div
+ className='btn-group'
+ data-toggle='buttons-radio'
+ >
+ {themeButtons}
</div>
</li>
);
themeSection = (
<SettingItemMax
- title="Theme Color"
+ title='Theme Color'
inputs={inputs}
submit={this.submitTheme}
- server_error={server_error}
- updateSection={function(e){self.props.updateSection("");e.preventDefault;}}
+ serverError={serverError}
+ updateSection={function updateSection(e) {
+ self.props.updateSection('');
+ e.preventDefault();
+ }}
/>
);
} else {
themeSection = (
<SettingItemMin
- title="Theme Color"
+ title='Theme Color'
describe={this.state.theme}
- updateSection={function(){self.props.updateSection("theme");}}
+ updateSection={function updateSection() {
+ self.props.updateSection('theme');
+ }}
/>
);
}
@@ -105,17 +138,38 @@ module.exports = React.createClass({
return (
<div>
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title" ref="title"><i className="modal-back"></i>Appearance Settings</h4>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4
+ className='modal-title'
+ ref='title'
+ >
+ <i className='modal-back'></i>Appearance Settings
+ </h4>
</div>
- <div className="user-settings">
- <h3 className="tab-header">Appearance Settings</h3>
- <div className="divider-dark first"/>
+ <div className='user-settings'>
+ <h3 className='tab-header'>Appearance Settings</h3>
+ <div className='divider-dark first'/>
{themeSection}
- <div className="divider-dark"/>
+ <div className='divider-dark'/>
</div>
</div>
);
}
-});
+}
+
+UserSettingsAppearance.defaultProps = {
+ activeSection: ''
+};
+UserSettingsAppearance.propTypes = {
+ activeSection: React.PropTypes.string,
+ updateSection: React.PropTypes.func,
+ updateTab: React.PropTypes.func
+};
diff --git a/web/react/components/user_settings_general.jsx b/web/react/components/user_settings_general.jsx
index ddd2fb607..ead7ac1d5 100644
--- a/web/react/components/user_settings_general.jsx
+++ b/web/react/components/user_settings_general.jsx
@@ -194,7 +194,7 @@ export default class UserSettingsGeneralTab extends React.Component {
this.props.updateSection(section);
}
handleClose() {
- $(this.getDOMNode()).find('.form-control').each(function clearForms() {
+ $(React.findDOMNode(this)).find('.form-control').each(function clearForms() {
this.value = '';
});
@@ -230,7 +230,6 @@ export default class UserSettingsGeneralTab extends React.Component {
}
var nameSection;
- var self = this;
var inputs = [];
if (this.props.activeSection === 'name') {
@@ -276,9 +275,9 @@ export default class UserSettingsGeneralTab extends React.Component {
server_error={serverError}
client_error={clientError}
updateSection={function clearSection(e) {
- self.updateSection('');
+ this.updateSection('');
e.preventDefault();
- }}
+ }.bind(this)}
/>
);
} else {
@@ -297,8 +296,8 @@ export default class UserSettingsGeneralTab extends React.Component {
title='Full Name'
describe={fullName}
updateSection={function updateNameSection() {
- self.updateSection('name');
- }}
+ this.updateSection('name');
+ }.bind(this)}
/>
);
}
@@ -335,9 +334,9 @@ export default class UserSettingsGeneralTab extends React.Component {
server_error={serverError}
client_error={clientError}
updateSection={function clearSection(e) {
- self.updateSection('');
+ this.updateSection('');
e.preventDefault();
- }}
+ }.bind(this)}
/>
);
} else {
@@ -346,8 +345,8 @@ export default class UserSettingsGeneralTab extends React.Component {
title='Nickname'
describe={UserStore.getCurrentUser().nickname}
updateSection={function updateNicknameSection() {
- self.updateSection('nickname');
- }}
+ this.updateSection('nickname');
+ }.bind(this)}
/>
);
}
@@ -384,9 +383,9 @@ export default class UserSettingsGeneralTab extends React.Component {
server_error={serverError}
client_error={clientError}
updateSection={function clearSection(e) {
- self.updateSection('');
+ this.updateSection('');
e.preventDefault();
- }}
+ }.bind(this)}
/>
);
} else {
@@ -395,8 +394,8 @@ export default class UserSettingsGeneralTab extends React.Component {
title='Username'
describe={UserStore.getCurrentUser().username}
updateSection={function updateUsernameSection() {
- self.updateSection('username');
- }}
+ this.updateSection('username');
+ }.bind(this)}
/>
);
}
@@ -433,9 +432,9 @@ export default class UserSettingsGeneralTab extends React.Component {
server_error={serverError}
client_error={emailError}
updateSection={function clearSection(e) {
- self.updateSection('');
+ this.updateSection('');
e.preventDefault();
- }}
+ }.bind(this)}
/>
);
} else {
@@ -444,8 +443,8 @@ export default class UserSettingsGeneralTab extends React.Component {
title='Email'
describe={UserStore.getCurrentUser().email}
updateSection={function updateEmailSection() {
- self.updateSection('email');
- }}
+ this.updateSection('email');
+ }.bind(this)}
/>
);
}
@@ -460,9 +459,9 @@ export default class UserSettingsGeneralTab extends React.Component {
server_error={serverError}
client_error={clientError}
updateSection={function clearSection(e) {
- self.updateSection('');
+ this.updateSection('');
e.preventDefault();
- }}
+ }.bind(this)}
picture={this.state.picture}
pictureChange={this.updatePicture}
submitActive={this.submitActive}
@@ -479,8 +478,8 @@ export default class UserSettingsGeneralTab extends React.Component {
title='Profile Picture'
describe={minMessage}
updateSection={function updatePictureSection() {
- self.updateSection('picture');
- }}
+ this.updateSection('picture');
+ }.bind(this)}
/>
);
}
diff --git a/web/react/components/user_settings_modal.jsx b/web/react/components/user_settings_modal.jsx
index f5a555951..7ec75e000 100644
--- a/web/react/components/user_settings_modal.jsx
+++ b/web/react/components/user_settings_modal.jsx
@@ -4,51 +4,75 @@
var SettingsSidebar = require('./settings_sidebar.jsx');
var UserSettings = require('./user_settings.jsx');
-module.exports = React.createClass({
- componentDidMount: function() {
- $('body').on('click', '.modal-back', function(){
+export default class UserSettingsModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.updateTab = this.updateTab.bind(this);
+ this.updateSection = this.updateSection.bind(this);
+
+ this.state = {active_tab: 'general', active_section: ''};
+ }
+ componentDidMount() {
+ $('body').on('click', '.modal-back', function changeDisplay() {
$(this).closest('.modal-dialog').removeClass('display--content');
});
- $('body').on('click', '.modal-header .close', function(){
- setTimeout(function() {
+ $('body').on('click', '.modal-header .close', function closeModal() {
+ setTimeout(function finishClose() {
$('.modal-dialog.display--content').removeClass('display--content');
}, 500);
});
- },
- updateTab: function(tab) {
- this.setState({ active_tab: tab });
- },
- updateSection: function(section) {
- this.setState({ active_section: section });
- },
- getInitialState: function() {
- return { active_tab: "general", active_section: "" };
- },
- render: function() {
+ }
+ updateTab(tab) {
+ this.setState({active_tab: tab});
+ }
+ updateSection(section) {
+ this.setState({active_section: section});
+ }
+ render() {
var tabs = [];
- tabs.push({name: "general", uiName: "General", icon: "glyphicon glyphicon-cog"});
- tabs.push({name: "security", uiName: "Security", icon: "glyphicon glyphicon-lock"});
- tabs.push({name: "notifications", uiName: "Notifications", icon: "glyphicon glyphicon-exclamation-sign"});
- tabs.push({name: "appearance", uiName: "Appearance", icon: "glyphicon glyphicon-wrench"});
+ tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'});
+ tabs.push({name: 'security', uiName: 'Security', icon: 'glyphicon glyphicon-lock'});
+ tabs.push({name: 'notifications', uiName: 'Notifications', icon: 'glyphicon glyphicon-exclamation-sign'});
+ tabs.push({name: 'appearance', uiName: 'Appearance', icon: 'glyphicon glyphicon-wrench'});
return (
- <div className="modal fade" ref="modal" id="user_settings" role="dialog" tabIndex="-1" aria-hidden="true">
- <div className="modal-dialog settings-modal">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title" ref="title">Account Settings</h4>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='user_settings'
+ role='dialog'
+ tabIndex='-1'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog settings-modal'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4
+ className='modal-title'
+ ref='title'
+ >
+ Account Settings
+ </h4>
</div>
- <div className="modal-body">
- <div className="settings-table">
- <div className="settings-links">
+ <div className='modal-body'>
+ <div className='settings-table'>
+ <div className='settings-links'>
<SettingsSidebar
tabs={tabs}
activeTab={this.state.active_tab}
updateTab={this.updateTab}
/>
</div>
- <div className="settings-content minimize-settings">
+ <div className='settings-content minimize-settings'>
<UserSettings
activeTab={this.state.active_tab}
activeSection={this.state.active_section}
@@ -63,4 +87,4 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
diff --git a/web/react/components/user_settings_notifications.jsx b/web/react/components/user_settings_notifications.jsx
index ba0bda78e..b97f29e29 100644
--- a/web/react/components/user_settings_notifications.jsx
+++ b/web/react/components/user_settings_notifications.jsx
@@ -163,15 +163,15 @@ export default class NotificationsTab extends React.Component {
}
handleNotifyRadio(notifyLevel) {
this.setState({notifyLevel: notifyLevel});
- this.refs.wrapper.getDOMNode().focus();
+ React.findDOMNode(this.refs.wrapper).focus();
}
handleEmailRadio(enableEmail) {
this.setState({enableEmail: enableEmail});
- this.refs.wrapper.getDOMNode().focus();
+ React.findDOMNode(this.refs.wrapper).focus();
}
handleSoundRadio(enableSound) {
this.setState({enableSound: enableSound});
- this.refs.wrapper.getDOMNode().focus();
+ React.findDOMNode(this.refs.wrapper).focus();
}
updateUsernameKey(val) {
this.setState({usernameKey: val});
@@ -189,10 +189,10 @@ export default class NotificationsTab extends React.Component {
this.setState({channelKey: val});
}
updateCustomMentionKeys() {
- var checked = this.refs.customcheck.getDOMNode().checked;
+ var checked = React.findDOMNode(this.refs.customcheck).checked;
if (checked) {
- var text = this.refs.custommentions.getDOMNode().value;
+ var text = React.findDOMNode(this.refs.custommentions).value;
// remove all spaces and split string into individual keys
this.setState({customKeys: text.replace(/ /g, ''), customKeysChecked: true});
@@ -201,7 +201,7 @@ export default class NotificationsTab extends React.Component {
}
}
onCustomChange() {
- this.refs.customcheck.getDOMNode().checked = true;
+ React.findDOMNode(this.refs.customcheck).checked = true;
this.updateCustomMentionKeys();
}
render() {
@@ -210,8 +210,6 @@ export default class NotificationsTab extends React.Component {
serverError = this.state.serverError;
}
- var self = this;
-
var user = this.props.user;
var desktopSection;
@@ -229,12 +227,12 @@ export default class NotificationsTab extends React.Component {
let inputs = [];
inputs.push(
- <div>
+ <div key='userNotificationLevelOption'>
<div className='radio'>
<label>
<input type='radio'
checked={notifyActive[0]}
- onChange={self.handleNotifyRadio.bind(this, 'all')}
+ onChange={this.handleNotifyRadio.bind(this, 'all')}
>
For all activity
</input>
@@ -246,7 +244,7 @@ export default class NotificationsTab extends React.Component {
<input
type='radio'
checked={notifyActive[1]}
- onChange={self.handleNotifyRadio.bind(this, 'mention')}
+ onChange={this.handleNotifyRadio.bind(this, 'mention')}
>
Only for mentions and private messages
</input>
@@ -258,7 +256,7 @@ export default class NotificationsTab extends React.Component {
<input
type='radio'
checked={notifyActive[2]}
- onChange={self.handleNotifyRadio.bind(this, 'none')}
+ onChange={this.handleNotifyRadio.bind(this, 'none')}
>
Never
</input>
@@ -268,15 +266,15 @@ export default class NotificationsTab extends React.Component {
);
handleUpdateDesktopSection = function updateDesktopSection(e) {
- self.props.updateSection('');
+ this.props.updateSection('');
e.preventDefault();
- };
+ }.bind(this);
let extraInfo = (
<div className='setting-list__hint'>
These settings will override the global notification settings for the <b>{this.state.curChannel}</b> channel
</div>
- )
+ );
desktopSection = (
<SettingItemMax
@@ -299,8 +297,8 @@ export default class NotificationsTab extends React.Component {
}
handleUpdateDesktopSection = function updateDesktopSection() {
- self.props.updateSection('desktop');
- };
+ this.props.updateSection('desktop');
+ }.bind(this);
desktopSection = (
<SettingItemMin
@@ -324,13 +322,13 @@ export default class NotificationsTab extends React.Component {
let inputs = [];
inputs.push(
- <div>
+ <div key='userNotificationSoundOptions'>
<div className='radio'>
<label>
<input
type='radio'
checked={soundActive[0]}
- onChange={self.handleSoundRadio.bind(this, 'true')}
+ onChange={this.handleSoundRadio.bind(this, 'true')}
>
On
</input>
@@ -342,7 +340,7 @@ export default class NotificationsTab extends React.Component {
<input
type='radio'
checked={soundActive[1]}
- onChange={self.handleSoundRadio.bind(this, 'false')}
+ onChange={this.handleSoundRadio.bind(this, 'false')}
>
Off
</input>
@@ -353,9 +351,9 @@ export default class NotificationsTab extends React.Component {
);
handleUpdateSoundSection = function updateSoundSection(e) {
- self.props.updateSection('');
+ this.props.updateSection('');
e.preventDefault();
- };
+ }.bind(this);
soundSection = (
<SettingItemMax
@@ -377,8 +375,8 @@ export default class NotificationsTab extends React.Component {
}
handleUpdateSoundSection = function updateSoundSection() {
- self.props.updateSection('sound');
- };
+ this.props.updateSection('sound');
+ }.bind(this);
soundSection = (
<SettingItemMin
@@ -403,13 +401,13 @@ export default class NotificationsTab extends React.Component {
let inputs = [];
inputs.push(
- <div>
+ <div key='userNotificationEmailOptions'>
<div className='radio'>
<label>
<input
type='radio'
checked={emailActive[0]}
- onChange={self.handleEmailRadio.bind(this, 'true')}
+ onChange={this.handleEmailRadio.bind(this, 'true')}
>
On
</input>
@@ -421,7 +419,7 @@ export default class NotificationsTab extends React.Component {
<input
type='radio'
checked={emailActive[1]}
- onChange={self.handleEmailRadio.bind(this, 'false')}
+ onChange={this.handleEmailRadio.bind(this, 'false')}
>
Off
</input>
@@ -433,9 +431,9 @@ export default class NotificationsTab extends React.Component {
);
handleUpdateEmailSection = function updateEmailSection(e) {
- self.props.updateSection('');
+ this.props.updateSection('');
e.preventDefault();
- };
+ }.bind(this);
emailSection = (
<SettingItemMax
@@ -455,8 +453,8 @@ export default class NotificationsTab extends React.Component {
}
handleUpdateEmailSection = function updateEmailSection() {
- self.props.updateSection('email');
- };
+ this.props.updateSection('email');
+ }.bind(this);
emailSection = (
<SettingItemMin
@@ -480,10 +478,10 @@ export default class NotificationsTab extends React.Component {
if (user.first_name) {
handleUpdateFirstNameKey = function handleFirstNameKeyChange(e) {
- self.updateFirstNameKey(e.target.checked);
- };
+ this.updateFirstNameKey(e.target.checked);
+ }.bind(this);
inputs.push(
- <div>
+ <div key='userNotificationFirstNameOption'>
<div className='checkbox'>
<label>
<input
@@ -500,10 +498,10 @@ export default class NotificationsTab extends React.Component {
}
handleUpdateUsernameKey = function handleUsernameKeyChange(e) {
- self.updateUsernameKey(e.target.checked);
- };
+ this.updateUsernameKey(e.target.checked);
+ }.bind(this);
inputs.push(
- <div>
+ <div key='userNotificationUsernameOption'>
<div className='checkbox'>
<label>
<input
@@ -519,10 +517,10 @@ export default class NotificationsTab extends React.Component {
);
handleUpdateMentionKey = function handleMentionKeyChange(e) {
- self.updateMentionKey(e.target.checked);
- };
+ this.updateMentionKey(e.target.checked);
+ }.bind(this);
inputs.push(
- <div>
+ <div key='userNotificationMentionOption'>
<div className='checkbox'>
<label>
<input
@@ -538,10 +536,10 @@ export default class NotificationsTab extends React.Component {
);
handleUpdateAllKey = function handleAllKeyChange(e) {
- self.updateAllKey(e.target.checked);
- };
+ this.updateAllKey(e.target.checked);
+ }.bind(this);
inputs.push(
- <div>
+ <div key='userNotificationAllOption'>
<div className='checkbox'>
<label>
<input
@@ -557,10 +555,10 @@ export default class NotificationsTab extends React.Component {
);
handleUpdateChannelKey = function handleChannelKeyChange(e) {
- self.updateChannelKey(e.target.checked);
- };
+ this.updateChannelKey(e.target.checked);
+ }.bind(this);
inputs.push(
- <div>
+ <div key='userNotificationChannelOption'>
<div className='checkbox'>
<label>
<input
@@ -576,7 +574,7 @@ export default class NotificationsTab extends React.Component {
);
inputs.push(
- <div>
+ <div key='userNotificationCustomOption'>
<div className='checkbox'>
<label>
<input
@@ -600,9 +598,9 @@ export default class NotificationsTab extends React.Component {
);
handleUpdateKeysSection = function updateKeysSection(e) {
- self.props.updateSection('');
+ this.props.updateSection('');
e.preventDefault();
- };
+ }.bind(this);
keysSection = (
<SettingItemMax
title='Words that trigger mentions'
@@ -645,8 +643,8 @@ export default class NotificationsTab extends React.Component {
}
handleUpdateKeysSection = function updateKeysSection() {
- self.props.updateSection('keys');
- };
+ this.props.updateSection('keys');
+ }.bind(this);
keysSection = (
<SettingItemMin
diff --git a/web/react/components/user_settings_security.jsx b/web/react/components/user_settings_security.jsx
index ae8a5f0bc..a9f62097a 100644
--- a/web/react/components/user_settings_security.jsx
+++ b/web/react/components/user_settings_security.jsx
@@ -3,13 +3,23 @@
var SettingItemMin = require('./setting_item_min.jsx');
var SettingItemMax = require('./setting_item_max.jsx');
-var client = require('../utils/client.jsx');
+var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var Constants = require('../utils/constants.jsx');
-module.exports = React.createClass({
- displayName: 'SecurityTab',
- submitPassword: function(e) {
+export default class SecurityTab extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitPassword = this.submitPassword.bind(this);
+ this.updateCurrentPassword = this.updateCurrentPassword.bind(this);
+ this.updateNewPassword = this.updateNewPassword.bind(this);
+ this.updateConfirmPassword = this.updateConfirmPassword.bind(this);
+ this.handleClose = this.handleClose.bind(this);
+
+ this.state = {currentPassword: '', newPassword: '', confirmPassword: ''};
+ }
+ submitPassword(e) {
e.preventDefault();
var user = this.props.user;
@@ -37,13 +47,13 @@ module.exports = React.createClass({
data.current_password = currentPassword;
data.new_password = newPassword;
- client.updatePassword(data,
- function() {
+ Client.updatePassword(data,
+ function success() {
this.props.updateSection('');
AsyncClient.getMe();
this.setState({currentPassword: '', newPassword: '', confirmPassword: ''});
}.bind(this),
- function(err) {
+ function fail(err) {
var state = this.getInitialState();
if (err.message) {
state.serverError = err.message;
@@ -54,47 +64,49 @@ module.exports = React.createClass({
this.setState(state);
}.bind(this)
);
- },
- updateCurrentPassword: function(e) {
+ }
+ updateCurrentPassword(e) {
this.setState({currentPassword: e.target.value});
- },
- updateNewPassword: function(e) {
+ }
+ updateNewPassword(e) {
this.setState({newPassword: e.target.value});
- },
- updateConfirmPassword: function(e) {
+ }
+ updateConfirmPassword(e) {
this.setState({confirmPassword: e.target.value});
- },
- handleHistoryOpen: function() {
- $("#user_settings").modal('hide');
- },
- handleDevicesOpen: function() {
- $("#user_settings").modal('hide');
- },
- handleClose: function() {
- $(this.getDOMNode()).find('.form-control').each(function() {
+ }
+ handleHistoryOpen() {
+ $('#user_settings').modal('hide');
+ }
+ handleDevicesOpen() {
+ $('#user_settings').modal('hide');
+ }
+ handleClose() {
+ $(React.findDOMNode(this)).find('.form-control').each(function resetValue() {
this.value = '';
});
this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
this.props.updateTab('general');
- },
- componentDidMount: function() {
+ }
+ componentDidMount() {
$('#user_settings').on('hidden.bs.modal', this.handleClose);
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
$('#user_settings').off('hidden.bs.modal', this.handleClose);
this.props.updateSection('');
- },
- getInitialState: function() {
- return {currentPassword: '', newPassword: '', confirmPassword: ''};
- },
- render: function() {
- var serverError = this.state.serverError ? this.state.serverError : null;
- var passwordError = this.state.passwordError ? this.state.passwordError : null;
+ }
+ render() {
+ var serverError;
+ if (this.state.serverError) {
+ serverError = this.state.serverError;
+ }
+ var passwordError;
+ if (this.state.passwordError) {
+ passwordError = this.state.passwordError;
+ }
var updateSectionStatus;
var passwordSection;
- var self = this;
if (this.props.activeSection === 'password') {
var inputs = [];
var submit = null;
@@ -104,7 +116,12 @@ module.exports = React.createClass({
<div className='form-group'>
<label className='col-sm-5 control-label'>Current Password</label>
<div className='col-sm-7'>
- <input className='form-control' type='password' onChange={this.updateCurrentPassword} value={this.state.currentPassword}/>
+ <input
+ className='form-control'
+ type='password'
+ onChange={this.updateCurrentPassword}
+ value={this.state.currentPassword}
+ />
</div>
</div>
);
@@ -112,7 +129,12 @@ module.exports = React.createClass({
<div className='form-group'>
<label className='col-sm-5 control-label'>New Password</label>
<div className='col-sm-7'>
- <input className='form-control' type='password' onChange={this.updateNewPassword} value={this.state.newPassword}/>
+ <input
+ className='form-control'
+ type='password'
+ onChange={this.updateNewPassword}
+ value={this.state.newPassword}
+ />
</div>
</div>
);
@@ -120,7 +142,12 @@ module.exports = React.createClass({
<div className='form-group'>
<label className='col-sm-5 control-label'>Retype New Password</label>
<div className='col-sm-7'>
- <input className='form-control' type='password' onChange={this.updateConfirmPassword} value={this.state.confirmPassword}/>
+ <input
+ className='form-control'
+ type='password'
+ onChange={this.updateConfirmPassword}
+ value={this.state.confirmPassword}
+ />
</div>
</div>
);
@@ -134,11 +161,11 @@ module.exports = React.createClass({
);
}
- updateSectionStatus = function(e) {
- self.props.updateSection('');
- self.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
+ updateSectionStatus = function resetSection(e) {
+ this.props.updateSection('');
+ this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
e.preventDefault();
- };
+ }.bind(this);
passwordSection = (
<SettingItemMax
@@ -154,17 +181,27 @@ module.exports = React.createClass({
var describe;
if (this.props.user.auth_service === '') {
var d = new Date(this.props.user.last_password_update);
- var hour = d.getHours() % 12 ? String(d.getHours() % 12) : '12';
- var min = d.getMinutes() < 10 ? '0' + d.getMinutes() : String(d.getMinutes());
- var timeOfDay = d.getHours() >= 12 ? ' pm' : ' am';
+ var hour = '12';
+ if (d.getHours() % 12) {
+ hour = String(d.getHours() % 12);
+ }
+ var min = String(d.getMinutes());
+ if (d.getMinutes() < 10) {
+ min = '0' + d.getMinutes();
+ }
+ var timeOfDay = ' am';
+ if (d.getHours() >= 12) {
+ timeOfDay = ' pm';
+ }
+
describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay;
} else {
describe = 'Log in done through GitLab';
}
- updateSectionStatus = function() {
- self.props.updateSection('password');
- };
+ updateSectionStatus = function updateSection() {
+ this.props.updateSection('password');
+ }.bind(this);
passwordSection = (
<SettingItemMin
@@ -178,8 +215,20 @@ module.exports = React.createClass({
return (
<div>
<div className='modal-header'>
- <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
- <h4 className='modal-title' ref='title'><i className='modal-back'></i>Security Settings</h4>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4
+ className='modal-title'
+ ref='title'
+ >
+ <i className='modal-back'></i>Security Settings
+ </h4>
</div>
<div className='user-settings'>
<h3 className='tab-header'>Security Settings</h3>
@@ -187,11 +236,38 @@ module.exports = React.createClass({
{passwordSection}
<div className='divider-dark'/>
<br></br>
- <a data-toggle='modal' className='security-links theme' data-target='#access-history' href='#' onClick={this.handleHistoryOpen}><i className='fa fa-clock-o'></i>View Access History</a>
+ <a
+ data-toggle='modal'
+ className='security-links theme'
+ data-target='#access-history'
+ href='#'
+ onClick={this.handleHistoryOpen}
+ >
+ <i className='fa fa-clock-o'></i>View Access History
+ </a>
<b> </b>
- <a data-toggle='modal' className='security-links theme' data-target='#activity-log' href='#' onClick={this.handleDevicesOpen}><i className='fa fa-globe'></i>View and Logout of Active Sessions</a>
+ <a
+ data-toggle='modal'
+ className='security-links theme'
+ data-target='#activity-log'
+ href='#'
+ onClick={this.handleDevicesOpen}
+ >
+ <i className='fa fa-globe'></i>View and Logout of Active Sessions
+ </a>
</div>
</div>
);
}
-});
+}
+
+SecurityTab.defaultProps = {
+ user: {},
+ activeSection: ''
+};
+SecurityTab.propTypes = {
+ user: React.PropTypes.object,
+ activeSection: React.PropTypes.string,
+ updateSection: React.PropTypes.func,
+ updateTab: React.PropTypes.func
+};
diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx
index 24efce0f1..6e61ab156 100644
--- a/web/react/components/view_image.jsx
+++ b/web/react/components/view_image.jsx
@@ -2,35 +2,46 @@
// See License.txt for license information.
var Client = require('../utils/client.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
-module.exports = React.createClass({
- displayName: 'ViewImageModal',
- propTypes: {
- filenames: React.PropTypes.array,
- modalId: React.PropTypes.string,
- channelId: React.PropTypes.string,
- userId: React.PropTypes.string,
- startId: React.PropTypes.number
- },
- canSetState: false,
- handleNext: function() {
+export default class ViewImageModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.canSetState = false;
+
+ this.loadImage = this.loadImage.bind(this);
+ this.handleNext = this.handleNext.bind(this);
+ this.handlePrev = this.handlePrev.bind(this);
+ this.handleKeyPress = this.handleKeyPress.bind(this);
+ this.getPublicLink = this.getPublicLink.bind(this);
+ this.getPreviewImagePath = this.getPreviewImagePath.bind(this);
+
+ var loaded = [];
+ var progress = [];
+ for (var i = 0; i < this.props.filenames.length; i++) {
+ loaded.push(false);
+ progress.push(0);
+ }
+ this.state = {imgId: this.props.startId, viewed: false, loaded: loaded, progress: progress, images: {}, fileSizes: {}};
+ }
+ handleNext() {
var id = this.state.imgId + 1;
if (id > this.props.filenames.length - 1) {
id = 0;
}
this.setState({imgId: id});
this.loadImage(id);
- },
- handlePrev: function() {
+ }
+ handlePrev() {
var id = this.state.imgId - 1;
if (id < 0) {
id = this.props.filenames.length - 1;
}
this.setState({imgId: id});
this.loadImage(id);
- },
- handleKeyPress: function handleKeyPress(e) {
+ }
+ handleKeyPress(e) {
if (!e) {
return;
} else if (e.keyCode === 39) {
@@ -38,11 +49,11 @@ module.exports = React.createClass({
} else if (e.keyCode === 37) {
this.handlePrev();
}
- },
- componentWillReceiveProps: function(nextProps) {
+ }
+ componentWillReceiveProps(nextProps) {
this.setState({imgId: nextProps.startId});
- },
- loadImage: function(id) {
+ }
+ loadImage(id) {
var imgHeight = $(window).height() - 100;
if (this.state.loaded[id] || this.state.images[id]) {
$('.modal .modal-image .image-wrapper img').css('max-height', imgHeight);
@@ -51,26 +62,25 @@ module.exports = React.createClass({
var filename = this.props.filenames[id];
- var fileInfo = utils.splitFileLocation(filename);
- var fileType = utils.getFileType(fileInfo.ext);
+ var fileInfo = Utils.splitFileLocation(filename);
+ var fileType = Utils.getFileType(fileInfo.ext);
if (fileType === 'image') {
- var self = this;
var img = new Image();
img.load(this.getPreviewImagePath(filename),
- function() {
- var progress = self.state.progress;
+ function load() {
+ var progress = this.state.progress;
progress[id] = img.completedPercentage;
- self.setState({progress: progress});
- });
- img.onload = function onload(imgid) {
+ this.setState({progress: progress});
+ }.bind(this));
+ img.onload = (function onload(imgid) {
return function onloadReturn() {
- var loaded = self.state.loaded;
+ var loaded = this.state.loaded;
loaded[imgid] = true;
- self.setState({loaded: loaded});
- $(self.refs.image.getDOMNode()).css('max-height', imgHeight);
- };
- }(id);
+ this.setState({loaded: loaded});
+ $(React.findDOMNode(this.refs.image)).css('max-height', imgHeight);
+ }.bind(this);
+ }.bind(this)(id));
var images = this.state.images;
images[id] = img;
this.setState({images: images});
@@ -80,52 +90,51 @@ module.exports = React.createClass({
loaded[id] = true;
this.setState({loaded: loaded});
}
- },
- componentDidUpdate: function() {
+ }
+ componentDidUpdate() {
if (this.state.loaded[this.state.imgId]) {
if (this.refs.imageWrap) {
- $(this.refs.imageWrap.getDOMNode()).removeClass('default');
+ $(React.findDOMNode(this.refs.imageWrap)).removeClass('default');
}
}
- },
- componentDidMount: function() {
- var self = this;
+ }
+ componentDidMount() {
$('#' + this.props.modalId).on('shown.bs.modal', function onModalShow() {
- self.setState({viewed: true});
- self.loadImage(self.state.imgId);
- });
+ this.setState({viewed: true});
+ this.loadImage(this.state.imgId);
+ }.bind(this));
- $(this.refs.modal.getDOMNode()).click(function onModalClick(e) {
- if (e.target === this || e.target === self.refs.imageBody.getDOMNode()) {
+ $(React.findDOMNode(this.refs.modal)).click(function onModalClick(e) {
+ if (e.target === this || e.target === React.findDOMNode(this.refs.imageBody)) {
$('.image_modal').modal('hide');
}
- });
+ }.bind(this));
- $(this.refs.imageWrap.getDOMNode()).hover(
+ $(React.findDOMNode(this.refs.imageWrap)).hover(
function onModalHover() {
- $(self.refs.imageFooter.getDOMNode()).addClass('footer--show');
- }, function offModalHover() {
- $(self.refs.imageFooter.getDOMNode()).removeClass('footer--show');
- }
+ $(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show');
+ }.bind(this), function offModalHover() {
+ $(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show');
+ }.bind(this)
);
if (this.refs.previewArrowLeft) {
- $(this.refs.previewArrowLeft.getDOMNode()).hover(
+ $(React.findDOMNode(this.refs.previewArrowLeft)).hover(
function onModalHover() {
- $(self.refs.imageFooter.getDOMNode()).addClass('footer--show');
- }, function offModalHover() {
- $(self.refs.imageFooter.getDOMNode()).removeClass('footer--show');
- }
+ $(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show');
+ }.bind(this), function offModalHover() {
+ $(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show');
+ }.bind(this)
);
}
if (this.refs.previewArrowRight) {
- $(this.refs.previewArrowRight.getDOMNode()).hover(
+ $(React.findDOMNode(this.refs.previewArrowRight)).hover(
function onModalHover() {
- $(self.refs.imageFooter.getDOMNode()).addClass('footer--show');
- }, function offModalHover() {
- $(self.refs.imageFooter.getDOMNode()).removeClass('footer--show');
- }
+ $(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show');
+ }.bind(this), function offModalHover() {
+ $(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show');
+ }.bind(this)
);
}
@@ -133,90 +142,93 @@ module.exports = React.createClass({
// keep track of whether or not this component is mounted so we can safely set the state asynchronously
this.canSetState = true;
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
this.canSetState = false;
$(window).off('keyup', this.handleKeyPress);
- },
- getPublicLink: function() {
+ }
+ getPublicLink() {
var data = {};
data.channel_id = this.props.channelId;
data.user_id = this.props.userId;
data.filename = this.props.filenames[this.state.imgId];
Client.getPublicLink(data,
function sucess(serverData) {
- if (utils.isMobile()) {
+ if (Utils.isMobile()) {
window.location.href = serverData.public_link;
} else {
window.open(serverData.public_link);
}
},
- function error() {
- }
+ function error() {}
);
- },
- getPreviewImagePath: function(filename) {
+ }
+ getPreviewImagePath(filename) {
// Returns the path to a preview image that can be used to represent a file.
- var fileInfo = utils.splitFileLocation(filename);
- var fileType = utils.getFileType(fileInfo.ext);
+ var fileInfo = Utils.splitFileLocation(filename);
+ var fileType = Utils.getFileType(fileInfo.ext);
if (fileType === 'image') {
// This is a temporary patch to fix issue with old files using absolute paths
if (fileInfo.path.indexOf('/api/v1/files/get') !== -1) {
fileInfo.path = fileInfo.path.split('/api/v1/files/get')[1];
}
- fileInfo.path = utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path;
+ fileInfo.path = Utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path;
return fileInfo.path + '_preview.jpg';
}
// only images have proper previews, so just use a placeholder icon for non-images
- return utils.getPreviewImagePathForFileType(fileType);
- },
- getInitialState: function() {
- var loaded = [];
- var progress = [];
- for (var i = 0; i < this.props.filenames.length; i++) {
- loaded.push(false);
- progress.push(0);
- }
- return {imgId: this.props.startId, viewed: false, loaded: loaded, progress: progress, images: {}, fileSizes: {}};
- },
- render: function() {
+ return Utils.getPreviewImagePathForFileType(fileType);
+ }
+ render() {
if (this.props.filenames.length < 1 || this.props.filenames.length - 1 < this.state.imgId) {
return <div/>;
}
var filename = this.props.filenames[this.state.imgId];
- var fileUrl = utils.getFileUrl(filename);
+ var fileUrl = Utils.getFileUrl(filename);
- var name = decodeURIComponent(utils.getFileName(filename));
+ var name = decodeURIComponent(Utils.getFileName(filename));
var content;
var bgClass = '';
if (this.state.loaded[this.state.imgId]) {
- var fileInfo = utils.splitFileLocation(filename);
- var fileType = utils.getFileType(fileInfo.ext);
+ var fileInfo = Utils.splitFileLocation(filename);
+ var fileType = Utils.getFileType(fileInfo.ext);
if (fileType === 'image') {
// image files just show a preview of the file
content = (
- <a href={fileUrl} target='_blank'>
- <img ref='image' src={this.getPreviewImagePath(filename)}/>
+ <a
+ href={fileUrl}
+ target='_blank'
+ >
+ <img
+ ref='image'
+ src={this.getPreviewImagePath(filename)}
+ />
</a>
);
} else {
// non-image files include a section providing details about the file
var infoString = 'File type ' + fileInfo.ext.toUpperCase();
if (this.state.fileSizes[filename] && this.state.fileSizes[filename] >= 0) {
- infoString += ', Size ' + utils.fileSizeToString(this.state.fileSizes[filename]);
+ infoString += ', Size ' + Utils.fileSizeToString(this.state.fileSizes[filename]);
}
content = (
<div className='file-details__container'>
- <a className={'file-details__preview'} href={fileUrl} target='_blank'>
+ <a
+ className={'file-details__preview'}
+ href={fileUrl}
+ target='_blank'
+ >
<span className='file-details__preview-helper' />
- <img ref='image' src={this.getPreviewImagePath(filename)} />
+ <img
+ ref='image'
+ src={this.getPreviewImagePath(filename)}
+ />
</a>
<div className='file-details'>
<div className='file-details__name'>{name}</div>
@@ -228,19 +240,16 @@ module.exports = React.createClass({
// asynchronously request the actual size of this file
if (!(filename in this.state.fileSizes)) {
- var self = this;
-
Client.getFileInfo(
filename,
- function(data) {
- if (self.canSetState) {
- var fileSizes = self.state.fileSizes;
- fileSizes[filename] = parseInt(data["size"], 10);
- self.setState(fileSizes);
+ function success(data) {
+ if (this.canSetState) {
+ var fileSizes = this.state.fileSizes;
+ fileSizes[filename] = parseInt(data.size, 10);
+ this.setState(fileSizes);
}
- },
- function(err) {
- }
+ }.bind(this),
+ function fail() {}
);
}
}
@@ -250,14 +259,22 @@ module.exports = React.createClass({
if (percentage) {
content = (
<div>
- <img className='loader-image' src='/static/images/load.gif' />
- <span className='loader-percent' >{'Previewing ' + percentage + '%'}</span>
+ <img
+ className='loader-image'
+ src='/static/images/load.gif'
+ />
+ <span className='loader-percent'>
+ {'Previewing ' + percentage + '%'}
+ </span>
</div>
);
} else {
content = (
<div>
- <img className='loader-image' src='/static/images/load.gif' />
+ <img
+ className='loader-image'
+ src='/static/images/load.gif'
+ />
</div>
);
}
@@ -268,7 +285,14 @@ module.exports = React.createClass({
if (config.AllowPublicLink) {
publicLink = (
<div>
- <a href='#' className='public-link text' data-title='Public Image' onClick={this.getPublicLink}>Get Public Link</a>
+ <a
+ href='#'
+ className='public-link text'
+ data-title='Public Image'
+ onClick={this.getPublicLink}
+ >
+ Get Public Link
+ </a>
<span className='text'> | </span>
</div>
);
@@ -299,18 +323,43 @@ module.exports = React.createClass({
}
return (
- <div className='modal fade image_modal' ref='modal' id={this.props.modalId} tabIndex='-1' role='dialog' aria-hidden='true'>
+ <div
+ className='modal fade image_modal'
+ ref='modal'
+ id={this.props.modalId}
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
<div className='modal-dialog modal-image'>
<div className='modal-content image-content'>
- <div ref='imageBody' className='modal-body image-body'>
- <div ref='imageWrap' className={'image-wrapper default ' + bgClass}>
- <div className='modal-close' data-dismiss='modal'></div>
+ <div
+ ref='imageBody'
+ className='modal-body image-body'
+ >
+ <div
+ ref='imageWrap'
+ className={'image-wrapper default ' + bgClass}
+ >
+ <div
+ className='modal-close'
+ data-dismiss='modal'
+ />
{content}
- <div ref='imageFooter' className='modal-button-bar'>
+ <div
+ ref='imageFooter'
+ className='modal-button-bar'
+ >
<span className='pull-left text'>{'File ' + (this.state.imgId + 1) + ' of ' + this.props.filenames.length}</span>
<div className='image-links'>
{publicLink}
- <a href={fileUrl} download={name} className='text'>Download</a>
+ <a
+ href={fileUrl}
+ download={name}
+ className='text'
+ >
+ Download
+ </a>
</div>
</div>
</div>
@@ -322,4 +371,19 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
+
+ViewImageModal.defaultProps = {
+ filenames: [],
+ modalId: '',
+ channelId: '',
+ userId: '',
+ startId: 0
+};
+ViewImageModal.propTypes = {
+ filenames: React.PropTypes.array,
+ modalId: React.PropTypes.string,
+ channelId: React.PropTypes.string,
+ userId: React.PropTypes.string,
+ startId: React.PropTypes.number
+};
diff --git a/web/react/dispatcher/app_dispatcher.jsx b/web/react/dispatcher/app_dispatcher.jsx
index 4ae28e8eb..04e026f46 100644
--- a/web/react/dispatcher/app_dispatcher.jsx
+++ b/web/react/dispatcher/app_dispatcher.jsx
@@ -8,23 +8,21 @@ var Constants = require('../utils/constants.jsx');
var PayloadSources = Constants.PayloadSources;
var AppDispatcher = assign(new Dispatcher(), {
+ handleServerAction: function performServerAction(action) {
+ var payload = {
+ source: PayloadSources.SERVER_ACTION,
+ action: action
+ };
+ this.dispatch(payload);
+ },
- handleServerAction: function(action) {
- var payload = {
- source: PayloadSources.SERVER_ACTION,
- action: action
- };
- this.dispatch(payload);
- },
-
- handleViewAction: function(action) {
- var payload = {
- source: PayloadSources.VIEW_ACTION,
- action: action
- };
- this.dispatch(payload);
- }
-
+ handleViewAction: function performViewAction(action) {
+ var payload = {
+ source: PayloadSources.VIEW_ACTION,
+ action: action
+ };
+ this.dispatch(payload);
+ }
});
module.exports = AppDispatcher;
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index 98014ed12..d56b309fa 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -26,13 +26,13 @@ var ChannelMembersModal = require('../components/channel_members.jsx');
var ChannelInviteModal = require('../components/channel_invite_modal.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 ChannelLoader = require('../components/channel_loader.jsx');
var MentionList = require('../components/mention_list.jsx');
var ChannelInfoModal = require('../components/channel_info_modal.jsx');
var AccessHistoryModal = require('../components/access_history_modal.jsx');
var ActivityLogModal = require('../components/activity_log_modal.jsx');
-var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx')
+var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx');
var FileUploadOverlay = require('../components/file_upload_overlay.jsx');
var AsyncClient = require('../utils/async_client.jsx');
@@ -40,18 +40,18 @@ var AsyncClient = require('../utils/async_client.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-global.window.setup_channel_page = function(team_name, team_type, team_id, channel_name, channel_id) {
+function setupChannelPage(teamName, teamType, teamId, channelName, channelId) {
AsyncClient.getConfig();
AppDispatcher.handleViewAction({
type: ActionTypes.CLICK_CHANNEL,
- name: channel_name,
- id: channel_id
+ name: channelName,
+ id: channelId
});
AppDispatcher.handleViewAction({
type: ActionTypes.CLICK_TEAM,
- id: team_id
+ id: teamId
});
// ChannelLoader must be rendered first
@@ -66,12 +66,14 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann
);
React.render(
- <Navbar teamDisplayName={team_name} />,
+ <Navbar teamDisplayName={teamName} />,
document.getElementById('navbar')
);
React.render(
- <Sidebar teamDisplayName={team_name} teamType={team_type} />,
+ <Sidebar
+ teamDisplayName={teamName}
+ teamType={teamType} />,
document.getElementById('sidebar-left')
);
@@ -86,17 +88,17 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann
);
React.render(
- <TeamSettingsModal teamDisplayName={team_name} />,
+ <TeamSettingsModal teamDisplayName={teamName} />,
document.getElementById('team_settings_modal')
);
React.render(
- <TeamMembersModal teamDisplayName={team_name} />,
+ <TeamMembersModal teamDisplayName={teamName} />,
document.getElementById('team_members_modal')
);
React.render(
- <MemberInviteModal teamType={team_type} />,
+ <MemberInviteModal teamType={teamType} />,
document.getElementById('invite_member_modal')
);
@@ -186,7 +188,9 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann
);
React.render(
- <SidebarRightMenu teamDisplayName={team_name} teamType={team_type} />,
+ <SidebarRightMenu
+ teamDisplayName={teamName}
+ teamType={teamType} />,
document.getElementById('sidebar-menu')
);
@@ -225,5 +229,6 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann
overlayType='center' />,
document.getElementById('file_upload_overlay')
);
+}
-};
+global.window.setup_channel_page = setupChannelPage;
diff --git a/web/react/pages/find_team.jsx b/web/react/pages/find_team.jsx
index 5346c0cf0..dd11857ac 100644
--- a/web/react/pages/find_team.jsx
+++ b/web/react/pages/find_team.jsx
@@ -3,11 +3,11 @@
var FindTeam = require('../components/find_team.jsx');
-global.window.setup_find_team_page = function() {
-
+function setupFindTeamPage() {
React.render(
<FindTeam />,
document.getElementById('find-team')
);
+}
-};
+global.window.setup_find_team_page = setupFindTeamPage;
diff --git a/web/react/pages/home.jsx b/web/react/pages/home.jsx
index b12fa4949..18553542c 100644
--- a/web/react/pages/home.jsx
+++ b/web/react/pages/home.jsx
@@ -2,14 +2,15 @@
// See License.txt for license information.
var ChannelStore = require('../stores/channel_store.jsx');
-var TeamStore = require('../stores/team_store.jsx');
var Constants = require('../utils/constants.jsx');
-global.window.setup_home_page = function(teamURL) {
+function setupHomePage(teamURL) {
var last = ChannelStore.getLastVisitedName();
if (last == null || last.length === 0) {
- window.location = teamURL + "/channels/" + Constants.DEFAULT_CHANNEL;
+ window.location = teamURL + '/channels/' + Constants.DEFAULT_CHANNEL;
} else {
- window.location = teamURL + "/channels/" + last;
+ window.location = teamURL + '/channels/' + last;
}
}
+
+global.window.setup_home_page = setupHomePage;
diff --git a/web/react/pages/login.jsx b/web/react/pages/login.jsx
index 6e7528373..e7305889d 100644
--- a/web/react/pages/login.jsx
+++ b/web/react/pages/login.jsx
@@ -3,9 +3,14 @@
var Login = require('../components/login.jsx');
-global.window.setup_login_page = function(team_display_name, team_name, auth_services) {
+function setupLoginPage(teamDisplayName, teamName, authServices) {
React.render(
- <Login teamDisplayName={team_display_name} teamName={team_name} authServices={auth_services} />,
+ <Login
+ teamDisplayName={teamDisplayName}
+ teamName={teamName}
+ authServices={authServices} />,
document.getElementById('login')
);
-};
+}
+
+global.window.setup_login_page = setupLoginPage;
diff --git a/web/react/pages/password_reset.jsx b/web/react/pages/password_reset.jsx
index c7a208973..2ca468bea 100644
--- a/web/react/pages/password_reset.jsx
+++ b/web/react/pages/password_reset.jsx
@@ -3,17 +3,17 @@
var PasswordReset = require('../components/password_reset.jsx');
-global.window.setup_password_reset_page = function(is_reset, team_display_name, team_name, hash, data) {
-
+function setupPasswordResetPage(isReset, teamDisplayName, teamName, hash, data) {
React.render(
<PasswordReset
- isReset={is_reset}
- teamDisplayName={team_display_name}
- teamName={team_name}
+ isReset={isReset}
+ teamDisplayName={teamDisplayName}
+ teamName={teamName}
hash={hash}
data={data}
/>,
document.getElementById('reset')
);
+}
-};
+global.window.setup_password_reset_page = setupPasswordResetPage;
diff --git a/web/react/pages/signup_team.jsx b/web/react/pages/signup_team.jsx
index 4b58025ac..e9e803aa4 100644
--- a/web/react/pages/signup_team.jsx
+++ b/web/react/pages/signup_team.jsx
@@ -5,7 +5,7 @@ var SignupTeam = require('../components/signup_team.jsx');
var AsyncClient = require('../utils/async_client.jsx');
-global.window.setup_signup_team_page = function(authServices) {
+function setupSignupTeamPage(authServices) {
AsyncClient.getConfig();
var services = JSON.parse(authServices);
@@ -14,4 +14,6 @@ global.window.setup_signup_team_page = function(authServices) {
<SignupTeam services={services} />,
document.getElementById('signup-team')
);
-};
+}
+
+global.window.setup_signup_team_page = setupSignupTeamPage;
diff --git a/web/react/pages/signup_team_complete.jsx b/web/react/pages/signup_team_complete.jsx
index 71806c2ea..43e3aae65 100644
--- a/web/react/pages/signup_team_complete.jsx
+++ b/web/react/pages/signup_team_complete.jsx
@@ -1,11 +1,16 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var SignupTeamComplete =require('../components/signup_team_complete.jsx');
+var SignupTeamComplete = require('../components/signup_team_complete.jsx');
-global.window.setup_signup_team_complete_page = function(email, data, hash) {
+function setupSignupTeamCompletePage(email, data, hash) {
React.render(
- <SignupTeamComplete email={email} hash={hash} data={data}/>,
+ <SignupTeamComplete
+ email={email}
+ hash={hash}
+ data={data}/>,
document.getElementById('signup-team-complete')
);
-};
+}
+
+global.window.setup_signup_team_complete_page = setupSignupTeamCompletePage;
diff --git a/web/react/pages/signup_user_complete.jsx b/web/react/pages/signup_user_complete.jsx
index 8f9be1f94..71b526e5d 100644
--- a/web/react/pages/signup_user_complete.jsx
+++ b/web/react/pages/signup_user_complete.jsx
@@ -3,9 +3,18 @@
var SignupUserComplete = require('../components/signup_user_complete.jsx');
-global.window.setup_signup_user_complete_page = function(email, name, ui_name, id, data, hash, auth_services) {
+function setupSignupUserCompletePage(email, name, uiName, id, data, hash, authServices) {
React.render(
- <SignupUserComplete teamId={id} teamName={name} teamDisplayName={ui_name} email={email} hash={hash} data={data} authServices={auth_services} />,
+ <SignupUserComplete
+ teamId={id}
+ teamName={name}
+ teamDisplayName={uiName}
+ email={email}
+ hash={hash}
+ data={data}
+ authServices={authServices} />,
document.getElementById('signup-user-complete')
);
-};
+}
+
+global.window.setup_signup_user_complete_page = setupSignupUserCompletePage;
diff --git a/web/react/pages/verify.jsx b/web/react/pages/verify.jsx
index 96b556983..f42913315 100644
--- a/web/react/pages/verify.jsx
+++ b/web/react/pages/verify.jsx
@@ -5,7 +5,10 @@ var EmailVerify = require('../components/email_verify.jsx');
global.window.setupVerifyPage = function setupVerifyPage(isVerified, teamURL, userEmail) {
React.render(
- <EmailVerify isVerified={isVerified} teamURL={teamURL} userEmail={userEmail} />,
+ <EmailVerify
+ isVerified={isVerified}
+ teamURL={teamURL}
+ userEmail={userEmail} />,
document.getElementById('verify')
);
};
diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx
index b1f51e5f4..e1ca52746 100644
--- a/web/react/stores/browser_store.jsx
+++ b/web/react/stores/browser_store.jsx
@@ -12,81 +12,70 @@ function getPrefix() {
// Also change model/utils.go ETAG_ROOT_VERSION
var BROWSER_STORE_VERSION = '.5';
-module.exports = {
- initialized: false,
+class BrowserStoreClass {
+ constructor() {
+ this.getItem = this.getItem.bind(this);
+ this.setItem = this.setItem.bind(this);
+ this.removeItem = this.removeItem.bind(this);
+ this.setGlobalItem = this.setGlobalItem.bind(this);
+ this.getGlobalItem = this.getGlobalItem.bind(this);
+ this.removeGlobalItem = this.removeGlobalItem.bind(this);
+ this.clear = this.clear.bind(this);
+ this.actionOnItemsWithPrefix = this.actionOnItemsWithPrefix.bind(this);
+ this.isLocalStorageSupported = this.isLocalStorageSupported.bind(this);
- initialize: function() {
var currentVersion = localStorage.getItem('local_storage_version');
if (currentVersion !== BROWSER_STORE_VERSION) {
this.clear();
localStorage.setItem('local_storage_version', BROWSER_STORE_VERSION);
}
- this.initialized = true;
- },
+ }
- getItem: function(name, defaultValue) {
+ getItem(name, defaultValue) {
return this.getGlobalItem(getPrefix() + name, defaultValue);
- },
+ }
- setItem: function(name, value) {
+ setItem(name, value) {
this.setGlobalItem(getPrefix() + name, value);
- },
-
- removeItem: function(name) {
- if (!this.initialized) {
- this.initialize();
- }
+ }
+ removeItem(name) {
localStorage.removeItem(getPrefix() + name);
- },
-
- setGlobalItem: function(name, value) {
- if (!this.initialized) {
- this.initialize();
- }
+ }
+ setGlobalItem(name, value) {
localStorage.setItem(name, JSON.stringify(value));
- },
-
- getGlobalItem: function(name, defaultValue) {
- if (!this.initialized) {
- this.initialize();
- }
+ }
+ getGlobalItem(name, defaultValue) {
var result = null;
try {
result = JSON.parse(localStorage.getItem(name));
- } catch (err) {}
+ } catch (err) {
+ result = null;
+ }
if (result === null && typeof defaultValue !== 'undefined') {
result = defaultValue;
}
return result;
- },
-
- removeGlobalItem: function(name) {
- if (!this.initialized) {
- this.initialize();
- }
+ }
+ removeGlobalItem(name) {
localStorage.removeItem(name);
- },
+ }
- clear: function() {
+ clear() {
localStorage.clear();
sessionStorage.clear();
- },
+ }
/**
* Preforms the given action on each item that has the given prefix
* Signature for action is action(key, value)
*/
- actionOnItemsWithPrefix: function(prefix, action) {
- if (!this.initialized) {
- this.initialize();
- }
-
+ actionOnItemsWithPrefix(prefix, action) {
var globalPrefix = getPrefix();
var globalPrefixiLen = globalPrefix.length;
for (var key in localStorage) {
@@ -95,9 +84,9 @@ module.exports = {
action(userkey, this.getGlobalItem(key));
}
}
- },
+ }
- isLocalStorageSupported: function() {
+ isLocalStorageSupported() {
try {
sessionStorage.setItem('testSession', '1');
sessionStorage.removeItem('testSession');
@@ -113,4 +102,7 @@ module.exports = {
return false;
}
}
-};
+}
+
+var BrowserStore = new BrowserStoreClass();
+export default BrowserStore;
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index 678d50bbd..bd655b767 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -3,7 +3,6 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var EventEmitter = require('events').EventEmitter;
-var assign = require('object-assign');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -14,36 +13,42 @@ var CHANGE_EVENT = 'change';
var MORE_CHANGE_EVENT = 'change';
var EXTRA_INFO_EVENT = 'extra_info';
-var ChannelStore = assign({}, EventEmitter.prototype, {
- currentId: null,
- emitChange: function() {
+class ChannelStoreClass extends EventEmitter {
+ constructor(props) {
+ super(props);
+
+ this.setMaxListeners(11);
+
+ this.currentId = null;
+ }
+ emitChange() {
this.emit(CHANGE_EVENT);
- },
- addChangeListener: function(callback) {
+ }
+ addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
- },
- removeChangeListener: function(callback) {
+ }
+ removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
- },
- emitMoreChange: function() {
+ }
+ emitMoreChange() {
this.emit(MORE_CHANGE_EVENT);
- },
- addMoreChangeListener: function(callback) {
+ }
+ addMoreChangeListener(callback) {
this.on(MORE_CHANGE_EVENT, callback);
- },
- removeMoreChangeListener: function(callback) {
+ }
+ removeMoreChangeListener(callback) {
this.removeListener(MORE_CHANGE_EVENT, callback);
- },
- emitExtraInfoChange: function() {
+ }
+ emitExtraInfoChange() {
this.emit(EXTRA_INFO_EVENT);
- },
- addExtraInfoChangeListener: function(callback) {
+ }
+ addExtraInfoChangeListener(callback) {
this.on(EXTRA_INFO_EVENT, callback);
- },
- removeExtraInfoChangeListener: function(callback) {
+ }
+ removeExtraInfoChangeListener(callback) {
this.removeListener(EXTRA_INFO_EVENT, callback);
- },
- findFirstBy: function(field, value) {
+ }
+ findFirstBy(field, value) {
var channels = this.pGetChannels();
for (var i = 0; i < channels.length; i++) {
if (channels[i][field] === value) {
@@ -52,39 +57,39 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
}
return null;
- },
- get: function(id) {
+ }
+ get(id) {
return this.findFirstBy('id', id);
- },
- getMember: function(id) {
+ }
+ getMember(id) {
return this.getAllMembers()[id];
- },
- getByName: function(name) {
+ }
+ getByName(name) {
return this.findFirstBy('name', name);
- },
- getAll: function() {
+ }
+ getAll() {
return this.pGetChannels();
- },
- getAllMembers: function() {
+ }
+ getAllMembers() {
return this.pGetChannelMembers();
- },
- getMoreAll: function() {
+ }
+ getMoreAll() {
return this.pGetMoreChannels();
- },
- setCurrentId: function(id) {
+ }
+ setCurrentId(id) {
this.currentId = id;
- },
- setLastVisitedName: function(name) {
+ }
+ setLastVisitedName(name) {
if (name == null) {
BrowserStore.removeItem('last_visited_name');
} else {
BrowserStore.setItem('last_visited_name', name);
}
- },
- getLastVisitedName: function() {
+ }
+ getLastVisitedName() {
return BrowserStore.getItem('last_visited_name');
- },
- resetCounts: function(id) {
+ }
+ resetCounts(id) {
var cm = this.pGetChannelMembers();
for (var cmid in cm) {
if (cm[cmid].channel_id === id) {
@@ -97,36 +102,36 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
}
}
this.pStoreChannelMembers(cm);
- },
- getCurrentId: function() {
+ }
+ getCurrentId() {
return this.currentId;
- },
- getCurrent: function() {
+ }
+ getCurrent() {
var currentId = this.getCurrentId();
if (currentId) {
return this.get(currentId);
- } else {
- return null;
}
- },
- getCurrentMember: function() {
- var currentId = ChannelStore.getCurrentId();
+
+ return null;
+ }
+ getCurrentMember() {
+ var currentId = this.getCurrentId();
if (currentId) {
return this.getAllMembers()[currentId];
- } else {
- return null;
}
- },
- setChannelMember: function(member) {
+
+ return null;
+ }
+ setChannelMember(member) {
var members = this.pGetChannelMembers();
members[member.channel_id] = member;
this.pStoreChannelMembers(members);
this.emitChange();
- },
- getCurrentExtraInfo: function() {
- var currentId = ChannelStore.getCurrentId();
+ }
+ getCurrentExtraInfo() {
+ var currentId = this.getCurrentId();
var extra = null;
if (currentId) {
@@ -138,8 +143,8 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
}
return extra;
- },
- getExtraInfo: function(channelId) {
+ }
+ getExtraInfo(channelId) {
var extra = null;
if (channelId) {
@@ -151,8 +156,8 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
}
return extra;
- },
- pStoreChannel: function(channel) {
+ }
+ pStoreChannel(channel) {
var channels = this.pGetChannels();
var found;
@@ -179,28 +184,28 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
});
this.pStoreChannels(channels);
- },
- pStoreChannels: function(channels) {
+ }
+ pStoreChannels(channels) {
BrowserStore.setItem('channels', channels);
- },
- pGetChannels: function() {
+ }
+ pGetChannels() {
return BrowserStore.getItem('channels', []);
- },
- pStoreChannelMember: function(channelMember) {
+ }
+ pStoreChannelMember(channelMember) {
var members = this.pGetChannelMembers();
members[channelMember.channel_id] = channelMember;
this.pStoreChannelMembers(members);
- },
- pStoreChannelMembers: function(channelMembers) {
+ }
+ pStoreChannelMembers(channelMembers) {
BrowserStore.setItem('channel_members', channelMembers);
- },
- pGetChannelMembers: function() {
+ }
+ pGetChannelMembers() {
return BrowserStore.getItem('channel_members', {});
- },
- pStoreMoreChannels: function(channels) {
+ }
+ pStoreMoreChannels(channels) {
BrowserStore.setItem('more_channels', channels);
- },
- pGetMoreChannels: function() {
+ }
+ pGetMoreChannels() {
var channels = BrowserStore.getItem('more_channels');
if (channels == null) {
@@ -209,66 +214,67 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
}
return channels;
- },
- pStoreExtraInfos: function(extraInfos) {
+ }
+ pStoreExtraInfos(extraInfos) {
BrowserStore.setItem('extra_infos', extraInfos);
- },
- pGetExtraInfos: function() {
+ }
+ pGetExtraInfos() {
return BrowserStore.getItem('extra_infos', {});
- },
- isDefault: function(channel) {
+ }
+ isDefault(channel) {
return channel.name === Constants.DEFAULT_CHANNEL;
}
-});
+}
+
+var ChannelStore = new ChannelStoreClass();
-ChannelStore.dispatchToken = AppDispatcher.register(function(payload) {
+ChannelStore.dispatchToken = AppDispatcher.register(function handleAction(payload) {
var action = payload.action;
var currentId;
- switch(action.type) {
-
- case ActionTypes.CLICK_CHANNEL:
- ChannelStore.setCurrentId(action.id);
- ChannelStore.setLastVisitedName(action.name);
- ChannelStore.resetCounts(action.id);
- ChannelStore.emitChange();
- break;
-
- case ActionTypes.RECIEVED_CHANNELS:
- ChannelStore.pStoreChannels(action.channels);
- ChannelStore.pStoreChannelMembers(action.members);
- currentId = ChannelStore.getCurrentId();
- if (currentId) {
- ChannelStore.resetCounts(currentId);
- }
- ChannelStore.emitChange();
- break;
-
- case ActionTypes.RECIEVED_CHANNEL:
- ChannelStore.pStoreChannel(action.channel);
- ChannelStore.pStoreChannelMember(action.member);
- currentId = ChannelStore.getCurrentId();
- if (currentId) {
- ChannelStore.resetCounts(currentId);
- }
- ChannelStore.emitChange();
- break;
+ switch (action.type) {
+ case ActionTypes.CLICK_CHANNEL:
+ ChannelStore.setCurrentId(action.id);
+ ChannelStore.setLastVisitedName(action.name);
+ ChannelStore.resetCounts(action.id);
+ ChannelStore.emitChange();
+ break;
+
+ case ActionTypes.RECIEVED_CHANNELS:
+ ChannelStore.pStoreChannels(action.channels);
+ ChannelStore.pStoreChannelMembers(action.members);
+ currentId = ChannelStore.getCurrentId();
+ if (currentId) {
+ ChannelStore.resetCounts(currentId);
+ }
+ ChannelStore.emitChange();
+ break;
+
+ case ActionTypes.RECIEVED_CHANNEL:
+ ChannelStore.pStoreChannel(action.channel);
+ ChannelStore.pStoreChannelMember(action.member);
+ currentId = ChannelStore.getCurrentId();
+ if (currentId) {
+ ChannelStore.resetCounts(currentId);
+ }
+ ChannelStore.emitChange();
+ break;
- case ActionTypes.RECIEVED_MORE_CHANNELS:
- ChannelStore.pStoreMoreChannels(action.channels);
- ChannelStore.emitMoreChange();
- break;
+ case ActionTypes.RECIEVED_MORE_CHANNELS:
+ ChannelStore.pStoreMoreChannels(action.channels);
+ ChannelStore.emitMoreChange();
+ break;
- case ActionTypes.RECIEVED_CHANNEL_EXTRA_INFO:
- var extraInfos = ChannelStore.pGetExtraInfos();
- extraInfos[action.extra_info.id] = action.extra_info;
- ChannelStore.pStoreExtraInfos(extraInfos);
- ChannelStore.emitExtraInfoChange();
- break;
+ case ActionTypes.RECIEVED_CHANNEL_EXTRA_INFO:
+ var extraInfos = ChannelStore.pGetExtraInfos();
+ extraInfos[action.extra_info.id] = action.extra_info;
+ ChannelStore.pStoreExtraInfos(extraInfos);
+ ChannelStore.emitExtraInfoChange();
+ break;
- default:
+ default:
+ break;
}
});
-ChannelStore.setMaxListeners(11);
-module.exports = ChannelStore;
+export default ChannelStore;
diff --git a/web/react/stores/config_store.jsx b/web/react/stores/config_store.jsx
index 7ff177b35..b397937be 100644
--- a/web/react/stores/config_store.jsx
+++ b/web/react/stores/config_store.jsx
@@ -3,7 +3,6 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var EventEmitter = require('events').EventEmitter;
-var assign = require('object-assign');
var BrowserStore = require('../stores/browser_store.jsx');
@@ -12,45 +11,59 @@ var ActionTypes = Constants.ActionTypes;
var CHANGE_EVENT = 'change';
-var ConfigStore = assign({}, EventEmitter.prototype, {
- emitChange: function emitChange() {
+class ConfigStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.emitChange = this.emitChange.bind(this);
+ this.addChangeListener = this.addChangeListener.bind(this);
+ this.removeChangeListener = this.removeChangeListener.bind(this);
+ this.getSetting = this.getSetting.bind(this);
+ this.getSettingAsBoolean = this.getSettingAsBoolean.bind(this);
+ this.updateStoredSettings = this.updateStoredSettings.bind(this);
+ }
+ emitChange() {
this.emit(CHANGE_EVENT);
- },
- addChangeListener: function addChangeListener(callback) {
+ }
+ addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
- },
- removeChangeListener: function removeChangeListener(callback) {
+ }
+ removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
- },
- getSetting: function getSetting(key, defaultValue) {
+ }
+ getSetting(key, defaultValue) {
return BrowserStore.getItem('config_' + key, defaultValue);
- },
- getSettingAsBoolean: function getSettingAsNumber(key, defaultValue) {
- var value = ConfigStore.getSetting(key, defaultValue);
+ }
+ getSettingAsBoolean(key, defaultValue) {
+ var value = this.getSetting(key, defaultValue);
if (typeof value !== 'string') {
- return !!value;
- } else {
- return value === 'true';
+ return Boolean(value);
}
- },
- updateStoredSettings: function updateStoredSettings(settings) {
- for (var key in settings) {
- BrowserStore.setItem('config_' + key, settings[key]);
+
+ return value === 'true';
+ }
+ updateStoredSettings(settings) {
+ for (let key in settings) {
+ if (settings.hasOwnProperty(key)) {
+ BrowserStore.setItem('config_' + key, settings[key]);
+ }
}
}
-});
+}
+
+var ConfigStore = new ConfigStoreClass();
ConfigStore.dispatchToken = AppDispatcher.register(function registry(payload) {
var action = payload.action;
switch (action.type) {
- case ActionTypes.RECIEVED_CONFIG:
- ConfigStore.updateStoredSettings(action.settings);
- ConfigStore.emitChange();
- break;
- default:
+ case ActionTypes.RECIEVED_CONFIG:
+ ConfigStore.updateStoredSettings(action.settings);
+ ConfigStore.emitChange();
+ break;
+ default:
}
});
-module.exports = ConfigStore;
+export default ConfigStore;
diff --git a/web/react/stores/error_store.jsx b/web/react/stores/error_store.jsx
index 203b692ec..597c88cff 100644
--- a/web/react/stores/error_store.jsx
+++ b/web/react/stores/error_store.jsx
@@ -3,7 +3,6 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var EventEmitter = require('events').EventEmitter;
-var assign = require('object-assign');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -12,43 +11,53 @@ var BrowserStore = require('../stores/browser_store.jsx');
var CHANGE_EVENT = 'change';
-var ErrorStore = assign({}, EventEmitter.prototype, {
-
- emitChange: function() {
- this.emit(CHANGE_EVENT);
- },
-
- addChangeListener: function(callback) {
- this.on(CHANGE_EVENT, callback);
- },
-
- removeChangeListener: function(callback) {
- this.removeListener(CHANGE_EVENT, callback);
- },
- handledError: function() {
- BrowserStore.removeItem("last_error");
- },
- getLastError: function() {
- return BrowserStore.getItem('last_error');
- },
-
- _storeLastError: function(error) {
- BrowserStore.setItem("last_error", error);
- },
-});
-
-ErrorStore.dispatchToken = AppDispatcher.register(function(payload) {
- var action = payload.action;
- switch(action.type) {
+class ErrorStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.emitChange = this.emitChange.bind(this);
+ this.addChangeListener = this.addChangeListener.bind(this);
+ this.removeChangeListener = this.removeChangeListener.bind(this);
+ this.handledError = this.handledError.bind(this);
+ this.getLastError = this.getLastError.bind(this);
+ this.storeLastError = this.storeLastError.bind(this);
+ }
+
+ emitChange() {
+ this.emit(CHANGE_EVENT);
+ }
+
+ addChangeListener(callback) {
+ this.on(CHANGE_EVENT, callback);
+ }
+
+ removeChangeListener(callback) {
+ this.removeListener(CHANGE_EVENT, callback);
+ }
+ handledError() {
+ BrowserStore.removeItem('last_error');
+ }
+ getLastError() {
+ return BrowserStore.getItem('last_error');
+ }
+
+ storeLastError(error) {
+ BrowserStore.setItem('last_error', error);
+ }
+}
+
+var ErrorStore = new ErrorStoreClass();
+
+ErrorStore.dispatchToken = AppDispatcher.register(function registry(payload) {
+ var action = payload.action;
+ switch (action.type) {
case ActionTypes.RECIEVED_ERROR:
- ErrorStore._storeLastError(action.err);
- ErrorStore.emitChange();
- break;
+ ErrorStore.storeLastError(action.err);
+ ErrorStore.emitChange();
+ break;
default:
- }
+ }
});
-module.exports = ErrorStore;
-
-
+export default ErrorStore;
diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx
index 4038814d2..5ffe65021 100644
--- a/web/react/stores/post_store.jsx
+++ b/web/react/stores/post_store.jsx
@@ -3,7 +3,6 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var EventEmitter = require('events').EventEmitter;
-var assign = require('object-assign');
var ChannelStore = require('../stores/channel_store.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
@@ -18,109 +17,169 @@ var SELECTED_POST_CHANGE_EVENT = 'selected_post_change';
var MENTION_DATA_CHANGE_EVENT = 'mention_data_change';
var ADD_MENTION_EVENT = 'add_mention';
-var PostStore = assign({}, EventEmitter.prototype, {
- emitChange: function emitChange() {
+class PostStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.emitChange = this.emitChange.bind(this);
+ this.addChangeListener = this.addChangeListener.bind(this);
+ this.removeChangeListener = this.removeChangeListener.bind(this);
+ this.emitSearchChange = this.emitSearchChange.bind(this);
+ this.addSearchChangeListener = this.addSearchChangeListener.bind(this);
+ this.removeSearchChangeListener = this.removeSearchChangeListener.bind(this);
+ this.emitSearchTermChange = this.emitSearchTermChange.bind(this);
+ this.addSearchTermChangeListener = this.addSearchTermChangeListener.bind(this);
+ this.removeSearchTermChangeListener = this.removeSearchTermChangeListener.bind(this);
+ this.emitSelectedPostChange = this.emitSelectedPostChange.bind(this);
+ this.addSelectedPostChangeListener = this.addSelectedPostChangeListener.bind(this);
+ this.removeSelectedPostChangeListener = this.removeSelectedPostChangeListener.bind(this);
+ this.emitMentionDataChange = this.emitMentionDataChange.bind(this);
+ this.addMentionDataChangeListener = this.addMentionDataChangeListener.bind(this);
+ this.removeMentionDataChangeListener = this.removeMentionDataChangeListener.bind(this);
+ this.emitAddMention = this.emitAddMention.bind(this);
+ this.addAddMentionListener = this.addAddMentionListener.bind(this);
+ this.removeAddMentionListener = this.removeAddMentionListener.bind(this);
+ this.getCurrentPosts = this.getCurrentPosts.bind(this);
+ this.storePosts = this.storePosts.bind(this);
+ this.pStorePosts = this.pStorePosts.bind(this);
+ this.getPosts = this.getPosts.bind(this);
+ this.storePost = this.storePost.bind(this);
+ this.pStorePost = this.pStorePost.bind(this);
+ this.removePost = this.removePost.bind(this);
+ this.storePendingPost = this.storePendingPost.bind(this);
+ this.pStorePendingPosts = this.pStorePendingPosts.bind(this);
+ this.getPendingPosts = this.getPendingPosts.bind(this);
+ this.storeUnseenDeletedPost = this.storeUnseenDeletedPost.bind(this);
+ this.storeUnseenDeletedPosts = this.storeUnseenDeletedPosts.bind(this);
+ this.getUnseenDeletedPosts = this.getUnseenDeletedPosts.bind(this);
+ this.clearUnseenDeletedPosts = this.clearUnseenDeletedPosts.bind(this);
+ this.removePendingPost = this.removePendingPost.bind(this);
+ this.pRemovePendingPost = this.pRemovePendingPost.bind(this);
+ this.clearPendingPosts = this.clearPendingPosts.bind(this);
+ this.updatePendingPost = this.updatePendingPost.bind(this);
+ this.storeSearchResults = this.storeSearchResults.bind(this);
+ this.getSearchResults = this.getSearchResults.bind(this);
+ this.getIsMentionSearch = this.getIsMentionSearch.bind(this);
+ this.storeSelectedPost = this.storeSelectedPost.bind(this);
+ this.getSelectedPost = this.getSelectedPost.bind(this);
+ this.storeSearchTerm = this.storeSearchTerm.bind(this);
+ this.getSearchTerm = this.getSearchTerm.bind(this);
+ this.getEmptyDraft = this.getEmptyDraft.bind(this);
+ this.storeCurrentDraft = this.storeCurrentDraft.bind(this);
+ this.getCurrentDraft = this.getCurrentDraft.bind(this);
+ this.storeDraft = this.storeDraft.bind(this);
+ this.getDraft = this.getDraft.bind(this);
+ this.storeCommentDraft = this.storeCommentDraft.bind(this);
+ this.getCommentDraft = this.getCommentDraft.bind(this);
+ this.clearDraftUploads = this.clearDraftUploads.bind(this);
+ this.clearCommentDraftUploads = this.clearCommentDraftUploads.bind(this);
+ this.storeLatestUpdate = this.storeLatestUpdate.bind(this);
+ this.getLatestUpdate = this.getLatestUpdate.bind(this);
+ }
+ emitChange() {
this.emit(CHANGE_EVENT);
- },
+ }
- addChangeListener: function addChangeListener(callback) {
+ addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
- },
+ }
- removeChangeListener: function removeChangeListener(callback) {
+ removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
- },
+ }
- emitSearchChange: function emitSearchChange() {
+ emitSearchChange() {
this.emit(SEARCH_CHANGE_EVENT);
- },
+ }
- addSearchChangeListener: function addSearchChangeListener(callback) {
+ addSearchChangeListener(callback) {
this.on(SEARCH_CHANGE_EVENT, callback);
- },
+ }
- removeSearchChangeListener: function removeSearchChangeListener(callback) {
+ removeSearchChangeListener(callback) {
this.removeListener(SEARCH_CHANGE_EVENT, callback);
- },
+ }
- emitSearchTermChange: function emitSearchTermChange(doSearch, isMentionSearch) {
+ emitSearchTermChange(doSearch, isMentionSearch) {
this.emit(SEARCH_TERM_CHANGE_EVENT, doSearch, isMentionSearch);
- },
+ }
- addSearchTermChangeListener: function addSearchTermChangeListener(callback) {
+ addSearchTermChangeListener(callback) {
this.on(SEARCH_TERM_CHANGE_EVENT, callback);
- },
+ }
- removeSearchTermChangeListener: function removeSearchTermChangeListener(callback) {
+ removeSearchTermChangeListener(callback) {
this.removeListener(SEARCH_TERM_CHANGE_EVENT, callback);
- },
+ }
- emitSelectedPostChange: function emitSelectedPostChange(fromSearch) {
+ emitSelectedPostChange(fromSearch) {
this.emit(SELECTED_POST_CHANGE_EVENT, fromSearch);
- },
+ }
- addSelectedPostChangeListener: function addSelectedPostChangeListener(callback) {
+ addSelectedPostChangeListener(callback) {
this.on(SELECTED_POST_CHANGE_EVENT, callback);
- },
+ }
- removeSelectedPostChangeListener: function removeSelectedPostChangeListener(callback) {
+ removeSelectedPostChangeListener(callback) {
this.removeListener(SELECTED_POST_CHANGE_EVENT, callback);
- },
+ }
- emitMentionDataChange: function emitMentionDataChange(id, mentionText) {
+ emitMentionDataChange(id, mentionText) {
this.emit(MENTION_DATA_CHANGE_EVENT, id, mentionText);
- },
+ }
- addMentionDataChangeListener: function addMentionDataChangeListener(callback) {
+ addMentionDataChangeListener(callback) {
this.on(MENTION_DATA_CHANGE_EVENT, callback);
- },
+ }
- removeMentionDataChangeListener: function removeMentionDataChangeListener(callback) {
+ removeMentionDataChangeListener(callback) {
this.removeListener(MENTION_DATA_CHANGE_EVENT, callback);
- },
+ }
- emitAddMention: function emitAddMention(id, username) {
+ emitAddMention(id, username) {
this.emit(ADD_MENTION_EVENT, id, username);
- },
+ }
- addAddMentionListener: function addAddMentionListener(callback) {
+ addAddMentionListener(callback) {
this.on(ADD_MENTION_EVENT, callback);
- },
+ }
- removeAddMentionListener: function removeAddMentionListener(callback) {
+ removeAddMentionListener(callback) {
this.removeListener(ADD_MENTION_EVENT, callback);
- },
+ }
- getCurrentPosts: function getCurrentPosts() {
+ getCurrentPosts() {
var currentId = ChannelStore.getCurrentId();
if (currentId != null) {
return this.getPosts(currentId);
}
return null;
- },
- storePosts: function storePosts(channelId, newPostList) {
+ }
+ storePosts(channelId, newPostList) {
if (isPostListNull(newPostList)) {
return;
}
- var postList = makePostListNonNull(PostStore.getPosts(channelId));
-
- for (var pid in newPostList.posts) {
- var np = newPostList.posts[pid];
- if (np.delete_at === 0) {
- postList.posts[pid] = np;
- if (postList.order.indexOf(pid) === -1) {
- postList.order.push(pid);
- }
- } else {
- if (pid in postList.posts) {
- delete postList.posts[pid];
- }
-
- var index = postList.order.indexOf(pid);
- if (index !== -1) {
- postList.order.splice(index, 1);
+ var postList = makePostListNonNull(this.getPosts(channelId));
+
+ for (let pid in newPostList.posts) {
+ if (newPostList.posts.hasOwnProperty(pid)) {
+ var np = newPostList.posts[pid];
+ if (np.delete_at === 0) {
+ postList.posts[pid] = np;
+ if (postList.order.indexOf(pid) === -1) {
+ postList.order.push(pid);
+ }
+ } else {
+ if (pid in postList.posts) {
+ delete postList.posts[pid];
+ }
+
+ var index = postList.order.indexOf(pid);
+ if (index !== -1) {
+ postList.order.splice(index, 1);
+ }
}
}
}
@@ -146,19 +205,19 @@ var PostStore = assign({}, EventEmitter.prototype, {
this.storeLatestUpdate(channelId, latestUpdate);
this.pStorePosts(channelId, postList);
this.emitChange();
- },
- pStorePosts: function pStorePosts(channelId, posts) {
+ }
+ pStorePosts(channelId, posts) {
BrowserStore.setItem('posts_' + channelId, posts);
- },
- getPosts: function getPosts(channelId) {
+ }
+ getPosts(channelId) {
return BrowserStore.getItem('posts_' + channelId);
- },
- storePost: function(post) {
+ }
+ storePost(post) {
this.pStorePost(post);
this.emitChange();
- },
- pStorePost: function(post) {
- var postList = PostStore.getPosts(post.channel_id);
+ }
+ pStorePost(post) {
+ var postList = this.getPosts(post.channel_id);
postList = makePostListNonNull(postList);
if (post.pending_post_id !== '') {
@@ -173,9 +232,9 @@ var PostStore = assign({}, EventEmitter.prototype, {
}
this.pStorePosts(post.channel_id, postList);
- },
- removePost: function(postId, channelId) {
- var postList = PostStore.getPosts(channelId);
+ }
+ removePost(postId, channelId) {
+ var postList = this.getPosts(channelId);
if (isPostListNull(postList)) {
return;
}
@@ -190,8 +249,8 @@ var PostStore = assign({}, EventEmitter.prototype, {
}
this.pStorePosts(channelId, postList);
- },
- storePendingPost: function(post) {
+ }
+ storePendingPost(post) {
post.state = Constants.POST_LOADING;
var postList = this.getPendingPosts(post.channel_id);
@@ -199,10 +258,10 @@ var PostStore = assign({}, EventEmitter.prototype, {
postList.posts[post.pending_post_id] = post;
postList.order.unshift(post.pending_post_id);
- this._storePendingPosts(post.channel_id, postList);
+ this.pStorePendingPosts(post.channel_id, postList);
this.emitChange();
- },
- _storePendingPosts: function(channelId, postList) {
+ }
+ pStorePendingPosts(channelId, postList) {
var posts = postList.posts;
// sort failed posts to the bottom
@@ -225,11 +284,11 @@ var PostStore = assign({}, EventEmitter.prototype, {
});
BrowserStore.setItem('pending_posts_' + channelId, postList);
- },
- getPendingPosts: function(channelId) {
+ }
+ getPendingPosts(channelId) {
return BrowserStore.getItem('pending_posts_' + channelId);
- },
- storeUnseenDeletedPost: function(post) {
+ }
+ storeUnseenDeletedPost(post) {
var posts = this.getUnseenDeletedPosts(post.channel_id);
if (!posts) {
@@ -241,21 +300,21 @@ var PostStore = assign({}, EventEmitter.prototype, {
posts[post.id] = post;
this.storeUnseenDeletedPosts(post.channel_id, posts);
- },
- storeUnseenDeletedPosts: function(channelId, posts) {
+ }
+ storeUnseenDeletedPosts(channelId, posts) {
BrowserStore.setItem('deleted_posts_' + channelId, posts);
- },
- getUnseenDeletedPosts: function(channelId) {
+ }
+ getUnseenDeletedPosts(channelId) {
return BrowserStore.getItem('deleted_posts_' + channelId);
- },
- clearUnseenDeletedPosts: function(channelId) {
+ }
+ clearUnseenDeletedPosts(channelId) {
BrowserStore.setItem('deleted_posts_' + channelId, {});
- },
- removePendingPost: function(channelId, pendingPostId) {
- this._removePendingPost(channelId, pendingPostId);
+ }
+ removePendingPost(channelId, pendingPostId) {
+ this.pRemovePendingPost(channelId, pendingPostId);
this.emitChange();
- },
- _removePendingPost: function(channelId, pendingPostId) {
+ }
+ pRemovePendingPost(channelId, pendingPostId) {
var postList = this.getPendingPosts(channelId);
postList = makePostListNonNull(postList);
@@ -267,14 +326,14 @@ var PostStore = assign({}, EventEmitter.prototype, {
postList.order.splice(index, 1);
}
- this._storePendingPosts(channelId, postList);
- },
- clearPendingPosts: function() {
+ this.pStorePendingPosts(channelId, postList);
+ }
+ clearPendingPosts() {
BrowserStore.actionOnItemsWithPrefix('pending_posts_', function clearPending(key) {
BrowserStore.removeItem(key);
});
- },
- updatePendingPost: function(post) {
+ }
+ updatePendingPost(post) {
var postList = this.getPendingPosts(post.channel_id);
postList = makePostListNonNull(postList);
@@ -283,112 +342,114 @@ var PostStore = assign({}, EventEmitter.prototype, {
}
postList.posts[post.pending_post_id] = post;
- this._storePendingPosts(post.channel_id, postList);
+ this.pStorePendingPosts(post.channel_id, postList);
this.emitChange();
- },
- storeSearchResults: function storeSearchResults(results, isMentionSearch) {
+ }
+ storeSearchResults(results, isMentionSearch) {
BrowserStore.setItem('search_results', results);
BrowserStore.setItem('is_mention_search', Boolean(isMentionSearch));
- },
- getSearchResults: function getSearchResults() {
+ }
+ getSearchResults() {
return BrowserStore.getItem('search_results');
- },
- getIsMentionSearch: function getIsMentionSearch() {
+ }
+ getIsMentionSearch() {
return BrowserStore.getItem('is_mention_search');
- },
- storeSelectedPost: function storeSelectedPost(postList) {
+ }
+ storeSelectedPost(postList) {
BrowserStore.setItem('select_post', postList);
- },
- getSelectedPost: function getSelectedPost() {
+ }
+ getSelectedPost() {
return BrowserStore.getItem('select_post');
- },
- storeSearchTerm: function storeSearchTerm(term) {
+ }
+ storeSearchTerm(term) {
BrowserStore.setItem('search_term', term);
- },
- getSearchTerm: function getSearchTerm() {
+ }
+ getSearchTerm() {
return BrowserStore.getItem('search_term');
- },
- getEmptyDraft: function getEmptyDraft(draft) {
+ }
+ getEmptyDraft() {
return {message: '', uploadsInProgress: [], previews: []};
- },
- storeCurrentDraft: function storeCurrentDraft(draft) {
+ }
+ storeCurrentDraft(draft) {
var channelId = ChannelStore.getCurrentId();
BrowserStore.setItem('draft_' + channelId, draft);
- },
- getCurrentDraft: function getCurrentDraft() {
+ }
+ getCurrentDraft() {
var channelId = ChannelStore.getCurrentId();
- return PostStore.getDraft(channelId);
- },
- storeDraft: function storeDraft(channelId, draft) {
+ return this.getDraft(channelId);
+ }
+ storeDraft(channelId, draft) {
BrowserStore.setItem('draft_' + channelId, draft);
- },
- getDraft: function getDraft(channelId) {
- return BrowserStore.getItem('draft_' + channelId, PostStore.getEmptyDraft());
- },
- storeCommentDraft: function storeCommentDraft(parentPostId, draft) {
+ }
+ getDraft(channelId) {
+ return BrowserStore.getItem('draft_' + channelId, this.getEmptyDraft());
+ }
+ storeCommentDraft(parentPostId, draft) {
BrowserStore.setItem('comment_draft_' + parentPostId, draft);
- },
- getCommentDraft: function getCommentDraft(parentPostId) {
- return BrowserStore.getItem('comment_draft_' + parentPostId, PostStore.getEmptyDraft());
- },
- clearDraftUploads: function clearDraftUploads() {
+ }
+ getCommentDraft(parentPostId) {
+ return BrowserStore.getItem('comment_draft_' + parentPostId, this.getEmptyDraft());
+ }
+ clearDraftUploads() {
BrowserStore.actionOnItemsWithPrefix('draft_', function clearUploads(key, value) {
if (value) {
value.uploadsInProgress = [];
BrowserStore.setItem(key, value);
}
});
- },
- clearCommentDraftUploads: function clearCommentDraftUploads() {
+ }
+ clearCommentDraftUploads() {
BrowserStore.actionOnItemsWithPrefix('comment_draft_', function clearUploads(key, value) {
if (value) {
value.uploadsInProgress = [];
BrowserStore.setItem(key, value);
}
});
- },
- storeLatestUpdate: function(channelId, time) {
+ }
+ storeLatestUpdate(channelId, time) {
BrowserStore.setItem('latest_post_' + channelId, time);
- },
- getLatestUpdate: function(channelId) {
+ }
+ getLatestUpdate(channelId) {
return BrowserStore.getItem('latest_post_' + channelId, 0);
}
-});
+}
+
+var PostStore = new PostStoreClass();
PostStore.dispatchToken = AppDispatcher.register(function registry(payload) {
var action = payload.action;
switch (action.type) {
- case ActionTypes.RECIEVED_POSTS:
- PostStore.storePosts(action.id, makePostListNonNull(action.post_list));
- break;
- case ActionTypes.RECIEVED_POST:
- PostStore.pStorePost(action.post);
- PostStore.emitChange();
- break;
- case ActionTypes.RECIEVED_SEARCH:
- PostStore.storeSearchResults(action.results, action.is_mention_search);
- PostStore.emitSearchChange();
- break;
- case ActionTypes.RECIEVED_SEARCH_TERM:
- PostStore.storeSearchTerm(action.term);
- PostStore.emitSearchTermChange(action.do_search, action.is_mention_search);
- break;
- case ActionTypes.RECIEVED_POST_SELECTED:
- PostStore.storeSelectedPost(action.post_list);
- PostStore.emitSelectedPostChange(action.from_search);
- break;
- case ActionTypes.RECIEVED_MENTION_DATA:
- PostStore.emitMentionDataChange(action.id, action.mention_text);
- break;
- case ActionTypes.RECIEVED_ADD_MENTION:
- PostStore.emitAddMention(action.id, action.username);
- break;
- default:
+ case ActionTypes.RECIEVED_POSTS:
+ PostStore.storePosts(action.id, makePostListNonNull(action.post_list));
+ break;
+ case ActionTypes.RECIEVED_POST:
+ PostStore.pStorePost(action.post);
+ PostStore.emitChange();
+ break;
+ case ActionTypes.RECIEVED_SEARCH:
+ PostStore.storeSearchResults(action.results, action.is_mention_search);
+ PostStore.emitSearchChange();
+ break;
+ case ActionTypes.RECIEVED_SEARCH_TERM:
+ PostStore.storeSearchTerm(action.term);
+ PostStore.emitSearchTermChange(action.do_search, action.is_mention_search);
+ break;
+ case ActionTypes.RECIEVED_POST_SELECTED:
+ PostStore.storeSelectedPost(action.post_list);
+ PostStore.emitSelectedPostChange(action.from_search);
+ break;
+ case ActionTypes.RECIEVED_MENTION_DATA:
+ PostStore.emitMentionDataChange(action.id, action.mention_text);
+ break;
+ case ActionTypes.RECIEVED_ADD_MENTION:
+ PostStore.emitAddMention(action.id, action.username);
+ break;
+ default:
}
});
-module.exports = PostStore;
+export default PostStore;
function makePostListNonNull(pl) {
var postList = pl;
diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx
index e43a8f2be..ae74059d1 100644
--- a/web/react/stores/socket_store.jsx
+++ b/web/react/stores/socket_store.jsx
@@ -2,10 +2,8 @@
// See License.txt for license information.
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var UserStore = require('./user_store.jsx')
+var UserStore = require('./user_store.jsx');
var EventEmitter = require('events').EventEmitter;
-var assign = require('object-assign');
-var client = require('../utils/client.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -14,14 +12,24 @@ var CHANGE_EVENT = 'change';
var conn;
-var SocketStore = assign({}, EventEmitter.prototype, {
- initialize: function() {
+class SocketStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.initialize = this.initialize.bind(this);
+ this.emitChange = this.emitChange.bind(this);
+ this.addChangeListener = this.addChangeListener.bind(this);
+ this.removeChangeListener = this.removeChangeListener.bind(this);
+ this.sendMessage = this.sendMessage.bind(this);
+
+ this.initialize();
+ }
+ initialize() {
if (!UserStore.getCurrentId()) {
return;
}
- var self = this;
- self.setMaxListeners(0);
+ this.setMaxListeners(0);
if (window.WebSocket && !conn) {
var protocol = 'ws://';
@@ -29,24 +37,24 @@ var SocketStore = assign({}, EventEmitter.prototype, {
protocol = 'wss://';
}
var connUrl = protocol + location.host + '/api/v1/websocket';
- console.log('connecting to ' + connUrl);
+ console.log('connecting to ' + connUrl); //eslint-disable-line no-console
conn = new WebSocket(connUrl);
conn.onclose = function closeConn(evt) {
- console.log('websocket closed');
- console.log(evt);
+ console.log('websocket closed'); //eslint-disable-line no-console
+ console.log(evt); //eslint-disable-line no-console
conn = null;
setTimeout(
function reconnect() {
- self.initialize();
- },
+ this.initialize();
+ }.bind(this),
3000
);
- };
+ }.bind(this);
conn.onerror = function connError(evt) {
- console.log('websocket error');
- console.log(evt);
+ console.log('websocket error'); //eslint-disable-line no-console
+ console.log(evt); //eslint-disable-line no-console
};
conn.onmessage = function connMessage(evt) {
@@ -56,17 +64,17 @@ var SocketStore = assign({}, EventEmitter.prototype, {
});
};
}
- },
- emitChange: function(msg) {
+ }
+ emitChange(msg) {
this.emit(CHANGE_EVENT, msg);
- },
- addChangeListener: function(callback) {
+ }
+ addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
- },
- removeChangeListener: function(callback) {
+ }
+ removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
- },
- sendMessage: function(msg) {
+ }
+ sendMessage(msg) {
if (conn && conn.readyState === WebSocket.OPEN) {
conn.send(JSON.stringify(msg));
} else if (!conn || conn.readyState === WebSocket.Closed) {
@@ -74,19 +82,20 @@ var SocketStore = assign({}, EventEmitter.prototype, {
this.initialize();
}
}
-});
+}
+
+var SocketStore = new SocketStoreClass();
-SocketStore.dispatchToken = AppDispatcher.register(function(payload) {
+SocketStore.dispatchToken = AppDispatcher.register(function registry(payload) {
var action = payload.action;
switch (action.type) {
- case ActionTypes.RECIEVED_MSG:
+ case ActionTypes.RECIEVED_MSG:
SocketStore.emitChange(action.msg);
break;
- default:
+ default:
}
});
-SocketStore.initialize();
-module.exports = SocketStore;
+export default SocketStore;
diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx
index 3f2248c44..1f33fe03b 100644
--- a/web/react/stores/team_store.jsx
+++ b/web/react/stores/team_store.jsx
@@ -3,7 +3,6 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var EventEmitter = require('events').EventEmitter;
-var assign = require('object-assign');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -19,21 +18,38 @@ function getWindowLocationOrigin() {
return utils.getWindowLocationOrigin();
}
-var TeamStore = assign({}, EventEmitter.prototype, {
- emitChange: function() {
+class TeamStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.emitChange = this.emitChange.bind(this);
+ this.addChangeListener = this.addChangeListener.bind(this);
+ this.removeChangeListener = this.removeChangeListener.bind(this);
+ this.get = this.get.bind(this);
+ this.getByName = this.getByName.bind(this);
+ this.getAll = this.getAll.bind(this);
+ this.setCurrentId = this.setCurrentId.bind(this);
+ this.getCurrentId = this.getCurrentId.bind(this);
+ this.getCurrent = this.getCurrent.bind(this);
+ this.getCurrentTeamUrl = this.getCurrentTeamUrl.bind(this);
+ this.storeTeam = this.storeTeam.bind(this);
+ this.pStoreTeams = this.pStoreTeams.bind(this);
+ this.pGetTeams = this.pGetTeams.bind(this);
+ }
+ emitChange() {
this.emit(CHANGE_EVENT);
- },
- addChangeListener: function(callback) {
+ }
+ addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
- },
- removeChangeListener: function(callback) {
+ }
+ removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
- },
- get: function(id) {
+ }
+ get(id) {
var c = this.pGetTeams();
return c[id];
- },
- getByName: function(name) {
+ }
+ getByName(name) {
var t = this.pGetTeams();
for (var id in t) {
@@ -43,64 +59,65 @@ var TeamStore = assign({}, EventEmitter.prototype, {
}
return null;
- },
- getAll: function() {
+ }
+ getAll() {
return this.pGetTeams();
- },
- setCurrentId: function(id) {
+ }
+ setCurrentId(id) {
if (id === null) {
BrowserStore.removeItem('current_team_id');
} else {
BrowserStore.setItem('current_team_id', id);
}
- },
- getCurrentId: function() {
+ }
+ getCurrentId() {
return BrowserStore.getItem('current_team_id');
- },
- getCurrent: function() {
- var currentId = TeamStore.getCurrentId();
+ }
+ getCurrent() {
+ var currentId = this.getCurrentId();
if (currentId !== null) {
return this.get(currentId);
}
return null;
- },
- getCurrentTeamUrl: function() {
+ }
+ getCurrentTeamUrl() {
if (this.getCurrent()) {
return getWindowLocationOrigin() + '/' + this.getCurrent().name;
}
return null;
- },
- storeTeam: function(team) {
+ }
+ storeTeam(team) {
var teams = this.pGetTeams();
teams[team.id] = team;
this.pStoreTeams(teams);
- },
- pStoreTeams: function(teams) {
+ }
+ pStoreTeams(teams) {
BrowserStore.setItem('user_teams', teams);
- },
- pGetTeams: function() {
+ }
+ pGetTeams() {
return BrowserStore.getItem('user_teams', {});
}
-});
+}
+
+var TeamStore = new TeamStoreClass();
TeamStore.dispatchToken = AppDispatcher.register(function registry(payload) {
var action = payload.action;
switch (action.type) {
+ case ActionTypes.CLICK_TEAM:
+ TeamStore.setCurrentId(action.id);
+ TeamStore.emitChange();
+ break;
- case ActionTypes.CLICK_TEAM:
- TeamStore.setCurrentId(action.id);
- TeamStore.emitChange();
- break;
-
- case ActionTypes.RECIEVED_TEAM:
- TeamStore.storeTeam(action.team);
- TeamStore.emitChange();
- break;
+ case ActionTypes.RECIEVED_TEAM:
+ TeamStore.storeTeam(action.team);
+ TeamStore.emitChange();
+ break;
- default:
+ default:
}
});
-module.exports = TeamStore;
+export default TeamStore;
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index 248495dac..f75c1d4c3 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -3,7 +3,6 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var EventEmitter = require('events').EventEmitter;
-var assign = require('object-assign');
var client = require('../utils/client.jsx');
var Constants = require('../utils/constants.jsx');
@@ -16,64 +15,114 @@ var CHANGE_EVENT_AUDITS = 'change_audits';
var CHANGE_EVENT_TEAMS = 'change_teams';
var CHANGE_EVENT_STATUSES = 'change_statuses';
-var UserStore = assign({}, EventEmitter.prototype, {
+class UserStoreClass extends EventEmitter {
+ constructor() {
+ super();
- gCurrentId: null,
+ this.emitChange = this.emitChange.bind(this);
+ this.addChangeListener = this.addChangeListener.bind(this);
+ this.removeChangeListener = this.removeChangeListener.bind(this);
+ this.emitSessionsChange = this.emitSessionsChange.bind(this);
+ this.addSessionsChangeListener = this.addSessionsChangeListener.bind(this);
+ this.removeSessionsChangeListener = this.removeSessionsChangeListener.bind(this);
+ this.emitAuditsChange = this.emitAuditsChange.bind(this);
+ this.addAuditsChangeListener = this.addAuditsChangeListener.bind(this);
+ this.removeAuditsChangeListener = this.removeAuditsChangeListener.bind(this);
+ this.emitTeamsChange = this.emitTeamsChange.bind(this);
+ this.addTeamsChangeListener = this.addTeamsChangeListener.bind(this);
+ this.removeTeamsChangeListener = this.removeTeamsChangeListener.bind(this);
+ this.emitStatusesChange = this.emitStatusesChange.bind(this);
+ this.addStatusesChangeListener = this.addStatusesChangeListener.bind(this);
+ this.removeStatusesChangeListener = this.removeStatusesChangeListener.bind(this);
+ this.setCurrentId = this.setCurrentId.bind(this);
+ this.getCurrentId = this.getCurrentId.bind(this);
+ this.getCurrentUser = this.getCurrentUser.bind(this);
+ this.setCurrentUser = this.setCurrentUser.bind(this);
+ this.getLastEmail = this.getLastEmail.bind(this);
+ this.setLastEmail = this.setLastEmail.bind(this);
+ this.removeCurrentUser = this.removeCurrentUser.bind(this);
+ this.hasProfile = this.hasProfile.bind(this);
+ this.getProfile = this.getProfile.bind(this);
+ this.getProfileByUsername = this.getProfileByUsername.bind(this);
+ this.getProfilesUsernameMap = this.getProfilesUsernameMap.bind(this);
+ this.getProfiles = this.getProfiles.bind(this);
+ this.getActiveOnlyProfiles = this.getActiveOnlyProfiles.bind(this);
+ this.saveProfile = this.saveProfile.bind(this);
+ this.pStoreProfiles = this.pStoreProfiles.bind(this);
+ this.pGetProfiles = this.pGetProfiles.bind(this);
+ this.pGetProfilesUsernameMap = this.pGetProfilesUsernameMap.bind(this);
+ this.setSessions = this.setSessions.bind(this);
+ this.getSessions = this.getSessions.bind(this);
+ this.setAudits = this.setAudits.bind(this);
+ this.getAudits = this.getAudits.bind(this);
+ this.setTeams = this.setTeams.bind(this);
+ this.getTeams = this.getTeams.bind(this);
+ this.getCurrentMentionKeys = this.getCurrentMentionKeys.bind(this);
+ this.getLastVersion = this.getLastVersion.bind(this);
+ this.setLastVersion = this.setLastVersion.bind(this);
+ this.setStatuses = this.setStatuses.bind(this);
+ this.pSetStatuses = this.pSetStatuses.bind(this);
+ this.setStatus = this.setStatus.bind(this);
+ this.getStatuses = this.getStatuses.bind(this);
+ this.getStatus = this.getStatus.bind(this);
- emitChange: function(userId) {
+ this.gCurrentId = null;
+ }
+
+ emitChange(userId) {
this.emit(CHANGE_EVENT, userId);
- },
- addChangeListener: function(callback) {
+ }
+ addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
- },
- removeChangeListener: function(callback) {
+ }
+ removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
- },
- emitSessionsChange: function() {
+ }
+ emitSessionsChange() {
this.emit(CHANGE_EVENT_SESSIONS);
- },
- addSessionsChangeListener: function(callback) {
+ }
+ addSessionsChangeListener(callback) {
this.on(CHANGE_EVENT_SESSIONS, callback);
- },
- removeSessionsChangeListener: function(callback) {
+ }
+ removeSessionsChangeListener(callback) {
this.removeListener(CHANGE_EVENT_SESSIONS, callback);
- },
- emitAuditsChange: function() {
+ }
+ emitAuditsChange() {
this.emit(CHANGE_EVENT_AUDITS);
- },
- addAuditsChangeListener: function(callback) {
+ }
+ addAuditsChangeListener(callback) {
this.on(CHANGE_EVENT_AUDITS, callback);
- },
- removeAuditsChangeListener: function(callback) {
+ }
+ removeAuditsChangeListener(callback) {
this.removeListener(CHANGE_EVENT_AUDITS, callback);
- },
- emitTeamsChange: function() {
+ }
+ emitTeamsChange() {
this.emit(CHANGE_EVENT_TEAMS);
- },
- addTeamsChangeListener: function(callback) {
+ }
+ addTeamsChangeListener(callback) {
this.on(CHANGE_EVENT_TEAMS, callback);
- },
- removeTeamsChangeListener: function(callback) {
+ }
+ removeTeamsChangeListener(callback) {
this.removeListener(CHANGE_EVENT_TEAMS, callback);
- },
- emitStatusesChange: function() {
+ }
+ emitStatusesChange() {
this.emit(CHANGE_EVENT_STATUSES);
- },
- addStatusesChangeListener: function(callback) {
+ }
+ addStatusesChangeListener(callback) {
this.on(CHANGE_EVENT_STATUSES, callback);
- },
- removeStatusesChangeListener: function(callback) {
+ }
+ removeStatusesChangeListener(callback) {
this.removeListener(CHANGE_EVENT_STATUSES, callback);
- },
- setCurrentId: function(id) {
+ }
+ setCurrentId(id) {
this.gCurrentId = id;
if (id == null) {
BrowserStore.removeGlobalItem('current_user_id');
} else {
BrowserStore.setGlobalItem('current_user_id', id);
}
- },
- getCurrentId: function(skipFetch) {
+ }
+ getCurrentId(skipFetch) {
var currentId = this.gCurrentId;
if (currentId == null) {
@@ -93,46 +142,45 @@ var UserStore = assign({}, EventEmitter.prototype, {
}
return currentId;
- },
- getCurrentUser: function() {
+ }
+ getCurrentUser() {
if (this.getCurrentId() == null) {
return null;
}
- return this._getProfiles()[this.getCurrentId()];
- },
- setCurrentUser: function(user) {
+ return this.pGetProfiles()[this.getCurrentId()];
+ }
+ setCurrentUser(user) {
this.setCurrentId(user.id);
this.saveProfile(user);
- },
- getLastEmail: function() {
+ }
+ getLastEmail() {
return BrowserStore.getItem('last_email', '');
- },
- setLastEmail: function(email) {
+ }
+ setLastEmail(email) {
BrowserStore.setItem('last_email', email);
- },
- removeCurrentUser: function() {
+ }
+ removeCurrentUser() {
this.setCurrentId(null);
- },
- hasProfile: function(userId) {
- return this._getProfiles()[userId] != null;
- },
- getProfile: function(userId) {
- return this._getProfiles()[userId];
- },
- getProfileByUsername: function(username) {
- return this._getProfilesUsernameMap()[username];
- },
- getProfilesUsernameMap: function() {
- return this._getProfilesUsernameMap();
- },
- getProfiles: function() {
-
- return this._getProfiles();
- },
- getActiveOnlyProfiles: function() {
+ }
+ hasProfile(userId) {
+ return this.pGetProfiles()[userId] != null;
+ }
+ getProfile(userId) {
+ return this.pGetProfiles()[userId];
+ }
+ getProfileByUsername(username) {
+ return this.pGetProfilesUsernameMap()[username];
+ }
+ getProfilesUsernameMap() {
+ return this.pGetProfilesUsernameMap();
+ }
+ getProfiles() {
+ return this.pGetProfiles();
+ }
+ getActiveOnlyProfiles() {
var active = {};
- var current = this._getProfiles();
+ var current = this.pGetProfiles();
for (var key in current) {
if (current[key].delete_at === 0) {
@@ -141,45 +189,47 @@ var UserStore = assign({}, EventEmitter.prototype, {
}
return active;
- },
- saveProfile: function(profile) {
- var ps = this._getProfiles();
+ }
+ saveProfile(profile) {
+ var ps = this.pGetProfiles();
ps[profile.id] = profile;
- this._storeProfiles(ps);
- },
- _storeProfiles: function(profiles) {
+ this.pStoreProfiles(ps);
+ }
+ pStoreProfiles(profiles) {
BrowserStore.setItem('profiles', profiles);
var profileUsernameMap = {};
for (var id in profiles) {
- profileUsernameMap[profiles[id].username] = profiles[id];
+ if (profiles.hasOwnProperty(id)) {
+ profileUsernameMap[profiles[id].username] = profiles[id];
+ }
}
BrowserStore.setItem('profileUsernameMap', profileUsernameMap);
- },
- _getProfiles: function() {
+ }
+ pGetProfiles() {
return BrowserStore.getItem('profiles', {});
- },
- _getProfilesUsernameMap: function() {
+ }
+ pGetProfilesUsernameMap() {
return BrowserStore.getItem('profileUsernameMap', {});
- },
- setSessions: function(sessions) {
+ }
+ setSessions(sessions) {
BrowserStore.setItem('sessions', sessions);
- },
- getSessions: function() {
+ }
+ getSessions() {
return BrowserStore.getItem('sessions', {loading: true});
- },
- setAudits: function(audits) {
+ }
+ setAudits(audits) {
BrowserStore.setItem('audits', audits);
- },
- getAudits: function() {
+ }
+ getAudits() {
return BrowserStore.getItem('audits', {loading: true});
- },
- setTeams: function(teams) {
+ }
+ setTeams(teams) {
BrowserStore.setItem('teams', teams);
- },
- getTeams: function() {
+ }
+ getTeams() {
return BrowserStore.getItem('teams', []);
- },
- getCurrentMentionKeys: function() {
+ }
+ getCurrentMentionKeys() {
var user = this.getCurrentUser();
var keys = [];
@@ -205,74 +255,76 @@ var UserStore = assign({}, EventEmitter.prototype, {
}
return keys;
- },
- getLastVersion: function() {
+ }
+ getLastVersion() {
return BrowserStore.getItem('last_version', '');
- },
- setLastVersion: function(version) {
+ }
+ setLastVersion(version) {
BrowserStore.setItem('last_version', version);
- },
- setStatuses: function(statuses) {
- this._setStatuses(statuses);
+ }
+ setStatuses(statuses) {
+ this.pSetStatuses(statuses);
this.emitStatusesChange();
- },
- _setStatuses: function(statuses) {
+ }
+ pSetStatuses(statuses) {
BrowserStore.setItem('statuses', statuses);
- },
- setStatus: function(userId, status) {
+ }
+ setStatus(userId, status) {
var statuses = this.getStatuses();
statuses[userId] = status;
- this._setStatuses(statuses);
+ this.pSetStatuses(statuses);
this.emitStatusesChange();
- },
- getStatuses: function() {
+ }
+ getStatuses() {
return BrowserStore.getItem('statuses', {});
- },
- getStatus: function(id) {
+ }
+ getStatus(id) {
return this.getStatuses()[id];
}
-});
+}
+
+var UserStore = new UserStoreClass();
+UserStore.setMaxListeners(0);
-UserStore.dispatchToken = AppDispatcher.register(function(payload) {
+UserStore.dispatchToken = AppDispatcher.register(function registry(payload) {
var action = payload.action;
switch (action.type) {
- case ActionTypes.RECIEVED_PROFILES:
- for (var id in action.profiles) {
- // profiles can have incomplete data, so don't overwrite current user
- if (id === UserStore.getCurrentId()) {
- continue;
- }
- var profile = action.profiles[id];
- UserStore.saveProfile(profile);
- UserStore.emitChange(profile.id);
+ case ActionTypes.RECIEVED_PROFILES:
+ for (var id in action.profiles) {
+ // profiles can have incomplete data, so don't overwrite current user
+ if (id === UserStore.getCurrentId()) {
+ continue;
}
- break;
- case ActionTypes.RECIEVED_ME:
- UserStore.setCurrentUser(action.me);
- UserStore.emitChange(action.me.id);
- break;
- case ActionTypes.RECIEVED_SESSIONS:
- UserStore.setSessions(action.sessions);
- UserStore.emitSessionsChange();
- break;
- case ActionTypes.RECIEVED_AUDITS:
- UserStore.setAudits(action.audits);
- UserStore.emitAuditsChange();
- break;
- case ActionTypes.RECIEVED_TEAMS:
- UserStore.setTeams(action.teams);
- UserStore.emitTeamsChange();
- break;
- case ActionTypes.RECIEVED_STATUSES:
- UserStore._setStatuses(action.statuses);
- UserStore.emitStatusesChange();
- break;
+ var profile = action.profiles[id];
+ UserStore.saveProfile(profile);
+ UserStore.emitChange(profile.id);
+ }
+ break;
+ case ActionTypes.RECIEVED_ME:
+ UserStore.setCurrentUser(action.me);
+ UserStore.emitChange(action.me.id);
+ break;
+ case ActionTypes.RECIEVED_SESSIONS:
+ UserStore.setSessions(action.sessions);
+ UserStore.emitSessionsChange();
+ break;
+ case ActionTypes.RECIEVED_AUDITS:
+ UserStore.setAudits(action.audits);
+ UserStore.emitAuditsChange();
+ break;
+ case ActionTypes.RECIEVED_TEAMS:
+ UserStore.setTeams(action.teams);
+ UserStore.emitTeamsChange();
+ break;
+ case ActionTypes.RECIEVED_STATUSES:
+ UserStore.pSetStatuses(action.statuses);
+ UserStore.emitStatusesChange();
+ break;
- default:
+ default:
}
});
-UserStore.setMaxListeners(0);
global.window.UserStore = UserStore;
-module.exports = UserStore;
+export default UserStore;
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index c03a0230b..6ccef0506 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -4,7 +4,6 @@
var client = require('./client.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
-var ConfigStore = require('../stores/config_store.jsx');
var PostStore = require('../stores/post_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var utils = require('./utils.jsx');
@@ -15,14 +14,13 @@ var ActionTypes = Constants.ActionTypes;
// Used to track in progress async calls
var callTracker = {};
-function dispatchError(err, method) {
+export function dispatchError(err, method) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_ERROR,
err: err,
method: method
});
}
-module.exports.dispatchError = dispatchError;
function isCallInProgress(callName) {
if (!(callName in callTracker)) {
@@ -34,14 +32,14 @@ function isCallInProgress(callName) {
}
if (utils.getTimestamp() - callTracker[callName] > 5000) {
- console.log('AsyncClient call ' + callName + ' expired after more than 5 seconds');
+ //console.log('AsyncClient call ' + callName + ' expired after more than 5 seconds');
return false;
}
return true;
}
-function getChannels(force, updateLastViewed, checkVersion) {
+export function getChannels(force, updateLastViewed, checkVersion) {
var channels = ChannelStore.getAll();
if (channels.length === 0 || force) {
@@ -52,7 +50,7 @@ function getChannels(force, updateLastViewed, checkVersion) {
callTracker.getChannels = utils.getTimestamp();
client.getChannels(
- function(data, textStatus, xhr) {
+ function getChannelsSuccess(data, textStatus, xhr) {
callTracker.getChannels = 0;
if (checkVersion) {
@@ -65,7 +63,7 @@ function getChannels(force, updateLastViewed, checkVersion) {
if (serverVersion !== UserStore.getLastVersion()) {
UserStore.setLastVersion(serverVersion);
window.location.href = window.location.href;
- console.log('Detected version update refreshing the page');
+ console.log('Detected version update refreshing the page'); //eslint-disable-line no-console
}
}
@@ -79,7 +77,7 @@ function getChannels(force, updateLastViewed, checkVersion) {
members: data.members
});
},
- function(err) {
+ function getChannelsFailure(err) {
callTracker.getChannels = 0;
dispatchError(err, 'getChannels');
}
@@ -92,7 +90,7 @@ function getChannels(force, updateLastViewed, checkVersion) {
callTracker.getChannelCounts = utils.getTimestamp();
client.getChannelCounts(
- function(data, textStatus, xhr) {
+ function getChannelCountsSuccess(data, textStatus, xhr) {
callTracker.getChannelCounts = 0;
if (xhr.status === 304 || !data) {
@@ -103,15 +101,17 @@ function getChannels(force, updateLastViewed, checkVersion) {
var updateAtMap = data.update_times;
for (var id in countMap) {
- var c = ChannelStore.get(id);
- var count = countMap[id];
- var updateAt = updateAtMap[id];
- if (!c || c.total_msg_count !== count || updateAt > c.update_at) {
- getChannel(id);
+ if ({}.hasOwnProperty.call(countMap, id)) {
+ var c = ChannelStore.get(id);
+ var count = countMap[id];
+ var updateAt = updateAtMap[id];
+ if (!c || c.total_msg_count !== count || updateAt > c.update_at) {
+ getChannel(id);
+ }
}
}
},
- function(err) {
+ function getChannelCountsFailure(err) {
callTracker.getChannelCounts = 0;
dispatchError(err, 'getChannelCounts');
}
@@ -119,12 +119,11 @@ function getChannels(force, updateLastViewed, checkVersion) {
}
if (updateLastViewed && ChannelStore.getCurrentId() != null) {
- module.exports.updateLastViewedAt();
+ updateLastViewedAt();
}
}
-module.exports.getChannels = getChannels;
-function getChannel(id) {
+export function getChannel(id) {
if (isCallInProgress('getChannel' + id)) {
return;
}
@@ -132,7 +131,7 @@ function getChannel(id) {
callTracker['getChannel' + id] = utils.getTimestamp();
client.getChannel(id,
- function(data, textStatus, xhr) {
+ function getChannelSuccess(data, textStatus, xhr) {
callTracker['getChannel' + id] = 0;
if (xhr.status === 304 || !data) {
@@ -145,43 +144,49 @@ function getChannel(id) {
member: data.member
});
},
- function(err) {
+ function getChannelFailure(err) {
callTracker['getChannel' + id] = 0;
dispatchError(err, 'getChannel');
}
);
}
-module.exports.getChannel = getChannel;
-module.exports.updateLastViewedAt = function() {
- if (isCallInProgress('updateLastViewed')) return;
+export function updateLastViewedAt() {
+ if (isCallInProgress('updateLastViewed')) {
+ return;
+ }
- if (ChannelStore.getCurrentId() == null) return;
+ if (ChannelStore.getCurrentId() == null) {
+ return;
+ }
- callTracker['updateLastViewed'] = utils.getTimestamp();
+ callTracker.updateLastViewed = utils.getTimestamp();
client.updateLastViewedAt(
ChannelStore.getCurrentId(),
- function(data) {
- callTracker['updateLastViewed'] = 0;
+ function updateLastViewedAtSuccess() {
+ callTracker.updateLastViewed = 0;
},
- function(err) {
- callTracker['updateLastViewed'] = 0;
+ function updateLastViewdAtFailure(err) {
+ callTracker.updateLastViewed = 0;
dispatchError(err, 'updateLastViewedAt');
}
);
}
-module.exports.getMoreChannels = function(force) {
- if (isCallInProgress('getMoreChannels')) return;
+export function getMoreChannels(force) {
+ if (isCallInProgress('getMoreChannels')) {
+ return;
+ }
if (ChannelStore.getMoreAll().loading || force) {
-
- callTracker['getMoreChannels'] = utils.getTimestamp();
+ callTracker.getMoreChannels = utils.getTimestamp();
client.getMoreChannels(
- function(data, textStatus, xhr) {
- callTracker['getMoreChannels'] = 0;
+ function getMoreChannelsSuccess(data, textStatus, xhr) {
+ callTracker.getMoreChannels = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_MORE_CHANNELS,
@@ -189,37 +194,44 @@ module.exports.getMoreChannels = function(force) {
members: data.members
});
},
- function(err) {
- callTracker['getMoreChannels'] = 0;
+ function getMoreChannelsFailure(err) {
+ callTracker.getMoreChannels = 0;
dispatchError(err, 'getMoreChannels');
}
);
}
}
-module.exports.getChannelExtraInfo = function(force) {
+export function getChannelExtraInfo(force) {
var channelId = ChannelStore.getCurrentId();
if (channelId != null) {
- if (isCallInProgress('getChannelExtraInfo_'+channelId)) return;
- var minMembers = ChannelStore.getCurrent() && ChannelStore.getCurrent().type === 'D' ? 1 : 0;
+ if (isCallInProgress('getChannelExtraInfo_' + channelId)) {
+ return;
+ }
+ var minMembers = 0;
+ if (ChannelStore.getCurrent() && ChannelStore.getCurrent().type === 'D') {
+ minMembers = 1;
+ }
if (ChannelStore.getCurrentExtraInfo().members.length <= minMembers || force) {
- callTracker['getChannelExtraInfo_'+channelId] = utils.getTimestamp();
+ callTracker['getChannelExtraInfo_' + channelId] = utils.getTimestamp();
client.getChannelExtraInfo(
channelId,
- function(data, textStatus, xhr) {
- callTracker['getChannelExtraInfo_'+channelId] = 0;
+ function getChannelExtraInfoSuccess(data, textStatus, xhr) {
+ callTracker['getChannelExtraInfo_' + channelId] = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_CHANNEL_EXTRA_INFO,
extra_info: data
});
},
- function(err) {
- callTracker['getChannelExtraInfo_'+channelId] = 0;
+ function getChannelExtraInfoFailure(err) {
+ callTracker['getChannelExtraInfo_' + channelId] = 0;
dispatchError(err, 'getChannelExtraInfo');
}
);
@@ -227,124 +239,144 @@ module.exports.getChannelExtraInfo = function(force) {
}
}
-module.exports.getProfiles = function() {
- if (isCallInProgress('getProfiles')) return;
+export function getProfiles() {
+ if (isCallInProgress('getProfiles')) {
+ return;
+ }
- callTracker['getProfiles'] = utils.getTimestamp();
+ callTracker.getProfiles = utils.getTimestamp();
client.getProfiles(
- function(data, textStatus, xhr) {
- callTracker['getProfiles'] = 0;
+ function getProfilesSuccess(data, textStatus, xhr) {
+ callTracker.getProfiles = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_PROFILES,
profiles: data
});
},
- function(err) {
- callTracker['getProfiles'] = 0;
+ function getProfilesFailure(err) {
+ callTracker.getProfiles = 0;
dispatchError(err, 'getProfiles');
}
);
}
-module.exports.getSessions = function() {
- if (isCallInProgress('getSessions')) return;
+export function getSessions() {
+ if (isCallInProgress('getSessions')) {
+ return;
+ }
- callTracker['getSessions'] = utils.getTimestamp();
+ callTracker.getSessions = utils.getTimestamp();
client.getSessions(
UserStore.getCurrentId(),
- function(data, textStatus, xhr) {
- callTracker['getSessions'] = 0;
+ function getSessionsSuccess(data, textStatus, xhr) {
+ callTracker.getSessions = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_SESSIONS,
sessions: data
});
},
- function(err) {
- callTracker['getSessions'] = 0;
+ function getSessionsFailure(err) {
+ callTracker.getSessions = 0;
dispatchError(err, 'getSessions');
}
);
}
-module.exports.getAudits = function() {
- if (isCallInProgress('getAudits')) return;
+export function getAudits() {
+ if (isCallInProgress('getAudits')) {
+ return;
+ }
- callTracker['getAudits'] = utils.getTimestamp();
+ callTracker.getAudits = utils.getTimestamp();
client.getAudits(
UserStore.getCurrentId(),
- function(data, textStatus, xhr) {
- callTracker['getAudits'] = 0;
+ function getAuditsSuccess(data, textStatus, xhr) {
+ callTracker.getAudits = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_AUDITS,
audits: data
});
},
- function(err) {
- callTracker['getAudits'] = 0;
+ function getAuditsFailure(err) {
+ callTracker.getAudits = 0;
dispatchError(err, 'getAudits');
}
);
}
-module.exports.findTeams = function(email) {
- if (isCallInProgress('findTeams_'+email)) return;
+export function findTeams(email) {
+ if (isCallInProgress('findTeams_' + email)) {
+ return;
+ }
var user = UserStore.getCurrentUser();
if (user) {
- callTracker['findTeams_'+email] = utils.getTimestamp();
+ callTracker['findTeams_' + email] = utils.getTimestamp();
client.findTeams(
user.email,
- function(data, textStatus, xhr) {
- callTracker['findTeams_'+email] = 0;
+ function findTeamsSuccess(data, textStatus, xhr) {
+ callTracker['findTeams_' + email] = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_TEAMS,
teams: data
});
},
- function(err) {
- callTracker['findTeams_'+email] = 0;
+ function findTeamsFailure(err) {
+ callTracker['findTeams_' + email] = 0;
dispatchError(err, 'findTeams');
}
);
}
}
-module.exports.search = function(terms) {
- if (isCallInProgress('search_'+String(terms))) return;
+export function search(terms) {
+ if (isCallInProgress('search_' + String(terms))) {
+ return;
+ }
- callTracker['search_'+String(terms)] = utils.getTimestamp();
+ callTracker['search_' + String(terms)] = utils.getTimestamp();
client.search(
terms,
- function(data, textStatus, xhr) {
- callTracker['search_'+String(terms)] = 0;
+ function searchSuccess(data, textStatus, xhr) {
+ callTracker['search_' + String(terms)] = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_SEARCH,
results: data
});
},
- function(err) {
- callTracker['search_'+String(terms)] = 0;
+ function searchFailure(err) {
+ callTracker['search_' + String(terms)] = 0;
dispatchError(err, 'search');
}
);
}
-module.exports.getPostsPage = function(force, id, maxPosts) {
+export function getPostsPage(force, id, maxPosts) {
if (PostStore.getCurrentPosts() == null || force) {
var channelId = id;
if (channelId == null) {
@@ -377,8 +409,10 @@ module.exports.getPostsPage = function(force, id, maxPosts) {
channelId,
0,
numPosts,
- function(data, textStatus, xhr) {
- if (xhr.status === 304 || !data) return;
+ function getPostsPageSuccess(data, textStatus, xhr) {
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_POSTS,
@@ -386,20 +420,20 @@ module.exports.getPostsPage = function(force, id, maxPosts) {
post_list: data
});
- module.exports.getProfiles();
+ getProfiles();
},
- function(err) {
+ function getPostsPageFailure(err) {
dispatchError(err, 'getPostsPage');
},
- function() {
+ function getPostsPageComplete() {
callTracker['getPostsPage_' + channelId] = 0;
}
);
}
}
-};
+}
-function getPosts(id) {
+export function getPosts(id) {
var channelId = id;
if (channelId == null) {
if (ChannelStore.getCurrentId() == null) {
@@ -413,7 +447,7 @@ function getPosts(id) {
}
if (PostStore.getCurrentPosts() == null) {
- module.exports.getPostsPage(true, id, Constants.POST_CHUNK_SIZE);
+ getPostsPage(true, id, Constants.POST_CHUNK_SIZE);
return;
}
@@ -435,7 +469,7 @@ function getPosts(id) {
post_list: data
});
- module.exports.getProfiles();
+ getProfiles();
},
function fail(err) {
dispatchError(err, 'getPosts');
@@ -445,86 +479,94 @@ function getPosts(id) {
}
);
}
-module.exports.getPosts = getPosts;
-function getMe() {
+export function getMe() {
if (isCallInProgress('getMe')) {
return;
}
callTracker.getMe = utils.getTimestamp();
client.getMeSynchronous(
- function(data, textStatus, xhr) {
+ function getMeSyncSuccess(data, textStatus, xhr) {
callTracker.getMe = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_ME,
me: data
});
},
- function(err) {
+ function getMeSyncFailure(err) {
callTracker.getMe = 0;
dispatchError(err, 'getMe');
}
);
}
-module.exports.getMe = getMe;
-module.exports.getStatuses = function() {
- if (isCallInProgress('getStatuses')) return;
+export function getStatuses() {
+ if (isCallInProgress('getStatuses')) {
+ return;
+ }
- callTracker['getStatuses'] = utils.getTimestamp();
+ callTracker.getStatuses = utils.getTimestamp();
client.getStatuses(
- function(data, textStatus, xhr) {
- callTracker['getStatuses'] = 0;
+ function getStatusesSuccess(data, textStatus, xhr) {
+ callTracker.getStatuses = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_STATUSES,
statuses: data
});
},
- function(err) {
- callTracker['getStatuses'] = 0;
+ function getStatusesFailure(err) {
+ callTracker.getStatuses = 0;
dispatchError(err, 'getStatuses');
}
);
}
-module.exports.getMyTeam = function() {
- if (isCallInProgress('getMyTeam')) return;
+export function getMyTeam() {
+ if (isCallInProgress('getMyTeam')) {
+ return;
+ }
- callTracker['getMyTeam'] = utils.getTimestamp();
+ callTracker.getMyTeam = utils.getTimestamp();
client.getMyTeam(
- function(data, textStatus, xhr) {
- callTracker['getMyTeam'] = 0;
+ function getMyTeamSuccess(data, textStatus, xhr) {
+ callTracker.getMyTeam = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_TEAM,
team: data
});
},
- function(err) {
- callTracker['getMyTeam'] = 0;
+ function getMyTeamFailure(err) {
+ callTracker.getMyTeam = 0;
dispatchError(err, 'getMyTeam');
}
);
}
-function getConfig() {
+export function getConfig() {
if (isCallInProgress('getConfig')) {
return;
}
- callTracker['getConfig'] = utils.getTimestamp();
+ callTracker.getConfig = utils.getTimestamp();
client.getConfig(
- function(data, textStatus, xhr) {
- callTracker['getConfig'] = 0;
+ function getConfigSuccess(data, textStatus, xhr) {
+ callTracker.getConfig = 0;
if (data && xhr.status !== 304) {
AppDispatcher.handleServerAction({
@@ -533,10 +575,9 @@ function getConfig() {
});
}
},
- function(err) {
- callTracker['getConfig'] = 0;
+ function getConfigFailure(err) {
+ callTracker.getConfig = 0;
dispatchError(err, 'getConfig');
}
);
}
-module.exports.getConfig = getConfig;
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 082f82a08..10f9c0b37 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -3,15 +3,15 @@
var BrowserStore = require('../stores/browser_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
-module.exports.track = function(category, action, label, prop, val) {
+export function track(category, action, label, prop, val) {
global.window.snowplow('trackStructEvent', category, action, label, prop, val);
global.window.analytics.track(action, {category: category, label: label, property: prop, value: val});
-};
+}
-module.exports.trackPage = function() {
+export function trackPage() {
global.window.snowplow('trackPageView');
global.window.analytics.page();
-};
+}
function handleError(methodName, xhr, status, err) {
var LTracker = global.window.LTracker || [];
@@ -41,7 +41,7 @@ function handleError(methodName, xhr, status, err) {
console.error(e); //eslint-disable-line no-console
LTracker.push(msg);
- module.exports.track('api', 'api_weberror', methodName, 'message', msg);
+ track('api', 'api_weberror', methodName, 'message', msg);
if (xhr.status === 401) {
if (window.location.href.indexOf('/channels') === 0) {
@@ -55,7 +55,7 @@ function handleError(methodName, xhr, status, err) {
return e;
}
-module.exports.createTeamFromSignup = function(teamSignup, success, error) {
+export function createTeamFromSignup(teamSignup, success, error) {
$.ajax({
url: '/api/v1/teams/create_from_signup',
dataType: 'json',
@@ -68,9 +68,9 @@ module.exports.createTeamFromSignup = function(teamSignup, success, error) {
error(e);
}
});
-};
+}
-module.exports.createTeamWithSSO = function(team, service, success, error) {
+export function createTeamWithSSO(team, service, success, error) {
$.ajax({
url: '/api/v1/teams/create_with_sso/' + service,
dataType: 'json',
@@ -83,9 +83,9 @@ module.exports.createTeamWithSSO = function(team, service, success, error) {
error(e);
}
});
-};
+}
-module.exports.createUser = function(user, data, emailHash, success, error) {
+export function createUser(user, data, emailHash, success, error) {
$.ajax({
url: '/api/v1/users/create?d=' + encodeURIComponent(data) + '&h=' + encodeURIComponent(emailHash),
dataType: 'json',
@@ -99,10 +99,10 @@ module.exports.createUser = function(user, data, emailHash, success, error) {
}
});
- module.exports.track('api', 'api_users_create', user.team_id, 'email', user.email);
-};
+ track('api', 'api_users_create', user.team_id, 'email', user.email);
+}
-module.exports.updateUser = function(user, success, error) {
+export function updateUser(user, success, error) {
$.ajax({
url: '/api/v1/users/update',
dataType: 'json',
@@ -116,10 +116,10 @@ module.exports.updateUser = function(user, success, error) {
}
});
- module.exports.track('api', 'api_users_update');
-};
+ track('api', 'api_users_update');
+}
-module.exports.updatePassword = function(data, success, error) {
+export function updatePassword(data, success, error) {
$.ajax({
url: '/api/v1/users/newpassword',
dataType: 'json',
@@ -133,10 +133,10 @@ module.exports.updatePassword = function(data, success, error) {
}
});
- module.exports.track('api', 'api_users_newpassword');
-};
+ track('api', 'api_users_newpassword');
+}
-module.exports.updateUserNotifyProps = function(data, success, error) {
+export function updateUserNotifyProps(data, success, error) {
$.ajax({
url: '/api/v1/users/update_notify',
dataType: 'json',
@@ -149,9 +149,9 @@ module.exports.updateUserNotifyProps = function(data, success, error) {
error(e);
}
});
-};
+}
-module.exports.updateRoles = function(data, success, error) {
+export function updateRoles(data, success, error) {
$.ajax({
url: '/api/v1/users/update_roles',
dataType: 'json',
@@ -165,10 +165,10 @@ module.exports.updateRoles = function(data, success, error) {
}
});
- module.exports.track('api', 'api_users_update_roles');
-};
+ track('api', 'api_users_update_roles');
+}
-module.exports.updateActive = function(userId, active, success, error) {
+export function updateActive(userId, active, success, error) {
var data = {};
data.user_id = userId;
data.active = '' + active;
@@ -186,10 +186,10 @@ module.exports.updateActive = function(userId, active, success, error) {
}
});
- module.exports.track('api', 'api_users_update_roles');
-};
+ track('api', 'api_users_update_roles');
+}
-module.exports.sendPasswordReset = function(data, success, error) {
+export function sendPasswordReset(data, success, error) {
$.ajax({
url: '/api/v1/users/send_password_reset',
dataType: 'json',
@@ -203,10 +203,10 @@ module.exports.sendPasswordReset = function(data, success, error) {
}
});
- module.exports.track('api', 'api_users_send_password_reset');
-};
+ track('api', 'api_users_send_password_reset');
+}
-module.exports.resetPassword = function(data, success, error) {
+export function resetPassword(data, success, error) {
$.ajax({
url: '/api/v1/users/reset_password',
dataType: 'json',
@@ -220,17 +220,17 @@ module.exports.resetPassword = function(data, success, error) {
}
});
- module.exports.track('api', 'api_users_reset_password');
-};
+ track('api', 'api_users_reset_password');
+}
-module.exports.logout = function() {
- module.exports.track('api', 'api_users_logout');
+export function logout() {
+ track('api', 'api_users_logout');
var currentTeamUrl = TeamStore.getCurrentTeamUrl();
BrowserStore.clear();
window.location.href = currentTeamUrl + '/logout';
-};
+}
-module.exports.loginByEmail = function(name, email, password, success, error) {
+export function loginByEmail(name, email, password, success, error) {
$.ajax({
url: '/api/v1/users/login',
dataType: 'json',
@@ -238,19 +238,19 @@ module.exports.loginByEmail = function(name, email, password, success, error) {
type: 'POST',
data: JSON.stringify({name: name, email: email, password: password}),
success: function onSuccess(data, textStatus, xhr) {
- module.exports.track('api', 'api_users_login_success', data.team_id, 'email', data.email);
+ track('api', 'api_users_login_success', data.team_id, 'email', data.email);
success(data, textStatus, xhr);
},
error: function onError(xhr, status, err) {
- module.exports.track('api', 'api_users_login_fail', window.getSubDomain(), 'email', email);
+ track('api', 'api_users_login_fail', name, 'email', email);
var e = handleError('loginByEmail', xhr, status, err);
error(e);
}
});
-};
+}
-module.exports.revokeSession = function(altId, success, error) {
+export function revokeSession(altId, success, error) {
$.ajax({
url: '/api/v1/users/revoke_session',
dataType: 'json',
@@ -263,9 +263,9 @@ module.exports.revokeSession = function(altId, success, error) {
error(e);
}
});
-};
+}
-module.exports.getSessions = function(userId, success, error) {
+export function getSessions(userId, success, error) {
$.ajax({
cache: false,
url: '/api/v1/users/' + userId + '/sessions',
@@ -278,9 +278,9 @@ module.exports.getSessions = function(userId, success, error) {
error(e);
}
});
-};
+}
-module.exports.getAudits = function(userId, success, error) {
+export function getAudits(userId, success, error) {
$.ajax({
url: '/api/v1/users/' + userId + '/audits',
dataType: 'json',
@@ -292,9 +292,9 @@ module.exports.getAudits = function(userId, success, error) {
error(e);
}
});
-};
+}
-module.exports.getMeSynchronous = function(success, error) {
+export function getMeSynchronous(success, error) {
var currentUser = null;
$.ajax({
async: false,
@@ -318,9 +318,9 @@ module.exports.getMeSynchronous = function(success, error) {
});
return currentUser;
-};
+}
-module.exports.inviteMembers = function(data, success, error) {
+export function inviteMembers(data, success, error) {
$.ajax({
url: '/api/v1/teams/invite_members',
dataType: 'json',
@@ -334,10 +334,10 @@ module.exports.inviteMembers = function(data, success, error) {
}
});
- module.exports.track('api', 'api_teams_invite_members');
-};
+ track('api', 'api_teams_invite_members');
+}
-module.exports.updateTeamDisplayName = function(data, success, error) {
+export function updateTeamDisplayName(data, success, error) {
$.ajax({
url: '/api/v1/teams/update_name',
dataType: 'json',
@@ -351,10 +351,10 @@ module.exports.updateTeamDisplayName = function(data, success, error) {
}
});
- module.exports.track('api', 'api_teams_update_name');
-};
+ track('api', 'api_teams_update_name');
+}
-module.exports.signupTeam = function(email, success, error) {
+export function signupTeam(email, success, error) {
$.ajax({
url: '/api/v1/teams/signup',
dataType: 'json',
@@ -368,10 +368,10 @@ module.exports.signupTeam = function(email, success, error) {
}
});
- module.exports.track('api', 'api_teams_signup');
-};
+ track('api', 'api_teams_signup');
+}
-module.exports.createTeam = function(team, success, error) {
+export function createTeam(team, success, error) {
$.ajax({
url: '/api/v1/teams/create',
dataType: 'json',
@@ -384,9 +384,9 @@ module.exports.createTeam = function(team, success, error) {
error(e);
}
});
-};
+}
-module.exports.findTeamByName = function(teamName, success, error) {
+export function findTeamByName(teamName, success, error) {
$.ajax({
url: '/api/v1/teams/find_team_by_name',
dataType: 'json',
@@ -399,9 +399,9 @@ module.exports.findTeamByName = function(teamName, success, error) {
error(e);
}
});
-};
+}
-module.exports.findTeamsSendEmail = function(email, success, error) {
+export function findTeamsSendEmail(email, success, error) {
$.ajax({
url: '/api/v1/teams/email_teams',
dataType: 'json',
@@ -415,10 +415,10 @@ module.exports.findTeamsSendEmail = function(email, success, error) {
}
});
- module.exports.track('api', 'api_teams_email_teams');
-};
+ track('api', 'api_teams_email_teams');
+}
-module.exports.findTeams = function(email, success, error) {
+export function findTeams(email, success, error) {
$.ajax({
url: '/api/v1/teams/find_teams',
dataType: 'json',
@@ -431,9 +431,9 @@ module.exports.findTeams = function(email, success, error) {
error(e);
}
});
-};
+}
-module.exports.createChannel = function(channel, success, error) {
+export function createChannel(channel, success, error) {
$.ajax({
url: '/api/v1/channels/create',
dataType: 'json',
@@ -447,10 +447,10 @@ module.exports.createChannel = function(channel, success, error) {
}
});
- module.exports.track('api', 'api_channels_create', channel.type, 'name', channel.name);
-};
+ track('api', 'api_channels_create', channel.type, 'name', channel.name);
+}
-module.exports.createDirectChannel = function(channel, userId, success, error) {
+export function createDirectChannel(channel, userId, success, error) {
$.ajax({
url: '/api/v1/channels/create_direct',
dataType: 'json',
@@ -458,16 +458,16 @@ module.exports.createDirectChannel = function(channel, userId, success, error) {
type: 'POST',
data: JSON.stringify({user_id: userId}),
success: success,
- error: function(xhr, status, err) {
+ error: function onError(xhr, status, err) {
var e = handleError('createDirectChannel', xhr, status, err);
error(e);
}
});
- module.exports.track('api', 'api_channels_create_direct', channel.type, 'name', channel.name);
-};
+ track('api', 'api_channels_create_direct', channel.type, 'name', channel.name);
+}
-module.exports.updateChannel = function(channel, success, error) {
+export function updateChannel(channel, success, error) {
$.ajax({
url: '/api/v1/channels/update',
dataType: 'json',
@@ -481,10 +481,10 @@ module.exports.updateChannel = function(channel, success, error) {
}
});
- module.exports.track('api', 'api_channels_update');
-};
+ track('api', 'api_channels_update');
+}
-module.exports.updateChannelDesc = function(data, success, error) {
+export function updateChannelDesc(data, success, error) {
$.ajax({
url: '/api/v1/channels/update_desc',
dataType: 'json',
@@ -498,10 +498,10 @@ module.exports.updateChannelDesc = function(data, success, error) {
}
});
- module.exports.track('api', 'api_channels_desc');
-};
+ track('api', 'api_channels_desc');
+}
-module.exports.updateNotifyLevel = function(data, success, error) {
+export function updateNotifyLevel(data, success, error) {
$.ajax({
url: '/api/v1/channels/update_notify_level',
dataType: 'json',
@@ -514,9 +514,9 @@ module.exports.updateNotifyLevel = function(data, success, error) {
error(e);
}
});
-};
+}
-module.exports.joinChannel = function(id, success, error) {
+export function joinChannel(id, success, error) {
$.ajax({
url: '/api/v1/channels/' + id + '/join',
dataType: 'json',
@@ -529,10 +529,10 @@ module.exports.joinChannel = function(id, success, error) {
}
});
- module.exports.track('api', 'api_channels_join');
-};
+ track('api', 'api_channels_join');
+}
-module.exports.leaveChannel = function(id, success, error) {
+export function leaveChannel(id, success, error) {
$.ajax({
url: '/api/v1/channels/' + id + '/leave',
dataType: 'json',
@@ -545,10 +545,10 @@ module.exports.leaveChannel = function(id, success, error) {
}
});
- module.exports.track('api', 'api_channels_leave');
-};
+ track('api', 'api_channels_leave');
+}
-module.exports.deleteChannel = function(id, success, error) {
+export function deleteChannel(id, success, error) {
$.ajax({
url: '/api/v1/channels/' + id + '/delete',
dataType: 'json',
@@ -561,10 +561,10 @@ module.exports.deleteChannel = function(id, success, error) {
}
});
- module.exports.track('api', 'api_channels_delete');
-};
+ track('api', 'api_channels_delete');
+}
-module.exports.updateLastViewedAt = function(channelId, success, error) {
+export function updateLastViewedAt(channelId, success, error) {
$.ajax({
url: '/api/v1/channels/' + channelId + '/update_last_viewed_at',
dataType: 'json',
@@ -576,9 +576,9 @@ module.exports.updateLastViewedAt = function(channelId, success, error) {
error(e);
}
});
-};
+}
-function getChannels(success, error) {
+export function getChannels(success, error) {
$.ajax({
cache: false,
url: '/api/v1/channels/',
@@ -592,9 +592,8 @@ function getChannels(success, error) {
}
});
}
-module.exports.getChannels = getChannels;
-module.exports.getChannel = function(id, success, error) {
+export function getChannel(id, success, error) {
$.ajax({
cache: false,
url: '/api/v1/channels/' + id + '/',
@@ -607,10 +606,10 @@ module.exports.getChannel = function(id, success, error) {
}
});
- module.exports.track('api', 'api_channel_get');
-};
+ track('api', 'api_channel_get');
+}
-module.exports.getMoreChannels = function(success, error) {
+export function getMoreChannels(success, error) {
$.ajax({
url: '/api/v1/channels/more',
dataType: 'json',
@@ -622,9 +621,9 @@ module.exports.getMoreChannels = function(success, error) {
error(e);
}
});
-};
+}
-function getChannelCounts(success, error) {
+export function getChannelCounts(success, error) {
$.ajax({
cache: false,
url: '/api/v1/channels/counts',
@@ -638,9 +637,8 @@ function getChannelCounts(success, error) {
}
});
}
-module.exports.getChannelCounts = getChannelCounts;
-module.exports.getChannelExtraInfo = function(id, success, error) {
+export function getChannelExtraInfo(id, success, error) {
$.ajax({
url: '/api/v1/channels/' + id + '/extra_info',
dataType: 'json',
@@ -651,9 +649,9 @@ module.exports.getChannelExtraInfo = function(id, success, error) {
error(e);
}
});
-};
+}
-module.exports.executeCommand = function(channelId, command, suggest, success, error) {
+export function executeCommand(channelId, command, suggest, success, error) {
$.ajax({
url: '/api/v1/command',
dataType: 'json',
@@ -666,9 +664,9 @@ module.exports.executeCommand = function(channelId, command, suggest, success, e
error(e);
}
});
-};
+}
-module.exports.getPostsPage = function(channelId, offset, limit, success, error, complete) {
+export function getPostsPage(channelId, offset, limit, success, error, complete) {
$.ajax({
cache: false,
url: '/api/v1/channels/' + channelId + '/posts/' + offset + '/' + limit,
@@ -682,9 +680,9 @@ module.exports.getPostsPage = function(channelId, offset, limit, success, error,
},
complete: complete
});
-};
+}
-module.exports.getPosts = function(channelId, since, success, error, complete) {
+export function getPosts(channelId, since, success, error, complete) {
$.ajax({
url: '/api/v1/channels/' + channelId + '/posts/' + since,
dataType: 'json',
@@ -697,9 +695,9 @@ module.exports.getPosts = function(channelId, since, success, error, complete) {
},
complete: complete
});
-};
+}
-module.exports.getPost = function(channelId, postId, success, error) {
+export function getPost(channelId, postId, success, error) {
$.ajax({
cache: false,
url: '/api/v1/channels/' + channelId + '/post/' + postId,
@@ -712,9 +710,9 @@ module.exports.getPost = function(channelId, postId, success, error) {
error(e);
}
});
-};
+}
-module.exports.search = function(terms, success, error) {
+export function search(terms, success, error) {
$.ajax({
url: '/api/v1/posts/search',
dataType: 'json',
@@ -727,10 +725,10 @@ module.exports.search = function(terms, success, error) {
}
});
- module.exports.track('api', 'api_posts_search');
-};
+ track('api', 'api_posts_search');
+}
-module.exports.deletePost = function(channelId, id, success, error) {
+export function deletePost(channelId, id, success, error) {
$.ajax({
url: '/api/v1/channels/' + channelId + '/post/' + id + '/delete',
dataType: 'json',
@@ -743,10 +741,10 @@ module.exports.deletePost = function(channelId, id, success, error) {
}
});
- module.exports.track('api', 'api_posts_delete');
-};
+ track('api', 'api_posts_delete');
+}
-module.exports.createPost = function(post, channel, success, error) {
+export function createPost(post, channel, success, error) {
$.ajax({
url: '/api/v1/channels/' + post.channel_id + '/create',
dataType: 'json',
@@ -760,7 +758,7 @@ module.exports.createPost = function(post, channel, success, error) {
}
});
- module.exports.track('api', 'api_posts_create', channel.name, 'length', post.message.length);
+ track('api', 'api_posts_create', channel.name, 'length', post.message.length);
// global.window.analytics.track('api_posts_create', {
// category: 'api',
@@ -770,9 +768,9 @@ module.exports.createPost = function(post, channel, success, error) {
// files: (post.filenames || []).length,
// mentions: (post.message.match('/<mention>/g') || []).length
// });
-};
+}
-module.exports.updatePost = function(post, success, error) {
+export function updatePost(post, success, error) {
$.ajax({
url: '/api/v1/channels/' + post.channel_id + '/update',
dataType: 'json',
@@ -786,10 +784,10 @@ module.exports.updatePost = function(post, success, error) {
}
});
- module.exports.track('api', 'api_posts_update');
-};
+ track('api', 'api_posts_update');
+}
-module.exports.addChannelMember = function(id, data, success, error) {
+export function addChannelMember(id, data, success, error) {
$.ajax({
url: '/api/v1/channels/' + id + '/add',
dataType: 'json',
@@ -803,10 +801,10 @@ module.exports.addChannelMember = function(id, data, success, error) {
}
});
- module.exports.track('api', 'api_channels_add_member');
-};
+ track('api', 'api_channels_add_member');
+}
-module.exports.removeChannelMember = function(id, data, success, error) {
+export function removeChannelMember(id, data, success, error) {
$.ajax({
url: '/api/v1/channels/' + id + '/remove',
dataType: 'json',
@@ -820,10 +818,10 @@ module.exports.removeChannelMember = function(id, data, success, error) {
}
});
- module.exports.track('api', 'api_channels_remove_member');
-};
+ track('api', 'api_channels_remove_member');
+}
-module.exports.getProfiles = function(success, error) {
+export function getProfiles(success, error) {
$.ajax({
cache: false,
url: '/api/v1/users/profiles',
@@ -837,9 +835,9 @@ module.exports.getProfiles = function(success, error) {
error(e);
}
});
-};
+}
-module.exports.uploadFile = function(formData, success, error) {
+export function uploadFile(formData, success, error) {
var request = $.ajax({
url: '/api/v1/files/upload',
type: 'POST',
@@ -856,12 +854,12 @@ module.exports.uploadFile = function(formData, success, error) {
}
});
- module.exports.track('api', 'api_files_upload');
+ track('api', 'api_files_upload');
return request;
-};
+}
-module.exports.getFileInfo = function(filename, success, error) {
+export function getFileInfo(filename, success, error) {
$.ajax({
url: '/api/v1/files/get_info' + filename,
dataType: 'json',
@@ -873,9 +871,9 @@ module.exports.getFileInfo = function(filename, success, error) {
error(e);
}
});
-};
+}
-module.exports.getPublicLink = function(data, success, error) {
+export function getPublicLink(data, success, error) {
$.ajax({
url: '/api/v1/files/get_public_link',
dataType: 'json',
@@ -887,9 +885,9 @@ module.exports.getPublicLink = function(data, success, error) {
error(e);
}
});
-};
+}
-module.exports.uploadProfileImage = function(imageData, success, error) {
+export function uploadProfileImage(imageData, success, error) {
$.ajax({
url: '/api/v1/users/newimage',
type: 'POST',
@@ -903,9 +901,9 @@ module.exports.uploadProfileImage = function(imageData, success, error) {
error(e);
}
});
-};
+}
-module.exports.importSlack = function(fileData, success, error) {
+export function importSlack(fileData, success, error) {
$.ajax({
url: '/api/v1/teams/import_team',
type: 'POST',
@@ -919,9 +917,9 @@ module.exports.importSlack = function(fileData, success, error) {
error(e);
}
});
-};
+}
-module.exports.getStatuses = function(success, error) {
+export function getStatuses(success, error) {
$.ajax({
url: '/api/v1/users/status',
dataType: 'json',
@@ -933,9 +931,9 @@ module.exports.getStatuses = function(success, error) {
error(e);
}
});
-};
+}
-module.exports.getMyTeam = function(success, error) {
+export function getMyTeam(success, error) {
$.ajax({
url: '/api/v1/teams/me',
dataType: 'json',
@@ -947,9 +945,9 @@ module.exports.getMyTeam = function(success, error) {
error(e);
}
});
-};
+}
-module.exports.updateValetFeature = function(data, success, error) {
+export function updateValetFeature(data, success, error) {
$.ajax({
url: '/api/v1/teams/update_valet_feature',
dataType: 'json',
@@ -963,10 +961,10 @@ module.exports.updateValetFeature = function(data, success, error) {
}
});
- module.exports.track('api', 'api_teams_update_valet_feature');
-};
+ track('api', 'api_teams_update_valet_feature');
+}
-function getConfig(success, error) {
+export function getConfig(success, error) {
$.ajax({
url: '/api/v1/config/get_all',
dataType: 'json',
@@ -979,4 +977,3 @@ function getConfig(success, error) {
}
});
}
-module.exports.getConfig = getConfig;
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 8721ced7c..3a6ca1b89 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -4,97 +4,107 @@
var keyMirror = require('keymirror');
module.exports = {
- ActionTypes: keyMirror({
- RECIEVED_ERROR: null,
+ ActionTypes: keyMirror({
+ RECIEVED_ERROR: null,
- CLICK_CHANNEL: null,
- CREATE_CHANNEL: null,
- RECIEVED_CHANNELS: null,
- RECIEVED_CHANNEL: null,
- RECIEVED_MORE_CHANNELS: null,
- RECIEVED_CHANNEL_EXTRA_INFO: null,
+ CLICK_CHANNEL: null,
+ CREATE_CHANNEL: null,
+ RECIEVED_CHANNELS: null,
+ RECIEVED_CHANNEL: null,
+ RECIEVED_MORE_CHANNELS: null,
+ RECIEVED_CHANNEL_EXTRA_INFO: null,
- RECIEVED_POSTS: null,
- RECIEVED_POST: null,
- RECIEVED_SEARCH: null,
- RECIEVED_POST_SELECTED: null,
- RECIEVED_MENTION_DATA: null,
- RECIEVED_ADD_MENTION: null,
+ RECIEVED_POSTS: null,
+ RECIEVED_POST: null,
+ RECIEVED_SEARCH: null,
+ RECIEVED_POST_SELECTED: null,
+ RECIEVED_MENTION_DATA: null,
+ RECIEVED_ADD_MENTION: null,
- RECIEVED_PROFILES: null,
- RECIEVED_ME: null,
- RECIEVED_SESSIONS: null,
- RECIEVED_AUDITS: null,
- RECIEVED_TEAMS: null,
- RECIEVED_STATUSES: null,
+ RECIEVED_PROFILES: null,
+ RECIEVED_ME: null,
+ RECIEVED_SESSIONS: null,
+ RECIEVED_AUDITS: null,
+ RECIEVED_TEAMS: null,
+ RECIEVED_STATUSES: null,
- RECIEVED_MSG: null,
+ RECIEVED_MSG: null,
- CLICK_TEAM: null,
- RECIEVED_TEAM: null,
+ CLICK_TEAM: null,
+ RECIEVED_TEAM: null,
- RECIEVED_CONFIG: null
- }),
+ RECIEVED_CONFIG: null
+ }),
- PayloadSources: keyMirror({
- SERVER_ACTION: null,
- VIEW_ACTION: null
- }),
- SPECIAL_MENTIONS: ['all', 'channel'],
- CHARACTER_LIMIT: 4000,
- IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg'],
- AUDIO_TYPES: ['mp3', 'wav', 'wma', 'm4a', 'flac', 'aac'],
- VIDEO_TYPES: ['mp4', 'avi', 'webm', 'mkv', 'wmv', 'mpg', 'mov', 'flv'],
- SPREADSHEET_TYPES: ['ppt', 'pptx', 'csv'],
- EXCEL_TYPES: ['xlsx'],
- WORD_TYPES: ['doc', 'docx'],
- CODE_TYPES: ['css', 'html', 'js', 'php', 'rb'],
- PDF_TYPES: ['pdf'],
- PATCH_TYPES: ['patch'],
- ICON_FROM_TYPE: {'audio': 'audio', 'video': 'video', 'spreadsheet': 'ppt', 'pdf': 'pdf', 'code': 'code' , 'word': 'word' , 'excel': 'excel' , 'patch': 'patch', 'other': 'generic'},
- MAX_DISPLAY_FILES: 5,
- MAX_UPLOAD_FILES: 5,
- MAX_FILE_SIZE: 50000000, // 50 MB
- THUMBNAIL_WIDTH: 128,
- THUMBNAIL_HEIGHT: 100,
- DEFAULT_CHANNEL: 'town-square',
- OFFTOPIC_CHANNEL: 'off-topic',
- GITLAB_SERVICE: 'gitlab',
- EMAIL_SERVICE: 'email',
- POST_CHUNK_SIZE: 60,
- MAX_POST_CHUNKS: 3,
- POST_LOADING: 'loading',
- POST_FAILED: 'failed',
- POST_DELETED: 'deleted',
- RESERVED_TEAM_NAMES: [
- "www",
- "web",
- "admin",
- "support",
- "notify",
- "test",
- "demo",
- "mail",
- "team",
- "channel",
- "internal",
- "localhost",
- "dockerhost",
- "stag",
- "post",
- "cluster",
- "api",
- ],
- RESERVED_USERNAMES: [
- "valet",
- "all",
- "channel",
- ],
- MONTHS: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
- MAX_DMS: 20,
- MAX_POST_LEN: 4000,
- ONLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path class='online--icon' d='M6,5.487c1.371,0,2.482-1.116,2.482-2.493c0-1.378-1.111-2.495-2.482-2.495S3.518,1.616,3.518,2.994C3.518,4.371,4.629,5.487,6,5.487z M10.452,8.545c-0.101-0.829-0.36-1.968-0.726-2.541C9.475,5.606,8.5,5.5,8.5,5.5S8.43,7.521,6,7.521C3.507,7.521,3.5,5.5,3.5,5.5S2.527,5.606,2.273,6.004C1.908,6.577,1.648,7.716,1.547,8.545C1.521,8.688,1.49,9.082,1.498,9.142c0.161,1.295,2.238,2.322,4.375,2.358C5.916,11.501,5.958,11.501,6,11.501c0.043,0,0.084,0,0.127-0.001c2.076-0.026,4.214-1.063,4.375-2.358C10.509,9.082,10.471,8.696,10.452,8.545z'/></g></g></svg>",
- OFFLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path fill='#cccccc' d='M6.002,7.143C5.645,7.363,5.167,7.52,4.502,7.52c-2.493,0-2.5-2.02-2.5-2.02S1.029,5.607,0.775,6.004C0.41,6.577,0.15,7.716,0.049,8.545c-0.025,0.145-0.057,0.537-0.05,0.598c0.162,1.295,2.237,2.321,4.375,2.357c0.043,0.001,0.085,0.001,0.127,0.001c0.043,0,0.084,0,0.127-0.001c1.879-0.023,3.793-0.879,4.263-2h-2.89L6.002,7.143L6.002,7.143z M4.501,5.488c1.372,0,2.483-1.117,2.483-2.494c0-1.378-1.111-2.495-2.483-2.495c-1.371,0-2.481,1.117-2.481,2.495C2.02,4.371,3.13,5.488,4.501,5.488z M7.002,6.5v2h5v-2H7.002z'/></g></g></svg>",
- MENU_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>",
- COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>"
+ PayloadSources: keyMirror({
+ SERVER_ACTION: null,
+ VIEW_ACTION: null
+ }),
+ SPECIAL_MENTIONS: ['all', 'channel'],
+ CHARACTER_LIMIT: 4000,
+ IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg'],
+ AUDIO_TYPES: ['mp3', 'wav', 'wma', 'm4a', 'flac', 'aac'],
+ VIDEO_TYPES: ['mp4', 'avi', 'webm', 'mkv', 'wmv', 'mpg', 'mov', 'flv'],
+ PRESENTATION_TYPES: ['ppt', 'pptx'],
+ SPREADSHEET_TYPES: ['xlsx', 'csv'],
+ WORD_TYPES: ['doc', 'docx'],
+ CODE_TYPES: ['css', 'html', 'js', 'php', 'rb'],
+ PDF_TYPES: ['pdf'],
+ PATCH_TYPES: ['patch'],
+ ICON_FROM_TYPE: {
+ audio: 'audio',
+ video: 'video',
+ spreadsheet: 'excel',
+ presentation: 'ppt',
+ pdf: 'pdf',
+ code: 'code',
+ word: 'word',
+ patch: 'patch',
+ other: 'generic'
+ },
+ MAX_DISPLAY_FILES: 5,
+ MAX_UPLOAD_FILES: 5,
+ MAX_FILE_SIZE: 50000000, // 50 MB
+ THUMBNAIL_WIDTH: 128,
+ THUMBNAIL_HEIGHT: 100,
+ DEFAULT_CHANNEL: 'town-square',
+ OFFTOPIC_CHANNEL: 'off-topic',
+ GITLAB_SERVICE: 'gitlab',
+ EMAIL_SERVICE: 'email',
+ POST_CHUNK_SIZE: 60,
+ MAX_POST_CHUNKS: 3,
+ POST_LOADING: 'loading',
+ POST_FAILED: 'failed',
+ POST_DELETED: 'deleted',
+ RESERVED_TEAM_NAMES: [
+ 'www',
+ 'web',
+ 'admin',
+ 'support',
+ 'notify',
+ 'test',
+ 'demo',
+ 'mail',
+ 'team',
+ 'channel',
+ 'internal',
+ 'localhost',
+ 'dockerhost',
+ 'stag',
+ 'post',
+ 'cluster',
+ 'api'
+ ],
+ RESERVED_USERNAMES: [
+ 'valet',
+ 'all',
+ 'channel'
+ ],
+ MONTHS: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+ MAX_DMS: 20,
+ MAX_POST_LEN: 4000,
+ ONLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path class='online--icon' d='M6,5.487c1.371,0,2.482-1.116,2.482-2.493c0-1.378-1.111-2.495-2.482-2.495S3.518,1.616,3.518,2.994C3.518,4.371,4.629,5.487,6,5.487z M10.452,8.545c-0.101-0.829-0.36-1.968-0.726-2.541C9.475,5.606,8.5,5.5,8.5,5.5S8.43,7.521,6,7.521C3.507,7.521,3.5,5.5,3.5,5.5S2.527,5.606,2.273,6.004C1.908,6.577,1.648,7.716,1.547,8.545C1.521,8.688,1.49,9.082,1.498,9.142c0.161,1.295,2.238,2.322,4.375,2.358C5.916,11.501,5.958,11.501,6,11.501c0.043,0,0.084,0,0.127-0.001c2.076-0.026,4.214-1.063,4.375-2.358C10.509,9.082,10.471,8.696,10.452,8.545z'/></g></g></svg>",
+ OFFLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path fill='#cccccc' d='M6.002,7.143C5.645,7.363,5.167,7.52,4.502,7.52c-2.493,0-2.5-2.02-2.5-2.02S1.029,5.607,0.775,6.004C0.41,6.577,0.15,7.716,0.049,8.545c-0.025,0.145-0.057,0.537-0.05,0.598c0.162,1.295,2.237,2.321,4.375,2.357c0.043,0.001,0.085,0.001,0.127,0.001c0.043,0,0.084,0,0.127-0.001c1.879-0.023,3.793-0.879,4.263-2h-2.89L6.002,7.143L6.002,7.143z M4.501,5.488c1.372,0,2.483-1.117,2.483-2.494c0-1.378-1.111-2.495-2.483-2.495c-1.371,0-2.481,1.117-2.481,2.495C2.02,4.371,3.13,5.488,4.501,5.488z M7.002,6.5v2h5v-2H7.002z'/></g></g></svg>",
+ MENU_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>",
+ COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>"
};
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index a1dc72ae2..6267ebd8f 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -10,19 +10,19 @@ var AsyncClient = require('./async_client.jsx');
var client = require('./client.jsx');
var Autolinker = require('autolinker');
-module.exports.isEmail = function(email) {
+export function isEmail(email) {
var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
return regex.test(email);
-};
+}
-module.exports.cleanUpUrlable = function(input) {
+export function cleanUpUrlable(input) {
var cleaned = input.trim().replace(/-/g, ' ').replace(/[^\w\s]/gi, '').toLowerCase().replace(/\s/g, '-');
cleaned = cleaned.replace(/^\-+/, '');
cleaned = cleaned.replace(/\-+$/, '');
return cleaned;
-};
+}
-module.exports.isTestDomain = function() {
+export function isTestDomain() {
if ((/^localhost/).test(window.location.hostname)) {
return true;
}
@@ -52,38 +52,9 @@ module.exports.isTestDomain = function() {
}
return false;
-};
-
-function getSubDomain() {
- if (module.exports.isTestDomain()) {
- return '';
- }
-
- if ((/^www/).test(window.location.hostname)) {
- return '';
- }
-
- if ((/^beta/).test(window.location.hostname)) {
- return '';
- }
-
- if ((/^ci/).test(window.location.hostname)) {
- return '';
- }
-
- var parts = window.location.hostname.split('.');
-
- if (parts.length !== 3) {
- return '';
- }
-
- return parts[0];
}
-global.window.getSubDomain = getSubDomain;
-module.exports.getSubDomain = getSubDomain;
-
-module.exports.getDomainWithOutSub = function() {
+export function getDomainWithOutSub() {
var parts = window.location.host.split('.');
if (parts.length === 1) {
@@ -95,17 +66,17 @@ module.exports.getDomainWithOutSub = function() {
}
return parts[1] + '.' + parts[2];
-};
+}
-module.exports.getCookie = function(name) {
+export function getCookie(name) {
var value = '; ' + document.cookie;
var parts = value.split('; ' + name + '=');
if (parts.length === 2) {
return parts.pop().split(';').shift();
}
-};
+}
-module.exports.notifyMe = function(title, body, channel) {
+export function notifyMe(title, body, channel) {
if ('Notification' in window && Notification.permission !== 'denied') {
Notification.requestPermission(function onRequestPermission(permission) {
if (Notification.permission !== permission) {
@@ -117,7 +88,7 @@ module.exports.notifyMe = function(title, body, channel) {
notification.onclick = function onClick() {
window.focus();
if (channel) {
- module.exports.switchChannel(channel);
+ switchChannel(channel);
} else {
window.location.href = '/';
}
@@ -128,16 +99,16 @@ module.exports.notifyMe = function(title, body, channel) {
}
});
}
-};
+}
-module.exports.ding = function() {
- if (!module.exports.isBrowserFirefox()) {
+export function ding() {
+ if (!isBrowserFirefox()) {
var audio = new Audio('/static/images/ding.mp3');
audio.play();
}
-};
+}
-module.exports.getUrlParameter = function(sParam) {
+export function getUrlParameter(sParam) {
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++) {
@@ -147,20 +118,20 @@ module.exports.getUrlParameter = function(sParam) {
}
}
return null;
-};
+}
-module.exports.getDateForUnixTicks = function(ticks) {
+export function getDateForUnixTicks(ticks) {
return new Date(ticks);
-};
+}
-module.exports.displayDate = function(ticks) {
+export function displayDate(ticks) {
var d = new Date(ticks);
var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
return monthNames[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear();
-};
+}
-module.exports.displayTime = function(ticks) {
+export function displayTime(ticks) {
var d = new Date(ticks);
var hours = d.getHours();
var minutes = d.getMinutes();
@@ -178,9 +149,9 @@ module.exports.displayTime = function(ticks) {
minutes = '0' + minutes;
}
return hours + ':' + minutes + ' ' + ampm;
-};
+}
-module.exports.displayDateTime = function(ticks) {
+export function displayDateTime(ticks) {
var seconds = Math.floor((Date.now() - ticks) / 1000);
var interval = Math.floor(seconds / 3600);
@@ -203,16 +174,16 @@ module.exports.displayDateTime = function(ticks) {
}
return '1 minute ago';
-};
+}
-module.exports.displayCommentDateTime = function(ticks) {
- return module.exports.displayDate(ticks) + ' ' + module.exports.displayTime(ticks);
+export function displayCommentDateTime(ticks) {
+ return displayDate(ticks) + ' ' + displayTime(ticks);
}
// returns Unix timestamp in milliseconds
-module.exports.getTimestamp = function() {
+export function getTimestamp() {
return Date.now();
-};
+}
function testUrlMatch(text) {
var urlMatcher = new Autolinker.matchParser.MatchParser({
@@ -240,7 +211,7 @@ function testUrlMatch(text) {
return result;
}
-module.exports.extractLinks = function(text) {
+export function extractLinks(text) {
var repRegex = new RegExp('<br>', 'g');
var matches = testUrlMatch(text.replace(repRegex, '\n'));
@@ -254,11 +225,11 @@ module.exports.extractLinks = function(text) {
}
return {links: links, text: text};
-};
+}
-module.exports.escapeRegExp = function(string) {
+export function escapeRegExp(string) {
return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
-};
+}
function handleYoutubeTime(link) {
var timeRegex = /[\\?&]t=([0-9hms]+)/;
@@ -317,7 +288,7 @@ function getYoutubeEmbed(link) {
return;
}
var metadata = data.items[0].snippet;
- $('.video-type.' + youtubeId).html("Youtube - ")
+ $('.video-type.' + youtubeId).html('Youtube - ');
$('.video-uploader.' + youtubeId).html(metadata.channelTitle);
$('.video-title.' + youtubeId).find('a').html(metadata.title);
$('.post-list-holder-by-time').scrollTop($('.post-list-holder-by-time')[0].scrollHeight);
@@ -328,7 +299,7 @@ function getYoutubeEmbed(link) {
async: true,
url: 'https://www.googleapis.com/youtube/v3/videos',
type: 'GET',
- data: {part: 'snippet', id: youtubeId, key:config.GoogleDeveloperKey},
+ data: {part: 'snippet', id: youtubeId, key: config.GoogleDeveloperKey},
success: success
});
}
@@ -340,12 +311,22 @@ function getYoutubeEmbed(link) {
<span className={'video-title ' + youtubeId}><a href={link}></a></span>
</h4>
<h4 className={'video-uploader ' + youtubeId}></h4>
- <div className='video-div embed-responsive-item' id={youtubeId} onClick={onClick}>
+ <div
+ className='video-div embed-responsive-item'
+ id={youtubeId}
+ onClick={onClick}
+ >
<div className='embed-responsive embed-responsive-4by3 video-div__placeholder'>
- <div id={youtubeId} className='video-thumbnail__container'>
- <img className='video-thumbnail' src={'https://i.ytimg.com/vi/' + youtubeId + '/hqdefault.jpg'}/>
+ <div
+ id={youtubeId}
+ className='video-thumbnail__container'
+ >
+ <img
+ className='video-thumbnail'
+ src={'https://i.ytimg.com/vi/' + youtubeId + '/hqdefault.jpg'}
+ />
<div className='block'>
- <span className='play-button'><span></span></span>
+ <span className='play-button'><span/></span>
</div>
</div>
</div>
@@ -354,7 +335,7 @@ function getYoutubeEmbed(link) {
);
}
-module.exports.getEmbed = function(link) {
+export function getEmbed(link) {
var ytRegex = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/;
var match = link.trim().match(ytRegex);
@@ -406,13 +387,13 @@ module.exports.getEmbed = function(link) {
</div>
);
*/
-};
+}
-module.exports.areStatesEqual = function(state1, state2) {
+export function areStatesEqual(state1, state2) {
return JSON.stringify(state1) === JSON.stringify(state2);
-};
+}
-module.exports.replaceHtmlEntities = function(text) {
+export function replaceHtmlEntities(text) {
var tagsToReplace = {
'&amp;': '&',
'&lt;': '<',
@@ -426,9 +407,9 @@ module.exports.replaceHtmlEntities = function(text) {
}
}
return newtext;
-};
+}
-module.exports.insertHtmlEntities = function(text) {
+export function insertHtmlEntities(text) {
var tagsToReplace = {
'&': '&amp;',
'<': '&lt;',
@@ -442,33 +423,33 @@ module.exports.insertHtmlEntities = function(text) {
}
}
return newtext;
-};
+}
-module.exports.searchForTerm = function(term) {
+export function searchForTerm(term) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_SEARCH_TERM,
term: term,
do_search: true
});
-};
+}
var puncStartRegex = /^((?![@#])\W)+/g;
var puncEndRegex = /(\W)+$/g;
-module.exports.textToJsx = function(text, options) {
-
- if (options && options['singleline']) {
- var repRegex = new RegExp('\n', 'g');
+export function textToJsx(textin, options) {
+ var text = textin;
+ if (options && options.singleline) {
+ var repRegex = new RegExp('\n', 'g'); //eslint-disable-line no-control-regex
text = text.replace(repRegex, ' ');
}
- var searchTerm = ''
- if (options && options['searchTerm']) {
- searchTerm = options['searchTerm'].toLowerCase()
+ var searchTerm = '';
+ if (options && options.searchTerm) {
+ searchTerm = options.searchTerm.toLowerCase();
}
var mentionClass = 'mention-highlight';
- if (options && options['noMentionHighlight']) {
+ if (options && options.noMentionHighlight) {
mentionClass = '';
}
@@ -480,11 +461,11 @@ module.exports.textToJsx = function(text, options) {
var implicitKeywords = UserStore.getCurrentMentionKeys();
var lines = text.split('\n');
- for (var i = 0; i < lines.length; i++) {
+ for (let i = 0; i < lines.length; i++) {
var line = lines[i];
var words = line.split(' ');
var highlightSearchClass = '';
- for (var z = 0; z < words.length; z++) {
+ for (let z = 0; z < words.length; z++) {
var word = words[z];
var trimWord = word.replace(puncStartRegex, '').replace(puncEndRegex, '').trim();
var mentionRegex = /^(?:@)([a-z0-9_]+)$/gi; // looks loop invariant but a weird JS bug needs it to be redefined here
@@ -493,15 +474,17 @@ module.exports.textToJsx = function(text, options) {
if (searchTerm !== '') {
let searchWords = searchTerm.split(' ');
for (let idx in searchWords) {
- let searchWord = searchWords[idx];
- if (searchWord === word.toLowerCase() || searchWord === trimWord.toLowerCase()) {
- highlightSearchClass = ' search-highlight';
- break;
- } else if (searchWord.charAt(searchWord.length - 1) === '*') {
- let searchWordPrefix = searchWord.slice(0,-1);
- if (trimWord.toLowerCase().indexOf(searchWordPrefix) > -1 || word.toLowerCase().indexOf(searchWordPrefix) > -1) {
+ if ({}.hasOwnProperty.call(searchWords, idx)) {
+ let searchWord = searchWords[idx];
+ if (searchWord === word.toLowerCase() || searchWord === trimWord.toLowerCase()) {
highlightSearchClass = ' search-highlight';
break;
+ } else if (searchWord.charAt(searchWord.length - 1) === '*') {
+ let searchWordPrefix = searchWord.slice(0, -1);
+ if (trimWord.toLowerCase().indexOf(searchWordPrefix) > -1 || word.toLowerCase().indexOf(searchWordPrefix) > -1) {
+ highlightSearchClass = ' search-highlight';
+ break;
+ }
}
}
}
@@ -509,68 +492,147 @@ module.exports.textToJsx = function(text, options) {
if (explicitMention &&
(UserStore.getProfileByUsername(explicitMention[1]) ||
- Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1))
- {
- var name = explicitMention[1];
- // do both a non-case sensitive and case senstive check
- var mClass = implicitKeywords.indexOf('@'+name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@'+name) !== -1 ? mentionClass : '';
+ Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1)) {
+ let name = explicitMention[1];
- var suffix = word.match(puncEndRegex);
- var prefix = word.match(puncStartRegex);
-
- if (searchTerm === name) {
- highlightSearchClass = ' search-highlight';
- }
+ // do both a non-case sensitive and case senstive check
+ let mClass = '';
+ if (('@' + name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@' + name) !== -1) {
+ mClass = mentionClass;
+ }
- inner.push(<span key={name+i+z+'_span'}>{prefix}<a className={mClass + highlightSearchClass + ' mention-link'} key={name+i+z+'_link'} href='#' onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>@{name}</a>{suffix} </span>);
- } else if (testUrlMatch(word).length) {
- var match = testUrlMatch(word)[0];
- var link = match.link;
+ let suffix = word.match(puncEndRegex);
+ let prefix = word.match(puncStartRegex);
- var prefix = word.substring(0,word.indexOf(match.text));
- var suffix = word.substring(word.indexOf(match.text)+match.text.length);
+ if (searchTerm === name) {
+ highlightSearchClass = ' search-highlight';
+ }
- inner.push(<span key={word+i+z+'_span'}>{prefix}<a key={word+i+z+'_link'} className={'theme' + highlightSearchClass} target='_blank' href={link}>{match.text}</a>{suffix} </span>);
+ inner.push(
+ <span key={name + i + z + '_span'}>
+ {prefix}
+ <a
+ className={mClass + highlightSearchClass + ' mention-link'}
+ key={name + i + z + '_link'}
+ href='#'
+ onClick={() => searchForTerm(name)} //eslint-disable-line no-loop-func
+ >
+ @{name}
+ </a>
+ {suffix}
+ {' '}
+ </span>
+ );
+ } else if (testUrlMatch(word).length) {
+ let match = testUrlMatch(word)[0];
+ let link = match.link;
+
+ let prefix = word.substring(0, word.indexOf(match.text));
+ let suffix = word.substring(word.indexOf(match.text) + match.text.length);
+
+ inner.push(
+ <span key={word + i + z + '_span'}>
+ {prefix}
+ <a
+ key={word + i + z + '_link'}
+ className={'theme' + highlightSearchClass}
+ target='_blank'
+ href={link}
+ >
+ {match.text}
+ </a>
+ {suffix}
+ {' '}
+ </span>
+ );
+ } else if (trimWord.match(hashRegex)) {
+ let suffix = word.match(puncEndRegex);
+ let prefix = word.match(puncStartRegex);
+ let mClass = '';
+ if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
+ mClass = mentionClass;
+ }
- } else if (trimWord.match(hashRegex)) {
- var suffix = word.match(puncEndRegex);
- var prefix = word.match(puncStartRegex);
- var mClass = implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1 ? mentionClass : '';
+ if (searchTerm === trimWord.substring(1).toLowerCase() || searchTerm === trimWord.toLowerCase()) {
+ highlightSearchClass = ' search-highlight';
+ }
- if (searchTerm === trimWord.substring(1).toLowerCase() || searchTerm === trimWord.toLowerCase()) {
+ inner.push(
+ <span key={word + i + z + '_span'}>
+ {prefix}
+ <a
+ key={word + i + z + '_hash'}
+ className={'theme ' + mClass + highlightSearchClass}
+ href='#'
+ onClick={() => searchForTerm(trimWord)} //eslint-disable-line no-loop-func
+ >
+ {trimWord}
+ </a>
+ {suffix}
+ {' '}
+ </span>
+ );
+ } else if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
+ let suffix = word.match(puncEndRegex);
+ let prefix = word.match(puncStartRegex);
+
+ if (trimWord.charAt(0) === '@') {
+ if (searchTerm === trimWord.substring(1).toLowerCase()) {
highlightSearchClass = ' search-highlight';
}
-
- inner.push(<span key={word+i+z+'_span'}>{prefix}<a key={word+i+z+'_hash'} className={'theme ' + mClass + highlightSearchClass} href='#' onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a>{suffix} </span>);
-
- } else if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
- var suffix = word.match(puncEndRegex);
- var prefix = word.match(puncStartRegex);
-
- if (trimWord.charAt(0) === '@') {
- if (searchTerm === trimWord.substring(1).toLowerCase()) {
- highlightSearchClass = ' search-highlight';
- }
- inner.push(<span key={word+i+z+'_span'} key={name+i+z+'_span'}>{prefix}<a className={mentionClass + highlightSearchClass} key={name+i+z+'_link'} href='#'>{trimWord}</a>{suffix} </span>);
- } else {
- inner.push(<span key={word+i+z+'_span'}>{prefix}<span className={mentionClass + highlightSearchClass}>{module.exports.replaceHtmlEntities(trimWord)}</span>{suffix} </span>);
- }
-
- } else if (word === '') {
- // if word is empty dont include a span
+ inner.push(
+ <span key={word + i + z + '_span'}>
+ {prefix}
+ <a
+ className={mentionClass + highlightSearchClass}
+ key={name + i + z + '_link'}
+ href='#'
+ >
+ {trimWord}
+ </a>
+ {suffix}
+ {' '}
+ </span>
+ );
} else {
- inner.push(<span key={word+i+z+'_span'}><span className={highlightSearchClass}>{module.exports.replaceHtmlEntities(word)}</span> </span>);
+ inner.push(
+ <span key={word + i + z + '_span'}>
+ {prefix}
+ <span className={mentionClass + highlightSearchClass}>
+ {replaceHtmlEntities(trimWord)}
+ </span>
+ {suffix}
+ {' '}
+ </span>
+ );
}
- highlightSearchClass = '';
+ } else if (word === '') {
+
+ // if word is empty dont include a span
+
+ } else {
+ inner.push(
+ <span key={word + i + z + '_span'}>
+ <span className={highlightSearchClass}>
+ {replaceHtmlEntities(word)}
+ </span>
+ {' '}
+ </span>
+ );
+ }
+ highlightSearchClass = '';
+ }
+ if (i !== lines.length - 1) {
+ inner.push(
+ <br key={'br_' + i}/>
+ );
}
- if (i != lines.length-1)
- inner.push(<br key={'br_'+i+z}/>);
}
return inner;
}
-module.exports.getFileType = function(extin) {
+export function getFileType(extin) {
var ext = extin.toLowerCase();
if (Constants.IMAGE_TYPES.indexOf(ext) > -1) {
return 'image';
@@ -596,8 +658,8 @@ module.exports.getFileType = function(extin) {
return 'word';
}
- if (Constants.EXCEL_TYPES.indexOf(ext) > -1) {
- return 'excel';
+ if (Constants.PRESENTATION_TYPES.indexOf(ext) > -1) {
+ return 'presentation';
}
if (Constants.PDF_TYPES.indexOf(ext) > -1) {
@@ -609,9 +671,9 @@ module.exports.getFileType = function(extin) {
}
return 'other';
-};
+}
-module.exports.getPreviewImagePathForFileType = function(fileTypeIn) {
+export function getPreviewImagePathForFileType(fileTypeIn) {
var fileType = fileTypeIn.toLowerCase();
var icon;
@@ -622,9 +684,9 @@ module.exports.getPreviewImagePathForFileType = function(fileTypeIn) {
}
return '/static/images/icons/' + icon + '.png';
-};
+}
-module.exports.getIconClassName = function(fileTypeIn) {
+export function getIconClassName(fileTypeIn) {
var fileType = fileTypeIn.toLowerCase();
if (fileType in Constants.ICON_FROM_TYPE) {
@@ -632,9 +694,9 @@ module.exports.getIconClassName = function(fileTypeIn) {
}
return 'glyphicon-file';
-};
+}
-module.exports.splitFileLocation = function(fileLocation) {
+export function splitFileLocation(fileLocation) {
var fileSplit = fileLocation.split('.');
var ext = '';
@@ -647,16 +709,16 @@ module.exports.splitFileLocation = function(fileLocation) {
var filename = filePath.split('/')[filePath.split('/').length - 1];
return {ext: ext, name: filename, path: filePath};
-};
+}
-module.exports.toTitleCase = function(str) {
+export function toTitleCase(str) {
function doTitleCase(txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
}
return str.replace(/\w\S*/g, doTitleCase);
-};
+}
-module.exports.changeCss = function(className, classValue) {
+export function changeCss(className, classValue) {
// we need invisible container to store additional css definitions
var cssMainContainer = $('#css-modifier-container');
if (cssMainContainer.length === 0) {
@@ -674,9 +736,9 @@ module.exports.changeCss = function(className, classValue) {
// append additional style
classContainer.html('<style>' + className + ' {' + classValue + '}</style>');
-};
+}
-module.exports.rgb2hex = function(rgbIn) {
+export function rgb2hex(rgbIn) {
if (/^#[0-9A-F]{6}$/i.test(rgbIn)) {
return rgbIn;
}
@@ -686,9 +748,9 @@ module.exports.rgb2hex = function(rgbIn) {
return ('0' + parseInt(x, 10).toString(16)).slice(-2);
}
return '#' + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
-};
+}
-module.exports.placeCaretAtEnd = function(el) {
+export function placeCaretAtEnd(el) {
el.focus();
if (typeof window.getSelection != 'undefined' && typeof document.createRange != 'undefined') {
var range = document.createRange();
@@ -703,9 +765,9 @@ module.exports.placeCaretAtEnd = function(el) {
textRange.collapse(false);
textRange.select();
}
-};
+}
-module.exports.getCaretPosition = function(el) {
+export function getCaretPosition(el) {
if (el.selectionStart) {
return el.selectionStart;
} else if (document.selection) {
@@ -724,9 +786,9 @@ module.exports.getCaretPosition = function(el) {
return rc.text.length;
}
return 0;
-};
+}
-module.exports.setSelectionRange = function(input, selectionStart, selectionEnd) {
+export function setSelectionRange(input, selectionStart, selectionEnd) {
if (input.setSelectionRange) {
input.focus();
input.setSelectionRange(selectionStart, selectionEnd);
@@ -737,13 +799,13 @@ module.exports.setSelectionRange = function(input, selectionStart, selectionEnd)
range.moveStart('character', selectionStart);
range.select();
}
-};
+}
-module.exports.setCaretPosition = function(input, pos) {
- module.exports.setSelectionRange(input, pos, pos);
-};
+export function setCaretPosition(input, pos) {
+ setSelectionRange(input, pos, pos);
+}
-module.exports.getSelectedText = function(input) {
+export function getSelectedText(input) {
var selectedText;
if (typeof document.selection !== 'undefined') {
input.focus();
@@ -756,9 +818,9 @@ module.exports.getSelectedText = function(input) {
}
return selectedText;
-};
+}
-module.exports.isValidUsername = function(name) {
+export function isValidUsername(name) {
var error = '';
if (!name) {
error = 'This field is required';
@@ -780,20 +842,18 @@ module.exports.isValidUsername = function(name) {
}
return error;
-};
+}
-function updateTabTitle(name) {
+export function updateTabTitle(name) {
document.title = name + ' ' + document.title.substring(document.title.lastIndexOf('-'));
}
-module.exports.updateTabTitle = updateTabTitle;
-function updateAddressBar(channelName) {
+export function updateAddressBar(channelName) {
var teamURL = window.location.href.split('/channels')[0];
history.replaceState('data', '', teamURL + '/channels/' + channelName);
}
-module.exports.updateAddressBar = updateAddressBar;
-function switchChannel(channel, teammateName) {
+export function switchChannel(channel, teammateName) {
AppDispatcher.handleViewAction({
type: ActionTypes.CLICK_CHANNEL,
name: channel.name,
@@ -819,20 +879,19 @@ function switchChannel(channel, teammateName) {
return false;
}
-module.exports.switchChannel = switchChannel;
-module.exports.isMobile = function() {
+export function isMobile() {
return screen.width <= 768;
-};
+}
-module.exports.isComment = function(post) {
+export function isComment(post) {
if ('root_id' in post) {
return post.root_id !== '';
}
return false;
-};
+}
-module.exports.getDirectTeammate = function(channelId) {
+export function getDirectTeammate(channelId) {
var userIds = ChannelStore.get(channelId).name.split('__');
var curUserId = UserStore.getCurrentId();
var teammate = {};
@@ -849,9 +908,9 @@ module.exports.getDirectTeammate = function(channelId) {
}
return teammate;
-};
+}
-Image.prototype.load = function(url, progressCallback) {
+Image.prototype.load = function imageLoad(url, progressCallback) {
var self = this;
var xmlHTTP = new XMLHttpRequest();
xmlHTTP.open('GET', url, true);
@@ -878,7 +937,7 @@ Image.prototype.load = function(url, progressCallback) {
Image.prototype.completedPercentage = 0;
-module.exports.changeColor = function(colourIn, amt) {
+export function changeColor(colourIn, amt) {
var usePound = false;
var col = colourIn;
@@ -919,10 +978,9 @@ module.exports.changeColor = function(colourIn, amt) {
}
return pound + String('000000' + (g | (b << 8) | (r << 16)).toString(16)).slice(-6);
-};
-
-module.exports.changeOpacity = function(oldColor, opacity) {
+}
+export function changeOpacity(oldColor, opacity) {
var col = oldColor;
if (col[0] === '#') {
col = col.slice(1);
@@ -933,9 +991,9 @@ module.exports.changeOpacity = function(oldColor, opacity) {
var b = parseInt(col.substring(4, 6), 16);
return 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')';
-};
+}
-module.exports.getFullName = function(user) {
+export function getFullName(user) {
if (user.first_name && user.last_name) {
return user.first_name + ' ' + user.last_name;
} else if (user.first_name) {
@@ -945,23 +1003,23 @@ module.exports.getFullName = function(user) {
}
return '';
-};
+}
-module.exports.getDisplayName = function(user) {
+export function getDisplayName(user) {
if (user.nickname && user.nickname.trim().length > 0) {
return user.nickname;
}
- var fullName = module.exports.getFullName(user);
+ var fullName = getFullName(user);
if (fullName) {
return fullName;
}
return user.username;
-};
+}
//IE10 does not set window.location.origin automatically so this must be called instead when using it
-module.exports.getWindowLocationOrigin = function() {
+export function getWindowLocationOrigin() {
var windowLocationOrigin = window.location.origin;
if (!windowLocationOrigin) {
windowLocationOrigin = window.location.protocol + '//' + window.location.hostname;
@@ -970,10 +1028,10 @@ module.exports.getWindowLocationOrigin = function() {
}
}
return windowLocationOrigin;
-};
+}
// Converts a file size in bytes into a human-readable string of the form '123MB'.
-module.exports.fileSizeToString = function(bytes) {
+export function fileSizeToString(bytes) {
// it's unlikely that we'll have files bigger than this
if (bytes > 1024 * 1024 * 1024 * 1024) {
return Math.floor(bytes / (1024 * 1024 * 1024 * 1024)) + 'TB';
@@ -986,29 +1044,29 @@ module.exports.fileSizeToString = function(bytes) {
}
return bytes + 'B';
-};
+}
// Converts a filename (like those attached to Post objects) to a url that can be used to retrieve attachments from the server.
-module.exports.getFileUrl = function(filename) {
+export function getFileUrl(filename) {
var url = filename;
// This is a temporary patch to fix issue with old files using absolute paths
if (url.indexOf('/api/v1/files/get') !== -1) {
url = filename.split('/api/v1/files/get')[1];
}
- url = module.exports.getWindowLocationOrigin() + '/api/v1/files/get' + url;
+ url = getWindowLocationOrigin() + '/api/v1/files/get' + url;
return url;
-};
+}
// Gets the name of a file (including extension) from a given url or file path.
-module.exports.getFileName = function(path) {
+export function getFileName(path) {
var split = path.split('/');
return split[split.length - 1];
-};
+}
// Generates a RFC-4122 version 4 compliant globally unique identifier.
-module.exports.generateId = function() {
+export function generateId() {
// implementation taken from http://stackoverflow.com/a/2117523
var id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
@@ -1026,14 +1084,14 @@ module.exports.generateId = function() {
});
return id;
-};
+}
-module.exports.isBrowserFirefox = function() {
+export function isBrowserFirefox() {
return navigator && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
-};
+}
// Checks if browser is IE10 or IE11
-module.exports.isBrowserIE = function() {
+export function isBrowserIE() {
if (window.navigator && window.navigator.userAgent) {
var ua = window.navigator.userAgent;
@@ -1041,14 +1099,14 @@ module.exports.isBrowserIE = function() {
}
return false;
-};
+}
-module.exports.isBrowserEdge = function() {
+export function isBrowserEdge() {
return window.naviagtor && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('edge') > -1;
-};
+}
// Used to get the id of the other user from a DM channel
-module.exports.getUserIdFromChannelName = function(channel) {
+export function getUserIdFromChannelName(channel) {
var ids = channel.name.split('__');
var otherUserId = '';
if (ids[0] === UserStore.getCurrentId()) {
@@ -1058,13 +1116,13 @@ module.exports.getUserIdFromChannelName = function(channel) {
}
return otherUserId;
-};
+}
-module.exports.importSlack = function(file, success, error) {
+export function importSlack(file, success, error) {
var formData = new FormData();
formData.append('file', file, file.name);
formData.append('filesize', file.size);
formData.append('importFrom', 'slack');
client.importSlack(formData, success, error);
-};
+}