diff options
Diffstat (limited to 'web/react')
60 files changed, 931 insertions, 681 deletions
diff --git a/web/react/.eslintrc b/web/react/.eslintrc index c0d0bb200..c4167829d 100644 --- a/web/react/.eslintrc +++ b/web/react/.eslintrc @@ -42,12 +42,13 @@ "valid-typeof": 2, "block-scoped-var": 2, - "complexity": [1, 8], + "complexity": [0, 8], "consistent-return": 2, "curly": [2, "all"], "dot-location": [2, "object"], "dot-notation": 2, "eqeqeq": [2, "smart"], + "global-require": 2, "guard-for-in": 2, "no-alert": 2, "no-array-constructor": 2, @@ -109,30 +110,34 @@ "func-names": 2, "func-style": [2, "declaration"], "indent": [2, 4, {"SwitchCase": 0}], + "jsx-quotes": [2, "prefer-single"], "key-spacing": [2, {"beforeColon": false, "afterColon": true}], - "lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }], "linebreak-style": 2, + "lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }], "new-cap": 2, "new-parens": 2, "no-lonely-if": 2, "no-mixed-spaces-and-tabs": 2, "no-multiple-empty-lines": [2, {"max": 1}], + "no-negated-condition": 2, + "no-nested-ternary": 2, "no-spaced-func": 2, - "no-ternary": 2, + "no-ternary": 0, "no-trailing-spaces": [2, { "skipBlankLines": false }], "no-underscore-dangle": 2, - "no-unneeded-ternary": 2, + "no-unneeded-ternary": [2, {"defaultAssignment": false}], "object-curly-spacing": [2, "never"], "one-var": [2, "never"], "operator-linebreak": [2, "after"], "padded-blocks": [2, "never"], "quote-props": [2, "as-needed"], "quotes": [2, "single", "avoid-escape"], - "semi-spacing": [2, {"before": false, "after": true}], "semi": [2, "always"], + "semi-spacing": [2, {"before": false, "after": true}], "space-after-keywords": [2, "always"], "space-before-blocks": [2, "always"], "space-before-function-paren": [2, "never"], + "space-before-keywords": [2, "always"], "space-in-parens": [2, "never"], "space-infix-ops": 2, "space-return-throw-case": 2, @@ -167,7 +172,6 @@ "react/jsx-no-duplicate-props": [2, { "ignoreCase": false }], "react/jsx-no-literals": 1, "react/jsx-no-undef": 2, - "react/jsx-quotes": [2, "single", "avoid-escape"], "react/jsx-uses-react": 2, "react/jsx-uses-vars": 2, "react/no-danger": 0, @@ -179,6 +183,7 @@ "react/prop-types": 2, "react/self-closing-comp": 2, "react/sort-comp": 0, - "react/wrap-multilines": 2 + "react/wrap-multilines": 2, + "react/no-direct-mutation-state": 2 } } diff --git a/web/react/components/about_build_modal.jsx b/web/react/components/about_build_modal.jsx new file mode 100644 index 000000000..d582f6bc8 --- /dev/null +++ b/web/react/components/about_build_modal.jsx @@ -0,0 +1,62 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Modal = ReactBootstrap.Modal; + +export default class AboutBuildModal extends React.Component { + constructor(props) { + super(props); + this.doHide = this.doHide.bind(this); + } + + doHide() { + this.props.onModalDismissed(); + } + + render() { + const config = global.window.config; + + return ( + <Modal + show={this.props.show} + onHide={this.doHide} + > + <Modal.Header closeButton={true}> + <Modal.Title>{`Mattermost ${config.Version}`}</Modal.Title> + </Modal.Header> + <Modal.Body> + <div className='row form-group'> + <div className='col-sm-3 info__label'>{'Build Number:'}</div> + <div className='col-sm-9'>{config.BuildNumber}</div> + </div> + <div className='row form-group'> + <div className='col-sm-3 info__label'>{'Build Date:'}</div> + <div className='col-sm-9'>{config.BuildDate}</div> + </div> + <div className='row'> + <div className='col-sm-3 info__label'>{'Build Hash:'}</div> + <div className='col-sm-9'>{config.BuildHash}</div> + </div> + </Modal.Body> + <Modal.Footer> + <button + type='button' + className='btn btn-default' + onClick={this.doHide} + > + {'Close'} + </button> + </Modal.Footer> + </Modal> + ); + } +} + +AboutBuildModal.defaultProps = { + show: false +}; + +AboutBuildModal.propTypes = { + show: React.PropTypes.bool.isRequired, + onModalDismissed: React.PropTypes.func.isRequired +};
\ No newline at end of file diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx index a080150dd..0dfd36717 100644 --- a/web/react/components/access_history_modal.jsx +++ b/web/react/components/access_history_modal.jsx @@ -12,25 +12,31 @@ export default class AccessHistoryModal extends React.Component { this.onAuditChange = this.onAuditChange.bind(this); this.handleMoreInfo = this.handleMoreInfo.bind(this); + this.onHide = this.onHide.bind(this); + this.onShow = this.onShow.bind(this); - this.state = this.getStateFromStoresForAudits(); - this.state.moreInfo = []; + let state = this.getStateFromStoresForAudits(); + state.moreInfo = []; + + this.state = state; } getStateFromStoresForAudits() { return { audits: UserStore.getAudits() }; } + onShow() { + AsyncClient.getAudits(); + } + onHide() { + $('#user_settings').modal('show'); + this.setState({moreInfo: []}); + } componentDidMount() { UserStore.addAuditsChangeListener(this.onAuditChange); - $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function show() { - AsyncClient.getAudits(); - }); + $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow); - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function hide() { - $('#user_settings').modal('show'); - this.setState({moreInfo: []}); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); } componentWillUnmount() { UserStore.removeAuditsChangeListener(this.onAuditChange); diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index fe40385a0..ff370c32e 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -14,9 +14,13 @@ export default class ActivityLogModal extends React.Component { this.submitRevoke = this.submitRevoke.bind(this); this.onListenerChange = this.onListenerChange.bind(this); this.handleMoreInfo = this.handleMoreInfo.bind(this); + this.onHide = this.onHide.bind(this); + this.onShow = this.onShow.bind(this); - this.state = this.getStateFromStores(); - this.state.moreInfo = []; + let state = this.getStateFromStores(); + state.moreInfo = []; + + this.state = state; } getStateFromStores() { return { @@ -27,6 +31,11 @@ export default class ActivityLogModal extends React.Component { } submitRevoke(altId, e) { e.preventDefault(); + var modalContent = $(e.target).closest('.modal-content'); + modalContent.addClass('animation--highlight'); + setTimeout(() => { + modalContent.removeClass('animation--highlight'); + }, 1500); Client.revokeSession(altId, function handleRevokeSuccess() { AsyncClient.getSessions(); @@ -38,16 +47,18 @@ export default class ActivityLogModal extends React.Component { }.bind(this) ); } + onShow() { + AsyncClient.getSessions(); + } + onHide() { + $('#user_settings').modal('show'); + this.setState({moreInfo: []}); + } componentDidMount() { UserStore.addSessionsChangeListener(this.onListenerChange); - $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function handleShow() { - AsyncClient.getSessions(); - }); + $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow); - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function handleHide() { - $('#user_settings').modal('show'); - this.setState({moreInfo: []}); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); } componentWillUnmount() { UserStore.removeSessionsChangeListener(this.onListenerChange); diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index 92f0bbdce..f40e48f70 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -147,7 +147,7 @@ export default class AdminController extends React.Component { } return ( - <div className='container-fluid'> + <div> <div className='sidebar--menu' id='sidebar-menu' diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index 4b9ff3cb8..f102661b2 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -129,141 +129,143 @@ export default class AdminSidebar extends React.Component { <div className='sidebar--left sidebar--collapsable'> <div> <AdminSidebarHeader /> - <ul className='nav nav-pills nav-stacked'> - <li> - <ul className='nav nav__sub-menu'> - <li> - <h4> - <span className='icon fa fa-gear'></span> - <span>{'SETTINGS'}</span> - </h4> - </li> - </ul> - <ul className='nav nav__sub-menu padded'> - <li> - <a - href='#' - className={this.isSelected('service_settings')} - onClick={this.handleClick.bind(this, 'service_settings', null)} - > - {'Service Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('team_settings')} - onClick={this.handleClick.bind(this, 'team_settings', null)} - > - {'Team Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('sql_settings')} - onClick={this.handleClick.bind(this, 'sql_settings', null)} - > - {'SQL Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('email_settings')} - onClick={this.handleClick.bind(this, 'email_settings', null)} - > - {'Email Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('image_settings')} - onClick={this.handleClick.bind(this, 'image_settings', null)} - > - {'File Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('log_settings')} - onClick={this.handleClick.bind(this, 'log_settings', null)} - > - {'Log Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('rate_settings')} - onClick={this.handleClick.bind(this, 'rate_settings', null)} - > - {'Rate Limit Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('privacy_settings')} - onClick={this.handleClick.bind(this, 'privacy_settings', null)} - > - {'Privacy Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('gitlab_settings')} - onClick={this.handleClick.bind(this, 'gitlab_settings', null)} - > - {'GitLab Settings'} - </a> - </li> - </ul> - <ul className='nav nav__sub-menu'> - <li> - <h4> - <span className='icon fa fa-gear'></span> - <span>{'TEAMS (' + count + ')'}</span> - <span className='menu-icon--right'> - <a - href='#' - onClick={this.showTeamSelect} - > - <i className='fa fa-plus'></i> - </a> - </span> - </h4> - </li> - </ul> - <ul className='nav nav__sub-menu padded'> - <li> - {teams} - </li> - </ul> - <ul className='nav nav__sub-menu'> - <li> - <h4> - <span className='icon fa fa-gear'></span> - <span>{'OTHER'}</span> - </h4> - </li> - </ul> - <ul className='nav nav__sub-menu padded'> - <li> - <a - href='#' - className={this.isSelected('logs')} - onClick={this.handleClick.bind(this, 'logs', null)} - > - {'Logs'} - </a> - </li> - </ul> - </li> - </ul> + <div className='nav-pills__container'> + <ul className='nav nav-pills nav-stacked'> + <li> + <ul className='nav nav__sub-menu'> + <li> + <h4> + <span className='icon fa fa-gear'></span> + <span>{'SETTINGS'}</span> + </h4> + </li> + </ul> + <ul className='nav nav__sub-menu padded'> + <li> + <a + href='#' + className={this.isSelected('service_settings')} + onClick={this.handleClick.bind(this, 'service_settings', null)} + > + {'Service Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('team_settings')} + onClick={this.handleClick.bind(this, 'team_settings', null)} + > + {'Team Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('sql_settings')} + onClick={this.handleClick.bind(this, 'sql_settings', null)} + > + {'SQL Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('email_settings')} + onClick={this.handleClick.bind(this, 'email_settings', null)} + > + {'Email Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('image_settings')} + onClick={this.handleClick.bind(this, 'image_settings', null)} + > + {'File Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('log_settings')} + onClick={this.handleClick.bind(this, 'log_settings', null)} + > + {'Log Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('rate_settings')} + onClick={this.handleClick.bind(this, 'rate_settings', null)} + > + {'Rate Limit Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('privacy_settings')} + onClick={this.handleClick.bind(this, 'privacy_settings', null)} + > + {'Privacy Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('gitlab_settings')} + onClick={this.handleClick.bind(this, 'gitlab_settings', null)} + > + {'GitLab Settings'} + </a> + </li> + </ul> + <ul className='nav nav__sub-menu'> + <li> + <h4> + <span className='icon fa fa-gear'></span> + <span>{'TEAMS (' + count + ')'}</span> + <span className='menu-icon--right'> + <a + href='#' + onClick={this.showTeamSelect} + > + <i className='fa fa-plus'></i> + </a> + </span> + </h4> + </li> + </ul> + <ul className='nav nav__sub-menu padded'> + <li> + {teams} + </li> + </ul> + <ul className='nav nav__sub-menu'> + <li> + <h4> + <span className='icon fa fa-gear'></span> + <span>{'OTHER'}</span> + </h4> + </li> + </ul> + <ul className='nav nav__sub-menu padded'> + <li> + <a + href='#' + className={this.isSelected('logs')} + onClick={this.handleClick.bind(this, 'logs', null)} + > + {'Logs'} + </a> + </li> + </ul> + </li> + </ul> + </div> </div> <SelectTeamModal diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index 762a4ab26..3432f69ff 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -437,7 +437,7 @@ export default class EmailSettings extends React.Component { </select> <div className='help-text'> <table - className='table-bordered' + className='table table-bordered' cellPadding='5' > <tr><td className='help-text'>{'None'}</td><td className='help-text'>{'Mattermost will send email over an unsecure connection.'}</td></tr> @@ -447,7 +447,7 @@ export default class EmailSettings extends React.Component { </div> <div className='help-text'> <button - className='help-link' + className='btn' onClick={this.handleTestConnection} disabled={!this.state.sendEmailNotifications} id='connection-button' @@ -482,7 +482,7 @@ export default class EmailSettings extends React.Component { <p className='help-text'>{'32-character salt added to signing of email invites. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p> <div className='help-text'> <button - className='help-link' + className='btn' onClick={this.handleGenerateInvite} disabled={!this.state.sendEmailNotifications} > @@ -513,7 +513,7 @@ export default class EmailSettings extends React.Component { <p className='help-text'>{'32-character salt added to signing of password reset emails. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p> <div className='help-text'> <button - className='help-link' + className='btn' onClick={this.handleGenerateReset} disabled={!this.state.sendEmailNotifications} > diff --git a/web/react/components/admin_console/image_settings.jsx b/web/react/components/admin_console/image_settings.jsx index e52f516e8..e08d39ca8 100644 --- a/web/react/components/admin_console/image_settings.jsx +++ b/web/react/components/admin_console/image_settings.jsx @@ -460,7 +460,7 @@ export default class FileSettings extends React.Component { <p className='help-text'>{'32-character salt added to signing of public image links. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p> <div className='help-text'> <button - className='help-link' + className='btn btn-default' onClick={this.handleGenerate} > {'Re-Generate'} diff --git a/web/react/components/admin_console/log_settings.jsx b/web/react/components/admin_console/log_settings.jsx index 1c39c60e8..608ef9cc0 100644 --- a/web/react/components/admin_console/log_settings.jsx +++ b/web/react/components/admin_console/log_settings.jsx @@ -253,7 +253,7 @@ export default class LogSettings extends React.Component { {'Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'} <div className='help-text'> <table - className='table-bordered' + className='table table-bordered' cellPadding='5' > <tr><td className='help-text'>{'%T'}</td><td className='help-text'>{'Time (15:04:05 MST)'}</td></tr> diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx index 32812e875..c5c6e19d4 100644 --- a/web/react/components/admin_console/user_item.jsx +++ b/web/react/components/admin_console/user_item.jsx @@ -127,7 +127,6 @@ export default class UserItem extends React.Component { if (user.delete_at > 0) { currentRoles = 'Inactive'; - currentRoles = 'Inactive'; showMakeMember = false; showMakeAdmin = false; showMakeSystemAdmin = false; diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index b81936b57..f15974d35 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -132,7 +132,26 @@ export default class ChannelHeader extends React.Component { } let dropdownContents = []; - if (!isDirect) { + if (isDirect) { + dropdownContents.push( + <li + key='edit_description_direct' + role='presentation' + > + <a + role='menuitem' + href='#' + data-toggle='modal' + data-target='#edit_channel' + data-desc={channel.description} + data-title={channel.display_name} + data-channelid={channel.id} + > + Set Channel Description... + </a> + </li> + ); + } else { dropdownContents.push( <li key='view_info' @@ -276,25 +295,6 @@ export default class ChannelHeader extends React.Component { </li> ); } - } else { - dropdownContents.push( - <li - key='edit_description_direct' - role='presentation' - > - <a - role='menuitem' - href='#' - data-toggle='modal' - data-target='#edit_channel' - data-desc={channel.description} - data-title={channel.display_name} - data-channelid={channel.id} - > - Set Channel Description... - </a> - </li> - ); } return ( diff --git a/web/react/components/channel_members.jsx b/web/react/components/channel_members.jsx index 1eda6a104..53c854eb7 100644 --- a/web/react/components/channel_members.jsx +++ b/web/react/components/channel_members.jsx @@ -15,6 +15,8 @@ export default class ChannelMembers extends React.Component { this.getStateFromStores = this.getStateFromStores.bind(this); this.onChange = this.onChange.bind(this); this.handleRemove = this.handleRemove.bind(this); + this.onHide = this.onHide.bind(this); + this.onShow = this.onShow.bind(this); this.state = this.getStateFromStores(); } @@ -63,16 +65,18 @@ export default class ChannelMembers extends React.Component { channelName: channelName }; } + onHide() { + this.setState({renderMembers: false}); + } + onShow() { + this.setState({renderMembers: true}); + } componentDidMount() { ChannelStore.addExtraInfoChangeListener(this.onChange); ChannelStore.addChangeListener(this.onChange); - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function handleHide() { - this.setState({renderMembers: false}); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow() { - this.setState({renderMembers: true}); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); } componentWillUnmount() { ChannelStore.removeExtraInfoChangeListener(this.onChange); diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx index 45981b295..ed76b7bce 100644 --- a/web/react/components/channel_notifications.jsx +++ b/web/react/components/channel_notifications.jsx @@ -23,6 +23,7 @@ export default class ChannelNotifications extends React.Component { this.handleSubmitMarkUnreadLevel = this.handleSubmitMarkUnreadLevel.bind(this); this.handleUpdateMarkUnreadLevel = this.handleUpdateMarkUnreadLevel.bind(this); this.createMarkUnreadLevelSection = this.createMarkUnreadLevelSection.bind(this); + this.onShow = this.onShow.bind(this); this.state = { notifyLevel: '', @@ -32,30 +33,29 @@ export default class ChannelNotifications extends React.Component { activeSection: '' }; } + onShow(e) { + var button = e.relatedTarget; + var channelId = button.getAttribute('data-channelid'); + const member = ChannelStore.getMember(channelId); + var notifyLevel = member.notify_props.desktop; + var markUnreadLevel = member.notify_props.mark_unread; + + this.setState({ + notifyLevel, + markUnreadLevel, + title: button.getAttribute('data-title'), + channelId + }); + } componentDidMount() { ChannelStore.addChangeListener(this.onListenerChange); - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function showModal(e) { - var button = e.relatedTarget; - var channelId = button.getAttribute('data-channelid'); - - const member = ChannelStore.getMember(channelId); - var notifyLevel = member.notify_props.desktop; - var markUnreadLevel = member.notify_props.mark_unread; - - this.setState({ - notifyLevel, - markUnreadLevel, - title: button.getAttribute('data-title'), - channelId: channelId - }); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); } componentWillUnmount() { ChannelStore.removeChangeListener(this.onListenerChange); } - onListenerChange() { if (!this.state.channelId) { return; @@ -76,7 +76,6 @@ export default class ChannelNotifications extends React.Component { updateSection(section) { this.setState({activeSection: section}); } - handleSubmitNotifyLevel() { var channelId = this.state.channelId; var notifyLevel = this.state.notifyLevel; @@ -103,12 +102,10 @@ export default class ChannelNotifications extends React.Component { } ); } - handleUpdateNotifyLevel(notifyLevel) { this.setState({notifyLevel}); React.findDOMNode(this.refs.modal).focus(); } - createNotifyLevelSection(serverError) { var handleUpdateSection; @@ -198,9 +195,7 @@ export default class ChannelNotifications extends React.Component { const extraInfo = ( <span> - {'Selecting an option other than "Default" will override the global notification settings.'} - <br/> - {'Desktop notifications are available on Firefox, Safari, and Chrome.'} + {'Selecting an option other than "Default" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.'} </span> ); diff --git a/web/react/components/command_list.jsx b/web/react/components/command_list.jsx index fea7085b7..e027e87ae 100644 --- a/web/react/components/command_list.jsx +++ b/web/react/components/command_list.jsx @@ -51,7 +51,7 @@ export default class CommandList extends React.Component { this.setState({suggestions: data.suggestions, cmd: cmd}); }.bind(this), function fail() { - } + } ); } diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 5097b3aa5..550f85d3d 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -106,10 +106,11 @@ export default class CreateComment extends React.Component { let state = {}; if (err.message === 'Invalid RootId parameter') { + PostStore.removePendingPost(post.channel_id, post.pending_post_id); + if ($('#post_deleted').length > 0) { $('#post_deleted').modal('show'); } - PostStore.removePendingPost(post.pending_post_id); } else { post.state = Constants.POST_FAILED; PostStore.updatePendingPost(post); @@ -170,7 +171,9 @@ export default class CreateComment extends React.Component { this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews}); } handleUploadError(err, clientId) { - if (clientId !== -1) { + if (clientId === -1) { + this.setState({serverError: err}); + } else { let draft = PostStore.getCommentDraft(this.props.rootId); const index = draft.uploadsInProgress.indexOf(clientId); @@ -181,8 +184,6 @@ export default class CreateComment extends React.Component { PostStore.storeCommentDraft(this.props.rootId, draft); this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err}); - } else { - this.setState({serverError: err}); } } handleTextDrop(text) { @@ -196,15 +197,15 @@ export default class CreateComment extends React.Component { // id can either be the path of an uploaded file or the client id of an in progress upload let index = previews.indexOf(id); - if (index !== -1) { - previews.splice(index, 1); - } else { + if (index === -1) { index = uploadsInProgress.indexOf(id); if (index !== -1) { uploadsInProgress.splice(index, 1); this.refs.fileUpload.cancelUpload(id); } + } else { + previews.splice(index, 1); } let draft = PostStore.getCommentDraft(this.props.rootId); diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 0cd14747d..6e83f4faf 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -222,7 +222,9 @@ export default class CreatePost extends React.Component { this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews}); } handleUploadError(err, clientId) { - if (clientId !== -1) { + if (clientId === -1) { + this.setState({serverError: err}); + } else { const draft = PostStore.getDraft(this.state.channelId); const index = draft.uploadsInProgress.indexOf(clientId); @@ -233,8 +235,6 @@ export default class CreatePost extends React.Component { PostStore.storeDraft(this.state.channelId, draft); this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err}); - } else { - this.setState({serverError: err}); } } handleTextDrop(text) { @@ -248,15 +248,15 @@ export default class CreatePost extends React.Component { // id can either be the path of an uploaded file or the client id of an in progress upload let index = previews.indexOf(id); - if (index !== -1) { - previews.splice(index, 1); - } else { + if (index === -1) { index = uploadsInProgress.indexOf(id); if (index !== -1) { uploadsInProgress.splice(index, 1); this.refs.fileUpload.cancelUpload(id); } + } else { + previews.splice(index, 1); } const draft = PostStore.getCurrentDraft(); diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx index 44c54db72..71c636921 100644 --- a/web/react/components/delete_channel_modal.jsx +++ b/web/react/components/delete_channel_modal.jsx @@ -11,6 +11,7 @@ export default class DeleteChannelModal extends React.Component { super(props); this.handleDelete = this.handleDelete.bind(this); + this.onShow = this.onShow.bind(this); this.state = { title: '', @@ -32,14 +33,15 @@ export default class DeleteChannelModal extends React.Component { } ); } + onShow(e) { + var button = $(e.relatedTarget); + this.setState({ + title: button.attr('data-title'), + channelId: button.attr('data-channelid') + }); + } componentDidMount() { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) { - var button = $(e.relatedTarget); - this.setState({ - title: button.attr('data-title'), - channelId: button.attr('data-channelid') - }); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); } render() { const channel = ChannelStore.getCurrent(); diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx index 075f9c742..8e48a7a1c 100644 --- a/web/react/components/delete_post_modal.jsx +++ b/web/react/components/delete_post_modal.jsx @@ -16,6 +16,7 @@ export default class DeletePostModal extends React.Component { this.handleDelete = this.handleDelete.bind(this); this.onListenerChange = this.onListenerChange.bind(this); + this.onShow = this.onShow.bind(this); this.state = {title: '', postId: '', channelId: '', selectedList: PostStore.getSelectedPost(), comments: 0}; } @@ -60,18 +61,19 @@ export default class DeletePostModal extends React.Component { } ); } + onShow(e) { + var newState = {}; + if (BrowserStore.getItem('edit_state_transfer')) { + newState = BrowserStore.getItem('edit_state_transfer'); + BrowserStore.removeItem('edit_state_transfer'); + } else { + var button = e.relatedTarget; + newState = {title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), postId: $(button).attr('data-postid'), comments: $(button).attr('data-comments')}; + } + this.setState(newState); + } componentDidMount() { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function freshOpen(e) { - var newState = {}; - if (BrowserStore.getItem('edit_state_transfer')) { - newState = BrowserStore.getItem('edit_state_transfer'); - BrowserStore.removeItem('edit_state_transfer'); - } else { - var button = e.relatedTarget; - newState = {title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), postId: $(button).attr('data-postid'), comments: $(button).attr('data-comments')}; - } - this.setState(newState); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); PostStore.addSelectedPostChangeListener(this.onListenerChange); } componentWillUnmount() { diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx index e93bab431..27219aba5 100644 --- a/web/react/components/edit_channel_modal.jsx +++ b/web/react/components/edit_channel_modal.jsx @@ -11,6 +11,7 @@ export default class EditChannelModal extends React.Component { this.handleEdit = this.handleEdit.bind(this); this.handleUserInput = this.handleUserInput.bind(this); this.handleClose = this.handleClose.bind(this); + this.onShow = this.onShow.bind(this); this.state = { description: '', @@ -50,11 +51,12 @@ export default class EditChannelModal extends React.Component { handleClose() { this.setState({description: '', serverError: ''}); } + onShow(e) { + const button = e.relatedTarget; + this.setState({description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''}); + } componentDidMount() { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) { - const button = e.relatedTarget; - this.setState({description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''}); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); } componentWillUnmount() { diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx index 8d3f15525..391de3326 100644 --- a/web/react/components/email_verify.jsx +++ b/web/react/components/email_verify.jsx @@ -38,8 +38,8 @@ export default class EmailVerify extends React.Component { } return ( - <div className='col-sm-offset-4 col-sm-4'> - <div className='panel panel-default'> + <div className='col-sm-12'> + <div className='panel panel-default verify_panel'> <div className='panel-heading'> <h3 className='panel-title'>{title}</h3> </div> diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx index 05726e860..5aa55be93 100644 --- a/web/react/components/error_bar.jsx +++ b/web/react/components/error_bar.jsx @@ -9,28 +9,71 @@ export default class ErrorBar extends React.Component { this.onErrorChange = this.onErrorChange.bind(this); this.handleClose = this.handleClose.bind(this); + this.resize = this.resize.bind(this); this.prevTimer = null; this.state = ErrorStore.getLastError(); - if (this.state && this.state.message) { + if (this.isValidError(this.state)) { this.prevTimer = setTimeout(this.handleClose, 10000); } } + isValidError(s) { + if (!s) { + return false; + } + + if (!s.message) { + return false; + } + + if (s.connErrorCount && s.connErrorCount >= 1 && s.connErrorCount < 7) { + return false; + } + + return true; + } + + isConnectionError(s) { + if (!s.connErrorCount || s.connErrorCount === 0) { + return false; + } + + if (s.connErrorCount > 7) { + return true; + } + + return false; + } + + resize() { + if (this.isValidError(this.state)) { + var height = $(React.findDOMNode(this)).outerHeight(); + height = height < 30 ? 30 : height; + $('body').css('padding-top', height + 'px'); + } else { + $('body').css('padding-top', '0'); + } + } + componentDidMount() { ErrorStore.addChangeListener(this.onErrorChange); - $('body').css('padding-top', $(React.findDOMNode(this)).outerHeight()); + $(window).resize(() => { - if (this.state && this.state.message) { - $('body').css('padding-top', $(React.findDOMNode(this)).outerHeight()); - } + this.resize(); }); + + this.resize(); } componentWillUnmount() { ErrorStore.removeChangeListener(this.onErrorChange); } + componentDidUpdate() { + this.resize(); + } + onErrorChange() { var newState = ErrorStore.getLastError(); @@ -41,7 +84,9 @@ export default class ErrorBar extends React.Component { if (newState) { this.setState(newState); - this.prevTimer = setTimeout(this.handleClose, 10000); + if (!this.isConnectionError(newState)) { + this.prevTimer = setTimeout(this.handleClose, 10000); + } } else { this.setState({message: null}); } @@ -52,22 +97,11 @@ export default class ErrorBar extends React.Component { e.preventDefault(); } - ErrorStore.storeLastError(null); - ErrorStore.emitChange(); - - $('body').css('padding-top', '0'); + this.setState({message: null}); } render() { - if (!this.state) { - return <div/>; - } - - if (!this.state.message) { - return <div/>; - } - - if (this.state.connErrorCount < 7) { + if (!this.isValidError(this.state)) { return <div/>; } diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index 3dc4e5de2..0e9297b7b 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -97,7 +97,7 @@ export default class FileUpload extends React.Component { element[0].type = 'text'; element[0].type = 'file'; } - } catch(e) { + } catch (e) { // Do nothing } } diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx index 5d8b13f00..6e0728862 100644 --- a/web/react/components/get_link_modal.jsx +++ b/web/react/components/get_link_modal.jsx @@ -8,18 +8,22 @@ export default class GetLinkModal extends React.Component { super(props); this.handleClick = this.handleClick.bind(this); + this.onShow = this.onShow.bind(this); + this.onHide = this.onHide.bind(this); this.state = {copiedLink: false}; } + onShow(e) { + var button = e.relatedTarget; + this.setState({title: $(button).attr('data-title'), value: $(button).attr('data-value')}); + } + onHide() { + this.setState({copiedLink: false}); + } componentDidMount() { if (this.refs.modal) { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) { - var button = e.relatedTarget; - this.setState({title: $(button).attr('data-title'), value: $(button).attr('data-value')}); - }.bind(this)); - $(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', function hide() { - this.setState({copiedLink: false}); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); + $(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', this.onHide); } } handleClick() { @@ -39,8 +43,23 @@ export default class GetLinkModal extends React.Component { } render() { var currentUser = UserStore.getCurrentUser(); - var copyLinkConfirm = null; + let copyLink = null; + if (document.queryCommandSupported('copy')) { + copyLink = ( + <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> + ); + } + + var copyLinkConfirm = null; if (this.state.copiedLink) { copyLinkConfirm = <p className='alert alert-success copy-link-confirm'><i className='fa fa-check'></i> Link copied to clipboard.</p>; } @@ -94,15 +113,7 @@ export default class GetLinkModal extends React.Component { > 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> + {copyLink} {copyLinkConfirm} </div> </div> diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index 8cc4f1483..54df75cbc 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -88,10 +88,10 @@ export default class Login extends React.Component { let focusEmail = false; let focusPassword = false; - if (priorEmail !== '') { - focusPassword = true; - } else { + if (priorEmail === '') { focusEmail = true; + } else { + focusPassword = true; } let loginMessage = []; @@ -112,6 +112,17 @@ export default class Login extends React.Component { errorClass = ' has-error'; } + const verifiedParam = Utils.getUrlParameter('verified'); + let verifiedBox = ''; + if (verifiedParam) { + verifiedBox = ( + <div className='alert alert-success'> + <i className='fa fa-check' /> + {' Email Verified'} + </div> + ); + } + let emailSignup; if (global.window.config.EnableSignUpWithEmail === 'true') { emailSignup = ( @@ -175,6 +186,7 @@ export default class Login extends React.Component { <h2 className='signup-team__name'>{teamDisplayName}</h2> <h2 className='signup-team__subdomain'>on {global.window.config.SiteName}</h2> <form onSubmit={this.handleSubmit}> + {verifiedBox} <div className={'form-group' + errorClass}> {serverError} </div> diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx index b7e81f843..629fb2ec4 100644 --- a/web/react/components/member_list_team_item.jsx +++ b/web/react/components/member_list_team_item.jsx @@ -24,32 +24,32 @@ export default class MemberListTeamItem extends React.Component { }; Client.updateRoles(data, - function handleMakeMemberSuccess() { + () => { AsyncClient.getProfiles(); }, - function handleMakeMemberError(err) { + (err) => { this.setState({serverError: err.message}); - }.bind(this) + } ); } handleMakeActive() { Client.updateActive(this.props.user.id, true, - function handleMakeActiveSuccess() { + () => { AsyncClient.getProfiles(); }, - function handleMakeActiveError(err) { + (err) => { this.setState({serverError: err.message}); - }.bind(this) + } ); } handleMakeNotActive() { Client.updateActive(this.props.user.id, false, - function handleMakeNotActiveSuccess() { + () => { AsyncClient.getProfiles(); }, - function handleMakeNotActiveError(err) { + (err) => { this.setState({serverError: err.message}); - }.bind(this) + } ); } handleMakeAdmin() { @@ -59,12 +59,12 @@ export default class MemberListTeamItem extends React.Component { }; Client.updateRoles(data, - function handleMakeAdminSuccess() { + () => { AsyncClient.getProfiles(); }, - function handleMakeAdmitError(err) { + (err) => { this.setState({serverError: err.message}); - }.bind(this) + } ); } render() { @@ -82,14 +82,18 @@ export default class MemberListTeamItem extends React.Component { const timestamp = UserStore.getCurrentUser().update_at; if (user.roles.length > 0) { - currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1); + if (user.roles.indexOf('system_admin') > -1) { + currentRoles = 'System Admin'; + } else { + currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1); + } } const email = user.email; - let showMakeMember = user.roles === 'admin'; - let showMakeAdmin = user.roles === ''; + let showMakeMember = user.roles === 'admin' || user.roles === 'system_admin'; + let showMakeAdmin = user.roles === '' || user.roles === 'system_admin'; let showMakeActive = false; - let showMakeNotActive = true; + let showMakeNotActive = user.roles !== 'system_admin'; if (user.delete_at > 0) { currentRoles = 'Inactive'; @@ -108,7 +112,7 @@ export default class MemberListTeamItem extends React.Component { href='#' onClick={this.handleMakeAdmin} > - Make Admin + {'Make Admin'} </a> </li> ); @@ -123,7 +127,7 @@ export default class MemberListTeamItem extends React.Component { href='#' onClick={this.handleMakeMember} > - Make Member + {'Make Member'} </a> </li> ); @@ -138,7 +142,7 @@ export default class MemberListTeamItem extends React.Component { href='#' onClick={this.handleMakeActive} > - Make Active + {'Make Active'} </a> </li> ); @@ -153,7 +157,7 @@ export default class MemberListTeamItem extends React.Component { href='#' onClick={this.handleMakeNotActive} > - Make Inactive + {'Make Inactive'} </a> </li> ); diff --git a/web/react/components/mention.jsx b/web/react/components/mention.jsx index 3a09e843d..ef7cec408 100644 --- a/web/react/components/mention.jsx +++ b/web/react/components/mention.jsx @@ -18,7 +18,9 @@ export default class Mention extends React.Component { var timestamp = UserStore.getCurrentUser().update_at; if (this.props.id === 'allmention' || this.props.id === 'channelmention') { icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>; - } else if (this.props.id != null) { + } else if (this.props.id == null) { + icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>; + } else { icon = ( <span> <img @@ -27,8 +29,6 @@ export default class Mention extends React.Component { /> </span> ); - } else { - icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>; } return ( <div diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx index 46a9d76ae..72f51013c 100644 --- a/web/react/components/mention_list.jsx +++ b/web/react/components/mention_list.jsx @@ -26,53 +26,64 @@ export default class MentionList extends React.Component { this.addFirstMention = this.addFirstMention.bind(this); this.isEmpty = this.isEmpty.bind(this); this.scrollToMention = this.scrollToMention.bind(this); + this.onScroll = this.onScroll.bind(this); + this.onMentionListKey = this.onMentionListKey.bind(this); + this.onClick = this.onClick.bind(this); this.state = {excludeUsers: [], mentionText: '-1', selectedMention: 0, selectedUsername: ''}; } - componentDidMount() { - PostStore.addMentionDataChangeListener(this.onListenerChange); + onScroll() { + if ($('.mentions--top').length) { + $('#reply_mention_tab .mentions--top').css({bottom: $(window).height() - $('.post-right__scroll #reply_textbox').offset().top}); + } + } + onMentionListKey(e) { + if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 13 || e.which === 9)) { + e.stopPropagation(); + e.preventDefault(); + this.addCurrentMention(); + } else if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 38 || e.which === 40)) { + e.stopPropagation(); + e.preventDefault(); - $('.post-right__scroll').scroll(function onScroll() { - if ($('.mentions--top').length) { - $('#reply_mention_tab .mentions--top').css({bottom: $(window).height() - $('.post-right__scroll #reply_textbox').offset().top}); + if (e.which === 38) { + if (this.getSelection(this.state.selectedMention - 1)) { + this.setState({selectedMention: this.state.selectedMention - 1, selectedUsername: this.refs['mention' + (this.state.selectedMention - 1)].props.username}); + } + } else if (e.which === 40) { + if (this.getSelection(this.state.selectedMention + 1)) { + this.setState({selectedMention: this.state.selectedMention + 1, selectedUsername: this.refs['mention' + (this.state.selectedMention + 1)].props.username}); + } } - }); - $('body').on('keydown.mentionlist', '#' + this.props.id, - function onMentionListKey(e) { - if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 13 || e.which === 9)) { - e.stopPropagation(); - e.preventDefault(); - this.addCurrentMention(); - } else if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 38 || e.which === 40)) { - e.stopPropagation(); - e.preventDefault(); + this.scrollToMention(e.which); + } + } + onClick(e) { + if (!($('#' + this.props.id).is(e.target) || $('#' + this.props.id).has(e.target).length || + ('mentionlist' in this.refs && $(React.findDOMNode(this.refs.mentionlist)).has(e.target).length))) { + this.setState({mentionText: '-1'}); + } + } + componentDidMount() { + PostStore.addMentionDataChangeListener(this.onListenerChange); - if (e.which === 38) { - if (this.getSelection(this.state.selectedMention - 1)) { - this.setState({selectedMention: this.state.selectedMention - 1, selectedUsername: this.refs['mention' + (this.state.selectedMention - 1)].props.username}); - } - } else if (e.which === 40) { - if (this.getSelection(this.state.selectedMention + 1)) { - this.setState({selectedMention: this.state.selectedMention + 1, selectedUsername: this.refs['mention' + (this.state.selectedMention + 1)].props.username}); - } - } + $('.post-right__scroll').scroll(this.onScroll); - this.scrollToMention(e.which); - } - }.bind(this) - ); - $(document).click(function onClick(e) { - if (!($('#' + this.props.id).is(e.target) || $('#' + this.props.id).has(e.target).length || - ('mentionlist' in this.refs && $(React.findDOMNode(this.refs.mentionlist)).has(e.target).length))) { - this.setState({mentionText: '-1'}); - } - }.bind(this)); + $('body').on('keydown.mentionlist', '#' + this.props.id, this.onMentionListKey); + $(document).click(this.onClick); } componentWillUnmount() { PostStore.removeMentionDataChangeListener(this.onListenerChange); $('body').off('keydown.mentionlist', '#' + this.props.id); } + + /* + * This component is poorly designed, nessesitating some state modification + * in the componentDidUpdate function. This is generally discouraged as it + * is a performance issue and breaks with good react design. This component + * should be redesigned. + */ componentDidUpdate() { if (this.state.mentionText !== '-1') { if (this.state.selectedUsername !== '' && (!this.getSelection(this.state.selectedMention) || this.state.selectedUsername !== this.refs['mention' + this.state.selectedMention].props.username)) { @@ -80,17 +91,17 @@ export default class MentionList extends React.Component { 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}); //eslint-disable-line react/no-did-update-set-state 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}); //eslint-disable-line react/no-did-update-set-state } } } else if (this.state.selectedMention !== 0) { - this.setState({selectedMention: 0, selectedUsername: ''}); + this.setState({selectedMention: 0, selectedUsername: ''}); //eslint-disable-line react/no-did-update-set-state } } onListenerChange(id, mentionText) { @@ -124,10 +135,10 @@ export default class MentionList extends React.Component { return true; } addCurrentMention() { - if (!this.getSelection(this.state.selectedMention)) { - this.addFirstMention(); - } else { + if (this.getSelection(this.state.selectedMention)) { this.refs['mention' + this.state.selectedMention].handleClick(); + } else { + this.addFirstMention(); } } addFirstMention() { diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx index 65cd40975..487192d91 100644 --- a/web/react/components/more_channels.jsx +++ b/web/react/components/more_channels.jsx @@ -60,9 +60,7 @@ export default class MoreChannels extends React.Component { this.setState({joiningChannel: -1}); }.bind(this), function joinFail(err) { - this.setState({joiningChannel: -1}); - this.state.serverError = err.message; - this.setState(this.state); + this.setState({joiningChannel: -1, serverError: err.message}); }.bind(this) ); } @@ -81,56 +79,54 @@ export default class MoreChannels extends React.Component { if (this.state.channels != null) { var channels = this.state.channels; - if (!channels.loading) { - if (channels.length) { - moreChannels = ( - <table className='more-channel-table table'> - <tbody> - {channels.map(function cMap(channel, index) { - var joinButton; - if (self.state.joiningChannel === index) { - joinButton = ( - <img - className='join-channel-loading-gif' - src='/static/images/load.gif' - /> - ); - } else { - joinButton = ( - <button - onClick={self.handleJoin.bind(self, channel, index)} - className='btn btn-primary' - > - Join - </button> - ); - } + if (channels.loading) { + moreChannels = <LoadingScreen />; + } else if (channels.length) { + moreChannels = ( + <table className='more-channel-table table'> + <tbody> + {channels.map(function cMap(channel, index) { + var joinButton; + if (self.state.joiningChannel === index) { + joinButton = ( + <img + className='join-channel-loading-gif' + src='/static/images/load.gif' + /> + ); + } else { + joinButton = ( + <button + onClick={self.handleJoin.bind(self, channel, index)} + className='btn btn-primary' + > + Join + </button> + ); + } - return ( - <tr key={channel.id}> - <td> - <p className='more-channel-name'>{channel.display_name}</p> - <p className='more-channel-description'>{channel.description}</p> - </td> - <td className='td--action'> - {joinButton} - </td> - </tr> - ); - })} - </tbody> - </table> - ); - } else { - moreChannels = ( - <div className='no-channel-message'> - <p className='primary-message'>No more channels to join</p> - <p className='secondary-message'>Click 'Create New Channel' to make a new one</p> - </div> - ); - } + return ( + <tr key={channel.id}> + <td> + <p className='more-channel-name'>{channel.display_name}</p> + <p className='more-channel-description'>{channel.description}</p> + </td> + <td className='td--action'> + {joinButton} + </td> + </tr> + ); + })} + </tbody> + </table> + ); } else { - moreChannels = <LoadingScreen />; + moreChannels = ( + <div className='no-channel-message'> + <p className='primary-message'>No more channels to join</p> + <p className='secondary-message'>Click 'Create New Channel' to make a new one</p> + </div> + ); } } diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx index 54d77c358..c71abd43a 100644 --- a/web/react/components/more_direct_channels.jsx +++ b/web/react/components/more_direct_channels.jsx @@ -31,22 +31,7 @@ export default class MoreDirectChannels extends React.Component { var active = ''; var handleClick = null; - if (!channel.fake) { - if (channel.id === ChannelStore.getCurrentId()) { - active = 'active'; - } - - if (channel.unread) { - badge = <span className='badge pull-right small'>{channel.unread}</span>; - titleClass = 'unread-title'; - } - - handleClick = function clickHandler(e) { - e.preventDefault(); - utils.switchChannel(channel); - $(React.findDOMNode(self.refs.modal)).modal('hide'); - }; - } else { + if (channel.fake) { // It's a direct message channel that doesn't exist yet so let's create it now var otherUserId = utils.getUserIdFromChannelName(channel); @@ -78,6 +63,21 @@ export default class MoreDirectChannels extends React.Component { ); }; } + } else { + if (channel.id === ChannelStore.getCurrentId()) { + active = 'active'; + } + + if (channel.unread) { + badge = <span className='badge pull-right small'>{channel.unread}</span>; + titleClass = 'unread-title'; + } + + handleClick = function clickHandler(e) { + e.preventDefault(); + utils.switchChannel(channel); + $(React.findDOMNode(self.refs.modal)).modal('hide'); + }; } return ( diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx index 78057d10b..ff7a53848 100644 --- a/web/react/components/navbar_dropdown.jsx +++ b/web/react/components/navbar_dropdown.jsx @@ -6,6 +6,8 @@ var client = require('../utils/client.jsx'); var UserStore = require('../stores/user_store.jsx'); var TeamStore = require('../stores/team_store.jsx'); +var AboutBuildModal = require('./about_build_modal.jsx'); + var Constants = require('../utils/constants.jsx'); function getStateFromStores() { @@ -18,7 +20,9 @@ export default class NavbarDropdown extends React.Component { this.blockToggle = false; this.handleLogoutClick = this.handleLogoutClick.bind(this); + this.handleAboutModal = this.handleAboutModal.bind(this); this.onListenerChange = this.onListenerChange.bind(this); + this.aboutModalDismissed = this.aboutModalDismissed.bind(this); this.state = getStateFromStores(); } @@ -26,6 +30,12 @@ export default class NavbarDropdown extends React.Component { e.preventDefault(); client.logout(); } + handleAboutModal() { + this.setState({showAboutModal: true}); + } + aboutModalDismissed() { + this.setState({showAboutModal: false}); + } componentDidMount() { UserStore.addTeamsChangeListener(this.onListenerChange); TeamStore.addChangeListener(this.onListenerChange); @@ -135,30 +145,35 @@ export default class NavbarDropdown extends React.Component { var teams = []; - teams.push( - <li - className='divider' - key='div' - > - </li> - ); - if (this.state.teams.length > 1) { + teams.push( + <li + className='divider' + key='div' + > + </li> + ); + this.state.teams.forEach((teamName) => { if (teamName !== this.props.teamName) { teams.push(<li key={teamName}><a href={Utils.getWindowLocationOrigin() + '/' + teamName}>{'Switch to ' + teamName}</a></li>); } }); } - teams.push(<li key='newTeam_li'> - <a - key='newTeam_a' - target='_blank' - href={Utils.getWindowLocationOrigin() + '/signup_team'} - > - {'Create a New Team'} - </a> - </li>); + + if (global.window.config.EnableTeamCreation === 'true') { + teams.push( + <li key='newTeam_li'> + <a + key='newTeam_a' + target='_blank' + href={Utils.getWindowLocationOrigin() + '/signup_team'} + > + {'Create a New Team'} + </a> + </li> + ); + } return ( <ul className='nav navbar-nav navbar-right'> @@ -223,6 +238,18 @@ export default class NavbarDropdown extends React.Component { {'Report a Problem'} </a> </li> + <li> + <a + href='#' + onClick={this.handleAboutModal} + > + {'About Mattermost'} + </a> + </li> + <AboutBuildModal + show={this.state.showAboutModal} + onModalDismissed={this.aboutModalDismissed} + /> </ul> </li> </ul> diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx index 95a88c3d6..aaaea3c6f 100644 --- a/web/react/components/popover_list_members.jsx +++ b/web/react/components/popover_list_members.jsx @@ -67,9 +67,6 @@ export default class PopoverListMembers extends React.Component { > <div id='member_tooltip' - data-placement='left' - data-toggle='tooltip' - title='View Channel Members' > {countText} <span diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index 6cfd243de..1d94cab47 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -215,7 +215,7 @@ export default class PostBody extends React.Component { comment = ( <p className='post-link'> <span> - {'Commented on '}{name}{apostrophe}{' message:'} + {'Commented on '}{name}{apostrophe}{' message: '} <a className='theme' onClick={this.props.handleCommentClick} diff --git a/web/react/components/post_deleted_modal.jsx b/web/react/components/post_deleted_modal.jsx index d284a9d1b..3f487d20f 100644 --- a/web/react/components/post_deleted_modal.jsx +++ b/web/react/components/post_deleted_modal.jsx @@ -2,13 +2,41 @@ // See License.txt for license information. var UserStore = require('../stores/user_store.jsx'); +var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); +var Constants = require('../utils/constants.jsx'); +var ActionTypes = Constants.ActionTypes; export default class PostDeletedModal extends React.Component { constructor(props) { super(props); + this.handleClose = this.handleClose.bind(this); + this.state = {}; } + componentDidMount() { + $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', () => { + this.handleClose(); + }); + } + handleClose() { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_SEARCH, + results: null + }); + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_SEARCH_TERM, + term: null, + do_search: false, + is_mention_search: false + }); + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_POST_SELECTED, + results: null + }); + } render() { var currentUser = UserStore.getCurrentUser(); @@ -31,17 +59,17 @@ export default class PostDeletedModal extends React.Component { data-dismiss='modal' aria-label='Close' > - <span aria-hidden='true'>×</span> + <span aria-hidden='true'>{'×'}</span> </button> <h4 className='modal-title' id='myModalLabel' > - Comment could not be posted + {'Comment could not be posted'} </h4> </div> <div className='modal-body'> - <p>Someone deleted the message on which you tried to post a comment.</p> + <p>{'Someone deleted the message on which you tried to post a comment.'}</p> </div> <div className='modal-footer'> <button @@ -49,7 +77,7 @@ export default class PostDeletedModal extends React.Component { className='btn btn-primary' data-dismiss='modal' > - Okay + {'Okay'} </button> </div> </div> diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx index dba75ac5f..c1e8979a4 100644 --- a/web/react/components/post_info.jsx +++ b/web/react/components/post_info.jsx @@ -13,12 +13,6 @@ export default class PostInfo extends React.Component { super(props); this.state = {}; } - shouldShowComment(state, type, isOwner) { - if (state === Constants.POST_FAILED || state === Constants.POST_LOADING) { - return false; - } - return isOwner || (this.props.allowReply === 'true' && type !== 'Comment'); - } createDropdown() { var post = this.props.post; var isOwner = UserStore.getCurrentId() === post.user_id; @@ -33,10 +27,6 @@ export default class PostInfo extends React.Component { type = 'Comment'; } - if (!this.shouldShowComment(post.state, type, isOwner)) { - return ''; - } - var dropdownContents = []; var dataComments = 0; if (type === 'Post') { @@ -106,6 +96,10 @@ export default class PostInfo extends React.Component { ); } + if (dropdownContents.length === 0) { + return ''; + } + return ( <div> <a diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index b90197ac4..6741a9bdd 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -37,9 +37,11 @@ export default class PostList extends React.Component { this.deactivate = this.deactivate.bind(this); this.resize = this.resize.bind(this); - this.state = this.getStateFromStores(props.channelId); - this.state.numToDisplay = Constants.POST_CHUNK_SIZE; - this.state.isFirstLoadComplete = false; + const state = this.getStateFromStores(props.channelId); + state.numToDisplay = Constants.POST_CHUNK_SIZE; + state.isFirstLoadComplete = false; + + this.state = state; } getStateFromStores(id) { var postList = PostStore.getPosts(id); @@ -449,10 +451,10 @@ export default class PostList extends React.Component { } var createMessage; - if (creatorName !== '') { - createMessage = (<span>This is the start of the <strong>{uiName}</strong> {uiType}, created by <strong>{creatorName}</strong> on <strong>{utils.displayDate(channel.create_at)}</strong></span>); - } else { + if (creatorName === '') { createMessage = 'This is the start of the ' + uiName + ' ' + uiType + ', created on ' + utils.displayDate(channel.create_at) + '.'; + } else { + createMessage = (<span>This is the start of the <strong>{uiName}</strong> {uiType}, created by <strong>{creatorName}</strong> on <strong>{utils.displayDate(channel.create_at)}</strong></span>); } return ( diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx index 9d514c741..d60206ecf 100644 --- a/web/react/components/rename_channel_modal.jsx +++ b/web/react/components/rename_channel_modal.jsx @@ -15,6 +15,7 @@ export default class RenameChannelModal extends React.Component { this.onDisplayNameChange = this.onDisplayNameChange.bind(this); this.displayNameKeyUp = this.displayNameKeyUp.bind(this); this.handleClose = this.handleClose.bind(this); + this.handleShow = this.handleShow.bind(this); this.handleSubmit = this.handleSubmit.bind(this); this.state = { @@ -59,11 +60,11 @@ export default class RenameChannelModal extends React.Component { state.invalid = true; } else { let cleanedName = Utils.cleanUpUrlable(channel.name); - if (cleanedName !== channel.name) { + if (cleanedName === channel.name) { + state.nameError = ''; + } else { state.nameError = 'Must be lowercase alphanumeric characters'; state.invalid = true; - } else { - state.nameError = ''; } } @@ -103,7 +104,7 @@ export default class RenameChannelModal extends React.Component { this.setState({channelName: channelName}); } handleClose() { - this.state = { + this.setState({ displayName: '', channelName: '', channelId: '', @@ -111,13 +112,14 @@ export default class RenameChannelModal extends React.Component { nameError: '', displayNameError: '', invalid: false - }; + }); + } + handleShow(e) { + const button = $(e.relatedTarget); + this.setState({displayName: button.attr('data-display'), channelName: button.attr('data-name'), channelId: button.attr('data-channelid')}); } componentDidMount() { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) { - const button = $(e.relatedTarget); - this.setState({displayName: button.attr('data-display'), channelName: button.attr('data-name'), channelId: button.attr('data-channelid')}); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.handleShow); $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); } componentWillUnmount() { diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx index 5b4694eb1..aa355f8cc 100644 --- a/web/react/components/rhs_comment.jsx +++ b/web/react/components/rhs_comment.jsx @@ -70,19 +70,84 @@ export default class RhsComment extends React.Component { componentDidUpdate() { this.parseEmojis(); } - render() { + createDropdown() { var post = this.props.post; - var currentUserCss = ''; - if (UserStore.getCurrentId() === post.user_id) { - currentUserCss = 'current--user'; + if (post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING || post.state === Constants.POST_DELETED) { + return ''; } var isOwner = UserStore.getCurrentId() === post.user_id; + var isAdmin = Utils.isAdmin(UserStore.getCurrentUser().roles); + + var dropdownContents = []; + + if (isOwner) { + dropdownContents.push( + <li role='presentation'> + <a + href='#' + role='menuitem' + data-toggle='modal' + data-target='#edit_post' + data-title='Comment' + data-message={post.message} + data-postid={post.id} + data-channelid={post.channel_id} + > + Edit + </a> + </li> + ); + } - var type = 'Post'; - if (post.root_id.length > 0) { - type = 'Comment'; + if (isOwner || isAdmin) { + dropdownContents.push( + <li role='presentation'> + <a + href='#' + role='menuitem' + data-toggle='modal' + data-target='#delete_post' + data-title='Comment' + data-postid={post.id} + data-channelid={post.channel_id} + data-comments={0} + > + Delete + </a> + </li> + ); + } + + if (dropdownContents.length === 0) { + return ''; + } + + return ( + <div className='dropdown'> + <a + href='#' + className='dropdown-toggle theme' + type='button' + data-toggle='dropdown' + aria-expanded='false' + /> + <ul + className='dropdown-menu' + role='menu' + > + {dropdownContents} + </ul> + </div> + ); + } + render() { + var post = this.props.post; + + var currentUserCss = ''; + if (UserStore.getCurrentId() === post.user_id) { + currentUserCss = 'current--user'; } var timestamp = UserStore.getCurrentUser().update_at; @@ -110,53 +175,7 @@ export default class RhsComment extends React.Component { ); } - var ownerOptions; - if (isOwner && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) { - ownerOptions = ( - <div className='dropdown'> - <a - href='#' - className='dropdown-toggle theme' - type='button' - data-toggle='dropdown' - aria-expanded='false' - /> - <ul - className='dropdown-menu' - role='menu' - > - <li role='presentation'> - <a - href='#' - role='menuitem' - data-toggle='modal' - data-target='#edit_post' - data-title={type} - data-message={post.message} - data-postid={post.id} - data-channelid={post.channel_id} - > - Edit - </a> - </li> - <li role='presentation'> - <a - href='#' - role='menuitem' - data-toggle='modal' - data-target='#delete_post' - data-title={type} - data-postid={post.id} - data-channelid={post.channel_id} - data-comments={0} - > - Delete - </a> - </li> - </ul> - </div> - ); - } + var dropdown = this.createDropdown(); var fileAttachment; if (post.filenames && post.filenames.length > 0) { @@ -190,7 +209,7 @@ export default class RhsComment extends React.Component { </time> </li> <li className='post-header-col post-header__reply'> - {ownerOptions} + {dropdown} </li> </ul> <div className='post-body'> diff --git a/web/react/components/rhs_thread.jsx b/web/react/components/rhs_thread.jsx index 2f23d80d9..27a784701 100644 --- a/web/react/components/rhs_thread.jsx +++ b/web/react/components/rhs_thread.jsx @@ -23,7 +23,7 @@ export default class RhsThread extends React.Component { } getStateFromStores() { var postList = PostStore.getSelectedPost(); - if (!postList || postList.order.length < 1) { + if (!postList || postList.order.length < 1 || !postList.posts[postList.order[0]]) { return {postList: {}}; } @@ -49,7 +49,10 @@ export default class RhsThread extends React.Component { }.bind(this)); } componentDidUpdate() { - $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight); + if ($('.post-right__scroll')[0]) { + $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight); + } + $('.post-right__scroll').perfectScrollbar('update'); this.resize(); } @@ -67,7 +70,7 @@ export default class RhsThread extends React.Component { // if something was changed in the channel like adding a // comment or post then lets refresh the sidebar list var currentSelected = PostStore.getSelectedPost(); - if (!currentSelected || currentSelected.order.length === 0) { + if (!currentSelected || currentSelected.order.length === 0 || !currentSelected.posts[currentSelected.order[0]]) { return; } @@ -103,7 +106,7 @@ export default class RhsThread extends React.Component { render() { var postList = this.state.postList; - if (postList == null) { + if (postList == null || !postList.order) { return ( <div></div> ); diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index c0841a508..88eaed335 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -29,9 +29,11 @@ export default class Sidebar extends React.Component { this.updateUnreadIndicators = this.updateUnreadIndicators.bind(this); this.createChannelElement = this.createChannelElement.bind(this); - this.state = this.getStateFromStores(); - this.state.modal = ''; - this.state.loadingDMChannel = -1; + const state = this.getStateFromStores(); + state.modal = ''; + state.loadingDMChannel = -1; + + this.state = state; } getStateFromStores() { var members = ChannelStore.getAllMembers(); @@ -65,7 +67,18 @@ export default class Sidebar extends React.Component { var channel = ChannelStore.getByName(channelName); - if (channel != null) { + if (channel == null) { + var tempChannel = {}; + tempChannel.fake = true; + tempChannel.name = channelName; + tempChannel.display_name = teammate.username; + tempChannel.teammate_username = teammate.username; + tempChannel.status = UserStore.getStatus(teammate.id); + tempChannel.last_post_at = 0; + tempChannel.total_msg_count = 0; + tempChannel.type = 'D'; + readDirectChannels.push(tempChannel); + } else { channel.display_name = teammate.username; channel.teammate_username = teammate.username; @@ -80,17 +93,6 @@ export default class Sidebar extends React.Component { } else { readDirectChannels.push(channel); } - } else { - var tempChannel = {}; - tempChannel.fake = true; - tempChannel.name = channelName; - tempChannel.display_name = teammate.username; - tempChannel.teammate_username = teammate.username; - tempChannel.status = UserStore.getStatus(teammate.id); - tempChannel.last_post_at = 0; - tempChannel.total_msg_count = 0; - tempChannel.type = 'D'; - readDirectChannels.push(tempChannel); } } @@ -203,7 +205,7 @@ export default class Sidebar extends React.Component { const user = UserStore.getCurrentUser(); const member = ChannelStore.getMember(msg.channel_id); - var notifyLevel = member.notify_props.desktop; + var notifyLevel = member && member.notify_props ? member.notify_props.desktop : 'default'; if (notifyLevel === 'default') { notifyLevel = user.notify_props.desktop; } diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx index 708cd04cb..573515a46 100644 --- a/web/react/components/sidebar_right.jsx +++ b/web/react/components/sidebar_right.jsx @@ -30,11 +30,11 @@ export default class SidebarRight extends React.Component { PostStore.removeSelectedPostChangeListener(this.onSelectedChange); } componentDidUpdate() { - if (!this.plScrolledToBottom) { - $('.top-visible-post')[0].scrollIntoView(); - } else { + if (this.plScrolledToBottom) { var postHolder = $('.post-list-holder-by-time').not('.inactive'); postHolder.scrollTop(postHolder[0].scrollHeight); + } else { + $('.top-visible-post')[0].scrollIntoView(); } } onSelectedChange(fromSearch) { diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 495159efc..4e17c6d06 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -20,8 +20,6 @@ export default class SignupUserComplete extends React.Component { initialState.user = {}; initialState.user.team_id = this.props.teamId; initialState.user.email = this.props.email; - initialState.hash = this.props.hash; - initialState.data = this.props.data; initialState.original_email = this.props.email; } @@ -41,15 +39,13 @@ export default class SignupUserComplete extends React.Component { return; } - this.state.user.email = providedEmail; - - this.state.user.username = React.findDOMNode(this.refs.name).value.trim().toLowerCase(); - if (!this.state.user.username) { + const providedUsername = React.findDOMNode(this.refs.name).value.trim().toLowerCase(); + if (!providedUsername) { this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''}); return; } - var usernameError = Utils.isValidUsername(this.state.user.username); + const usernameError = Utils.isValidUsername(providedUsername); if (usernameError === 'Cannot use a reserved word as a username.') { this.setState({nameError: 'This username is reserved, please choose a new one.', emailError: '', passwordError: '', serverError: ''}); return; @@ -63,23 +59,35 @@ export default class SignupUserComplete extends React.Component { return; } - this.state.user.password = React.findDOMNode(this.refs.password).value.trim(); - if (!this.state.user.password || this.state.user.password .length < 5) { + const providedPassword = React.findDOMNode(this.refs.password).value.trim(); + if (!providedPassword || providedPassword.length < 5) { this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''}); return; } - this.setState({nameError: '', emailError: '', passwordError: '', serverError: ''}); + const user = { + team_id: this.props.teamId, + email: providedEmail, + username: providedUsername, + password: providedPassword, + allow_marketing: true + }; - this.state.user.allow_marketing = true; + this.setState({ + user, + nameError: '', + emailError: '', + passwordError: '', + serverError: '' + }); - client.createUser(this.state.user, this.state.data, this.state.hash, + client.createUser(user, this.props.data, this.props.hash, function createUserSuccess() { client.track('signup', 'signup_user_02_complete'); - client.loginByEmail(this.props.teamName, this.state.user.email, this.state.user.password, + client.loginByEmail(this.props.teamName, user.email, user.password, function emailLoginSuccess(data) { - UserStore.setLastEmail(this.state.user.email); + UserStore.setLastEmail(user.email); UserStore.setCurrentUser(data); if (this.props.hash > 0) { BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'})); @@ -88,7 +96,7 @@ export default class SignupUserComplete extends React.Component { }.bind(this), function emailLoginFailure(err) { if (err.message === 'Login failed because email address has not been verified') { - window.location.href = '/verify_email?email=' + encodeURIComponent(this.state.user.email) + '&teamname=' + encodeURIComponent(this.props.teamName); + window.location.href = '/verify_email?email=' + encodeURIComponent(user.email) + '&teamname=' + encodeURIComponent(this.props.teamName); } else { this.setState({serverError: err.message}); } diff --git a/web/react/components/team_signup_choose_auth.jsx b/web/react/components/team_signup_choose_auth.jsx index b8264b887..8cdeace03 100644 --- a/web/react/components/team_signup_choose_auth.jsx +++ b/web/react/components/team_signup_choose_auth.jsx @@ -52,7 +52,7 @@ export default class ChooseAuthPage extends React.Component { <div> {buttons} <div className='form-group margin--extra-2x'> - <span><a href='/find_team'>{'Find my team'}</a></span> + <span><a href='/find_team'>{'Find my teams'}</a></span> </div> </div> ); diff --git a/web/react/components/team_signup_email_item.jsx b/web/react/components/team_signup_email_item.jsx index 10bb2d69e..01330a46c 100644 --- a/web/react/components/team_signup_email_item.jsx +++ b/web/react/components/team_signup_email_item.jsx @@ -23,17 +23,14 @@ export default class TeamSignupEmailItem extends React.Component { } if (!Utils.isEmail(email)) { - this.state.emailError = 'Please enter a valid email address'; - this.setState(this.state); + this.setState({emailError: 'Please enter a valid email address'}); return false; } else if (email === teamEmail) { - this.state.emailError = 'Please use a different email than the one used at signup'; - this.setState(this.state); + this.setState({emailError: 'Please use a different email than the one used at signup'}); return false; } - this.state.emailError = ''; - this.setState(this.state); + this.setState({emailError: ''}); return true; } render() { diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/team_signup_send_invites_page.jsx index 524bd5b50..8d8fb92ff 100644 --- a/web/react/components/team_signup_send_invites_page.jsx +++ b/web/react/components/team_signup_send_invites_page.jsx @@ -36,10 +36,10 @@ export default class TeamSignupSendInvitesPage extends React.Component { var emails = []; for (var i = 0; i < this.props.state.invites.length; i++) { - if (!this.refs['email_' + i].validate(this.props.state.team.email)) { - valid = false; - } else { + if (this.refs['email_' + i].validate(this.props.state.team.email)) { emails.push(this.refs['email_' + i].getValue()); + } else { + valid = false; } } diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/team_signup_url_page.jsx index a3f89a217..a682bb49e 100644 --- a/web/react/components/team_signup_url_page.jsx +++ b/web/react/components/team_signup_url_page.jsx @@ -48,22 +48,20 @@ export default class TeamSignupUrlPage extends React.Component { } Client.findTeamByName(name, - function success(data) { - if (!data) { - this.props.state.wizard = 'send_invites'; - this.props.state.team.type = 'O'; - - this.props.state.team.name = name; - this.props.updateParent(this.props.state); - } else { - this.state.nameError = 'This URL is unavailable. Please try another.'; - this.setState(this.state); - } - }.bind(this), - function error(err) { - this.state.nameError = err.message; - this.setState(this.state); - }.bind(this) + (data) => { + if (data) { + this.setState({nameError: 'This URL is unavailable. Please try another.'}); + } else { + this.props.state.wizard = 'send_invites'; + this.props.state.team.type = 'O'; + + this.props.state.team.name = name; + this.props.updateParent(this.props.state); + } + }, + (err) => { + this.setState({nameError: err.message}); + } ); } handleFocus(e) { diff --git a/web/react/components/team_signup_welcome_page.jsx b/web/react/components/team_signup_welcome_page.jsx index 626c6a17b..019456c9f 100644 --- a/web/react/components/team_signup_welcome_page.jsx +++ b/web/react/components/team_signup_welcome_page.jsx @@ -59,8 +59,7 @@ export default class TeamSignupWelcomePage extends React.Component { } }.bind(this), function error(err) { - this.state.serverError = err.message; - this.setState(this.state); + this.setState({serverError: err.message}); }.bind(this) ); } diff --git a/web/react/components/team_signup_with_email.jsx b/web/react/components/team_signup_with_email.jsx index 4fb1c0d01..f27def191 100644 --- a/web/react/components/team_signup_with_email.jsx +++ b/web/react/components/team_signup_with_email.jsx @@ -69,7 +69,7 @@ export default class EmailSignUpPage extends React.Component { </button> </div> <div className='form-group margin--extra-2x'> - <span><a href='/find_team'>{`Find my team`}</a></span> + <span><a href='/find_team'>{`Find my teams`}</a></span> </div> </form> ); diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx index 14f281f7a..5267f44b6 100644 --- a/web/react/components/team_signup_with_sso.jsx +++ b/web/react/components/team_signup_with_sso.jsx @@ -112,7 +112,7 @@ export default class SSOSignUpPage extends React.Component { {serverError} </div> <div className='form-group margin--extra-2x'> - <span><a href='/find_team'>{'Find my team'}</a></span> + <span><a href='/find_team'>{'Find my teams'}</a></span> </div> </form> ); diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx index 0f5cb59f5..3301c6596 100644 --- a/web/react/components/user_settings/import_theme_modal.jsx +++ b/web/react/components/user_settings/import_theme_modal.jsx @@ -49,7 +49,6 @@ export default class ImportThemeModal extends React.Component { theme.sidebarText = colors[5]; theme.sidebarUnreadText = colors[5]; theme.sidebarTextHoverBg = colors[4]; - theme.sidebarTextHoverColor = colors[5]; theme.sidebarTextActiveBg = colors[2]; theme.sidebarTextActiveColor = colors[3]; theme.sidebarHeaderBg = colors[1]; @@ -150,7 +149,9 @@ export default class ImportThemeModal extends React.Component { className='form-control' onChange={this.handleChange} /> - {this.state.inputError} + <div className='input__help'> + {this.state.inputError} + </div> </div> </div> </Modal.Body> diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx index c4a137ed8..be6cf1f42 100644 --- a/web/react/components/user_settings/user_settings_appearance.jsx +++ b/web/react/components/user_settings/user_settings_appearance.jsx @@ -214,14 +214,14 @@ export default class UserSettingsAppearance extends React.Component { <div className='divider-dark first'/> {themeUI} <div className='divider-dark'/> + <br/> + <a + className='theme' + onClick={this.handleImportModal} + > + {'Import theme colors from Slack'} + </a> </div> - <br/> - <a - className='theme' - onClick={this.handleImportModal} - > - {'Import theme colors from Slack'} - </a> </div> ); } diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx index b59c08af0..4ff4775a7 100644 --- a/web/react/components/user_settings/user_settings_security.jsx +++ b/web/react/components/user_settings/user_settings_security.jsx @@ -251,17 +251,6 @@ export default class SecurityTab extends React.Component { <div className='divider-dark first'/> {passwordSection} <div className='divider-dark'/> - <ul - className='section-min' - > - <li className='col-sm-10 section-title'>{'Version ' + global.window.config.Version}</li> - <li className='col-sm-7 section-describe'> - <div className='text-nowrap'>{'Build Number: ' + global.window.config.BuildNumber}</div> - <div className='text-nowrap'>{'Build Date: ' + global.window.config.BuildDate}</div> - <div className='text-nowrap'>{'Build Hash: ' + global.window.config.BuildHash}</div> - </li> - </ul> - <div className='divider-dark'/> <br></br> <a data-toggle='modal' diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index a7fecb689..fe34034dc 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -350,7 +350,7 @@ export default class ViewImageModal extends React.Component { totalFiles={this.props.filenames.length} filename={name} fileURL={fileUrl} - onGetPublicLinkPressed={this.getPublicLink} + getPublicLink={this.getPublicLink} /> </div> {leftArrow} diff --git a/web/react/components/view_image_popover_bar.jsx b/web/react/components/view_image_popover_bar.jsx index 68817d751..132212afb 100644 --- a/web/react/components/view_image_popover_bar.jsx +++ b/web/react/components/view_image_popover_bar.jsx @@ -14,7 +14,7 @@ export default class ViewImagePopoverBar extends React.Component { href='#' className='public-link text' data-title='Public Image' - onClick={this.getPublicLink} + onClick={this.props.getPublicLink} > {'Get Public Link'} </a> @@ -62,5 +62,5 @@ ViewImagePopoverBar.propTypes = { totalFiles: React.PropTypes.number.isRequired, filename: React.PropTypes.string.isRequired, fileURL: React.PropTypes.string.isRequired, - onGetPublicLinkPressed: React.PropTypes.func.isRequired + getPublicLink: React.PropTypes.func.isRequired }; diff --git a/web/react/package.json b/web/react/package.json index a9eba6c6c..e6a662375 100644 --- a/web/react/package.json +++ b/web/react/package.json @@ -5,27 +5,25 @@ "dependencies": { "autolinker": "0.18.1", "babel-runtime": "5.8.24", - "bootstrap-colorpicker": "2.2.0", "flux": "2.1.1", "keymirror": "0.1.1", "marked": "0.3.5", "object-assign": "3.0.0", - "react-zeroclipboard-mixin": "0.1.0", "twemoji": "1.4.1" }, "devDependencies": { - "browserify": "11.0.1", - "envify": "3.4.0", - "babelify": "6.1.3", + "browserify": "11.2.0", + "babelify": "6.3.0", "uglify-js": "2.4.24", - "watchify": "3.3.1", - "eslint": "1.3.1", - "eslint-plugin-react": "3.3.1" + "watchify": "3.4.0", + "eslint": "1.6.0", + "eslint-plugin-react": "3.5.1" }, "scripts": { - "start": "watchify --extension=jsx -o ../static/js/bundle.js -v -d ./**/*.jsx", - "build": "NODE_ENV=production browserify ./**/*.jsx | uglifyjs -c -m --screw-ie8 > ../static/js/bundle.min.js", - "test": "jest" + "check": "", + "build-libs": "browserify -r crypto -r autolinker -r flux -r keymirror -r marked -r object-assign -r twemoji | uglifyjs -c -m --screw-ie8 > ../static/js/libs.min.js", + "start": "watchify --fast -x crypto -x node -x autolinker -x flux -x keymirror -x marked -x object-assign -x twemoji -o ../static/js/bundle.js -v -d ./**/*.jsx", + "build": "browserify -x crypto -x autolinker -x flux -x keymirror -x marked -x object-assign -x twemoji ./**/*.jsx | uglifyjs -c -m --screw-ie8 > ../static/js/bundle.min.js" }, "browserify": { "transform": [ @@ -36,11 +34,7 @@ "runtime" ] } - ], - "envify" + ] ] - }, - "jest": { - "rootDir": "." } } diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx index d2dedb271..27a74fb2b 100644 --- a/web/react/stores/browser_store.jsx +++ b/web/react/stores/browser_store.jsx @@ -4,7 +4,7 @@ var UserStore; function getPrefix() { if (!UserStore) { - UserStore = require('./user_store.jsx'); + UserStore = require('./user_store.jsx'); //eslint-disable-line global-require } return UserStore.getCurrentId() + '_'; } @@ -41,7 +41,13 @@ class BrowserStoreClass { } setGlobalItem(name, value) { - localStorage.setItem(name, JSON.stringify(value)); + try { + localStorage.setItem(name, JSON.stringify(value)); + } catch (err) { + console.log('An error occurred while setting local storage, clearing all props'); //eslint-disable-line no-console + localStorage.clear(); + window.location.href = window.location.href; + } } getGlobalItem(name, defaultValue) { diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx index 1d853f979..9f354965e 100644 --- a/web/react/stores/socket_store.jsx +++ b/web/react/stores/socket_store.jsx @@ -50,8 +50,10 @@ class SocketStoreClass extends EventEmitter { } this.failCount = 0; - ErrorStore.storeLastError(null); - ErrorStore.emitChange(); + if (ErrorStore.getLastError()) { + ErrorStore.storeLastError(null); + ErrorStore.emitChange(); + } }; conn.onclose = () => { diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx index 1f33fe03b..fd9117747 100644 --- a/web/react/stores/team_store.jsx +++ b/web/react/stores/team_store.jsx @@ -10,12 +10,12 @@ var BrowserStore = require('../stores/browser_store.jsx'); var CHANGE_EVENT = 'change'; -var utils; +var Utils; function getWindowLocationOrigin() { - if (!utils) { - utils = require('../utils/utils.jsx'); + if (!Utils) { + Utils = require('../utils/utils.jsx'); //eslint-disable-line global-require } - return utils.getWindowLocationOrigin(); + return Utils.getWindowLocationOrigin(); } class TeamStoreClass extends EventEmitter { diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 5cb165b4c..6dccfcdeb 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -2,6 +2,7 @@ var BrowserStore = require('../stores/browser_store.jsx'); var TeamStore = require('../stores/team_store.jsx'); +var ErrorStore = require('../stores/error_store.jsx'); export function track(category, action, label, prop, val) { global.window.analytics.track(action, {category: category, label: label, property: prop, value: val}); @@ -15,7 +16,7 @@ function handleError(methodName, xhr, status, err) { var e = null; try { e = JSON.parse(xhr.responseText); - } catch(parseError) { + } catch (parseError) { e = null; } @@ -27,7 +28,16 @@ function handleError(methodName, xhr, status, err) { msg = 'error in ' + methodName + ' status=' + status + ' statusCode=' + xhr.status + ' err=' + err; if (xhr.status === 0) { - e = {message: 'There appears to be a problem with your internet connection', connErrorCount: 1}; + let errorCount = 1; + const oldError = ErrorStore.getLastError(); + let connectError = 'There appears to be a problem with your internet connection'; + + if (oldError && oldError.connErrorCount) { + errorCount += oldError.connErrorCount; + connectError = 'We cannot reach the Mattermost service. The service may be down or misconfigured. Please contact an administrator to make sure the WebSocket port is configured properly.'; + } + + e = {message: connectError, connErrorCount: errorCount}; } else { e = {message: 'We received an unexpected status code from the server (' + xhr.status + ')'}; } diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 67414dc3b..8fd0ab79b 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -122,10 +122,9 @@ module.exports = { default: { type: 'Mattermost', sidebarBg: '#fafafa', - sidebarText: '#999999', + sidebarText: '#333333', sidebarUnreadText: '#333333', sidebarTextHoverBg: '#e6f2fa', - sidebarTextHoverColor: '#999999', sidebarTextActiveBg: '#e1e1e1', sidebarTextActiveColor: '#111111', sidebarHeaderBg: '#2389d7', @@ -138,15 +137,16 @@ module.exports = { newMessageSeparator: '#FF8800', linkColor: '#2389d7', buttonBg: '#2389d7', - buttonColor: '#FFFFFF' + buttonColor: '#FFFFFF', + mentionHighlightBg: '#fff2bb', + mentionHighlightLink: '#2f81b7' }, organization: { type: 'Organization', sidebarBg: '#2071a7', - sidebarText: '#bfcde8', + sidebarText: '#fff', sidebarUnreadText: '#fff', sidebarTextHoverBg: '#136197', - sidebarTextHoverColor: '#bfcde8', sidebarTextActiveBg: '#136197', sidebarTextActiveColor: '#FFFFFF', sidebarHeaderBg: '#2f81b7', @@ -159,15 +159,16 @@ module.exports = { newMessageSeparator: '#FF8800', linkColor: '#2f81b7', buttonBg: '#1dacfc', - buttonColor: '#FFFFFF' + buttonColor: '#FFFFFF', + mentionHighlightBg: '#fff2bb', + mentionHighlightLink: '#2f81b7' }, mattermostDark: { type: 'Mattermost Dark', sidebarBg: '#1B2C3E', - sidebarText: '#bbbbbb', + sidebarText: '#fff', sidebarUnreadText: '#fff', sidebarTextHoverBg: '#4A5664', - sidebarTextHoverColor: '#bbbbbb', sidebarTextActiveBg: '#39769C', sidebarTextActiveColor: '#FFFFFF', sidebarHeaderBg: '#1B2C3E', @@ -180,15 +181,16 @@ module.exports = { newMessageSeparator: '#5de5da', linkColor: '#A4FFEB', buttonBg: '#4CBBA4', - buttonColor: '#FFFFFF' + buttonColor: '#FFFFFF', + mentionHighlightBg: '#984063', + mentionHighlightLink: '#A4FFEB' }, windows10: { type: 'Windows Dark', sidebarBg: '#171717', - sidebarText: '#eee', + sidebarText: '#fff', sidebarUnreadText: '#fff', sidebarTextHoverBg: '#302e30', - sidebarTextHoverColor: '#fff', sidebarTextActiveBg: '#484748', sidebarTextActiveColor: '#FFFFFF', sidebarHeaderBg: '#1f1f1f', @@ -201,7 +203,9 @@ module.exports = { newMessageSeparator: '#CC992D', linkColor: '#0177e7', buttonBg: '#0177e7', - buttonColor: '#FFFFFF' + buttonColor: '#FFFFFF', + mentionHighlightBg: '#784098', + mentionHighlightLink: '#A4FFEB' } }, THEME_ELEMENTS: [ @@ -230,10 +234,6 @@ module.exports = { uiName: 'Sidebar Text Hover BG' }, { - id: 'sidebarTextHoverColor', - uiName: 'Sidebar Text Hover Color' - }, - { id: 'sidebarTextActiveBg', uiName: 'Sidebar Text Active BG' }, @@ -263,7 +263,7 @@ module.exports = { }, { id: 'newMessageSeparator', - uiName: 'New message separator' + uiName: 'New Message Separator' }, { id: 'linkColor', @@ -273,10 +273,17 @@ module.exports = { id: 'buttonBg', uiName: 'Button BG' }, - { id: 'buttonColor', uiName: 'Button Text' + }, + { + id: 'mentionHighlightBg', + uiName: 'Mention Highlight BG' + }, + { + id: 'mentionHighlightLink', + uiName: 'Mention Highlight Link' } ] }; diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 1bc082175..f79f3492f 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -231,10 +231,10 @@ function testUrlMatch(text) { var matchText = match.getMatchedText(); linkData.text = matchText; - if (matchText.trim().indexOf('http') !== 0) { - linkData.link = 'http://' + matchText; - } else { + if (matchText.trim().indexOf('http') === 0) { linkData.link = matchText; + } else { + linkData.link = 'http://' + matchText; } result.push(linkData); @@ -399,9 +399,9 @@ export function applyTheme(theme) { } if (theme.sidebarText) { - changeCss('.sidebar--left .nav-pills__container li>a, .sidebar--right, .settings-modal .nav-pills>li a, .sidebar--menu', 'color:' + theme.sidebarText, 1); + changeCss('.sidebar--left .nav-pills__container li>a, .sidebar--right, .settings-modal .nav-pills>li a, .sidebar--menu', 'color:' + changeOpacity(theme.sidebarText, 0.6), 1); changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li>a', 'color:' + theme.sidebarText, 1); - changeCss('.sidebar--left .nav-pills__container li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.8), 1); + changeCss('.sidebar--left .nav-pills__container li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.6), 1); changeCss('.sidebar--left .add-channel-btn:hover, .sidebar--left .add-channel-btn:focus', 'color:' + theme.sidebarText, 1); changeCss('.sidebar--left, .sidebar--right .sidebar--right__header', 'border-color:' + changeOpacity(theme.sidebarText, 0.2), 1); changeCss('.sidebar--left .status path', 'fill:' + changeOpacity(theme.sidebarText, 0.5), 1); @@ -417,17 +417,13 @@ export function applyTheme(theme) { changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li:hover a', 'background:' + theme.sidebarTextHoverBg, 1); } - if (theme.sidebarTextHoverColor) { - changeCss('.sidebar--left .nav-pills__container li>a:hover, .sidebar--left .nav-pills__container li>a:focus, .settings-modal .nav-pills>li:hover a, .settings-modal .nav-pills>li:focus a', 'color:' + theme.sidebarTextHoverColor, 2); - changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li:hover a', 'color:' + theme.sidebarTextHoverColor, 2); - } - if (theme.sidebarTextActiveBg) { changeCss('.sidebar--left .nav-pills__container li.active a, .sidebar--left .nav-pills__container li.active a:hover, .sidebar--left .nav-pills__container li.active a:focus, .settings-modal .nav-pills>li.active a, .settings-modal .nav-pills>li.active a:hover, .settings-modal .nav-pills>li.active a:active', 'background:' + theme.sidebarTextActiveBg, 1); } if (theme.sidebarTextActiveColor) { changeCss('.sidebar--left .nav-pills__container li.active a, .sidebar--left .nav-pills__container li.active a:hover, .sidebar--left .nav-pills__container li.active a:focus, .settings-modal .nav-pills>li.active a, .settings-modal .nav-pills>li.active a:hover, .settings-modal .nav-pills>li.active a:active', 'color:' + theme.sidebarTextActiveColor, 2); + changeCss('.sidebar--left .nav-pills__container li.active a .status .online--icon', 'fill:' + theme.sidebarTextActiveColor, 2); } if (theme.sidebarHeaderBg) { @@ -468,10 +464,11 @@ export function applyTheme(theme) { changeCss('.date-separator .separator__text, .new-separator .separator__text', 'background:' + theme.centerChannelBg, 1); changeCss('.post-image__column .post-image__details', 'background:' + theme.centerChannelBg, 1); changeCss('.sidebar--right, .dropdown-menu, .popover', 'background:' + theme.centerChannelBg, 1); + changeCss('.search-bar__container .search__form .search-bar, .form-control', 'background:' + theme.centerChannelBg, 1); } if (theme.centerChannelColor) { - changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .loading-screen .loading__content .round, .command-name, .modal .modal-content, .dropdown-menu, .popover, .mentions-name', 'color:' + theme.centerChannelColor, 1); + changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .command-name, .modal .modal-content, .dropdown-menu, .popover, .mentions-name', 'color:' + theme.centerChannelColor, 1); changeCss('#post-create', 'color:' + theme.centerChannelColor, 2); changeCss('.mentions--top, .command-box', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 3); changeCss('.mentions--top, .command-box', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 2); @@ -479,7 +476,7 @@ export function applyTheme(theme) { changeCss('.dropdown-menu, .popover ', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px', 3); changeCss('.dropdown-menu, .popover ', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px', 2); changeCss('.dropdown-menu, .popover ', '-moz-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px', 1); - changeCss('.post-body hr', 'background:' + theme.centerChannelColor, 1); + changeCss('.post-body hr, .loading-screen .loading__content .round', 'background:' + theme.centerChannelColor, 1); changeCss('.channel-header .heading', 'color:' + theme.centerChannelColor, 1); changeCss('.markdown__table tbody tr:nth-child(2n)', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); changeCss('.channel-header__info>div.dropdown .header-dropdown__icon', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1); @@ -491,20 +488,21 @@ export function applyTheme(theme) { changeCss('.post-image__column', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 2); changeCss('.post-image__column .post-image__details', 'color:' + theme.centerChannelColor, 2); changeCss('.post-image__column a, .post-image__column a:hover, .post-image__column a:focus', 'color:' + theme.centerChannelColor, 1); - changeCss('.search-bar__container .search__form .search-bar', 'background: transparent; color:' + theme.centerChannelColor, 1); + changeCss('.search-bar__container .search__form .search-bar, .form-control', 'color:' + theme.centerChannelColor, 2); changeCss('@media(max-width: 768px){.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: inherit;', 1); - changeCss('.search-bar__container .search__form', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.input-group-addon, .search-bar__container .search__form, .form-control', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.form-control:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1); changeCss('.channel-intro .channel-intro__content', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1); changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2); changeCss('.date-separator .separator__hr, .modal-footer, .modal .custom-textarea, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); changeCss('.modal .custom-textarea:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1); changeCss('.channel-intro, .settings-modal .settings-table .settings-content .divider-dark, hr, .settings-modal .settings-table .settings-links', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); - changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment, .post.post--comment.other--root .post-comment, .post.same--root .post-body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); + changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment, .post.post--comment.other--root .post-comment, .post.same--root .post-body, .modal .more-channel-table tbody>tr td, .member-div:first-child, .member-div, .access-history__table .access__report, .activity-log__table', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.1), 2); changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); changeCss('@media(max-width: 1800px){.inner__wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); - changeCss('.post:hover, .sidebar--right .sidebar--right__header, .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); + changeCss('.post:hover, .modal .more-channel-table tbody>tr:hover td, .sidebar--right .sidebar--right__header, .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); changeCss('.date-separator.hovered--before:after, .date-separator.hovered--after:before, .new-separator.hovered--after:before, .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); changeCss('.command-name:hover, .mentions-name:hover, .mentions-focus, .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1); changeCss('.post.current--user:hover .post-body ', 'background: none;', 1); @@ -529,6 +527,14 @@ export function applyTheme(theme) { if (theme.buttonColor) { changeCss('.btn.btn-primary', 'color:' + theme.buttonColor, 2); } + + if (theme.mentionHighlightBg) { + changeCss('.mention-highlight, .search-highlight', 'background:' + theme.mentionHighlightBg, 1); + } + + if (theme.mentionHighlightLink) { + changeCss('.mention-highlight .mention-link', 'color:' + theme.mentionHighlightLink, 1); + } } export function changeCss(className, classValue, classRepeat) { // we need invisible container to store additional css definitions @@ -640,7 +646,7 @@ export function isValidUsername(name) { error = 'Must be between 3 and 15 characters'; } else if (!(/^[a-z0-9\.\-\_]+$/).test(name)) { error = "Must contain only letters, numbers, and the symbols '.', '-', and '_'."; - } else if (!(/[a-z]/).test(name.charAt(0))) { + } else if (!(/[a-z]/).test(name.charAt(0))) { //eslint-disable-line no-negated-condition error = 'First character must be a letter.'; } else { for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++) { |