diff options
Diffstat (limited to 'web/react/components')
76 files changed, 1096 insertions, 550 deletions
diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx index 2ad4d5b00..c8af2553d 100644 --- a/web/react/components/access_history_modal.jsx +++ b/web/react/components/access_history_modal.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. var UserStore = require('../stores/user_store.jsx'); +var ChannelStore = require('../stores/channel_store.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var LoadingScreen = require('./loading_screen.jsx'); var Utils = require('../utils/utils.jsx'); @@ -14,8 +15,10 @@ export default class AccessHistoryModal extends React.Component { this.handleMoreInfo = this.handleMoreInfo.bind(this); this.onHide = this.onHide.bind(this); this.onShow = this.onShow.bind(this); + this.formatAuditInfo = this.formatAuditInfo.bind(this); + this.handleRevokedSession = this.handleRevokedSession.bind(this); - let state = this.getStateFromStoresForAudits(); + const state = this.getStateFromStoresForAudits(); state.moreInfo = []; this.state = state; @@ -34,9 +37,9 @@ export default class AccessHistoryModal extends React.Component { } componentDidMount() { UserStore.addAuditsChangeListener(this.onAuditChange); - $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow); + $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow); - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); + $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); } componentWillUnmount() { UserStore.removeAuditsChangeListener(this.onAuditChange); @@ -52,23 +55,269 @@ export default class AccessHistoryModal extends React.Component { newMoreInfo[index] = true; this.setState({moreInfo: newMoreInfo}); } - render() { - var accessList = []; - var currentHistoryDate = null; + handleRevokedSession(sessionId) { + return 'The session with id ' + sessionId + ' was revoked'; + } + formatAuditInfo(currentAudit) { + const currentActionURL = currentAudit.action.replace(/\/api\/v[1-9]/, ''); - for (var i = 0; i < this.state.audits.length; i++) { - var currentAudit = this.state.audits[i]; - var newHistoryDate = new Date(currentAudit.create_at); - var newDate = null; + let currentAuditDesc = ''; + + if (currentActionURL.indexOf('/channels') === 0) { + const channelInfo = currentAudit.extra_info.split(' '); + const channelNameField = channelInfo[0].split('='); + + let channelURL = ''; + let channelObj; + let channelName = ''; + if (channelNameField.indexOf('name') >= 0) { + channelURL = channelNameField[channelNameField.indexOf('name') + 1]; + channelObj = ChannelStore.getByName(channelURL); + if (channelObj) { + channelName = channelObj.display_name; + } else { + channelName = channelURL; + } + } + + switch (currentActionURL) { + case '/channels/create': + currentAuditDesc = 'Created the ' + channelName + ' channel/group'; + break; + case '/channels/create_direct': + currentAuditDesc = 'Established a direct message channel with ' + Utils.getDirectTeammate(channelObj.id).username; + break; + case '/channels/update': + currentAuditDesc = 'Updated the ' + channelName + ' channel/group name'; + break; + case '/channels/update_desc': + currentAuditDesc = 'Updated the ' + channelName + ' channel/group description'; + break; + default: + let userIdField = []; + let userId = ''; + let username = ''; + + if (channelInfo[1]) { + userIdField = channelInfo[1].split('='); + + if (userIdField.indexOf('user_id') >= 0) { + userId = userIdField[userIdField.indexOf('user_id') + 1]; + username = UserStore.getProfile(userId).username; + } + } + + if (/\/channels\/[A-Za-z0-9]+\/delete/.test(currentActionURL)) { + currentAuditDesc = 'Deleted the channel/group with the URL ' + channelURL; + } else if (/\/channels\/[A-Za-z0-9]+\/add/.test(currentActionURL)) { + currentAuditDesc = 'Added ' + username + ' to the ' + channelName + ' channel/group'; + } else if (/\/channels\/[A-Za-z0-9]+\/remove/.test(currentActionURL)) { + currentAuditDesc = 'Removed ' + username + ' from the ' + channelName + ' channel/group'; + } + + break; + } + } else if (currentActionURL.indexOf('/oauth') === 0) { + const oauthInfo = currentAudit.extra_info.split(' '); + + switch (currentActionURL) { + case '/oauth/register': + const clientIdField = oauthInfo[0].split('='); + + if (clientIdField[0] === 'client_id') { + currentAuditDesc = 'Attempted to register a new OAuth Application with ID ' + clientIdField[1]; + } + + break; + case '/oauth/allow': + if (oauthInfo[0] === 'attempt') { + currentAuditDesc = 'Attempted to allow a new OAuth service access'; + } else if (oauthInfo[0] === 'success') { + currentAuditDesc = 'Successfully gave a new OAuth service access'; + } else if (oauthInfo[0] === 'fail - redirect_uri did not match registered callback') { + currentAuditDesc = 'Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback'; + } + + break; + case '/oauth/access_token': + if (oauthInfo[0] === 'attempt') { + currentAuditDesc = 'Attempted to get an OAuth access token'; + } else if (oauthInfo[0] === 'success') { + currentAuditDesc = 'Successfully added a new OAuth service'; + } else { + const oauthTokenFailure = oauthInfo[0].split('-'); + + if (oauthTokenFailure[0].trim() === 'fail' && oauthTokenFailure[1]) { + currentAuditDesc = 'Failed to get an OAuth access token - ' + oauthTokenFailure[1].trim(); + } + } + + break; + default: + break; + } + } else if (currentActionURL.indexOf('/users') === 0) { + const userInfo = currentAudit.extra_info.split(' '); + + switch (currentActionURL) { + case '/users/login': + if (userInfo[0] === 'attempt') { + currentAuditDesc = 'Attempted to login'; + } else if (userInfo[0] === 'success') { + currentAuditDesc = 'Successfully logged in'; + } else if (userInfo[0]) { + currentAuditDesc = 'FAILED login attempt'; + } + + break; + case '/users/revoke_session': + currentAuditDesc = this.handleRevokedSession(userInfo[0].split('=')[1]); + break; + case '/users/newimage': + currentAuditDesc = 'Updated your profile picture'; + break; + case '/users/update': + currentAuditDesc = 'Updated the general settings of your account'; + break; + case '/users/newpassword': + if (userInfo[0] === 'attempted') { + currentAuditDesc = 'Attempted to change password'; + } else if (userInfo[0] === 'completed') { + currentAuditDesc = 'Successfully changed password'; + } else if (userInfo[0] === 'failed - tried to update user password who was logged in through oauth') { + currentAuditDesc = 'Failed to change password - tried to update user password who was logged in through oauth'; + } + + break; + case '/users/update_roles': + const userRoles = userInfo[0].split('=')[1]; - if (!currentHistoryDate || currentHistoryDate.toLocaleDateString() !== newHistoryDate.toLocaleDateString()) { - currentHistoryDate = newHistoryDate; - newDate = (<div> {currentHistoryDate.toDateString()} </div>); + currentAuditDesc = 'Updated user role(s) to '; + if (userRoles.trim()) { + currentAuditDesc += userRoles; + } else { + currentAuditDesc += 'member'; + } + + break; + case '/users/update_active': + const updateType = userInfo[0].split('=')[0]; + const updateField = userInfo[0].split('=')[1]; + + /* Either describes account activation/deactivation or a revoked session as part of an account deactivation */ + if (updateType === 'active') { + if (updateField === 'true') { + currentAuditDesc = 'Account made active'; + } else if (updateField === 'false') { + currentAuditDesc = 'Account made inactive'; + } + + const actingUserInfo = userInfo[1].split('='); + if (actingUserInfo[0] === 'session_user') { + const actingUser = UserStore.getProfile(actingUserInfo[1]); + const currentUser = UserStore.getCurrentUser(); + if (currentUser && actingUser && (Utils.isAdmin(currentUser.roles) || Utils.isSystemAdmin(currentUser.roles))) { + currentAuditDesc += ' by ' + actingUser.username; + } else if (currentUser && actingUser) { + currentAuditDesc += ' by an admin'; + } + } + } else if (updateType === 'session_id') { + currentAuditDesc = this.handleRevokedSession(updateField); + } + + break; + case '/users/send_password_reset': + currentAuditDesc = 'Sent an email to ' + userInfo[0].split('=')[1] + ' to reset your password'; + break; + case '/users/reset_password': + if (userInfo[0] === 'attempt') { + currentAuditDesc = 'Attempted to reset password'; + } else if (userInfo[0] === 'success') { + currentAuditDesc = 'Successfully reset password'; + } + + break; + case '/users/update_notify': + currentAuditDesc = 'Updated your global notification settings'; + break; + default: + break; } + } else if (currentActionURL.indexOf('/hooks') === 0) { + const webhookInfo = currentAudit.extra_info.split(' '); + + switch (currentActionURL) { + case '/hooks/incoming/create': + if (webhookInfo[0] === 'attempt') { + currentAuditDesc = 'Attempted to create a webhook'; + } else if (webhookInfo[0] === 'success') { + currentAuditDesc = 'Successfully created a webhook'; + } else if (webhookInfo[0] === 'fail - bad channel permissions') { + currentAuditDesc = 'Failed to create a webhook - bad channel permissions'; + } - if (!currentAudit.session_id && currentAudit.action.search('/users/login') !== -1) { - currentAudit.session_id = 'N/A (Login attempt)'; + break; + case '/hooks/incoming/delete': + if (webhookInfo[0] === 'attempt') { + currentAuditDesc = 'Attempted to delete a webhook'; + } else if (webhookInfo[0] === 'success') { + currentAuditDesc = 'Successfully deleted a webhook'; + } else if (webhookInfo[0] === 'fail - inappropriate conditions') { + currentAuditDesc = 'Failed to delete a webhook - inappropriate conditions'; + } + + break; + default: + break; + } + } else { + switch (currentActionURL) { + case '/logout': + currentAuditDesc = 'Logged out of your account'; + break; + case '/verify_email': + currentAuditDesc = 'Sucessfully verified your email address'; + break; + default: + break; } + } + + /* If all else fails... */ + if (!currentAuditDesc) { + /* Currently not called anywhere */ + if (currentAudit.extra_info.indexOf('revoked_all=') >= 0) { + currentAuditDesc = 'Revoked all current sessions for the team'; + } else { + let currentActionDesc = ''; + if (currentActionURL && currentActionURL.lastIndexOf('/') !== -1) { + currentActionDesc = currentActionURL.substring(currentActionURL.lastIndexOf('/') + 1).replace('_', ' '); + currentActionDesc = Utils.toTitleCase(currentActionDesc); + } + + let currentExtraInfoDesc = ''; + if (currentAudit.extra_info) { + currentExtraInfoDesc = currentAudit.extra_info; + + if (currentExtraInfoDesc.indexOf('=') !== -1) { + currentExtraInfoDesc = currentExtraInfoDesc.substring(currentExtraInfoDesc.indexOf('=') + 1); + } + } + currentAuditDesc = currentActionDesc + ' ' + currentExtraInfoDesc; + } + } + + const currentDate = new Date(currentAudit.create_at); + const currentAuditInfo = currentDate.toDateString() + ' - ' + currentDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute: '2-digit'}) + ' | ' + currentAuditDesc; + return currentAuditInfo; + } + render() { + var accessList = []; + + for (var i = 0; i < this.state.audits.length; i++) { + const currentAudit = this.state.audits[i]; + const currentAuditInfo = this.formatAuditInfo(currentAudit); var moreInfo = ( <a @@ -76,15 +325,27 @@ export default class AccessHistoryModal extends React.Component { className='theme' onClick={this.handleMoreInfo.bind(this, i)} > - More info + {'More info'} </a> ); if (this.state.moreInfo[i]) { + if (!currentAudit.session_id) { + currentAudit.session_id = 'N/A'; + + if (currentAudit.action.search('/users/login') >= 0) { + if (currentAudit.extra_info === 'attempt') { + currentAudit.session_id += ' (Login attempt)'; + } else { + currentAudit.session_id += ' (Login failure)'; + } + } + } + moreInfo = ( <div> + <div>{'IP: ' + currentAudit.ip_address}</div> <div>{'Session ID: ' + currentAudit.session_id}</div> - <div>{'URL: ' + currentAudit.action.replace(/\/api\/v[1-9]/, '')}</div> </div> ); } @@ -99,11 +360,9 @@ export default class AccessHistoryModal extends React.Component { key={'accessHistoryEntryKey' + i} className='access-history__table' > - <div className='access__date'>{newDate}</div> <div className='access__report'> - <div className='report__time'>{newHistoryDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute: '2-digit'})}</div> + <div className='report__time'>{currentAuditInfo}</div> <div className='report__info'> - <div>{'IP: ' + currentAudit.ip_address}</div> {moreInfo} </div> {divider} @@ -138,13 +397,13 @@ export default class AccessHistoryModal extends React.Component { data-dismiss='modal' aria-label='Close' > - <span aria-hidden='true'>×</span> + <span aria-hidden='true'>{'×'}</span> </button> <h4 className='modal-title' id='myModalLabel' > - Access History + {'Access History'} </h4> </div> <div diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index 1fe2133ec..74d6c64e3 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -56,9 +56,9 @@ export default class ActivityLogModal extends React.Component { } componentDidMount() { UserStore.addSessionsChangeListener(this.onListenerChange); - $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow); + $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow); - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); + $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); } componentWillUnmount() { UserStore.removeSessionsChangeListener(this.onListenerChange); diff --git a/web/react/components/admin_console/admin_navbar_dropdown.jsx b/web/react/components/admin_console/admin_navbar_dropdown.jsx index 21ec5c3cf..df8da94e1 100644 --- a/web/react/components/admin_console/admin_navbar_dropdown.jsx +++ b/web/react/components/admin_console/admin_navbar_dropdown.jsx @@ -27,7 +27,7 @@ export default class AdminNavbarDropdown extends React.Component { } componentDidMount() { - $(React.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => { + $(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => { this.blockToggle = true; setTimeout(() => { this.blockToggle = false; @@ -36,7 +36,7 @@ export default class AdminNavbarDropdown extends React.Component { } componentWillUnmount() { - $(React.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown'); + $(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown'); } render() { diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index bc6ad1931..4c2a473b6 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -100,6 +100,7 @@ export default class AdminSidebar extends React.Component { className='menu-icon--right menu__close' onClick={this.removeTeam.bind(this, team.id)} style={{cursor: 'pointer'}} + title='Remove team from sidebar menu' > {'x'} </span> @@ -233,7 +234,10 @@ export default class AdminSidebar extends React.Component { href='#' onClick={this.showTeamSelect} > - <i className='fa fa-plus'></i> + <i + className='fa fa-plus' + title='Add team to sidebar menu' + ></i> </a> </span> </h4> diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index c028d605d..01759b222 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -41,28 +41,28 @@ export default class EmailSettings extends React.Component { buildConfig() { var config = this.props.config; - config.EmailSettings.EnableSignUpWithEmail = React.findDOMNode(this.refs.allowSignUpWithEmail).checked; - config.EmailSettings.SendEmailNotifications = React.findDOMNode(this.refs.sendEmailNotifications).checked; - config.EmailSettings.RequireEmailVerification = React.findDOMNode(this.refs.requireEmailVerification).checked; - config.EmailSettings.SendEmailNotifications = React.findDOMNode(this.refs.sendEmailNotifications).checked; - config.EmailSettings.FeedbackName = React.findDOMNode(this.refs.feedbackName).value.trim(); - config.EmailSettings.FeedbackEmail = React.findDOMNode(this.refs.feedbackEmail).value.trim(); - config.EmailSettings.SMTPServer = React.findDOMNode(this.refs.SMTPServer).value.trim(); - config.EmailSettings.SMTPPort = React.findDOMNode(this.refs.SMTPPort).value.trim(); - config.EmailSettings.SMTPUsername = React.findDOMNode(this.refs.SMTPUsername).value.trim(); - config.EmailSettings.SMTPPassword = React.findDOMNode(this.refs.SMTPPassword).value.trim(); - config.EmailSettings.ConnectionSecurity = React.findDOMNode(this.refs.ConnectionSecurity).value.trim(); - - config.EmailSettings.InviteSalt = React.findDOMNode(this.refs.InviteSalt).value.trim(); + config.EmailSettings.EnableSignUpWithEmail = ReactDOM.findDOMNode(this.refs.allowSignUpWithEmail).checked; + config.EmailSettings.SendEmailNotifications = ReactDOM.findDOMNode(this.refs.sendEmailNotifications).checked; + config.EmailSettings.RequireEmailVerification = ReactDOM.findDOMNode(this.refs.requireEmailVerification).checked; + config.EmailSettings.SendEmailNotifications = ReactDOM.findDOMNode(this.refs.sendEmailNotifications).checked; + config.EmailSettings.FeedbackName = ReactDOM.findDOMNode(this.refs.feedbackName).value.trim(); + config.EmailSettings.FeedbackEmail = ReactDOM.findDOMNode(this.refs.feedbackEmail).value.trim(); + config.EmailSettings.SMTPServer = ReactDOM.findDOMNode(this.refs.SMTPServer).value.trim(); + config.EmailSettings.SMTPPort = ReactDOM.findDOMNode(this.refs.SMTPPort).value.trim(); + config.EmailSettings.SMTPUsername = ReactDOM.findDOMNode(this.refs.SMTPUsername).value.trim(); + config.EmailSettings.SMTPPassword = ReactDOM.findDOMNode(this.refs.SMTPPassword).value.trim(); + config.EmailSettings.ConnectionSecurity = ReactDOM.findDOMNode(this.refs.ConnectionSecurity).value.trim(); + + config.EmailSettings.InviteSalt = ReactDOM.findDOMNode(this.refs.InviteSalt).value.trim(); if (config.EmailSettings.InviteSalt === '') { config.EmailSettings.InviteSalt = crypto.randomBytes(256).toString('base64').substring(0, 32); - React.findDOMNode(this.refs.InviteSalt).value = config.EmailSettings.InviteSalt; + ReactDOM.findDOMNode(this.refs.InviteSalt).value = config.EmailSettings.InviteSalt; } - config.EmailSettings.PasswordResetSalt = React.findDOMNode(this.refs.PasswordResetSalt).value.trim(); + config.EmailSettings.PasswordResetSalt = ReactDOM.findDOMNode(this.refs.PasswordResetSalt).value.trim(); if (config.EmailSettings.PasswordResetSalt === '') { config.EmailSettings.PasswordResetSalt = crypto.randomBytes(256).toString('base64').substring(0, 32); - React.findDOMNode(this.refs.PasswordResetSalt).value = config.EmailSettings.PasswordResetSalt; + ReactDOM.findDOMNode(this.refs.PasswordResetSalt).value = config.EmailSettings.PasswordResetSalt; } return config; @@ -70,14 +70,14 @@ export default class EmailSettings extends React.Component { handleGenerateInvite(e) { e.preventDefault(); - React.findDOMNode(this.refs.InviteSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); + ReactDOM.findDOMNode(this.refs.InviteSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); var s = {saveNeeded: true, serverError: this.state.serverError}; this.setState(s); } handleGenerateReset(e) { e.preventDefault(); - React.findDOMNode(this.refs.PasswordResetSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); + ReactDOM.findDOMNode(this.refs.PasswordResetSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); var s = {saveNeeded: true, serverError: this.state.serverError}; this.setState(s); } @@ -447,7 +447,7 @@ export default class EmailSettings extends React.Component { </div> <div className='help-text'> <button - className='btn' + className='btn btn-default' onClick={this.handleTestConnection} disabled={!this.state.sendEmailNotifications} id='connection-button' @@ -482,7 +482,7 @@ export default class EmailSettings extends React.Component { <p className='help-text'>{'32-character salt added to signing of email invites. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p> <div className='help-text'> <button - className='btn' + className='btn btn-default' onClick={this.handleGenerateInvite} disabled={!this.state.sendEmailNotifications} > @@ -513,7 +513,7 @@ export default class EmailSettings extends React.Component { <p className='help-text'>{'32-character salt added to signing of password reset emails. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p> <div className='help-text'> <button - className='btn' + className='btn btn-default' onClick={this.handleGenerateReset} disabled={!this.state.sendEmailNotifications} > diff --git a/web/react/components/admin_console/gitlab_settings.jsx b/web/react/components/admin_console/gitlab_settings.jsx index 5c22bf5cf..8b0f00083 100644 --- a/web/react/components/admin_console/gitlab_settings.jsx +++ b/web/react/components/admin_console/gitlab_settings.jsx @@ -37,12 +37,12 @@ export default class GitLabSettings extends React.Component { $('#save-button').button('loading'); var config = this.props.config; - config.GitLabSettings.Enable = React.findDOMNode(this.refs.Enable).checked; - config.GitLabSettings.Secret = React.findDOMNode(this.refs.Secret).value.trim(); - config.GitLabSettings.Id = React.findDOMNode(this.refs.Id).value.trim(); - config.GitLabSettings.AuthEndpoint = React.findDOMNode(this.refs.AuthEndpoint).value.trim(); - config.GitLabSettings.TokenEndpoint = React.findDOMNode(this.refs.TokenEndpoint).value.trim(); - config.GitLabSettings.UserApiEndpoint = React.findDOMNode(this.refs.UserApiEndpoint).value.trim(); + config.GitLabSettings.Enable = ReactDOM.findDOMNode(this.refs.Enable).checked; + config.GitLabSettings.Secret = ReactDOM.findDOMNode(this.refs.Secret).value.trim(); + config.GitLabSettings.Id = ReactDOM.findDOMNode(this.refs.Id).value.trim(); + config.GitLabSettings.AuthEndpoint = ReactDOM.findDOMNode(this.refs.AuthEndpoint).value.trim(); + config.GitLabSettings.TokenEndpoint = ReactDOM.findDOMNode(this.refs.TokenEndpoint).value.trim(); + config.GitLabSettings.UserApiEndpoint = ReactDOM.findDOMNode(this.refs.UserApiEndpoint).value.trim(); Client.saveConfig( config, @@ -116,12 +116,14 @@ export default class GitLabSettings extends React.Component { <p className='help-text'> {'When true, Mattermost allows team creation and account signup using GitLab OAuth.'} <br/> </p> - <ol className='help-text'> - <li>{'Log in to your GitLab account and go to Applications -> Profile Settings.'}</li> - <li>{'Enter Redirect URIs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete". '}</li> - <li>{'Then use "Secret" and "Id" fields from GitLab to complete the options below.'}</li> - <li>{'Complete the Endpoint URLs below. '}</li> - </ol> + <div className='help-text'> + <ol> + <li>{'Log in to your GitLab account and go to Applications -> Profile Settings.'}</li> + <li>{'Enter Redirect URIs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete". '}</li> + <li>{'Then use "Secret" and "Id" fields from GitLab to complete the options below.'}</li> + <li>{'Complete the Endpoint URLs below. '}</li> + </ol> + </div> </div> </div> @@ -258,7 +260,7 @@ export default class GitLabSettings extends React.Component { } -//config.GitLabSettings.Scope = React.findDOMNode(this.refs.Scope).value.trim(); +//config.GitLabSettings.Scope = ReactDOM.findDOMNode(this.refs.Scope).value.trim(); // <div className='form-group'> // <label // className='control-label col-sm-4' diff --git a/web/react/components/admin_console/image_settings.jsx b/web/react/components/admin_console/image_settings.jsx index 24ed5a0a8..8b577e012 100644 --- a/web/react/components/admin_console/image_settings.jsx +++ b/web/react/components/admin_console/image_settings.jsx @@ -24,7 +24,7 @@ export default class FileSettings extends React.Component { var s = {saveNeeded: true, serverError: this.state.serverError}; if (action === 'DriverName') { - s.DriverName = React.findDOMNode(this.refs.DriverName).value; + s.DriverName = ReactDOM.findDOMNode(this.refs.DriverName).value; } this.setState(s); @@ -32,7 +32,7 @@ export default class FileSettings extends React.Component { handleGenerate(e) { e.preventDefault(); - React.findDOMNode(this.refs.PublicLinkSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); + ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); var s = {saveNeeded: true, serverError: this.state.serverError}; this.setState(s); } @@ -42,62 +42,62 @@ export default class FileSettings extends React.Component { $('#save-button').button('loading'); var config = this.props.config; - config.FileSettings.DriverName = React.findDOMNode(this.refs.DriverName).value; - config.FileSettings.Directory = React.findDOMNode(this.refs.Directory).value; - config.FileSettings.AmazonS3AccessKeyId = React.findDOMNode(this.refs.AmazonS3AccessKeyId).value; - config.FileSettings.AmazonS3SecretAccessKey = React.findDOMNode(this.refs.AmazonS3SecretAccessKey).value; - config.FileSettings.AmazonS3Bucket = React.findDOMNode(this.refs.AmazonS3Bucket).value; - config.FileSettings.AmazonS3Region = React.findDOMNode(this.refs.AmazonS3Region).value; - config.FileSettings.EnablePublicLink = React.findDOMNode(this.refs.EnablePublicLink).checked; + config.FileSettings.DriverName = ReactDOM.findDOMNode(this.refs.DriverName).value; + config.FileSettings.Directory = ReactDOM.findDOMNode(this.refs.Directory).value; + config.FileSettings.AmazonS3AccessKeyId = ReactDOM.findDOMNode(this.refs.AmazonS3AccessKeyId).value; + config.FileSettings.AmazonS3SecretAccessKey = ReactDOM.findDOMNode(this.refs.AmazonS3SecretAccessKey).value; + config.FileSettings.AmazonS3Bucket = ReactDOM.findDOMNode(this.refs.AmazonS3Bucket).value; + config.FileSettings.AmazonS3Region = ReactDOM.findDOMNode(this.refs.AmazonS3Region).value; + config.FileSettings.EnablePublicLink = ReactDOM.findDOMNode(this.refs.EnablePublicLink).checked; - config.FileSettings.PublicLinkSalt = React.findDOMNode(this.refs.PublicLinkSalt).value.trim(); + config.FileSettings.PublicLinkSalt = ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value.trim(); if (config.FileSettings.PublicLinkSalt === '') { config.FileSettings.PublicLinkSalt = crypto.randomBytes(256).toString('base64').substring(0, 32); - React.findDOMNode(this.refs.PublicLinkSalt).value = config.FileSettings.PublicLinkSalt; + ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value = config.FileSettings.PublicLinkSalt; } var thumbnailWidth = 120; - if (!isNaN(parseInt(React.findDOMNode(this.refs.ThumbnailWidth).value, 10))) { - thumbnailWidth = parseInt(React.findDOMNode(this.refs.ThumbnailWidth).value, 10); + if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailWidth).value, 10))) { + thumbnailWidth = parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailWidth).value, 10); } config.FileSettings.ThumbnailWidth = thumbnailWidth; - React.findDOMNode(this.refs.ThumbnailWidth).value = thumbnailWidth; + ReactDOM.findDOMNode(this.refs.ThumbnailWidth).value = thumbnailWidth; var thumbnailHeight = 100; - if (!isNaN(parseInt(React.findDOMNode(this.refs.ThumbnailHeight).value, 10))) { - thumbnailHeight = parseInt(React.findDOMNode(this.refs.ThumbnailHeight).value, 10); + if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailHeight).value, 10))) { + thumbnailHeight = parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailHeight).value, 10); } config.FileSettings.ThumbnailHeight = thumbnailHeight; - React.findDOMNode(this.refs.ThumbnailHeight).value = thumbnailHeight; + ReactDOM.findDOMNode(this.refs.ThumbnailHeight).value = thumbnailHeight; var previewWidth = 1024; - if (!isNaN(parseInt(React.findDOMNode(this.refs.PreviewWidth).value, 10))) { - previewWidth = parseInt(React.findDOMNode(this.refs.PreviewWidth).value, 10); + if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.PreviewWidth).value, 10))) { + previewWidth = parseInt(ReactDOM.findDOMNode(this.refs.PreviewWidth).value, 10); } config.FileSettings.PreviewWidth = previewWidth; - React.findDOMNode(this.refs.PreviewWidth).value = previewWidth; + ReactDOM.findDOMNode(this.refs.PreviewWidth).value = previewWidth; var previewHeight = 0; - if (!isNaN(parseInt(React.findDOMNode(this.refs.PreviewHeight).value, 10))) { - previewHeight = parseInt(React.findDOMNode(this.refs.PreviewHeight).value, 10); + if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.PreviewHeight).value, 10))) { + previewHeight = parseInt(ReactDOM.findDOMNode(this.refs.PreviewHeight).value, 10); } config.FileSettings.PreviewHeight = previewHeight; - React.findDOMNode(this.refs.PreviewHeight).value = previewHeight; + ReactDOM.findDOMNode(this.refs.PreviewHeight).value = previewHeight; var profileWidth = 128; - if (!isNaN(parseInt(React.findDOMNode(this.refs.ProfileWidth).value, 10))) { - profileWidth = parseInt(React.findDOMNode(this.refs.ProfileWidth).value, 10); + if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ProfileWidth).value, 10))) { + profileWidth = parseInt(ReactDOM.findDOMNode(this.refs.ProfileWidth).value, 10); } config.FileSettings.ProfileWidth = profileWidth; - React.findDOMNode(this.refs.ProfileWidth).value = profileWidth; + ReactDOM.findDOMNode(this.refs.ProfileWidth).value = profileWidth; var profileHeight = 128; - if (!isNaN(parseInt(React.findDOMNode(this.refs.ProfileHeight).value, 10))) { - profileHeight = parseInt(React.findDOMNode(this.refs.ProfileHeight).value, 10); + if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ProfileHeight).value, 10))) { + profileHeight = parseInt(ReactDOM.findDOMNode(this.refs.ProfileHeight).value, 10); } config.FileSettings.ProfileHeight = profileHeight; - React.findDOMNode(this.refs.ProfileHeight).value = profileHeight; + ReactDOM.findDOMNode(this.refs.ProfileHeight).value = profileHeight; Client.saveConfig( config, diff --git a/web/react/components/admin_console/log_settings.jsx b/web/react/components/admin_console/log_settings.jsx index 367605f14..931818bb8 100644 --- a/web/react/components/admin_console/log_settings.jsx +++ b/web/react/components/admin_console/log_settings.jsx @@ -46,12 +46,12 @@ export default class LogSettings extends React.Component { $('#save-button').button('loading'); var config = this.props.config; - config.LogSettings.EnableConsole = React.findDOMNode(this.refs.consoleEnable).checked; - config.LogSettings.ConsoleLevel = React.findDOMNode(this.refs.consoleLevel).value; - config.LogSettings.EnableFile = React.findDOMNode(this.refs.fileEnable).checked; - config.LogSettings.FileLevel = React.findDOMNode(this.refs.fileLevel).value; - config.LogSettings.FileLocation = React.findDOMNode(this.refs.fileLocation).value.trim(); - config.LogSettings.FileFormat = React.findDOMNode(this.refs.fileFormat).value.trim(); + config.LogSettings.EnableConsole = ReactDOM.findDOMNode(this.refs.consoleEnable).checked; + config.LogSettings.ConsoleLevel = ReactDOM.findDOMNode(this.refs.consoleLevel).value; + config.LogSettings.EnableFile = ReactDOM.findDOMNode(this.refs.fileEnable).checked; + config.LogSettings.FileLevel = ReactDOM.findDOMNode(this.refs.fileLevel).value; + config.LogSettings.FileLocation = ReactDOM.findDOMNode(this.refs.fileLocation).value.trim(); + config.LogSettings.FileFormat = ReactDOM.findDOMNode(this.refs.fileFormat).value.trim(); Client.saveConfig( config, diff --git a/web/react/components/admin_console/privacy_settings.jsx b/web/react/components/admin_console/privacy_settings.jsx index 70ec04f4a..f2d22f36e 100644 --- a/web/react/components/admin_console/privacy_settings.jsx +++ b/web/react/components/admin_console/privacy_settings.jsx @@ -28,8 +28,8 @@ export default class PrivacySettings extends React.Component { $('#save-button').button('loading'); var config = this.props.config; - config.PrivacySettings.ShowEmailAddress = React.findDOMNode(this.refs.ShowEmailAddress).checked; - config.PrivacySettings.ShowFullName = React.findDOMNode(this.refs.ShowFullName).checked; + config.PrivacySettings.ShowEmailAddress = ReactDOM.findDOMNode(this.refs.ShowEmailAddress).checked; + config.PrivacySettings.ShowFullName = ReactDOM.findDOMNode(this.refs.ShowFullName).checked; Client.saveConfig( config, diff --git a/web/react/components/admin_console/rate_settings.jsx b/web/react/components/admin_console/rate_settings.jsx index 65c39ac42..4d71777c4 100644 --- a/web/react/components/admin_console/rate_settings.jsx +++ b/web/react/components/admin_console/rate_settings.jsx @@ -46,23 +46,23 @@ export default class RateSettings extends React.Component { $('#save-button').button('loading'); var config = this.props.config; - config.RateLimitSettings.EnableRateLimiter = React.findDOMNode(this.refs.EnableRateLimiter).checked; - config.RateLimitSettings.VaryByRemoteAddr = React.findDOMNode(this.refs.VaryByRemoteAddr).checked; - config.RateLimitSettings.VaryByHeader = React.findDOMNode(this.refs.VaryByHeader).value.trim(); + config.RateLimitSettings.EnableRateLimiter = ReactDOM.findDOMNode(this.refs.EnableRateLimiter).checked; + config.RateLimitSettings.VaryByRemoteAddr = ReactDOM.findDOMNode(this.refs.VaryByRemoteAddr).checked; + config.RateLimitSettings.VaryByHeader = ReactDOM.findDOMNode(this.refs.VaryByHeader).value.trim(); var PerSec = 10; - if (!isNaN(parseInt(React.findDOMNode(this.refs.PerSec).value, 10))) { - PerSec = parseInt(React.findDOMNode(this.refs.PerSec).value, 10); + if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.PerSec).value, 10))) { + PerSec = parseInt(ReactDOM.findDOMNode(this.refs.PerSec).value, 10); } config.RateLimitSettings.PerSec = PerSec; - React.findDOMNode(this.refs.PerSec).value = PerSec; + ReactDOM.findDOMNode(this.refs.PerSec).value = PerSec; var MemoryStoreSize = 10000; - if (!isNaN(parseInt(React.findDOMNode(this.refs.MemoryStoreSize).value, 10))) { - MemoryStoreSize = parseInt(React.findDOMNode(this.refs.MemoryStoreSize).value, 10); + if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MemoryStoreSize).value, 10))) { + MemoryStoreSize = parseInt(ReactDOM.findDOMNode(this.refs.MemoryStoreSize).value, 10); } config.RateLimitSettings.MemoryStoreSize = MemoryStoreSize; - React.findDOMNode(this.refs.MemoryStoreSize).value = MemoryStoreSize; + ReactDOM.findDOMNode(this.refs.MemoryStoreSize).value = MemoryStoreSize; Client.saveConfig( config, diff --git a/web/react/components/admin_console/reset_password_modal.jsx b/web/react/components/admin_console/reset_password_modal.jsx index 75264be45..35d3cdd17 100644 --- a/web/react/components/admin_console/reset_password_modal.jsx +++ b/web/react/components/admin_console/reset_password_modal.jsx @@ -18,7 +18,7 @@ export default class ResetPasswordModal extends React.Component { doSubmit(e) { e.preventDefault(); - var password = React.findDOMNode(this.refs.password).value; + var password = ReactDOM.findDOMNode(this.refs.password).value; if (!password || password.length < 5) { this.setState({serverError: 'Please enter at least 5 characters.'}); @@ -34,7 +34,7 @@ export default class ResetPasswordModal extends React.Component { Client.resetPassword(data, () => { - this.props.onModalSubmit(React.findDOMNode(this.refs.password).value); + this.props.onModalSubmit(ReactDOM.findDOMNode(this.refs.password).value); }, (err) => { this.setState({serverError: err.message}); diff --git a/web/react/components/admin_console/select_team_modal.jsx b/web/react/components/admin_console/select_team_modal.jsx index 21d1c25c3..22189821b 100644 --- a/web/react/components/admin_console/select_team_modal.jsx +++ b/web/react/components/admin_console/select_team_modal.jsx @@ -13,7 +13,7 @@ export default class SelectTeamModal extends React.Component { doSubmit(e) { e.preventDefault(); - this.props.onModalSubmit(React.findDOMNode(this.refs.team).value); + this.props.onModalSubmit(ReactDOM.findDOMNode(this.refs.team).value); } doCancel() { this.props.onModalDismissed(); diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx index f29d62646..4105ba6da 100644 --- a/web/react/components/admin_console/service_settings.jsx +++ b/web/react/components/admin_console/service_settings.jsx @@ -27,28 +27,28 @@ export default class ServiceSettings extends React.Component { $('#save-button').button('loading'); var config = this.props.config; - config.ServiceSettings.ListenAddress = React.findDOMNode(this.refs.ListenAddress).value.trim(); + config.ServiceSettings.ListenAddress = ReactDOM.findDOMNode(this.refs.ListenAddress).value.trim(); if (config.ServiceSettings.ListenAddress === '') { config.ServiceSettings.ListenAddress = ':8065'; - React.findDOMNode(this.refs.ListenAddress).value = config.ServiceSettings.ListenAddress; + ReactDOM.findDOMNode(this.refs.ListenAddress).value = config.ServiceSettings.ListenAddress; } - config.ServiceSettings.SegmentDeveloperKey = React.findDOMNode(this.refs.SegmentDeveloperKey).value.trim(); - config.ServiceSettings.GoogleDeveloperKey = React.findDOMNode(this.refs.GoogleDeveloperKey).value.trim(); - config.ServiceSettings.EnableIncomingWebhooks = React.findDOMNode(this.refs.EnableIncomingWebhooks).checked; - config.ServiceSettings.EnablePostUsernameOverride = React.findDOMNode(this.refs.EnablePostUsernameOverride).checked; - config.ServiceSettings.EnablePostIconOverride = React.findDOMNode(this.refs.EnablePostIconOverride).checked; - config.ServiceSettings.EnableTesting = React.findDOMNode(this.refs.EnableTesting).checked; - config.ServiceSettings.EnableSecurityFixAlert = React.findDOMNode(this.refs.EnableSecurityFixAlert).checked; + config.ServiceSettings.SegmentDeveloperKey = ReactDOM.findDOMNode(this.refs.SegmentDeveloperKey).value.trim(); + config.ServiceSettings.GoogleDeveloperKey = ReactDOM.findDOMNode(this.refs.GoogleDeveloperKey).value.trim(); + config.ServiceSettings.EnableIncomingWebhooks = ReactDOM.findDOMNode(this.refs.EnableIncomingWebhooks).checked; + config.ServiceSettings.EnablePostUsernameOverride = ReactDOM.findDOMNode(this.refs.EnablePostUsernameOverride).checked; + config.ServiceSettings.EnablePostIconOverride = ReactDOM.findDOMNode(this.refs.EnablePostIconOverride).checked; + config.ServiceSettings.EnableTesting = ReactDOM.findDOMNode(this.refs.EnableTesting).checked; + config.ServiceSettings.EnableSecurityFixAlert = ReactDOM.findDOMNode(this.refs.EnableSecurityFixAlert).checked; - //config.ServiceSettings.EnableOAuthServiceProvider = React.findDOMNode(this.refs.EnableOAuthServiceProvider).checked; + //config.ServiceSettings.EnableOAuthServiceProvider = ReactDOM.findDOMNode(this.refs.EnableOAuthServiceProvider).checked; var MaximumLoginAttempts = 10; - if (!isNaN(parseInt(React.findDOMNode(this.refs.MaximumLoginAttempts).value, 10))) { - MaximumLoginAttempts = parseInt(React.findDOMNode(this.refs.MaximumLoginAttempts).value, 10); + if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaximumLoginAttempts).value, 10))) { + MaximumLoginAttempts = parseInt(ReactDOM.findDOMNode(this.refs.MaximumLoginAttempts).value, 10); } config.ServiceSettings.MaximumLoginAttempts = MaximumLoginAttempts; - React.findDOMNode(this.refs.MaximumLoginAttempts).value = MaximumLoginAttempts; + ReactDOM.findDOMNode(this.refs.MaximumLoginAttempts).value = MaximumLoginAttempts; Client.saveConfig( config, diff --git a/web/react/components/admin_console/sql_settings.jsx b/web/react/components/admin_console/sql_settings.jsx index 16a69e664..b43108bf7 100644 --- a/web/react/components/admin_console/sql_settings.jsx +++ b/web/react/components/admin_console/sql_settings.jsx @@ -29,27 +29,27 @@ export default class SqlSettings extends React.Component { $('#save-button').button('loading'); var config = this.props.config; - config.SqlSettings.Trace = React.findDOMNode(this.refs.Trace).checked; - config.SqlSettings.AtRestEncryptKey = React.findDOMNode(this.refs.AtRestEncryptKey).value.trim(); + config.SqlSettings.Trace = ReactDOM.findDOMNode(this.refs.Trace).checked; + config.SqlSettings.AtRestEncryptKey = ReactDOM.findDOMNode(this.refs.AtRestEncryptKey).value.trim(); if (config.SqlSettings.AtRestEncryptKey === '') { config.SqlSettings.AtRestEncryptKey = crypto.randomBytes(256).toString('base64').substring(0, 32); - React.findDOMNode(this.refs.AtRestEncryptKey).value = config.SqlSettings.AtRestEncryptKey; + ReactDOM.findDOMNode(this.refs.AtRestEncryptKey).value = config.SqlSettings.AtRestEncryptKey; } var MaxOpenConns = 10; - if (!isNaN(parseInt(React.findDOMNode(this.refs.MaxOpenConns).value, 10))) { - MaxOpenConns = parseInt(React.findDOMNode(this.refs.MaxOpenConns).value, 10); + if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaxOpenConns).value, 10))) { + MaxOpenConns = parseInt(ReactDOM.findDOMNode(this.refs.MaxOpenConns).value, 10); } config.SqlSettings.MaxOpenConns = MaxOpenConns; - React.findDOMNode(this.refs.MaxOpenConns).value = MaxOpenConns; + ReactDOM.findDOMNode(this.refs.MaxOpenConns).value = MaxOpenConns; var MaxIdleConns = 10; - if (!isNaN(parseInt(React.findDOMNode(this.refs.MaxIdleConns).value, 10))) { - MaxIdleConns = parseInt(React.findDOMNode(this.refs.MaxIdleConns).value, 10); + if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaxIdleConns).value, 10))) { + MaxIdleConns = parseInt(ReactDOM.findDOMNode(this.refs.MaxIdleConns).value, 10); } config.SqlSettings.MaxIdleConns = MaxIdleConns; - React.findDOMNode(this.refs.MaxIdleConns).value = MaxIdleConns; + ReactDOM.findDOMNode(this.refs.MaxIdleConns).value = MaxIdleConns; Client.saveConfig( config, @@ -79,7 +79,7 @@ export default class SqlSettings extends React.Component { return; } - React.findDOMNode(this.refs.AtRestEncryptKey).value = crypto.randomBytes(256).toString('base64').substring(0, 32); + ReactDOM.findDOMNode(this.refs.AtRestEncryptKey).value = crypto.randomBytes(256).toString('base64').substring(0, 32); var s = {saveNeeded: true, serverError: this.state.serverError}; this.setState(s); } @@ -220,7 +220,7 @@ export default class SqlSettings extends React.Component { <p className='help-text'>{'32-character salt available to encrypt and decrypt sensitive fields in database.'}</p> <div className='help-text'> <button - className='help-link' + className='btn btn-default' onClick={this.handleGenerate} > {'Re-Generate'} diff --git a/web/react/components/admin_console/team_settings.jsx b/web/react/components/admin_console/team_settings.jsx index a517c56fe..da4299714 100644 --- a/web/react/components/admin_console/team_settings.jsx +++ b/web/react/components/admin_console/team_settings.jsx @@ -27,17 +27,17 @@ export default class TeamSettings extends React.Component { $('#save-button').button('loading'); var config = this.props.config; - config.TeamSettings.SiteName = React.findDOMNode(this.refs.SiteName).value.trim(); - config.TeamSettings.RestrictCreationToDomains = React.findDOMNode(this.refs.RestrictCreationToDomains).value.trim(); - config.TeamSettings.EnableTeamCreation = React.findDOMNode(this.refs.EnableTeamCreation).checked; - config.TeamSettings.EnableUserCreation = React.findDOMNode(this.refs.EnableUserCreation).checked; + config.TeamSettings.SiteName = ReactDOM.findDOMNode(this.refs.SiteName).value.trim(); + config.TeamSettings.RestrictCreationToDomains = ReactDOM.findDOMNode(this.refs.RestrictCreationToDomains).value.trim(); + config.TeamSettings.EnableTeamCreation = ReactDOM.findDOMNode(this.refs.EnableTeamCreation).checked; + config.TeamSettings.EnableUserCreation = ReactDOM.findDOMNode(this.refs.EnableUserCreation).checked; var MaxUsersPerTeam = 50; - if (!isNaN(parseInt(React.findDOMNode(this.refs.MaxUsersPerTeam).value, 10))) { - MaxUsersPerTeam = parseInt(React.findDOMNode(this.refs.MaxUsersPerTeam).value, 10); + if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaxUsersPerTeam).value, 10))) { + MaxUsersPerTeam = parseInt(ReactDOM.findDOMNode(this.refs.MaxUsersPerTeam).value, 10); } config.TeamSettings.MaxUsersPerTeam = MaxUsersPerTeam; - React.findDOMNode(this.refs.MaxUsersPerTeam).value = MaxUsersPerTeam; + ReactDOM.findDOMNode(this.refs.MaxUsersPerTeam).value = MaxUsersPerTeam; Client.saveConfig( config, diff --git a/web/react/components/change_url_modal.jsx b/web/react/components/change_url_modal.jsx index f8db13392..714e93ff8 100644 --- a/web/react/components/change_url_modal.jsx +++ b/web/react/components/change_url_modal.jsx @@ -29,7 +29,7 @@ export default class ChangeUrlModal extends React.Component { } componentDidUpdate(prevProps) { if (this.props.show === true && prevProps.show === false) { - React.findDOMNode(this.refs.urlinput).select(); + ReactDOM.findDOMNode(this.refs.urlinput).select(); } } onURLChanged(e) { @@ -60,7 +60,7 @@ export default class ChangeUrlModal extends React.Component { doSubmit(e) { e.preventDefault(); - const url = React.findDOMNode(this.refs.urlinput).value; + const url = ReactDOM.findDOMNode(this.refs.urlinput).value; const cleanedURL = Utils.cleanUpUrlable(url); if (cleanedURL !== url || url.length < 2 || url.indexOf('__') > -1) { this.setState({urlError: this.getURLError(url)}); diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index e47db073d..7582de6c4 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -17,6 +17,9 @@ const AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); const Constants = require('../utils/constants.jsx'); const ActionTypes = Constants.ActionTypes; +const Popover = ReactBootstrap.Popover; +const OverlayTrigger = ReactBootstrap.OverlayTrigger; + export default class ChannelHeader extends React.Component { constructor(props) { super(props); @@ -110,7 +113,21 @@ export default class ChannelHeader extends React.Component { } const channel = this.state.channel; - const popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>); + const popoverContent = ( + <Popover + id='hader-popover' + bStyle='info' + bSize='large' + placement='bottom' + className='description' + onMouseOver={() => this.refs.descriptionOverlay.show()} + onMouseOut={() => this.refs.descriptionOverlay.hide()} + > + <MessageWrapper + message={channel.description} + /> + </Popover> + ); let channelTitle = channel.display_name; const currentId = UserStore.getCurrentId(); const isAdmin = Utils.isAdmin(this.state.memberChannel.roles) || Utils.isAdmin(this.state.memberTeam.roles); @@ -301,75 +318,82 @@ export default class ChannelHeader extends React.Component { return ( <table className='channel-header alt'> - <tr> - <th> - <div className='channel-header__info'> - <div className='dropdown'> + <tbody> + <tr> + <th> + <div className='channel-header__info'> + <div className='dropdown'> + <a + href='#' + className='dropdown-toggle theme' + type='button' + id='channel_header_dropdown' + data-toggle='dropdown' + aria-expanded='true' + > + <strong className='heading'>{channelTitle} </strong> + <span className='glyphicon glyphicon-chevron-down header-dropdown__icon' /> + </a> + <ul + className='dropdown-menu' + role='menu' + aria-labelledby='channel_header_dropdown' + > + {dropdownContents} + </ul> + </div> + <OverlayTrigger + trigger={['hover', 'focus']} + placement='bottom' + overlay={popoverContent} + ref='descriptionOverlay' + > + <div + onClick={TextFormatting.handleClick} + className='description' + dangerouslySetInnerHTML={{__html: TextFormatting.formatText(channel.description, {singleline: true, mentionHighlight: false})}} + /> + </OverlayTrigger> + </div> + </th> + <th> + <PopoverListMembers + members={this.state.users} + channelId={channel.id} + /> + </th> + <th className='search-bar__container'><NavbarSearchBox /></th> + <th> + <div className='dropdown channel-header__links'> <a href='#' className='dropdown-toggle theme' type='button' - id='channel_header_dropdown' + id='channel_header_right_dropdown' data-toggle='dropdown' aria-expanded='true' > - <strong className='heading'>{channelTitle} </strong> - <span className='glyphicon glyphicon-chevron-down header-dropdown__icon' /> + <span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} /> </a> <ul - className='dropdown-menu' + className='dropdown-menu dropdown-menu-right' role='menu' - aria-labelledby='channel_header_dropdown' + aria-labelledby='channel_header_right_dropdown' > - {dropdownContents} + <li role='presentation'> + <a + role='menuitem' + href='#' + onClick={this.searchMentions} + > + Recent Mentions + </a> + </li> </ul> </div> - <div - data-toggle='popover' - data-content={popoverContent} - className='description' - onClick={TextFormatting.handleClick} - dangerouslySetInnerHTML={{__html: TextFormatting.formatText(channel.description, {singleline: true, mentionHighlight: false})}} - /> - </div> - </th> - <th> - <PopoverListMembers - members={this.state.users} - channelId={channel.id} - /> - </th> - <th className='search-bar__container'><NavbarSearchBox /></th> - <th> - <div className='dropdown channel-header__links'> - <a - href='#' - className='dropdown-toggle theme' - type='button' - id='channel_header_right_dropdown' - data-toggle='dropdown' - aria-expanded='true' - > - <span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} /> - </a> - <ul - className='dropdown-menu dropdown-menu-right' - role='menu' - aria-labelledby='channel_header_right_dropdown' - > - <li role='presentation'> - <a - role='menuitem' - href='#' - onClick={this.searchMentions} - > - Recent Mentions - </a> - </li> - </ul> - </div> - </th> - </tr> + </th> + </tr> + </tbody> </table> ); } diff --git a/web/react/components/channel_info_modal.jsx b/web/react/components/channel_info_modal.jsx index d6de958e7..bccd8d0b9 100644 --- a/web/react/components/channel_info_modal.jsx +++ b/web/react/components/channel_info_modal.jsx @@ -15,7 +15,7 @@ export default class CommandList extends React.Component { componentDidMount() { var self = this; if (this.refs.modal) { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) { + $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) { var button = e.relatedTarget; self.setState({channel_id: $(button).attr('data-channelid')}); }); diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx index 1e621c683..7c7770095 100644 --- a/web/react/components/channel_invite_modal.jsx +++ b/web/react/components/channel_invite_modal.jsx @@ -56,8 +56,8 @@ export default class ChannelInviteModal extends React.Component { }; } componentDidMount() { - $(React.findDOMNode(this)).on('hidden.bs.modal', this.onHide); - $(React.findDOMNode(this)).on('show.bs.modal', this.onShow); + $(ReactDOM.findDOMNode(this)).on('hidden.bs.modal', this.onHide); + $(ReactDOM.findDOMNode(this)).on('show.bs.modal', this.onShow); ChannelStore.addExtraInfoChangeListener(this.onListenerChange); ChannelStore.addChangeListener(this.onListenerChange); diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx index d0d6ab5e2..270631db2 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -75,15 +75,6 @@ export default class ChannelLoader extends React.Component { Utils.applyTheme(Constants.THEMES.default); } - /* Setup global mouse events */ - $('body').on('click', function hidePopover(e) { - $('[data-toggle="popover"]').each(function eachPopover() { - if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) { - $(this).popover('hide'); - } - }); - }); - $('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) { if (ev.type === 'mouseenter') { $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after'); diff --git a/web/react/components/channel_members.jsx b/web/react/components/channel_members.jsx index 0cd384977..86cc2464d 100644 --- a/web/react/components/channel_members.jsx +++ b/web/react/components/channel_members.jsx @@ -74,9 +74,9 @@ export default class ChannelMembers extends React.Component { componentDidMount() { ChannelStore.addExtraInfoChangeListener(this.onChange); ChannelStore.addChangeListener(this.onChange); - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); + $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); + $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); } componentWillUnmount() { ChannelStore.removeExtraInfoChangeListener(this.onChange); diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx index 2114be905..6151d4bdd 100644 --- a/web/react/components/channel_notifications.jsx +++ b/web/react/components/channel_notifications.jsx @@ -51,7 +51,7 @@ export default class ChannelNotifications extends React.Component { componentDidMount() { ChannelStore.addChangeListener(this.onListenerChange); - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); + $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); } componentWillUnmount() { ChannelStore.removeChangeListener(this.onListenerChange); @@ -104,7 +104,7 @@ export default class ChannelNotifications extends React.Component { } handleUpdateNotifyLevel(notifyLevel) { this.setState({notifyLevel}); - React.findDOMNode(this.refs.modal).focus(); + ReactDOM.findDOMNode(this.refs.modal).focus(); } createNotifyLevelSection(serverError) { var handleUpdateSection; @@ -266,7 +266,7 @@ export default class ChannelNotifications extends React.Component { handleUpdateMarkUnreadLevel(markUnreadLevel) { this.setState({markUnreadLevel}); - React.findDOMNode(this.refs.modal).focus(); + ReactDOM.findDOMNode(this.refs.modal).focus(); } createMarkUnreadLevelSection(serverError) { diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 680d693f1..2df3dc40f 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -46,7 +46,9 @@ export default class CreateComment extends React.Component { componentDidUpdate(prevProps, prevState) { if (prevState.uploadsInProgress < this.state.uploadsInProgress) { $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight); - $('.post-right__scroll').perfectScrollbar('update'); + if ($(window).width() > 768) { + $('.post-right__scroll').perfectScrollbar('update'); + } } } handleSubmit(e) { @@ -126,7 +128,7 @@ export default class CreateComment extends React.Component { commentMsgKeyPress(e) { if (e.which === 13 && !e.shiftKey && !e.altKey) { e.preventDefault(); - React.findDOMNode(this.refs.textbox).blur(); + ReactDOM.findDOMNode(this.refs.textbox).blur(); this.handleSubmit(e); } @@ -189,7 +191,7 @@ export default class CreateComment extends React.Component { handleTextDrop(text) { const newText = this.state.messageText + text; this.handleUserInput(newText); - Utils.setCaretPosition(React.findDOMNode(this.refs.textbox.refs.message), newText.length); + Utils.setCaretPosition(ReactDOM.findDOMNode(this.refs.textbox.refs.message), newText.length); } removePreview(id) { let previews = this.state.previews; @@ -255,6 +257,17 @@ export default class CreateComment extends React.Component { postFooterClassName += ' has-error'; } + let uploadsInProgressText = null; + if (this.state.uploadsInProgress.length > 0) { + uploadsInProgressText = ( + <span + className='pull-right post-right-comments-upload-in-progress' + > + {this.state.uploadsInProgress.length === 1 ? 'File uploading' : 'Files uploading'} + </span> + ); + } + return ( <form onSubmit={this.handleSubmit}> <div className='post-create'> @@ -295,6 +308,7 @@ export default class CreateComment extends React.Component { value='Add Comment' onClick={this.handleSubmit} /> + {uploadsInProgressText} {postError} {serverError} </div> diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index f35079383..2581bdcca 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -16,6 +16,7 @@ const Utils = require('../utils/utils.jsx'); const Constants = require('../utils/constants.jsx'); const ActionTypes = Constants.ActionTypes; +const KeyCodes = Constants.KeyCodes; export default class CreatePost extends React.Component { constructor(props) { @@ -35,6 +36,7 @@ export default class CreatePost extends React.Component { this.removePreview = this.removePreview.bind(this); this.onChange = this.onChange.bind(this); this.getFileCount = this.getFileCount.bind(this); + this.handleArrowUp = this.handleArrowUp.bind(this); PostStore.clearDraftUploads(); @@ -172,9 +174,9 @@ export default class CreatePost extends React.Component { } } postMsgKeyPress(e) { - if (e.which === 13 && !e.shiftKey && !e.altKey) { + if (e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) { e.preventDefault(); - React.findDOMNode(this.refs.textbox).blur(); + ReactDOM.findDOMNode(this.refs.textbox).blur(); this.handleSubmit(e); } @@ -192,7 +194,7 @@ export default class CreatePost extends React.Component { PostStore.storeCurrentDraft(draft); } resizePostHolder() { - const height = $(window).height() - $(React.findDOMNode(this.refs.topDiv)).height() - 50; + const height = $(window).height() - $(ReactDOM.findDOMNode(this.refs.topDiv)).height() - 50; $('.post-list-holder-by-time').css('height', `${height}px`); $(window).trigger('resize'); if ($(window).width() > 960) { @@ -243,7 +245,7 @@ export default class CreatePost extends React.Component { handleTextDrop(text) { const newText = this.state.messageText + text; this.handleUserInput(newText); - Utils.setCaretPosition(React.findDOMNode(this.refs.textbox.refs.message), newText.length); + Utils.setCaretPosition(ReactDOM.findDOMNode(this.refs.textbox.refs.message), newText.length); } removePreview(id) { const previews = Object.assign([], this.state.previews); @@ -292,6 +294,27 @@ export default class CreatePost extends React.Component { const draft = PostStore.getDraft(channelId); return draft.previews.length + draft.uploadsInProgress.length; } + handleArrowUp(e) { + if (e.keyCode === KeyCodes.UP && this.state.messageText === '') { + e.preventDefault(); + + const channelId = ChannelStore.getCurrentId(); + const lastPost = PostStore.getCurrentUsersLatestPost(channelId); + if (!lastPost) { + return; + } + var type = (lastPost.root_id && lastPost.root_id.length > 0) ? 'Comment' : 'Post'; + + AppDispatcher.handleViewAction({ + type: ActionTypes.RECIEVED_EDIT_POST, + refocusId: '#post_textbox', + title: type, + message: lastPost.message, + postId: lastPost.id, + channelId: lastPost.channel_id + }); + } + } render() { let serverError = null; if (this.state.serverError) { @@ -336,6 +359,7 @@ export default class CreatePost extends React.Component { <Textbox onUserInput={this.handleUserInput} onKeyPress={this.postMsgKeyPress} + onKeyDown={this.handleArrowUp} onHeightChange={this.resizePostHolder} messageText={this.state.messageText} createMessage='Write a message...' diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx index 9358c98d7..b7d633b38 100644 --- a/web/react/components/delete_channel_modal.jsx +++ b/web/react/components/delete_channel_modal.jsx @@ -41,7 +41,7 @@ export default class DeleteChannelModal extends React.Component { }); } componentDidMount() { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); + $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); } render() { const channel = ChannelStore.getCurrent(); diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx index ea7d76b1e..3a3dabce5 100644 --- a/web/react/components/delete_post_modal.jsx +++ b/web/react/components/delete_post_modal.jsx @@ -73,7 +73,7 @@ export default class DeletePostModal extends React.Component { this.setState(newState); } componentDidMount() { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); + $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); PostStore.addSelectedPostChangeListener(this.onListenerChange); } componentWillUnmount() { diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx index 6ccf12be6..d63a1db30 100644 --- a/web/react/components/edit_channel_modal.jsx +++ b/web/react/components/edit_channel_modal.jsx @@ -34,7 +34,7 @@ export default class EditChannelModal extends React.Component { function handleUpdateSuccess() { this.setState({serverError: ''}); AsyncClient.getChannel(this.state.channelId); - $(React.findDOMNode(this.refs.modal)).modal('hide'); + $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide'); }.bind(this), function handleUpdateError(err) { if (err.message === 'Invalid channel_description parameter') { @@ -56,11 +56,11 @@ export default class EditChannelModal extends React.Component { this.setState({description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''}); } componentDidMount() { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); + $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); + $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); } componentWillUnmount() { - $(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose); + $(ReactDOM.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose); } render() { var serverError = null; diff --git a/web/react/components/edit_post_modal.jsx b/web/react/components/edit_post_modal.jsx index 165a28a64..90d9696e7 100644 --- a/web/react/components/edit_post_modal.jsx +++ b/web/react/components/edit_post_modal.jsx @@ -5,6 +5,7 @@ var Client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var Textbox = require('./textbox.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); +var PostStore = require('../stores/post_store.jsx'); export default class EditPostModal extends React.Component { constructor() { @@ -14,6 +15,7 @@ export default class EditPostModal extends React.Component { this.handleEditInput = this.handleEditInput.bind(this); this.handleEditKeyPress = this.handleEditKeyPress.bind(this); this.handleUserInput = this.handleUserInput.bind(this); + this.handleEditPostEvent = this.handleEditPostEvent.bind(this); this.state = {editText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: ''}; } @@ -35,16 +37,15 @@ export default class EditPostModal extends React.Component { Client.updatePost(updatedPost, function success() { - AsyncClient.getPosts(this.state.channel_id); + AsyncClient.getPosts(updatedPost.channel_id); window.scrollTo(0, 0); - }.bind(this), + }, function error(err) { AsyncClient.dispatchError(err, 'updatePost'); } ); $('#edit_post').modal('hide'); - $(this.state.refocusId).focus(); } handleEditInput(editMessage) { this.setState({editText: editMessage}); @@ -52,28 +53,57 @@ export default class EditPostModal extends React.Component { handleEditKeyPress(e) { if (e.which === 13 && !e.shiftKey && !e.altKey) { e.preventDefault(); - React.findDOMNode(this.refs.editbox).blur(); + ReactDOM.findDOMNode(this.refs.editbox).blur(); this.handleEdit(e); } } handleUserInput(e) { this.setState({editText: e.target.value}); } + handleEditPostEvent(options) { + this.setState({ + editText: options.message || '', + title: options.title || '', + post_id: options.postId || '', + channel_id: options.channelId || '', + comments: options.comments || 0, + refocusId: options.refocusId || '' + }); + + $(React.findDOMNode(this.refs.modal)).modal('show'); + } componentDidMount() { var self = this; - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function onHidden() { + $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function onHidden() { self.setState({editText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: ''}); }); - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function onShow(e) { + $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', function onShow(e) { var button = e.relatedTarget; + if (!button) { + return; + } 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-refoucsid')}); }); - $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function onShown() { + $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', function onShown() { self.refs.editbox.resize(); + $('#edit_textbox').get(0).focus(); }); + + $(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', function onShown() { + if (self.state.refocusId !== '') { + setTimeout(() => { + $(self.state.refocusId).get(0).focus(); + }); + } + }); + + PostStore.addEditPostListener(this.handleEditPostEvent); + } + componentWillUnmount() { + PostStore.removeEditPostListener(this.handleEditPostEvent); } render() { var error = (<div className='form-group'><br /></div>); diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx index 9be7f97f8..940b01f8d 100644 --- a/web/react/components/email_verify.jsx +++ b/web/react/components/email_verify.jsx @@ -39,11 +39,9 @@ export default class EmailVerify extends React.Component { return ( <div className='col-sm-12'> - <div className='panel panel-default verify_panel'> - <div className='panel-heading'> - <h3 className='panel-title'>{title}</h3> - </div> - <div className='panel-body'> + <div className='signup-team__container'> + <h3>{title}</h3> + <div> {body} {resend} {resendConfirm} diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index 659da4f5e..c6dff6550 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -43,7 +43,7 @@ export default class FileAttachment extends React.Component { return function loader() { $(this).remove(); if (name in self.refs) { - var imgDiv = React.findDOMNode(self.refs[name]); + var imgDiv = ReactDOM.findDOMNode(self.refs[name]); $(imgDiv).removeClass('post__load'); $(imgDiv).addClass('post__image'); @@ -82,7 +82,7 @@ export default class FileAttachment extends React.Component { if (nextState.fileSize !== this.state.fileSize) { if (this.refs.fileSize) { // update the UI element to display the file size without re-rendering the whole component - React.findDOMNode(this.refs.fileSize).innerHTML = utils.fileSizeToString(nextState.fileSize); + ReactDOM.findDOMNode(this.refs.fileSize).innerHTML = utils.fileSizeToString(nextState.fileSize); return false; } diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index e947fc50c..8854a54df 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -86,7 +86,7 @@ export default class FileUpload extends React.Component { } handleChange() { - var element = $(React.findDOMNode(this.refs.fileInput)); + var element = $(ReactDOM.findDOMNode(this.refs.fileInput)); this.uploadFiles(element.prop('files')); @@ -115,7 +115,7 @@ export default class FileUpload extends React.Component { } componentDidMount() { - var inputDiv = React.findDOMNode(this.refs.input); + var inputDiv = ReactDOM.findDOMNode(this.refs.input); var self = this; if (this.props.postType === 'post') { diff --git a/web/react/components/find_team.jsx b/web/react/components/find_team.jsx index 9e3e3a683..e324f3666 100644 --- a/web/react/components/find_team.jsx +++ b/web/react/components/find_team.jsx @@ -17,7 +17,7 @@ export default class FindTeam extends React.Component { var state = { }; - var email = React.findDOMNode(this.refs.email).value.trim().toLowerCase(); + var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); if (!email || !utils.isEmail(email)) { state.email_error = 'Please enter a valid email address'; this.setState(state); diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx index eb6bfa9b6..325e86f3d 100644 --- a/web/react/components/get_link_modal.jsx +++ b/web/react/components/get_link_modal.jsx @@ -22,12 +22,12 @@ export default class GetLinkModal extends React.Component { } componentDidMount() { if (this.refs.modal) { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); - $(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', this.onHide); + $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); + $(ReactDOM.findDOMNode(this.refs.modal)).on('hide.bs.modal', this.onHide); } } handleClick() { - var copyTextarea = $(React.findDOMNode(this.refs.textarea)); + var copyTextarea = $(ReactDOM.findDOMNode(this.refs.textarea)); copyTextarea.select(); try { diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx index c2f2c15ac..90290099d 100644 --- a/web/react/components/invite_member_modal.jsx +++ b/web/react/components/invite_member_modal.jsx @@ -36,7 +36,7 @@ export default class InviteMemberModal extends React.Component { var notEmpty = false; for (var i = 0; i < self.state.inviteIds.length; i++) { var index = self.state.inviteIds[i]; - if (React.findDOMNode(self.refs['email' + index]).value.trim() !== '') { + if (ReactDOM.findDOMNode(self.refs['email' + index]).value.trim() !== '') { notEmpty = true; break; } @@ -69,7 +69,7 @@ export default class InviteMemberModal extends React.Component { for (var i = 0; i < count; i++) { var index = inviteIds[i]; var invite = {}; - invite.email = React.findDOMNode(this.refs['email' + index]).value.trim(); + invite.email = ReactDOM.findDOMNode(this.refs['email' + index]).value.trim(); if (!invite.email || !utils.isEmail(invite.email)) { emailErrors[index] = 'Please enter a valid email address'; valid = false; @@ -77,9 +77,9 @@ export default class InviteMemberModal extends React.Component { emailErrors[index] = ''; } - invite.firstName = React.findDOMNode(this.refs['first_name' + index]).value.trim(); + invite.firstName = ReactDOM.findDOMNode(this.refs['first_name' + index]).value.trim(); - invite.lastName = React.findDOMNode(this.refs['last_name' + index]).value.trim(); + invite.lastName = ReactDOM.findDOMNode(this.refs['last_name' + index]).value.trim(); invites.push(invite); } @@ -95,8 +95,8 @@ export default class InviteMemberModal extends React.Component { Client.inviteMembers(data, function success() { - $(React.findDOMNode(this.refs.modal)).attr('data-confirm', 'true'); - $(React.findDOMNode(this.refs.modal)).modal('hide'); + $(ReactDOM.findDOMNode(this.refs.modal)).attr('data-confirm', 'true'); + $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide'); }.bind(this), function fail(err) { if (err.message === 'This person is already on your team') { @@ -110,8 +110,8 @@ export default class InviteMemberModal extends React.Component { } componentDidUpdate() { - $(React.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200); - $(React.findDOMNode(this.refs.modalBody)).css('overflow-y', 'scroll'); + $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200); + $(ReactDOM.findDOMNode(this.refs.modalBody)).css('overflow-y', 'scroll'); } addInviteFields() { @@ -126,9 +126,9 @@ export default class InviteMemberModal extends React.Component { for (var i = 0; i < inviteIds.length; i++) { var index = inviteIds[i]; - React.findDOMNode(this.refs['email' + index]).value = ''; - React.findDOMNode(this.refs['first_name' + index]).value = ''; - React.findDOMNode(this.refs['last_name' + index]).value = ''; + ReactDOM.findDOMNode(this.refs['email' + index]).value = ''; + ReactDOM.findDOMNode(this.refs['first_name' + index]).value = ''; + ReactDOM.findDOMNode(this.refs['last_name' + index]).value = ''; } this.setState({ diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index f81822e1e..c982d57ca 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -25,14 +25,14 @@ export default class Login extends React.Component { return; } - const email = React.findDOMNode(this.refs.email).value.trim(); + const email = ReactDOM.findDOMNode(this.refs.email).value.trim(); if (!email) { state.serverError = 'An email is required'; this.setState(state); return; } - const password = React.findDOMNode(this.refs.password).value.trim(); + const password = ReactDOM.findDOMNode(this.refs.password).value.trim(); if (!password) { state.serverError = 'A password is required'; this.setState(state); diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx index b9ba5f58c..8c1da942d 100644 --- a/web/react/components/mention_list.jsx +++ b/web/react/components/mention_list.jsx @@ -61,7 +61,7 @@ export default class MentionList extends React.Component { } onClick(e) { if (!($('#' + this.props.id).is(e.target) || $('#' + this.props.id).has(e.target).length || - ('mentionlist' in this.refs && $(React.findDOMNode(this.refs.mentionlist)).has(e.target).length))) { + ('mentionlist' in this.refs && $(ReactDOM.findDOMNode(this.refs.mentionlist)).has(e.target).length))) { this.setState({mentionText: '-1'}); } } diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx index 884b0d6ca..a20c5cad5 100644 --- a/web/react/components/more_channels.jsx +++ b/web/react/components/more_channels.jsx @@ -31,12 +31,12 @@ export default class MoreChannels extends React.Component { } componentDidMount() { ChannelStore.addMoreChangeListener(this.onListenerChange); - $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function shown() { + $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', function shown() { asyncClient.getMoreChannels(true); }); var self = this; - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) { + $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) { var button = e.relatedTarget; self.setState({channelType: $(button).attr('data-channeltype')}); }); @@ -54,7 +54,7 @@ export default class MoreChannels extends React.Component { this.setState({joiningChannel: channelIndex}); client.joinChannel(channel.id, function joinSuccess() { - $(React.findDOMNode(this.refs.modal)).modal('hide'); + $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide'); asyncClient.getChannel(channel.id); utils.switchChannel(channel); this.setState({joiningChannel: -1}); @@ -65,7 +65,7 @@ export default class MoreChannels extends React.Component { ); } handleNewChannel() { - $(React.findDOMNode(this.refs.modal)).modal('hide'); + $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide'); this.setState({showNewChannelModal: true}); } render() { diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx index bc610cd60..08b64de8b 100644 --- a/web/react/components/more_direct_channels.jsx +++ b/web/react/components/more_direct_channels.jsx @@ -1,133 +1,273 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var TeamStore = require('../stores/team_store.jsx'); -var Client = require('../utils/client.jsx'); -var Constants = require('../utils/constants.jsx'); -var AsyncClient = require('../utils/async_client.jsx'); -var PreferenceStore = require('../stores/preference_store.jsx'); -var utils = require('../utils/utils.jsx'); +const AsyncClient = require('../utils/async_client.jsx'); +const ChannelStore = require('../stores/channel_store.jsx'); +const Constants = require('../utils/constants.jsx'); +const Client = require('../utils/client.jsx'); +const Modal = ReactBootstrap.Modal; +const PreferenceStore = require('../stores/preference_store.jsx'); +const TeamStore = require('../stores/team_store.jsx'); +const UserStore = require('../stores/user_store.jsx'); +const Utils = require('../utils/utils.jsx'); export default class MoreDirectChannels extends React.Component { constructor(props) { super(props); - this.state = {channels: [], loadingDMChannel: -1}; + this.handleFilterChange = this.handleFilterChange.bind(this); + this.handleHide = this.handleHide.bind(this); + this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this); + this.handleUserChange = this.handleUserChange.bind(this); + + this.createRowForUser = this.createRowForUser.bind(this); + + this.state = { + users: this.getUsersFromStore(), + filter: '', + loadingDMChannel: -1 + }; + } + + getUsersFromStore() { + const currentId = UserStore.getCurrentId(); + const profiles = UserStore.getProfiles(); + const users = []; + + for (const id in profiles) { + if (id !== currentId) { + users.push(profiles[id]); + } + } + + users.sort((a, b) => a.username.localeCompare(b.username)); + + return users; } componentDidMount() { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', (e) => { - var button = e.relatedTarget; - this.setState({channels: $(button).data('channels')}); // eslint-disable-line react/no-did-mount-set-state - }); + UserStore.addChangeListener(this.handleUserChange); } - handleJoinDirectChannel(channel) { - const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, channel.teammate_id, 'true'); - AsyncClient.savePreferences([preference]); + componentWillUnmount() { + UserStore.addChangeListener(this.handleUserChange); } - render() { - var directMessageItems = this.state.channels.map((channel, index) => { - var badge = ''; - var titleClass = ''; - var handleClick = null; - - if (channel.fake) { - // It's a direct message channel that doesn't exist yet so let's create it now - var otherUserId = utils.getUserIdFromChannelName(channel); - - if (this.state.loadingDMChannel === index) { - badge = ( - <img - className='channel-loading-gif pull-right' - src='/static/images/load.gif' - /> - ); - } + handleFilterChange() { + const filter = ReactDOM.findDOMNode(this.refs.filter).value; - if (this.state.loadingDMChannel === -1) { - handleClick = (e) => { - e.preventDefault(); - this.setState({loadingDMChannel: index}); - this.handleJoinDirectChannel(channel); - - Client.createDirectChannel(channel, otherUserId, - (data) => { - $(React.findDOMNode(this.refs.modal)).modal('hide'); - this.setState({loadingDMChannel: -1}); - AsyncClient.getChannel(data.id); - utils.switchChannel(data); - }, - () => { - this.setState({loadingDMChannel: -1}); - window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name; - } - ); - }; - } - } else { - if (channel.unread) { - badge = <span className='badge pull-right small'>{channel.unread}</span>; - titleClass = 'unread-title'; + if (filter !== this.state.filter) { + this.setState({filter}); + } + } + + handleHide() { + if (this.props.onModalDismissed) { + this.props.onModalDismissed(); + } + + this.setState({filter: ''}); + } + + handleShowDirectChannel(teammate, e) { + if (this.state.loadingDMChannel !== -1) { + return; + } + + e.preventDefault(); + + const channelName = Utils.getDirectChannelName(UserStore.getCurrentId(), teammate.id); + let channel = ChannelStore.getByName(channelName); + + const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, teammate.id, 'true'); + AsyncClient.savePreferences([preference]); + + if (channel) { + Utils.switchChannel(channel); + + this.handleHide(); + } else { + this.setState({loadingDMChannel: teammate.id}); + + channel = { + name: channelName, + last_post_at: 0, + total_msg_count: 0, + type: 'D', + display_name: teammate.username, + teammate_id: teammate.id, + status: UserStore.getStatus(teammate.id) + }; + + Client.createDirectChannel( + channel, + teammate.id, + (data) => { + this.setState({loadingDMChannel: -1}); + + AsyncClient.getChannel(data.id); + Utils.switchChannel(data); + + this.handleHide(); + }, + () => { + this.setState({loadingDMChannel: -1}); + window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channelName; } + ); + } + } - handleClick = (e) => { - e.preventDefault(); - this.handleJoinDirectChannel(channel); - utils.switchChannel(channel); - $(React.findDOMNode(this.refs.modal)).modal('hide'); - }; - } + handleUserChange() { + this.setState({users: this.getUsersFromStore()}); + } + + createRowForUser(user) { + const details = []; + + const fullName = Utils.getFullName(user); + if (fullName) { + details.push( + <span + key={`${user.id}__full-name`} + className='full-name' + > + {fullName} + </span> + ); + } - return ( - <li key={channel.name}> - <a - className={'sidebar-channel ' + titleClass} - href='#' - onClick={handleClick} - >{badge}{channel.display_name}</a> - </li> + if (user.nickname) { + const separator = fullName ? ' - ' : ''; + details.push( + <span + key={`${user.nickname}__nickname`} + className='nickname' + > + {separator + user.nickname} + </span> ); - }); + } + + let joinButton; + if (this.state.loadingDMChannel === user.id) { + joinButton = ( + <img + className='channel-loading-gif' + src='/static/images/load.gif' + /> + ); + } else { + joinButton = ( + <button + type='button' + className='btn btn-primary btn-message' + onClick={this.handleShowDirectChannel.bind(this, user)} + > + {'Message'} + </button> + ); + } return ( - <div - className='modal fade' - id='more_direct_channels' - ref='modal' - tabIndex='-1' - role='dialog' - aria-hidden='true' + <li + key={user.id} + className='direct-channel' > - <div className='modal-dialog'> - <div className='modal-content'> - <div className='modal-header'> - <button - type='button' - className='close' - data-dismiss='modal' - > - <span aria-hidden='true'>{'×'}</span> - <span className='sr-only'>{'Close'}</span> - </button> - <h4 className='modal-title'>{'More Direct Messages'}</h4> - </div> - <div className='modal-body'> - <ul className='nav nav-pills nav-stacked'> - {directMessageItems} - </ul> - </div> - <div className='modal-footer'> - <button - type='button' - className='btn btn-default' - data-dismiss='modal' - >{'Close'}</button> - </div> + <div className='col-xs-1 image-div'> + <img + className='profile-image' + src={`/api/v1/users/${user.id}/image?time=${user.update_at}`} + /> + </div> + <div className='col-xs-9'> + <div className='username'> + {user.username} </div> + <div> + {details} + </div> + </div> + <div className='col-xs-2 btn-div'> + {joinButton} </div> - </div> + </li> + ); + } + + render() { + if (!this.props.show) { + return null; + } + + let users = this.state.users; + if (this.state.filter !== '') { + users = users.filter((user) => { + return user.username.indexOf(this.state.filter) !== -1 || + user.first_name.indexOf(this.state.filter) !== -1 || + user.last_name.indexOf(this.state.filter) !== -1 || + user.nickname.indexOf(this.state.filter) !== -1; + }); + } + + const userEntries = users.map(this.createRowForUser); + + if (userEntries.length === 0) { + userEntries.push(<li key='no-users-found'>{'No users found :('}</li>); + } + + let memberString = 'Member'; + if (users.length !== 1) { + memberString += 's'; + } + + let count; + if (users.length === this.state.users.length) { + count = `${users.length} ${memberString}`; + } else { + count = `${users.length} ${memberString} of ${this.state.users.length} Total`; + } + + return ( + <Modal + className='modal-direct-channels' + show={this.props.show} + bsSize='large' + onHide={this.handleHide} + > + <Modal.Header closeButton={true}> + <Modal.Title>{'More Direct Messages'}</Modal.Title> + </Modal.Header> + <Modal.Body> + <div> + <input + ref='filter' + className='form-control filter-textbox' + placeholder='Search members' + onInput={this.handleFilterChange} + style={{width: '200px', display: 'inline'}} + /> + <span className='member-count pull-right'>{count}</span> + </div> + <ul className='user-list'> + {userEntries} + </ul> + </Modal.Body> + <Modal.Footer> + <button + type='button' + className='btn btn-default' + onClick={this.handleHide} + > + {'Close'} + </button> + </Modal.Footer> + </Modal> ); } } + +MoreDirectChannels.propTypes = { + show: React.PropTypes.bool.isRequired, + onModalDismissed: React.PropTypes.func +}; diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx index 6503bd801..f9cd525fd 100644 --- a/web/react/components/navbar.jsx +++ b/web/react/components/navbar.jsx @@ -14,6 +14,9 @@ var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); +var Popover = ReactBootstrap.Popover; +var OverlayTrigger = ReactBootstrap.OverlayTrigger; + export default class Navbar extends React.Component { constructor(props) { super(props); @@ -36,12 +39,6 @@ export default class Navbar extends React.Component { ChannelStore.addChangeListener(this.onChange); ChannelStore.addExtraInfoChangeListener(this.onChange); $('.inner__wrap').click(this.hideSidebars); - - $('body').on('click.infopopover', function handlePopoverClick(e) { - if ($(e.target).attr('data-toggle') !== 'popover' && $(e.target).parents('.popover.in').length === 0) { - $('.info-popover').popover('hide'); - } - }); } componentWillUnmount() { ChannelStore.removeChangeListener(this.onChange); @@ -224,11 +221,15 @@ export default class Navbar extends React.Component { return ( <div className='navbar-brand'> <div className='dropdown'> - <div - data-toggle='popover' - data-content={popoverContent} - className='description info-popover' - /> + <OverlayTrigger + trigger='click' + placement='bottom' + overlay={popoverContent} + className='description' + rootClose={true} + > + <div className='description info-popover'/> + </OverlayTrigger> <a href='#' className='dropdown-toggle theme' @@ -330,11 +331,17 @@ export default class Navbar extends React.Component { var isDirect = false; if (channel) { - popoverContent = React.renderToString( - <MessageWrapper - message={channel.description} - options={{singleline: true, mentionHighlight: false}} - /> + popoverContent = ( + <Popover + bsStyle='info' + placement='bottom' + id='description-popover' + > + <MessageWrapper + message={channel.description} + options={{singleline: true, mentionHighlight: false}} + /> + </Popover> ); isAdmin = Utils.isAdmin(this.state.member.roles); @@ -354,19 +361,28 @@ export default class Navbar extends React.Component { } if (channel.description.length === 0) { - popoverContent = React.renderToString( - <div> - No channel description yet. <br/> - <a - href='#' - data-toggle='modal' - data-desc={channel.description} - data-title={channel.display_name} - data-channelid={channel.id} - data-target='#edit_channel' - > - Click here - </a> to add one.</div> + popoverContent = ( + <Popover + bsStyle='info' + placement='bottom' + id='description-popover' + > + <div> + {'No channel description yet.'} + <br/> + <a + href='#' + data-toggle='modal' + data-desc={channel.description} + data-title={channel.display_name} + data-channelid={channel.id} + data-target='#edit_channel' + > + {'Click here'} + </a> + {' to add one.'} + </div> + </Popover> ); } } diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx index 49d517419..e0410800f 100644 --- a/web/react/components/navbar_dropdown.jsx +++ b/web/react/components/navbar_dropdown.jsx @@ -40,7 +40,7 @@ export default class NavbarDropdown extends React.Component { UserStore.addTeamsChangeListener(this.onListenerChange); TeamStore.addChangeListener(this.onListenerChange); - $(React.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => { + $(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => { this.blockToggle = true; setTimeout(() => { this.blockToggle = false; @@ -51,7 +51,7 @@ export default class NavbarDropdown extends React.Component { UserStore.removeTeamsChangeListener(this.onListenerChange); TeamStore.removeChangeListener(this.onListenerChange); - $(React.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown'); + $(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown'); } onListenerChange() { var newState = getStateFromStores(); diff --git a/web/react/components/new_channel_modal.jsx b/web/react/components/new_channel_modal.jsx index c3d9c046d..4e6280c99 100644 --- a/web/react/components/new_channel_modal.jsx +++ b/web/react/components/new_channel_modal.jsx @@ -25,7 +25,7 @@ export default class NewChannelModal extends React.Component { handleSubmit(e) { e.preventDefault(); - const displayName = React.findDOMNode(this.refs.display_name).value.trim(); + const displayName = ReactDOM.findDOMNode(this.refs.display_name).value.trim(); if (displayName.length < 1) { this.setState({displayNameError: 'This field is required'}); return; @@ -35,8 +35,8 @@ export default class NewChannelModal extends React.Component { } handleChange() { const newData = { - displayName: React.findDOMNode(this.refs.display_name).value, - description: React.findDOMNode(this.refs.channel_desc).value + displayName: ReactDOM.findDOMNode(this.refs.display_name).value, + description: ReactDOM.findDOMNode(this.refs.channel_desc).value }; this.props.onDataChanged(newData); } diff --git a/web/react/components/password_reset_form.jsx b/web/react/components/password_reset_form.jsx index 6112adbaf..217f1b393 100644 --- a/web/react/components/password_reset_form.jsx +++ b/web/react/components/password_reset_form.jsx @@ -15,7 +15,7 @@ export default class PasswordResetForm extends React.Component { e.preventDefault(); var state = {}; - var password = React.findDOMNode(this.refs.password).value.trim(); + var password = ReactDOM.findDOMNode(this.refs.password).value.trim(); if (!password || password.length < 5) { state.error = 'Please enter at least 5 characters.'; this.setState(state); diff --git a/web/react/components/password_reset_send_link.jsx b/web/react/components/password_reset_send_link.jsx index f547499b0..8f1890705 100644 --- a/web/react/components/password_reset_send_link.jsx +++ b/web/react/components/password_reset_send_link.jsx @@ -16,7 +16,7 @@ export default class PasswordResetSendLink extends React.Component { e.preventDefault(); var state = {}; - var email = React.findDOMNode(this.refs.email).value.trim().toLowerCase(); + var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); if (!email || !Utils.isEmail(email)) { state.error = 'Please enter a valid email address.'; this.setState(state); @@ -33,7 +33,7 @@ export default class PasswordResetSendLink extends React.Component { client.sendPasswordReset(data, function passwordResetSent() { this.setState({error: null, updateText: <p>A password reset link has been sent to <b>{email}</b> for your <b>{this.props.teamDisplayName}</b> team on {window.location.hostname}.</p>, moreUpdateText: 'Please check your inbox.'}); - $(React.findDOMNode(this.refs.reset_form)).hide(); + $(ReactDOM.findDOMNode(this.refs.reset_form)).hide(); }.bind(this), function passwordResetFailedToSend(err) { this.setState({error: err.message, update_text: null, moreUpdateText: null}); diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx index 5d9052fd7..16ae693fa 100644 --- a/web/react/components/popover_list_members.jsx +++ b/web/react/components/popover_list_members.jsx @@ -2,6 +2,8 @@ // See License.txt for license information. var UserStore = require('../stores/user_store.jsx'); +var Popover = ReactBootstrap.Popover; +var OverlayTrigger = ReactBootstrap.OverlayTrigger; export default class PopoverListMembers extends React.Component { componentDidMount() { @@ -24,16 +26,9 @@ export default class PopoverListMembers extends React.Component { }); } }; - - $('#member_popover').popover({placement: 'bottom', trigger: 'click', html: true}); - $('body').on('click', function onClick(e) { - if (e.target.parentNode && $(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) { - $('#member_popover').popover('hide'); - } - }); } render() { - let popoverHtml = ''; + let popoverHtml = []; let count = 0; let countText = '-'; const members = this.props.members; @@ -46,7 +41,7 @@ export default class PopoverListMembers extends React.Component { members.forEach(function addMemberElement(m) { if (teamMembers[m.username] && teamMembers[m.username].delete_at <= 0) { - popoverHtml += `<div class='text--nowrap'>${m.username}</div>`; + popoverHtml.push(<div className='text--nowrap'>{m.username}</div>); count++; } }); @@ -59,15 +54,14 @@ export default class PopoverListMembers extends React.Component { } return ( - <div - id='member_popover' - data-toggle='popover' - data-content={popoverHtml} - data-original-title='Members' + <OverlayTrigger + trigger='click' + placement='bottom' + rootClose='true' + overlay={<Popover title='Members'>{popoverHtml}</Popover>} > - <div - id='member_tooltip' - > + <div id='member_popover'> + <div> {countText} <span className='fa fa-user' @@ -75,6 +69,7 @@ export default class PopoverListMembers extends React.Component { /> </div> </div> + </OverlayTrigger> ); } } diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index e87ac6743..1db0b12e7 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -37,7 +37,7 @@ export default class PostBody extends React.Component { } parseEmojis() { - twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE}); + twemoji.parse(ReactDOM.findDOMNode(this), {size: Constants.EMOJI_SIZE}); } componentDidMount() { diff --git a/web/react/components/post_deleted_modal.jsx b/web/react/components/post_deleted_modal.jsx index 87afc6a7f..ba07a22f5 100644 --- a/web/react/components/post_deleted_modal.jsx +++ b/web/react/components/post_deleted_modal.jsx @@ -15,7 +15,7 @@ export default class PostDeletedModal extends React.Component { this.state = {}; } componentDidMount() { - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', () => { + $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', () => { this.handleClose(); }); } diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index ebbe319b5..29728d368 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -109,7 +109,7 @@ export default class PostList extends React.Component { PreferenceStore.addChangeListener(this.onTimeChange); SocketStore.addChangeListener(this.onSocketChange); - const postHolder = $(React.findDOMNode(this.refs.postlist)); + const postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); $(window).resize(() => { this.resize(); @@ -135,7 +135,7 @@ export default class PostList extends React.Component { $('.top-visible-post').removeClass('top-visible-post'); - $(React.findDOMNode(this.refs.postlistcontent)).children().each(function select() { + $(ReactDOM.findDOMNode(this.refs.postlistcontent)).children().each(function select() { if ($(this).position().top + $(this).height() / 2 > 0) { $(this).addClass('top-visible-post'); return false; @@ -161,7 +161,7 @@ export default class PostList extends React.Component { PreferenceStore.removeChangeListener(this.onTimeChange); $('body').off('click.userpopover'); $(window).off('resize'); - var postHolder = $(React.findDOMNode(this.refs.postlist)); + var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); postHolder.off('scroll'); } componentDidUpdate(prevProps, prevState) { @@ -217,7 +217,7 @@ export default class PostList extends React.Component { } } componentWillUpdate() { - var postHolder = $(React.findDOMNode(this.refs.postlist)); + var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); this.prevScrollTop = postHolder.scrollTop(); } componentWillReceiveProps(nextProps) { @@ -228,7 +228,7 @@ export default class PostList extends React.Component { } } resize() { - const postHolder = $(React.findDOMNode(this.refs.postlist)); + const postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); if ($('#create_post').length > 0) { const height = $(window).height() - $('#create_post').height() - $('#error_bar').outerHeight() - 50; postHolder.css('height', height + 'px'); @@ -236,12 +236,12 @@ export default class PostList extends React.Component { } scrollTo(val) { this.isUserScroll = false; - var postHolder = $(React.findDOMNode(this.refs.postlist)); + var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); postHolder[0].scrollTop = val; } scrollToBottom(force) { this.isUserScroll = false; - var postHolder = $(React.findDOMNode(this.refs.postlist)); + var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); if ($('#new_message_' + this.props.channelId)[0] && !this.userHasSeenNew && !force) { $('#new_message_' + this.props.channelId)[0].scrollIntoView(); } else { @@ -599,14 +599,14 @@ export default class PostList extends React.Component { var order = this.state.postList.order; var channelId = this.props.channelId; - $(React.findDOMNode(this.refs.loadmore)).text('Retrieving more messages...'); + $(ReactDOM.findDOMNode(this.refs.loadmore)).text('Retrieving more messages...'); Client.getPostsPage( channelId, order.length, Constants.POST_CHUNK_SIZE, function success(data) { - $(React.findDOMNode(this.refs.loadmore)).text('Load more messages'); + $(ReactDOM.findDOMNode(this.refs.loadmore)).text('Load more messages'); this.gotMorePosts = true; this.setState({numToDisplay: this.state.numToDisplay + Constants.POST_CHUNK_SIZE}); @@ -631,7 +631,7 @@ export default class PostList extends React.Component { Client.getProfiles(); }.bind(this), function fail(err) { - $(React.findDOMNode(this.refs.loadmore)).text('Load more messages'); + $(ReactDOM.findDOMNode(this.refs.loadmore)).text('Load more messages'); AsyncClient.dispatchError(err, 'getPosts'); }.bind(this) ); diff --git a/web/react/components/register_app_modal.jsx b/web/react/components/register_app_modal.jsx index e93c44126..3d4d9bf45 100644 --- a/web/react/components/register_app_modal.jsx +++ b/web/react/components/register_app_modal.jsx @@ -14,7 +14,7 @@ export default class RegisterAppModal extends React.Component { this.state = {clientId: '', clientSecret: '', saved: false}; } componentDidMount() { - $(React.findDOMNode(this)).on('hide.bs.modal', this.onHide); + $(ReactDOM.findDOMNode(this)).on('hide.bs.modal', this.onHide); } register() { var state = this.state; @@ -22,7 +22,7 @@ export default class RegisterAppModal extends React.Component { var app = {}; - var name = this.refs.name.getDOMNode().value; + var name = this.refs.name.value; if (!name || name.length === 0) { state.nameError = 'Application name must be filled in.'; this.setState(state); @@ -31,7 +31,7 @@ export default class RegisterAppModal extends React.Component { state.nameError = null; app.name = name; - var homepage = this.refs.homepage.getDOMNode().value; + var homepage = this.refs.homepage.value; if (!homepage || homepage.length === 0) { state.homepageError = 'Homepage must be filled in.'; this.setState(state); @@ -40,10 +40,10 @@ export default class RegisterAppModal extends React.Component { state.homepageError = null; app.homepage = homepage; - var desc = this.refs.desc.getDOMNode().value; + var desc = this.refs.desc.value; app.description = desc; - var rawCallbacks = this.refs.callback.getDOMNode().value.trim(); + var rawCallbacks = this.refs.callback.value.trim(); if (!rawCallbacks || rawCallbacks.length === 0) { state.callbackError = 'At least one callback URL must be filled in.'; this.setState(state); @@ -73,7 +73,7 @@ export default class RegisterAppModal extends React.Component { this.setState({clientId: '', clientSecret: '', saved: false}); } save() { - this.setState({saved: this.refs.save.getDOMNode().checked}); + this.setState({saved: this.refs.save.checked}); } render() { var nameError; diff --git a/web/react/components/removed_from_channel_modal.jsx b/web/react/components/removed_from_channel_modal.jsx index ca35d6fb0..7cf0a2ef1 100644 --- a/web/react/components/removed_from_channel_modal.jsx +++ b/web/react/components/removed_from_channel_modal.jsx @@ -37,13 +37,13 @@ export default class RemovedFromChannelModal extends React.Component { } componentDidMount() { - $(React.findDOMNode(this)).on('show.bs.modal', this.handleShow); - $(React.findDOMNode(this)).on('hidden.bs.modal', this.handleClose); + $(ReactDOM.findDOMNode(this)).on('show.bs.modal', this.handleShow); + $(ReactDOM.findDOMNode(this)).on('hidden.bs.modal', this.handleClose); } componentWillUnmount() { - $(React.findDOMNode(this)).off('show.bs.modal', this.handleShow); - $(React.findDOMNode(this)).off('hidden.bs.modal', this.handleClose); + $(ReactDOM.findDOMNode(this)).off('show.bs.modal', this.handleShow); + $(ReactDOM.findDOMNode(this)).off('hidden.bs.modal', this.handleClose); } render() { diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx index 7f7d4554c..80f0956f2 100644 --- a/web/react/components/rename_channel_modal.jsx +++ b/web/react/components/rename_channel_modal.jsx @@ -76,13 +76,13 @@ export default class RenameChannelModal extends React.Component { Client.updateChannel(channel, function handleUpdateSuccess() { - $(React.findDOMNode(this.refs.modal)).modal('hide'); + $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide'); AsyncClient.getChannel(channel.id); Utils.updateAddressBar(channel.name); - React.findDOMNode(this.refs.displayName).value = ''; - React.findDOMNode(this.refs.channelName).value = ''; + ReactDOM.findDOMNode(this.refs.displayName).value = ''; + ReactDOM.findDOMNode(this.refs.channelName).value = ''; }.bind(this), function handleUpdateError(err) { state.serverError = err.message; @@ -92,15 +92,15 @@ export default class RenameChannelModal extends React.Component { ); } onNameChange() { - this.setState({channelName: React.findDOMNode(this.refs.channelName).value}); + this.setState({channelName: ReactDOM.findDOMNode(this.refs.channelName).value}); } onDisplayNameChange() { - this.setState({displayName: React.findDOMNode(this.refs.displayName).value}); + this.setState({displayName: ReactDOM.findDOMNode(this.refs.displayName).value}); } displayNameKeyUp() { - const displayName = React.findDOMNode(this.refs.displayName).value.trim(); + const displayName = ReactDOM.findDOMNode(this.refs.displayName).value.trim(); const channelName = Utils.cleanUpUrlable(displayName); - React.findDOMNode(this.refs.channelName).value = channelName; + ReactDOM.findDOMNode(this.refs.channelName).value = channelName; this.setState({channelName: channelName}); } handleClose() { @@ -119,11 +119,11 @@ export default class RenameChannelModal extends React.Component { this.setState({displayName: button.attr('data-display'), channelName: button.attr('data-name'), channelId: button.attr('data-channelid')}); } componentDidMount() { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.handleShow); - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); + $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.handleShow); + $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); } componentWillUnmount() { - $(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose); + $(ReactDOM.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose); } render() { let displayNameError = null; diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx index d1ed9b2c0..402e64080 100644 --- a/web/react/components/rhs_comment.jsx +++ b/web/react/components/rhs_comment.jsx @@ -55,7 +55,7 @@ export default class RhsComment extends React.Component { this.forceUpdate(); } parseEmojis() { - twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE}); + twemoji.parse(ReactDOM.findDOMNode(this), {size: Constants.EMOJI_SIZE}); } componentDidMount() { this.parseEmojis(); diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx index 442d3483e..a9f1fcd30 100644 --- a/web/react/components/rhs_root_post.jsx +++ b/web/react/components/rhs_root_post.jsx @@ -19,7 +19,7 @@ export default class RhsRootPost extends React.Component { this.state = {}; } parseEmojis() { - twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE}); + twemoji.parse(ReactDOM.findDOMNode(this), {size: Constants.EMOJI_SIZE}); } componentDidMount() { this.parseEmojis(); diff --git a/web/react/components/rhs_thread.jsx b/web/react/components/rhs_thread.jsx index 748cb16a8..467d74681 100644 --- a/web/react/components/rhs_thread.jsx +++ b/web/react/components/rhs_thread.jsx @@ -55,8 +55,6 @@ export default class RhsThread extends React.Component { if ($('.post-right__scroll')[0]) { $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight); } - - $('.post-right__scroll').perfectScrollbar('update'); this.resize(); } componentWillUnmount() { @@ -113,8 +111,10 @@ export default class RhsThread extends React.Component { var height = $(window).height() - $('#error_bar').outerHeight() - 100; $('.post-right__scroll').css('height', height + 'px'); $('.post-right__scroll').scrollTop(100000); - $('.post-right__scroll').perfectScrollbar(); - $('.post-right__scroll').perfectScrollbar('update'); + if ($(window).width() > 768) { + $('.post-right__scroll').perfectScrollbar(); + $('.post-right__scroll').perfectScrollbar('update'); + } } render() { var postList = this.state.postList; diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx index a6f9441ec..2e9764bd9 100644 --- a/web/react/components/search_bar.jsx +++ b/web/react/components/search_bar.jsx @@ -90,7 +90,7 @@ export default class SearchBar extends React.Component { function success(data) { this.setState({isSearching: false}); if (utils.isMobile()) { - React.findDOMNode(this.refs.search).value = ''; + ReactDOM.findDOMNode(this.refs.search).value = ''; } AppDispatcher.handleServerAction({ diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx index 5eea3c501..e55fd3752 100644 --- a/web/react/components/search_results.jsx +++ b/web/react/components/search_results.jsx @@ -56,7 +56,9 @@ export default class SearchResults extends React.Component { var height = $(window).height() - $('#error_bar').outerHeight() - 100; $('#search-items-container').css('height', height + 'px'); $('#search-items-container').scrollTop(0); - $('#search-items-container').perfectScrollbar(); + if ($(window).width() > 768) { + $('#search-items-container').perfectScrollbar(); + } } render() { diff --git a/web/react/components/setting_picture.jsx b/web/react/components/setting_picture.jsx index c9031e67c..2f577fe39 100644 --- a/web/react/components/setting_picture.jsx +++ b/web/react/components/setting_picture.jsx @@ -12,7 +12,7 @@ export default class SettingPicture extends React.Component { if (file) { var reader = new FileReader(); - var img = React.findDOMNode(this.refs.image); + var img = ReactDOM.findDOMNode(this.refs.image); reader.onload = function load(e) { $(img).attr('src', e.target.result); }; diff --git a/web/react/components/setting_upload.jsx b/web/react/components/setting_upload.jsx index c7107b84d..a25789dff 100644 --- a/web/react/components/setting_upload.jsx +++ b/web/react/components/setting_upload.jsx @@ -37,7 +37,7 @@ export default class SettingsUpload extends React.Component { doSubmit(e) { e.preventDefault(); - var inputnode = React.findDOMNode(this.refs.uploadinput); + var inputnode = ReactDOM.findDOMNode(this.refs.uploadinput); if (inputnode.files && inputnode.files[0]) { this.props.submit(inputnode.files[0]); } else { diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 4911f17ef..7a209de2b 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -8,6 +8,7 @@ const Client = require('../utils/client.jsx'); const Constants = require('../utils/constants.jsx'); const PreferenceStore = require('../stores/preference_store.jsx'); const NewChannelFlow = require('./new_channel_flow.jsx'); +const MoreDirectChannels = require('./more_direct_channels.jsx'); const SearchBox = require('./search_bar.jsx'); const SidebarHeader = require('./sidebar_header.jsx'); const SocketStore = require('../stores/socket_store.jsx'); @@ -33,12 +34,19 @@ export default class Sidebar extends React.Component { this.onResize = this.onResize.bind(this); this.updateUnreadIndicators = this.updateUnreadIndicators.bind(this); this.handleLeaveDirectChannel = this.handleLeaveDirectChannel.bind(this); + + this.showNewChannelModal = this.showNewChannelModal.bind(this); + this.hideNewChannelModal = this.hideNewChannelModal.bind(this); + this.showMoreDirectChannelsModal = this.showMoreDirectChannelsModal.bind(this); + this.hideMoreDirectChannelsModal = this.hideMoreDirectChannelsModal.bind(this); + this.createChannelElement = this.createChannelElement.bind(this); this.isLeaving = new Map(); const state = this.getStateFromStores(); - state.modal = ''; + state.newChannelModalType = ''; + state.showMoreDirectChannelsModal = false; state.loadingDMChannel = -1; this.state = state; @@ -47,10 +55,11 @@ export default class Sidebar extends React.Component { const members = ChannelStore.getAllMembers(); var teamMemberMap = UserStore.getActiveOnlyProfiles(); var currentId = ChannelStore.getCurrentId(); + const currentUserId = UserStore.getCurrentId(); var teammates = []; for (var id in teamMemberMap) { - if (id === UserStore.getCurrentId()) { + if (id === currentUserId) { continue; } teammates.push(teamMemberMap[id]); @@ -58,22 +67,16 @@ export default class Sidebar extends React.Component { const preferences = PreferenceStore.getPreferences(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW); - // Create lists of all read and unread direct channels var visibleDirectChannels = []; - var hiddenDirectChannels = []; + var hiddenDirectChannelCount = 0; for (var i = 0; i < teammates.length; i++) { const teammate = teammates[i]; - if (teammate.id === UserStore.getCurrentId()) { + if (teammate.id === currentUserId) { continue; } - var channelName = ''; - if (teammate.id > UserStore.getCurrentId()) { - channelName = UserStore.getCurrentId() + '__' + teammate.id; - } else { - channelName = teammate.id + '__' + UserStore.getCurrentId(); - } + const channelName = Utils.getDirectChannelName(currentUserId, teammate.id); let forceShow = false; let channel = ChannelStore.getByName(channelName); @@ -106,19 +109,18 @@ export default class Sidebar extends React.Component { visibleDirectChannels.push(channel); } else { - hiddenDirectChannels.push(channel); + hiddenDirectChannelCount += 1; } } visibleDirectChannels.sort(this.sortChannelsByDisplayName); - hiddenDirectChannels.sort(this.sortChannelsByDisplayName); return { activeId: currentId, channels: ChannelStore.getAll(), members, visibleDirectChannels, - hiddenDirectChannels + hiddenDirectChannelCount }; } @@ -132,7 +134,9 @@ export default class Sidebar extends React.Component { AsyncClient.getDirectChannelPreferences(); - $('.nav-pills__container').perfectScrollbar(); + if ($(window).width() > 768) { + $('.nav-pills__container').perfectScrollbar(); + } this.updateTitle(); this.updateUnreadIndicators(); @@ -280,13 +284,13 @@ export default class Sidebar extends React.Component { this.updateUnreadIndicators(); } updateUnreadIndicators() { - const container = $(React.findDOMNode(this.refs.container)); + const container = $(ReactDOM.findDOMNode(this.refs.container)); var showTopUnread = false; var showBottomUnread = false; if (this.firstUnreadChannel) { - var firstUnreadElement = $(React.findDOMNode(this.refs[this.firstUnreadChannel])); + var firstUnreadElement = $(ReactDOM.findDOMNode(this.refs[this.firstUnreadChannel])); if (firstUnreadElement.position().top + firstUnreadElement.height() < 0) { showTopUnread = true; @@ -294,7 +298,7 @@ export default class Sidebar extends React.Component { } if (this.lastUnreadChannel) { - var lastUnreadElement = $(React.findDOMNode(this.refs[this.lastUnreadChannel])); + var lastUnreadElement = $(ReactDOM.findDOMNode(this.refs[this.lastUnreadChannel])); if (lastUnreadElement.position().top > container.height()) { showBottomUnread = true; @@ -336,6 +340,20 @@ export default class Sidebar extends React.Component { return a.display_name.localeCompare(b.display_name); } + showNewChannelModal(type) { + this.setState({newChannelModalType: type}); + } + hideNewChannelModal() { + this.setState({newChannelModalType: ''}); + } + + showMoreDirectChannelsModal() { + this.setState({showDirectChannelsModal: true}); + } + hideMoreDirectChannelsModal() { + this.setState({showDirectChannelsModal: false}); + } + createChannelElement(channel, index, arr, handleClose) { var members = this.state.members; var activeId = this.state.activeId; @@ -532,25 +550,21 @@ export default class Sidebar extends React.Component { head.appendChild(link); var directMessageMore = null; - if (this.state.hiddenDirectChannels.length > 0) { + if (this.state.hiddenDirectChannelCount > 0) { directMessageMore = ( <li key='more'> <a - key={`more${this.state.hiddenDirectChannels.length}`} href='#' - data-toggle='modal' - className='nav-more' - data-target='#more_direct_channels' - data-channels={JSON.stringify(this.state.hiddenDirectChannels)} + onClick={this.showMoreDirectChannelsModal} > - {'More (' + this.state.hiddenDirectChannels.length + ')'} + {'More (' + this.state.hiddenDirectChannelCount + ')'} </a> </li> ); } let showChannelModal = false; - if (this.state.modal !== '') { + if (this.state.newChannelModalType !== '') { showChannelModal = true; } @@ -561,9 +575,14 @@ export default class Sidebar extends React.Component { <div> <NewChannelFlow show={showChannelModal} - channelType={this.state.modal} - onModalDismissed={() => this.setState({modal: ''})} + channelType={this.state.newChannelModalType} + onModalDismissed={this.hideNewChannelModal} + /> + <MoreDirectChannels + show={this.state.showDirectChannelsModal} + onModalDismissed={this.hideMoreDirectChannelsModal} /> + <SidebarHeader teamDisplayName={this.props.teamDisplayName} teamName={this.props.teamName} @@ -599,7 +618,7 @@ export default class Sidebar extends React.Component { <a className='add-channel-btn' href='#' - onClick={() => this.setState({modal: 'O'})} + onClick={this.showNewChannelModal.bind(this, 'O')} > {'+'} </a> @@ -632,7 +651,7 @@ export default class Sidebar extends React.Component { <a className='add-channel-btn' href='#' - onClick={() => this.setState({modal: 'P'})} + onClick={this.showNewChannelModal.bind(this, 'P')} > {'+'} </a> diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 98f862e69..f74c29d27 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -28,7 +28,7 @@ export default class SignupUserComplete extends React.Component { handleSubmit(e) { e.preventDefault(); - const providedEmail = React.findDOMNode(this.refs.email).value.trim(); + const providedEmail = ReactDOM.findDOMNode(this.refs.email).value.trim(); if (!providedEmail) { this.setState({nameError: '', emailError: 'This field is required', passwordError: ''}); return; @@ -39,7 +39,7 @@ export default class SignupUserComplete extends React.Component { return; } - const providedUsername = React.findDOMNode(this.refs.name).value.trim().toLowerCase(); + const providedUsername = ReactDOM.findDOMNode(this.refs.name).value.trim().toLowerCase(); if (!providedUsername) { this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''}); return; @@ -59,7 +59,7 @@ export default class SignupUserComplete extends React.Component { return; } - const providedPassword = React.findDOMNode(this.refs.password).value.trim(); + const providedPassword = ReactDOM.findDOMNode(this.refs.password).value.trim(); if (!providedPassword || providedPassword.length < 5) { this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''}); return; diff --git a/web/react/components/team_members.jsx b/web/react/components/team_members.jsx index 0f60d93b2..33590c89a 100644 --- a/web/react/components/team_members.jsx +++ b/web/react/components/team_members.jsx @@ -44,11 +44,11 @@ export default class TeamMembers extends React.Component { UserStore.addChangeListener(this.onChange); var self = this; - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function show() { + $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function show() { self.setState({render_members: false}); }); - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function hide() { + $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', function hide() { self.setState({render_members: true}); }); } diff --git a/web/react/components/team_signup_display_name_page.jsx b/web/react/components/team_signup_display_name_page.jsx index c91ed0811..4d08274e4 100644 --- a/web/react/components/team_signup_display_name_page.jsx +++ b/web/react/components/team_signup_display_name_page.jsx @@ -21,7 +21,7 @@ export default class TeamSignupDisplayNamePage extends React.Component { submitNext(e) { e.preventDefault(); - var displayName = React.findDOMNode(this.refs.name).value.trim(); + var displayName = ReactDOM.findDOMNode(this.refs.name).value.trim(); if (!displayName) { this.setState({nameError: 'This field is required'}); return; diff --git a/web/react/components/team_signup_email_item.jsx b/web/react/components/team_signup_email_item.jsx index 7253e80e9..1d2b24ed7 100644 --- a/web/react/components/team_signup_email_item.jsx +++ b/web/react/components/team_signup_email_item.jsx @@ -13,10 +13,10 @@ export default class TeamSignupEmailItem extends React.Component { this.state = {}; } getValue() { - return React.findDOMNode(this.refs.email).value.trim(); + return ReactDOM.findDOMNode(this.refs.email).value.trim(); } validate(teamEmail) { - const email = React.findDOMNode(this.refs.email).value.trim().toLowerCase(); + const email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); if (!email) { return true; diff --git a/web/react/components/team_signup_password_page.jsx b/web/react/components/team_signup_password_page.jsx index cb9a9f05b..daa898b53 100644 --- a/web/react/components/team_signup_password_page.jsx +++ b/web/react/components/team_signup_password_page.jsx @@ -22,7 +22,7 @@ export default class TeamSignupPasswordPage extends React.Component { submitNext(e) { e.preventDefault(); - var password = React.findDOMNode(this.refs.password).value.trim(); + var password = ReactDOM.findDOMNode(this.refs.password).value.trim(); if (!password || password.length < 5) { this.setState({passwordError: 'Please enter at least 5 characters'}); return; diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/team_signup_url_page.jsx index 3fb0aaa27..67e4c9dd7 100644 --- a/web/react/components/team_signup_url_page.jsx +++ b/web/react/components/team_signup_url_page.jsx @@ -23,7 +23,7 @@ export default class TeamSignupUrlPage extends React.Component { submitNext(e) { e.preventDefault(); - const name = React.findDOMNode(this.refs.name).value.trim(); + const name = ReactDOM.findDOMNode(this.refs.name).value.trim(); if (!name) { this.setState({nameError: 'This field is required'}); return; diff --git a/web/react/components/team_signup_username_page.jsx b/web/react/components/team_signup_username_page.jsx index 82dabad3d..fa8a031a0 100644 --- a/web/react/components/team_signup_username_page.jsx +++ b/web/react/components/team_signup_username_page.jsx @@ -21,7 +21,7 @@ export default class TeamSignupUsernamePage extends React.Component { submitNext(e) { e.preventDefault(); - var name = React.findDOMNode(this.refs.name).value.trim().toLowerCase(); + var name = ReactDOM.findDOMNode(this.refs.name).value.trim().toLowerCase(); var usernameError = Utils.isValidUsername(name); if (usernameError === 'Cannot use a reserved word as a username.') { diff --git a/web/react/components/team_signup_welcome_page.jsx b/web/react/components/team_signup_welcome_page.jsx index 2d7ef081f..1e9d8df0a 100644 --- a/web/react/components/team_signup_welcome_page.jsx +++ b/web/react/components/team_signup_welcome_page.jsx @@ -36,7 +36,7 @@ export default class TeamSignupWelcomePage extends React.Component { var state = {useDiff: true, serverError: ''}; - var email = React.findDOMNode(this.refs.email).value.trim().toLowerCase(); + var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); if (!email || !Utils.isEmail(email)) { state.emailError = 'Please enter a valid email address'; this.setState(state); diff --git a/web/react/components/team_signup_with_email.jsx b/web/react/components/team_signup_with_email.jsx index ba32a9f97..ff4ccd4d8 100644 --- a/web/react/components/team_signup_with_email.jsx +++ b/web/react/components/team_signup_with_email.jsx @@ -17,7 +17,7 @@ export default class EmailSignUpPage extends React.Component { var team = {}; var state = {serverError: ''}; - team.email = React.findDOMNode(this.refs.email).value.trim().toLowerCase(); + team.email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); if (!team.email || !Utils.isEmail(team.email)) { state.emailError = 'Please enter a valid email address'; state.inValid = true; diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx index 6ccf762c1..a0ccdf2c7 100644 --- a/web/react/components/team_signup_with_sso.jsx +++ b/web/react/components/team_signup_with_sso.jsx @@ -54,7 +54,7 @@ export default class SSOSignUpPage extends React.Component { ); } nameChange() { - this.setState({name: React.findDOMNode(this.refs.teamname).value.trim()}); + this.setState({name: ReactDOM.findDOMNode(this.refs.teamname).value.trim()}); } render() { var nameError = null; diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx index 741dbcd5d..86bb42f62 100644 --- a/web/react/components/textbox.jsx +++ b/web/react/components/textbox.jsx @@ -9,6 +9,7 @@ const ErrorStore = require('../stores/error_store.jsx'); const Utils = require('../utils/utils.jsx'); const Constants = require('../utils/constants.jsx'); const ActionTypes = Constants.ActionTypes; +const KeyCodes = Constants.KeyCodes; export default class Textbox extends React.Component { constructor(props) { @@ -83,7 +84,7 @@ export default class Textbox extends React.Component { componentDidUpdate() { if (this.caret >= 0) { - Utils.setCaretPosition(React.findDOMNode(this.refs.message), this.caret); + Utils.setCaretPosition(ReactDOM.findDOMNode(this.refs.message), this.caret); this.caret = -1; } if (this.doProcessMentions) { @@ -97,7 +98,7 @@ export default class Textbox extends React.Component { if (!this.addedMention) { this.checkForNewMention(nextProps.messageText); } - const text = React.findDOMNode(this.refs.message).value; + const text = ReactDOM.findDOMNode(this.refs.message).value; if (nextProps.channelId !== this.props.channelId || nextProps.messageText !== text) { this.doProcessMentions = true; } @@ -117,11 +118,11 @@ export default class Textbox extends React.Component { } handleChange() { - this.props.onUserInput(React.findDOMNode(this.refs.message).value); + this.props.onUserInput(ReactDOM.findDOMNode(this.refs.message).value); } handleKeyPress(e) { - const text = React.findDOMNode(this.refs.message).value; + const text = ReactDOM.findDOMNode(this.refs.message).value; if (!this.refs.commands.isEmpty() && text.indexOf('/') === 0 && e.which === 13) { this.refs.commands.addFirstCommand(); @@ -130,7 +131,7 @@ export default class Textbox extends React.Component { } if (!this.doProcessMentions) { - const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message)); + const caret = Utils.getCaretPosition(ReactDOM.findDOMNode(this.refs.message)); const preText = text.substring(0, caret); const lastSpace = preText.lastIndexOf(' '); const lastAt = preText.lastIndexOf('@'); @@ -144,17 +145,19 @@ export default class Textbox extends React.Component { } handleKeyDown(e) { - if (Utils.getSelectedText(React.findDOMNode(this.refs.message)) !== '') { + if (Utils.getSelectedText(ReactDOM.findDOMNode(this.refs.message)) !== '') { this.doProcessMentions = true; } - if (e.keyCode === 8) { + if (e.keyCode === KeyCodes.BACKSPACE) { this.handleBackspace(e); + } else if (this.props.onKeyDown) { + this.props.onKeyDown(e); } } handleBackspace() { - const text = React.findDOMNode(this.refs.message).value; + const text = ReactDOM.findDOMNode(this.refs.message).value; if (text.indexOf('/') === 0) { this.refs.commands.getSuggestedCommands(text.substring(0, text.length - 1)); } @@ -163,7 +166,7 @@ export default class Textbox extends React.Component { return; } - const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message)); + const caret = Utils.getCaretPosition(ReactDOM.findDOMNode(this.refs.message)); const preText = text.substring(0, caret); const lastSpace = preText.lastIndexOf(' '); const lastAt = preText.lastIndexOf('@'); @@ -174,7 +177,7 @@ export default class Textbox extends React.Component { } checkForNewMention(text) { - const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message)); + const caret = Utils.getCaretPosition(ReactDOM.findDOMNode(this.refs.message)); const preText = text.substring(0, caret); @@ -201,7 +204,7 @@ export default class Textbox extends React.Component { } addMention(name) { - const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message)); + const caret = Utils.getCaretPosition(ReactDOM.findDOMNode(this.refs.message)); const text = this.props.messageText; @@ -224,14 +227,14 @@ export default class Textbox extends React.Component { } addCommand(cmd) { - const elm = React.findDOMNode(this.refs.message); + const elm = ReactDOM.findDOMNode(this.refs.message); elm.value = cmd; this.handleChange(); } resize() { - const e = React.findDOMNode(this.refs.message); - const w = React.findDOMNode(this.refs.wrapper); + const e = ReactDOM.findDOMNode(this.refs.message); + const w = ReactDOM.findDOMNode(this.refs.wrapper); const prevHeight = $(e).height(); @@ -259,14 +262,14 @@ export default class Textbox extends React.Component { } handleFocus() { - const elm = React.findDOMNode(this.refs.message); + const elm = ReactDOM.findDOMNode(this.refs.message); if (elm.title === elm.value) { elm.value = ''; } } handleBlur() { - const elm = React.findDOMNode(this.refs.message); + const elm = ReactDOM.findDOMNode(this.refs.message); if (elm.value === '') { elm.value = elm.title; } @@ -318,5 +321,6 @@ Textbox.propTypes = { onUserInput: React.PropTypes.func.isRequired, onKeyPress: React.PropTypes.func.isRequired, onHeightChange: React.PropTypes.func, - createMessage: React.PropTypes.string.isRequired + createMessage: React.PropTypes.string.isRequired, + onKeyDown: React.PropTypes.func }; diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx index cc6165c1b..715161b4f 100644 --- a/web/react/components/user_profile.jsx +++ b/web/react/components/user_profile.jsx @@ -3,6 +3,8 @@ var Utils = require('../utils/utils.jsx'); var UserStore = require('../stores/user_store.jsx'); +var Popover = ReactBootstrap.Popover; +var OverlayTrigger = ReactBootstrap.OverlayTrigger; var id = 0; @@ -32,7 +34,6 @@ export default class UserProfile extends React.Component { componentDidMount() { UserStore.addChangeListener(this.onChange); if (!this.props.disablePopover) { - $('#profile_' + this.uniqueId).popover({placement: 'right', container: 'body', trigger: 'click hover', html: true, delay: {show: 200, hide: 100}}); $('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'}); } } @@ -62,23 +63,46 @@ export default class UserProfile extends React.Component { return <div>{name}</div>; } - var dataContent = '<img class="user-popover__image" src="/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at + '" height="128" width="128" />'; + var dataContent = []; + dataContent.push( + <img className='user-popover__image' + src={'/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at} + height='128' + width='128' + /> + ); if (!global.window.config.ShowEmailAddress === 'true') { - dataContent += '<div class="text-nowrap">Email not shared</div>'; + dataContent.push(<div className='text-nowrap'>{'Email not shared'}</div>); } else { - dataContent += '<div data-toggle="tooltip" title="' + this.state.profile.email + '"><a href="mailto:' + this.state.profile.email + '" class="text-nowrap text-lowercase user-popover__email">' + this.state.profile.email + '</a></div>'; + dataContent.push( + <div + data-toggle='tooltip' + title="' + this.state.profile.email + '" + > + <a + href="mailto:' + this.state.profile.email + '" + className='text-nowrap text-lowercase user-popover__email' + > + {this.state.profile.email} + </a> + </div> + ); } return ( + <OverlayTrigger + trigger='click' + placement='right' + rootClose='true' + overlay={<Popover title={this.state.profile.username}>{dataContent}</Popover>} + > <div className='user-popover' id={'profile_' + this.uniqueId} - data-toggle='popover' - data-content={dataContent} - data-original-title={this.state.profile.username} > {name} </div> + </OverlayTrigger> ); } } diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx index de0663874..1a9ac0ad3 100644 --- a/web/react/components/user_settings/import_theme_modal.jsx +++ b/web/react/components/user_settings/import_theme_modal.jsx @@ -35,7 +35,7 @@ export default class ImportThemeModal extends React.Component { handleSubmit(e) { e.preventDefault(); - const text = React.findDOMNode(this.refs.input).value; + const text = ReactDOM.findDOMNode(this.refs.input).value; if (!this.isInputValid(text)) { this.setState({inputError: 'Invalid format, please try copying and pasting in again.'}); diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx index a16440d55..7f363e92e 100644 --- a/web/react/components/user_settings/user_settings_appearance.jsx +++ b/web/react/components/user_settings/user_settings_appearance.jsx @@ -29,14 +29,14 @@ export default class UserSettingsAppearance extends React.Component { UserStore.addChangeListener(this.onChange); if (this.props.activeSection === 'theme') { - $(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); + $(ReactDOM.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); } $('#user_settings').on('hidden.bs.modal', this.handleClose); } componentDidUpdate() { if (this.props.activeSection === 'theme') { $('.color-btn').removeClass('active-border'); - $(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); + $(ReactDOM.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); } } componentWillUnmount() { diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx index 0e872315d..9c03f77a6 100644 --- a/web/react/components/user_settings/user_settings_general.jsx +++ b/web/react/components/user_settings/user_settings_general.jsx @@ -211,7 +211,7 @@ export default class UserSettingsGeneralTab extends React.Component { this.props.updateSection(section); } handleClose() { - $(React.findDOMNode(this)).find('.form-control').each(function clearForms() { + $(ReactDOM.findDOMNode(this)).find('.form-control').each(function clearForms() { this.value = ''; }); diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx index 4728a33ee..4dbb9b96f 100644 --- a/web/react/components/user_settings/user_settings_notifications.jsx +++ b/web/react/components/user_settings/user_settings_notifications.jsx @@ -129,7 +129,7 @@ export default class NotificationsTab extends React.Component { ); } handleClose() { - $(React.findDOMNode(this)).find('.form-control').each(function clearField() { + $(ReactDOM.findDOMNode(this)).find('.form-control').each(function clearField() { this.value = ''; }); @@ -158,15 +158,15 @@ export default class NotificationsTab extends React.Component { } handleNotifyRadio(notifyLevel) { this.setState({notifyLevel: notifyLevel}); - React.findDOMNode(this.refs.wrapper).focus(); + ReactDOM.findDOMNode(this.refs.wrapper).focus(); } handleEmailRadio(enableEmail) { this.setState({enableEmail: enableEmail}); - React.findDOMNode(this.refs.wrapper).focus(); + ReactDOM.findDOMNode(this.refs.wrapper).focus(); } handleSoundRadio(enableSound) { this.setState({enableSound: enableSound}); - React.findDOMNode(this.refs.wrapper).focus(); + ReactDOM.findDOMNode(this.refs.wrapper).focus(); } updateUsernameKey(val) { this.setState({usernameKey: val}); @@ -184,10 +184,10 @@ export default class NotificationsTab extends React.Component { this.setState({channelKey: val}); } updateCustomMentionKeys() { - var checked = React.findDOMNode(this.refs.customcheck).checked; + var checked = ReactDOM.findDOMNode(this.refs.customcheck).checked; if (checked) { - var text = React.findDOMNode(this.refs.custommentions).value; + var text = ReactDOM.findDOMNode(this.refs.custommentions).value; // remove all spaces and split string into individual keys this.setState({customKeys: text.replace(/ /g, ''), customKeysChecked: true}); @@ -196,7 +196,7 @@ export default class NotificationsTab extends React.Component { } } onCustomChange() { - React.findDOMNode(this.refs.customcheck).checked = true; + ReactDOM.findDOMNode(this.refs.customcheck).checked = true; this.updateCustomMentionKeys(); } render() { diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx index 74190781c..983a10df0 100644 --- a/web/react/components/user_settings/user_settings_security.jsx +++ b/web/react/components/user_settings/user_settings_security.jsx @@ -82,7 +82,7 @@ export default class SecurityTab extends React.Component { $('#user_settings').modal('hide'); } handleClose() { - $(React.findDOMNode(this)).find('.form-control').each(function resetValue() { + $(ReactDOM.findDOMNode(this)).find('.form-control').each(function resetValue() { this.value = ''; }); this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null}); diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index c5f0abc12..f75693470 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -77,7 +77,7 @@ export default class ViewImageModal extends React.Component { } onModalHidden() { if (this.refs.video) { - var video = React.findDOMNode(this.refs.video); + var video = ReactDOM.findDOMNode(this.refs.video); video.pause(); video.currentTime = 0; } |