diff options
Diffstat (limited to 'web/react')
47 files changed, 567 insertions, 359 deletions
diff --git a/web/react/.eslintrc b/web/react/.eslintrc index c0d0bb200..c4167829d 100644 --- a/web/react/.eslintrc +++ b/web/react/.eslintrc @@ -42,12 +42,13 @@ "valid-typeof": 2, "block-scoped-var": 2, - "complexity": [1, 8], + "complexity": [0, 8], "consistent-return": 2, "curly": [2, "all"], "dot-location": [2, "object"], "dot-notation": 2, "eqeqeq": [2, "smart"], + "global-require": 2, "guard-for-in": 2, "no-alert": 2, "no-array-constructor": 2, @@ -109,30 +110,34 @@ "func-names": 2, "func-style": [2, "declaration"], "indent": [2, 4, {"SwitchCase": 0}], + "jsx-quotes": [2, "prefer-single"], "key-spacing": [2, {"beforeColon": false, "afterColon": true}], - "lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }], "linebreak-style": 2, + "lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }], "new-cap": 2, "new-parens": 2, "no-lonely-if": 2, "no-mixed-spaces-and-tabs": 2, "no-multiple-empty-lines": [2, {"max": 1}], + "no-negated-condition": 2, + "no-nested-ternary": 2, "no-spaced-func": 2, - "no-ternary": 2, + "no-ternary": 0, "no-trailing-spaces": [2, { "skipBlankLines": false }], "no-underscore-dangle": 2, - "no-unneeded-ternary": 2, + "no-unneeded-ternary": [2, {"defaultAssignment": false}], "object-curly-spacing": [2, "never"], "one-var": [2, "never"], "operator-linebreak": [2, "after"], "padded-blocks": [2, "never"], "quote-props": [2, "as-needed"], "quotes": [2, "single", "avoid-escape"], - "semi-spacing": [2, {"before": false, "after": true}], "semi": [2, "always"], + "semi-spacing": [2, {"before": false, "after": true}], "space-after-keywords": [2, "always"], "space-before-blocks": [2, "always"], "space-before-function-paren": [2, "never"], + "space-before-keywords": [2, "always"], "space-in-parens": [2, "never"], "space-infix-ops": 2, "space-return-throw-case": 2, @@ -167,7 +172,6 @@ "react/jsx-no-duplicate-props": [2, { "ignoreCase": false }], "react/jsx-no-literals": 1, "react/jsx-no-undef": 2, - "react/jsx-quotes": [2, "single", "avoid-escape"], "react/jsx-uses-react": 2, "react/jsx-uses-vars": 2, "react/no-danger": 0, @@ -179,6 +183,7 @@ "react/prop-types": 2, "react/self-closing-comp": 2, "react/sort-comp": 0, - "react/wrap-multilines": 2 + "react/wrap-multilines": 2, + "react/no-direct-mutation-state": 2 } } diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx index a080150dd..0dfd36717 100644 --- a/web/react/components/access_history_modal.jsx +++ b/web/react/components/access_history_modal.jsx @@ -12,25 +12,31 @@ export default class AccessHistoryModal extends React.Component { this.onAuditChange = this.onAuditChange.bind(this); this.handleMoreInfo = this.handleMoreInfo.bind(this); + this.onHide = this.onHide.bind(this); + this.onShow = this.onShow.bind(this); - this.state = this.getStateFromStoresForAudits(); - this.state.moreInfo = []; + let state = this.getStateFromStoresForAudits(); + state.moreInfo = []; + + this.state = state; } getStateFromStoresForAudits() { return { audits: UserStore.getAudits() }; } + onShow() { + AsyncClient.getAudits(); + } + onHide() { + $('#user_settings').modal('show'); + this.setState({moreInfo: []}); + } componentDidMount() { UserStore.addAuditsChangeListener(this.onAuditChange); - $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function show() { - AsyncClient.getAudits(); - }); + $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow); - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function hide() { - $('#user_settings').modal('show'); - this.setState({moreInfo: []}); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); } componentWillUnmount() { UserStore.removeAuditsChangeListener(this.onAuditChange); diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index fe40385a0..aee2541b5 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -14,9 +14,13 @@ export default class ActivityLogModal extends React.Component { this.submitRevoke = this.submitRevoke.bind(this); this.onListenerChange = this.onListenerChange.bind(this); this.handleMoreInfo = this.handleMoreInfo.bind(this); + this.onHide = this.onHide.bind(this); + this.onShow = this.onShow.bind(this); - this.state = this.getStateFromStores(); - this.state.moreInfo = []; + let state = this.getStateFromStores(); + state.moreInfo = []; + + this.state = state; } getStateFromStores() { return { @@ -38,16 +42,18 @@ export default class ActivityLogModal extends React.Component { }.bind(this) ); } + onShow() { + AsyncClient.getSessions(); + } + onHide() { + $('#user_settings').modal('show'); + this.setState({moreInfo: []}); + } componentDidMount() { UserStore.addSessionsChangeListener(this.onListenerChange); - $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function handleShow() { - AsyncClient.getSessions(); - }); + $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow); - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function handleHide() { - $('#user_settings').modal('show'); - this.setState({moreInfo: []}); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); } componentWillUnmount() { UserStore.removeSessionsChangeListener(this.onListenerChange); diff --git a/web/react/components/admin_console/privacy_settings.jsx b/web/react/components/admin_console/privacy_settings.jsx index affd8ae11..c74d321e6 100644 --- a/web/react/components/admin_console/privacy_settings.jsx +++ b/web/react/components/admin_console/privacy_settings.jsx @@ -30,6 +30,7 @@ export default class PrivacySettings extends React.Component { 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.EnableDiagnostic = React.findDOMNode(this.refs.EnableDiagnostic).checked; Client.saveConfig( config, @@ -137,6 +138,39 @@ export default class PrivacySettings extends React.Component { </div> <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='EnableDiagnostic' + > + {'Send Error and Diagnostic: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnableDiagnostic' + value='true' + ref='EnableDiagnostic' + defaultChecked={this.props.config.PrivacySettings.EnableDiagnostic} + onChange={this.handleChange} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnableDiagnostic' + value='false' + defaultChecked={!this.props.config.PrivacySettings.EnableDiagnostic} + onChange={this.handleChange} + /> + {'false'} + </label> + <p className='help-text'>{'When true, The server will periodically send error and diagnostic information to Mattermost.'}</p> + </div> + </div> + + <div className='form-group'> <div className='col-sm-12'> {serverError} <button diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx index 245ffa871..b2d1b7b4d 100644 --- a/web/react/components/admin_console/service_settings.jsx +++ b/web/react/components/admin_console/service_settings.jsx @@ -37,6 +37,8 @@ export default class ServiceSettings extends React.Component { config.ServiceSettings.GoogleDeveloperKey = React.findDOMNode(this.refs.GoogleDeveloperKey).value.trim(); //config.ServiceSettings.EnableOAuthServiceProvider = React.findDOMNode(this.refs.EnableOAuthServiceProvider).checked; 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; var MaximumLoginAttempts = 10; @@ -199,7 +201,73 @@ export default class ServiceSettings extends React.Component { /> {'false'} </label> - <p className='help-text'>{'When true, incoming webhooks will be allowed.'}</p> + <p className='help-text'>{'When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='EnablePostUsernameOverride' + > + {'Enable Overriding Usernames from Webhooks: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnablePostUsernameOverride' + value='true' + ref='EnablePostUsernameOverride' + defaultChecked={this.props.config.ServiceSettings.EnablePostUsernameOverride} + onChange={this.handleChange} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnablePostUsernameOverride' + value='false' + defaultChecked={!this.props.config.ServiceSettings.EnablePostUsernameOverride} + onChange={this.handleChange} + /> + {'false'} + </label> + <p className='help-text'>{'When true, webhooks will be allowed to change the username they are posting as. Note, combined with allowing icon overriding, this could open users up to phishing attacks.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='EnablePostIconOverride' + > + {'Enable Overriding Icon from Webhooks: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnablePostIconOverride' + value='true' + ref='EnablePostIconOverride' + defaultChecked={this.props.config.ServiceSettings.EnablePostIconOverride} + onChange={this.handleChange} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnablePostIconOverride' + value='false' + defaultChecked={!this.props.config.ServiceSettings.EnablePostIconOverride} + onChange={this.handleChange} + /> + {'false'} + </label> + <p className='help-text'>{'When true, webhooks will be allowed to change the icon they post with. Note, combined with allowing username overriding, this could open users up to phishing attacks.'}</p> </div> </div> diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index b81936b57..f15974d35 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -132,7 +132,26 @@ export default class ChannelHeader extends React.Component { } let dropdownContents = []; - if (!isDirect) { + if (isDirect) { + dropdownContents.push( + <li + key='edit_description_direct' + role='presentation' + > + <a + role='menuitem' + href='#' + data-toggle='modal' + data-target='#edit_channel' + data-desc={channel.description} + data-title={channel.display_name} + data-channelid={channel.id} + > + Set Channel Description... + </a> + </li> + ); + } else { dropdownContents.push( <li key='view_info' @@ -276,25 +295,6 @@ export default class ChannelHeader extends React.Component { </li> ); } - } else { - dropdownContents.push( - <li - key='edit_description_direct' - role='presentation' - > - <a - role='menuitem' - href='#' - data-toggle='modal' - data-target='#edit_channel' - data-desc={channel.description} - data-title={channel.display_name} - data-channelid={channel.id} - > - Set Channel Description... - </a> - </li> - ); } return ( diff --git a/web/react/components/channel_members.jsx b/web/react/components/channel_members.jsx index 1eda6a104..53c854eb7 100644 --- a/web/react/components/channel_members.jsx +++ b/web/react/components/channel_members.jsx @@ -15,6 +15,8 @@ export default class ChannelMembers extends React.Component { this.getStateFromStores = this.getStateFromStores.bind(this); this.onChange = this.onChange.bind(this); this.handleRemove = this.handleRemove.bind(this); + this.onHide = this.onHide.bind(this); + this.onShow = this.onShow.bind(this); this.state = this.getStateFromStores(); } @@ -63,16 +65,18 @@ export default class ChannelMembers extends React.Component { channelName: channelName }; } + onHide() { + this.setState({renderMembers: false}); + } + onShow() { + this.setState({renderMembers: true}); + } componentDidMount() { ChannelStore.addExtraInfoChangeListener(this.onChange); ChannelStore.addChangeListener(this.onChange); - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function handleHide() { - this.setState({renderMembers: false}); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow() { - this.setState({renderMembers: true}); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); } componentWillUnmount() { ChannelStore.removeExtraInfoChangeListener(this.onChange); diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx index 45981b295..fed8e789e 100644 --- a/web/react/components/channel_notifications.jsx +++ b/web/react/components/channel_notifications.jsx @@ -23,6 +23,7 @@ export default class ChannelNotifications extends React.Component { this.handleSubmitMarkUnreadLevel = this.handleSubmitMarkUnreadLevel.bind(this); this.handleUpdateMarkUnreadLevel = this.handleUpdateMarkUnreadLevel.bind(this); this.createMarkUnreadLevelSection = this.createMarkUnreadLevelSection.bind(this); + this.onShow = this.onShow.bind(this); this.state = { notifyLevel: '', @@ -32,30 +33,29 @@ export default class ChannelNotifications extends React.Component { activeSection: '' }; } + onShow(e) { + var button = e.relatedTarget; + var channelId = button.getAttribute('data-channelid'); + const member = ChannelStore.getMember(channelId); + var notifyLevel = member.notify_props.desktop; + var markUnreadLevel = member.notify_props.mark_unread; + + this.setState({ + notifyLevel, + markUnreadLevel, + title: button.getAttribute('data-title'), + channelId + }); + } componentDidMount() { ChannelStore.addChangeListener(this.onListenerChange); - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function showModal(e) { - var button = e.relatedTarget; - var channelId = button.getAttribute('data-channelid'); - - const member = ChannelStore.getMember(channelId); - var notifyLevel = member.notify_props.desktop; - var markUnreadLevel = member.notify_props.mark_unread; - - this.setState({ - notifyLevel, - markUnreadLevel, - title: button.getAttribute('data-title'), - channelId: channelId - }); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); } componentWillUnmount() { ChannelStore.removeChangeListener(this.onListenerChange); } - onListenerChange() { if (!this.state.channelId) { return; @@ -76,7 +76,6 @@ export default class ChannelNotifications extends React.Component { updateSection(section) { this.setState({activeSection: section}); } - handleSubmitNotifyLevel() { var channelId = this.state.channelId; var notifyLevel = this.state.notifyLevel; @@ -103,12 +102,10 @@ export default class ChannelNotifications extends React.Component { } ); } - handleUpdateNotifyLevel(notifyLevel) { this.setState({notifyLevel}); React.findDOMNode(this.refs.modal).focus(); } - createNotifyLevelSection(serverError) { var handleUpdateSection; diff --git a/web/react/components/command_list.jsx b/web/react/components/command_list.jsx index fea7085b7..e027e87ae 100644 --- a/web/react/components/command_list.jsx +++ b/web/react/components/command_list.jsx @@ -51,7 +51,7 @@ export default class CommandList extends React.Component { this.setState({suggestions: data.suggestions, cmd: cmd}); }.bind(this), function fail() { - } + } ); } diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 5097b3aa5..9c233ea26 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -170,7 +170,9 @@ export default class CreateComment extends React.Component { this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews}); } handleUploadError(err, clientId) { - if (clientId !== -1) { + if (clientId === -1) { + this.setState({serverError: err}); + } else { let draft = PostStore.getCommentDraft(this.props.rootId); const index = draft.uploadsInProgress.indexOf(clientId); @@ -181,8 +183,6 @@ export default class CreateComment extends React.Component { PostStore.storeCommentDraft(this.props.rootId, draft); this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err}); - } else { - this.setState({serverError: err}); } } handleTextDrop(text) { @@ -196,15 +196,15 @@ export default class CreateComment extends React.Component { // id can either be the path of an uploaded file or the client id of an in progress upload let index = previews.indexOf(id); - if (index !== -1) { - previews.splice(index, 1); - } else { + if (index === -1) { index = uploadsInProgress.indexOf(id); if (index !== -1) { uploadsInProgress.splice(index, 1); this.refs.fileUpload.cancelUpload(id); } + } else { + previews.splice(index, 1); } let draft = PostStore.getCommentDraft(this.props.rootId); diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 0cd14747d..6e83f4faf 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -222,7 +222,9 @@ export default class CreatePost extends React.Component { this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews}); } handleUploadError(err, clientId) { - if (clientId !== -1) { + if (clientId === -1) { + this.setState({serverError: err}); + } else { const draft = PostStore.getDraft(this.state.channelId); const index = draft.uploadsInProgress.indexOf(clientId); @@ -233,8 +235,6 @@ export default class CreatePost extends React.Component { PostStore.storeDraft(this.state.channelId, draft); this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err}); - } else { - this.setState({serverError: err}); } } handleTextDrop(text) { @@ -248,15 +248,15 @@ export default class CreatePost extends React.Component { // id can either be the path of an uploaded file or the client id of an in progress upload let index = previews.indexOf(id); - if (index !== -1) { - previews.splice(index, 1); - } else { + if (index === -1) { index = uploadsInProgress.indexOf(id); if (index !== -1) { uploadsInProgress.splice(index, 1); this.refs.fileUpload.cancelUpload(id); } + } else { + previews.splice(index, 1); } const draft = PostStore.getCurrentDraft(); diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx index 44c54db72..71c636921 100644 --- a/web/react/components/delete_channel_modal.jsx +++ b/web/react/components/delete_channel_modal.jsx @@ -11,6 +11,7 @@ export default class DeleteChannelModal extends React.Component { super(props); this.handleDelete = this.handleDelete.bind(this); + this.onShow = this.onShow.bind(this); this.state = { title: '', @@ -32,14 +33,15 @@ export default class DeleteChannelModal extends React.Component { } ); } + onShow(e) { + var button = $(e.relatedTarget); + this.setState({ + title: button.attr('data-title'), + channelId: button.attr('data-channelid') + }); + } componentDidMount() { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) { - var button = $(e.relatedTarget); - this.setState({ - title: button.attr('data-title'), - channelId: button.attr('data-channelid') - }); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); } render() { const channel = ChannelStore.getCurrent(); diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx index 075f9c742..8e48a7a1c 100644 --- a/web/react/components/delete_post_modal.jsx +++ b/web/react/components/delete_post_modal.jsx @@ -16,6 +16,7 @@ export default class DeletePostModal extends React.Component { this.handleDelete = this.handleDelete.bind(this); this.onListenerChange = this.onListenerChange.bind(this); + this.onShow = this.onShow.bind(this); this.state = {title: '', postId: '', channelId: '', selectedList: PostStore.getSelectedPost(), comments: 0}; } @@ -60,18 +61,19 @@ export default class DeletePostModal extends React.Component { } ); } + onShow(e) { + var newState = {}; + if (BrowserStore.getItem('edit_state_transfer')) { + newState = BrowserStore.getItem('edit_state_transfer'); + BrowserStore.removeItem('edit_state_transfer'); + } else { + var button = e.relatedTarget; + newState = {title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), postId: $(button).attr('data-postid'), comments: $(button).attr('data-comments')}; + } + this.setState(newState); + } componentDidMount() { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function freshOpen(e) { - var newState = {}; - if (BrowserStore.getItem('edit_state_transfer')) { - newState = BrowserStore.getItem('edit_state_transfer'); - BrowserStore.removeItem('edit_state_transfer'); - } else { - var button = e.relatedTarget; - newState = {title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), postId: $(button).attr('data-postid'), comments: $(button).attr('data-comments')}; - } - this.setState(newState); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); PostStore.addSelectedPostChangeListener(this.onListenerChange); } componentWillUnmount() { diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx index e93bab431..27219aba5 100644 --- a/web/react/components/edit_channel_modal.jsx +++ b/web/react/components/edit_channel_modal.jsx @@ -11,6 +11,7 @@ export default class EditChannelModal extends React.Component { this.handleEdit = this.handleEdit.bind(this); this.handleUserInput = this.handleUserInput.bind(this); this.handleClose = this.handleClose.bind(this); + this.onShow = this.onShow.bind(this); this.state = { description: '', @@ -50,11 +51,12 @@ export default class EditChannelModal extends React.Component { handleClose() { this.setState({description: '', serverError: ''}); } + onShow(e) { + const button = e.relatedTarget; + this.setState({description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''}); + } componentDidMount() { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) { - const button = e.relatedTarget; - this.setState({description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''}); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); } componentWillUnmount() { diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index 3dc4e5de2..0e9297b7b 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -97,7 +97,7 @@ export default class FileUpload extends React.Component { element[0].type = 'text'; element[0].type = 'file'; } - } catch(e) { + } catch (e) { // Do nothing } } diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx index 5d8b13f00..1d4aac3e6 100644 --- a/web/react/components/get_link_modal.jsx +++ b/web/react/components/get_link_modal.jsx @@ -8,18 +8,22 @@ export default class GetLinkModal extends React.Component { super(props); this.handleClick = this.handleClick.bind(this); + this.onShow = this.onShow.bind(this); + this.onHide = this.onHide.bind(this); this.state = {copiedLink: false}; } + onShow(e) { + var button = e.relatedTarget; + this.setState({title: $(button).attr('data-title'), value: $(button).attr('data-value')}); + } + onHide() { + this.setState({copiedLink: false}); + } componentDidMount() { if (this.refs.modal) { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) { - var button = e.relatedTarget; - this.setState({title: $(button).attr('data-title'), value: $(button).attr('data-value')}); - }.bind(this)); - $(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', function hide() { - this.setState({copiedLink: false}); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); + $(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', this.onShow); } } handleClick() { diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index 8cc4f1483..70f7a5d6e 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -88,10 +88,10 @@ export default class Login extends React.Component { let focusEmail = false; let focusPassword = false; - if (priorEmail !== '') { - focusPassword = true; - } else { + if (priorEmail === '') { focusEmail = true; + } else { + focusPassword = true; } let loginMessage = []; diff --git a/web/react/components/mention.jsx b/web/react/components/mention.jsx index 3a09e843d..ef7cec408 100644 --- a/web/react/components/mention.jsx +++ b/web/react/components/mention.jsx @@ -18,7 +18,9 @@ export default class Mention extends React.Component { var timestamp = UserStore.getCurrentUser().update_at; if (this.props.id === 'allmention' || this.props.id === 'channelmention') { icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>; - } else if (this.props.id != null) { + } else if (this.props.id == null) { + icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>; + } else { icon = ( <span> <img @@ -27,8 +29,6 @@ export default class Mention extends React.Component { /> </span> ); - } else { - icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>; } return ( <div diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx index 46a9d76ae..72f51013c 100644 --- a/web/react/components/mention_list.jsx +++ b/web/react/components/mention_list.jsx @@ -26,53 +26,64 @@ export default class MentionList extends React.Component { this.addFirstMention = this.addFirstMention.bind(this); this.isEmpty = this.isEmpty.bind(this); this.scrollToMention = this.scrollToMention.bind(this); + this.onScroll = this.onScroll.bind(this); + this.onMentionListKey = this.onMentionListKey.bind(this); + this.onClick = this.onClick.bind(this); this.state = {excludeUsers: [], mentionText: '-1', selectedMention: 0, selectedUsername: ''}; } - componentDidMount() { - PostStore.addMentionDataChangeListener(this.onListenerChange); + onScroll() { + if ($('.mentions--top').length) { + $('#reply_mention_tab .mentions--top').css({bottom: $(window).height() - $('.post-right__scroll #reply_textbox').offset().top}); + } + } + onMentionListKey(e) { + if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 13 || e.which === 9)) { + e.stopPropagation(); + e.preventDefault(); + this.addCurrentMention(); + } else if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 38 || e.which === 40)) { + e.stopPropagation(); + e.preventDefault(); - $('.post-right__scroll').scroll(function onScroll() { - if ($('.mentions--top').length) { - $('#reply_mention_tab .mentions--top').css({bottom: $(window).height() - $('.post-right__scroll #reply_textbox').offset().top}); + if (e.which === 38) { + if (this.getSelection(this.state.selectedMention - 1)) { + this.setState({selectedMention: this.state.selectedMention - 1, selectedUsername: this.refs['mention' + (this.state.selectedMention - 1)].props.username}); + } + } else if (e.which === 40) { + if (this.getSelection(this.state.selectedMention + 1)) { + this.setState({selectedMention: this.state.selectedMention + 1, selectedUsername: this.refs['mention' + (this.state.selectedMention + 1)].props.username}); + } } - }); - $('body').on('keydown.mentionlist', '#' + this.props.id, - function onMentionListKey(e) { - if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 13 || e.which === 9)) { - e.stopPropagation(); - e.preventDefault(); - this.addCurrentMention(); - } else if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 38 || e.which === 40)) { - e.stopPropagation(); - e.preventDefault(); + this.scrollToMention(e.which); + } + } + onClick(e) { + if (!($('#' + this.props.id).is(e.target) || $('#' + this.props.id).has(e.target).length || + ('mentionlist' in this.refs && $(React.findDOMNode(this.refs.mentionlist)).has(e.target).length))) { + this.setState({mentionText: '-1'}); + } + } + componentDidMount() { + PostStore.addMentionDataChangeListener(this.onListenerChange); - if (e.which === 38) { - if (this.getSelection(this.state.selectedMention - 1)) { - this.setState({selectedMention: this.state.selectedMention - 1, selectedUsername: this.refs['mention' + (this.state.selectedMention - 1)].props.username}); - } - } else if (e.which === 40) { - if (this.getSelection(this.state.selectedMention + 1)) { - this.setState({selectedMention: this.state.selectedMention + 1, selectedUsername: this.refs['mention' + (this.state.selectedMention + 1)].props.username}); - } - } + $('.post-right__scroll').scroll(this.onScroll); - this.scrollToMention(e.which); - } - }.bind(this) - ); - $(document).click(function onClick(e) { - if (!($('#' + this.props.id).is(e.target) || $('#' + this.props.id).has(e.target).length || - ('mentionlist' in this.refs && $(React.findDOMNode(this.refs.mentionlist)).has(e.target).length))) { - this.setState({mentionText: '-1'}); - } - }.bind(this)); + $('body').on('keydown.mentionlist', '#' + this.props.id, this.onMentionListKey); + $(document).click(this.onClick); } componentWillUnmount() { PostStore.removeMentionDataChangeListener(this.onListenerChange); $('body').off('keydown.mentionlist', '#' + this.props.id); } + + /* + * This component is poorly designed, nessesitating some state modification + * in the componentDidUpdate function. This is generally discouraged as it + * is a performance issue and breaks with good react design. This component + * should be redesigned. + */ componentDidUpdate() { if (this.state.mentionText !== '-1') { if (this.state.selectedUsername !== '' && (!this.getSelection(this.state.selectedMention) || this.state.selectedUsername !== this.refs['mention' + this.state.selectedMention].props.username)) { @@ -80,17 +91,17 @@ export default class MentionList extends React.Component { var foundMatch = false; while (tempSelectedMention < this.state.selectedMention && this.getSelection(++tempSelectedMention)) { if (this.state.selectedUsername === this.refs['mention' + tempSelectedMention].props.username) { - this.setState({selectedMention: tempSelectedMention}); + this.setState({selectedMention: tempSelectedMention}); //eslint-disable-line react/no-did-update-set-state foundMatch = true; break; } } if (this.getSelection(0) && !foundMatch) { - this.setState({selectedMention: 0, selectedUsername: this.refs.mention0.props.username}); + this.setState({selectedMention: 0, selectedUsername: this.refs.mention0.props.username}); //eslint-disable-line react/no-did-update-set-state } } } else if (this.state.selectedMention !== 0) { - this.setState({selectedMention: 0, selectedUsername: ''}); + this.setState({selectedMention: 0, selectedUsername: ''}); //eslint-disable-line react/no-did-update-set-state } } onListenerChange(id, mentionText) { @@ -124,10 +135,10 @@ export default class MentionList extends React.Component { return true; } addCurrentMention() { - if (!this.getSelection(this.state.selectedMention)) { - this.addFirstMention(); - } else { + if (this.getSelection(this.state.selectedMention)) { this.refs['mention' + this.state.selectedMention].handleClick(); + } else { + this.addFirstMention(); } } addFirstMention() { diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx index 65cd40975..487192d91 100644 --- a/web/react/components/more_channels.jsx +++ b/web/react/components/more_channels.jsx @@ -60,9 +60,7 @@ export default class MoreChannels extends React.Component { this.setState({joiningChannel: -1}); }.bind(this), function joinFail(err) { - this.setState({joiningChannel: -1}); - this.state.serverError = err.message; - this.setState(this.state); + this.setState({joiningChannel: -1, serverError: err.message}); }.bind(this) ); } @@ -81,56 +79,54 @@ export default class MoreChannels extends React.Component { if (this.state.channels != null) { var channels = this.state.channels; - if (!channels.loading) { - if (channels.length) { - moreChannels = ( - <table className='more-channel-table table'> - <tbody> - {channels.map(function cMap(channel, index) { - var joinButton; - if (self.state.joiningChannel === index) { - joinButton = ( - <img - className='join-channel-loading-gif' - src='/static/images/load.gif' - /> - ); - } else { - joinButton = ( - <button - onClick={self.handleJoin.bind(self, channel, index)} - className='btn btn-primary' - > - Join - </button> - ); - } + if (channels.loading) { + moreChannels = <LoadingScreen />; + } else if (channels.length) { + moreChannels = ( + <table className='more-channel-table table'> + <tbody> + {channels.map(function cMap(channel, index) { + var joinButton; + if (self.state.joiningChannel === index) { + joinButton = ( + <img + className='join-channel-loading-gif' + src='/static/images/load.gif' + /> + ); + } else { + joinButton = ( + <button + onClick={self.handleJoin.bind(self, channel, index)} + className='btn btn-primary' + > + Join + </button> + ); + } - return ( - <tr key={channel.id}> - <td> - <p className='more-channel-name'>{channel.display_name}</p> - <p className='more-channel-description'>{channel.description}</p> - </td> - <td className='td--action'> - {joinButton} - </td> - </tr> - ); - })} - </tbody> - </table> - ); - } else { - moreChannels = ( - <div className='no-channel-message'> - <p className='primary-message'>No more channels to join</p> - <p className='secondary-message'>Click 'Create New Channel' to make a new one</p> - </div> - ); - } + return ( + <tr key={channel.id}> + <td> + <p className='more-channel-name'>{channel.display_name}</p> + <p className='more-channel-description'>{channel.description}</p> + </td> + <td className='td--action'> + {joinButton} + </td> + </tr> + ); + })} + </tbody> + </table> + ); } else { - moreChannels = <LoadingScreen />; + moreChannels = ( + <div className='no-channel-message'> + <p className='primary-message'>No more channels to join</p> + <p className='secondary-message'>Click 'Create New Channel' to make a new one</p> + </div> + ); } } diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx index 54d77c358..c71abd43a 100644 --- a/web/react/components/more_direct_channels.jsx +++ b/web/react/components/more_direct_channels.jsx @@ -31,22 +31,7 @@ export default class MoreDirectChannels extends React.Component { var active = ''; var handleClick = null; - if (!channel.fake) { - if (channel.id === ChannelStore.getCurrentId()) { - active = 'active'; - } - - if (channel.unread) { - badge = <span className='badge pull-right small'>{channel.unread}</span>; - titleClass = 'unread-title'; - } - - handleClick = function clickHandler(e) { - e.preventDefault(); - utils.switchChannel(channel); - $(React.findDOMNode(self.refs.modal)).modal('hide'); - }; - } else { + if (channel.fake) { // It's a direct message channel that doesn't exist yet so let's create it now var otherUserId = utils.getUserIdFromChannelName(channel); @@ -78,6 +63,21 @@ export default class MoreDirectChannels extends React.Component { ); }; } + } else { + if (channel.id === ChannelStore.getCurrentId()) { + active = 'active'; + } + + if (channel.unread) { + badge = <span className='badge pull-right small'>{channel.unread}</span>; + titleClass = 'unread-title'; + } + + handleClick = function clickHandler(e) { + e.preventDefault(); + utils.switchChannel(channel); + $(React.findDOMNode(self.refs.modal)).modal('hide'); + }; } return ( diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx index 57a78a0d4..78057d10b 100644 --- a/web/react/components/navbar_dropdown.jsx +++ b/web/react/components/navbar_dropdown.jsx @@ -9,7 +9,7 @@ var TeamStore = require('../stores/team_store.jsx'); var Constants = require('../utils/constants.jsx'); function getStateFromStores() { - return {teams: UserStore.getTeams(), currentTeam: TeamStore.getCurrent()}; + return {teams: UserStore.getTeams()}; } export default class NavbarDropdown extends React.Component { @@ -142,10 +142,10 @@ export default class NavbarDropdown extends React.Component { > </li> ); - if (this.state.teams.length > 1 && this.state.currentTeam) { - var curTeamName = this.state.currentTeam.name; + + if (this.state.teams.length > 1) { this.state.teams.forEach((teamName) => { - if (teamName !== curTeamName) { + if (teamName !== this.props.teamName) { teams.push(<li key={teamName}><a href={Utils.getWindowLocationOrigin() + '/' + teamName}>{'Switch to ' + teamName}</a></li>); } }); @@ -234,5 +234,7 @@ NavbarDropdown.defaultProps = { teamType: '' }; NavbarDropdown.propTypes = { - teamType: React.PropTypes.string + teamType: React.PropTypes.string, + teamDisplayName: React.PropTypes.string, + teamName: React.PropTypes.string }; diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx index 95a88c3d6..aaaea3c6f 100644 --- a/web/react/components/popover_list_members.jsx +++ b/web/react/components/popover_list_members.jsx @@ -67,9 +67,6 @@ export default class PopoverListMembers extends React.Component { > <div id='member_tooltip' - data-placement='left' - data-toggle='tooltip' - title='View Channel Members' > {countText} <span diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index 9127f00de..ac9c9252e 100644 --- a/web/react/components/post.jsx +++ b/web/react/components/post.jsx @@ -158,11 +158,18 @@ export default class Post extends React.Component { var profilePic = null; if (!this.props.hideProfilePic) { + let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp; + if (post.props && post.props.from_webhook && global.window.config.EnablePostIconOverride === 'true') { + if (post.props.override_icon_url) { + src = post.props.override_icon_url; + } + } + profilePic = ( <div className='post-profile-img__container'> <img className='post-profile-img' - src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp} + src={src} height='36' width='36' /> diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index 48b268700..6cfd243de 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -125,7 +125,7 @@ export default class PostBody extends React.Component { url: 'https://www.googleapis.com/youtube/v3/videos', type: 'GET', data: {part: 'snippet', id: youtubeId, key: global.window.config.GoogleDeveloperKey}, - success + success: success.bind(this) }); } diff --git a/web/react/components/post_header.jsx b/web/react/components/post_header.jsx index 9dc525e03..dd79b3e36 100644 --- a/web/react/components/post_header.jsx +++ b/web/react/components/post_header.jsx @@ -12,9 +12,27 @@ export default class PostHeader extends React.Component { render() { var post = this.props.post; + let userProfile = <UserProfile userId={post.user_id} />; + let botIndicator; + + if (post.props && post.props.from_webhook) { + if (post.props.override_username && global.window.config.EnablePostUsernameOverride === 'true') { + userProfile = ( + <UserProfile + userId={post.user_id} + overwriteName={post.props.override_username} + disablePopover={true} + /> + ); + } + + botIndicator = <li className='post-header-col post-header__name bot-indicator'>{'BOT'}</li>; + } + return ( <ul className='post-header post-header-post'> - <li className='post-header-col post-header__name'><strong><UserProfile userId={post.user_id} /></strong></li> + <li className='post-header-col post-header__name'><strong>{userProfile}</strong></li> + {botIndicator} <li className='post-info--hidden'> <PostInfo post={post} diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index a31967257..6741a9bdd 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -37,9 +37,11 @@ export default class PostList extends React.Component { this.deactivate = this.deactivate.bind(this); this.resize = this.resize.bind(this); - this.state = this.getStateFromStores(props.channelId); - this.state.numToDisplay = Constants.POST_CHUNK_SIZE; - this.state.isFirstLoadComplete = false; + const state = this.getStateFromStores(props.channelId); + state.numToDisplay = Constants.POST_CHUNK_SIZE; + state.isFirstLoadComplete = false; + + this.state = state; } getStateFromStores(id) { var postList = PostStore.getPosts(id); @@ -449,10 +451,10 @@ export default class PostList extends React.Component { } var createMessage; - if (creatorName !== '') { - createMessage = (<span>This is the start of the <strong>{uiName}</strong> {uiType}, created by <strong>{creatorName}</strong> on <strong>{utils.displayDate(channel.create_at)}</strong></span>); - } else { + if (creatorName === '') { createMessage = 'This is the start of the ' + uiName + ' ' + uiType + ', created on ' + utils.displayDate(channel.create_at) + '.'; + } else { + createMessage = (<span>This is the start of the <strong>{uiName}</strong> {uiType}, created by <strong>{creatorName}</strong> on <strong>{utils.displayDate(channel.create_at)}</strong></span>); } return ( @@ -516,8 +518,19 @@ export default class PostList extends React.Component { sameRoot = utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id); - // we only hide the profile pic if the previous post is not a comment, the current post is not a comment, and the previous post was made by the same user as the current post - hideProfilePic = (prevPost.user_id === post.user_id) && !utils.isComment(prevPost) && !utils.isComment(post); + // hide the profile pic if: + // the previous post was made by the same user as the current post, + // the previous post is not a comment, + // the current post is not a comment, + // the current post is not from a webhook + // and the previous post is not from a webhook + if ((prevPost.user_id === post.user_id) && + !utils.isComment(prevPost) && + !utils.isComment(post) && + (!post.props || !post.props.from_webhook) && + (!prevPost.props || !prevPost.props.from_webhook)) { + hideProfilePic = true; + } } // check if it's the last comment in a consecutive string of comments on the same post diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx index 9d514c741..d60206ecf 100644 --- a/web/react/components/rename_channel_modal.jsx +++ b/web/react/components/rename_channel_modal.jsx @@ -15,6 +15,7 @@ export default class RenameChannelModal extends React.Component { this.onDisplayNameChange = this.onDisplayNameChange.bind(this); this.displayNameKeyUp = this.displayNameKeyUp.bind(this); this.handleClose = this.handleClose.bind(this); + this.handleShow = this.handleShow.bind(this); this.handleSubmit = this.handleSubmit.bind(this); this.state = { @@ -59,11 +60,11 @@ export default class RenameChannelModal extends React.Component { state.invalid = true; } else { let cleanedName = Utils.cleanUpUrlable(channel.name); - if (cleanedName !== channel.name) { + if (cleanedName === channel.name) { + state.nameError = ''; + } else { state.nameError = 'Must be lowercase alphanumeric characters'; state.invalid = true; - } else { - state.nameError = ''; } } @@ -103,7 +104,7 @@ export default class RenameChannelModal extends React.Component { this.setState({channelName: channelName}); } handleClose() { - this.state = { + this.setState({ displayName: '', channelName: '', channelId: '', @@ -111,13 +112,14 @@ export default class RenameChannelModal extends React.Component { nameError: '', displayNameError: '', invalid: false - }; + }); + } + handleShow(e) { + const button = $(e.relatedTarget); + this.setState({displayName: button.attr('data-display'), channelName: button.attr('data-name'), channelId: button.attr('data-channelid')}); } componentDidMount() { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) { - const button = $(e.relatedTarget); - this.setState({displayName: button.attr('data-display'), channelName: button.attr('data-name'), channelId: button.attr('data-channelid')}); - }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.handleShow); $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); } componentWillUnmount() { diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 6d4b56b7b..b696f4b53 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -29,9 +29,11 @@ export default class Sidebar extends React.Component { this.updateUnreadIndicators = this.updateUnreadIndicators.bind(this); this.createChannelElement = this.createChannelElement.bind(this); - this.state = this.getStateFromStores(); - this.state.modal = ''; - this.state.loadingDMChannel = -1; + const state = this.getStateFromStores(); + state.modal = ''; + state.loadingDMChannel = -1; + + this.state = state; } getStateFromStores() { var members = ChannelStore.getAllMembers(); @@ -65,7 +67,18 @@ export default class Sidebar extends React.Component { var channel = ChannelStore.getByName(channelName); - if (channel != null) { + if (channel == null) { + var tempChannel = {}; + tempChannel.fake = true; + tempChannel.name = channelName; + tempChannel.display_name = teammate.username; + tempChannel.teammate_username = teammate.username; + tempChannel.status = UserStore.getStatus(teammate.id); + tempChannel.last_post_at = 0; + tempChannel.total_msg_count = 0; + tempChannel.type = 'D'; + readDirectChannels.push(tempChannel); + } else { channel.display_name = teammate.username; channel.teammate_username = teammate.username; @@ -80,17 +93,6 @@ export default class Sidebar extends React.Component { } else { readDirectChannels.push(channel); } - } else { - var tempChannel = {}; - tempChannel.fake = true; - tempChannel.name = channelName; - tempChannel.display_name = teammate.username; - tempChannel.teammate_username = teammate.username; - tempChannel.status = UserStore.getStatus(teammate.id); - tempChannel.last_post_at = 0; - tempChannel.total_msg_count = 0; - tempChannel.type = 'D'; - readDirectChannels.push(tempChannel); } } @@ -512,6 +514,7 @@ export default class Sidebar extends React.Component { /> <SidebarHeader teamDisplayName={this.props.teamDisplayName} + teamName={this.props.teamName} teamType={this.props.teamType} /> <SearchBox /> @@ -591,5 +594,6 @@ Sidebar.defaultProps = { }; Sidebar.propTypes = { teamType: React.PropTypes.string, - teamDisplayName: React.PropTypes.string + teamDisplayName: React.PropTypes.string, + teamName: React.PropTypes.string }; diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index 072c14e0a..33de35064 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -52,6 +52,8 @@ export default class SidebarHeader extends React.Component { <NavbarDropdown ref='dropdown' teamType={this.props.teamType} + teamDisplayName={this.props.teamDisplayName} + teamName={this.props.teamName} /> </div> ); @@ -64,5 +66,6 @@ SidebarHeader.defaultProps = { }; SidebarHeader.propTypes = { teamDisplayName: React.PropTypes.string, + teamName: React.PropTypes.string, teamType: React.PropTypes.string }; diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx index 708cd04cb..573515a46 100644 --- a/web/react/components/sidebar_right.jsx +++ b/web/react/components/sidebar_right.jsx @@ -30,11 +30,11 @@ export default class SidebarRight extends React.Component { PostStore.removeSelectedPostChangeListener(this.onSelectedChange); } componentDidUpdate() { - if (!this.plScrolledToBottom) { - $('.top-visible-post')[0].scrollIntoView(); - } else { + if (this.plScrolledToBottom) { var postHolder = $('.post-list-holder-by-time').not('.inactive'); postHolder.scrollTop(postHolder[0].scrollHeight); + } else { + $('.top-visible-post')[0].scrollIntoView(); } } onSelectedChange(fromSearch) { diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 495159efc..ae3075495 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -41,15 +41,13 @@ export default class SignupUserComplete extends React.Component { return; } - this.state.user.email = providedEmail; - - this.state.user.username = React.findDOMNode(this.refs.name).value.trim().toLowerCase(); - if (!this.state.user.username) { + const providedUsername = React.findDOMNode(this.refs.name).value.trim().toLowerCase(); + if (!providedUsername) { this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''}); return; } - var usernameError = Utils.isValidUsername(this.state.user.username); + const usernameError = Utils.isValidUsername(this.state.user.username); if (usernameError === 'Cannot use a reserved word as a username.') { this.setState({nameError: 'This username is reserved, please choose a new one.', emailError: '', passwordError: '', serverError: ''}); return; @@ -63,15 +61,24 @@ export default class SignupUserComplete extends React.Component { return; } - this.state.user.password = React.findDOMNode(this.refs.password).value.trim(); - if (!this.state.user.password || this.state.user.password .length < 5) { + const providedPassword = React.findDOMNode(this.refs.password).value.trim(); + if (!providedPassword || providedPassword.length < 5) { this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''}); return; } - this.setState({nameError: '', emailError: '', passwordError: '', serverError: ''}); - - this.state.user.allow_marketing = true; + this.setState({ + user: { + email: providedEmail, + username: providedUsername, + password: providedPassword, + allow_marketing: true + }, + nameError: '', + emailError: '', + passwordError: '', + serverError: '' + }); client.createUser(this.state.user, this.state.data, this.state.hash, function createUserSuccess() { diff --git a/web/react/components/team_signup_email_item.jsx b/web/react/components/team_signup_email_item.jsx index 10bb2d69e..01330a46c 100644 --- a/web/react/components/team_signup_email_item.jsx +++ b/web/react/components/team_signup_email_item.jsx @@ -23,17 +23,14 @@ export default class TeamSignupEmailItem extends React.Component { } if (!Utils.isEmail(email)) { - this.state.emailError = 'Please enter a valid email address'; - this.setState(this.state); + this.setState({emailError: 'Please enter a valid email address'}); return false; } else if (email === teamEmail) { - this.state.emailError = 'Please use a different email than the one used at signup'; - this.setState(this.state); + this.setState({emailError: 'Please use a different email than the one used at signup'}); return false; } - this.state.emailError = ''; - this.setState(this.state); + this.setState({emailError: ''}); return true; } render() { diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/team_signup_send_invites_page.jsx index 524bd5b50..8d8fb92ff 100644 --- a/web/react/components/team_signup_send_invites_page.jsx +++ b/web/react/components/team_signup_send_invites_page.jsx @@ -36,10 +36,10 @@ export default class TeamSignupSendInvitesPage extends React.Component { var emails = []; for (var i = 0; i < this.props.state.invites.length; i++) { - if (!this.refs['email_' + i].validate(this.props.state.team.email)) { - valid = false; - } else { + if (this.refs['email_' + i].validate(this.props.state.team.email)) { emails.push(this.refs['email_' + i].getValue()); + } else { + valid = false; } } diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/team_signup_url_page.jsx index a3f89a217..a682bb49e 100644 --- a/web/react/components/team_signup_url_page.jsx +++ b/web/react/components/team_signup_url_page.jsx @@ -48,22 +48,20 @@ export default class TeamSignupUrlPage extends React.Component { } Client.findTeamByName(name, - function success(data) { - if (!data) { - this.props.state.wizard = 'send_invites'; - this.props.state.team.type = 'O'; - - this.props.state.team.name = name; - this.props.updateParent(this.props.state); - } else { - this.state.nameError = 'This URL is unavailable. Please try another.'; - this.setState(this.state); - } - }.bind(this), - function error(err) { - this.state.nameError = err.message; - this.setState(this.state); - }.bind(this) + (data) => { + if (data) { + this.setState({nameError: 'This URL is unavailable. Please try another.'}); + } else { + this.props.state.wizard = 'send_invites'; + this.props.state.team.type = 'O'; + + this.props.state.team.name = name; + this.props.updateParent(this.props.state); + } + }, + (err) => { + this.setState({nameError: err.message}); + } ); } handleFocus(e) { diff --git a/web/react/components/team_signup_welcome_page.jsx b/web/react/components/team_signup_welcome_page.jsx index 626c6a17b..019456c9f 100644 --- a/web/react/components/team_signup_welcome_page.jsx +++ b/web/react/components/team_signup_welcome_page.jsx @@ -59,8 +59,7 @@ export default class TeamSignupWelcomePage extends React.Component { } }.bind(this), function error(err) { - this.state.serverError = err.message; - this.setState(this.state); + this.setState({serverError: err.message}); }.bind(this) ); } diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx index c5d028d31..ceb8f52a7 100644 --- a/web/react/components/user_profile.jsx +++ b/web/react/components/user_profile.jsx @@ -31,8 +31,10 @@ export default class UserProfile extends React.Component { } componentDidMount() { UserStore.addChangeListener(this.onChange); - $('#profile_' + this.uniqueId).popover({placement: 'right', container: 'body', trigger: 'hover', html: true, delay: {show: 200, hide: 100}}); - $('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'}); + if (!this.props.disablePopover) { + $('#profile_' + this.uniqueId).popover({placement: 'right', container: 'body', trigger: 'hover', html: true, delay: {show: 200, hide: 100}}); + $('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'}); + } } componentWillUnmount() { UserStore.removeChangeListener(this.onChange); @@ -56,6 +58,10 @@ export default class UserProfile extends React.Component { name = this.props.overwriteName; } + if (this.props.disablePopover) { + 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" />'; if (!global.window.config.ShowEmailAddress === 'true') { dataContent += '<div class="text-nowrap">Email not shared</div>'; @@ -79,9 +85,11 @@ export default class UserProfile extends React.Component { UserProfile.defaultProps = { userId: '', - overwriteName: '' + overwriteName: '', + disablePopover: false }; UserProfile.propTypes = { userId: React.PropTypes.string, - overwriteName: React.PropTypes.string + overwriteName: React.PropTypes.string, + disablePopover: React.PropTypes.bool }; diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx index 0f5cb59f5..4e8ee03fa 100644 --- a/web/react/components/user_settings/import_theme_modal.jsx +++ b/web/react/components/user_settings/import_theme_modal.jsx @@ -150,7 +150,9 @@ export default class ImportThemeModal extends React.Component { className='form-control' onChange={this.handleChange} /> - {this.state.inputError} + <div className='input__help'> + {this.state.inputError} + </div> </div> </div> </Modal.Body> diff --git a/web/react/components/user_settings/premade_theme_chooser.jsx b/web/react/components/user_settings/premade_theme_chooser.jsx index f8f916bd0..8116bffcc 100644 --- a/web/react/components/user_settings/premade_theme_chooser.jsx +++ b/web/react/components/user_settings/premade_theme_chooser.jsx @@ -24,7 +24,7 @@ export default class PremadeThemeChooser extends React.Component { premadeThemes.push( <div - className='col-sm-3 premade-themes' + className='col-xs-6 col-sm-3 premade-themes' key={'premade-theme-key' + k} > <div diff --git a/web/react/package.json b/web/react/package.json index a9eba6c6c..31295873b 100644 --- a/web/react/package.json +++ b/web/react/package.json @@ -19,8 +19,8 @@ "babelify": "6.1.3", "uglify-js": "2.4.24", "watchify": "3.3.1", - "eslint": "1.3.1", - "eslint-plugin-react": "3.3.1" + "eslint": "1.6.0", + "eslint-plugin-react": "3.5.1" }, "scripts": { "start": "watchify --extension=jsx -o ../static/js/bundle.js -v -d ./**/*.jsx", diff --git a/web/react/pages/authorize.jsx b/web/react/pages/authorize.jsx index db42c8266..8ea8b13eb 100644 --- a/web/react/pages/authorize.jsx +++ b/web/react/pages/authorize.jsx @@ -3,16 +3,16 @@ var Authorize = require('../components/authorize.jsx'); -function setupAuthorizePage(teamName, appName, responseType, clientId, redirectUri, scope, state) { +function setupAuthorizePage(props) { React.render( <Authorize - teamName={teamName} - appName={appName} - responseType={responseType} - clientId={clientId} - redirectUri={redirectUri} - scope={scope} - state={state} + teamName={props.TeamName} + appName={props.AppName} + responseType={props.ResponseType} + clientId={props.ClientId} + redirectUri={props.RedirectUri} + scope={props.Scope} + state={props.State} />, document.getElementById('authorize') ); diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index 74259194a..c333fd57d 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -36,11 +36,14 @@ var RemovedFromChannelModal = require('../components/removed_from_channel_modal. var FileUploadOverlay = require('../components/file_upload_overlay.jsx'); var RegisterAppModal = require('../components/register_app_modal.jsx'); var ImportThemeModal = require('../components/user_settings/import_theme_modal.jsx'); +var TeamStore = require('../stores/team_store.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; function setupChannelPage(props) { + TeamStore.setCurrentId(props.TeamId); + AppDispatcher.handleViewAction({ type: ActionTypes.CLICK_CHANNEL, name: props.ChannelName, @@ -71,6 +74,7 @@ function setupChannelPage(props) { React.render( <Sidebar teamDisplayName={props.TeamDisplayName} + teamName={props.TeamName} teamType={props.TeamType} />, document.getElementById('sidebar-left') diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx index d2dedb271..e45d3d981 100644 --- a/web/react/stores/browser_store.jsx +++ b/web/react/stores/browser_store.jsx @@ -4,7 +4,7 @@ var UserStore; function getPrefix() { if (!UserStore) { - UserStore = require('./user_store.jsx'); + UserStore = require('./user_store.jsx'); //eslint-disable-line global-require } return UserStore.getCurrentId() + '_'; } diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx index 1f33fe03b..fd9117747 100644 --- a/web/react/stores/team_store.jsx +++ b/web/react/stores/team_store.jsx @@ -10,12 +10,12 @@ var BrowserStore = require('../stores/browser_store.jsx'); var CHANGE_EVENT = 'change'; -var utils; +var Utils; function getWindowLocationOrigin() { - if (!utils) { - utils = require('../utils/utils.jsx'); + if (!Utils) { + Utils = require('../utils/utils.jsx'); //eslint-disable-line global-require } - return utils.getWindowLocationOrigin(); + return Utils.getWindowLocationOrigin(); } class TeamStoreClass extends EventEmitter { diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 5cb165b4c..d9f486009 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -15,7 +15,7 @@ function handleError(methodName, xhr, status, err) { var e = null; try { e = JSON.parse(xhr.responseText); - } catch(parseError) { + } catch (parseError) { e = null; } diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index da59f8e5a..67414dc3b 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -179,7 +179,7 @@ module.exports = { centerChannelColor: '#DDDDDD', newMessageSeparator: '#5de5da', linkColor: '#A4FFEB', - buttonBg: '#1dacfc', + buttonBg: '#4CBBA4', buttonColor: '#FFFFFF' }, windows10: { diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 8b20e2adf..307a311ab 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -231,10 +231,10 @@ function testUrlMatch(text) { var matchText = match.getMatchedText(); linkData.text = matchText; - if (matchText.trim().indexOf('http') !== 0) { - linkData.link = 'http://' + matchText; - } else { + if (matchText.trim().indexOf('http') === 0) { linkData.link = matchText; + } else { + linkData.link = 'http://' + matchText; } result.push(linkData); @@ -395,35 +395,39 @@ export function toTitleCase(str) { export function applyTheme(theme) { if (theme.sidebarBg) { - changeCss('.sidebar--left', 'background:' + theme.sidebarBg, 1); + changeCss('.sidebar--left, .settings-modal .settings-table .settings-links, .sidebar--menu', 'background:' + theme.sidebarBg, 1); } if (theme.sidebarText) { - changeCss('.sidebar--left .nav li>a, .sidebar--right', 'color:' + theme.sidebarText, 1); - changeCss('.sidebar--left .nav li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.8), 1); + changeCss('.sidebar--left .nav-pills__container li>a, .sidebar--right, .settings-modal .nav-pills>li a, .sidebar--menu', 'color:' + theme.sidebarText, 1); + changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li>a', 'color:' + theme.sidebarText, 1); + changeCss('.sidebar--left .nav-pills__container li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.8), 1); changeCss('.sidebar--left .add-channel-btn:hover, .sidebar--left .add-channel-btn:focus', 'color:' + theme.sidebarText, 1); changeCss('.sidebar--left, .sidebar--right .sidebar--right__header', 'border-color:' + changeOpacity(theme.sidebarText, 0.2), 1); changeCss('.sidebar--left .status path', 'fill:' + changeOpacity(theme.sidebarText, 0.5), 1); + changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li>a', 'border-color:' + changeOpacity(theme.sidebarText, 0.2), 2); } if (theme.sidebarUnreadText) { - changeCss('.sidebar--left .nav li>a.unread-title', 'color:' + theme.sidebarUnreadText + '!important;', 1); + changeCss('.sidebar--left .nav-pills__container li>a.unread-title', 'color:' + theme.sidebarUnreadText + '!important;', 2); } if (theme.sidebarTextHoverBg) { - changeCss('.sidebar--left .nav li>a:hover, .sidebar--left .nav li>a:focus', 'background:' + theme.sidebarTextHoverBg, 1); + changeCss('.sidebar--left .nav-pills__container li>a:hover, .sidebar--left .nav-pills__container li>a:focus, .settings-modal .nav-pills>li:hover a, .settings-modal .nav-pills>li:focus a', 'background:' + theme.sidebarTextHoverBg, 1); + changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li:hover a', 'background:' + theme.sidebarTextHoverBg, 1); } if (theme.sidebarTextHoverColor) { - changeCss('.sidebar--left .nav li>a:hover, .sidebar--left .nav li>a:focus', 'color:' + theme.sidebarTextHoverColor, 2); + changeCss('.sidebar--left .nav-pills__container li>a:hover, .sidebar--left .nav-pills__container li>a:focus, .settings-modal .nav-pills>li:hover a, .settings-modal .nav-pills>li:focus a', 'color:' + theme.sidebarTextHoverColor, 2); + changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li:hover a', 'color:' + theme.sidebarTextHoverColor, 2); } if (theme.sidebarTextActiveBg) { - changeCss('.sidebar--left .nav li.active a, .sidebar--left .nav li.active a:hover, .sidebar--left .nav li.active a:focus', 'background:' + theme.sidebarTextActiveBg, 1); + changeCss('.sidebar--left .nav-pills__container li.active a, .sidebar--left .nav-pills__container li.active a:hover, .sidebar--left .nav-pills__container li.active a:focus, .settings-modal .nav-pills>li.active a, .settings-modal .nav-pills>li.active a:hover, .settings-modal .nav-pills>li.active a:active', 'background:' + theme.sidebarTextActiveBg, 1); } if (theme.sidebarTextActiveColor) { - changeCss('.sidebar--left .nav li.active a, .sidebar--left .nav li.active a:hover, .sidebar--left .nav li.active a:focus', 'color:' + theme.sidebarTextActiveColor, 2); + changeCss('.sidebar--left .nav-pills__container li.active a, .sidebar--left .nav-pills__container li.active a:hover, .sidebar--left .nav-pills__container li.active a:focus, .settings-modal .nav-pills>li.active a, .settings-modal .nav-pills>li.active a:hover, .settings-modal .nav-pills>li.active a:active', 'color:' + theme.sidebarTextActiveColor, 2); } if (theme.sidebarHeaderBg) { @@ -458,45 +462,51 @@ export function applyTheme(theme) { } if (theme.centerChannelBg) { - changeCss('.app__content, .markdown__table, .markdown__table tbody tr, .command-box', 'background:' + theme.centerChannelBg, 1); + changeCss('.app__content, .markdown__table, .markdown__table tbody tr, .command-box, .modal .modal-content, .mentions-name, .mentions--top .mentions-box', 'background:' + theme.centerChannelBg, 1); changeCss('#post-list .post-list-holder-by-time', 'background:' + theme.centerChannelBg, 1); changeCss('#post-create', 'background:' + theme.centerChannelBg, 1); changeCss('.date-separator .separator__text, .new-separator .separator__text', 'background:' + theme.centerChannelBg, 1); changeCss('.post-image__column .post-image__details', 'background:' + theme.centerChannelBg, 1); - changeCss('.sidebar--right', 'background:' + theme.centerChannelBg, 1); + changeCss('.sidebar--right, .dropdown-menu, .popover', 'background:' + theme.centerChannelBg, 1); } if (theme.centerChannelColor) { - changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .loading-screen .loading__content .round, .command-name', 'color:' + theme.centerChannelColor, 1); + changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .command-name, .modal .modal-content, .dropdown-menu, .popover, .mentions-name', 'color:' + theme.centerChannelColor, 1); changeCss('#post-create', 'color:' + theme.centerChannelColor, 2); changeCss('.mentions--top, .command-box', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 3); changeCss('.mentions--top, .command-box', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 2); changeCss('.mentions--top, .command-box', '-moz-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 1); - changeCss('.post-body hr', 'background:' + theme.centerChannelColor, 1); + changeCss('.dropdown-menu, .popover ', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px', 3); + changeCss('.dropdown-menu, .popover ', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px', 2); + changeCss('.dropdown-menu, .popover ', '-moz-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px', 1); + changeCss('.post-body hr, .loading-screen .loading__content .round', 'background:' + theme.centerChannelColor, 1); changeCss('.channel-header .heading', 'color:' + theme.centerChannelColor, 1); changeCss('.markdown__table tbody tr:nth-child(2n)', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); changeCss('.channel-header__info>div.dropdown .header-dropdown__icon', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1); changeCss('.channel-header #member_popover', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1); - changeCss('.custom-textarea, .custom-textarea:focus, .preview-container .preview-div, .post-image__column .post-image__details, .sidebar--right .sidebar-right__body, .markdown__table th, .markdown__table td, .command-box', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); - changeCss('.command-name', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.custom-textarea, .custom-textarea:focus, .preview-container .preview-div, .post-image__column .post-image__details, .sidebar--right .sidebar-right__body, .markdown__table th, .markdown__table td, .command-box, .modal .modal-content, .settings-modal .settings-table .settings-content .divider-light, .dropdown-menu, .modal .modal-header, .popover, .mentions--top .mentions-box', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.command-name, .popover .popover-title', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.dropdown-menu .divider', 'background:' + theme.centerChannelColor, 1); changeCss('.custom-textarea', 'color:' + theme.centerChannelColor, 1); changeCss('.post-image__column', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 2); changeCss('.post-image__column .post-image__details', 'color:' + theme.centerChannelColor, 2); changeCss('.post-image__column a, .post-image__column a:hover, .post-image__column a:focus', 'color:' + theme.centerChannelColor, 1); - changeCss('@media(max-width: 768px){.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.search-bar__container .search__form .search-bar', 'background: transparent; color:' + theme.centerChannelColor, 1); + changeCss('@media(max-width: 768px){.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: inherit;', 1); changeCss('.search-bar__container .search__form', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); changeCss('.channel-intro .channel-intro__content', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1); changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2); - changeCss('.date-separator .separator__hr, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); - changeCss('.channel-intro', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.date-separator .separator__hr, .modal-footer, .modal .custom-textarea, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.modal .custom-textarea:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1); + changeCss('.channel-intro, .settings-modal .settings-table .settings-content .divider-dark, hr, .settings-modal .settings-table .settings-links', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment, .post.post--comment.other--root .post-comment, .post.same--root .post-body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); changeCss('@media(max-width: 1800px){.inner__wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); - changeCss('.post:hover, .sidebar--right .sidebar--right__header', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); - changeCss('.date-separator.hovered--before:after, .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); - changeCss('.date-separator.hovered--after:before, .new-separator.hovered--after:before, .command-name:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); + changeCss('.post:hover, .sidebar--right .sidebar--right__header, .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); + changeCss('.date-separator.hovered--before:after, .date-separator.hovered--after:before, .new-separator.hovered--after:before, .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); + changeCss('.command-name:hover, .mentions-name:hover, .mentions-focus, .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1); changeCss('.post.current--user:hover .post-body ', 'background: none;', 1); changeCss('.sidebar--right', 'color:' + theme.centerChannelColor, 2); } @@ -507,7 +517,7 @@ export function applyTheme(theme) { } if (theme.linkColor) { - changeCss('a, a:focus, a:hover', 'color:' + theme.linkColor, 1); + changeCss('a, a:focus, a:hover, .btn, .btn:focus, .btn:hover', 'color:' + theme.linkColor, 1); changeCss('.post .comment-icon__container', 'fill:' + theme.linkColor, 1); } @@ -630,7 +640,7 @@ export function isValidUsername(name) { error = 'Must be between 3 and 15 characters'; } else if (!(/^[a-z0-9\.\-\_]+$/).test(name)) { error = "Must contain only letters, numbers, and the symbols '.', '-', and '_'."; - } else if (!(/[a-z]/).test(name.charAt(0))) { + } else if (!(/[a-z]/).test(name.charAt(0))) { //eslint-disable-line no-negated-condition error = 'First character must be a letter.'; } else { for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++) { |