diff options
Diffstat (limited to 'webapp/components')
25 files changed, 785 insertions, 475 deletions
diff --git a/webapp/components/access_history_modal.jsx b/webapp/components/access_history_modal.jsx index 94a10c97f..9c49c3879 100644 --- a/webapp/components/access_history_modal.jsx +++ b/webapp/components/access_history_modal.jsx @@ -2,7 +2,6 @@ // See License.txt for license information. import $ from 'jquery'; -import ReactDOM from 'react-dom'; import {Modal} from 'react-bootstrap'; import LoadingScreen from './loading_screen.jsx'; import AuditTable from './audit_table.jsx'; @@ -36,11 +35,8 @@ class AccessHistoryModal extends React.Component { } onShow() { AsyncClient.getAudits(); - - if ($(window).width() > 768) { - $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200); - } else { - $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 150); + if (!Utils.isMobile()) { + $('.modal-body').perfectScrollbar(); } } onHide() { diff --git a/webapp/components/activity_log_modal.jsx b/webapp/components/activity_log_modal.jsx index 9a4ff3ef2..f1dd4a26a 100644 --- a/webapp/components/activity_log_modal.jsx +++ b/webapp/components/activity_log_modal.jsx @@ -2,7 +2,6 @@ // See License.txt for license information. import $ from 'jquery'; -import ReactDOM from 'react-dom'; import UserStore from 'stores/user_store.jsx'; import * as Client from 'utils/client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; @@ -56,11 +55,8 @@ export default class ActivityLogModal extends React.Component { } onShow() { AsyncClient.getSessions(); - - if ($(window).width() > 768) { - $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200); - } else { - $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 150); + if (!Utils.isMobile()) { + $('.modal-body').perfectScrollbar(); } } onHide() { diff --git a/webapp/components/admin_console/admin_sidebar.jsx b/webapp/components/admin_console/admin_sidebar.jsx index 8ee75e2ef..9f9e85de1 100644 --- a/webapp/components/admin_console/admin_sidebar.jsx +++ b/webapp/components/admin_console/admin_sidebar.jsx @@ -1,8 +1,11 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import $ from 'jquery'; + import AdminSidebarHeader from './admin_sidebar_header.jsx'; import SelectTeamModal from './select_team_modal.jsx'; +import * as Utils from 'utils/utils.jsx'; import {FormattedMessage} from 'react-intl'; @@ -27,6 +30,12 @@ export default class AdminSidebar extends React.Component { }; } + componentDidUpdate() { + if (!Utils.isMobile()) { + $('.sidebar--left .nav-pills__container').perfectScrollbar(); + } + } + handleClick(name, teamId, e) { e.preventDefault(); this.props.selectTab(name, teamId); @@ -242,244 +251,242 @@ export default class AdminSidebar extends React.Component { return ( <div className='sidebar--left sidebar--collapsable'> - <div> - <AdminSidebarHeader/> - <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> - <FormattedMessage - id='admin.sidebar.reports' - defaultMessage='SITE REPORTS' - /> - </span> - </h4> - </li> - </ul> - <ul className='nav nav__sub-menu padded'> - <li> - <a - href='#' - className={this.isSelected('system_analytics')} - onClick={this.handleClick.bind(this, 'system_analytics', null)} - > - <FormattedMessage - id='admin.sidebar.view_statistics' - defaultMessage='View Statistics' - /> - </a> - </li> - </ul> - <ul className='nav nav__sub-menu'> - <li> - <h4> - <span className='icon fa fa-gear'></span> - <span> - <FormattedMessage - id='admin.sidebar.settings' - defaultMessage='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)} - > - <FormattedMessage - id='admin.sidebar.service' - defaultMessage='Service Settings' - /> - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('team_settings')} - onClick={this.handleClick.bind(this, 'team_settings', null)} - > - <FormattedMessage - id='admin.sidebar.team' - defaultMessage='Team Settings' - /> - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('sql_settings')} - onClick={this.handleClick.bind(this, 'sql_settings', null)} - > - <FormattedMessage - id='admin.sidebar.sql' - defaultMessage='SQL Settings' - /> - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('email_settings')} - onClick={this.handleClick.bind(this, 'email_settings', null)} - > - <FormattedMessage - id='admin.sidebar.email' - defaultMessage='Email Settings' - /> - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('image_settings')} - onClick={this.handleClick.bind(this, 'image_settings', null)} - > - <FormattedMessage - id='admin.sidebar.file' - defaultMessage='File Settings' - /> - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('log_settings')} - onClick={this.handleClick.bind(this, 'log_settings', null)} - > - <FormattedMessage - id='admin.sidebar.log' - defaultMessage='Log Settings' - /> - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('rate_settings')} - onClick={this.handleClick.bind(this, 'rate_settings', null)} - > - <FormattedMessage - id='admin.sidebar.rate_limit' - defaultMessage='Rate Limit Settings' - /> - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('privacy_settings')} - onClick={this.handleClick.bind(this, 'privacy_settings', null)} - > + <AdminSidebarHeader/> + <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> <FormattedMessage - id='admin.sidebar.privacy' - defaultMessage='Privacy Settings' + id='admin.sidebar.reports' + defaultMessage='SITE REPORTS' /> - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('gitlab_settings')} - onClick={this.handleClick.bind(this, 'gitlab_settings', null)} - > + </span> + </h4> + </li> + </ul> + <ul className='nav nav__sub-menu padded'> + <li> + <a + href='#' + className={this.isSelected('system_analytics')} + onClick={this.handleClick.bind(this, 'system_analytics', null)} + > + <FormattedMessage + id='admin.sidebar.view_statistics' + defaultMessage='View Statistics' + /> + </a> + </li> + </ul> + <ul className='nav nav__sub-menu'> + <li> + <h4> + <span className='icon fa fa-gear'></span> + <span> <FormattedMessage - id='admin.sidebar.gitlab' - defaultMessage='GitLab Settings' + id='admin.sidebar.settings' + defaultMessage='SETTINGS' /> - </a> - </li> - {ldapSettings} - {complianceSettings} - <li> - <a - href='#' - className={this.isSelected('legal_and_support_settings')} - onClick={this.handleClick.bind(this, 'legal_and_support_settings', null)} - > + </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)} + > + <FormattedMessage + id='admin.sidebar.service' + defaultMessage='Service Settings' + /> + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('team_settings')} + onClick={this.handleClick.bind(this, 'team_settings', null)} + > + <FormattedMessage + id='admin.sidebar.team' + defaultMessage='Team Settings' + /> + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('sql_settings')} + onClick={this.handleClick.bind(this, 'sql_settings', null)} + > + <FormattedMessage + id='admin.sidebar.sql' + defaultMessage='SQL Settings' + /> + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('email_settings')} + onClick={this.handleClick.bind(this, 'email_settings', null)} + > + <FormattedMessage + id='admin.sidebar.email' + defaultMessage='Email Settings' + /> + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('image_settings')} + onClick={this.handleClick.bind(this, 'image_settings', null)} + > + <FormattedMessage + id='admin.sidebar.file' + defaultMessage='File Settings' + /> + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('log_settings')} + onClick={this.handleClick.bind(this, 'log_settings', null)} + > + <FormattedMessage + id='admin.sidebar.log' + defaultMessage='Log Settings' + /> + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('rate_settings')} + onClick={this.handleClick.bind(this, 'rate_settings', null)} + > + <FormattedMessage + id='admin.sidebar.rate_limit' + defaultMessage='Rate Limit Settings' + /> + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('privacy_settings')} + onClick={this.handleClick.bind(this, 'privacy_settings', null)} + > + <FormattedMessage + id='admin.sidebar.privacy' + defaultMessage='Privacy Settings' + /> + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('gitlab_settings')} + onClick={this.handleClick.bind(this, 'gitlab_settings', null)} + > + <FormattedMessage + id='admin.sidebar.gitlab' + defaultMessage='GitLab Settings' + /> + </a> + </li> + {ldapSettings} + {complianceSettings} + <li> + <a + href='#' + className={this.isSelected('legal_and_support_settings')} + onClick={this.handleClick.bind(this, 'legal_and_support_settings', null)} + > + <FormattedMessage + id='admin.sidebar.support' + defaultMessage='Legal and Support Settings' + /> + </a> + </li> + </ul> + <ul className='nav nav__sub-menu'> + <li> + <h4> + <span className='icon fa fa-gear'></span> + <span> <FormattedMessage - id='admin.sidebar.support' - defaultMessage='Legal and Support Settings' + id='admin.sidebar.teams' + defaultMessage='TEAMS ({count})' + values={{ + count: count + }} /> - </a> - </li> - </ul> - <ul className='nav nav__sub-menu'> - <li> - <h4> - <span className='icon fa fa-gear'></span> - <span> - <FormattedMessage - id='admin.sidebar.teams' - defaultMessage='TEAMS ({count})' - values={{ - count: count - }} - /> - </span> - <span className='menu-icon--right'> - <OverlayTrigger - delayShow={1000} - placement='top' - overlay={addTeamTooltip} - > - <a - href='#' - onClick={this.showTeamSelect} - > - <i - className='fa fa-plus' - ></i> - </a> - </OverlayTrigger> - </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> - <FormattedMessage - id='admin.sidebar.other' - defaultMessage='OTHER' - /> - </span> - </h4> - </li> - </ul> - <ul className='nav nav__sub-menu padded'> - {licenseSettings} - {audits} - <li> - <a - href='#' - className={this.isSelected('logs')} - onClick={this.handleClick.bind(this, 'logs', null)} - > + </span> + <span className='menu-icon--right'> + <OverlayTrigger + delayShow={1000} + placement='top' + overlay={addTeamTooltip} + > + <a + href='#' + onClick={this.showTeamSelect} + > + <i + className='fa fa-plus' + ></i> + </a> + </OverlayTrigger> + </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> <FormattedMessage - id='admin.sidebar.logs' - defaultMessage='Logs' + id='admin.sidebar.other' + defaultMessage='OTHER' /> - </a> - </li> - </ul> - </li> - </ul> - </div> + </span> + </h4> + </li> + </ul> + <ul className='nav nav__sub-menu padded'> + {licenseSettings} + {audits} + <li> + <a + href='#' + className={this.isSelected('logs')} + onClick={this.handleClick.bind(this, 'logs', null)} + > + <FormattedMessage + id='admin.sidebar.logs' + defaultMessage='Logs' + /> + </a> + </li> + </ul> + </li> + </ul> </div> <SelectTeamModal diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx index 7cd713942..369fa2dbb 100644 --- a/webapp/components/channel_header.jsx +++ b/webapp/components/channel_header.jsx @@ -80,6 +80,7 @@ export default class ChannelHeader extends React.Component { SearchStore.addSearchChangeListener(this.onListenerChange); PreferenceStore.addChangeListener(this.onListenerChange); UserStore.addChangeListener(this.onListenerChange); + $('.sidebar--left .dropdown-menu').perfectScrollbar(); } componentWillUnmount() { ChannelStore.removeChangeListener(this.onListenerChange); diff --git a/webapp/components/claim/claim_account.jsx b/webapp/components/claim/claim.jsx index b6495e283..464187c37 100644 --- a/webapp/components/claim/claim_account.jsx +++ b/webapp/components/claim/claim.jsx @@ -1,16 +1,14 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import EmailToSSO from './email_to_sso.jsx'; -import SSOToEmail from './sso_to_email.jsx'; import TeamStore from 'stores/team_store.jsx'; +import React from 'react'; import {FormattedMessage} from 'react-intl'; -import React from 'react'; import logoImage from 'images/logo.png'; -export default class ClaimAccount extends React.Component { +export default class Claim extends React.Component { constructor(props) { super(props); @@ -39,7 +37,7 @@ export default class ClaimAccount extends React.Component { const team = TeamStore.getByName(this.state.teamName); let displayName = ''; if (team) { - displayName = team.displayName; + displayName = team.display_name; } this.setState({ teamDisplayName: displayName @@ -49,39 +47,6 @@ export default class ClaimAccount extends React.Component { this.updateStateFromStores(); } render() { - if (this.state.teamDisplayName === '') { - return (<div/>); - } - let content; - if (this.state.email === '') { - content = ( - <p> - <FormattedMessage - id='claim.account.noEmail' - defaultMessage='No email specified' - /> - </p> - ); - } else if (this.state.oldType === '' && this.state.newType !== '') { - content = ( - <EmailToSSO - email={this.state.email} - type={this.state.newType} - teamName={this.state.teamName} - teamDisplayName={this.state.teamDisplayName} - /> - ); - } else { - content = ( - <SSOToEmail - email={this.state.email} - currentType={this.state.oldType} - teamName={this.state.teamName} - teamDisplayName={this.state.teamDisplayName} - /> - ); - } - return ( <div> <div className='signup-header'> @@ -99,7 +64,13 @@ export default class ClaimAccount extends React.Component { src={logoImage} /> <div id='claim'> - {content} + {React.cloneElement(this.props.children, { + teamName: this.state.teamName, + teamDisplayName: this.state.teamDisplayName, + currentType: this.state.oldType, + newType: this.state.newType, + email: this.state.email + })} </div> </div> </div> @@ -108,9 +79,10 @@ export default class ClaimAccount extends React.Component { } } -ClaimAccount.defaultProps = { +Claim.defaultProps = { }; -ClaimAccount.propTypes = { +Claim.propTypes = { params: React.PropTypes.object.isRequired, - location: React.PropTypes.object.isRequired + location: React.PropTypes.object.isRequired, + children: React.PropTypes.node }; diff --git a/webapp/components/claim/components/email_to_ldap.jsx b/webapp/components/claim/components/email_to_ldap.jsx new file mode 100644 index 000000000..f3046fa74 --- /dev/null +++ b/webapp/components/claim/components/email_to_ldap.jsx @@ -0,0 +1,169 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Utils from 'utils/utils.jsx'; +import * as Client from 'utils/client.jsx'; + +import React from 'react'; +import ReactDOM from 'react-dom'; +import {FormattedMessage} from 'react-intl'; + +export default class EmailToLDAP extends React.Component { + constructor(props) { + super(props); + + this.submit = this.submit.bind(this); + + this.state = {}; + } + submit(e) { + e.preventDefault(); + var state = {}; + + const password = ReactDOM.findDOMNode(this.refs.password).value.trim(); + if (!password) { + state.error = Utils.localizeMessage('claim.email_to_ldap.pwdError', 'Please enter your password.'); + this.setState(state); + return; + } + + const ldapId = ReactDOM.findDOMNode(this.refs.ldapid).value.trim(); + if (!ldapId) { + state.error = Utils.localizeMessage('claim.email_to_ldap.ldapIdError', 'Please enter your LDAP ID.'); + this.setState(state); + return; + } + + const ldapPassword = ReactDOM.findDOMNode(this.refs.ldappassword).value.trim(); + if (!ldapPassword) { + state.error = Utils.localizeMessage('claim.email_to_ldap.ldapPasswordError', 'Please enter your LDAP password.'); + this.setState(state); + return; + } + + state.error = null; + this.setState(state); + + var postData = {}; + postData.email_password = password; + postData.ldap_id = ldapId; + postData.ldap_password = ldapPassword; + postData.email = this.props.email; + postData.team_name = this.props.teamName; + + Client.emailToLDAP(postData, + (data) => { + if (data.follow_link) { + window.location.href = data.follow_link; + } + }, + (error) => { + this.setState({error}); + } + ); + } + render() { + var error = null; + if (this.state.error) { + error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>; + } + + var formClass = 'form-group'; + if (error) { + formClass += ' has-error'; + } + + return ( + <div> + <h3> + <FormattedMessage + id='claim.email_to_ldap.title' + defaultMessage='Switch Email/Password Account to LDAP' + /> + </h3> + <form onSubmit={this.submit}> + <p> + <FormattedMessage + id='claim.email_to_ldap.ssoType' + defaultMessage='Upon claiming your account, you will only be able to login with LDAP' + /> + </p> + <p> + <FormattedMessage + id='claim.email_to_ldap.ssoNote' + defaultMessage='You must already have a valid LDAP account' + /> + </p> + <p> + <FormattedMessage + id='claim.email_to_ldap.enterPwd' + defaultMessage='Enter the password for your {team} {site} email account' + values={{ + team: this.props.teamDisplayName, + site: global.window.mm_config.SiteName + }} + /> + </p> + <div className={formClass}> + <input + type='password' + className='form-control' + name='password' + ref='password' + placeholder={Utils.localizeMessage('claim.email_to_ldap.pwd', 'Password')} + spellCheck='false' + /> + </div> + <p> + <FormattedMessage + id='claim.email_to_ldap.enterLdapPwd' + defaultMessage='Enter the ID and password for your LDAP account' + values={{ + team: this.props.teamDisplayName, + site: global.window.mm_config.SiteName + }} + /> + </p> + <div className={formClass}> + <input + type='text' + className='form-control' + name='ldapId' + ref='ldapid' + placeholder={Utils.localizeMessage('claim.email_to_ldap.ldapId', 'LDAP ID')} + spellCheck='false' + /> + </div> + <div className={formClass}> + <input + type='password' + className='form-control' + name='ldapPassword' + ref='ldappassword' + placeholder={Utils.localizeMessage('claim.email_to_ldap.ldapPwd', 'LDAP Password')} + spellCheck='false' + /> + </div> + {error} + <button + type='submit' + className='btn btn-primary' + > + <FormattedMessage + id='claim.email_to_ldap.switchTo' + defaultMessage='Switch account to LDAP' + /> + </button> + </form> + </div> + ); + } +} + +EmailToLDAP.defaultProps = { +}; +EmailToLDAP.propTypes = { + email: React.PropTypes.string, + teamName: React.PropTypes.string, + teamDisplayName: React.PropTypes.string +}; diff --git a/webapp/components/claim/email_to_sso.jsx b/webapp/components/claim/components/email_to_oauth.jsx index d09449247..f3e370a4a 100644 --- a/webapp/components/claim/email_to_sso.jsx +++ b/webapp/components/claim/components/email_to_oauth.jsx @@ -1,26 +1,14 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; import * as Client from 'utils/client.jsx'; -import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl'; - -const holders = defineMessages({ - pwdError: { - id: 'claim.email_to_sso.pwdError', - defaultMessage: 'Please enter your password.' - }, - pwd: { - id: 'claim.email_to_sso.pwd', - defaultMessage: 'Password' - } -}); - import React from 'react'; +import ReactDOM from 'react-dom'; +import {FormattedMessage} from 'react-intl'; -class EmailToSSO extends React.Component { +export default class EmailToOAuth extends React.Component { constructor(props) { super(props); @@ -34,7 +22,7 @@ class EmailToSSO extends React.Component { var password = ReactDOM.findDOMNode(this.refs.password).value.trim(); if (!password) { - state.error = this.props.intl.formatMessage(holders.pwdError); + state.error = Utils.localizeMessage('claim.email_to_oauth.pwdError', 'Please enter your password.'); this.setState(state); return; } @@ -46,9 +34,9 @@ class EmailToSSO extends React.Component { postData.password = password; postData.email = this.props.email; postData.team_name = this.props.teamName; - postData.service = this.props.type; + postData.service = this.props.newType; - Client.switchToSSO(postData, + Client.emailToOAuth(postData, (data) => { if (data.follow_link) { window.location.href = data.follow_link; @@ -70,41 +58,41 @@ class EmailToSSO extends React.Component { formClass += ' has-error'; } - const uiType = Utils.toTitleCase(this.props.type) + ' SSO'; + const uiType = Utils.toTitleCase(this.props.newType) + ' SSO'; return ( <div> <h3> <FormattedMessage - id='claim.email_to_sso.title' + id='claim.email_to_oauth.title' defaultMessage='Switch Email/Password Account to {uiType}' values={{ - uiType: uiType + uiType }} /> </h3> <form onSubmit={this.submit}> <p> <FormattedMessage - id='claim.email_to_sso.ssoType' + id='claim.email_to_oauth.ssoType' defaultMessage='Upon claiming your account, you will only be able to login with {type} SSO' values={{ - type: Utils.toTitleCase(this.props.type) + type: Utils.toTitleCase(this.props.newType) }} /> </p> <p> <FormattedMessage - id='claim.email_to_sso.ssoNote' + id='claim.email_to_oauth.ssoNote' defaultMessage='You must already have a valid {type} account' values={{ - type: Utils.toTitleCase(this.props.type) + type: Utils.toTitleCase(this.props.newType) }} /> </p> <p> <FormattedMessage - id='claim.email_to_sso.enterPwd' + id='claim.email_to_oauth.enterPwd' defaultMessage='Enter the password for your {team} {site} account' values={{ team: this.props.teamDisplayName, @@ -118,7 +106,7 @@ class EmailToSSO extends React.Component { className='form-control' name='password' ref='password' - placeholder={this.props.intl.formatMessage(holders.pwd)} + placeholder={Utils.localizeMessage('claim.email_to_oauth.pwd', 'Password')} spellCheck='false' /> </div> @@ -128,10 +116,10 @@ class EmailToSSO extends React.Component { className='btn btn-primary' > <FormattedMessage - id='claim.email_to_sso.switchTo' + id='claim.email_to_oauth.switchTo' defaultMessage='Switch account to {uiType}' values={{ - uiType: uiType + uiType }} /> </button> @@ -141,14 +129,11 @@ class EmailToSSO extends React.Component { } } -EmailToSSO.defaultProps = { +EmailToOAuth.defaultProps = { }; -EmailToSSO.propTypes = { - intl: intlShape.isRequired, - type: React.PropTypes.string.isRequired, - email: React.PropTypes.string.isRequired, - teamName: React.PropTypes.string.isRequired, - teamDisplayName: React.PropTypes.string.isRequired +EmailToOAuth.propTypes = { + newType: React.PropTypes.string, + email: React.PropTypes.string, + teamName: React.PropTypes.string, + teamDisplayName: React.PropTypes.string }; - -export default injectIntl(EmailToSSO); diff --git a/webapp/components/claim/components/ldap_to_email.jsx b/webapp/components/claim/components/ldap_to_email.jsx new file mode 100644 index 000000000..b4ffd4944 --- /dev/null +++ b/webapp/components/claim/components/ldap_to_email.jsx @@ -0,0 +1,167 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Utils from 'utils/utils.jsx'; +import * as Client from 'utils/client.jsx'; + +import React from 'react'; +import ReactDOM from 'react-dom'; +import {FormattedMessage} from 'react-intl'; + +export default class LDAPToEmail extends React.Component { + constructor(props) { + super(props); + + this.submit = this.submit.bind(this); + + this.state = {}; + } + submit(e) { + e.preventDefault(); + var state = {}; + + const password = ReactDOM.findDOMNode(this.refs.password).value.trim(); + if (!password) { + state.error = Utils.localizeMessage('claim.ldap_to_email.pwdError', 'Please enter your password.'); + this.setState(state); + return; + } + + const confirmPassword = ReactDOM.findDOMNode(this.refs.passwordconfirm).value.trim(); + if (!confirmPassword || password !== confirmPassword) { + state.error = Utils.localizeMessage('claim.ldap_to_email.pwdNotMatch', 'Passwords do not match.'); + this.setState(state); + return; + } + + const ldapPassword = ReactDOM.findDOMNode(this.refs.ldappassword).value.trim(); + if (!ldapPassword) { + state.error = Utils.localizeMessage('claim.ldap_to_email.ldapPasswordError', 'Please enter your LDAP password.'); + this.setState(state); + return; + } + + state.error = null; + this.setState(state); + + var postData = {}; + postData.email_password = password; + postData.ldap_password = ldapPassword; + postData.email = this.props.email; + postData.team_name = this.props.teamName; + + Client.ldapToEmail(postData, + (data) => { + if (data.follow_link) { + window.location.href = data.follow_link; + } + }, + (error) => { + this.setState({error}); + } + ); + } + render() { + var error = null; + if (this.state.error) { + error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>; + } + + var formClass = 'form-group'; + if (error) { + formClass += ' has-error'; + } + + return ( + <div> + <h3> + <FormattedMessage + id='claim.ldap_to_email.title' + defaultMessage='Switch LDAP Account to Email/Password' + /> + </h3> + <form onSubmit={this.submit}> + <p> + <FormattedMessage + id='claim.ldap_to_email.ssoType' + defaultMessage='Upon claiming your account, you will only be able to login with your email and password' + /> + </p> + <p> + <FormattedMessage + id='claim.ldap_to_email.email' + defaultMessage='You will use the email {email} to login' + values={{ + email: this.props.email + }} + /> + </p> + <p> + <FormattedMessage + id='claim.ldap_to_email.enterLdapPwd' + defaultMessage='Enter your LDAP password for your {team} {site} email account' + values={{ + team: this.props.teamDisplayName, + site: global.window.mm_config.SiteName + }} + /> + </p> + <div className={formClass}> + <input + type='password' + className='form-control' + name='ldapPassword' + ref='ldappassword' + placeholder={Utils.localizeMessage('claim.ldap_to_email.ldapPwd', 'LDAP Password')} + spellCheck='false' + /> + </div> + <p> + <FormattedMessage + id='claim.ldap_to_email.enterPwd' + defaultMessage='Enter a new password for your email account' + /> + </p> + <div className={formClass}> + <input + type='password' + className='form-control' + name='password' + ref='password' + placeholder={Utils.localizeMessage('claim.ldap_to_email.pwd', 'Password')} + spellCheck='false' + /> + </div> + <div className={formClass}> + <input + type='password' + className='form-control' + name='passwordconfirm' + ref='passwordconfirm' + placeholder={Utils.localizeMessage('claim.ldap_to_email.confirm', 'Confirm Password')} + spellCheck='false' + /> + </div> + {error} + <button + type='submit' + className='btn btn-primary' + > + <FormattedMessage + id='claim.ldap_to_email.switchTo' + defaultMessage='Switch account to email/password' + /> + </button> + </form> + </div> + ); + } +} + +LDAPToEmail.defaultProps = { +}; +LDAPToEmail.propTypes = { + email: React.PropTypes.string, + teamName: React.PropTypes.string, + teamDisplayName: React.PropTypes.string +}; diff --git a/webapp/components/claim/sso_to_email.jsx b/webapp/components/claim/components/oauth_to_email.jsx index a41e09969..476677aeb 100644 --- a/webapp/components/claim/sso_to_email.jsx +++ b/webapp/components/claim/components/oauth_to_email.jsx @@ -1,34 +1,14 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; import * as Client from 'utils/client.jsx'; -import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl'; - -const holders = defineMessages({ - enterPwd: { - id: 'claim.sso_to_email.enterPwd', - defaultMessage: 'Please enter a password.' - }, - pwdNotMatch: { - id: 'claim.sso_to_email.pwdNotMatch', - defaultMessage: 'Password do not match.' - }, - newPwd: { - id: 'claim.sso_to_email.newPwd', - defaultMessage: 'New Password' - }, - confirm: { - id: 'claim.sso_to_email.confirm', - defaultMessage: 'Confirm Password' - } -}); - import React from 'react'; +import ReactDOM from 'react-dom'; +import {FormattedMessage} from 'react-intl'; -class SSOToEmail extends React.Component { +export default class OAuthToEmail extends React.Component { constructor(props) { super(props); @@ -37,20 +17,19 @@ class SSOToEmail extends React.Component { this.state = {}; } submit(e) { - const {formatMessage} = this.props.intl; e.preventDefault(); const state = {}; const password = ReactDOM.findDOMNode(this.refs.password).value.trim(); if (!password) { - state.error = formatMessage(holders.enterPwd); + state.error = Utils.localizeMessage('claim.oauth_to_email.enterPwd', 'Please enter a password.'); this.setState(state); return; } const confirmPassword = ReactDOM.findDOMNode(this.refs.passwordconfirm).value.trim(); if (!confirmPassword || password !== confirmPassword) { - state.error = formatMessage(holders.pwdNotMatch); + state.error = Utils.localizeMessage('claim.oauth_to_email.pwdNotMatch', 'Password do not match.'); this.setState(state); return; } @@ -63,7 +42,7 @@ class SSOToEmail extends React.Component { postData.email = this.props.email; postData.team_name = this.props.teamName; - Client.switchToEmail(postData, + Client.oauthToEmail(postData, (data) => { if (data.follow_link) { window.location.href = data.follow_link; @@ -75,7 +54,6 @@ class SSOToEmail extends React.Component { ); } render() { - const {formatMessage} = this.props.intl; var error = null; if (this.state.error) { error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>; @@ -92,7 +70,7 @@ class SSOToEmail extends React.Component { <div> <h3> <FormattedMessage - id='claim.sso_to_email.title' + id='claim.oauth_to_email.title' defaultMessage='Switch {type} Account to Email' values={{ type: uiType @@ -102,13 +80,13 @@ class SSOToEmail extends React.Component { <form onSubmit={this.submit}> <p> <FormattedMessage - id='claim.sso_to_email.description' + id='claim.oauth_to_email.description' defaultMessage='Upon changing your account type, you will only be able to login with your email and password.' /> </p> <p> <FormattedMessage - id='claim.sso_to_email_newPwd' + id='claim.oauth_to_email_newPwd' defaultMessage='Enter a new password for your {team} {site} account' values={{ team: this.props.teamDisplayName, @@ -122,7 +100,7 @@ class SSOToEmail extends React.Component { className='form-control' name='password' ref='password' - placeholder={formatMessage(holders.newPwd)} + placeholder={Utils.localizeMessage('claim.oauth_to_email.newPwd', 'New Password')} spellCheck='false' /> </div> @@ -132,7 +110,7 @@ class SSOToEmail extends React.Component { className='form-control' name='passwordconfirm' ref='passwordconfirm' - placeholder={formatMessage(holders.confirm)} + placeholder={Utils.localizeMessage('claim.oauth_to_email.confirm', 'Confirm Password')} spellCheck='false' /> </div> @@ -142,7 +120,7 @@ class SSOToEmail extends React.Component { className='btn btn-primary' > <FormattedMessage - id='claim.sso_to_email.switchTo' + id='claim.oauth_to_email.switchTo' defaultMessage='Switch {type} to email and password' values={{ type: uiType @@ -155,14 +133,11 @@ class SSOToEmail extends React.Component { } } -SSOToEmail.defaultProps = { +OAuthToEmail.defaultProps = { }; -SSOToEmail.propTypes = { - intl: intlShape.isRequired, - currentType: React.PropTypes.string.isRequired, - email: React.PropTypes.string.isRequired, - teamName: React.PropTypes.string.isRequired, - teamDisplayName: React.PropTypes.string +OAuthToEmail.propTypes = { + teamName: React.PropTypes.string, + teamDisplayName: React.PropTypes.string, + currentType: React.PropTypes.string, + email: React.PropTypes.string }; - -export default injectIntl(SSOToEmail); diff --git a/webapp/components/edit_post_modal.jsx b/webapp/components/edit_post_modal.jsx index 0a55b2968..caf9a0ee5 100644 --- a/webapp/components/edit_post_modal.jsx +++ b/webapp/components/edit_post_modal.jsx @@ -27,8 +27,8 @@ const holders = defineMessages({ import React from 'react'; class EditPostModal extends React.Component { - constructor() { - super(); + constructor(props) { + super(props); this.handleEdit = this.handleEdit.bind(this); this.handleEditInput = this.handleEditInput.bind(this); @@ -56,12 +56,13 @@ class EditPostModal extends React.Component { updatedPost.id = this.state.post_id; updatedPost.channel_id = this.state.channel_id; - Client.updatePost(updatedPost, - function success() { + Client.updatePost( + updatedPost, + () => { AsyncClient.getPosts(updatedPost.channel_id); window.scrollTo(0, 0); }, - function error(err) { + (err) => { AsyncClient.dispatchError(err, 'updatePost'); } ); @@ -106,11 +107,11 @@ class EditPostModal extends React.Component { componentDidMount() { var self = this; - $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function onHidden() { + $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', () => { self.setState({editText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: ''}); }); - $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', function onShow(e) { + $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', (e) => { var button = e.relatedTarget; if (!button) { return; @@ -118,12 +119,11 @@ class EditPostModal extends React.Component { self.setState({editText: $(button).attr('data-message'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments'), refocusId: $(button).attr('data-refocusid')}); }); - $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', function onShown() { - self.refs.editbox.resize(); - $('#edit_textbox').get(0).focus(); + $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', () => { + self.refs.editbox.focus(); }); - $(ReactDOM.findDOMNode(this.refs.modal)).on('hide.bs.modal', function onShown() { + $(ReactDOM.findDOMNode(this.refs.modal)).on('hide.bs.modal', () => { if (self.state.refocusId !== '') { setTimeout(() => { $(self.state.refocusId).get(0).focus(); @@ -221,4 +221,4 @@ EditPostModal.propTypes = { intl: intlShape.isRequired }; -export default injectIntl(EditPostModal);
\ No newline at end of file +export default injectIntl(EditPostModal); diff --git a/webapp/components/invite_member_modal.jsx b/webapp/components/invite_member_modal.jsx index d567183ac..1f8fd6133 100644 --- a/webapp/components/invite_member_modal.jsx +++ b/webapp/components/invite_member_modal.jsx @@ -1,7 +1,6 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import $ from 'jquery'; import ReactDOM from 'react-dom'; import * as utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; @@ -176,12 +175,6 @@ class InviteMemberModal extends React.Component { }); } - componentDidUpdate(prevProps, prevState) { - if (!prevState.show && this.state.show) { - $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200); - } - } - addInviteFields() { var count = this.state.idCount + 1; var inviteIds = this.state.inviteIds; diff --git a/webapp/components/logged_in.jsx b/webapp/components/logged_in.jsx index 7ddb6b83d..c6f7b50b1 100644 --- a/webapp/components/logged_in.jsx +++ b/webapp/components/logged_in.jsx @@ -6,6 +6,7 @@ import * as AsyncClient from 'utils/async_client.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; import UserStore from 'stores/user_store.jsx'; import ChannelStore from 'stores/channel_store.jsx'; +import BrowserStore from 'stores/browser_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; @@ -84,7 +85,7 @@ export default class LoggedIn extends React.Component { // when one tab on a browser logs out, it sets __logout__ in localStorage to trigger other tabs to log out if (e.originalEvent.key === '__logout__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) { // make sure it isn't this tab that is sending the logout signal (only necessary for IE11) - if (window.BrowserStore.isSignallingLogout(e.originalEvent.newValue)) { + if (BrowserStore.isSignallingLogout(e.originalEvent.newValue)) { return; } @@ -94,7 +95,7 @@ export default class LoggedIn extends React.Component { if (e.originalEvent.key === '__login__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) { // make sure it isn't this tab that is sending the logout signal (only necessary for IE11) - if (window.BrowserStore.isSignallingLogin(e.originalEvent.newValue)) { + if (BrowserStore.isSignallingLogin(e.originalEvent.newValue)) { return; } diff --git a/webapp/components/popover_list_members.jsx b/webapp/components/popover_list_members.jsx index 7d048019c..819c7f590 100644 --- a/webapp/components/popover_list_members.jsx +++ b/webapp/components/popover_list_members.jsx @@ -1,6 +1,8 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import $ from 'jquery'; + import UserStore from 'stores/user_store.jsx'; import {Popover, Overlay} from 'react-bootstrap'; import * as Utils from 'utils/utils.jsx'; @@ -20,6 +22,10 @@ export default class PopoverListMembers extends React.Component { this.closePopover = this.closePopover.bind(this); } + componentDidUpdate() { + $('.member-list__popover .popover-content').perfectScrollbar(); + } + componentWillMount() { this.setState({showPopover: false}); } diff --git a/webapp/components/rhs_thread.jsx b/webapp/components/rhs_thread.jsx index 2760765eb..bbd9f8b28 100644 --- a/webapp/components/rhs_thread.jsx +++ b/webapp/components/rhs_thread.jsx @@ -46,11 +46,15 @@ export default class RhsThread extends React.Component { window.addEventListener('resize', this.handleResize); this.mounted = true; + if (!Utils.isMobile()) { + $('.sidebar--right .post-right__scroll').perfectScrollbar(); + } } componentDidUpdate() { if ($('.post-right__scroll')[0]) { $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight); } + $('.sidebar--right .post-right__scroll').perfectScrollbar('update'); this.resize(); } componentWillUnmount() { diff --git a/webapp/components/search_bar.jsx b/webapp/components/search_bar.jsx index c8dbb9d3f..caaf0f844 100644 --- a/webapp/components/search_bar.jsx +++ b/webapp/components/search_bar.jsx @@ -184,6 +184,7 @@ class SearchBar extends React.Component { onUserInput={this.handleUserInput} listComponent={SearchSuggestionList} providers={this.suggestionProviders} + type='search' /> {isSearching} <Popover diff --git a/webapp/components/search_results.jsx b/webapp/components/search_results.jsx index 7619e41cd..c5baf50ef 100644 --- a/webapp/components/search_results.jsx +++ b/webapp/components/search_results.jsx @@ -61,6 +61,9 @@ export default class SearchResults extends React.Component { UserStore.addChangeListener(this.onUserChange); this.resize(); window.addEventListener('resize', this.handleResize); + if (!Utils.isMobile()) { + $('.sidebar--right .search-items-container').perfectScrollbar(); + } } shouldComponentUpdate(nextProps, nextState) { diff --git a/webapp/components/sidebar.jsx b/webapp/components/sidebar.jsx index d8d825fd4..0e1b7dd0e 100644 --- a/webapp/components/sidebar.jsx +++ b/webapp/components/sidebar.jsx @@ -163,6 +163,9 @@ export default class Sidebar extends React.Component { componentDidUpdate() { this.updateTitle(); this.updateUnreadIndicators(); + if (!Utils.isMobile()) { + $('.sidebar--left .nav-pills__container').perfectScrollbar(); + } } componentWillUnmount() { window.removeEventListener('resize', this.handleResize); diff --git a/webapp/components/suggestion/suggestion_box.jsx b/webapp/components/suggestion/suggestion_box.jsx index e3ec63194..dbec024ac 100644 --- a/webapp/components/suggestion/suggestion_box.jsx +++ b/webapp/components/suggestion/suggestion_box.jsx @@ -3,11 +3,14 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; + import Constants from 'utils/constants.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; import SuggestionStore from 'stores/suggestion_store.jsx'; import * as Utils from 'utils/utils.jsx'; +import TextareaAutosize from 'react-textarea-autosize'; + const KeyCodes = Constants.KeyCodes; import React from 'react'; @@ -44,7 +47,13 @@ export default class SuggestionBox extends React.Component { getTextbox() { // this is to support old code that looks at the input/textarea DOM nodes - return ReactDOM.findDOMNode(this.refs.textbox); + let textbox = this.refs.textbox; + + if (!(textbox instanceof HTMLElement)) { + textbox = ReactDOM.findDOMNode(textbox); + } + + return textbox; } handleDocumentClick(e) { @@ -130,9 +139,18 @@ export default class SuggestionBox extends React.Component { {...newProps} /> ); + } else if (this.props.type === 'search') { + textbox = ( + <input + ref='textbox' + type='search' + {...newProps} + /> + ); } else if (this.props.type === 'textarea') { textbox = ( - <textarea + <TextareaAutosize + id={this.suggestionId} ref='textbox' {...newProps} /> @@ -156,12 +174,13 @@ SuggestionBox.defaultProps = { SuggestionBox.propTypes = { listComponent: React.PropTypes.func.isRequired, - type: React.PropTypes.oneOf(['input', 'textarea']).isRequired, + type: React.PropTypes.oneOf(['input', 'textarea', 'search']).isRequired, value: React.PropTypes.string.isRequired, onUserInput: React.PropTypes.func, providers: React.PropTypes.arrayOf(React.PropTypes.object), // explicitly name any input event handlers we override and need to manually call onChange: React.PropTypes.func, - onKeyDown: React.PropTypes.func + onKeyDown: React.PropTypes.func, + onHeightChange: React.PropTypes.func }; diff --git a/webapp/components/team_settings_modal.jsx b/webapp/components/team_settings_modal.jsx index 7dbbd680a..c19787993 100644 --- a/webapp/components/team_settings_modal.jsx +++ b/webapp/components/team_settings_modal.jsx @@ -5,6 +5,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import SettingsSidebar from './settings_sidebar.jsx'; import TeamSettings from './team_settings.jsx'; +import * as Utils from 'utils/utils.jsx'; import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl'; @@ -49,9 +50,16 @@ class TeamSettingsModal extends React.Component { $('.modal-dialog.display--content').removeClass('display--content'); }, 500); }); + + if (!Utils.isMobile()) { + $('.settings-modal .settings-content').perfectScrollbar(); + } } updateTab(tab) { this.setState({activeTab: tab, activeSection: ''}); + if (!Utils.isMobile()) { + $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update'); + } } updateSection(section) { this.setState({activeSection: section}); diff --git a/webapp/components/textbox.jsx b/webapp/components/textbox.jsx index 1a395072e..371c581e5 100644 --- a/webapp/components/textbox.jsx +++ b/webapp/components/textbox.jsx @@ -2,7 +2,6 @@ // See License.txt for license information. import $ from 'jquery'; -import ReactDOM from 'react-dom'; import AtMentionProvider from './suggestion/at_mention_provider.jsx'; import CommandProvider from './suggestion/command_provider.jsx'; import EmoticonProvider from './suggestion/emoticon_provider.jsx'; @@ -29,7 +28,7 @@ export default class Textbox extends React.Component { this.onRecievedError = this.onRecievedError.bind(this); this.handleKeyPress = this.handleKeyPress.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); - this.resize = this.resize.bind(this); + this.handleHeightChange = this.handleHeightChange.bind(this); this.showPreview = this.showPreview.bind(this); this.state = { @@ -54,8 +53,6 @@ export default class Textbox extends React.Component { componentDidMount() { ErrorStore.addChangeListener(this.onRecievedError); - - this.resize(); } componentWillUnmount() { @@ -72,10 +69,6 @@ export default class Textbox extends React.Component { } } - componentDidUpdate() { - this.resize(); - } - handleKeyPress(e) { this.props.onKeyPress(e); } @@ -86,50 +79,28 @@ export default class Textbox extends React.Component { } } - focus() { - this.refs.message.getTextbox().focus(); - } + handleHeightChange(height) { + const textbox = $(this.refs.message.getTextbox()); + const wrapper = $(this.refs.wrapper); - resize() { - const textbox = this.refs.message.getTextbox(); - const $textbox = $(textbox); - const $wrapper = $(ReactDOM.findDOMNode(this.refs.wrapper)); + const maxHeight = parseInt(textbox.css('max-height'), 10); - const padding = parseInt($textbox.css('padding-bottom'), 10) + parseInt($textbox.css('padding-top'), 10); - const borders = parseInt($textbox.css('border-bottom-width'), 10) + parseInt($textbox.css('border-top-width'), 10); - const maxHeight = parseInt($textbox.css('max-height'), 10) - borders; - - // set the height to auto and remove the scrollbar so we can get the actual size of the contents - $textbox.css('height', 'auto').css('overflow-y', 'hidden'); - - let height = textbox.scrollHeight - padding; - - if (height + padding > maxHeight) { - height = maxHeight - padding; - - // turn scrollbar on and move over attachment icon to compensate for that - $textbox.css('overflow-y', 'scroll'); - $wrapper.closest('.post-body__cell').addClass('scroll'); + // move over attachment icon to compensate for the scrollbar + if (height > maxHeight) { + wrapper.closest('.post-body__cell').addClass('scroll'); } else { - $wrapper.closest('.post-body__cell').removeClass('scroll'); + wrapper.closest('.post-body__cell').removeClass('scroll'); } + } - // set the textarea to be the proper height - $textbox.height(height); - - // set the wrapper height to match the height of the textbox including padding and borders - $wrapper.height(height + padding + borders); - - if (this.state.preview) { - $(ReactDOM.findDOMNode(this.refs.preview)).height(height + borders); - } + focus() { + this.refs.message.getTextbox().focus(); } showPreview(e) { e.preventDefault(); e.target.blur(); this.setState({preview: !this.state.preview}); - this.resize(); } render() { @@ -157,7 +128,7 @@ export default class Textbox extends React.Component { ); } - let helpText = ( + const helpText = ( <div style={{visibility: hasText ? 'visible' : 'hidden', opacity: hasText ? '0.5' : '0'}} className='help_format_text' @@ -174,12 +145,16 @@ export default class Textbox extends React.Component { defaultMessage='_italic_' /> </i> - <span>~~<strike> - <FormattedMessage - id='textbox.strike' - defaultMessage='strike' - /> - </strike>~~ </span> + <span> + {'~~'} + <strike> + <FormattedMessage + id='textbox.strike' + defaultMessage='strike' + /> + </strike> + {'~~ '} + </span> <code> <FormattedMessage id='textbox.inlinecode' @@ -214,16 +189,17 @@ export default class Textbox extends React.Component { spellCheck='true' autoComplete='off' autoCorrect='off' - rows='1' maxLength={Constants.MAX_POST_LEN} placeholder={this.props.createMessage} value={this.props.messageText} onUserInput={this.props.onUserInput} onKeyPress={this.handleKeyPress} onKeyDown={this.handleKeyDown} + onHeightChange={this.handleHeightChange} style={{visibility: this.state.preview ? 'hidden' : 'visible'}} listComponent={SuggestionList} providers={this.suggestionProviders} + channelId={this.props.channelId} /> <div ref='preview' diff --git a/webapp/components/tutorial/tutorial_intro_screens.jsx b/webapp/components/tutorial/tutorial_intro_screens.jsx index 734842cad..913a30483 100644 --- a/webapp/components/tutorial/tutorial_intro_screens.jsx +++ b/webapp/components/tutorial/tutorial_intro_screens.jsx @@ -36,12 +36,12 @@ export default class TutorialIntroScreens extends React.Component { Utils.switchChannel(ChannelStore.getByName(Constants.DEFAULT_CHANNEL)); - let step = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 0); + const step = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 0); AsyncClient.savePreference( Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), - step + 1 + (step + 1).toString() ); } skipTutorial(e) { @@ -50,7 +50,7 @@ export default class TutorialIntroScreens extends React.Component { AsyncClient.savePreference( Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), - 999 + '999' ); } createScreen() { diff --git a/webapp/components/tutorial/tutorial_tip.jsx b/webapp/components/tutorial/tutorial_tip.jsx index d93fff1b1..3508e29a2 100644 --- a/webapp/components/tutorial/tutorial_tip.jsx +++ b/webapp/components/tutorial/tutorial_tip.jsx @@ -29,12 +29,12 @@ export default class TutorialTip extends React.Component { this.setState({show}); if (!show && this.state.currentScreen >= this.props.screens.length - 1) { - let step = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 0); + const step = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 0); AsyncClient.savePreference( Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), - step + 1 + (step + 1).toString() ); } } @@ -48,8 +48,12 @@ export default class TutorialTip extends React.Component { } skipTutorial(e) { e.preventDefault(); - const preference = PreferenceStore.setPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), '999'); - AsyncClient.savePreferences([preference]); + + AsyncClient.savePreference( + Preferences.TUTORIAL_STEP, + UserStore.getCurrentId(), + '999' + ); } render() { const buttonText = this.state.currentScreen === this.props.screens.length - 1 ? ( diff --git a/webapp/components/user_list.jsx b/webapp/components/user_list.jsx index b5d42decc..3652723be 100644 --- a/webapp/components/user_list.jsx +++ b/webapp/components/user_list.jsx @@ -24,11 +24,16 @@ export default class UserList extends React.Component { }); } else { content = ( - <div key='no-users-found'> - <FormattedMessage - id='user_list.notFound' - defaultMessage='No users found :(' - /> + <div + key='no-users-found' + className='no-channel-message' + > + <p className='primary-message'> + <FormattedMessage + id='user_list.notFound' + defaultMessage='No users found :(' + /> + </p> </div> ); } diff --git a/webapp/components/user_settings/user_settings_modal.jsx b/webapp/components/user_settings/user_settings_modal.jsx index bd1df6ea5..d1c1f0fe2 100644 --- a/webapp/components/user_settings/user_settings_modal.jsx +++ b/webapp/components/user_settings/user_settings_modal.jsx @@ -64,7 +64,6 @@ class UserSettingsModal extends React.Component { constructor(props) { super(props); - this.handleShow = this.handleShow.bind(this); this.handleHide = this.handleHide.bind(this); this.handleHidden = this.handleHidden.bind(this); this.handleCollapse = this.handleCollapse.bind(this); @@ -95,24 +94,13 @@ class UserSettingsModal extends React.Component { } componentDidMount() { - if (this.props.show) { - this.handleShow(); - } UserStore.addChangeListener(this.onUserChanged); } - componentDidUpdate(prevProps) { - if (this.props.show && !prevProps.show) { - this.handleShow(); - } + componentDidUpdate() { UserStore.removeChangeListener(this.onUserChanged); - } - - handleShow() { - if ($(window).width() > 768) { - $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200); - } else { - $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 50); + if (!Utils.isMobile()) { + $('.settings-modal .modal-body').perfectScrollbar(); } } @@ -222,6 +210,10 @@ class UserSettingsModal extends React.Component { active_section: '' }); } + + if (!Utils.isMobile()) { + $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update'); + } } updateSection(section, skipConfirm) { diff --git a/webapp/components/user_settings/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security.jsx index e42de91ea..283d2c425 100644 --- a/webapp/components/user_settings/user_settings_security.jsx +++ b/webapp/components/user_settings/user_settings_security.jsx @@ -15,6 +15,7 @@ import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedTime, FormattedDate} from 'react-intl'; +import {Link} from 'react-router'; const holders = defineMessages({ currentPasswordError: { @@ -268,17 +269,24 @@ class SecurityTab extends React.Component { let emailOption; if (global.window.mm_config.EnableSignUpWithEmail === 'true' && user.auth_service !== '') { + let link; + if (user.auth_service === Constants.LDAP_SERVICE) { + link = '/' + teamName + '/claim/ldap_to_email?email=' + encodeURIComponent(user.email); + } else { + link = '/' + teamName + '/claim/oauth_to_email?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service; + } + emailOption = ( <div> - <a + <Link className='btn btn-primary' - href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service} + to={link} > <FormattedMessage id='user.settings.security.switchEmail' defaultMessage='Switch to using email and password' /> - </a> + </Link> <br/> </div> ); @@ -288,15 +296,15 @@ class SecurityTab extends React.Component { if (global.window.mm_config.EnableSignUpWithGitLab === 'true' && user.auth_service === '') { gitlabOption = ( <div> - <a + <Link className='btn btn-primary' - href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GITLAB_SERVICE} + to={'/' + teamName + '/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GITLAB_SERVICE} > <FormattedMessage id='user.settings.security.switchGitlab' defaultMessage='Switch to using GitLab SSO' /> - </a> + </Link> <br/> </div> ); @@ -306,15 +314,33 @@ class SecurityTab extends React.Component { if (global.window.mm_config.EnableSignUpWithGoogle === 'true' && user.auth_service === '') { googleOption = ( <div> - <a + <Link className='btn btn-primary' - href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GOOGLE_SERVICE} + to={'/' + teamName + '/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GOOGLE_SERVICE} > <FormattedMessage id='user.settings.security.switchGoogle' defaultMessage='Switch to using Google SSO' /> - </a> + </Link> + <br/> + </div> + ); + } + + let ldapOption; + if (global.window.mm_config.EnableLdap === 'true' && user.auth_service === '') { + ldapOption = ( + <div> + <Link + className='btn btn-primary' + to={'/' + teamName + '/claim/email_to_ldap?email=' + encodeURIComponent(user.email)} + > + <FormattedMessage + id='user.settings.security.switchLdap' + defaultMessage='Switch to using LDAP' + /> + </Link> <br/> </div> ); @@ -325,6 +351,7 @@ class SecurityTab extends React.Component { {emailOption} {gitlabOption} <br/> + {ldapOption} {googleOption} </div> ); |