Merge branch 'release-4.0'
Этот коммит содержится в:
@@ -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}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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'}
|
||||
/>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -61,6 +61,7 @@ class LeaveTeamModal extends React.Component {
|
||||
}
|
||||
|
||||
GlobalActions.emitLeaveTeam();
|
||||
GlobalActions.toggleSideBarRightMenuAction();
|
||||
}
|
||||
|
||||
handleHide() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.heightTimeout = 0;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.waitForHeight();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.href !== prevProps.href) {
|
||||
this.waitForHeight();
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
const props = {...this.props};
|
||||
props.onLoad = this.handleLoad;
|
||||
|
||||
return <img {...props}/>;
|
||||
return (
|
||||
<img
|
||||
{...this.props}
|
||||
ref='image'
|
||||
onLoad={this.stopWaitingForHeight}
|
||||
onError={this.stopWaitingForHeight}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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'
|
||||
/>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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='#'
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Ссылка в новой задаче
Block a user