summaryrefslogtreecommitdiffstats
path: root/webapp/components
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/components')
-rw-r--r--webapp/components/add_users_to_team/add_users_to_team.jsx7
-rw-r--r--webapp/components/admin_console/push_settings.jsx4
-rw-r--r--webapp/components/admin_console/saml_settings.jsx6
-rw-r--r--webapp/components/channel_header.jsx5
-rw-r--r--webapp/components/channel_invite_modal/channel_invite_modal.jsx7
-rw-r--r--webapp/components/dot_menu/dot_menu.jsx26
-rw-r--r--webapp/components/dot_menu/index.js3
-rw-r--r--webapp/components/emoji_picker/emoji_picker.jsx102
-rw-r--r--webapp/components/emoji_picker/emoji_picker_overlay.jsx17
-rw-r--r--webapp/components/integrations/components/edit_incoming_webhook.jsx10
-rw-r--r--webapp/components/leave_team_modal.jsx1
-rw-r--r--webapp/components/markdown_image.jsx64
-rw-r--r--webapp/components/member_list_channel/member_list_channel.jsx2
-rw-r--r--webapp/components/more_direct_channels/more_direct_channels.jsx7
-rw-r--r--webapp/components/navbar.jsx2
-rw-r--r--webapp/components/pdf_preview.jsx2
-rw-r--r--webapp/components/post_view/post_body/index.js2
-rw-r--r--webapp/components/post_view/post_body_additional_content.jsx2
-rw-r--r--webapp/components/post_view/post_info/post_info.jsx3
-rw-r--r--webapp/components/post_view/post_list.jsx88
-rw-r--r--webapp/components/quick_switch_modal/quick_switch_modal.jsx2
-rw-r--r--webapp/components/rhs_comment.jsx32
-rw-r--r--webapp/components/rhs_root_post.jsx6
-rw-r--r--webapp/components/rhs_thread/index.js12
-rw-r--r--webapp/components/rhs_thread/rhs_thread.jsx17
-rw-r--r--webapp/components/search_results_item.jsx3
-rw-r--r--webapp/components/sidebar_right/sidebar_right.jsx4
-rw-r--r--webapp/components/sidebar_right_menu.jsx17
-rw-r--r--webapp/components/spinner_button.jsx6
-rw-r--r--webapp/components/team_import_tab.jsx8
-rw-r--r--webapp/components/user_profile.jsx6
31 files changed, 301 insertions, 172 deletions
diff --git a/webapp/components/add_users_to_team/add_users_to_team.jsx b/webapp/components/add_users_to_team/add_users_to_team.jsx
index 19e0d674b..e3eb8477b 100644
--- a/webapp/components/add_users_to_team/add_users_to_team.jsx
+++ b/webapp/components/add_users_to_team/add_users_to_team.jsx
@@ -215,6 +215,11 @@ export default class AddUsersToTeam extends React.Component {
/>
);
+ let users = [];
+ if (this.state.users) {
+ users = this.state.users.filter((user) => user.delete_at === 0);
+ }
+
return (
<Modal
dialogClassName={'more-modal more-direct-channels'}
@@ -238,7 +243,7 @@ export default class AddUsersToTeam extends React.Component {
<Modal.Body>
<MultiSelect
key='addUsersToTeamKey'
- options={this.state.users}
+ options={users}
optionRenderer={this.renderOption}
values={this.state.values}
valueRenderer={this.renderValue}
diff --git a/webapp/components/admin_console/push_settings.jsx b/webapp/components/admin_console/push_settings.jsx
index 5461ef730..3b21f727a 100644
--- a/webapp/components/admin_console/push_settings.jsx
+++ b/webapp/components/admin_console/push_settings.jsx
@@ -127,14 +127,14 @@ export default class PushSettings extends AdminSettings {
pushServerHelpText = (
<FormattedHTMLMessage
id='admin.email.mhpnsHelp'
- defaultMessage='Download <a href="https://itunes.apple.com/us/app/mattermost/id984966508?mt=8" target="_blank">Mattermost iOS app</a> from iTunes. Download <a href="https://play.google.com/store/apps/details?id=com.mattermost.mattermost&hl=en" target="_blank">Mattermost Android app</a> from Google Play. Learn more about the <a href="http://docs.mattermost.com/deployment/push.html#hosted-push-notifications-service-hpns" target="_blank">Mattermost Hosted Push Notification Service</a>.'
+ defaultMessage='Download <a href="https://about.mattermost.com/mattermost-ios-app/" target="_blank">Mattermost iOS app</a> from iTunes. Download <a href="https://about.mattermost.com/mattermost-android-app/" target="_blank">Mattermost Android app</a> from Google Play. Learn more about the <a href="http://docs.mattermost.com/deployment/push.html#hosted-push-notifications-service-hpns" target="_blank">Mattermost Hosted Push Notification Service</a>.'
/>
);
} else if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_MTPNS) {
pushServerHelpText = (
<FormattedHTMLMessage
id='admin.email.mtpnsHelp'
- defaultMessage='Download <a href="https://itunes.apple.com/us/app/mattermost/id984966508?mt=8" target="_blank">Mattermost iOS app</a> from iTunes. Download <a href="https://play.google.com/store/apps/details?id=com.mattermost.mattermost&hl=en" target="_blank">Mattermost Android app</a> from Google Play. Learn more about the <a href="http://docs.mattermost.com/deployment/push.html#test-push-notifications-service-tpns" target="_blank">Mattermost Test Push Notification Service</a>.'
+ defaultMessage='Download <a href="https://about.mattermost.com/mattermost-ios-app/" target="_blank">Mattermost iOS app</a> from iTunes. Download <a href="https://about.mattermost.com/mattermost-android-app/" target="_blank">Mattermost Android app</a> from Google Play. Learn more about the <a href="http://docs.mattermost.com/deployment/push.html#test-push-notifications-service-tpns" target="_blank">Mattermost Test Push Notification Service</a>.'
/>
);
} else {
diff --git a/webapp/components/admin_console/saml_settings.jsx b/webapp/components/admin_console/saml_settings.jsx
index 4c0c0c8fd..2358660da 100644
--- a/webapp/components/admin_console/saml_settings.jsx
+++ b/webapp/components/admin_console/saml_settings.jsx
@@ -77,15 +77,15 @@ export default class SamlSettings extends AdminSettings {
AdminActions.samlCertificateStatus(
(data) => {
const files = {};
- if (!data.IdpCertificateFile) {
+ if (!data.idp_certificate_file) {
files.idpCertificateFile = '';
}
- if (!data.PublicCertificateFile) {
+ if (!data.public_certificate_file) {
files.publicCertificateFile = '';
}
- if (!data.PrivateKeyFile) {
+ if (!data.private_key_file) {
files.privateKeyFile = '';
}
this.setState(files);
diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx
index 34d58f5aa..10e568794 100644
--- a/webapp/components/channel_header.jsx
+++ b/webapp/components/channel_header.jsx
@@ -964,7 +964,10 @@ export default class ChannelHeader extends React.Component {
placement='bottom'
overlay={recentMentionsTooltip}
>
- <div className='channel-header__icon icon--hidden'>
+ <div
+ className='channel-header__icon icon--hidden'
+ onClick={this.searchMentions}
+ >
<span
className='icon icon__mentions'
dangerouslySetInnerHTML={{__html: mentionsIcon}}
diff --git a/webapp/components/channel_invite_modal/channel_invite_modal.jsx b/webapp/components/channel_invite_modal/channel_invite_modal.jsx
index d2fceb2c3..8b09a7496 100644
--- a/webapp/components/channel_invite_modal/channel_invite_modal.jsx
+++ b/webapp/components/channel_invite_modal/channel_invite_modal.jsx
@@ -143,13 +143,18 @@ export default class ChannelInviteModal extends React.Component {
inviteError = (<label className='has-error control-label'>{this.state.inviteError}</label>);
}
+ let users = [];
+ if (this.state.users) {
+ users = this.state.users.filter((user) => user.delete_at === 0);
+ }
+
let content;
if (this.state.loading) {
content = (<LoadingScreen/>);
} else {
content = (
<SearchableUserList
- users={this.state.users}
+ users={users}
usersPerPage={USERS_PER_PAGE}
total={this.state.total}
nextPage={this.nextPage}
diff --git a/webapp/components/dot_menu/dot_menu.jsx b/webapp/components/dot_menu/dot_menu.jsx
index eb6a6c005..a2cbb9b48 100644
--- a/webapp/components/dot_menu/dot_menu.jsx
+++ b/webapp/components/dot_menu/dot_menu.jsx
@@ -58,10 +58,12 @@ export default class DotMenu extends Component {
constructor(props) {
super(props);
- this.handleDropdownOpened = this.handleDropdownOpened.bind(this);
- this.canDelete = false;
- this.canEdit = false;
this.editDisableAction = new DelayedAction(this.handleEditDisable);
+
+ this.state = {
+ canDelete: PostUtils.canDeletePost(props.post),
+ canEdit: PostUtils.canEditPost(props.post, this.editDisableAction)
+ };
}
componentDidMount() {
@@ -69,7 +71,11 @@ export default class DotMenu extends Component {
$('#' + this.props.idPrefix + '_dropdown' + this.props.post.id).on('hidden.bs.dropdown', () => this.props.handleDropdownOpened(false));
}
- handleDropdownOpened() {
+ componentWillUnmount() {
+ this.editDisableAction.cancel();
+ }
+
+ handleDropdownOpened = () => {
this.props.handleDropdownOpened(true);
const position = $('#post-list').height() - $(this.refs.dropdownToggle).offset().top;
@@ -80,17 +86,15 @@ export default class DotMenu extends Component {
}
}
- handleEditDisable() {
- this.canEdit = false;
+ handleEditDisable = () => {
+ this.setState({canEdit: false});
}
render() {
const isSystemMessage = PostUtils.isSystemMessage(this.props.post);
const isMobile = Utils.isMobile();
- this.canDelete = PostUtils.canDeletePost(this.props.post);
- this.canEdit = PostUtils.canEditPost(this.props.post, this.editDisableAction);
- if (this.props.idPrefix === Constants.CENTER && (!isMobile && isSystemMessage && !this.canDelete && !this.canEdit)) {
+ if (this.props.idPrefix === Constants.CENTER && (!isMobile && isSystemMessage && !this.state.canDelete && !this.state.canEdit)) {
return null;
}
@@ -157,7 +161,7 @@ export default class DotMenu extends Component {
}
let dotMenuDelete = null;
- if (this.canDelete) {
+ if (this.state.canDelete) {
dotMenuDelete = (
<DotMenuItem
idPrefix={idPrefix + 'Delete'}
@@ -169,7 +173,7 @@ export default class DotMenu extends Component {
}
let dotMenuEdit = null;
- if (this.canEdit) {
+ if (this.state.canEdit) {
dotMenuEdit = (
<DotMenuEdit
idPrefix={idPrefix + 'Edit'}
diff --git a/webapp/components/dot_menu/index.js b/webapp/components/dot_menu/index.js
index eaa1e8d2c..852fe6791 100644
--- a/webapp/components/dot_menu/index.js
+++ b/webapp/components/dot_menu/index.js
@@ -3,7 +3,8 @@
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
-import {flagPost, unflagPost, pinPost, unpinPost} from 'mattermost-redux/actions/posts';
+import {flagPost, unflagPost} from 'mattermost-redux/actions/posts';
+import {pinPost, unpinPost} from 'actions/post_actions.jsx';
import DotMenu from './dot_menu.jsx';
diff --git a/webapp/components/emoji_picker/emoji_picker.jsx b/webapp/components/emoji_picker/emoji_picker.jsx
index a047c1277..0d9b34176 100644
--- a/webapp/components/emoji_picker/emoji_picker.jsx
+++ b/webapp/components/emoji_picker/emoji_picker.jsx
@@ -309,7 +309,7 @@ export default class EmojiPicker extends React.Component {
right: this.props.rightOffset
};
} else {
- pickerStyle = this.props.style;
+ pickerStyle = {...this.props.style};
}
}
@@ -325,91 +325,111 @@ export default class EmojiPicker extends React.Component {
<div className='emoji-picker__categories'>
<EmojiPickerCategory
category='recent'
- icon={<i
- className='fa fa-clock-o'
- title={Utils.localizeMessage('emoji_picker.recent', 'Recently Used')}
- />}
+ icon={
+ <i
+ className='fa fa-clock-o'
+ title={Utils.localizeMessage('emoji_picker.recent', 'Recently Used')}
+ />
+ }
onCategoryClick={this.handleCategoryClick}
selected={this.state.category === 'recent'}
/>
<EmojiPickerCategory
category='people'
- icon={<i
- className='fa fa-smile-o'
- title={Utils.localizeMessage('emoji_picker.people', 'People')}
- />}
+ icon={
+ <i
+ className='fa fa-smile-o'
+ title={Utils.localizeMessage('emoji_picker.people', 'People')}
+ />
+ }
onCategoryClick={this.handleCategoryClick}
selected={this.state.category === 'people'}
/>
<EmojiPickerCategory
category='nature'
- icon={<i
- className='fa fa-leaf'
- title={Utils.localizeMessage('emoji_picker.nature', 'Nature')}
- />}
+ icon={
+ <i
+ className='fa fa-leaf'
+ title={Utils.localizeMessage('emoji_picker.nature', 'Nature')}
+ />
+ }
onCategoryClick={this.handleCategoryClick}
selected={this.state.category === 'nature'}
/>
<EmojiPickerCategory
category='food'
- icon={<i
- className='fa fa-cutlery'
- title={Utils.localizeMessage('emoji_picker.food', 'Food')}
- />}
+ icon={
+ <i
+ className='fa fa-cutlery'
+ title={Utils.localizeMessage('emoji_picker.food', 'Food')}
+ />
+ }
onCategoryClick={this.handleCategoryClick}
selected={this.state.category === 'food'}
/>
<EmojiPickerCategory
category='activity'
- icon={<i
- className='fa fa-futbol-o'
- title={Utils.localizeMessage('emoji_picker.activity', 'Activity')}
- />}
+ icon={
+ <i
+ className='fa fa-futbol-o'
+ title={Utils.localizeMessage('emoji_picker.activity', 'Activity')}
+ />
+ }
onCategoryClick={this.handleCategoryClick}
selected={this.state.category === 'activity'}
/>
<EmojiPickerCategory
category='travel'
- icon={<i
- className='fa fa-plane'
- title={Utils.localizeMessage('emoji_picker.travel', 'Travel')}
- />}
+ icon={
+ <i
+ className='fa fa-plane'
+ title={Utils.localizeMessage('emoji_picker.travel', 'Travel')}
+ />
+ }
onCategoryClick={this.handleCategoryClick}
selected={this.state.category === 'travel'}
/>
<EmojiPickerCategory
category='objects'
- icon={<i
- className='fa fa-lightbulb-o'
- title={Utils.localizeMessage('emoji_picker.objects', 'Objects')}
- />}
+ icon={
+ <i
+ className='fa fa-lightbulb-o'
+ title={Utils.localizeMessage('emoji_picker.objects', 'Objects')}
+ />
+ }
onCategoryClick={this.handleCategoryClick}
selected={this.state.category === 'objects'}
/>
<EmojiPickerCategory
category='symbols'
- icon={<i
- className='fa fa-heart-o'
- title={Utils.localizeMessage('emoji_picker.symbols', 'Symbols')}
- />}
+ icon={
+ <i
+ className='fa fa-heart-o'
+ title={Utils.localizeMessage('emoji_picker.symbols', 'Symbols')}
+ />
+ }
onCategoryClick={this.handleCategoryClick}
selected={this.state.category === 'symbols'}
/>
<EmojiPickerCategory
category='flags'
- icon={<i
- className='fa fa-flag-o'
- title={Utils.localizeMessage('emoji_picker.flags', 'Flags')}
- />}
+ icon={
+ <i
+ className='fa fa-flag-o'
+ title={Utils.localizeMessage('emoji_picker.flags', 'Flags')}
+ />
+ }
onCategoryClick={this.handleCategoryClick}
selected={this.state.category === 'flags'}
/>
<EmojiPickerCategory
category='custom'
- icon={<i
- className='fa fa-at'
- title={Utils.localizeMessage('emoji_picker.custom', 'Custom')}
- />}
+ icon={
+ <i
+ className='fa fa-at'
+ title={Utils.localizeMessage('emoji_picker.custom', 'Custom')}
+ />
+ }
onCategoryClick={this.handleCategoryClick}
selected={this.state.category === 'custom'}
/>
diff --git a/webapp/components/emoji_picker/emoji_picker_overlay.jsx b/webapp/components/emoji_picker/emoji_picker_overlay.jsx
index 0a289a242..7174e004c 100644
--- a/webapp/components/emoji_picker/emoji_picker_overlay.jsx
+++ b/webapp/components/emoji_picker/emoji_picker_overlay.jsx
@@ -15,7 +15,15 @@ export default class EmojiPickerOverlay extends React.PureComponent {
onEmojiClick: PropTypes.func.isRequired,
onHide: PropTypes.func.isRequired,
rightOffset: PropTypes.number,
- topOffset: PropTypes.number
+ topOffset: PropTypes.number,
+ spaceRequiredAbove: PropTypes.number,
+ spaceRequiredBelow: PropTypes.number
+ }
+
+ // Reasonable defaults calculated from from the center channel
+ static defaultProps = {
+ spaceRequiredAbove: 422,
+ spaceRequiredBelow: 436
}
constructor(props) {
@@ -28,15 +36,12 @@ export default class EmojiPickerOverlay extends React.PureComponent {
componentWillUpdate(nextProps) {
if (nextProps.show && !this.props.show) {
- const spaceRequiredAbove = 422;
- const spaceRequiredBelow = 436;
-
const targetBounds = nextProps.target().getBoundingClientRect();
let placement;
- if (targetBounds.top > spaceRequiredAbove) {
+ if (targetBounds.top > nextProps.spaceRequiredAbove) {
placement = 'top';
- } else if (window.innerHeight - targetBounds.bottom > spaceRequiredBelow) {
+ } else if (window.innerHeight - targetBounds.bottom > nextProps.spaceRequiredBelow) {
placement = 'bottom';
} else {
placement = 'left';
diff --git a/webapp/components/integrations/components/edit_incoming_webhook.jsx b/webapp/components/integrations/components/edit_incoming_webhook.jsx
index 5a6309212..00cb50cbd 100644
--- a/webapp/components/integrations/components/edit_incoming_webhook.jsx
+++ b/webapp/components/integrations/components/edit_incoming_webhook.jsx
@@ -31,13 +31,11 @@ export default class EditIncomingWebhook extends AbstractIncomingWebhook {
handleIntegrationChange() {
const teamId = TeamStore.getCurrentId();
- this.setState({
- hooks: IntegrationStore.getIncomingWebhooks(teamId),
- loading: !IntegrationStore.hasReceivedIncomingWebhooks(teamId)
- });
+ const hooks = IntegrationStore.getIncomingWebhooks(teamId);
+ const loading = !IntegrationStore.hasReceivedIncomingWebhooks(teamId);
- if (!this.state.loading) {
- this.originalIncomingHook = this.state.hooks.filter((hook) => hook.id === this.props.location.query.id)[0];
+ if (!loading) {
+ this.originalIncomingHook = hooks.filter((hook) => hook.id === this.props.location.query.id)[0];
this.setState({
displayName: this.originalIncomingHook.display_name,
diff --git a/webapp/components/leave_team_modal.jsx b/webapp/components/leave_team_modal.jsx
index 379ece4c3..a38dd2da1 100644
--- a/webapp/components/leave_team_modal.jsx
+++ b/webapp/components/leave_team_modal.jsx
@@ -61,6 +61,7 @@ class LeaveTeamModal extends React.Component {
}
GlobalActions.emitLeaveTeam();
+ GlobalActions.toggleSideBarRightMenuAction();
}
handleHide() {
diff --git a/webapp/components/markdown_image.jsx b/webapp/components/markdown_image.jsx
index 75a6ce9ea..2634ef3f6 100644
--- a/webapp/components/markdown_image.jsx
+++ b/webapp/components/markdown_image.jsx
@@ -1,19 +1,67 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import React from 'react';
import {postListScrollChange} from 'actions/global_actions.jsx';
-export default class MarkdownImage extends PureComponent {
- handleLoad = () => {
- postListScrollChange();
+const WAIT_FOR_HEIGHT_TIMEOUT = 100;
+
+export default class MarkdownImage extends React.PureComponent {
+ static propTypes = {
+
+ /*
+ * The href of the image to be loaded
+ */
+ href: PropTypes.string
}
- render() {
- const props = {...this.props};
- props.onLoad = this.handleLoad;
+ constructor(props) {
+ super(props);
+
+ this.heightTimeout = 0;
+ }
+
+ componentDidMount() {
+ this.waitForHeight();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.href !== prevProps.href) {
+ this.waitForHeight();
+ }
+ }
- return <img {...props}/>;
+ componentWillUnmount() {
+ this.stopWaitingForHeight();
+ }
+
+ waitForHeight = () => {
+ if (this.refs.image.height) {
+ setTimeout(postListScrollChange, 0);
+
+ this.heightTimeout = 0;
+ } else {
+ this.heightTimeout = setTimeout(this.waitForHeight, WAIT_FOR_HEIGHT_TIMEOUT);
+ }
+ }
+
+ stopWaitingForHeight = () => {
+ if (this.heightTimeout !== 0) {
+ clearTimeout(this.heightTimeout);
+ this.heightTimeout = 0;
+ }
+ }
+
+ render() {
+ return (
+ <img
+ {...this.props}
+ ref='image'
+ onLoad={this.stopWaitingForHeight}
+ onError={this.stopWaitingForHeight}
+ />
+ );
}
}
diff --git a/webapp/components/member_list_channel/member_list_channel.jsx b/webapp/components/member_list_channel/member_list_channel.jsx
index f47f26cf6..272e210ce 100644
--- a/webapp/components/member_list_channel/member_list_channel.jsx
+++ b/webapp/components/member_list_channel/member_list_channel.jsx
@@ -146,7 +146,7 @@ export default class MemberListChannel extends React.Component {
for (let i = 0; i < users.length; i++) {
const user = users[i];
- if (teamMembers[user.id] && channelMembers[user.id]) {
+ if (teamMembers[user.id] && channelMembers[user.id] && user.delete_at === 0) {
usersToDisplay.push(user);
actionUserProps[user.id] = {
channel: this.props.channel,
diff --git a/webapp/components/more_direct_channels/more_direct_channels.jsx b/webapp/components/more_direct_channels/more_direct_channels.jsx
index 705c1ac95..0e50eca72 100644
--- a/webapp/components/more_direct_channels/more_direct_channels.jsx
+++ b/webapp/components/more_direct_channels/more_direct_channels.jsx
@@ -292,6 +292,11 @@ export default class MoreDirectChannels extends React.Component {
/>
);
+ let users = [];
+ if (this.state.users) {
+ users = this.state.users.filter((user) => user.delete_at === 0);
+ }
+
return (
<Modal
dialogClassName={'more-modal more-direct-channels'}
@@ -310,7 +315,7 @@ export default class MoreDirectChannels extends React.Component {
<Modal.Body>
<MultiSelect
key='moreDirectChannelsList'
- options={this.state.users}
+ options={users}
optionRenderer={this.renderOption}
values={this.state.values}
valueRenderer={this.renderValue}
diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx
index f61f58a8d..6305f870e 100644
--- a/webapp/components/navbar.jsx
+++ b/webapp/components/navbar.jsx
@@ -698,7 +698,7 @@ export default class Navbar extends React.Component {
/>
</span>
<span
- className='icon icon__menu'
+ className='icon icon__menu icon--sidebarHeaderTextColor'
dangerouslySetInnerHTML={{__html: menuIcon}}
aria-hidden='true'
/>
diff --git a/webapp/components/pdf_preview.jsx b/webapp/components/pdf_preview.jsx
index 790355561..09913afcf 100644
--- a/webapp/components/pdf_preview.jsx
+++ b/webapp/components/pdf_preview.jsx
@@ -84,7 +84,7 @@ export default class PDFPreview extends React.Component {
success: false
});
- PDFJS.getDocument(window.mm_config.SiteURL + props.fileUrl).then(this.onDocumentLoad);
+ PDFJS.getDocument(props.fileUrl).then(this.onDocumentLoad);
}
onDocumentLoad(pdf) {
diff --git a/webapp/components/post_view/post_body/index.js b/webapp/components/post_view/post_body/index.js
index 37cf114b0..90f04e0f9 100644
--- a/webapp/components/post_view/post_body/index.js
+++ b/webapp/components/post_view/post_body/index.js
@@ -16,7 +16,7 @@ function mapStateToProps(state, ownProps) {
let parentPostUser;
if (ownProps.post.root_id) {
parentPost = getPost(state, ownProps.post.root_id);
- parentPostUser = getUser(state, parentPost.user_id);
+ parentPostUser = parentPost ? getUser(state, parentPost.user_id) : null;
}
return {
diff --git a/webapp/components/post_view/post_body_additional_content.jsx b/webapp/components/post_view/post_body_additional_content.jsx
index 768b999d0..be9e37827 100644
--- a/webapp/components/post_view/post_body_additional_content.jsx
+++ b/webapp/components/post_view/post_body_additional_content.jsx
@@ -191,7 +191,7 @@ export default class PostBodyAdditionalContent extends React.PureComponent {
);
const contents = [message];
- if (this.state.linkLoaded) {
+ if (this.state.linkLoaded || this.props.previewCollapsed.startsWith('true')) {
if (prependToggle) {
contents.unshift(toggle);
} else {
diff --git a/webapp/components/post_view/post_info/post_info.jsx b/webapp/components/post_view/post_info/post_info.jsx
index d64d1aca6..6eaef0e0b 100644
--- a/webapp/components/post_view/post_info/post_info.jsx
+++ b/webapp/components/post_view/post_info/post_info.jsx
@@ -81,8 +81,7 @@ export default class PostInfo extends React.PureComponent {
this.state = {
showEmojiPicker: false,
- reactionPickerOffset: 21,
- canEdit: PostUtils.canEditPost(props.post, this.editDisableAction)
+ reactionPickerOffset: 21
};
}
diff --git a/webapp/components/post_view/post_list.jsx b/webapp/components/post_view/post_list.jsx
index 13cc28da3..d8a56fe83 100644
--- a/webapp/components/post_view/post_list.jsx
+++ b/webapp/components/post_view/post_list.jsx
@@ -104,12 +104,13 @@ export default class PostList extends React.PureComponent {
this.previousScrollTop = Number.MAX_SAFE_INTEGER;
this.previousScrollHeight = 0;
this.previousClientHeight = 0;
+ this.atBottom = false;
this.state = {
atEnd: false,
unViewedCount: 0,
isScrolling: false,
- lastViewed: Number.MAX_SAFE_INTEGER
+ lastViewed: props.lastViewedAt
};
}
@@ -144,24 +145,20 @@ export default class PostList extends React.PureComponent {
this.hasScrolled = false;
this.hasScrolledToFocusedPost = false;
this.hasScrolledToNewMessageSeparator = false;
- this.setState({atEnd: false});
+ this.atBottom = false;
+ this.setState({atEnd: false, lastViewed: nextProps.lastViewedAt});
if (nextChannel.id) {
this.loadPosts(nextChannel.id);
}
- return;
}
- if (!this.wasAtBottom() && this.props.posts !== nextProps.posts) {
- const unViewedCount = nextProps.posts.reduce((count, post) => {
- if (post.create_at > this.state.lastViewed &&
- post.user_id !== nextProps.currentUserId &&
- post.state !== Constants.POST_DELETED) {
- return count + 1;
- }
- return count;
- }, 0);
- this.setState({unViewedCount});
+ const nextPosts = nextProps.posts || [];
+ const posts = this.props.posts || [];
+ const hasNewPosts = (posts.length === 0 && nextPosts.length > 0) || (posts.length > 0 && nextPosts.length > 0 && posts[0].id !== nextPosts[0].id);
+
+ if (!this.checkBottom() && hasNewPosts) {
+ this.setUnreadsBelow(nextPosts, nextProps.currentUserId);
}
}
}
@@ -184,6 +181,10 @@ export default class PostList extends React.PureComponent {
const posts = this.props.posts;
const postList = this.refs.postlist;
+ if (!postList) {
+ return;
+ }
+
// Scroll to focused post on first load
const focusedPost = this.refs[this.props.focusedPostId];
if (focusedPost && this.props.posts) {
@@ -203,9 +204,13 @@ export default class PostList extends React.PureComponent {
if (messageSeparator && !this.hasScrolledToNewMessageSeparator) {
const element = ReactDOM.findDOMNode(messageSeparator);
element.scrollIntoView();
+ if (!this.checkBottom()) {
+ this.setUnreadsBelow(posts, this.props.currentUserId);
+ }
return;
} else if (postList && !this.hasScrolledToNewMessageSeparator) {
postList.scrollTop = postList.scrollHeight;
+ this.atBottom = true;
return;
}
@@ -218,7 +223,7 @@ export default class PostList extends React.PureComponent {
const pendingPostId = posts[0].pending_post_id;
if (postId !== prevPostId && pendingPostId !== prevPostId) {
// If already scrolled to bottom
- if (this.wasAtBottom()) {
+ if (this.atBottom) {
doScrollToBottom = true;
}
@@ -229,6 +234,7 @@ export default class PostList extends React.PureComponent {
}
if (doScrollToBottom) {
+ this.atBottom = true;
postList.scrollTop = postList.scrollHeight;
return;
}
@@ -240,26 +246,55 @@ export default class PostList extends React.PureComponent {
}
}
+ setUnreadsBelow = (posts, currentUserId) => {
+ const unViewedCount = posts.reduce((count, post) => {
+ if (post.create_at > this.state.lastViewed &&
+ post.user_id !== currentUserId &&
+ post.state !== Constants.POST_DELETED) {
+ return count + 1;
+ }
+ return count;
+ }, 0);
+ this.setState({unViewedCount});
+ }
+
handleScrollStop = () => {
this.setState({
isScrolling: false
});
}
- wasAtBottom = () => {
- return this.previousClientHeight + this.previousScrollTop >= this.previousScrollHeight - CLOSE_TO_BOTTOM_SCROLL_MARGIN;
+ checkBottom = () => {
+ if (!this.refs.postlist) {
+ return true;
+ }
+
+ // No scroll bar so we're at the bottom
+ if (this.refs.postlist.scrollHeight <= this.refs.postlist.clientHeight) {
+ return true;
+ }
+
+ return this.refs.postlist.clientHeight + this.refs.postlist.scrollTop >= this.refs.postlist.scrollHeight - CLOSE_TO_BOTTOM_SCROLL_MARGIN;
}
handleResize = (forceScrollToBottom) => {
const postList = this.refs.postlist;
- const doScrollToBottom = this.wasAtBottom() || forceScrollToBottom;
+ const messageSeparator = this.refs.newMessageSeparator;
+ const doScrollToBottom = this.atBottom || forceScrollToBottom;
- if (postList && doScrollToBottom) {
- postList.scrollTop = postList.scrollHeight;
+ if (postList) {
+ if (doScrollToBottom) {
+ postList.scrollTop = postList.scrollHeight;
+ } else if (!this.hasScrolled && messageSeparator) {
+ const element = ReactDOM.findDOMNode(messageSeparator);
+ element.scrollIntoView();
+ }
this.previousScrollHeight = postList.scrollHeight;
this.previousScrollTop = postList.scrollTop;
this.previousClientHeight = postList.clientHeight;
+
+ this.atBottom = this.checkBottom();
}
}
@@ -296,9 +331,18 @@ export default class PostList extends React.PureComponent {
}
handleScroll = () => {
- this.hasScrolled = true;
+ // Only count as user scroll if we've already performed our first load scroll
+ this.hasScrolled = this.hasScrolledToNewMessageSeparator || this.hasScrolledToFocusedPost;
+ if (!this.refs.postlist) {
+ return;
+ }
+
this.previousScrollTop = this.refs.postlist.scrollTop;
+ if (this.refs.postlist.scrollHeight === this.previousScrollHeight) {
+ this.atBottom = this.checkBottom();
+ }
+
this.updateFloatingTimestamp();
if (!this.state.isScrolling) {
@@ -307,7 +351,7 @@ export default class PostList extends React.PureComponent {
});
}
- if (this.wasAtBottom()) {
+ if (this.checkBottom()) {
this.setState({
lastViewed: new Date().getTime(),
unViewedCount: 0,
@@ -509,7 +553,7 @@ export default class PostList extends React.PureComponent {
/>
<ScrollToBottomArrows
isScrolling={this.state.isScrolling}
- atBottom={this.wasAtBottom()}
+ atBottom={this.atBottom}
onClick={this.scrollToBottom}
/>
<NewMessageIndicator
diff --git a/webapp/components/quick_switch_modal/quick_switch_modal.jsx b/webapp/components/quick_switch_modal/quick_switch_modal.jsx
index 2fbfdb2bd..736b728f0 100644
--- a/webapp/components/quick_switch_modal/quick_switch_modal.jsx
+++ b/webapp/components/quick_switch_modal/quick_switch_modal.jsx
@@ -305,7 +305,7 @@ export default class QuickSwitchModal extends React.PureComponent {
<Modal.Header closeButton={true}/>
<Modal.Body>
{header}
- <div className='modal__hint hidden-xs'>
+ <div className='modal__hint'>
{help}
</div>
<SuggestionBox
diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx
index 202be9748..568d85304 100644
--- a/webapp/components/rhs_comment.jsx
+++ b/webapp/components/rhs_comment.jsx
@@ -11,7 +11,6 @@ import FailedPostOptions from 'components/post_view/failed_post_options';
import DotMenu from 'components/dot_menu';
import EmojiPickerOverlay from 'components/emoji_picker/emoji_picker_overlay.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
import {addReaction} from 'actions/post_actions.jsx';
import TeamStore from 'stores/team_store.jsx';
@@ -27,6 +26,19 @@ import {Link} from 'react-router/es6';
import {FormattedMessage} from 'react-intl';
export default class RhsComment extends React.Component {
+ static propTypes = {
+ post: PropTypes.object,
+ lastPostCount: PropTypes.number,
+ user: PropTypes.object.isRequired,
+ currentUser: PropTypes.object.isRequired,
+ compactDisplay: PropTypes.bool,
+ useMilitaryTime: PropTypes.bool.isRequired,
+ isFlagged: PropTypes.bool,
+ status: PropTypes.string,
+ isBusy: PropTypes.bool,
+ removePost: PropTypes.func.isRequired
+ };
+
constructor(props) {
super(props);
@@ -56,7 +68,7 @@ export default class RhsComment extends React.Component {
}
removePost() {
- GlobalActions.emitRemovePost(this.props.post);
+ this.props.removePost(this.props.post);
}
createRemovePostButton() {
@@ -333,9 +345,10 @@ export default class RhsComment extends React.Component {
show={this.state.showEmojiPicker}
onHide={this.toggleEmojiPicker}
target={() => this.refs.dotMenu}
- container={this.props.getPostList}
onEmojiClick={this.reactEmojiClick}
rightOffset={15}
+ spaceRequiredAbove={342}
+ spaceRequiredBelow={342}
/>
<a
href='#'
@@ -436,16 +449,3 @@ export default class RhsComment extends React.Component {
);
}
}
-
-RhsComment.propTypes = {
- post: PropTypes.object,
- lastPostCount: PropTypes.number,
- user: PropTypes.object.isRequired,
- currentUser: PropTypes.object.isRequired,
- compactDisplay: PropTypes.bool,
- useMilitaryTime: PropTypes.bool.isRequired,
- isFlagged: PropTypes.bool,
- status: PropTypes.string,
- isBusy: PropTypes.bool,
- getPostList: PropTypes.func.isRequired
-};
diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx
index 352de0c67..fa72da167 100644
--- a/webapp/components/rhs_root_post.jsx
+++ b/webapp/components/rhs_root_post.jsx
@@ -38,8 +38,7 @@ export default class RhsRootPost extends React.Component {
isFlagged: PropTypes.bool,
status: PropTypes.string,
previewCollapsed: PropTypes.string,
- isBusy: PropTypes.bool,
- getPostList: PropTypes.func.isRequired
+ isBusy: PropTypes.bool
}
static defaultProps = {
@@ -226,9 +225,10 @@ export default class RhsRootPost extends React.Component {
show={this.state.showEmojiPicker}
onHide={this.toggleEmojiPicker}
target={() => this.refs.dotMenu}
- container={this.props.getPostList}
onEmojiClick={this.reactEmojiClick}
rightOffset={15}
+ spaceRequiredAbove={342}
+ spaceRequiredBelow={342}
/>
<a
href='#'
diff --git a/webapp/components/rhs_thread/index.js b/webapp/components/rhs_thread/index.js
index c4465cafd..ed7618427 100644
--- a/webapp/components/rhs_thread/index.js
+++ b/webapp/components/rhs_thread/index.js
@@ -2,7 +2,9 @@
// See License.txt for license information.
import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
import {getPost, makeGetPostsForThread} from 'mattermost-redux/selectors/entities/posts';
+import {removePost} from 'mattermost-redux/actions/posts';
import RhsThread from './rhs_thread.jsx';
@@ -24,4 +26,12 @@ function makeMapStateToProps() {
};
}
-export default connect(makeMapStateToProps)(RhsThread);
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ removePost
+ }, dispatch)
+ };
+}
+
+export default connect(makeMapStateToProps, mapDispatchToProps)(RhsThread);
diff --git a/webapp/components/rhs_thread/rhs_thread.jsx b/webapp/components/rhs_thread/rhs_thread.jsx
index bbf61af19..58325e8cc 100644
--- a/webapp/components/rhs_thread/rhs_thread.jsx
+++ b/webapp/components/rhs_thread/rhs_thread.jsx
@@ -59,7 +59,10 @@ export default class RhsThread extends React.Component {
currentUser: PropTypes.object.isRequired,
useMilitaryTime: PropTypes.bool.isRequired,
toggleSize: PropTypes.func,
- shrink: PropTypes.func
+ shrink: PropTypes.func,
+ actions: PropTypes.shape({
+ removePost: PropTypes.func.isRequired
+ }).isRequired
}
static defaultProps = {
@@ -316,10 +319,6 @@ export default class RhsThread extends React.Component {
});
}
- getPostListContainer = () => {
- return this.refs.postListContainer;
- }
-
getSidebarBody = () => {
return this.refs.sidebarbody;
}
@@ -400,7 +399,7 @@ export default class RhsThread extends React.Component {
isFlagged={isFlagged}
status={status}
isBusy={this.state.isBusy}
- getPostList={this.getPostListContainer}
+ removePost={this.props.actions.removePost}
/>
</div>
);
@@ -435,10 +434,7 @@ export default class RhsThread extends React.Component {
renderView={renderView}
onScroll={this.handleScroll}
>
- <div
- ref='postListContainer'
- className='post-right__scroll'
- >
+ <div className='post-right__scroll'>
<DateSeparator
date={rootPostDay}
/>
@@ -454,7 +450,6 @@ export default class RhsThread extends React.Component {
status={rootStatus}
previewCollapsed={this.state.previewsCollapsed}
isBusy={this.state.isBusy}
- getPostList={this.getPostListContainer}
/>
<div
ref='rhspostlist'
diff --git a/webapp/components/search_results_item.jsx b/webapp/components/search_results_item.jsx
index c0f405eb4..3b632ee5e 100644
--- a/webapp/components/search_results_item.jsx
+++ b/webapp/components/search_results_item.jsx
@@ -21,7 +21,6 @@ import * as PostUtils from 'utils/post_utils.jsx';
import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
-import {Posts} from 'mattermost-redux/constants';
import React from 'react';
import PropTypes from 'prop-types';
@@ -187,7 +186,7 @@ export default class SearchResultsItem extends React.Component {
let message;
let flagContent;
let rhsControls;
- if (post.state === Posts.POST_DELETED) {
+ if (post.state === Constants.POST_DELETED) {
message = (
<p>
<FormattedMessage
diff --git a/webapp/components/sidebar_right/sidebar_right.jsx b/webapp/components/sidebar_right/sidebar_right.jsx
index 21d3df345..737254682 100644
--- a/webapp/components/sidebar_right/sidebar_right.jsx
+++ b/webapp/components/sidebar_right/sidebar_right.jsx
@@ -77,12 +77,12 @@ export default class SidebarRight extends React.Component {
}
shouldComponentUpdate(nextProps, nextState) {
- return !Utils.areObjectsEqual(nextState, this.state) || !Utils.areObjectsEqual(nextProps, this.props);
+ return !Utils.areObjectsEqual(nextState, this.state) || this.props.postRightVisible !== nextProps.postRightVisible;
}
componentWillUpdate(nextProps, nextState) {
const isOpen = this.state.searchVisible || this.props.postRightVisible;
- const willOpen = nextState.searchVisible || nextState.postRightVisible;
+ const willOpen = nextState.searchVisible || nextProps.postRightVisible;
if (!isOpen && willOpen) {
trackEvent('ui', 'ui_rhs_opened');
diff --git a/webapp/components/sidebar_right_menu.jsx b/webapp/components/sidebar_right_menu.jsx
index d40e90279..f0e6f10a1 100644
--- a/webapp/components/sidebar_right_menu.jsx
+++ b/webapp/components/sidebar_right_menu.jsx
@@ -1,7 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import TeamMembersModal from './team_members_modal.jsx';
import ToggleModalButton from './toggle_modal_button.jsx';
import AboutBuildModal from './about_build_modal.jsx';
@@ -19,7 +18,6 @@ import * as UserAgent from 'utils/user_agent.jsx';
import * as Utils from 'utils/utils.jsx';
import {Constants, WebrtcActionTypes} from 'utils/constants.jsx';
-const ActionTypes = Constants.ActionTypes;
const Preferences = Constants.Preferences;
const TutorialSteps = Constants.TutorialSteps;
@@ -143,20 +141,7 @@ export default class SidebarRightMenu extends React.Component {
hideSidebars() {
if (Utils.isMobile()) {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_SEARCH,
- results: null
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_POST_SELECTED,
- postId: null
- });
-
- document.querySelector('.app__body .inner-wrap').classList.remove('move--right', 'move--left', 'move--left-small');
- document.querySelector('.app__body .sidebar--left').classList.remove('move--right');
- document.querySelector('.app__body .sidebar--right').classList.remove('move--left');
- document.querySelector('.app__body .sidebar--menu').classList.remove('move--left');
+ GlobalActions.toggleSideBarRightMenuAction();
}
}
diff --git a/webapp/components/spinner_button.jsx b/webapp/components/spinner_button.jsx
index 78079b2b4..b3b291ff8 100644
--- a/webapp/components/spinner_button.jsx
+++ b/webapp/components/spinner_button.jsx
@@ -16,6 +16,12 @@ export default class SpinnerButton extends React.Component {
};
}
+ static get defaultProps() {
+ return {
+ spinning: false
+ };
+ }
+
render() {
const {spinning, children, ...props} = this.props; // eslint-disable-line no-use-before-define
diff --git a/webapp/components/team_import_tab.jsx b/webapp/components/team_import_tab.jsx
index a17442dc9..b310cdc12 100644
--- a/webapp/components/team_import_tab.jsx
+++ b/webapp/components/team_import_tab.jsx
@@ -29,12 +29,12 @@ class TeamImportTab extends React.Component {
};
}
- onImportFailure(e, err, res) {
- this.setState({status: 'fail', link: 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(res.text)});
+ onImportFailure() {
+ this.setState({status: 'fail'});
}
- onImportSuccess(data, res) {
- this.setState({status: 'done', link: 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(res.text)});
+ onImportSuccess(data) {
+ this.setState({status: 'done', link: 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(atob(data.results))});
}
doImportSlack(file) {
diff --git a/webapp/components/user_profile.jsx b/webapp/components/user_profile.jsx
index 22d6b6b77..1cd2ef637 100644
--- a/webapp/components/user_profile.jsx
+++ b/webapp/components/user_profile.jsx
@@ -56,10 +56,6 @@ export default class UserProfile extends React.Component {
render() {
let name = '...';
let profileImg = '';
- let popoverPosition = 'right';
- if (Utils.isMobile()) {
- popoverPosition = 'bottom';
- }
if (this.props.user) {
name = Utils.displayUsername(this.props.user.id);
@@ -78,7 +74,7 @@ export default class UserProfile extends React.Component {
<OverlayTrigger
ref='overlay'
trigger='click'
- placement={popoverPosition}
+ placement='right'
rootClose={true}
overlay={
<ProfilePopover