diff options
Diffstat (limited to 'web/react')
30 files changed, 227 insertions, 154 deletions
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index 1a0c9c6d5..95b4caa12 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -254,7 +254,7 @@ export default class ActivityLogModal extends React.Component { <p className='session-help-text'> <FormattedMessage id='activity_log.sessionsDescription' - defaultMessage="Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session." + defaultMessage="Sessions are created when you log in to a new browser on a device. Sessions let you use Mattermost without having to log in again for a time period specified by the System Admin. If you want to log out sooner, use the 'Logout' button below to end a session." /> </p> {content} diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx index 97c615768..2422588cf 100644 --- a/web/react/components/center_panel.jsx +++ b/web/react/components/center_panel.jsx @@ -33,7 +33,8 @@ export default class CenterPanel extends React.Component { this.state = { showTutorialScreens: tutorialStep === TutorialSteps.INTRO_SCREENS, showPostFocus: ChannelStore.getPostMode() === ChannelStore.POST_MODE_FOCUS, - user: UserStore.getCurrentUser() + user: UserStore.getCurrentUser(), + profiles: JSON.parse(JSON.stringify(UserStore.getProfiles())) }; } componentDidMount() { @@ -54,7 +55,7 @@ export default class CenterPanel extends React.Component { this.setState({showPostFocus: ChannelStore.getPostMode() === ChannelStore.POST_MODE_FOCUS}); } onUserChange() { - this.setState({user: UserStore.getCurrentUser()}); + this.setState({user: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))}); } render() { const channel = ChannelStore.getCurrent(); @@ -65,7 +66,7 @@ export default class CenterPanel extends React.Component { postsContainer = <TutorialIntroScreens/>; createPost = null; } else if (this.state.showPostFocus) { - postsContainer = <PostFocusView/>; + postsContainer = <PostFocusView profiles={this.state.profiles}/>; handleClick = function clickHandler(e) { e.preventDefault(); @@ -87,7 +88,7 @@ export default class CenterPanel extends React.Component { </div> ); } else { - postsContainer = <PostsViewContainer/>; + postsContainer = <PostsViewContainer profiles={this.state.profiles}/>; createPost = ( <div className='post-create__container' diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index 727f84e8e..51be13dcf 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -194,6 +194,17 @@ export default class ChannelHeader extends React.Component { ); } + let popoverListMembers; + if (!isDirect) { + popoverListMembers = ( + <PopoverListMembers + members={this.state.users} + memberCount={this.state.userCount} + channelId={channel.id} + /> + ); + } + const dropdownContents = []; if (isDirect) { dropdownContents.push( @@ -442,11 +453,7 @@ export default class ChannelHeader extends React.Component { </div> </th> <th> - <PopoverListMembers - members={this.state.users} - memberCount={this.state.userCount} - channelId={channel.id} - /> + {popoverListMembers} </th> <th className='search-bar__container'><NavbarSearchBox/></th> <th> diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx index f3000ee05..e47f2aa50 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -6,6 +6,7 @@ AsyncClient with requests. */ import * as AsyncClient from '../utils/async_client.jsx'; +import * as Client from '../utils/client.jsx'; import SocketStore from '../stores/socket_store.jsx'; import ChannelStore from '../stores/channel_store.jsx'; import PostStore from '../stores/post_store.jsx'; @@ -45,6 +46,14 @@ const holders = defineMessages({ wrote: { id: 'channel_loader.wrote', defaultMessage: ' wrote: ' + }, + connectionError: { + id: 'channel_loader.connection_error', + defaultMessage: 'There appears to be a problem with your internet connection.' + }, + unknownError: { + id: 'channel_loader.unknown_error', + defaultMessage: 'We received an unexpected status code from the server.' } }); @@ -67,6 +76,11 @@ class ChannelLoader extends React.Component { wrote: formatMessage(holders.wrote) }); + Client.setTranslations({ + connectionError: formatMessage(holders.connectionError), + unknownError: formatMessage(holders.unknownError) + }); + this.state = {}; } componentDidMount() { diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx index f8e3e406a..5b2dd1197 100644 --- a/web/react/components/delete_post_modal.jsx +++ b/web/react/components/delete_post_modal.jsx @@ -5,7 +5,6 @@ import * as Client from '../utils/client.jsx'; import PostStore from '../stores/post_store.jsx'; import ModalStore from '../stores/modal_store.jsx'; var Modal = ReactBootstrap.Modal; -import * as Utils from '../utils/utils.jsx'; import * as AsyncClient from '../utils/async_client.jsx'; import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; import Constants from '../utils/constants.jsx'; @@ -21,9 +20,6 @@ export default class DeletePostModal extends React.Component { this.handleDelete = this.handleDelete.bind(this); this.handleToggle = this.handleToggle.bind(this); this.handleHide = this.handleHide.bind(this); - this.onListenerChange = this.onListenerChange.bind(this); - - this.selectedList = null; this.state = { show: false, @@ -35,11 +31,9 @@ export default class DeletePostModal extends React.Component { componentDidMount() { ModalStore.addModalListener(ActionTypes.TOGGLE_DELETE_POST_MODAL, this.handleToggle); - PostStore.addSelectedPostChangeListener(this.onListenerChange); } componentWillUnmount() { - PostStore.removeSelectedPostChangeListener(this.onListenerChange); ModalStore.removeModalListener(ActionTypes.TOGGLE_DELETE_POST_MODAL, this.handleToggle); } @@ -56,40 +50,15 @@ export default class DeletePostModal extends React.Component { this.state.post.channel_id, this.state.post.id, () => { - var selectedList = this.selectedList; - - if (selectedList && selectedList.order && selectedList.order.length > 0) { - var selectedPost = selectedList.posts[selectedList.order[0]]; - if ((selectedPost.id === this.state.post.id && !this.state.root_id) || selectedPost.root_id === this.state.post.id) { - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_SEARCH, - results: null - }); - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_POST_SELECTED, - postId: null - }); - } else if (selectedPost.id === this.state.post.id && this.state.root_id) { - if (selectedPost.root_id && selectedPost.root_id.length > 0 && selectedList.posts[selectedPost.root_id]) { - selectedList.order = [selectedPost.root_id]; - delete selectedList.posts[selectedPost.id]; - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_POST_SELECTED, - postId: selectedPost.root_id - }); - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_SEARCH, - results: null - }); - } - } - } - PostStore.deletePost(this.state.post); AsyncClient.getPosts(this.state.post.channel_id); + + if (this.state.post.id === PostStore.getSelectedPostId()) { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_POST_SELECTED, + postId: null + }); + } }, (err) => { AsyncClient.dispatchError(err, 'deletePost'); @@ -112,13 +81,6 @@ export default class DeletePostModal extends React.Component { this.setState({show: false}); } - onListenerChange() { - var newList = PostStore.getSelectedPost(); - if (!Utils.areObjectsEqual(this.selectedList, newList)) { - this.selectedList = newList; - } - } - render() { if (!this.state.post) { return null; diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx index f04185b46..9a114c544 100644 --- a/web/react/components/error_bar.jsx +++ b/web/react/components/error_bar.jsx @@ -38,25 +38,9 @@ export default class ErrorBar extends React.Component { return false; } - if (s.connErrorCount && s.connErrorCount >= 1 && s.connErrorCount < 7) { - return false; - } - return true; } - isConnectionError(s) { - if (!s.connErrorCount || s.connErrorCount === 0) { - return false; - } - - if (s.connErrorCount > 7) { - return true; - } - - return false; - } - componentWillMount() { if (global.window.mm_config.SendEmailNotifications === 'false') { ErrorStore.storeLastError({message: this.props.intl.formatMessage(messages.preview)}); diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx index 7b1f6170d..23bc10781 100644 --- a/web/react/components/member_list_team_item.jsx +++ b/web/react/components/member_list_team_item.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. import UserStore from '../stores/user_store.jsx'; +import ChannelStore from '../stores/channel_store.jsx'; import * as Client from '../utils/client.jsx'; import * as AsyncClient from '../utils/async_client.jsx'; import * as Utils from '../utils/utils.jsx'; @@ -71,6 +72,7 @@ export default class MemberListTeamItem extends React.Component { Client.updateActive(this.props.user.id, true, () => { AsyncClient.getProfiles(); + AsyncClient.getChannelExtraInfo(ChannelStore.getCurrentId()); }, (err) => { this.setState({serverError: err.message}); @@ -81,6 +83,7 @@ export default class MemberListTeamItem extends React.Component { Client.updateActive(this.props.user.id, false, () => { AsyncClient.getProfiles(); + AsyncClient.getChannelExtraInfo(ChannelStore.getCurrentId()); }, (err) => { this.setState({serverError: err.message}); diff --git a/web/react/components/post_body_additional_content.jsx b/web/react/components/post_body_additional_content.jsx index a76c59fb3..c2a928f3b 100644 --- a/web/react/components/post_body_additional_content.jsx +++ b/web/react/components/post_body_additional_content.jsx @@ -16,16 +16,28 @@ export default class PostBodyAdditionalContent extends React.Component { this.getSlackAttachment = this.getSlackAttachment.bind(this); this.getOEmbedProvider = this.getOEmbedProvider.bind(this); + this.generateEmbed = this.generateEmbed.bind(this); + this.toggleEmbedVisibility = this.toggleEmbedVisibility.bind(this); + + this.state = { + embedVisible: true + }; } - shouldComponentUpdate(nextProps) { + shouldComponentUpdate(nextProps, nextState) { if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) { return true; } - + if (nextState.embedVisible !== this.state.embedVisible) { + return true; + } return false; } + toggleEmbedVisibility() { + this.setState({embedVisible: !this.state.embedVisible}); + } + getSlackAttachment() { let attachments = []; if (this.props.post.props && this.props.post.props.attachments) { @@ -51,7 +63,7 @@ export default class PostBodyAdditionalContent extends React.Component { return null; } - render() { + generateEmbed() { if (this.props.post.type === 'slack_attachment') { return this.getSlackAttachment(); } @@ -98,6 +110,28 @@ export default class PostBodyAdditionalContent extends React.Component { return null; } + + render() { + var generateEmbed = this.generateEmbed(); + if (generateEmbed) { + return ( + <div> + <a className='post__embed-visibility' + data-expanded={this.state.embedVisible} + aria-label='Toggle Embed Visibility' + onClick={this.toggleEmbedVisibility} + > + </a> + <div className='post__embed-container' + hidden={!this.state.embedVisible} + > + {generateEmbed} + </div> + </div> + ); + } + return null; + } } PostBodyAdditionalContent.propTypes = { diff --git a/web/react/components/post_focus_view.jsx b/web/react/components/post_focus_view.jsx index b9b6acd5f..44a0bae09 100644 --- a/web/react/components/post_focus_view.jsx +++ b/web/react/components/post_focus_view.jsx @@ -105,6 +105,7 @@ export default class PostFocusView extends React.Component { introText={this.getIntroMessage()} messageSeparatorTime={0} postsToHighlight={postsToHighlight} + profiles={this.props.profiles} /> </div> ); @@ -114,4 +115,5 @@ PostFocusView.defaultProps = { }; PostFocusView.propTypes = { + profiles: React.PropTypes.object }; diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx index 1ea7711ea..9a1673483 100644 --- a/web/react/components/posts_view.jsx +++ b/web/react/components/posts_view.jsx @@ -321,7 +321,7 @@ export default class PostsView extends React.Component { if (this.refs.newMessageSeparator) { var objDiv = this.refs.postlist; objDiv.scrollTop = this.refs.newMessageSeparator.offsetTop; //scrolls node to top of Div - } else { + } else if (this.refs.postlist) { this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight; } }); diff --git a/web/react/components/posts_view_container.jsx b/web/react/components/posts_view_container.jsx index 1b14e8681..92d658b55 100644 --- a/web/react/components/posts_view_container.jsx +++ b/web/react/components/posts_view_container.jsx @@ -6,7 +6,6 @@ import LoadingScreen from './loading_screen.jsx'; import ChannelStore from '../stores/channel_store.jsx'; import PostStore from '../stores/post_store.jsx'; -import UserStore from '../stores/user_store.jsx'; import * as Utils from '../utils/utils.jsx'; import * as EventHelpers from '../dispatcher/event_helpers.jsx'; @@ -25,13 +24,11 @@ export default class PostsViewContainer extends React.Component { this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this); this.loadMorePostsTop = this.loadMorePostsTop.bind(this); this.handlePostsViewJumpRequest = this.handlePostsViewJumpRequest.bind(this); - this.onUserChange = this.onUserChange.bind(this); const currentChannelId = ChannelStore.getCurrentId(); const state = { scrollType: PostsView.SCROLL_TYPE_BOTTOM, - scrollPost: null, - profiles: JSON.parse(JSON.stringify(UserStore.getProfiles())) + scrollPost: null }; if (currentChannelId) { Object.assign(state, { @@ -57,14 +54,12 @@ export default class PostsViewContainer extends React.Component { ChannelStore.addLeaveListener(this.onChannelLeave); PostStore.addChangeListener(this.onPostsChange); PostStore.addPostsViewJumpListener(this.handlePostsViewJumpRequest); - UserStore.addChangeListener(this.onUserChange); } componentWillUnmount() { ChannelStore.removeChangeListener(this.onChannelChange); ChannelStore.removeLeaveListener(this.onChannelLeave); PostStore.removeChangeListener(this.onPostsChange); PostStore.removePostsViewJumpListener(this.handlePostsViewJumpRequest); - UserStore.removeChangeListener(this.onUserChange); } handlePostsViewJumpRequest(type, post) { switch (type) { @@ -140,9 +135,6 @@ export default class PostsViewContainer extends React.Component { atTop[this.state.currentChannelIndex] = PostStore.getVisibilityAtTop(currentChannelId); this.setState({postLists, atTop}); } - onUserChange() { - this.setState({profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))}); - } getChannelPosts(id) { return PostStore.getVisiblePosts(id); } @@ -188,7 +180,7 @@ export default class PostsViewContainer extends React.Component { showMoreMessagesBottom={false} introText={channel ? createChannelIntroMessage(channel) : null} messageSeparatorTime={this.state.currentLastViewed} - profiles={this.state.profiles} + profiles={this.props.profiles} /> ); if (!postLists[i] && isActive) { @@ -208,3 +200,7 @@ export default class PostsViewContainer extends React.Component { ); } } + +PostsViewContainer.propTypes = { + profiles: React.PropTypes.object +}; diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx index 49ebccdbb..e96ff0db2 100644 --- a/web/react/components/rename_channel_modal.jsx +++ b/web/react/components/rename_channel_modal.jsx @@ -229,7 +229,6 @@ export default class RenameChannelModal extends React.Component { let readOnlyHandleInput = false; if (this.state.channelName === Constants.DEFAULT_CHANNEL) { handleInputLabel += formatMessage(holders.defaultError); - handleInputClass += ' disabled-input'; readOnlyHandleInput = true; } diff --git a/web/react/components/rhs_thread.jsx b/web/react/components/rhs_thread.jsx index 4d770287c..292624846 100644 --- a/web/react/components/rhs_thread.jsx +++ b/web/react/components/rhs_thread.jsx @@ -91,9 +91,11 @@ export default class RhsThread extends React.Component { }); } onPostChange() { - const selected = PostStore.getSelectedPost(); - const posts = PostStore.getSelectedPostThread(); - this.setState({posts, selected}); + if (this.mounted) { + const selected = PostStore.getSelectedPost(); + const posts = PostStore.getSelectedPostThread(); + this.setState({posts, selected}); + } } onUserChange() { const profiles = JSON.parse(JSON.stringify(UserStore.getProfiles())); @@ -185,7 +187,7 @@ export default class RhsThread extends React.Component { <div className='post-right-comments-container'> {postsArray.map(function mapPosts(comPost) { let p; - if (UserStore.getCurrentId() === selected.user_id) { + if (UserStore.getCurrentId() === comPost.user_id) { p = UserStore.getCurrentUser(); } else { p = profiles[comPost.user_id]; diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx index 55ece2c97..8985063a0 100644 --- a/web/react/components/search_results.jsx +++ b/web/react/components/search_results.jsx @@ -61,7 +61,15 @@ export default class SearchResults extends React.Component { } shouldComponentUpdate(nextProps, nextState) { - return !Utils.areObjectsEqual(this.props, nextProps) || !Utils.areObjectsEqual(this.state, nextState); + if (!Utils.areObjectsEqual(this.props, nextProps)) { + return true; + } + + if (!Utils.areObjectsEqual(this.state, nextState)) { + return true; + } + + return false; } componentDidUpdate() { @@ -143,13 +151,19 @@ export default class SearchResults extends React.Component { ); } else { ctls = results.order.map(function mymap(id) { - var post = results.posts[id]; + const post = results.posts[id]; + let profile; + if (UserStore.getCurrentId() === post.user_id) { + profile = UserStore.getCurrentUser(); + } else { + profile = profiles[post.user_id]; + } return ( <SearchResultsItem key={post.id} channel={this.state.channels.get(post.channel_id)} post={post} - user={profiles[post.user_id]} + user={profile} term={searchTerm} isMentionSearch={this.props.isMentionSearch} /> diff --git a/web/react/components/suggestion/at_mention_provider.jsx b/web/react/components/suggestion/at_mention_provider.jsx index c4e1314c9..064b75ac5 100644 --- a/web/react/components/suggestion/at_mention_provider.jsx +++ b/web/react/components/suggestion/at_mention_provider.jsx @@ -83,13 +83,13 @@ export default class AtMentionProvider { if (captured) { const usernamePrefix = captured[1]; - const users = UserStore.getProfiles(); + const users = UserStore.getActiveOnlyProfiles(true); let filtered = []; for (const id of Object.keys(users)) { const user = users[id]; - if (user.username.startsWith(usernamePrefix)) { + if (user.username.startsWith(usernamePrefix) && user.delete_at <= 0) { filtered.push(user); } diff --git a/web/react/components/suggestion/command_provider.jsx b/web/react/components/suggestion/command_provider.jsx index 09c9b9982..21d6d0e0e 100644 --- a/web/react/components/suggestion/command_provider.jsx +++ b/web/react/components/suggestion/command_provider.jsx @@ -18,7 +18,7 @@ class CommandSuggestion extends React.Component { onClick={onClick} > <div className='command__title'> - <string>{item.suggestion}</string> + <string>{item.suggestion} {item.hint}</string> </div> <div className='command__desc'> {item.description} diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx index ec299087d..23ecfb57b 100644 --- a/web/react/components/textbox.jsx +++ b/web/react/components/textbox.jsx @@ -59,9 +59,9 @@ export default class Textbox extends React.Component { } onRecievedError() { - const errorState = ErrorStore.getLastError(); + const errorCount = ErrorStore.getConnectionErrorCount(); - if (errorState && errorState.connErrorCount > 0) { + if (errorCount > 0) { this.setState({connection: 'bad-connection'}); } else { this.setState({connection: ''}); diff --git a/web/react/components/tutorial/tutorial_intro_screens.jsx b/web/react/components/tutorial/tutorial_intro_screens.jsx index 2bacdeb28..45ebceb28 100644 --- a/web/react/components/tutorial/tutorial_intro_screens.jsx +++ b/web/react/components/tutorial/tutorial_intro_screens.jsx @@ -211,26 +211,28 @@ export default class TutorialIntroScreens extends React.Component { <div className='tutorial__content'> <div className='tutorial__steps'> {screen} - <button - className='btn btn-primary' - tabIndex='1' - onClick={this.handleNext} - > - <FormattedMessage - id='tutorial_intro.next' - defaultMessage='Next' - /> - </button> - <a - className='tutorial-skip' - href='#' - onClick={this.skipTutorial} - > - <FormattedMessage - id='tutorial_intro.skip' - defaultMessage='Skip tutorial' - /> - </a> + <div className='tutorial__footer'> + <button + className='btn btn-primary' + tabIndex='1' + onClick={this.handleNext} + > + <FormattedMessage + id='tutorial_intro.next' + defaultMessage='Next' + /> + </button> + <a + className='tutorial-skip' + href='#' + onClick={this.skipTutorial} + > + <FormattedMessage + id='tutorial_intro.skip' + defaultMessage='Skip tutorial' + /> + </a> + </div> </div> </div> </div> diff --git a/web/react/components/user_settings/manage_command_hooks.jsx b/web/react/components/user_settings/manage_command_hooks.jsx index bd0659a47..2947138be 100644 --- a/web/react/components/user_settings/manage_command_hooks.jsx +++ b/web/react/components/user_settings/manage_command_hooks.jsx @@ -420,7 +420,7 @@ export default class ManageCommandCmds extends React.Component { <div key='addCommandCmd'> <FormattedHTMLMessage id='user.settings.cmds.add_desc' - defaultMessage='Create slash commands to send events to external integrations and receive a response. For example typing `/patient Joe Smith` could bring back search results from your internal health records management system for the name “Joe Smith”. Please see <a href="http://docs.mattermost.com/developer/slash-commands.html">Slash commands documentation</a> for detailed instructions.' + defaultMessage='Create slash commands to send events to external integrations and receive a response. For example typing `/patient Joe Smith` could bring back search results from your internal health records management system for the name “Joe Smith”. Please see <a href="http://docs.mattermost.com/developer/slash-commands.html">Slash commands documentation</a> for detailed instructions. View all slash commands configured on this team below.' /> <div><label className='control-label padding-top x2'> <FormattedMessage diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx index 68e99be7d..79a71b5ac 100644 --- a/web/react/components/user_settings/manage_incoming_hooks.jsx +++ b/web/react/components/user_settings/manage_incoming_hooks.jsx @@ -183,7 +183,7 @@ export default class ManageIncomingHooks extends React.Component { <div key='addIncomingHook'> <FormattedHTMLMessage id='user.settings.hooks_in.description' - defaultMessage='Create webhook URLs for use in external integrations. Please see <a href="http://docs.mattermost.com/developer/webhooks-incoming.html" target="_blank">incoming webhooks documentation</a> to learn more.' + defaultMessage='Create webhook URLs for use in external integrations. Please see <a href="http://docs.mattermost.com/developer/webhooks-incoming.html" target="_blank">incoming webhooks documentation</a> to learn more. View all incoming webhooks configured on this team below.' /> <div><label className='control-label padding-top x2'> <FormattedMessage diff --git a/web/react/components/user_settings/manage_outgoing_hooks.jsx b/web/react/components/user_settings/manage_outgoing_hooks.jsx index 9c3a60ed5..487254d15 100644 --- a/web/react/components/user_settings/manage_outgoing_hooks.jsx +++ b/web/react/components/user_settings/manage_outgoing_hooks.jsx @@ -284,7 +284,7 @@ class ManageOutgoingHooks extends React.Component { <div key='addOutgoingHook'> <FormattedHTMLMessage id='user.settings.hooks_out.addDescription' - defaultMessage='Create webhooks to send new message events to an external integration. Please see <a href="http://docs.mattermost.com/developer/webhooks-outgoing.html" target="_blank">outgoing webhooks documentation</a> to learn more.' + defaultMessage='Create webhooks to send new message events to an external integration. Please see <a href="http://docs.mattermost.com/developer/webhooks-outgoing.html" target="_blank">outgoing webhooks documentation</a> to learn more. View all outgoing webhooks configured on this team below.' /> <div><label className='control-label padding-top x2'> <FormattedMessage diff --git a/web/react/components/user_settings/user_settings_advanced.jsx b/web/react/components/user_settings/user_settings_advanced.jsx index e513f81d2..efaf63ada 100644 --- a/web/react/components/user_settings/user_settings_advanced.jsx +++ b/web/react/components/user_settings/user_settings_advanced.jsx @@ -47,6 +47,10 @@ const holders = defineMessages({ EMBED_PREVIEW: { id: 'user.settings.advance.embed_preview', defaultMessage: 'Show preview snippet of links below message' + }, + EMBED_TOGGLE: { + id: 'user.settings.advance.embed_toggle', + defaultMessage: 'Show toggle for all embed previews' } }); @@ -240,7 +244,7 @@ class AdvancedSettingsDisplay extends React.Component { this.toggleFeature(feature.label, e.target.checked); }} /> - {formatMessage({id: 'user.settings.advance.' + feature.label})} + {formatMessage(holders[key])} </label> </div> </div> @@ -334,4 +338,4 @@ AdvancedSettingsDisplay.propTypes = { collapseModal: React.PropTypes.func.isRequired }; -export default injectIntl(AdvancedSettingsDisplay);
\ No newline at end of file +export default injectIntl(AdvancedSettingsDisplay); diff --git a/web/react/dispatcher/event_helpers.jsx b/web/react/dispatcher/event_helpers.jsx index 47a16950e..367347d4b 100644 --- a/web/react/dispatcher/event_helpers.jsx +++ b/web/react/dispatcher/event_helpers.jsx @@ -47,8 +47,15 @@ export function emitPostFocusRightHandSideFromSearch(post, isMentionSearch) { post.id, (data) => { AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_POSTS, + id: post.channel_id, + numRequested: 0, + post_list: data + }); + + AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_POST_SELECTED, - post_list: data, + postId: Utils.getRootId(post), from_search: SearchStore.getSearchTerm() }); diff --git a/web/react/stores/error_store.jsx b/web/react/stores/error_store.jsx index 5afcefd12..6928b1e59 100644 --- a/web/react/stores/error_store.jsx +++ b/web/react/stores/error_store.jsx @@ -18,7 +18,6 @@ class ErrorStoreClass extends EventEmitter { this.emitChange = this.emitChange.bind(this); this.addChangeListener = this.addChangeListener.bind(this); this.removeChangeListener = this.removeChangeListener.bind(this); - this.handledError = this.handledError.bind(this); this.getLastError = this.getLastError.bind(this); this.storeLastError = this.storeLastError.bind(this); } @@ -35,10 +34,6 @@ class ErrorStoreClass extends EventEmitter { this.removeListener(CHANGE_EVENT, callback); } - handledError() { - BrowserStore.removeItem('last_error'); - } - getLastError() { return BrowserStore.getItem('last_error'); } @@ -47,8 +42,23 @@ class ErrorStoreClass extends EventEmitter { BrowserStore.setItem('last_error', error); } + getConnectionErrorCount() { + var count = BrowserStore.getItem('last_error_conn'); + + if (count == null) { + return 0; + } + + return count; + } + + setConnectionErrorCount(count) { + BrowserStore.setItem('last_error_conn', count); + } + clearLastError() { BrowserStore.removeItem('last_error'); + BrowserStore.removeItem('last_error_conn'); } } diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx index 5100a4936..a6dfcd46f 100644 --- a/web/react/stores/post_store.jsx +++ b/web/react/stores/post_store.jsx @@ -265,6 +265,12 @@ class PostStoreClass extends EventEmitter { } deletePost(post) { + const postInfo = this.postsInfo[post.channel_id]; + if (!postInfo) { + // the post that has been deleted is in a channel that we haven't seen so just ignore it + return; + } + const postList = this.postsInfo[post.channel_id].postList; if (isPostListNull(postList)) { @@ -297,6 +303,20 @@ class PostStoreClass extends EventEmitter { postList.order.splice(index, 1); } + for (const pid in postList.posts) { + if (!postList.posts.hasOwnProperty(pid)) { + continue; + } + + if (postList.posts[pid].root_id === post.id) { + Reflect.deleteProperty(postList.posts, pid); + const commentIndex = postList.order.indexOf(pid); + if (commentIndex !== -1) { + postList.order.splice(commentIndex, 1); + } + } + } + this.postsInfo[channelId].postList = postList; } diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx index efb57e226..9b2b049b7 100644 --- a/web/react/stores/socket_store.jsx +++ b/web/react/stores/socket_store.jsx @@ -58,6 +58,10 @@ class SocketStoreClass extends EventEmitter { if (this.failCount === 0) { console.log('websocket connecting to ' + connUrl); //eslint-disable-line no-console + if (ErrorStore.getConnectionErrorCount() > 0) { + ErrorStore.setConnectionErrorCount(0); + ErrorStore.emitChange(); + } } conn = new WebSocket(connUrl); @@ -65,10 +69,8 @@ class SocketStoreClass extends EventEmitter { if (this.failCount > 0) { console.log('websocket re-established connection'); //eslint-disable-line no-console - if (ErrorStore.getLastError()) { - ErrorStore.storeLastError(null); - ErrorStore.emitChange(); - } + ErrorStore.clearLastError(); + ErrorStore.emitChange(); AsyncClient.getChannels(); AsyncClient.getPosts(ChannelStore.getCurrentId()); @@ -86,7 +88,11 @@ class SocketStoreClass extends EventEmitter { this.failCount = this.failCount + 1; - ErrorStore.storeLastError({connErrorCount: this.failCount, message: this.translations.socketError}); + if (this.failCount > 7) { + ErrorStore.storeLastError({message: this.translations.socketError}); + } + + ErrorStore.setConnectionErrorCount(this.failCount); ErrorStore.emitChange(); setTimeout( diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index 13b57092d..ca9d81865 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -787,11 +787,13 @@ export function getSuggestedCommands(command, suggestionId, component) { data.forEach((cmd) => { if (('/' + cmd.trigger).indexOf(command) === 0) { let s = '/' + cmd.trigger; + let hint = ''; if (cmd.auto_complete_hint && cmd.auto_complete_hint.length !== 0) { - s += ' ' + cmd.auto_complete_hint; + hint = cmd.auto_complete_hint; } matches.push({ suggestion: s, + hint, description: cmd.auto_complete_desc }); } diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 81bdb7293..f647e2296 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -4,6 +4,15 @@ import BrowserStore from '../stores/browser_store.jsx'; import TeamStore from '../stores/team_store.jsx'; import ErrorStore from '../stores/error_store.jsx'; +let translations = { + connectionError: 'There appears to be a problem with your internet connection.', + unknownError: 'We received an unexpected status code from the server.' +}; + +export function setTranslations(messages) { + translations = messages; +} + export function track(category, action, label, property, value) { global.window.analytics.track(action, {category, label, property, value}); } @@ -23,23 +32,14 @@ function handleError(methodName, xhr, status, err) { var msg = ''; if (e) { - msg = 'error in ' + methodName + ' msg=' + e.message + ' detail=' + e.detailed_error + ' rid=' + e.request_id; + msg = 'method=' + methodName + ' msg=' + e.message + ' detail=' + e.detailed_error + ' rid=' + e.request_id; } else { - msg = 'error in ' + methodName + ' status=' + status + ' statusCode=' + xhr.status + ' err=' + err; + msg = 'method=' + methodName + ' status=' + status + ' statusCode=' + xhr.status + ' err=' + err; if (xhr.status === 0) { - let errorCount = 1; - const oldError = ErrorStore.getLastError(); - let connectError = 'There appears to be a problem with your internet connection'; - - if (oldError && oldError.connErrorCount) { - errorCount += oldError.connErrorCount; - connectError = 'Please check connection, Mattermost unreachable. If issue persists, ask administrator to check WebSocket port.'; - } - - e = {message: connectError, connErrorCount: errorCount}; + e = {message: translations.connectionError}; } else { - e = {message: 'We received an unexpected status code from the server (' + xhr.status + ')'}; + e = {message: translations.unknownError + ' (' + xhr.status + ')'}; } } @@ -279,7 +279,7 @@ export function logout() { var currentTeamUrl = TeamStore.getCurrentTeamUrl(); BrowserStore.signalLogout(); BrowserStore.clear(); - ErrorStore.storeLastError(null); + ErrorStore.clearLastError(); window.location.href = currentTeamUrl + '/logout'; } diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 766a86686..0a4944708 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -479,6 +479,10 @@ export default { EMBED_PREVIEW: { label: 'embed_preview', description: 'Show preview snippet of links below message' + }, + EMBED_TOGGLE: { + label: 'embed_toggle', + description: 'Show toggle for all embed previews' } }, OVERLAY_TIME_DELAY: 400, diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 77eab7395..3e531c821 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -156,10 +156,6 @@ export function notifyMe(title, body, channel) { requestedNotificationPermission = true; Notification.requestPermission((permission) => { - if (Notification.permission !== permission) { - Notification.permission = permission; - } - if (permission === 'granted') { try { var notification = new Notification(title, {body: body, tag: body, icon: '/static/images/icon50x50.png'}); @@ -737,7 +733,7 @@ export function applyTheme(theme) { changeCss('.post:hover, .modal .more-table tbody>tr:hover td, .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, .suggestion--selected, .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover, .bot-indicator', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1); - changeCss('code', 'background:' + changeOpacity(theme.centerChannelColor, 0.1), 1); + changeCss('code, .form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control', 'background:' + changeOpacity(theme.centerChannelColor, 0.1), 1); changeCss('@media(min-width: 960px){.post.current--user:hover .post__body ', 'background: none;', 1); changeCss('.sidebar--right', 'color:' + theme.centerChannelColor, 2); changeCss('.search-help-popover .search-autocomplete__item:hover, .settings-modal .settings-table .settings-content .appearance-section .theme-elements__body', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1); @@ -1388,6 +1384,10 @@ export function languages() { { value: 'es', name: 'Español (Beta)' + }, + { + value: 'pt', + name: 'Portugues (Beta)' } ] ); |