diff options
-rw-r--r-- | README.md | 51 | ||||
-rw-r--r-- | web/react/components/channel_invite_modal.jsx | 178 | ||||
-rw-r--r-- | web/react/components/create_post.jsx | 4 | ||||
-rw-r--r-- | web/react/components/file_upload.jsx | 2 | ||||
-rw-r--r-- | web/react/components/team_signup_send_invites_page.jsx | 116 | ||||
-rw-r--r-- | web/react/utils/utils.jsx | 2 |
6 files changed, 235 insertions, 118 deletions
@@ -79,18 +79,26 @@ Local Machine Setup (Docker) 3. When docker is done fetching the image, open http://localhost:8065/ in your browser. ### Additional Notes ### -- If you want to work with the latest 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 master from the repository (i.e. not a stable release) you can run the cmd: -- You can update to the latest bits by running: -`docker pull mattermost/platform:dev` + ``` bash + docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:dev + ``` + +- Instructions on how to update your docker image are found below. - If you wish to remove mattermost-dev use: - `docker stop mattermost-dev` - `docker rm -v mattermost-dev` + + ``` bash + docker stop mattermost-dev + docker rm -v mattermost-dev + ``` - If you wish to gain access to a shell on the container use: - `docker exec -ti mattermost-dev /bin/bash` + + ``` bash + docker exec -ti mattermost-dev /bin/bash + ``` AWS Elastic Beanstalk Setup (Docker) ------------------------------------ @@ -124,6 +132,35 @@ There are a few configuration settings you might want to adjust when setting up * *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. +Upgrading Mattermost +--------------------- + +### Docker ### +To upgrade your docker image to the latest release (NOTE: this will destroy all data in the docker container): + +1. Stop your docker container by running: + + ``` bash + docker stop mattermost-dev + ``` +2. Delete your docker container by running: + + ``` bash + docker rm mattermost-dev + ``` +3. Update your docker image by running: + + ``` bash + docker pull mattermost/platform + ``` +4. Start your docker container by running: + + ``` bash + docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform + ``` + +To upgrade to the latest master from the repository replace `mattermost/platform` with `mattermost/platform:dev` in the above instructions. + Contributing ------------ diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx index 1b8fe4199..d90522e8c 100644 --- a/web/react/components/channel_invite_modal.jsx +++ b/web/react/components/channel_invite_modal.jsx @@ -4,131 +4,153 @@ var UserStore = require('../stores/user_store.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); var MemberList = require('./member_list.jsx'); +var LoadingScreen = require('./loading_screen.jsx'); var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); -function getStateFromStores() { - var users = UserStore.getActiveOnlyProfiles(); - var memberIds = ChannelStore.getCurrentExtraInfo().members.map(function(user) { return user.id; }); - var nonmembers = []; - for (var id in users) { - if (memberIds.indexOf(id) == -1) { - nonmembers.push(users[id]); - } - } +export default class ChannelInviteModal extends React.Component { + constructor() { + super(); - nonmembers.sort(function(a,b) { - return a.username.localeCompare(b.username); - }); + this.componentDidMount = this.componentDidMount.bind(this); + this.componentWillUnmount = this.componentWillUnmount.bind(this); + this.onShow = this.onShow.bind(this); + this.onHide = this.onHide.bind(this); + this.onListenerChange = this.onListenerChange.bind(this); + this.handleInvite = this.handleInvite.bind(this); - var channel_name = ChannelStore.getCurrent() ? ChannelStore.getCurrent().display_name : ""; + this.isShown = false; + this.state = this.getStateFromStores(); + } + getStateFromStores() { + function getId(user) { + return user.id; + } + var users = UserStore.getActiveOnlyProfiles(); + var memberIds = ChannelStore.getCurrentExtraInfo().members.map(getId); - return { - nonmembers: nonmembers, - memberIds: memberIds, - channel_name: channel_name - }; -} + var loading = $.isEmptyObject(users); -module.exports = React.createClass({ - displayName: "ChannelInviteModal", + var nonmembers = []; + for (var id in users) { + if (memberIds.indexOf(id) === -1) { + nonmembers.push(users[id]); + } + } - isShown: false, - getInitialState: function() { - return {}; - }, + nonmembers.sort(function sortByUsername(a, b) { + return a.username.localeCompare(b.username); + }); - componentDidMount: function() { - $(React.findDOMNode(this)) - .on('hidden.bs.modal', this._onHide) - .on('show.bs.modal', this._onShow); - }, + var channelName = ''; + if (ChannelStore.getCurrent()) { + channelName = ChannelStore.getCurrent().display_name; + } - _onShow: function() { - ChannelStore.addExtraInfoChangeListener(this._onChange); - ChannelStore.addChangeListener(this._onChange); - this.isShown = true; - this._onChange(); - }, + return { + nonmembers: nonmembers, + memberIds: memberIds, + channelName: channelName, + loading: loading + }; + } + componentDidMount() { + $(React.findDOMNode(this)).on('hidden.bs.modal', this.onHide); + $(React.findDOMNode(this)).on('show.bs.modal', this.onShow); - _onHide: function() { - ChannelStore.removeExtraInfoChangeListener(this._onChange); - ChannelStore.removeChangeListener(this._onChange); + ChannelStore.addExtraInfoChangeListener(this.onListenerChange); + ChannelStore.addChangeListener(this.onListenerChange); + UserStore.addChangeListener(this.onListenerChange); + } + componentWillUnmount() { + ChannelStore.removeExtraInfoChangeListener(this.onListenerChange); + ChannelStore.removeChangeListener(this.onListenerChange); + UserStore.removeChangeListener(this.onListenerChange); + } + onShow() { + this.isShown = true; + this.onListenerChange(); + } + onHide() { this.isShown = false; - }, - - _onChange: function() { - this.setState(getStateFromStores()); - }, - - handleInvite: function(user_id) { + } + onListenerChange() { + var newState = this.getStateFromStores(); + if (!utils.areStatesEqual(this.state, newState) && this.isShown) { + this.setState(newState); + } + } + handleInvite(userId) { // Make sure the user isn't already a member of the channel - if (this.state.memberIds.indexOf(user_id) > -1) { + if (this.state.memberIds.indexOf(userId) > -1) { return; } var data = {}; - data.user_id = user_id; + data.user_id = userId; client.addChannelMember(ChannelStore.getCurrentId(), data, - function() { + function sucess() { var nonmembers = this.state.nonmembers; var memberIds = this.state.memberIds; for (var i = 0; i < nonmembers.length; i++) { - if (user_id === nonmembers[i].id) { + if (userId === nonmembers[i].id) { nonmembers[i].invited = true; - memberIds.push(user_id); + memberIds.push(userId); break; } } - this.setState({ invite_error: null, memberIds: memberIds, nonmembers: nonmembers }); + this.setState({inviteError: null, memberIds: memberIds, nonmembers: nonmembers}); AsyncClient.getChannelExtraInfo(true); }.bind(this), - function(err) { - this.setState({ invite_error: err.message }); + function error(err) { + this.setState({inviteError: err.message}); }.bind(this) ); - }, - - shouldComponentUpdate: function(nextProps, nextState) { - return this.isShown && !utils.areStatesEqual(this.state, nextState); - }, - - render: function() { - var invite_error = this.state.invite_error ? <label className='has-error control-label'>{this.state.invite_error}</label> : null; + } + render() { + var inviteError = null; + if (this.state.inviteError) { + inviteError = (<label className='has-error control-label'>{this.state.inviteError}</label>); + } var currentMember = ChannelStore.getCurrentMember(); var isAdmin = false; if (currentMember) { - isAdmin = currentMember.roles.indexOf("admin") > -1 || UserStore.getCurrentUser().roles.indexOf("admin") > -1; + isAdmin = currentMember.roles.indexOf('admin') > -1 || UserStore.getCurrentUser().roles.indexOf('admin') > -1; + } + + var content; + if (this.state.loading) { + content = (<LoadingScreen />); + } else { + content = (<MemberList memberList={this.state.nonmembers} isAdmin={isAdmin} handleInvite={this.handleInvite} />); } return ( - <div className="modal fade" id="channel_invite" tabIndex="-1" role="dialog" aria-hidden="true"> - <div className="modal-dialog" role="document"> - <div className="modal-content"> - <div className="modal-header"> - <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 className="modal-title">Add New Members to {this.state.channel_name}</h4> + <div className='modal fade' id='channel_invite' tabIndex='-1' role='dialog' aria-hidden='true'> + <div className='modal-dialog' role='document'> + <div className='modal-content'> + <div className='modal-header'> + <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button> + <h4 className='modal-title'>Add New Members to {this.state.channelName}</h4> </div> - <div className="modal-body"> - { invite_error } - <MemberList - memberList={this.state.nonmembers} - isAdmin={isAdmin} - handleInvite={this.handleInvite} /> + <div className='modal-body'> + {inviteError} + {content} </div> - <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> + <div className='modal-footer'> + <button type='button' className='btn btn-default' data-dismiss='modal'>Close</button> </div> </div> </div> </div> ); } -}); +} +ChannelInviteModal.displayName = 'ChannelInviteModal'; diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 3aa8cc39b..efaa40577 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -223,7 +223,7 @@ module.exports = React.createClass({ var previews = []; var messageText = ''; - var uploadsInProgress = 0; + var uploadsInProgress = []; if (draft && draft.previews && draft.message) { previews = draft.previews; messageText = draft.message; @@ -239,7 +239,7 @@ module.exports = React.createClass({ var draft = PostStore.getCurrentDraft(); var previews = []; var messageText = ''; - var uploadsInProgress = 0; + var uploadsInProgress = []; if (draft && draft.previews && draft.message) { previews = draft.previews; messageText = draft.message; diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index 7497ec330..e77982559 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -219,7 +219,7 @@ module.exports = React.createClass({ continue; } - var channelId = this.props.channelId || ChannelStore.getCurrentId(); + var channelId = self.props.channelId || ChannelStore.getCurrentId(); // generate a unique id that can be used by other components to refer back to this file upload var clientId = utils.generateId(); diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/team_signup_send_invites_page.jsx index 4bc03798b..a1e12661e 100644 --- a/web/react/components/team_signup_send_invites_page.jsx +++ b/web/react/components/team_signup_send_invites_page.jsx @@ -6,13 +6,18 @@ var utils = require('../utils/utils.jsx'); var ConfigStore = require('../stores/config_store.jsx'); var client = require('../utils/client.jsx'); -module.exports = React.createClass({ - displayName: 'TeamSignupSendInivtesPage', - propTypes: { - state: React.PropTypes.object, - updateParent: React.PropTypes.func - }, - submitBack: function(e) { +export default class TeamSignupSendInvitesPage extends React.Component { + constructor(props) { + super(props); + this.submitBack = this.submitBack.bind(this); + this.submitNext = this.submitNext.bind(this); + this.submitAddInvite = this.submitAddInvite.bind(this); + this.submitSkip = this.submitSkip.bind(this); + this.state = { + emailEnabled: !ConfigStore.getSettingAsBoolean('ByPassEmail', false) + }; + } + submitBack(e) { e.preventDefault(); if (config.AllowSignupDomainsWizard) { @@ -22,8 +27,8 @@ module.exports = React.createClass({ } this.props.updateParent(this.props.state); - }, - submitNext: function(e) { + } + submitNext(e) { e.preventDefault(); var valid = true; @@ -48,8 +53,8 @@ module.exports = React.createClass({ this.props.state.wizard = 'username'; this.props.updateParent(this.props.state); } - }, - submitAddInvite: function(e) { + } + submitAddInvite(e) { e.preventDefault(); this.props.state.wizard = 'send_invites'; if (!this.props.state.invites) { @@ -57,18 +62,19 @@ module.exports = React.createClass({ } this.props.state.invites.push(''); this.props.updateParent(this.props.state); - }, - submitSkip: function(e) { + } + submitSkip(e) { e.preventDefault(); this.props.state.wizard = 'username'; this.props.updateParent(this.props.state); - }, - getInitialState: function() { - return { - emailEnabled: !ConfigStore.getSettingAsBoolean('ByPassEmail', false) - }; - }, - render: function() { + } + componentWillMount() { + if (!this.state.emailEnabled) { + this.props.state.wizard = 'username'; + this.props.updateParent(this.props.state); + } + } + render() { client.track('signup', 'signup_team_05_send_invites'); var content = null; @@ -79,43 +85,95 @@ module.exports = React.createClass({ for (var i = 0; i < this.props.state.invites.length; i++) { if (i === 0) { - emails.push(<EmailItem focus={true} key={i} ref={'email_' + i} email={this.props.state.invites[i]} />); + emails.push( + <EmailItem + focus={true} + key={i} + ref={'email_' + i} + email={this.props.state.invites[i]} + /> + ); } else { - emails.push(<EmailItem focus={false} key={i} ref={'email_' + i} email={this.props.state.invites[i]} />); + emails.push( + <EmailItem + focus={false} + key={i} + ref={'email_' + i} + email={this.props.state.invites[i]} + /> + ); } } content = ( <div> {emails} - <div className='form-group text-right'><a href='#' onClick={this.submitAddInvite}>Add Invitation</a></div> + <div className='form-group text-right'> + <a + href='#' + onClick={this.submitAddInvite} + > + Add Invitation + </a> + </div> </div> ); bottomContent = ( - <p className='color--light'>{'if you prefer, you can invite ' + strings.Team + ' members later'}<br /> and <a href='#' onClick={this.submitSkip}>skip this step</a> for now.</p> + <p className='color--light'> + {'if you prefer, you can invite ' + strings.Team + ' members later'} + <br /> + {' and '} + <a + href='#' + onClick={this.submitSkip} + > + {'skip this step '} + </a> + {'for now.'} + </p> ); } else { content = ( - <div className='form-group color--light'>{'Email is currently disabled for your ' + strings.Team + ', and emails cannot be sent. Contact your system administrator to enable email and email invitations.'}</div> + <div className='form-group color--light'> + {'Email is currently disabled for your ' + strings.Team + ', and emails cannot be sent. Contact your system administrator to enable email and email invitations.'} + </div> ); } return ( <div> <form> - <img className='signup-team-logo' src='/static/images/logo.png' /> + <img + className='signup-team-logo' + src='/static/images/logo.png' + /> <h2>{'Invite ' + utils.toTitleCase(strings.Team) + ' Members'}</h2> {content} <div className='form-group'> - <button type='submit' className='btn-primary btn' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button> + <button + type='submit' + className='btn-primary btn' + onClick={this.submitNext} + > + Next<i className='glyphicon glyphicon-chevron-right' /> + </button> </div> </form> {bottomContent} <div className='margin--extra'> - <a href='#' onClick={this.submitBack}>Back to previous step</a> + <a + href='#' + onClick={this.submitBack} + > + Back to previous step + </a> </div> </div> ); } -}); +} +TeamSignupSendInvitesPage.propTypes = { + state: React.PropTypes.object.isRequired, + updateParent: React.PropTypes.func.isRequired +}; diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index df7a1e697..09cd299df 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -167,7 +167,7 @@ module.exports.displayTime = function(ticks) { var ampm = 'AM'; if (hours >= 12) { - ampm = 'AM'; + ampm = 'PM'; } hours = hours % 12; |