diff options
Diffstat (limited to 'webapp')
47 files changed, 1104 insertions, 703 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> ); diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index d9411df07..00f4f333d 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -633,21 +633,44 @@ "choose_auth_page.ldapCreate": "Create new team with LDAP Account", "choose_auth_page.noSignup": "No sign-up methods configured, please contact your system administrator.", "claim.account.noEmail": "No email specified", - "claim.email_to_sso.enterPwd": "Enter the password for your {team} {site} account", - "claim.email_to_sso.pwd": "Password", - "claim.email_to_sso.pwdError": "Please enter your password.", - "claim.email_to_sso.ssoNote": "You must already have a valid {type} account", - "claim.email_to_sso.ssoType": "Upon claiming your account, you will only be able to login with {type} SSO", - "claim.email_to_sso.switchTo": "Switch account to {uiType}", - "claim.email_to_sso.title": "Switch Email/Password Account to {uiType}", - "claim.sso_to_email.confirm": "Confirm Password", - "claim.sso_to_email.description": "Upon changing your account type, you will only be able to login with your email and password.", - "claim.sso_to_email.enterPwd": "Please enter a password.", - "claim.sso_to_email.newPwd": "New Password", - "claim.sso_to_email.pwdNotMatch": "Password do not match.", - "claim.sso_to_email.switchTo": "Switch {type} to email and password", - "claim.sso_to_email.title": "Switch {type} Account to Email", - "claim.sso_to_email_newPwd": "Enter a new password for your {team} {site} account", + "claim.email_to_ldap.enterLdapPwd": "Enter the ID and password for your LDAP account", + "claim.email_to_ldap.enterPwd": "Enter the password for your {team} {site} email account", + "claim.email_to_ldap.ldapId": "LDAP ID", + "claim.email_to_ldap.ldapIdError": "Please enter your LDAP ID.", + "claim.email_to_ldap.ldapPasswordError": "Please enter your LDAP password.", + "claim.email_to_ldap.ldapPwd": "LDAP Password", + "claim.email_to_ldap.pwd": "Password", + "claim.email_to_ldap.pwdError": "Please enter your password.", + "claim.email_to_ldap.ssoNote": "You must already have a valid LDAP account", + "claim.email_to_ldap.ssoType": "Upon claiming your account, you will only be able to login with LDAP", + "claim.email_to_ldap.switchTo": "Switch account to LDAP", + "claim.email_to_ldap.title": "Switch Email/Password Account to LDAP", + "claim.email_to_oauth.enterPwd": "Enter the password for your {team} {site} account", + "claim.email_to_oauth.pwd": "Password", + "claim.email_to_oauth.pwdError": "Please enter your password.", + "claim.email_to_oauth.ssoNote": "You must already have a valid {type} account", + "claim.email_to_oauth.ssoType": "Upon claiming your account, you will only be able to login with {type} SSO", + "claim.email_to_oauth.switchTo": "Switch account to {uiType}", + "claim.email_to_oauth.title": "Switch Email/Password Account to {uiType}", + "claim.ldap_to_email.confirm": "Confirm Password", + "claim.ldap_to_email.email": "You will use the email {email} to login", + "claim.ldap_to_email.enterLdapPwd": "Enter your LDAP password for your {team} {site} email account", + "claim.ldap_to_email.enterPwd": "Enter a new password for your email account", + "claim.ldap_to_email.ldapPasswordError": "Please enter your LDAP password.", + "claim.ldap_to_email.ldapPwd": "LDAP Password", + "claim.ldap_to_email.pwd": "Password", + "claim.ldap_to_email.pwdError": "Please enter your password.", + "claim.ldap_to_email.pwdNotMatch": "Passwords do not match.", + "claim.ldap_to_email.ssoType": "Upon claiming your account, you will only be able to login with your email and password", + "claim.ldap_to_email.switchTo": "Switch account to email/password", + "claim.ldap_to_email.title": "Switch LDAP Account to Email/Password", + "claim.oauth_to_email.confirm": "Confirm Password", + "claim.oauth_to_email.description": "Upon changing your account type, you will only be able to login with your email and password.", + "claim.oauth_to_email.enterPwd": "Please enter a password.", + "claim.oauth_to_email.newPwd": "New Password", + "claim.oauth_to_email.pwdNotMatch": "Password do not match.", + "claim.oauth_to_email.switchTo": "Switch {type} to email and password", + "claim.oauth_to_email.title": "Switch {type} Account to Email", "confirm_modal.cancel": "Cancel", "create_comment.addComment": "Add a comment...", "create_comment.comment": "Add Comment", @@ -1335,6 +1358,7 @@ "user.settings.security.switchEmail": "Switch to using email and password", "user.settings.security.switchGitlab": "Switch to using GitLab SSO", "user.settings.security.switchGoogle": "Switch to using Google SSO", + "user.settings.security.switchLda": "Switch to using LDAP", "user.settings.security.title": "Security Settings", "user.settings.security.viewHistory": "View Access History", "user_list.notFound": "No users found :(", diff --git a/webapp/i18n/es.json b/webapp/i18n/es.json index 8852dd3c6..20b79fc84 100644 --- a/webapp/i18n/es.json +++ b/webapp/i18n/es.json @@ -633,21 +633,44 @@ "choose_auth_page.ldapCreate": "Crea un nuevo equipo con tu cuenta de LDAP", "choose_auth_page.noSignup": "No hay métodos de inicio de sesión configurad, por favor contacte al administrador de sistemasos", "claim.account.noEmail": "No se especifico un correo electrónico.", - "claim.email_to_sso.enterPwd": "Ingresa la contraseña para tu cuenta para {team} {site}", - "claim.email_to_sso.pwd": "Contraseña", - "claim.email_to_sso.pwdError": "Por favor introduce tu contraseña.", - "claim.email_to_sso.ssoNote": "Debes tener una cuenta válida con {type}", - "claim.email_to_sso.ssoType": "Al reclamar tu cuenta, sólo podrás iniciar sesión con {type} SSO", - "claim.email_to_sso.switchTo": "Cambiar cuenta a {uiType}", - "claim.email_to_sso.title": "Cambiar Cuenta de Correo/Contraseña a {uiType}", - "claim.sso_to_email.confirm": "Confirmar Contraseña", - "claim.sso_to_email.description": "Al cambiar el tipo de cuenta, sólo podrás iniciar sesión con tu correo electrónico y contraseña.", - "claim.sso_to_email.enterPwd": "Por favor ingresa una contraseña.", - "claim.sso_to_email.newPwd": "Nueva Contraseña", - "claim.sso_to_email.pwdNotMatch": "Las contraseñas no coinciden.", - "claim.sso_to_email.switchTo": "Cambiar {type} a correo electrónico y contraseña", - "claim.sso_to_email.title": "Cambiar la cuenta de {type} a Correo Electrónico", - "claim.sso_to_email_newPwd": "Ingresa una nueva contraseña para tu cuenta de {team} {site}", + "claim.email_to_ldap.enterLdapPwd": "Ingresa el ID y la contraseña de tu cuenta LDAP", + "claim.email_to_ldap.enterPwd": "Ingresa la contraseña para tu cuenta de correo en {team} {site}", + "claim.email_to_ldap.ldapId": "LDAP ID", + "claim.email_to_ldap.ldapIdError": "Por favor ingresa tu ID de LDAP.", + "claim.email_to_ldap.ldapPasswordError": "Por favor ingresa tu contraseña de LDAP.", + "claim.email_to_ldap.ldapPwd": "Contraseña de LDAP", + "claim.email_to_ldap.pwd": "Contraseña", + "claim.email_to_ldap.pwdError": "Por favor ingresa tu contraseña.", + "claim.email_to_ldap.ssoNote": "Debes tener una cuenta de LDAP válida", + "claim.email_to_ldap.ssoType": "Al reclamar tu cuenta, sólo podrás iniciar sesión con LDAP", + "claim.email_to_ldap.switchTo": "Cambiar cuenta a LDAP", + "claim.email_to_ldap.title": "Cambiar Cuenta de Correo/Contraseña a LDAP", + "claim.email_to_oauth.enterPwd": "Ingresa la contraseña para tu cuenta para {team} {site}", + "claim.email_to_oauth.pwd": "Contraseña", + "claim.email_to_oauth.pwdError": "Por favor introduce tu contraseña.", + "claim.email_to_oauth.ssoNote": "Debes tener una cuenta válida con {type}", + "claim.email_to_oauth.ssoType": "Al reclamar tu cuenta, sólo podrás iniciar sesión con {type} SSO", + "claim.email_to_oauth.switchTo": "Cambiar cuenta a {uiType}", + "claim.email_to_oauth.title": "Cambiar Cuenta de Correo/Contraseña a {uiType}", + "claim.ldap_to_email.confirm": "Confirmar Contraseña", + "claim.ldap_to_email.email": "Para iniciar sesión debes utilizar el correo electrónico {email}", + "claim.ldap_to_email.enterLdapPwd": "Ingresa tu contraseña de LDAP para tu cuenta de correo en {team} {site}", + "claim.ldap_to_email.enterPwd": "Ingresa una nueva contraseña para tu cuenta de correo", + "claim.ldap_to_email.ldapPasswordError": "Por favor ingresa tu contraseña LDAP.", + "claim.ldap_to_email.ldapPwd": "Contraseña LDAP", + "claim.ldap_to_email.pwd": "Contraseña", + "claim.ldap_to_email.pwdError": "Por favor ingresa tu contraseña.", + "claim.ldap_to_email.pwdNotMatch": "Las contraseñas no coinciden.", + "claim.ldap_to_email.ssoType": "Al cambiar el tipo de cuenta, sólo podrás iniciar sesión con tu correo electrónico y contraseña.", + "claim.ldap_to_email.switchTo": "Cambiar cuenta a correo/contraseña", + "claim.ldap_to_email.title": "Cambiar la cuenta de LDAP a Correo/Contraseña", + "claim.oauth_to_email.confirm": "Confirmar Contraseña", + "claim.oauth_to_email.description": "Al cambiar el tipo de cuenta, sólo podrás iniciar sesión con tu correo electrónico y contraseña.", + "claim.oauth_to_email.enterPwd": "Por favor ingresa una contraseña.", + "claim.oauth_to_email.newPwd": "Nueva Contraseña", + "claim.oauth_to_email.pwdNotMatch": "Las contraseñas no coinciden.", + "claim.oauth_to_email.switchTo": "Cambiar {type} a correo electrónico y contraseña", + "claim.oauth_to_email.title": "Cambiar la cuenta de {type} a Correo Electrónico", "confirm_modal.cancel": "Cancelar", "create_comment.addComment": "Agregar un comentario...", "create_comment.comment": "Agregar Comentario", @@ -1335,6 +1358,7 @@ "user.settings.security.switchEmail": "Cambiar para utilizar correo electrónico y contraseña", "user.settings.security.switchGitlab": "Cambiar para utilizar GitLab SSO", "user.settings.security.switchGoogle": "Cambiar para utilizar Google SSO", + "user.settings.security.switchLda": "Cambiar a utilizar LDAP", "user.settings.security.title": "Configuración de Seguridad", "user.settings.security.viewHistory": "Visualizar historial de acceso", "user_list.notFound": "No se encontraron usuarios :(", diff --git a/webapp/i18n/pt.json b/webapp/i18n/pt.json index 41d3bbc1c..0b06b77af 100644 --- a/webapp/i18n/pt.json +++ b/webapp/i18n/pt.json @@ -639,21 +639,21 @@ "choose_auth_page.ldapCreate": "Criar uma nova equipe com uma conta LDAP", "choose_auth_page.noSignup": "Nenhum método de inscrição configurado, por favor contate seu administrador do sistema.", "claim.account.noEmail": "Nenhum email específicado", - "claim.email_to_sso.enterPwd": "Entre a senha para o sua conta {team} {site}", - "claim.email_to_sso.pwd": "Senha", - "claim.email_to_sso.pwdError": "Por favor digite a sua senha.", - "claim.email_to_sso.ssoNote": "Você precisa já ter uma conta {type} válida", - "claim.email_to_sso.ssoType": "Ao retirar a sua conta, você só vai ser capaz de logar com SSO {type}", - "claim.email_to_sso.switchTo": "Trocar a conta para {uiType}", - "claim.email_to_sso.title": "Trocar E-mail/Senha da Conta para {uiType}", - "claim.sso_to_email.confirm": "Confirmar senha", - "claim.sso_to_email.description": "Após a alteração do tipo de conta, você só vai ser capaz de logar com seu e-mail e senha.", - "claim.sso_to_email.enterPwd": "Por favor entre uma senha.", - "claim.sso_to_email.newPwd": "Nova Senha", - "claim.sso_to_email.pwdNotMatch": "As senha não correspondem.", - "claim.sso_to_email.switchTo": "Trocar {type} para email e senha", - "claim.sso_to_email.title": "Trocar Conta {type} para E-mail", - "claim.sso_to_email_newPwd": "Entre a nova senha para o sua conta {team} {site}", + "claim.email_to_oauth.enterPwd": "Entre a senha para o sua conta {team} {site}", + "claim.email_to_oauth.pwd": "Senha", + "claim.email_to_oauth.pwdError": "Por favor digite a sua senha.", + "claim.email_to_oauth.ssoNote": "Você precisa já ter uma conta {type} válida", + "claim.email_to_oauth.ssoType": "Ao retirar a sua conta, você só vai ser capaz de logar com SSO {type}", + "claim.email_to_oauth.switchTo": "Trocar a conta para {uiType}", + "claim.email_to_oauth.title": "Trocar E-mail/Senha da Conta para {uiType}", + "claim.oauth_to_email.confirm": "Confirmar senha", + "claim.oauth_to_email.description": "Após a alteração do tipo de conta, você só vai ser capaz de logar com seu e-mail e senha.", + "claim.oauth_to_email.enterPwd": "Por favor entre uma senha.", + "claim.oauth_to_email.newPwd": "Nova Senha", + "claim.oauth_to_email.pwdNotMatch": "As senha não correspondem.", + "claim.oauth_to_email.switchTo": "Trocar {type} para email e senha", + "claim.oauth_to_email.title": "Trocar Conta {type} para E-mail", + "claim.oauth_to_email.newPwd": "Entre a nova senha para o sua conta {team} {site}", "confirm_modal.cancel": "Cancelar", "create_comment.addComment": "Adicionar um comentário...", "create_comment.comment": "Adicionar Comentário", diff --git a/webapp/package.json b/webapp/package.json index cdfba8ef0..6f50962a4 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -24,6 +24,7 @@ "react-dom": "0.14.7", "react-intl": "2.0.0-rc-1", "react-router": "2.0.1", + "react-textarea-autosize": "3.3.0", "twemoji": "1.4.1", "velocity-animate": "1.2.3" }, @@ -53,8 +54,8 @@ "webpack": "webpack/webpack#master" }, "scripts": { - "check": "eslint --ext \".jsx\" --ignore-pattern node_modules --quiet .", - "build": "webpack", - "run": "webpack --progress --watch" + "check": "eslint --ext \".jsx\" --ignore-pattern node_modules --quiet .", + "build": "webpack", + "run": "webpack --progress --watch" } } diff --git a/webapp/root.jsx b/webapp/root.jsx index 6ab5e80a0..f4d9a0e47 100644 --- a/webapp/root.jsx +++ b/webapp/root.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. import $ from 'jquery'; +require('perfect-scrollbar/jquery')($); import 'bootstrap/dist/css/bootstrap.css'; import 'jasny-bootstrap/dist/css/jasny-bootstrap.css'; @@ -35,7 +36,6 @@ import SignupUserComplete from 'components/signup_user_complete.jsx'; import ShouldVerifyEmail from 'components/should_verify_email.jsx'; import DoVerifyEmail from 'components/do_verify_email.jsx'; import AdminConsole from 'components/admin_console/admin_controller.jsx'; -import ClaimAccount from 'components/claim/claim_account.jsx'; import SignupTeamComplete from 'components/signup_team_complete/components/signup_team_complete.jsx'; import WelcomePage from 'components/signup_team_complete/components/team_signup_welcome_page.jsx'; @@ -46,6 +46,12 @@ import UsernamePage from 'components/signup_team_complete/components/team_signup import PasswordPage from 'components/signup_team_complete/components/team_signup_password_page.jsx'; import FinishedPage from 'components/signup_team_complete/components/team_signup_finished.jsx'; +import Claim from 'components/claim/claim.jsx'; +import EmailToOAuth from 'components/claim/components/email_to_oauth.jsx'; +import OAuthToEmail from 'components/claim/components/oauth_to_email.jsx'; +import LDAPToEmail from 'components/claim/components/ldap_to_email.jsx'; +import EmailToLDAP from 'components/claim/components/email_to_ldap.jsx'; + import {addLocaleData} from 'react-intl'; import enLocaleData from 'react-intl/locale-data/en'; import esLocaleData from 'react-intl/locale-data/es'; @@ -308,10 +314,6 @@ function renderRootComponent() { component={Login} /> <Route - path='claim' - component={ClaimAccount} - /> - <Route path='reset_password' component={PasswordResetSendLink} /> @@ -319,6 +321,27 @@ function renderRootComponent() { path='reset_password_complete' component={PasswordResetForm} /> + <Route + path='claim' + component={Claim} + > + <Route + path='oauth_to_email' + component={OAuthToEmail} + /> + <Route + path='email_to_oauth' + component={EmailToOAuth} + /> + <Route + path='email_to_ldap' + component={EmailToLDAP} + /> + <Route + path='ldap_to_email' + component={LDAPToEmail} + /> + </Route> </Route> </Route> </Route> diff --git a/webapp/sass/components/_buttons.scss b/webapp/sass/components/_buttons.scss index efef11ce9..5d95759a2 100644 --- a/webapp/sass/components/_buttons.scss +++ b/webapp/sass/components/_buttons.scss @@ -15,6 +15,16 @@ } } + &.btn-danger { + color: $white; + + &:hover, + &:focus, + &:active { + color: $white; + } + } + &.btn-inactive { background: $light-gray; border-color: transparent; diff --git a/webapp/sass/components/_modal.scss b/webapp/sass/components/_modal.scss index 94378aabe..4e2049857 100644 --- a/webapp/sass/components/_modal.scss +++ b/webapp/sass/components/_modal.scss @@ -9,6 +9,7 @@ } .modal-body { + max-height: calc(90vh - 62px); overflow: auto; padding: 20px 15px; diff --git a/webapp/sass/components/_search.scss b/webapp/sass/components/_search.scss index 499c4fad4..98c27ecdc 100644 --- a/webapp/sass/components/_search.scss +++ b/webapp/sass/components/_search.scss @@ -6,17 +6,19 @@ } } -.search-bar__container { - @include flex(0 0 56px); - padding: 12px 8px 0 0; - - .sidebar--right { - &.move--left & { +.sidebar--right { + &.move--left { + .search-bar__container { padding-right: 42px; } } } +.search-bar__container { + @include flex(0 0 56px); + padding: 12px 8px 0 0; +} + .glyphicon-refresh-animate { @include animation(spin .7s infinite linear); } @@ -95,7 +97,7 @@ } .glyphicon-refresh-animate { - color: $dark-gray; + @include opacity(0.5); position: absolute; right: 27px; top: 27px; diff --git a/webapp/sass/components/_suggestion-list.scss b/webapp/sass/components/_suggestion-list.scss index 0100026ca..39269642d 100644 --- a/webapp/sass/components/_suggestion-list.scss +++ b/webapp/sass/components/_suggestion-list.scss @@ -8,6 +8,7 @@ .suggestion-list--top { position: absolute; + bottom: 100%; } .suggestion-list__content { diff --git a/webapp/sass/layout/_headers.scss b/webapp/sass/layout/_headers.scss index 9b631d00f..e75d2556b 100644 --- a/webapp/sass/layout/_headers.scss +++ b/webapp/sass/layout/_headers.scss @@ -134,6 +134,10 @@ @include alpha-property(background, $black, .1); } + a { + text-decoration: none; + } + .user__name { color: $white; } diff --git a/webapp/sass/layout/_navigation.scss b/webapp/sass/layout/_navigation.scss index 87e4b4d27..3daf6e56b 100644 --- a/webapp/sass/layout/_navigation.scss +++ b/webapp/sass/layout/_navigation.scss @@ -89,7 +89,7 @@ &.info-popover { @include background-size(100% 100%); - background: url('../images/info__icon.png'); + background-image: url('../images/info__icon.png'); cursor: pointer; height: 19px; position: relative; diff --git a/webapp/sass/layout/_post.scss b/webapp/sass/layout/_post.scss index 9d5be239a..4170483db 100644 --- a/webapp/sass/layout/_post.scss +++ b/webapp/sass/layout/_post.scss @@ -7,9 +7,7 @@ line-height: 20px; min-height: 36px; overflow-x: hidden; - position: absolute; resize: none; - top: 0px; white-space: pre-wrap; word-wrap: break-word; diff --git a/webapp/sass/layout/_sidebar-left.scss b/webapp/sass/layout/_sidebar-left.scss index 4c65d0a65..3a5e74570 100644 --- a/webapp/sass/layout/_sidebar-left.scss +++ b/webapp/sass/layout/_sidebar-left.scss @@ -1,7 +1,6 @@ @charset 'UTF-8'; .sidebar--left { - background: #fafafa; border-right: $border-gray; height: 100%; left: 0; diff --git a/webapp/sass/responsive/_mobile.scss b/webapp/sass/responsive/_mobile.scss index 53ea6d329..0e1a471cf 100644 --- a/webapp/sass/responsive/_mobile.scss +++ b/webapp/sass/responsive/_mobile.scss @@ -14,23 +14,23 @@ .post { .post__dropdown { + display: inline-block; + height: 20px; line-height: 9px; + text-align: center; text-decoration: none; width: 20px; - display: inline-block; - text-align: center; - height: 20px; &:after { - font-size: 20px; content: '...'; + font-size: 20px; top: -3px; } } .comment-icon__container { - visibility: visible; display: none; + visibility: visible; &.icon--show { display: inline-block; @@ -57,8 +57,8 @@ } .post__reply { - margin-right: 20px; float: right; + margin-right: 20px; svg { top: 1px; @@ -97,15 +97,15 @@ width: 40px; img { - width: 32px; height: 32px; + width: 32px; } } } .post-image__column { - width: 200px; height: 95px; + width: 200px; } .textarea-wrapper { @@ -114,6 +114,7 @@ display: none; } } + .form-control { &.min-height { min-height: 100px; @@ -132,6 +133,7 @@ .tip-overlay { &.tip-overlay--chat { margin-left: 10px; + .arrow { left: 32px; } @@ -142,20 +144,21 @@ display: block; .file-details__preview { + border-bottom: 1px solid #dddddd; + border-right: none; display: block; - width: 100%; height: 150px; - border-right: none; - border-bottom: 1px solid #ddd; + width: 100%; + img { - width: 64px; height: 64px; + width: 64px; } } .file-details { - width: 100%; height: auto; + width: 100%; } } @@ -165,9 +168,9 @@ .modal-direct-channels { .member-count { + display: block; float: none; margin-top: 10px; - display: block; } } @@ -181,9 +184,9 @@ } .overlay__circle { - width: 300px; height: 300px; margin: -150px 0 0 -150px; + width: 300px; } .overlay__files { @@ -212,9 +215,9 @@ } .signup-team__container { - padding: 30px 0; - margin-bottom: 30px; font-size: .9em; + margin-bottom: 30px; + padding: 30px 0; .signup-team__name { font-size: 2em; @@ -240,8 +243,8 @@ .modal { .info__label { - text-align: left; padding-bottom: 5px; + text-align: left; } .modal-header { @@ -250,8 +253,8 @@ } button.close { - font-weight: normal; font-size: 27px; + font-weight: normal; } .modal-title { @@ -262,9 +265,9 @@ .btn { &.btn-primary { display: block; + float: none; margin: 10px 0 6px; width: 100%; - float: none; } } } @@ -304,8 +307,8 @@ z-index: 5; .modal-title { - width: 100%; text-align: center; + width: 100%; } } @@ -323,6 +326,7 @@ .settings-table { padding: 0; + .nav { position: relative; top: auto; @@ -339,6 +343,7 @@ top: 14px; right: 0; padding-right: 0; + .fa { display: inline-block; } @@ -347,6 +352,7 @@ &.minimize-settings { padding: 0; display: none; + .user-settings { padding: 70px 20px 30px; } @@ -360,10 +366,10 @@ .nav { > li { > a { - border-top: 1px solid #ddd; + border-top: 1px solid #dddddd; font-size: 1.1em; line-height: 2.7; - color: #555; + color: #555555; .glyphicon { margin-left: 7px; @@ -438,25 +444,24 @@ } .file-preview__remove { - width: 28px; + @include alpha-property(background, $black, .5); height: 28px; left: auto; right: 0; - top: 0; - background: #444; - background: rgba(#000, .5); text-align: center; + top: 0; + width: 28px; &:after { display: none; } i { + font-size: 16px; line-height: 29px; - top: auto; - right: auto; position: relative; - font-size: 16px; + right: auto; + top: auto; } } @@ -467,10 +472,10 @@ margin: 0 -15px; .dropdown__icon { - width: 4px; - height: 16px; @include background-size(100% 100%); display: inline-block; + height: 16px; + width: 4px; } } @@ -502,10 +507,11 @@ .footer-pane { .footer-link { + line-height: 1.7; padding: 0; - width: 100%; text-align: right; - line-height: 1.7; + width: 100%; + &.copyright { width: 100%; } @@ -517,10 +523,10 @@ } .search-bar__container { - padding: 0; @include flex(0 0 44px); background: $primary-color; - color: #fff; + color: $white; + padding: 0; &.focused { .sidebar__collapse { @@ -534,40 +540,40 @@ } .search__clear { - @include translateX(0px); + @include translateX(0); } } + .search__form { + @include single-transition(all, .2s, linear); + @include translateX(0); border: none; - @include translateX(0px); - padding: 7px 20px 0 49px; height: 45px; + padding: 7px 20px 0 49px; position: relative; - @include single-transition(all, .2s, linear); .glyphicon-refresh-animate { + color: $black; right: 33px; top: 15px; - color: #fff; - color: rgba(255,255,255,.5); } .form-control { + @include border-radius(3px); + background: $white; border: none; + color: $dark-gray; padding: 0 10px 0 31px; - background: rgba(black, .2); - @include border-radius(3px); - color: #444; - background: #fff; } } } + .sidebar--menu { @include single-transition(transform, .5s, ease); @include translate3d(290px, 0, 0); - width: 290px; border: none; display: block; + width: 290px; &.move--left { @include translate3d(0, 0, 0); @@ -627,7 +633,7 @@ ul { clear: both; - background: #fff; + background: #ffffff; border-radius: 3px; top: 5px; position: relative; @@ -795,6 +801,7 @@ } } } + .activity-log__table { > div { display: block; @@ -805,8 +812,8 @@ } .activity-log__action { - text-align: left; margin-top: 10px; + text-align: left; } } } @@ -816,8 +823,8 @@ .settings-modal { .settings-table { .security-links { - margin-bottom: 10px; display: block; + margin-bottom: 10px; &:last-child { margin-bottom: 0; @@ -850,11 +857,11 @@ .member-drop, .member-role { - position: relative; - margin: 0px 0 0 44px; + margin: 0 0 0 44px; padding: 5px 0; - top: 0; + position: relative; right: 0; + top: 0; } .open > .dropdown-menu { diff --git a/webapp/sass/routes/_admin-console.scss b/webapp/sass/routes/_admin-console.scss index 0b47e5ab6..7e53713c0 100644 --- a/webapp/sass/routes/_admin-console.scss +++ b/webapp/sass/routes/_admin-console.scss @@ -10,12 +10,8 @@ width: 100%; } - .row { - margin: 0; - } - h3 { - border-bottom: 1px solid #ddd; + border-bottom: 1px solid alpha-color($black, .1); font-weight: 600; margin: 1em 0; padding-bottom: .5em; @@ -75,11 +71,18 @@ width: 17px; } + &.divider { + @include alpha-property(background, $black, .1); + } + > a { &:hover, - &.active, &:focus { - background-color: #eaeaea; + @include alpha-property(background, $black, .1); + } + + &.active { + background-color: transparent; } } diff --git a/webapp/stores/browser_store.jsx b/webapp/stores/browser_store.jsx index 66190f6a2..bba146e38 100644 --- a/webapp/stores/browser_store.jsx +++ b/webapp/stores/browser_store.jsx @@ -8,6 +8,8 @@ function getPrefix() { return global.window.mm_current_user_id + '_'; } + console.log('BrowserStore tried to operate without user present'); //eslint-disable-line no-console + return 'unknown_'; } @@ -34,46 +36,35 @@ class BrowserStoreClass { } checkVersion() { - var currentVersion = sessionStorage.getItem('storage_version'); + var currentVersion = this.getGlobalItem('storage_version'); if (currentVersion !== global.window.mm_config.Version) { - sessionStorage.clear(); + this.clearAll(); try { - sessionStorage.setItem('storage_version', global.window.mm_config.Version); + this.setGlobalItem('storage_version', global.window.mm_config.Version); } catch (e) { // Do nothing } } } - getItem(name, defaultValue) { - var result = null; - try { - result = JSON.parse(sessionStorage.getItem(getPrefix() + name)); - } catch (err) { - result = null; - } - - if (result === null && typeof defaultValue !== 'undefined') { - result = defaultValue; - } - - return result; + setItem(name, value) { + this.setGlobalItem(getPrefix() + name, value); } - setItem(name, value) { - sessionStorage.setItem(getPrefix() + name, JSON.stringify(value)); + getItem(name, defaultValue) { + return this.getGlobalItem(getPrefix() + name, defaultValue); } removeItem(name) { - sessionStorage.removeItem(getPrefix() + name); + this.removeGlobalItem(getPrefix() + name); } setGlobalItem(name, value) { try { if (this.isLocalStorageSupported()) { - localStorage.setItem(getPrefix() + name, JSON.stringify(value)); + localStorage.setItem(name, JSON.stringify(value)); } else { - sessionStorage.setItem(getPrefix() + name, JSON.stringify(value)); + sessionStorage.setItem(name, JSON.stringify(value)); } } catch (err) { console.log('An error occurred while setting local storage, clearing all props'); //eslint-disable-line no-console @@ -87,9 +78,9 @@ class BrowserStoreClass { var result = null; try { if (this.isLocalStorageSupported()) { - result = JSON.parse(localStorage.getItem(getPrefix() + name)); + result = JSON.parse(localStorage.getItem(name)); } else { - result = JSON.parse(sessionStorage.getItem(getPrefix() + name)); + result = JSON.parse(sessionStorage.getItem(name)); } } catch (err) { result = null; @@ -104,18 +95,18 @@ class BrowserStoreClass { removeGlobalItem(name) { if (this.isLocalStorageSupported()) { - localStorage.removeItem(getPrefix() + name); + localStorage.removeItem(name); } else { - sessionStorage.removeItem(getPrefix() + name); + sessionStorage.removeItem(name); } } getLastServerVersion() { - return sessionStorage.getItem('last_server_version'); + return this.getGlobalItem('last_server_version'); } setLastServerVersion(version) { - sessionStorage.setItem('last_server_version', version); + this.setGlobalItem('last_server_version', version); } signalLogout() { @@ -185,6 +176,7 @@ class BrowserStoreClass { const logoutId = sessionStorage.getItem('__logout__'); sessionStorage.clear(); + localStorage.clear(); if (logoutId) { sessionStorage.setItem('__logout__', logoutId); @@ -222,4 +214,3 @@ class BrowserStoreClass { var BrowserStore = new BrowserStoreClass(); export default BrowserStore; -window.BrowserStore = BrowserStore; diff --git a/webapp/stores/error_store.jsx b/webapp/stores/error_store.jsx index 776375a82..7c695a335 100644 --- a/webapp/stores/error_store.jsx +++ b/webapp/stores/error_store.jsx @@ -35,15 +35,15 @@ class ErrorStoreClass extends EventEmitter { } getLastError() { - return BrowserStore.getItem('last_error'); + return BrowserStore.getGlobalItem('last_error'); } storeLastError(error) { - BrowserStore.setItem('last_error', error); + BrowserStore.setGlobalItem('last_error', error); } getConnectionErrorCount() { - var count = BrowserStore.getItem('last_error_conn'); + var count = BrowserStore.getGlobalItem('last_error_conn'); if (count == null) { return 0; @@ -53,12 +53,12 @@ class ErrorStoreClass extends EventEmitter { } setConnectionErrorCount(count) { - BrowserStore.setItem('last_error_conn', count); + BrowserStore.setGlobalItem('last_error_conn', count); } clearLastError() { - BrowserStore.removeItem('last_error'); - BrowserStore.removeItem('last_error_conn'); + BrowserStore.removeGlobalItem('last_error'); + BrowserStore.removeGlobalItem('last_error_conn'); } } diff --git a/webapp/stores/search_store.jsx b/webapp/stores/search_store.jsx index c7818a858..acaa9e52f 100644 --- a/webapp/stores/search_store.jsx +++ b/webapp/stores/search_store.jsx @@ -4,8 +4,6 @@ import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; import EventEmitter from 'events'; -import BrowserStore from 'stores/browser_store.jsx'; - import Constants from 'utils/constants.jsx'; var ActionTypes = Constants.ActionTypes; @@ -18,29 +16,9 @@ class SearchStoreClass extends EventEmitter { constructor() { super(); - this.emitChange = this.emitChange.bind(this); - this.addChangeListener = this.addChangeListener.bind(this); - this.removeChangeListener = this.removeChangeListener.bind(this); - - this.emitSearchChange = this.emitSearchChange.bind(this); - this.addSearchChangeListener = this.addSearchChangeListener.bind(this); - this.removeSearchChangeListener = this.removeSearchChangeListener.bind(this); - - this.emitSearchTermChange = this.emitSearchTermChange.bind(this); - this.addSearchTermChangeListener = this.addSearchTermChangeListener.bind(this); - this.removeSearchTermChangeListener = this.removeSearchTermChangeListener.bind(this); - - this.emitShowSearch = this.emitShowSearch.bind(this); - this.addShowSearchListener = this.addShowSearchListener.bind(this); - this.removeShowSearchListener = this.removeShowSearchListener.bind(this); - - this.getSearchResults = this.getSearchResults.bind(this); - this.getIsMentionSearch = this.getIsMentionSearch.bind(this); - - this.storeSearchTerm = this.storeSearchTerm.bind(this); - this.getSearchTerm = this.getSearchTerm.bind(this); - - this.storeSearchResults = this.storeSearchResults.bind(this); + this.searchResults = {}; + this.isMentionSearch = false; + this.searchTerm = ''; } emitChange() { @@ -92,24 +70,24 @@ class SearchStoreClass extends EventEmitter { } getSearchResults() { - return BrowserStore.getItem('search_results'); + return this.searchResults; } getIsMentionSearch() { - return BrowserStore.getItem('is_mention_search'); + return this.isMentionSearch; } storeSearchTerm(term) { - BrowserStore.setItem('search_term', term); + this.searchTerm = term; } getSearchTerm() { - return BrowserStore.getItem('search_term'); + return this.searchTerm; } storeSearchResults(results, isMentionSearch) { - BrowserStore.setItem('search_results', results); - BrowserStore.setItem('is_mention_search', Boolean(isMentionSearch)); + this.searchResults = results; + this.isMentionSearch = isMentionSearch; } } diff --git a/webapp/stores/user_store.jsx b/webapp/stores/user_store.jsx index 98cc2f3f1..4213e6e8c 100644 --- a/webapp/stores/user_store.jsx +++ b/webapp/stores/user_store.jsx @@ -16,7 +16,10 @@ const CHANGE_EVENT_STATUSES = 'change_statuses'; class UserStoreClass extends EventEmitter { constructor() { super(); - this.profileCache = null; + this.profiles = {}; + this.statuses = {}; + this.sessions = {}; + this.audits = {}; this.currentUserId = ''; } @@ -135,11 +138,7 @@ class UserStoreClass extends EventEmitter { } getProfiles() { - if (this.profileCache !== null) { - return this.profileCache; - } - - return BrowserStore.getItem('profiles', {}); + return this.profiles; } getActiveOnlyProfiles(skipCurrent) { @@ -171,47 +170,38 @@ class UserStoreClass extends EventEmitter { } saveProfile(profile) { - var ps = this.getProfiles(); - ps[profile.id] = profile; - this.profileCache = ps; - BrowserStore.setItem('profiles', ps); + this.profiles[profile.id] = profile; } saveProfiles(profiles) { const currentId = this.getCurrentId(); - if (this.profileCache) { - const currentUser = this.profileCache[currentId]; - if (currentUser) { - if (currentId in profiles) { - delete profiles[currentId]; - } - - this.profileCache = profiles; - this.profileCache[currentId] = currentUser; - } else { - this.profileCache = profiles; + const currentUser = this.profiles[currentId]; + if (currentUser) { + if (currentId in this.profiles) { + delete this.profiles[currentId]; } + + this.profiles = profiles; + this.profiles[currentId] = currentUser; } else { - this.profileCache = profiles; + this.profiles = profiles; } - - BrowserStore.setItem('profiles', profiles); } setSessions(sessions) { - BrowserStore.setItem('sessions', sessions); + this.sessions = sessions; } getSessions() { - return BrowserStore.getItem('sessions', {loading: true}); + return this.sessions; } setAudits(audits) { - BrowserStore.setItem('audits', audits); + this.audits = audits; } getAudits() { - return BrowserStore.getItem('audits', {loading: true}); + return this.audits; } getCurrentMentionKeys() { @@ -252,7 +242,7 @@ class UserStoreClass extends EventEmitter { } pSetStatuses(statuses) { - BrowserStore.setItem('statuses', statuses); + this.statuses = statuses; } setStatus(userId, status) { @@ -263,7 +253,7 @@ class UserStoreClass extends EventEmitter { } getStatuses() { - return BrowserStore.getItem('statuses', {}); + return this.statuses; } getStatus(id) { diff --git a/webapp/utils/client.jsx b/webapp/utils/client.jsx index e29cf71d3..d42767d31 100644 --- a/webapp/utils/client.jsx +++ b/webapp/utils/client.jsx @@ -256,38 +256,72 @@ export function resetPassword(data, success, error) { track('api', 'api_users_reset_password'); } -export function switchToSSO(data, success, error) { +export function emailToOAuth(data, success, error) { $.ajax({ - url: '/api/v1/users/switch_to_sso', + url: '/api/v1/users/claim/email_to_oauth', dataType: 'json', contentType: 'application/json', type: 'POST', data: JSON.stringify(data), success, error: function onError(xhr, status, err) { - var e = handleError('switchToSSO', xhr, status, err); + var e = handleError('emailToOAuth', xhr, status, err); error(e); } }); - track('api', 'api_users_switch_to_sso'); + track('api', 'api_users_email_to_oauth'); } -export function switchToEmail(data, success, error) { +export function oauthToEmail(data, success, error) { $.ajax({ - url: '/api/v1/users/switch_to_email', + url: '/api/v1/users/claim/oauth_to_email', dataType: 'json', contentType: 'application/json', type: 'POST', data: JSON.stringify(data), success, error: function onError(xhr, status, err) { - var e = handleError('switchToEmail', xhr, status, err); + var e = handleError('oauthToEmail', xhr, status, err); error(e); } }); - track('api', 'api_users_switch_to_email'); + track('api', 'api_users_oauth_to_email'); +} + +export function emailToLDAP(data, success, error) { + $.ajax({ + url: '/api/v1/users/claim/email_to_ldap', + dataType: 'json', + contentType: 'application/json', + type: 'POST', + data: JSON.stringify(data), + success, + error: function onError(xhr, status, err) { + var e = handleError('emailToLDAP', xhr, status, err); + error(e); + } + }); + + track('api', 'api_users_email_to_ldap'); +} + +export function ldapToEmail(data, success, error) { + $.ajax({ + url: '/api/v1/users/claim/ldap_to_email', + dataType: 'json', + contentType: 'application/json', + type: 'POST', + data: JSON.stringify(data), + success, + error: function onError(xhr, status, err) { + var e = handleError('ldapToEmail', xhr, status, err); + error(e); + } + }); + + track('api', 'api_users_ldap_to_email'); } export function logout(success, error) { diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx index 28c9df60b..bcd2fadb9 100644 --- a/webapp/utils/constants.jsx +++ b/webapp/utils/constants.jsx @@ -185,6 +185,7 @@ export default { OFFTOPIC_CHANNEL: 'off-topic', GITLAB_SERVICE: 'gitlab', GOOGLE_SERVICE: 'google', + LDAP_SERVICE: 'ldap', EMAIL_SERVICE: 'email', SIGNIN_CHANGE: 'signin_change', SIGNIN_VERIFIED: 'verified', diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx index 5f16baf1f..1379455ca 100644 --- a/webapp/utils/utils.jsx +++ b/webapp/utils/utils.jsx @@ -687,7 +687,7 @@ export function applyTheme(theme) { } if (theme.centerChannelBg) { - changeCss('.app__content, .markdown__table, .markdown__table tbody tr, .suggestion-list__content, .modal .modal-content, .modal .modal-back', 'background:' + theme.centerChannelBg, 1); + changeCss('.app__content, .markdown__table, .markdown__table tbody tr, .suggestion-list__content, .modal .modal-content', 'background:' + theme.centerChannelBg, 1); changeCss('#post-list .post-list-holder-by-time', 'background:' + theme.centerChannelBg, 1); changeCss('#post-create', 'background:' + theme.centerChannelBg, 1); changeCss('.date-separator .separator__text, .new-separator .separator__text', 'background:' + theme.centerChannelBg, 1); |