summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile12
-rw-r--r--README.md100
-rw-r--r--docker/0.6/config_docker.json2
-rw-r--r--docker/dev/config_docker.json16
-rw-r--r--web/react/components/channel_header.jsx180
-rw-r--r--web/react/components/delete_channel_modal.jsx2
-rw-r--r--web/react/components/delete_post_modal.jsx2
-rw-r--r--web/react/components/edit_channel_modal.jsx2
-rw-r--r--web/react/components/edit_post_modal.jsx2
-rw-r--r--web/react/components/get_link_modal.jsx49
-rw-r--r--web/react/components/invite_member_modal.jsx270
-rw-r--r--web/react/components/mention_list.jsx186
-rw-r--r--web/react/components/new_channel.jsx134
-rw-r--r--web/react/components/post_list.jsx15
-rw-r--r--web/react/components/setting_picture.jsx30
-rw-r--r--web/react/components/sidebar.jsx233
-rw-r--r--web/react/components/signup_team_complete.jsx6
-rw-r--r--web/react/components/team_settings_modal.jsx2
-rw-r--r--web/react/components/user_profile.jsx3
-rw-r--r--web/react/components/user_settings.jsx107
-rw-r--r--web/react/components/user_settings_modal.jsx2
-rw-r--r--web/sass-files/sass/partials/_base.scss10
-rw-r--r--web/sass-files/sass/partials/_files.scss68
-rw-r--r--web/sass-files/sass/partials/_get-link.scss6
-rw-r--r--web/sass-files/sass/partials/_headers.scss14
-rw-r--r--web/sass-files/sass/partials/_modal.scss9
-rw-r--r--web/sass-files/sass/partials/_navbar.scss1
-rw-r--r--web/sass-files/sass/partials/_popover.scss7
-rw-r--r--web/sass-files/sass/partials/_responsive.scss10
-rw-r--r--web/sass-files/sass/partials/_signup.scss22
-rw-r--r--web/sass-files/sass/styles.scss1
-rw-r--r--web/templates/channel.html4
-rw-r--r--web/templates/signup_team.html2
33 files changed, 876 insertions, 633 deletions
diff --git a/Makefile b/Makefile
index e7a5d9e72..370289507 100644
--- a/Makefile
+++ b/Makefile
@@ -69,6 +69,12 @@ travis:
mkdir -p $(DIST_PATH)/api
cp -RL api/templates $(DIST_PATH)/api
+ cp APACHE-2.0.txt $(DIST_PATH)
+ cp GNU-AGPL-3.0.txt $(DIST_PATH)
+ cp LICENSE.txt $(DIST_PATH)
+ cp NOTICE.txt $(DIST_PATH)
+ cp README.md $(DIST_PATH)
+
mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js
@sed -i'.bak' 's|react-with-addons-0.13.1.js|react-with-addons-0.13.1.min.js|g' $(DIST_PATH)/web/templates/head.html
@@ -242,6 +248,12 @@ dist: install
mkdir -p $(DIST_PATH)/api
cp -RL api/templates $(DIST_PATH)/api
+ cp APACHE-2.0.txt $(DIST_PATH)
+ cp GNU-AGPL-3.0.txt $(DIST_PATH)
+ cp LICENSE.txt $(DIST_PATH)
+ cp NOTICE.txt $(DIST_PATH)
+ cp README.md $(DIST_PATH)
+
mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js
@sed -i'.bak' 's|react-with-addons-0.13.1.js|react-with-addons-0.13.1.min.js|g' $(DIST_PATH)/web/templates/head.html
diff --git a/README.md b/README.md
index 55d1383a8..cf5868f08 100644
--- a/README.md
+++ b/README.md
@@ -1,47 +1,47 @@
-**Mattermost Alpha**
-**Team Communication Service**
+**Mattermost Alpha**
+**Team Communication Service**
**Development Build**
About Mattermost
================
-Mattermost is a team communication service. It brings team messaging and file sharing into one place, accessible across PCs and phones, with archiving and search.
-
-We built Mattermost to help teams focus on what matters most to them. It works for us, we hope it works for you too.
+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.
Learn More
==========
-<ul>
-<li/>Ask the core team anything at: http://forum.mattermost.org</li>
-<li/>Share feature requests and upvotes: http://www.mattermost.org/feature-requests/</li>
-<li/>File bugs: http://www.mattermost.org/filing-issues/</li>
-<li/>Make a pull request: http://www.mattermost.org/contribute-to-mattermost/</li>
-</ul>
+- 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/
+
Installing Mattermost
=====================
-You're installing "Mattermost Alpha", a pre-released version intended for an early look at what we're building. While SpinPunch runs this version internally, it's not recommended for production deployments since we can't guarantee API stability or backwards compatibility until our production release.
+You're installing "Mattermost Alpha", a pre-released version providing an early look at what we're building. While the core team runs this version internally, it's not recommended for production since we can't guarantee API stability or backwards compatibility.
That said, any issues at all, please let us know on the Mattermost forum at: http://forum.mattermost.org
+Notes:
+- For Alpha, Docker is intentionally setup as a single container, since production deployment not yet recommended.
+
Local Machine Setup (Docker)
-----------------------------
### Mac OSX ###
-1. Follow the instructions at http://docs.docker.com/installation/mac/
- 1. Use the Boot2Docker command-line utility
+1. Follow the instructions at: http://docs.docker.com/installation/mac/
+ 1. Use the Boot2Docker command-line utility.
2. If you do command-line setup use: `boot2docker init eval “$(boot2docker shellinit)”`
-2. Get your Docker IP address with `boot2docker ip`
-3. Add a line to your /etc/hosts that goes `<Docker IP> dockerhost`
-4. Run `boot2docker shellinit` and copy the export statements to your ~/.bash\_profile
-5. Run `docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:helium`.
-6. When docker is done fetching the image, open http://dockerhost:8065/ in your browser
+2. Get your Docker IP address with: `boot2docker ip`
+3. Add a line to your /etc/hosts that goes: `<Docker IP> dockerhost`
+4. Run: `boot2docker shellinit` and copy the export statements to your ~/.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.
+1. Follow the instructions at https://docs.docker.com/installation/ubuntulinux/ or use the summary below:
``` bash
sudo apt-get update
@@ -52,8 +52,13 @@ Local Machine Setup (Docker)
newgrp docker
```
-2. Run `docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:helium`
-3. When docker is done fetching the image, open http://localhost:8065/ in your browser
+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:
@@ -69,39 +74,36 @@ Local Machine Setup (Docker)
2. Start docker container:
``` bash
- docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:helium
+ 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.
-### Notes ###
-If your ISP blocks port 25 then you may install locally but email will not be sent.
+### Additional Notes ###
+- If you want to work with the latest bits in the repository (i.e. not a stable release) you can run the cmd:
+`docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:dev`
-If you want to work with the latest bits in the repo you can run the cmd
-`docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:latest`
+- You can update to the latest bits by running:
+`docker pull mattermost/platform:dev`
-You can update to the latest bits by running
-`docker pull mattermost/platform:latest`
+- If you wish to remove mattermost-dev use:
+ `docker stop mattermost-dev`
+ `docker rm -v mattermost-dev`
-If you wish to remove mattermost-dev use the following commands
-
-1. `docker stop mattermost-dev`
-2. `docker rm -v mattermost-dev`
-
-If you wish to gain access to the container use the following commands
-1. `docker exec -ti mattermost-dev /bin/bash`
+- If you wish to gain access to a shell on the container use:
+ `docker exec -ti mattermost-dev /bin/bash`
AWS Elastic Beanstalk Setup (Docker)
------------------------------------
-1. Create a new elastic beanstalk docker application using the Dockerrun.aws.json file provided.
- 1. From the AWS console select Elastic Beanstalk
+1. Create a new elastic beanstalk docker application using the [Dockerrun.aws.json](docker/0.6/Dockerrun.aws.json) 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
+ 3. Name the application and press next.
4. Select "Create a web server" environment.
- 5. If asked, select create and IAM role and instance profile and press next.
- 6. For predefined configuration select docker. For environment type select single instance.
- 7. For application source, select upload your own and upload Dockerrun.aws.json from docker/Dockerrun.aws.json. Everything else may be left at default.
+ 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.json from [docker/0.6/Dockerrun.aws.json](docker/0.6/Dockerrun.aws.json). Everything else may be left at default.
8. Select an environment name, this is how you will refer to your environment. Make sure the URL is available then press next.
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.
@@ -111,22 +113,24 @@ AWS Elastic Beanstalk Setup (Docker)
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.
+ 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 or ./config/config/config_docker.json if you're running a docker instance.
+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.
+* *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.
Contributing
------------
-To contribute to this open source project please review the Mattermost Contribution Guidelines at http://www.mattermost.org/contribute-to-mattermost/.
+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
-------
diff --git a/docker/0.6/config_docker.json b/docker/0.6/config_docker.json
index fc3d2132a..128dc1274 100644
--- a/docker/0.6/config_docker.json
+++ b/docker/0.6/config_docker.json
@@ -86,7 +86,7 @@
"TeamSettings": {
"MaxUsersPerTeam": 150,
"AllowPublicLink": true,
- "AllowValet": false,
+ "AllowValetDefault": false,
"TermsLink": "/static/help/configure_links.html",
"PrivacyLink": "/static/help/configure_links.html",
"AboutLink": "/static/help/configure_links.html",
diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json
index f6b1e7b79..cd612c7fe 100644
--- a/docker/dev/config_docker.json
+++ b/docker/dev/config_docker.json
@@ -21,7 +21,8 @@
"ResetSalt": "IPxFzSfnDFsNsRafZxz8NaYqFKhf9y2t",
"AnalyticsUrl": "",
"UseLocalStorage": true,
- "StorageDirectory": "/mattermost/data/"
+ "StorageDirectory": "/mattermost/data/",
+ "AllowedLoginAttempts": 10
},
"SSOSettings": {
"gitlab": {
@@ -49,8 +50,8 @@
"S3Region": ""
},
"ImageSettings": {
- "ThumbnailWidth": 200,
- "ThumbnailHeight": 0,
+ "ThumbnailWidth": 120,
+ "ThumbnailHeight": 100,
"PreviewWidth": 1024,
"PreviewHeight": 0,
"ProfileWidth": 128,
@@ -69,6 +70,13 @@
"ApplePushCertPublic": "",
"ApplePushCertPrivate": ""
},
+ "RateLimitSettings": {
+ "UseRateLimiter": true,
+ "PerSec": 10,
+ "MemoryStoreSize": 10000,
+ "VaryByRemoteAddr": true,
+ "VaryByHeader": ""
+ },
"PrivacySettings": {
"ShowEmailAddress": true,
"ShowPhoneNumber": true,
@@ -78,7 +86,7 @@
"TeamSettings": {
"MaxUsersPerTeam": 150,
"AllowPublicLink": true,
- "AllowValet": false,
+ "AllowValetDefault": false,
"TermsLink": "/static/help/configure_links.html",
"PrivacyLink": "/static/help/configure_links.html",
"AboutLink": "/static/help/configure_links.html",
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 76dbe370b..a79d3547f 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -1,13 +1,11 @@
// 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 UserProfile = require( './user_profile.jsx' );
-var NavbarSearchBox =require('./search_bar.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');
@@ -21,23 +19,28 @@ var PopoverListMembers = React.createClass({
componentDidMount: function() {
var originalLeave = $.fn.popover.Constructor.prototype.leave;
$.fn.popover.Constructor.prototype.leave = function(obj) {
- var self = obj instanceof this.constructor ? obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type);
+ 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 && self.$tip) {
- self.$tip.one('mouseenter', function() {
- clearTimeout(self.timeout);
- self.$tip.one('mouseleave', function() {
- $.fn.popover.Constructor.prototype.leave.call(self, self);
+ 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);
});
- })
+ });
}
};
- $("#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');
+ $('#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');
}
});
},
@@ -45,22 +48,27 @@ var PopoverListMembers = React.createClass({
render: function() {
var popoverHtml = '';
var members = this.props.members;
- var count = (members.length > 20) ? "20+" : (members.length || '-');
+ var count;
+ if (members.length > 20) {
+ count = '20+';
+ } else {
+ count = members.length || '-';
+ }
if (members) {
- members.sort(function(a,b) {
+ members.sort(function(a, b) {
return a.username.localeCompare(b.username);
});
members.forEach(function(m) {
- popoverHtml += "<div class='text--nowrap'>" + m.username + "</div>";
+ 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-toggle="tooltip" title="View Channel Members">
- {count} <span className="glyphicon glyphicon-user" aria-hidden="true"></span>
+ <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>
);
@@ -68,53 +76,53 @@ var PopoverListMembers = React.createClass({
});
function getStateFromStores() {
- return {
- channel: ChannelStore.getCurrent(),
- memberChannel: ChannelStore.getCurrentMember(),
- memberTeam: UserStore.getCurrentUser(),
- users: ChannelStore.getCurrentExtraInfo().members,
- search_visible: PostStore.getSearchResults() != null
- };
+ 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() {
- ChannelStore.addChangeListener(this._onChange);
- ChannelStore.addExtraInfoChangeListener(this._onChange);
- PostStore.addSearchChangeListener(this._onChange);
- UserStore.addChangeListener(this._onChange);
- SocketStore.addChangeListener(this._onSocketChange);
+ ChannelStore.addChangeListener(this.onListenerChange);
+ ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
+ PostStore.addSearchChangeListener(this.onListenerChange);
+ UserStore.addChangeListener(this.onListenerChange);
+ SocketStore.addChangeListener(this.onSocketChange);
},
componentWillUnmount: function() {
- ChannelStore.removeChangeListener(this._onChange);
- ChannelStore.removeExtraInfoChangeListener(this._onChange);
- PostStore.removeSearchChangeListener(this._onChange);
- UserStore.addChangeListener(this._onChange);
+ ChannelStore.removeChangeListener(this.onListenerChange);
+ ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
+ PostStore.removeSearchChangeListener(this.onListenerChange);
+ UserStore.addChangeListener(this.onListenerChange);
},
- _onChange: function() {
+ onListenerChange: function() {
var newState = getStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
this.setState(newState);
}
- $(".channel-header__info .description").popover({placement : 'bottom', trigger: 'hover', html: true, delay: {show: 500, hide: 500}});
+ $('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover', html: true, delay: {show: 500, hide: 500}});
},
- _onSocketChange: function(msg) {
- if (msg.action === "new_user") {
+ onSocketChange: function(msg) {
+ if (msg.action === 'new_user') {
AsyncClient.getChannelExtraInfo(true);
}
},
getInitialState: function() {
return getStateFromStores();
},
- handleLeave: function(e) {
+ handleLeave: function() {
Client.leaveChannel(this.state.channel.id,
- function(data) {
+ function() {
var townsquare = ChannelStore.getByName('town-square');
utils.switchChannel(townsquare);
},
function(err) {
- AsyncClient.dispatchError(err, "handleLeave");
+ AsyncClient.dispatchError(err, 'handleLeave');
}
);
},
@@ -123,9 +131,16 @@ module.exports = React.createClass({
var user = UserStore.getCurrentUser();
- var terms = "";
+ var terms = '';
if (user.notify_props && user.notify_props.mention_keys) {
- terms = UserStore.getCurrentMentionKeys().join(' ');
+ var termKeys = UserStore.getCurrentMentionKeys();
+ if (user.notify_props.all === 'true' && termKeys.indexOf('@all') !== -1) {
+ termKeys.splice(termKeys.indexOf('@all'), 1);
+ }
+ if (user.notify_props.channel === 'true' && termKeys.indexOf('@channel') !== -1) {
+ termKeys.splice(termKeys.indexOf('@channel'), 1);
+ }
+ terms = termKeys.join(' ');
}
AppDispatcher.handleServerAction({
@@ -135,81 +150,84 @@ module.exports = React.createClass({
is_mention_search: true
});
},
-
render: function() {
-
if (this.state.channel == null) {
return null;
}
var channel = this.state.channel;
- var description = utils.textToJsx(channel.description, {"singleline": true, "noMentionHighlight": true});
+ 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 isAdmin = this.state.memberChannel.roles.indexOf('admin') > -1 || this.state.memberTeam.roles.indexOf('admin') > -1;
var isDirect = (this.state.channel.type === 'D');
if (isDirect) {
if (this.state.users.length > 1) {
- var contact = this.state.users[((this.state.users[0].id === currentId) ? 1 : 0)];
+ var contact;
+ if (this.state.users[0].id === currentId) {
+ contact = this.state.users[1];
+ } else {
+ contact = this.state.users[0];
+ }
channelTitle = contact.nickname || contact.username;
}
}
return (
- <table className="channel-header alt">
+ <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">
- <strong className="heading">{channelTitle} </strong>
- <span className="glyphicon glyphicon-chevron-down header-dropdown__icon"></span>
+ <div className='channel-header__info'>
+ <div className='dropdown'>
+ <a href='#' className='dropdown-toggle theme' type='button' id='channel_header_dropdown' data-toggle='dropdown' aria-expanded='true'>
+ <strong className='heading'>{channelTitle} </strong>
+ <span className='glyphicon glyphicon-chevron-down header-dropdown__icon'></span>
</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>
+ {!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>
+ {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 Channel 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 Channel...</a></li>
+ <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>
+ <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 Channel...</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 Channel...</a></li>
+ {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 Channel...</a></li>
: null
}
- { !ChannelStore.isDefault(channel) ?
- <li role="presentation"><a role="menuitem" href="#" onClick={this.handleLeave}>Leave Channel</a></li>
+ {!ChannelStore.isDefault(channel) ?
+ <li role='presentation'><a role='menuitem' href='#' onClick={this.handleLeave}>Leave Channel</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'>
+ <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>
}
</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 className="search-bar__container"><NavbarSearchBox /></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>
+ <div className='dropdown channel-header__links'>
+ <a href='#' className='dropdown-toggle theme' type='button' id='channel_header_right_dropdown' data-toggle='dropdown' aria-expanded='true'>
+ <span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} /> </a>
+ <ul className='dropdown-menu dropdown-menu-right' role='menu' aria-labelledby='channel_header_right_dropdown'>
+ <li role='presentation'><a role='menuitem' href='#' onClick={this.searchMentions}>Recent Mentions</a></li>
</ul>
</div>
</th>
diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx
index 64ceec450..589737271 100644
--- a/web/react/components/delete_channel_modal.jsx
+++ b/web/react/components/delete_channel_modal.jsx
@@ -34,7 +34,7 @@ module.exports = React.createClass({
var channelType = ChannelStore.getCurrent() && ChannelStore.getCurrent().type === 'P' ? "private group" : "channel"
return (
- <div className="modal fade" ref="modal" id="delete_channel" role="dialog" aria-hidden="true">
+ <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">
diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx
index f0cb809af..1b6a7e162 100644
--- a/web/react/components/delete_post_modal.jsx
+++ b/web/react/components/delete_post_modal.jsx
@@ -82,7 +82,7 @@ module.exports = React.createClass({
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" aria-hidden="true">
+ <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">
diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx
index 1b0cc185f..06d7fc3e8 100644
--- a/web/react/components/edit_channel_modal.jsx
+++ b/web/react/components/edit_channel_modal.jsx
@@ -51,7 +51,7 @@ module.exports = React.createClass({
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;
return (
- <div className="modal fade" ref="modal" id="edit_channel" role="dialog" aria-hidden="true">
+ <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">
diff --git a/web/react/components/edit_post_modal.jsx b/web/react/components/edit_post_modal.jsx
index 21b75bb6e..064d3fa94 100644
--- a/web/react/components/edit_post_modal.jsx
+++ b/web/react/components/edit_post_modal.jsx
@@ -71,7 +71,7 @@ module.exports = React.createClass({
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 edit-modal" ref="modal" id="edit_post" role="dialog" aria-hidden="true">
+ <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">
diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx
index af5314e64..ea22ad0f3 100644
--- a/web/react/components/get_link_modal.jsx
+++ b/web/react/components/get_link_modal.jsx
@@ -10,46 +10,57 @@ ZeroClipboardMixin.ZeroClipboard.config({
module.exports = React.createClass({
zeroclipboardElementsSelector: '[data-copy-btn]',
- mixins: [ ZeroClipboardMixin ],
+ mixins: [ZeroClipboardMixin],
componentDidMount: function() {
var self = this;
- if(this.refs.modal) {
+ 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') });
+ 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});
});
}
},
getInitialState: function() {
- return { };
+ return {copiedLink: false};
+ },
+ handleClick: function() {
+ this.setState({copiedLink: true});
},
render: function() {
- var currentUser = UserStore.getCurrentUser()
+ var currentUser = UserStore.getCurrentUser();
+ var copyLinkConfirm = null;
+
+ if (this.state.copiedLink) {
+ copyLinkConfirm = <p className='copy-link-confirm'>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 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>{"The link below is used for open " + strings.TeamPlural + " or if you allowed your " + strings.Team + " members to sign up using their " + strings.Company + " email addresses."}
+ <div className='modal-body'>
+ <p>{'The link below is used for open ' + strings.TeamPlural + ' or if you allowed your ' + strings.Team + ' members to sign up using their ' + strings.Company + ' email addresses.'}
</p>
- <textarea className="form-control no-resize" readOnly="true" value={this.state.value}></textarea>
+ <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 type="button" className="btn btn-primary pull-left" data-clipboard-text={this.state.value}>Copy Link</button>
+ <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>
);
- } else {
- return <div/>;
}
+ return <div/>;
}
});
diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx
index fed96b50a..3eca79bae 100644
--- a/web/react/components/invite_member_modal.jsx
+++ b/web/react/components/invite_member_modal.jsx
@@ -2,7 +2,7 @@
// 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 UserStore = require('../stores/user_store.jsx');
var ConfirmModal = require('./confirm_modal.jsx');
@@ -15,20 +15,19 @@ module.exports = React.createClass({
return;
}
- var not_empty = false;
- for (var i = 0; i < self.state.invite_ids.length; i++) {
- var index = self.state.invite_ids[i];
- if (self.refs["email"+index].getDOMNode().value.trim() !== '') {
- not_empty = true;
+ var notEmpty = false;
+ for (var i = 0; i < self.state.inviteIds.length; i++) {
+ var index = self.state.inviteIds[i];
+ if (self.refs['email' + index].getDOMNode().value.trim() !== '') {
+ notEmpty = true;
break;
}
}
- if (not_empty) {
+ if (notEmpty) {
$('#confirm_invite_modal').modal('show');
e.preventDefault();
}
-
});
$('#invite_member').on('hidden.bs.modal', function() {
@@ -36,52 +35,54 @@ module.exports = React.createClass({
});
},
handleSubmit: function(e) {
- var invite_ids = this.state.invite_ids;
- var count = invite_ids.length;
+ var inviteIds = this.state.inviteIds;
+ var count = inviteIds.length;
var invites = [];
- var email_errors = this.state.email_errors;
- var first_name_errors = this.state.first_name_errors;
- var last_name_errors = this.state.last_name_errors;
+ var emailErrors = this.state.emailErrors;
+ var firstNameErrors = this.state.firstNameErrors;
+ var lastNameErrors = this.state.lastNameErrors;
var valid = true;
for (var i = 0; i < count; i++) {
- var index = invite_ids[i];
+ var index = inviteIds[i];
var invite = {};
- invite.email = this.refs["email"+index].getDOMNode().value.trim();
+ invite.email = this.refs['email' + index].getDOMNode().value.trim();
if (!invite.email || !utils.isEmail(invite.email)) {
- email_errors[index] = "Please enter a valid email address";
+ emailErrors[index] = 'Please enter a valid email address';
valid = false;
} else {
- email_errors[index] = "";
+ emailErrors[index] = '';
}
if (config.AllowInviteNames) {
- invite.first_name = this.refs["first_name"+index].getDOMNode().value.trim();
- if (!invite.first_name && config.RequireInviteNames) {
- first_name_errors[index] = "This is a required field";
+ invite.firstName = this.refs['first_name' + index].getDOMNode().value.trim();
+ if (!invite.firstName && config.RequireInviteNames) {
+ firstNameErrors[index] = 'This is a required field';
valid = false;
} else {
- first_name_errors[index] = "";
+ firstNameErrors[index] = '';
}
- invite.last_name = this.refs["last_name"+index].getDOMNode().value.trim();
- if (!invite.last_name && config.RequireInviteNames) {
- last_name_errors[index] = "This is a required field";
+ invite.lastName = this.refs['last_name' + index].getDOMNode().value.trim();
+ if (!invite.lastName && config.RequireInviteNames) {
+ lastNameErrors[index] = 'This is a required field';
valid = false;
} else {
- last_name_errors[index] = "";
+ lastNameErrors[index] = '';
}
}
invites.push(invite);
}
- this.setState({ email_errors: email_errors, first_name_errors: first_name_errors, last_name_errors: last_name_errors });
+ this.setState({emailErrors: emailErrors, firstNameErrors: firstNameErrors, lastNameErrors: lastNameErrors});
- if (!valid || invites.length === 0) return;
+ if (!valid || invites.length === 0) {
+ return;
+ }
- var data = {}
- data["invites"] = invites;
+ var data = {};
+ data.invites = invites;
Client.inviteMembers(data,
function() {
@@ -89,146 +90,177 @@ module.exports = React.createClass({
$(this.refs.modal.getDOMNode()).modal('hide');
}.bind(this),
function(err) {
- if (err.message === "This person is already on your team") {
- email_errors[err.detailed_error] = err.message;
- this.setState({ email_errors: email_errors });
+ if (err.message === 'This person is already on your team') {
+ emailErrors[err.detailed_error] = err.message;
+ this.setState({emailErrors: emailErrors});
+ } else {
+ this.setState({serverError: err.message});
}
- else
- this.setState({ server_error: err.message});
}.bind(this)
);
-
},
componentDidUpdate: function() {
$(this.refs.modalBody.getDOMNode()).css('max-height', $(window).height() - 200);
$(this.refs.modalBody.getDOMNode()).css('overflow-y', 'scroll');
},
addInviteFields: function() {
- var count = this.state.id_count + 1;
- var invite_ids = this.state.invite_ids;
- invite_ids.push(count);
- this.setState({ invite_ids: invite_ids, id_count: count });
+ var count = this.state.idCount + 1;
+ var inviteIds = this.state.inviteIds;
+ inviteIds.push(count);
+ this.setState({inviteIds: inviteIds, idCount: count});
},
clearFields: function() {
- var invite_ids = this.state.invite_ids;
+ var inviteIds = this.state.inviteIds;
- for (var i = 0; i < invite_ids.length; i++) {
- var index = invite_ids[i];
- this.refs["email"+index].getDOMNode().value = "";
+ for (var i = 0; i < inviteIds.length; i++) {
+ var index = inviteIds[i];
+ this.refs['email' + index].getDOMNode().value = '';
if (config.AllowInviteNames) {
- this.refs["first_name"+index].getDOMNode().value = "";
- this.refs["last_name"+index].getDOMNode().value = "";
+ this.refs['first_name' + index].getDOMNode().value = '';
+ this.refs['last_name' + index].getDOMNode().value = '';
}
}
this.setState({
- invite_ids: [0],
- id_count: 0,
- email_errors: {},
- first_name_errors: {},
- last_name_errors: {}
+ inviteIds: [0],
+ idCount: 0,
+ emailErrors: {},
+ firstNameErrors: {},
+ lastNameErrors: {}
});
},
removeInviteFields: function(index) {
- var count = this.state.id_count;
- var invite_ids = this.state.invite_ids;
- var i = invite_ids.indexOf(index);
- if (i > -1) invite_ids.splice(i, 1);
- if (!invite_ids.length) invite_ids.push(++count);
- this.setState({ invite_ids: invite_ids, id_count: count });
+ var count = this.state.idCount;
+ var inviteIds = this.state.inviteIds;
+ var i = inviteIds.indexOf(index);
+ if (i > -1) {
+ inviteIds.splice(i, 1);
+ }
+ if (!inviteIds.length) {
+ inviteIds.push(++count);
+ }
+ this.setState({inviteIds: inviteIds, idCount: count});
},
getInitialState: function() {
return {
- invite_ids: [0],
- id_count: 0,
- email_errors: {},
- first_name_errors: {},
- last_name_errors: {}
+ inviteIds: [0],
+ idCount: 0,
+ emailErrors: {},
+ firstNameErrors: {},
+ lastNameErrors: {}
};
},
render: function() {
- var currentUser = UserStore.getCurrentUser()
+ var currentUser = UserStore.getCurrentUser();
if (currentUser != null) {
- var invite_sections = [];
- var invite_ids = this.state.invite_ids;
- var self = this;
- for (var i = 0; i < invite_ids.length; i++) {
- var index = invite_ids[i];
- var email_error = this.state.email_errors[index] ? <label className='control-label'>{ this.state.email_errors[index] }</label> : null;
- var first_name_error = this.state.first_name_errors[index] ? <label className='control-label'>{ this.state.first_name_errors[index] }</label> : null;
- var last_name_error = this.state.last_name_errors[index] ? <label className='control-label'>{ this.state.last_name_errors[index] }</label> : null;
-
- invite_sections[index] = (
- <div key={"key" + index}>
- <div>
- <button type="button" className="btn btn-link remove__member" onClick={this.removeInviteFields.bind(this, index)}><span className="fa fa-trash"></span></button>
- </div>
- <div className={ email_error ? "form-group invite has-error" : "form-group invite" }>
- <input onKeyUp={this.displayNameKeyUp} type="text" ref={"email"+index} className="form-control" placeholder="email@domain.com" maxLength="64" />
- { email_error }
- </div>
- <div className="row--invite">
- { config.AllowInviteNames ?
- <div className="col-sm-6">
- <div className={ first_name_error ? "form-group has-error" : "form-group" }>
- <input type="text" className="form-control" ref={"first_name"+index} placeholder="First name" maxLength="64" />
- { first_name_error }
- </div>
- </div>
- : "" }
- { config.AllowInviteNames ?
- <div className="col-sm-6">
- <div className={ last_name_error ? "form-group has-error" : "form-group" }>
- <input type="text" className="form-control" ref={"last_name"+index} placeholder="Last name" maxLength="64" />
- { last_name_error }
- </div>
- </div>
- : "" }
+ var inviteSections = [];
+ var inviteIds = this.state.inviteIds;
+ for (var i = 0; i < inviteIds.length; i++) {
+ var index = inviteIds[i];
+ var emailError = null;
+ if (this.state.emailErrors[index]) {
+ emailError = <label className='control-label'>{this.state.emailErrors[index]}</label>;
+ }
+ var firstNameError = null;
+ if (this.state.firstNameErrors[index]) {
+ firstNameError = <label className='control-label'>{this.state.firstNameErrors[index]}</label>;
+ }
+ var lastNameError = null;
+ if (this.state.lastNameErrors[index]) {
+ lastNameError = <label className='control-label'>{this.state.lastNameErrors[index]}</label>;
+ }
+
+ var removeButton = null;
+ if (index) {
+ removeButton = (<div>
+ <button type='button' className='btn btn-link remove__member' onClick={this.removeInviteFields.bind(this, index)}><span className='fa fa-trash'></span></button>
+ </div>);
+ }
+ var emailClass = 'form-group invite';
+ if (emailError) {
+ emailClass += ' has-error';
+ }
+
+ var nameFields = null;
+ if (config.AllowInviteNames) {
+ var firstNameClass = 'form-group';
+ if (firstNameError) {
+ firstNameClass += ' has-error';
+ }
+ var lastNameClass = 'form-group';
+ if (lastNameError) {
+ lastNameClass += ' has-error';
+ }
+ nameFields = (<div className='row--invite'>
+ <div className='col-sm-6'>
+ <div className={firstNameClass}>
+ <input type='text' className='form-control' ref={'first_name' + index} placeholder='First name' maxLength='64' />
+ {firstNameError}
+ </div>
+ </div>
+ <div className='col-sm-6'>
+ <div className={lastNameClass}>
+ <input type='text' className='form-control' ref={'last_name' + index} placeholder='Last name' maxLength='64' />
+ {lastNameError}
+ </div>
+ </div>
+ </div>);
+ }
+
+ inviteSections[index] = (
+ <div key={'key' + index}>
+ {removeButton}
+ <div className={emailClass}>
+ <input onKeyUp={this.displayNameKeyUp} type='text' ref={'email' + index} className='form-control' placeholder='email@domain.com' maxLength='64' />
+ {emailError}
</div>
+ {nameFields}
</div>
);
}
- var server_error = this.state.server_error ? <div className='form-group has-error'><label className='control-label'>{ this.state.server_error }</label></div> : null;
+ var serverError = null;
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
+ }
return (
<div>
- <div className="modal fade" ref="modal" id="invite_member" 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" data-reactid=".5.0.0.0.0"><span aria-hidden="true" data-reactid=".5.0.0.0.0.0">×</span></button>
- <h4 className="modal-title" id="myModalLabel">Invite New Member</h4>
+ <div className='modal fade' ref='modal' id='invite_member' 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' data-reactid='.5.0.0.0.0'><span aria-hidden='true' data-reactid='.5.0.0.0.0.0'>×</span></button>
+ <h4 className='modal-title' id='myModalLabel'>Invite New Member</h4>
</div>
- <div ref="modalBody" className="modal-body">
- <form role="form">
- { invite_sections }
+ <div ref='modalBody' className='modal-body'>
+ <form role='form'>
+ {inviteSections}
</form>
- { server_error }
- <button type="button" className="btn btn-default" onClick={this.addInviteFields}>Add another</button>
+ {serverError}
+ <button type='button' className='btn btn-default' onClick={this.addInviteFields}>Add another</button>
<br/>
<br/>
<span>People invited automatically join Town Square channel.</span>
</div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button>
- <button onClick={this.handleSubmit} type="button" className="btn btn-primary">Send Invitations</button>
+ <div className='modal-footer'>
+ <button type='button' className='btn btn-default' data-dismiss='modal'>Cancel</button>
+ <button onClick={this.handleSubmit} type='button' className='btn btn-primary'>Send Invitations</button>
</div>
</div>
</div>
</div>
<ConfirmModal
- id="confirm_invite_modal"
- parent_id="invite_member"
- title="Discard Invitations?"
- message="You have unsent invitations, are you sure you want to discard them?"
- confirm_button="Yes, Discard"
+ id='confirm_invite_modal'
+ parent_id='invite_member'
+ title='Discard Invitations?'
+ message='You have unsent invitations, are you sure you want to discard them?'
+ confirm_button='Yes, Discard/'
/>
</div>
);
- } else {
- return <div/>;
}
+ return <div/>;
}
});
diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx
index 71a6083d2..5f1bb6d0e 100644
--- a/web/react/components/mention_list.jsx
+++ b/web/react/components/mention_list.jsx
@@ -15,81 +15,84 @@ var MAX_ITEMS_IN_LIST = 25;
var ITEM_HEIGHT = 36;
module.exports = React.createClass({
- displayName: "MentionList",
+ displayName: 'MentionList',
componentDidMount: function() {
- PostStore.addMentionDataChangeListener(this._onChange);
+ PostStore.addMentionDataChangeListener(this.onListenerChange);
var self = this;
- $('body').on('keydown.mentionlist', '#'+this.props.id,
+ $('.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 });
+ }
+ });
+
+ $('body').on('keydown.mentionlist', '#' + this.props.id,
function(e) {
- if (!self.isEmpty() && self.state.mentionText != '-1' && (e.which === 13 || e.which === 9)) {
+ if (!self.isEmpty() && self.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)) {
+ } else if (!self.isEmpty() && self.state.mentionText !== '-1' && (e.which === 38 || e.which === 40)) {
e.stopPropagation();
e.preventDefault();
- var tempSelectedMention = -1;
- 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 });
- else {
- while (self.getSelection(++tempSelectedMention))
- ; //Need to find the top of the list
- self.setState({ selectedMention: tempSelectedMention - 1, selectedUsername: self.refs['mention' + (tempSelectedMention - 1)].props.username });
+ 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});
+ }
+ } 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});
}
- }
- 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 });
- else
- self.setState({ selectedMention: 0, selectedUsername: self.refs.mention0.props.username });
}
- self.scrollToMention(e.which, tempSelectedMention);
+ self.scrollToMention(e.which);
}
}
);
$(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"})
+ 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'});
}
});
},
componentWillUnmount: function() {
- PostStore.removeMentionDataChangeListener(this._onChange);
- $('body').off('keydown.mentionlist', '#'+this.props.id);
+ PostStore.removeMentionDataChangeListener(this.onListenerChange);
+ $('body').off('keydown.mentionlist', '#' + this.props.id);
},
componentDidUpdate: function() {
- 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)) {
+ 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;
var foundMatch = false;
while (tempSelectedMention < this.state.selectedMention && this.getSelection(++tempSelectedMention)) {
if (this.state.selectedUsername === this.refs['mention' + tempSelectedMention].props.username) {
- this.setState({ selectedMention: tempSelectedMention });
+ this.setState({selectedMention: tempSelectedMention});
foundMatch = true;
break;
}
}
if (this.getSelection(0) && !foundMatch) {
- this.setState({ selectedMention: 0, selectedUsername: this.refs.mention0.props.username });
+ this.setState({selectedMention: 0, selectedUsername: this.refs.mention0.props.username});
}
}
- }
- else if (this.state.selectedMention !== 0) {
- this.setState({ selectedMention: 0, selectedUsername: "" });
+ } else if (this.state.selectedMention !== 0) {
+ this.setState({selectedMention: 0, selectedUsername: ''});
}
},
- _onChange: function(id, mentionText, excludeList) {
- if (id !== this.props.id) return;
+ onListenerChange: function(id, mentionText, excludeList) {
+ if (id !== this.props.id) {
+ return;
+ }
var newState = this.state;
- if (mentionText != null) newState.mentionText = mentionText;
- if (excludeList != null) newState.excludeUsers = excludeList;
+ if (mentionText != null) {
+ newState.mentionText = mentionText;
+ }
+ if (excludeList != null) {
+ newState.excludeUsers = excludeList;
+ }
this.setState(newState);
},
@@ -100,44 +103,49 @@ module.exports = React.createClass({
username: name
});
- this.setState({ mentionText: '-1' });
+ this.setState({mentionText: '-1'});
},
handleMouseEnter: function(listId) {
- this.setState({ selectedMention: listId, selectedUsername: this.refs['mention' + listId].props.username });
+ this.setState({selectedMention: listId, selectedUsername: this.refs['mention' + listId].props.username});
},
getSelection: function(listId) {
- if (!this.refs['mention' + listId])
+ if (!this.refs['mention' + listId]) {
return false;
- else
- return true;
+ }
+ return true;
},
addCurrentMention: function() {
- if (!this.getSelection(this.state.selectedMention))
+ if (!this.getSelection(this.state.selectedMention)) {
this.addFirstMention();
- else
+ } else {
this.refs['mention' + this.state.selectedMention].handleClick();
+ }
},
addFirstMention: function() {
- if (!this.refs.mention0) return;
+ if (!this.refs.mention0) {
+ return;
+ }
this.refs.mention0.handleClick();
},
isEmpty: function() {
return (!this.refs.mention0);
},
- scrollToMention: function(keyPressed, ifLoopUp) {
- var direction = keyPressed === 38 ? "up" : "down";
+ scrollToMention: function(keyPressed) {
+ var direction;
+ if (keyPressed === 38) {
+ direction = 'up';
+ } else {
+ direction = 'down';
+ }
var scrollAmount = 0;
- if (direction === "up" && ifLoopUp !== -1)
- scrollAmount = $("#mentionsbox").height() * 100; //Makes sure that it scrolls all the way to the bottom
- else if (direction === "down" && this.state.selectedMention === 0)
- scrollAmount = 0;
- else if (direction === "up")
- scrollAmount = "-=" + ($('#'+this.refs['mention' + this.state.selectedMention].props.id +"_mentions").innerHeight() - 5);
- else if (direction === "down")
- scrollAmount = "+=" + ($('#'+this.refs['mention' + this.state.selectedMention].props.id +"_mentions").innerHeight() - 5);
+ if (direction === 'up') {
+ scrollAmount = '-=' + ($('#' + this.refs['mention' + this.state.selectedMention].props.id + '_mentions').innerHeight() - 5);
+ } else if (direction === 'down') {
+ scrollAmount = '+=' + ($('#' + this.refs['mention' + this.state.selectedMention].props.id + '_mentions').innerHeight() - 5);
+ }
- $("#mentionsbox").animate({
+ $('#mentionsbox').animate({
scrollTop: scrollAmount
}, 75);
},
@@ -151,12 +159,14 @@ module.exports = React.createClass({
return false;
},
getInitialState: function() {
- return { excludeUsers: [], mentionText: "-1", selectedMention: 0, selectedUsername: "" };
+ return {excludeUsers: [], mentionText: '-1', selectedMention: 0, selectedUsername: ''};
},
render: function() {
var self = this;
var mentionText = this.state.mentionText;
- if (mentionText === '-1') return null;
+ if (mentionText === '-1') {
+ return null;
+ }
var profiles = UserStore.getActiveOnlyProfiles();
var users = [];
@@ -165,32 +175,38 @@ module.exports = React.createClass({
}
var all = {};
- all.username = "all";
- all.nickname = "";
- all.secondary_text = "Notifies everyone in the team";
- all.id = "allmention";
+ all.username = 'all';
+ all.nickname = '';
+ all.secondary_text = 'Notifies everyone in the team';
+ all.id = 'allmention';
users.push(all);
var channel = {};
- channel.username = "channel";
- channel.nickname = "";
- channel.secondary_text = "Notifies everyone in the channel";
- channel.id = "channelmention";
+ channel.username = 'channel';
+ channel.nickname = '';
+ channel.secondary_text = 'Notifies everyone in the channel';
+ channel.id = 'channelmention';
users.push(channel);
- users.sort(function(a,b) {
- if (a.username < b.username) return -1;
- if (a.username > b.username) return 1;
+ users.sort(function(a, b) {
+ if (a.username < b.username) {
+ return -1;
+ }
+ if (a.username > b.username) {
+ return 1;
+ }
return 0;
});
var mentions = {};
var index = 0;
for (var i = 0; i < users.length && index < MAX_ITEMS_IN_LIST; i++) {
- if (this.alreadyMentioned(users[i].username)) continue;
-
- 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) {
+ if (this.alreadyMentioned(users[i].username)) {
+ continue;
+ }
+ 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) {
mentions[index] = (
<Mention
ref={'mention' + index}
@@ -198,7 +214,7 @@ module.exports = React.createClass({
secondary_text={Utils.getFullName(users[i])}
id={users[i].id}
listId={index}
- isFocused={this.state.selectedMention === index ? "mentions-focus" : ""}
+ isFocused={this.state.selectedMention === index ? 'mentions-focus' : ''}
handleMouseEnter={function(value) { return function() { self.handleMouseEnter(value); } }(index)}
handleClick={this.handleClick} />
);
@@ -208,21 +224,23 @@ module.exports = React.createClass({
var numMentions = Object.keys(mentions).length;
- if (numMentions < 1) return null;
+ if (numMentions < 1) {
+ return null;
+ }
- var $mention_tab = $('#'+this.props.id);
- var maxHeight = Math.min(MAX_HEIGHT_LIST, $mention_tab.offset().top - 10);
+ var $mentionTab = $('#' + this.props.id);
+ var maxHeight = Math.min(MAX_HEIGHT_LIST, $mentionTab.offset().top - 10);
var style = {
- height: Math.min(maxHeight, (numMentions*ITEM_HEIGHT) + 4),
- width: $mention_tab.parent().width(),
- bottom: $(window).height() - $mention_tab.offset().top,
- left: $mention_tab.offset().left
+ height: Math.min(maxHeight, (numMentions * ITEM_HEIGHT) + 4),
+ width: $mentionTab.parent().width(),
+ bottom: $(window).height() - $mentionTab.offset().top,
+ left: $mentionTab.offset().left
};
return (
- <div className="mentions--top" style={style}>
- <div ref="mentionlist" className="mentions-box" id="mentionsbox">
- { mentions }
+ <div className='mentions--top' style={style}>
+ <div ref='mentionlist' className='mentions-box' id='mentionsbox'>
+ {mentions}
</div>
</div>
);
diff --git a/web/react/components/new_channel.jsx b/web/react/components/new_channel.jsx
index 49e088458..93884f6eb 100644
--- a/web/react/components/new_channel.jsx
+++ b/web/react/components/new_channel.jsx
@@ -1,138 +1,146 @@
// 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 asyncClient = require('../utils/async_client.jsx');
var UserStore = require('../stores/user_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
-var Constants = require('../utils/constants.jsx');
module.exports = React.createClass({
+ displayName: 'NewChannelModal',
handleSubmit: function(e) {
e.preventDefault();
var channel = {};
- var state = { server_error: "" };
+ var state = {serverError: ''};
channel.display_name = this.refs.display_name.getDOMNode().value.trim();
if (!channel.display_name) {
- state.display_name_error = "This field is required";
+ state.displayNameError = '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";
+ } else if (channel.display_name.length > 22) {
+ state.displayNameError = 'This field must be less than 22 characters';
state.inValid = true;
- }
- else {
- state.display_name_error = "";
+ } else {
+ state.displayNameError = '';
}
channel.name = this.refs.channel_name.getDOMNode().value.trim();
if (!channel.name) {
- state.name_error = "This field is required";
+ state.nameError = 'This field is required';
state.inValid = true;
- }
- else if(channel.name.length > 22){
- state.name_error = "This field must be less than 22 characters";
+ } else if (channel.name.length > 22) {
+ state.nameError = '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, allowing '-' but not starting or ending with '-'";
+ } else {
+ var cleanedName = utils.cleanUpUrlable(channel.name);
+ if (cleanedName !== channel.name) {
+ state.nameError = "Must be lowercase alphanumeric characters, allowing '-' but not starting or ending with '-'";
state.inValid = true;
- }
- else {
- state.name_error = "";
+ } else {
+ state.nameError = '';
}
}
this.setState(state);
- if (state.inValid)
+ if (state.inValid) {
return;
+ }
var cu = UserStore.getCurrentUser();
channel.team_id = cu.team_id;
channel.description = this.refs.channel_desc.getDOMNode().value.trim();
- channel.type = this.state.channel_type;
+ channel.type = this.state.channelType;
var self = this;
client.createChannel(channel,
function() {
- this.refs.display_name.getDOMNode().value = "";
- this.refs.channel_name.getDOMNode().value = "";
- this.refs.channel_desc.getDOMNode().value = "";
+ this.refs.display_name.getDOMNode().value = '';
+ this.refs.channel_name.getDOMNode().value = '';
+ this.refs.channel_desc.getDOMNode().value = '';
$(self.refs.modal.getDOMNode()).modal('hide');
- window.location = TeamStore.getCurrentTeamUrl() + "/channels/" + channel.name;
+ window.location = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
asyncClient.getChannels(true);
}.bind(this),
function(err) {
- state.server_error = err.message;
+ state.serverError = err.message;
state.inValid = true;
this.setState(state);
}.bind(this)
);
},
- 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;
+ displayNameKeyUp: function() {
+ var displayName = this.refs.display_name.getDOMNode().value.trim();
+ var channelName = utils.cleanUpUrlable(displayName);
+ this.refs.channel_name.getDOMNode().value = channelName;
},
componentDidMount: function() {
var self = this;
$(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
var button = e.relatedTarget;
- self.setState({ channel_type: $(button).attr('data-channeltype') });
+ self.setState({channelType: $(button).attr('data-channeltype')});
});
},
getInitialState: function() {
- return { channel_type: "" };
+ return {channelType: ''};
},
render: function() {
+ var displayNameError = null;
+ var nameError = null;
+ var serverError = null;
+ var displayNameClass = 'form-group';
+ var nameClass = 'form-group';
- 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;
+ if (this.state.displayNameError) {
+ displayNameError = <label className='control-label'>{this.state.displayNameError}</label>;
+ displayNameClass += ' has-error';
+ }
+ if (this.state.nameError) {
+ nameError = <label className='control-label'>{this.state.nameError}</label>;
+ nameClass += ' has-error';
+ }
+ 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" 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">
- <span aria-hidden="true">&times;</span>
- <span className="sr-only">Close</span>
+ <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'>
+ <span aria-hidden='true'>&times;</span>
+ <span className='sr-only'>Cancel</span>
</button>
- <h4 className="modal-title">New Channel</h4>
+ <h4 className='modal-title'>New Channel</h4>
</div>
- <div className="modal-body">
- <form role="form">
- <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} type="text" ref="display_name" className="form-control" placeholder="Enter display name" maxLength="64" />
- { display_name_error }
+ <input onKeyUp={this.displayNameKeyUp} type='text' ref='display_name' className='form-control' placeholder='Enter display name' 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 type="text" className="form-control" ref="channel_name" placeholder="lowercase alphanumeric's only" maxLength="64" />
- { name_error }
+ <input type='text' className='form-control' ref='channel_name' placeholder="lowercase alphanumeric's only" maxLength='64' />
+ {nameError}
</div>
- <div className="form-group">
+ <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'></textarea>
</div>
- { server_error }
- </form>
- </div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
- <button onClick={this.handleSubmit} type="button" className="btn btn-primary">Create New Channel</button>
- </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 Channel</button>
+ </div>
+ </form>
</div>
</div>
</div>
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 3f59d5843..bb1b1704c 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -37,15 +37,23 @@ module.exports = React.createClass({
componentDidMount: function() {
var user = UserStore.getCurrentUser();
if (user.props && user.props.theme) {
- utils.changeCss('a.theme', 'color:'+user.props.theme+'; fill:'+user.props.theme+'!important;');
utils.changeCss('div.theme', 'background-color:'+user.props.theme+';');
utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme+';');
- utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, -10) +';');
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+';}');
}
+ 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;');
+ } 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) +';');
+ $('.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) +';');
+ $('.team__header').addClass('theme--gray');
+ }
PostStore.addChangeListener(this._onChange);
ChannelStore.addChangeListener(this._onChange);
@@ -311,7 +319,6 @@ module.exports = React.createClass({
} else if (channel.type === 'D') {
var teammate = utils.getDirectTeammate(channel.id)
-
if (teammate) {
var teammate_name = teammate.nickname.length > 0 ? teammate.nickname : teammate.username;
more_messages = (
@@ -399,7 +406,7 @@ module.exports = React.createClass({
var postCtls = [];
if (posts) {
- var previousPostDay = posts[order[order.length-1]] ? utils.getDateForUnixTicks(posts[order[order.length-1]].create_at): new Date();
+ var previousPostDay = new Date(0);
var currentPostDay;
for (var i = order.length-1; i >= 0; i--) {
diff --git a/web/react/components/setting_picture.jsx b/web/react/components/setting_picture.jsx
index fa4c8bb62..e97b67706 100644
--- a/web/react/components/setting_picture.jsx
+++ b/web/react/components/setting_picture.jsx
@@ -20,8 +20,14 @@ module.exports = React.createClass({
}
},
render: function() {
- var client_error = this.props.client_error ? <div className='form-group has-error'><label className='control-label'>{ this.props.client_error }</label></div> : null;
- var server_error = this.props.server_error ? <div className='form-group has-error'><label className='control-label'>{ this.props.server_error }</label></div> : null;
+ var clientError = null;
+ if (this.props.client_error) {
+ clientError = <div className='form-group has-error'><label className='control-label'>{this.props.client_error}</label></div>;
+ }
+ var serverError = null;
+ if (this.props.server_error) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.props.server_error}</label></div>;
+ }
var img = null;
if (this.props.picture) {
@@ -30,8 +36,20 @@ module.exports = React.createClass({
img = (<img ref='image' className='profile-img' src={this.props.src}/>);
}
- var self = this;
+ var confirmButton;
+ if (this.props.loadingPicture) {
+ confirmButton = <img className='spinner' src='/static/images/load.gif'/>;
+ } else {
+ var confirmButtonClass = 'btn btn-sm';
+ if (this.props.submitActive) {
+ confirmButtonClass += ' btn-primary';
+ } else {
+ confirmButtonClass += ' btn-inactive disabled';
+ }
+ confirmButton = <a className={confirmButtonClass} onClick={this.props.submit}>Save</a>;
+ }
+ var self = this;
return (
<ul className='section-max'>
<li className='col-xs-12 section-title'>{this.props.title}</li>
@@ -41,10 +59,10 @@ module.exports = React.createClass({
{img}
</li>
<li className='setting-list-item'>
- {server_error}
- {client_error}
+ {serverError}
+ {clientError}
<span className='btn btn-sm btn-primary btn-file sel-btn'>Select<input ref='input' accept='.jpg,.png,.bmp' type='file' onChange={this.props.pictureChange}/></span>
- <a className={this.props.submitActive ? 'btn btn-sm btn-primary' : 'btn btn-sm btn-inactive disabled'} onClick={this.props.submit}>Save</a>
+ {confirmButton}
<a className='btn btn-sm theme' href='#' onClick={self.props.updateSection}>Cancel</a>
</li>
</ul>
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 5b8d6c542..1d39f5f67 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -7,7 +7,7 @@ var AsyncClient = require('../utils/async_client.jsx');
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 BrowserStore = require('../stores/browser_store.jsx');
var utils = require('../utils/utils.jsx');
var SidebarHeader = require('./sidebar_header.jsx');
var SearchBox = require('./search_bar.jsx');
@@ -17,13 +17,15 @@ var ActionTypes = Constants.ActionTypes;
function getStateFromStores() {
var members = ChannelStore.getAllMembers();
- var team_member_map = UserStore.getActiveOnlyProfiles();
- var current_id = ChannelStore.getCurrentId();
+ var teamMemberMap = UserStore.getActiveOnlyProfiles();
+ var currentId = ChannelStore.getCurrentId();
var teammates = [];
- for (var id in team_member_map) {
- if (id === UserStore.getCurrentId()) continue;
- teammates.push(team_member_map[id]);
+ for (var id in teamMemberMap) {
+ if (id === UserStore.getCurrentId()) {
+ continue;
+ }
+ teammates.push(teamMemberMap[id]);
}
// Create lists of all read and unread direct channels
@@ -32,11 +34,11 @@ function getStateFromStores() {
for (var i = 0; i < teammates.length; i++) {
var teammate = teammates[i];
- if (teammate.id == UserStore.getCurrentId()) {
+ if (teammate.id === UserStore.getCurrentId()) {
continue;
}
- var channelName = "";
+ var channelName = '';
if (teammate.id > UserStore.getCurrentId()) {
channelName = UserStore.getCurrentId() + '__' + teammate.id;
} else {
@@ -46,17 +48,17 @@ function getStateFromStores() {
var channel = ChannelStore.getByName(channelName);
if (channel != null) {
- channel.display_name = utils.getDisplayName(teammate);
+ channel.display_name = teammate.username;
channel.teammate_username = teammate.username;
channel.status = UserStore.getStatus(teammate.id);
var channelMember = members[channel.id];
- var msg_count = channel.total_msg_count - channelMember.msg_count;
- if (msg_count > 0) {
- channel.unread = msg_count;
+ var msgCount = channel.total_msg_count - channelMember.msg_count;
+ if (msgCount > 0) {
+ channel.unread = msgCount;
showDirectChannels.push(channel);
- } else if (current_id === channel.id) {
+ } else if (currentId === channel.id) {
showDirectChannels.push(channel);
} else {
readDirectChannels.push(channel);
@@ -74,13 +76,22 @@ function getStateFromStores() {
// 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(a,b) {
+ readDirectChannels.sort(function(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 (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 (a.display_name < b.display_name) {
+ return -1;
+ }
+ if (a.display_name > b.display_name) {
+ return 1;
+ }
return 0;
});
@@ -91,15 +102,19 @@ function getStateFromStores() {
}
readDirectChannels = readDirectChannels.slice(index);
- showDirectChannels.sort(function(a,b) {
- if (a.display_name < b.display_name) return -1;
- if (a.display_name > b.display_name) return 1;
+ showDirectChannels.sort(function(a, b) {
+ if (a.display_name < b.display_name) {
+ return -1;
+ }
+ if (a.display_name > b.display_name) {
+ return 1;
+ }
return 0;
});
}
return {
- active_id: current_id,
+ active_id: currentId,
channels: ChannelStore.getAll(),
members: members,
showDirectChannels: showDirectChannels,
@@ -108,12 +123,13 @@ function getStateFromStores() {
}
module.exports = React.createClass({
+ displayName: 'Sidebar',
componentDidMount: function() {
- ChannelStore.addChangeListener(this._onChange);
- UserStore.addChangeListener(this._onChange);
- UserStore.addStatusesChangeListener(this._onChange);
- SocketStore.addChangeListener(this._onSocketChange);
- $(".nav-pills__container").perfectScrollbar();
+ ChannelStore.addChangeListener(this.onChange);
+ UserStore.addChangeListener(this.onChange);
+ UserStore.addStatusesChangeListener(this.onChange);
+ SocketStore.addChangeListener(this.onSocketChange);
+ $('.nav-pills__container').perfectScrollbar();
this.updateTitle();
},
@@ -121,93 +137,88 @@ module.exports = React.createClass({
this.updateTitle();
},
componentWillUnmount: function() {
- ChannelStore.removeChangeListener(this._onChange);
- UserStore.removeChangeListener(this._onChange);
- UserStore.removeStatusesChangeListener(this._onChange);
- SocketStore.removeChangeListener(this._onSocketChange);
+ ChannelStore.removeChangeListener(this.onChange);
+ UserStore.removeChangeListener(this.onChange);
+ UserStore.removeStatusesChangeListener(this.onChange);
+ SocketStore.removeChangeListener(this.onSocketChange);
},
- _onChange: function() {
+ onChange: function() {
var newState = getStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
this.setState(newState);
}
},
- _onSocketChange: function(msg) {
- if (msg.action == "posted") {
+ onSocketChange: function(msg) {
+ if (msg.action === 'posted') {
if (ChannelStore.getCurrentId() === msg.channel_id) {
AsyncClient.getChannels(true, window.isActive);
} else {
AsyncClient.getChannels(true);
}
- if (UserStore.getCurrentId() != msg.user_id) {
-
+ if (UserStore.getCurrentId() !== msg.user_id) {
var mentions = msg.props.mentions ? JSON.parse(msg.props.mentions) : [];
var channel = ChannelStore.get(msg.channel_id);
var user = UserStore.getCurrentUser();
- if (user.notify_props && ((user.notify_props.desktop === "mention" && mentions.indexOf(user.id) === -1 && channel.type !== 'D') || user.notify_props.desktop === "none")) {
+ if (user.notify_props && ((user.notify_props.desktop === 'mention' && mentions.indexOf(user.id) === -1 && channel.type !== 'D') || user.notify_props.desktop === 'none')) {
return;
}
var member = ChannelStore.getMember(msg.channel_id);
- if ((member.notify_level === "mention" && mentions.indexOf(user.id) === -1) || member.notify_level === "none" || member.notify_level === "quiet") {
+ if ((member.notify_level === 'mention' && mentions.indexOf(user.id) === -1) || member.notify_level === 'none' || member.notify_level === 'quiet') {
return;
}
- var username = "Someone";
+ var username = 'Someone';
if (UserStore.hasProfile(msg.user_id)) {
username = UserStore.getProfile(msg.user_id).username;
}
- var title = channel ? channel.display_name : "Posted";
+ var title = channel ? channel.display_name : 'Posted';
- var repRegex = new RegExp("<br>", "g");
+ var repRegex = new RegExp('<br>', 'g');
var post = JSON.parse(msg.props.post);
var msgProps = msg.props;
- var msg = post.message.replace(repRegex, "\n").replace(/\n+/g, " ").replace("<mention>", "").replace("</mention>", "");
-
- if (msg.length > 50) {
- msg = msg.substring(0,49) + "...";
+ var notifyText = post.message.replace(repRegex, '\n').replace(/\n+/g, ' ').replace('<mention>', '').replace('</mention>', '');
+
+ if (notifyText.length > 50) {
+ notifyText = notifyText.substring(0, 49) + '...';
}
- if (msg.length === 0) {
+ if (notifyText.length === 0) {
if (msgProps.image) {
- 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 an image', channel);
+ } else if (msgProps.otherFile) {
+ utils.notifyMe(title, username + ' uploaded a file', channel);
+ } else {
+ utils.notifyMe(title, username + ' did something new', channel);
}
- else {
- utils.notifyMe(title, username + " did something new", channel);
- }
- }
- else {
- utils.notifyMe(title, username + " wrote: " + msg, channel);
+ } else {
+ utils.notifyMe(title, username + ' wrote: ' + notifyText, channel);
}
- if (!user.notify_props || user.notify_props.desktop_sound === "true") {
+ if (!user.notify_props || user.notify_props.desktop_sound === 'true') {
utils.ding();
}
}
-
- } else if (msg.action == "viewed") {
+ } else if (msg.action === 'viewed') {
if (ChannelStore.getCurrentId() != msg.channel_id) {
AsyncClient.getChannels(true);
}
- } else if (msg.action == "user_added") {
+ } else if (msg.action === 'user_added') {
if (UserStore.getCurrentId() === msg.user_id) {
AsyncClient.getChannels(true);
}
- } else if(msg.action === "user_removed") {
- if(msg.user_id === UserStore.getCurrentId()) {
+ } else if (msg.action === 'user_removed') {
+ if (msg.user_id === UserStore.getCurrentId()) {
AsyncClient.getChannels(true);
- if(msg.props.channel_id === ChannelStore.getCurrentId() && $('#removed_from_channel').length > 0) {
+ if (msg.props.channel_id === ChannelStore.getCurrentId() && $('#removed_from_channel').length > 0) {
var sentState = {};
sentState.channelName = ChannelStore.getCurrent().display_name;
sentState.remover = UserStore.getProfile(msg.props.remover).username;
- BrowserStore.setItem('channel-removed-state',sentState);
+ BrowserStore.setItem('channel-removed-state', sentState);
$('#removed_from_channel').modal('show');
}
}
@@ -217,10 +228,10 @@ module.exports = React.createClass({
var channel = ChannelStore.getCurrent();
if (channel) {
if (channel.type === 'D') {
- var teammate_username = utils.getDirectTeammate(channel.id).username
- document.title = teammate_username + " " + document.title.substring(document.title.lastIndexOf("-"));
+ var teammate_username = utils.getDirectTeammate(channel.id).username;
+ document.title = teammate_username + ' ' + document.title.substring(document.title.lastIndexOf('-'));
} else {
- document.title = channel.display_name + " " + document.title.substring(document.title.lastIndexOf("-"))
+ document.title = channel.display_name + ' ' + document.title.substring(document.title.lastIndexOf('-'));
}
}
},
@@ -229,92 +240,96 @@ module.exports = React.createClass({
},
render: function() {
var members = this.state.members;
- var newsActive = window.location.pathname === "/" ? "active" : "";
+ var newsActive = window.location.pathname === '/' ? 'active' : '';
var badgesActive = false;
var self = this;
var channelItems = this.state.channels.map(function(channel) {
if (channel.type != 'O') {
- return "";
+ return '';
}
var channelMember = members[channel.id];
- var active = channel.id === self.state.active_id ? "active" : "";
+ var active = channel.id === self.state.active_id ? 'active' : '';
- var msg_count = channel.total_msg_count - channelMember.msg_count;
- var titleClass = ""
- if (msg_count > 0 && channelMember.notify_level !== "quiet") {
- titleClass = "unread-title"
+ var msgCount = channel.total_msg_count - channelMember.msg_count;
+ var titleClass = '';
+ if (msgCount > 0 && channelMember.notify_level !== 'quiet') {
+ titleClass = 'unread-title';
}
- var badge = "";
+ var badge = '';
if (channelMember.mention_count > 0) {
- badge = <span className="badge pull-right small">{channelMember.mention_count}</span>;
+ badge = <span className='badge pull-right small'>{channelMember.mention_count}</span>;
badgesActive = true;
- titleClass = "unread-title"
+ titleClass = 'unread-title';
}
return (
- <li key={channel.id} className={active}><a className={"sidebar-channel " + titleClass} href="#" onClick={function(e){e.preventDefault(); utils.switchChannel(channel);}}>{badge}{channel.display_name}</a></li>
+ <li key={channel.id} className={active}><a className={'sidebar-channel ' + titleClass} href='#' onClick={function(e){e.preventDefault(); utils.switchChannel(channel);}}>{badge}{channel.display_name}</a></li>
);
});
var privateChannelItems = this.state.channels.map(function(channel) {
- if (channel.type != 'P') {
- return "";
+ if (channel.type !== 'P') {
+ return '';
}
var channelMember = members[channel.id];
- var active = channel.id === self.state.active_id ? "active" : "";
+ var active = channel.id === self.state.active_id ? 'active' : '';
- var msg_count = channel.total_msg_count - channelMember.msg_count;
- var titleClass = ""
- if (msg_count > 0 && channelMember.notify_level !== "quiet") {
- titleClass = "unread-title"
+ var msgCount = channel.total_msg_count - channelMember.msg_count;
+ var titleClass = ''
+ if (msgCount > 0 && channelMember.notify_level !== 'quiet') {
+ titleClass = 'unread-title'
}
- var badge = "";
+ var badge = '';
if (channelMember.mention_count > 0) {
- badge = <span className="badge pull-right small">{channelMember.mention_count}</span>;
+ badge = <span className='badge pull-right small'>{channelMember.mention_count}</span>;
badgesActive = true;
- titleClass = "unread-title"
+ titleClass = 'unread-title';
}
return (
- <li key={channel.id} className={active}><a className={"sidebar-channel " + titleClass} href="#" onClick={function(e){e.preventDefault(); utils.switchChannel(channel);}}>{badge}{channel.display_name}</a></li>
+ <li key={channel.id} className={active}><a className={'sidebar-channel ' + titleClass} href='#' onClick={function(e){e.preventDefault(); utils.switchChannel(channel);}}>{badge}{channel.display_name}</a></li>
);
});
var directMessageItems = this.state.showDirectChannels.map(function(channel) {
- var badge = "";
- var titleClass = "";
+ var badge = '';
+ var titleClass = '';
- var statusIcon = "";
- if (channel.status === "online") {
+ var statusIcon = '';
+ if (channel.status === 'online') {
statusIcon = Constants.ONLINE_ICON_SVG;
- } else if (channel.status === "away") {
+ } else if (channel.status === 'away') {
statusIcon = Constants.ONLINE_ICON_SVG;
} else {
statusIcon = Constants.OFFLINE_ICON_SVG;
}
if (!channel.fake) {
- var active = channel.id === self.state.active_id ? "active" : "";
+ var active = channel.id === self.state.active_id ? 'active' : '';
if (channel.unread) {
- badge = <span className="badge pull-right small">{channel.unread}</span>;
+ badge = <span className='badge pull-right small'>{channel.unread}</span>;
badgesActive = true;
- titleClass = "unread-title"
+ titleClass = 'unread-title';
+ }
+
+ function handleClick(e) {
+ e.preventDefault();
+ utils.switchChannel(channel, channel.teammate_username);
}
return (
- <li key={channel.name} className={active}><a className={"sidebar-channel " + titleClass} href="#" onClick={function(e){e.preventDefault(); utils.switchChannel(channel, channel.teammate_username);}}><span className="status" dangerouslySetInnerHTML={{__html: statusIcon}} /> {badge}{channel.display_name}</a></li>
+ <li key={channel.name} className={active}><a className={'sidebar-channel ' + titleClass} href='#' onClick={handleClick}><span className='status' dangerouslySetInnerHTML={{__html: statusIcon}} /> {badge}{channel.display_name}</a></li>
);
} else {
return (
- <li key={channel.name} className={active}><a className={"sidebar-channel " + titleClass} href={TeamStore.getCurrentTeamUrl() + "/channels/"+channel.name}><span className="status" dangerouslySetInnerHTML={{__html: statusIcon}} /> {badge}{channel.display_name}</a></li>
+ <li key={channel.name} className={active}><a className={'sidebar-channel ' + titleClass} href={TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name}><span className='status' dangerouslySetInnerHTML={{__html: statusIcon}} /> {badge}{channel.display_name}</a></li>
);
}
-
});
var link = document.createElement('link');
@@ -345,23 +360,23 @@ module.exports = React.createClass({
<SidebarHeader teamDisplayName={this.props.teamDisplayName} teamType={this.props.teamType} />
<SearchBox />
- <div className="nav-pills__container">
- <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>
+ <div className='nav-pills__container'>
+ <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>
{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>
+ <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>
{privateChannelItems}
</ul>
- <ul className="nav nav-pills nav-stacked">
+ <ul className='nav nav-pills nav-stacked'>
<li><h4>Private Messages</h4></li>
{directMessageItems}
{ this.state.hideDirectChannels.length > 0 ?
- <li><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>
- : "" }
+ <li><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>
+ : '' }
</ul>
</div>
</div>
diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx
index 447a405bd..3f35a5912 100644
--- a/web/react/components/signup_team_complete.jsx
+++ b/web/react/components/signup_team_complete.jsx
@@ -248,6 +248,8 @@ TeamURLPage = React.createClass({
},
render: function() {
+ $('body').tooltip( {selector: '[data-toggle=tooltip]', trigger: 'hover click'} );
+
client.track('signup', 'signup_team_03_url');
var name_error = this.state.name_error ? <label className="control-label">{ this.state.name_error }</label> : null;
@@ -260,8 +262,8 @@ TeamURLPage = React.createClass({
<div className={ name_error ? "form-group has-error" : "form-group" }>
<div className="row">
<div className="col-sm-11">
- <div className="input-group">
- <span className="input-group-addon">{ utils.getWindowLocationOrigin() + "/" }</span>
+ <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}/>
</div>
</div>
diff --git a/web/react/components/team_settings_modal.jsx b/web/react/components/team_settings_modal.jsx
index e50378b7f..b1c38fd16 100644
--- a/web/react/components/team_settings_modal.jsx
+++ b/web/react/components/team_settings_modal.jsx
@@ -29,7 +29,7 @@ module.exports = React.createClass({
tabs.push({name: "feature", ui_name: "Features", icon: "glyphicon glyphicon-wrench"});
return (
- <div className="modal fade" ref="modal" id="team_settings" role="dialog" aria-hidden="true">
+ <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">
diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx
index 65f025919..5c4d26a23 100644
--- a/web/react/components/user_profile.jsx
+++ b/web/react/components/user_profile.jsx
@@ -28,6 +28,7 @@ module.exports = React.createClass({
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);
@@ -57,7 +58,7 @@ module.exports = React.createClass({
if (!config.ShowEmail) {
data_content += "<div class='text-nowrap'>Email not shared</div>";
} else {
- data_content += "<div><a href='mailto:" + this.state.profile.email + "' class='text-nowrap text-lowercase'>" + this.state.profile.email + "</a></div>";
+ 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>";
}
return (
diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx
index c574d2365..e224f2a87 100644
--- a/web/react/components/user_settings.jsx
+++ b/web/react/components/user_settings.jsx
@@ -5,8 +5,6 @@ var UserStore = require('../stores/user_store.jsx');
var SettingItemMin = require('./setting_item_min.jsx');
var SettingItemMax = require('./setting_item_max.jsx');
var SettingPicture = require('./setting_picture.jsx');
-var AccessHistoryModal = require('./access_history_modal.jsx');
-var ActivityLogModal = require('./activity_log_modal.jsx');
var client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var utils = require('../utils/utils.jsx');
@@ -642,17 +640,17 @@ var GeneralTab = React.createClass({
var user = this.props.user;
var username = this.state.username.trim();
- var username_error = utils.isValidUsername(username);
- if (username_error === 'Cannot use a reserved word as a username.') {
- this.setState({client_error: 'This username is reserved, please choose a new one.' });
+ var usernameError = utils.isValidUsername(username);
+ if (usernameError === 'Cannot use a reserved word as a username.') {
+ this.setState({clientError: 'This username is reserved, please choose a new one.'});
return;
- } else if (username_error) {
- this.setState({client_error: "Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'." });
+ } else if (usernameError) {
+ this.setState({clientError: "Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'."});
return;
}
if (user.username === username) {
- this.setState({client_error: 'You must submit a new username'});
+ this.setState({clientError: 'You must submit a new username'});
return;
}
@@ -667,7 +665,7 @@ var GeneralTab = React.createClass({
var nickname = this.state.nickname.trim();
if (user.nickname === nickname) {
- this.setState({client_error: 'You must submit a new nickname'})
+ this.setState({clientError: 'You must submit a new nickname'});
return;
}
@@ -679,11 +677,11 @@ var GeneralTab = React.createClass({
e.preventDefault();
var user = UserStore.getCurrentUser();
- var firstName = this.state.first_name.trim();
- var lastName = this.state.last_name.trim();
+ var firstName = this.state.firstName.trim();
+ var lastName = this.state.lastName.trim();
if (user.first_name === firstName && user.last_name === lastName) {
- this.setState({client_error: 'You must submit a new first or last name'})
+ this.setState({clientError: 'You must submit a new first or last name'});
return;
}
@@ -703,7 +701,7 @@ var GeneralTab = React.createClass({
}
if (email === '' || !utils.isEmail(email)) {
- this.setState({ email_error: 'Please enter a valid email address' });
+ this.setState({emailError: 'Please enter a valid email address'});
return;
}
@@ -718,11 +716,11 @@ var GeneralTab = React.createClass({
AsyncClient.getMe();
}.bind(this),
function(err) {
- state = this.getInitialState();
+ var state = this.getInitialState();
if (err.message) {
- state.server_error = err.message;
+ state.serverError = err.message;
} else {
- state.server_error = err;
+ state.serverError = err;
}
this.setState(state);
}.bind(this)
@@ -742,12 +740,13 @@ var GeneralTab = React.createClass({
var picture = this.state.picture;
if (picture.type !== 'image/jpeg' && picture.type !== 'image/png') {
- this.setState({client_error: 'Only JPG or PNG images may be used for profile pictures'});
+ this.setState({clientError: 'Only JPG or PNG images may be used for profile pictures'});
return;
}
var formData = new FormData();
formData.append('image', picture, picture.name);
+ this.setState({loadingPicture: true});
client.uploadProfileImage(formData,
function() {
@@ -756,8 +755,8 @@ var GeneralTab = React.createClass({
window.location.reload();
}.bind(this),
function(err) {
- state = this.getInitialState();
- state.server_error = err;
+ var state = this.getInitialState();
+ state.serverError = err;
this.setState(state);
}.bind(this)
);
@@ -766,10 +765,10 @@ var GeneralTab = React.createClass({
this.setState({username: e.target.value});
},
updateFirstName: function(e) {
- this.setState({first_name: e.target.value});
+ this.setState({firstName: e.target.value});
},
updateLastName: function(e) {
- this.setState({last_name: e.target.value});
+ this.setState({lastName: e.target.value});
},
updateNickname: function(e) {
this.setState({nickname: e.target.value});
@@ -779,17 +778,16 @@ var GeneralTab = React.createClass({
},
updatePicture: function(e) {
if (e.target.files && e.target.files[0]) {
- this.setState({ picture: e.target.files[0] });
+ this.setState({picture: e.target.files[0]});
this.submitActive = true;
- this.setState({client_error: null});
-
+ this.setState({clientError: null});
} else {
this.setState({picture: null});
}
},
updateSection: function(section) {
- this.setState({client_error:''});
+ this.setState({clientError: ''});
this.submitActive = false;
this.props.updateSection(section);
},
@@ -798,7 +796,7 @@ var GeneralTab = React.createClass({
this.value = '';
});
- this.setState(assign({}, this.getInitialState(), {client_error: null, server_error: null, email_error: null}));
+ this.setState(assign({}, this.getInitialState(), {clientError: null, serverError: null, emailError: null}));
this.props.updateSection('');
},
componentDidMount: function() {
@@ -810,15 +808,24 @@ var GeneralTab = React.createClass({
getInitialState: function() {
var user = this.props.user;
- return { username: user.username, first_name: user.first_name, last_name: user.last_name, nickname: user.nickname,
- email: user.email, picture: null };
+ return {username: user.username, firstName: user.first_name, lastName: user.last_name, nickname: user.nickname,
+ email: user.email, picture: null, loadingPicture: false};
},
render: function() {
var user = this.props.user;
- var client_error = this.state.client_error ? this.state.client_error : null;
- var server_error = this.state.server_error ? this.state.server_error : null;
- var email_error = this.state.email_error ? this.state.email_error : null;
+ var clientError = null;
+ if (this.state.clientError) {
+ clientError = this.state.clientError;
+ }
+ var serverError = null;
+ if (this.state.serverError) {
+ serverError = this.state.serverError;
+ }
+ var emailError = null;
+ if (this.state.emailError) {
+ emailError = this.state.emailError;
+ }
var nameSection;
var self = this;
@@ -829,7 +836,7 @@ var GeneralTab = React.createClass({
<div className='form-group'>
<label className='col-sm-5 control-label'>First Name</label>
<div className='col-sm-7'>
- <input className='form-control' type='text' onChange={this.updateFirstName} value={this.state.first_name}/>
+ <input className='form-control' type='text' onChange={this.updateFirstName} value={this.state.firstName}/>
</div>
</div>
);
@@ -838,7 +845,7 @@ var GeneralTab = React.createClass({
<div className='form-group'>
<label className='col-sm-5 control-label'>Last Name</label>
<div className='col-sm-7'>
- <input className='form-control' type='text' onChange={this.updateLastName} value={this.state.last_name}/>
+ <input className='form-control' type='text' onChange={this.updateLastName} value={this.state.lastName}/>
</div>
</div>
);
@@ -848,8 +855,8 @@ var GeneralTab = React.createClass({
title='Full Name'
inputs={inputs}
submit={this.submitName}
- server_error={server_error}
- client_error={client_error}
+ server_error={serverError}
+ client_error={clientError}
updateSection={function(e) {
self.updateSection('');
e.preventDefault();
@@ -857,20 +864,20 @@ var GeneralTab = React.createClass({
/>
);
} else {
- var full_name = '';
+ var fullName = '';
if (user.first_name && user.last_name) {
- full_name = user.first_name + ' ' + user.last_name;
+ fullName = user.first_name + ' ' + user.last_name;
} else if (user.first_name) {
- full_name = user.first_name;
+ fullName = user.first_name;
} else if (user.last_name) {
- full_name = user.last_name;
+ fullName = user.last_name;
}
nameSection = (
<SettingItemMin
title='Full Name'
- describe={full_name}
+ describe={fullName}
updateSection={function() {
self.updateSection('name');
}}
@@ -880,7 +887,6 @@ var GeneralTab = React.createClass({
var nicknameSection;
if (this.props.activeSection === 'nickname') {
-
inputs.push(
<div className='form-group'>
<label className='col-sm-5 control-label'>{utils.isMobile() ? '' : 'Nickname'}</label>
@@ -895,8 +901,8 @@ var GeneralTab = React.createClass({
title='Nickname'
inputs={inputs}
submit={this.submitNickname}
- server_error={server_error}
- client_error={client_error}
+ server_error={serverError}
+ client_error={clientError}
updateSection={function(e) {
self.updateSection('');
e.preventDefault();
@@ -919,7 +925,7 @@ var GeneralTab = React.createClass({
if (this.props.activeSection === 'username') {
inputs.push(
<div className='form-group'>
- <label className='col-sm-5 control-label'>{utils.isMobile() ? '': 'Username'}</label>
+ <label className='col-sm-5 control-label'>{utils.isMobile() ? '' : 'Username'}</label>
<div className='col-sm-7'>
<input className='form-control' type='text' onChange={this.updateUsername} value={this.state.username}/>
</div>
@@ -931,8 +937,8 @@ var GeneralTab = React.createClass({
title='Username'
inputs={inputs}
submit={this.submitUsername}
- server_error={server_error}
- client_error={client_error}
+ server_error={serverError}
+ client_error={clientError}
updateSection={function(e) {
self.updateSection('');
e.preventDefault();
@@ -966,8 +972,8 @@ var GeneralTab = React.createClass({
title='Email'
inputs={inputs}
submit={this.submitEmail}
- server_error={server_error}
- client_error={email_error}
+ server_error={serverError}
+ client_error={emailError}
updateSection={function(e) {
self.updateSection('');
e.preventDefault();
@@ -993,8 +999,8 @@ var GeneralTab = React.createClass({
title='Profile Picture'
submit={this.submitPicture}
src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update}
- server_error={server_error}
- client_error={client_error}
+ server_error={serverError}
+ client_error={clientError}
updateSection={function(e) {
self.updateSection('');
e.preventDefault();
@@ -1002,6 +1008,7 @@ var GeneralTab = React.createClass({
picture={this.state.picture}
pictureChange={this.updatePicture}
submitActive={this.submitActive}
+ loadingPicture={this.state.loadingPicture}
/>
);
} else {
diff --git a/web/react/components/user_settings_modal.jsx b/web/react/components/user_settings_modal.jsx
index d1aff74f2..702e7ad7a 100644
--- a/web/react/components/user_settings_modal.jsx
+++ b/web/react/components/user_settings_modal.jsx
@@ -32,7 +32,7 @@ module.exports = React.createClass({
tabs.push({name: "appearance", ui_name: "Appearance", icon: "glyphicon glyphicon-wrench"});
return (
- <div className="modal fade" ref="modal" id="user_settings1" role="dialog" aria-hidden="true">
+ <div className="modal fade" ref="modal" id="user_settings1" role="dialog" tabIndex="-1" aria-hidden="true">
<div className="modal-dialog settings-modal">
<div className="modal-content">
<div className="modal-header">
diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss
index 52659521d..78006ff18 100644
--- a/web/sass-files/sass/partials/_base.scss
+++ b/web/sass-files/sass/partials/_base.scss
@@ -49,6 +49,12 @@ div.theme {
background-color: $primary-color;
}
+.tooltip {
+ .tooltip-inner {
+ word-break: break-word;
+ }
+}
+
.nopadding {
padding: 0;
margin: 0;
@@ -61,6 +67,10 @@ div.theme {
}
}
+.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control {
+ cursor: auto;
+}
+
.form-group {
&.form-group--small {
margin-bottom: 10px;
diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss
index ddc5e98bb..65775f01e 100644
--- a/web/sass-files/sass/partials/_files.scss
+++ b/web/sass-files/sass/partials/_files.scss
@@ -4,7 +4,8 @@
max-height: 110px;
height: 110px;
white-space: nowrap;
- overflow: auto;
+ overflow-x: auto;
+ overflow-y: hidden;
.preview-div {
display: inline-block;
width: 120px;
@@ -28,9 +29,9 @@
}
}
.preview-img {
- display: block;
- height: auto;
- max-width: 100%;
+ display: block;
+ height: auto;
+ max-width: 100%;
}
.remove-preview {
position: absolute;
@@ -114,8 +115,7 @@
height: 100px;
float: left;
margin: 5px 10px 5px 0;
- @include display-flex;
- display: -ms-flexbox;
+ display: table;
border: 1px solid lightgrey;
.post__load {
height: 100%;
@@ -130,17 +130,23 @@
background-color: #FFF;
background-repeat: no-repeat;
&.small {
- background-position: center;
+ background-position: center;
}
&.normal {
- background-position: top left;
+ background-position: top left;
}
}
.post-image__thumbnail {
+ display: table-cell;
+ vertical-align: top;
width: 50%;
height: 100%;
+ cursor: zoom-in;
+ cursor: -webkit-zoom-in;
}
.post-image__details {
+ display: table-cell;
+ vertical-align: top;
width: 50%;
height: 100%;
background: white;
@@ -165,34 +171,34 @@
}
.file-details__container {
- @include display-flex;
- display: -ms-flexbox;
+ @include display-flex;
+ display: -ms-flexbox;
- .file-details {
- width: 320px;
- height: 270px;
- padding: 14px;
- text-align: left;
- vertical-align: top;
+ .file-details {
+ width: 320px;
+ height: 270px;
+ padding: 14px;
+ text-align: left;
+ vertical-align: top;
- .file-details__name {
- font-size: 16px;
- }
- .file-details__info {
- color: grey;
- }
+ .file-details__name {
+ font-size: 16px;
+ }
+ .file-details__info {
+ color: grey;
}
- .file-details__preview {
- width: 320px;
- height: 270px;
- border-right: 1px solid #ddd;
- vertical-align: center;
+ }
+ .file-details__preview {
+ width: 320px;
+ height: 270px;
+ border-right: 1px solid #ddd;
+ vertical-align: center;
// helper to center the image icon in the preview window
.file-details__preview-helper {
- height: 100%;
- display: inline-block;
- vertical-align: middle;
+ height: 100%;
+ display: inline-block;
+ vertical-align: middle;
}
+ }
}
-}
diff --git a/web/sass-files/sass/partials/_get-link.scss b/web/sass-files/sass/partials/_get-link.scss
new file mode 100644
index 000000000..c84befd6a
--- /dev/null
+++ b/web/sass-files/sass/partials/_get-link.scss
@@ -0,0 +1,6 @@
+.copy-link-confirm {
+ position: fixed;
+ color: rgb(153, 230, 153);
+ top: 84%;
+ left: 130px;
+} \ No newline at end of file
diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss
index fb37c43eb..da648a170 100644
--- a/web/sass-files/sass/partials/_headers.scss
+++ b/web/sass-files/sass/partials/_headers.scss
@@ -110,6 +110,20 @@
}
}
}
+ &.theme--black {
+ &:hover {
+ &:before {
+ background: rgba(white, 0.2);
+ }
+ }
+ }
+ &.theme--gray {
+ &:hover {
+ &:before {
+ background: rgba(white, 0.1);
+ }
+ }
+ }
a {
color: #fff;
}
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index f359037c5..014f834ed 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -15,10 +15,13 @@
}
.remove__member {
float: right;
- color: #E56565;
+ color: #999;
font-size: 20px;
line-height: 0;
padding: 6px;
+ &:hover {
+ color: #E56565;
+ }
}
.modal-dialog {
max-width: 95%;
@@ -151,10 +154,9 @@
height: 100%;
margin: 0 auto;
.image-wrapper {
- background: #FFF;
position: relative;
max-width: 90%;
- min-height: 50px;
+ min-height: 100px;
min-width: 320px;
@include border-radius(3px);
display: table;
@@ -182,6 +184,7 @@
z-index: 9999;
}
> a {
+ background: #FFF;
display: table-cell;
vertical-align: middle;
}
diff --git a/web/sass-files/sass/partials/_navbar.scss b/web/sass-files/sass/partials/_navbar.scss
index 905907d84..2e78a8728 100644
--- a/web/sass-files/sass/partials/_navbar.scss
+++ b/web/sass-files/sass/partials/_navbar.scss
@@ -19,6 +19,7 @@
}
}
.navbar-toggle {
+ width: 43px;
float: left;
border-color: transparent;
border-radius: 0;
diff --git a/web/sass-files/sass/partials/_popover.scss b/web/sass-files/sass/partials/_popover.scss
index fa1b44841..5008331b4 100644
--- a/web/sass-files/sass/partials/_popover.scss
+++ b/web/sass-files/sass/partials/_popover.scss
@@ -6,4 +6,11 @@
.user-popover__image {
margin: 0 0 10px;
@include border-radius(128px);
+}
+
+.user-popover__email {
+ max-width: 200px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: block;
} \ No newline at end of file
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index e3f140413..47b2b6bd7 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -229,6 +229,16 @@
}
}
+@media screen and (max-height: 640px) {
+ .signup-team__container {
+ padding: 30px 0;
+ margin-bottom: 30px;
+ font-size: 0.9em;
+ .signup-team__name {
+ font-size: 2em;
+ }
+ }
+}
@media screen and (max-width: 768px) {
.date-separator, .new-separator {
&.hovered--after {
diff --git a/web/sass-files/sass/partials/_signup.scss b/web/sass-files/sass/partials/_signup.scss
index 4b6ee79a1..3a6f73316 100644
--- a/web/sass-files/sass/partials/_signup.scss
+++ b/web/sass-files/sass/partials/_signup.scss
@@ -54,6 +54,28 @@
margin-bottom: 1em;
}
+ .input-group {
+ &.input-group--limit {
+ table-layout: fixed;
+ width: 100%;
+ .tooltip-inner {
+ word-wrap: break-word;
+ }
+ .form-control {
+ text-align: left;
+ display: table-cell;
+ width: 100%;
+ }
+ .input-group-addon {
+ text-align: left;
+ width: 50%;
+ display: table-cell;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+
.inner__content {
padding: 0 15px;
margin: 30px 0 20px;
diff --git a/web/sass-files/sass/styles.scss b/web/sass-files/sass/styles.scss
index ffd1f42b8..eb5152a2c 100644
--- a/web/sass-files/sass/styles.scss
+++ b/web/sass-files/sass/styles.scss
@@ -33,6 +33,7 @@
@import "partials/error";
@import "partials/error-bar";
@import "partials/loading";
+@import "partials/get-link";
// Responsive Css
@import "partials/responsive";
diff --git a/web/templates/channel.html b/web/templates/channel.html
index 6325069ee..da6fed97d 100644
--- a/web/templates/channel.html
+++ b/web/templates/channel.html
@@ -49,7 +49,9 @@
<div id="activity_log_modal"></div>
<div id="removed_from_channel_modal"></div>
<script>
-window.setup_channel_page('{{ .Props.TeamDisplayName }}', '{{ .Props.TeamType }}', '{{ .Props.TeamId }}', '{{ .Props.ChannelName }}', '{{ .Props.ChannelId }}');
+ window.setup_channel_page('{{ .Props.TeamDisplayName }}', '{{ .Props.TeamType }}', '{{ .Props.TeamId }}', '{{ .Props.ChannelName }}', '{{ .Props.ChannelId }}');
+ $('body').tooltip( {selector: '[data-toggle=tooltip]'} );
+ $('.modal-body').perfectScrollbar();
</script>
</body>
</html>
diff --git a/web/templates/signup_team.html b/web/templates/signup_team.html
index b84b8e486..313ed9d5f 100644
--- a/web/templates/signup_team.html
+++ b/web/templates/signup_team.html
@@ -10,7 +10,7 @@
<div class="signup-team__container">
<img class="signup-team-logo" src="/static/images/logo.png" />
<h1>Mattermost</h1>
- <h4 class="color--light">All team communication in one place, searchable and accesible anywhere</h4>
+ <h4 class="color--light">All team communication in one place, searchable and accessible anywhere</h4>
<div id="signup-team"></div>
</div>
</div>