General react performance improvements (#2796)
* General React performance improvements * Cleaned up unused props/state in PermaLinkView and PostFocusView
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
9961ccca7d
Коммит
1f4974dc02
@@ -9,6 +9,17 @@ import {Modal} from 'react-bootstrap';
|
||||
import React from 'react';
|
||||
|
||||
export default class ChannelInfoModal extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (nextProps.show !== this.props.show) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Utils.areObjectsEqual(nextProps.channel, this.props.channel)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
render() {
|
||||
let channel = this.props.channel;
|
||||
if (!channel) {
|
||||
@@ -93,4 +104,4 @@ ChannelInfoModal.propTypes = {
|
||||
show: React.PropTypes.bool.isRequired,
|
||||
onHide: React.PropTypes.func.isRequired,
|
||||
channel: React.PropTypes.object.isRequired
|
||||
};
|
||||
};
|
||||
|
||||
@@ -11,6 +11,8 @@ import PostsViewContainer from 'components/posts_view_container.jsx';
|
||||
|
||||
import ChannelStore from 'stores/channel_store.jsx';
|
||||
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
|
||||
export default class ChannelView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -47,6 +49,17 @@ export default class ChannelView extends React.Component {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState(this.getStateFromStores(nextProps));
|
||||
}
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (!Utils.areObjectsEqual(nextProps.params, this.props.params)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextState.channelId !== this.state.channelId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
@@ -57,7 +70,7 @@ export default class ChannelView extends React.Component {
|
||||
<ChannelHeader
|
||||
channelId={this.state.channelId}
|
||||
/>
|
||||
<PostsViewContainer profiles={this.props.profiles}/>
|
||||
<PostsViewContainer/>
|
||||
<div
|
||||
className='post-create__container'
|
||||
id='post-create'
|
||||
@@ -72,6 +85,5 @@ ChannelView.defaultProps = {
|
||||
};
|
||||
|
||||
ChannelView.propTypes = {
|
||||
params: React.PropTypes.object.isRequired,
|
||||
profiles: React.PropTypes.object
|
||||
params: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
54
webapp/components/floating_timestamp.jsx
Обычный файл
54
webapp/components/floating_timestamp.jsx
Обычный файл
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {FormattedDate} from 'react-intl';
|
||||
|
||||
import React from 'react';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
export default class FloatingTimestamp extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.isMobile) {
|
||||
return <noscript/>;
|
||||
}
|
||||
|
||||
if (this.props.createAt === 0) {
|
||||
return <noscript/>;
|
||||
}
|
||||
|
||||
const dateString = (
|
||||
<FormattedDate
|
||||
value={this.props.createAt}
|
||||
weekday='short'
|
||||
day='2-digit'
|
||||
month='short'
|
||||
year='numeric'
|
||||
/>
|
||||
);
|
||||
|
||||
let className = 'post-list__timestamp';
|
||||
if (this.props.isScrolling) {
|
||||
className += ' scrolling';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div>
|
||||
<span>{dateString}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FloatingTimestamp.propTypes = {
|
||||
isScrolling: React.PropTypes.bool.isRequired,
|
||||
isMobile: React.PropTypes.bool,
|
||||
createAt: React.PropTypes.number
|
||||
};
|
||||
@@ -1,34 +1,25 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import Constants from 'utils/constants.jsx';
|
||||
import GetLinkModal from './get_link_modal.jsx';
|
||||
import ModalStore from 'stores/modal_store.jsx';
|
||||
import TeamStore from 'stores/team_store.jsx';
|
||||
|
||||
import {intlShape, injectIntl, defineMessages} from 'react-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
title: {
|
||||
id: 'get_post_link_modal.title',
|
||||
defaultMessage: 'Copy Permalink'
|
||||
},
|
||||
help: {
|
||||
id: 'get_post_link_modal.help',
|
||||
defaultMessage: 'The link below allows authorized users to see your post.'
|
||||
}
|
||||
});
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
|
||||
import React from 'react';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
class GetPostLinkModal extends React.Component {
|
||||
export default class GetPostLinkModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleToggle = this.handleToggle.bind(this);
|
||||
|
||||
this.hide = this.hide.bind(this);
|
||||
|
||||
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
|
||||
|
||||
this.state = {
|
||||
show: false,
|
||||
post: {}
|
||||
@@ -57,22 +48,14 @@ class GetPostLinkModal extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
|
||||
return (
|
||||
<GetLinkModal
|
||||
show={this.state.show}
|
||||
onHide={this.hide}
|
||||
title={formatMessage(holders.title)}
|
||||
helpText={formatMessage(holders.help)}
|
||||
title={Utils.localizeMessage('get_post_link_modal.title', 'Copy Permalink')}
|
||||
helpText={Utils.localizeMessage('get_post_link_modal.help', 'The link below allows authorized users to see your post.')}
|
||||
link={TeamStore.getCurrentTeamUrl() + '/pl/' + this.state.post.id}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
GetPostLinkModal.propTypes = {
|
||||
intl: intlShape.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(GetPostLinkModal);
|
||||
|
||||
@@ -1,36 +1,24 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import Constants from 'utils/constants.jsx';
|
||||
import GetLinkModal from './get_link_modal.jsx';
|
||||
import ModalStore from 'stores/modal_store.jsx';
|
||||
import TeamStore from 'stores/team_store.jsx';
|
||||
|
||||
import {intlShape, injectIntl, defineMessages} from 'react-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
title: {
|
||||
id: 'get_team_invite_link_modal.title',
|
||||
defaultMessage: 'Team Invite Link'
|
||||
},
|
||||
help: {
|
||||
id: 'get_team_invite_link_modal.help',
|
||||
defaultMessage: 'Send teammates the link below for them to sign-up to this team site. The Team Invite Link can be shared with multiple teammates as it does not change unless it\'s regenerated in Team Settings by a Team Admin.'
|
||||
},
|
||||
helpDisabled: {
|
||||
id: 'get_team_invite_link_modal.helpDisabled',
|
||||
defaultMessage: 'User creation has been disabled for your team. Please ask your team administrator for details.'
|
||||
}
|
||||
});
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
|
||||
import React from 'react';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
class GetTeamInviteLinkModal extends React.Component {
|
||||
export default class GetTeamInviteLinkModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleToggle = this.handleToggle.bind(this);
|
||||
|
||||
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
|
||||
|
||||
this.state = {
|
||||
show: false
|
||||
};
|
||||
@@ -51,28 +39,21 @@ class GetTeamInviteLinkModal extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
|
||||
let helpText = formatMessage(holders.helpDisabled);
|
||||
|
||||
let helpText;
|
||||
if (global.window.mm_config.EnableUserCreation === 'true') {
|
||||
helpText = formatMessage(holders.help);
|
||||
helpText = Utils.localizeMessage('get_team_invite_link_modal.help', 'Send teammates the link below for them to sign-up to this team site. The Team Invite Link can be shared with multiple teammates as it does not change unless it\'s regenerated in Team Settings by a Team Admin.');
|
||||
} else {
|
||||
helpText = Utils.localizeMessage('get_team_invite_link_modal.helpDisabled', 'User creation has been disabled for your team. Please ask your team administrator for details.');
|
||||
}
|
||||
|
||||
return (
|
||||
<GetLinkModal
|
||||
show={this.state.show}
|
||||
onHide={() => this.setState({show: false})}
|
||||
title={formatMessage(holders.title)}
|
||||
title={Utils.localizeMessage('get_team_invite_link_modal.title', 'Team Invite Link')}
|
||||
helpText={helpText}
|
||||
link={TeamStore.getCurrentInviteLink()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
GetTeamInviteLinkModal.propTypes = {
|
||||
intl: intlShape.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(GetTeamInviteLinkModal);
|
||||
|
||||
@@ -2,17 +2,22 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import $ from 'jquery';
|
||||
import ReactDOM from 'react-dom';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import * as AsyncClient from 'utils/async_client.jsx';
|
||||
import * as GlobalActions from 'action_creators/global_actions.jsx';
|
||||
import ChannelStore from 'stores/channel_store.jsx';
|
||||
import LoadingScreen from './loading_screen.jsx';
|
||||
import NewChannelFlow from './new_channel_flow.jsx';
|
||||
|
||||
import ChannelStore from 'stores/channel_store.jsx';
|
||||
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import * as AsyncClient from 'utils/async_client.jsx';
|
||||
import * as GlobalActions from 'action_creators/global_actions.jsx';
|
||||
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {browserHistory} from 'react-router';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
import loadingGif from 'images/load.gif';
|
||||
|
||||
function getStateFromStores() {
|
||||
@@ -22,8 +27,6 @@ function getStateFromStores() {
|
||||
};
|
||||
}
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default class MoreChannels extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -33,6 +36,8 @@ export default class MoreChannels extends React.Component {
|
||||
this.handleNewChannel = this.handleNewChannel.bind(this);
|
||||
this.createChannelRow = this.createChannelRow.bind(this);
|
||||
|
||||
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
|
||||
|
||||
var initState = getStateFromStores();
|
||||
initState.channelType = '';
|
||||
initState.joiningChannel = -1;
|
||||
|
||||
@@ -19,7 +19,6 @@ export default class MoreDirectChannels extends React.Component {
|
||||
this.handleHide = this.handleHide.bind(this);
|
||||
this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
|
||||
this.handleUserChange = this.handleUserChange.bind(this);
|
||||
|
||||
this.createJoinDirectChannelButton = this.createJoinDirectChannelButton.bind(this);
|
||||
|
||||
this.state = {
|
||||
@@ -52,6 +51,26 @@ export default class MoreDirectChannels extends React.Component {
|
||||
UserStore.removeChangeListener(this.handleUserChange);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (nextProps.show !== this.props.show) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextProps.onModalDismissed.toString() !== this.props.onModalDismissed.toString()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextState.loadingDMChannel !== this.state.loadingDMChannel) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Utils.areObjectsEqual(nextState.users, this.state.users)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
handleHide() {
|
||||
if (this.props.onModalDismissed) {
|
||||
this.props.onModalDismissed();
|
||||
|
||||
@@ -42,20 +42,18 @@ export default class NeedsTeam extends React.Component {
|
||||
this.onChanged = this.onChanged.bind(this);
|
||||
|
||||
this.state = {
|
||||
profiles: UserStore.getProfiles(),
|
||||
team: TeamStore.getCurrent()
|
||||
};
|
||||
}
|
||||
|
||||
onChanged() {
|
||||
this.setState({
|
||||
profiles: UserStore.getProfiles(),
|
||||
team: TeamStore.getCurrent()
|
||||
});
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// Go to tutorial if we are first arrivign
|
||||
// Go to tutorial if we are first arriving
|
||||
const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
|
||||
if (tutorialStep <= TutorialSteps.INTRO_SCREENS) {
|
||||
browserHistory.push(Utils.getTeamURLNoOriginFromAddressBar() + '/tutorial');
|
||||
@@ -63,7 +61,6 @@ export default class NeedsTeam extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
UserStore.addChangeListener(this.onChanged);
|
||||
TeamStore.addChangeListener(this.onChanged);
|
||||
|
||||
// Emit view action
|
||||
@@ -84,7 +81,6 @@ export default class NeedsTeam extends React.Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
UserStore.removeChangeListener(this.onChanged);
|
||||
TeamStore.removeChangeListener(this.onChanged);
|
||||
$(window).off('focus');
|
||||
$(window).off('blur');
|
||||
@@ -114,7 +110,6 @@ export default class NeedsTeam extends React.Component {
|
||||
<div className='row main'>
|
||||
{React.cloneElement(this.props.center, {
|
||||
user: this.props.user,
|
||||
profiles: this.state.profiles,
|
||||
team: this.state.team
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -70,7 +70,7 @@ export default class PermalinkView extends React.Component {
|
||||
<ChannelHeader
|
||||
channelId={this.state.channelId}
|
||||
/>
|
||||
<PostFocusView profiles={this.props.profiles}/>
|
||||
<PostFocusView/>
|
||||
<div
|
||||
id='archive-link-home'
|
||||
>
|
||||
@@ -89,10 +89,6 @@ export default class PermalinkView extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
PermalinkView.defaultProps = {
|
||||
};
|
||||
|
||||
PermalinkView.propTypes = {
|
||||
params: React.PropTypes.object.isRequired,
|
||||
profiles: React.PropTypes.object
|
||||
params: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
@@ -10,24 +10,13 @@ import * as TextFormatting from 'utils/text_formatting.jsx';
|
||||
import twemoji from 'twemoji';
|
||||
import PostBodyAdditionalContent from './post_body_additional_content.jsx';
|
||||
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import loadingGif from 'images/load.gif';
|
||||
|
||||
const holders = defineMessages({
|
||||
plusOne: {
|
||||
id: 'post_body.plusOne',
|
||||
defaultMessage: ' plus 1 other file'
|
||||
},
|
||||
plusMore: {
|
||||
id: 'post_body.plusMore',
|
||||
defaultMessage: ' plus {count} other files'
|
||||
}
|
||||
});
|
||||
|
||||
import React from 'react';
|
||||
|
||||
class PostBody extends React.Component {
|
||||
export default class PostBody extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -61,8 +50,27 @@ class PostBody extends React.Component {
|
||||
this.parseEmojis();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Utils.areObjectsEqual(nextProps.parentPost, this.props.parentPost)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextProps.retryPost.toString() !== this.props.retryPost.toString()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextProps.handleCommentClick.toString() !== this.props.handleCommentClick.toString()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
const post = this.props.post;
|
||||
const filenames = this.props.post.filenames;
|
||||
const parentPost = this.props.parentPost;
|
||||
@@ -106,9 +114,9 @@ class PostBody extends React.Component {
|
||||
message = parentPost.filenames[0].split('/').pop();
|
||||
|
||||
if (parentPost.filenames.length === 2) {
|
||||
message += formatMessage(holders.plusOne);
|
||||
message += Utils.localizeMessage('post_body.plusOne', ' plus 1 other file');
|
||||
} else if (parentPost.filenames.length > 2) {
|
||||
message += formatMessage(holders.plusMore, {count: (parentPost.filenames.length - 1)});
|
||||
message += Utils.localizeMessage('post_body.plusMore', ' plus {count} other files').replace('{count}', (parentPost.filenames.length - 1).toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +127,8 @@ class PostBody extends React.Component {
|
||||
id='post_body.commentedOn'
|
||||
defaultMessage='Commented on {name}{apostrophe} message: '
|
||||
values={{
|
||||
name: (name),
|
||||
apostrophe: apostrophe
|
||||
name,
|
||||
apostrophe
|
||||
}}
|
||||
/>
|
||||
<a
|
||||
@@ -213,11 +221,8 @@ class PostBody extends React.Component {
|
||||
}
|
||||
|
||||
PostBody.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
post: React.PropTypes.object.isRequired,
|
||||
parentPost: React.PropTypes.object,
|
||||
retryPost: React.PropTypes.func.isRequired,
|
||||
handleCommentClick: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(PostBody);
|
||||
|
||||
@@ -5,7 +5,6 @@ import PostsView from './posts_view.jsx';
|
||||
|
||||
import PostStore from 'stores/post_store.jsx';
|
||||
import ChannelStore from 'stores/channel_store.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
import * as GlobalActions from 'action_creators/global_actions.jsx';
|
||||
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
@@ -18,7 +17,6 @@ export default class PostFocusView extends React.Component {
|
||||
|
||||
this.onChannelChange = this.onChannelChange.bind(this);
|
||||
this.onPostsChange = this.onPostsChange.bind(this);
|
||||
this.onUserChange = this.onUserChange.bind(this);
|
||||
this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
|
||||
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
|
||||
this.loadMorePostsBottom = this.loadMorePostsBottom.bind(this);
|
||||
@@ -31,21 +29,18 @@ export default class PostFocusView extends React.Component {
|
||||
scrollPostId: focusedPostId,
|
||||
postList: PostStore.getVisiblePosts(focusedPostId),
|
||||
atTop: PostStore.getVisibilityAtTop(focusedPostId),
|
||||
atBottom: PostStore.getVisibilityAtBottom(focusedPostId),
|
||||
currentUser: UserStore.getCurrentUser()
|
||||
atBottom: PostStore.getVisibilityAtBottom(focusedPostId)
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
ChannelStore.addChangeListener(this.onChannelChange);
|
||||
PostStore.addChangeListener(this.onPostsChange);
|
||||
UserStore.addChangeListener(this.onUserChange);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
ChannelStore.removeChangeListener(this.onChannelChange);
|
||||
PostStore.removeChangeListener(this.onPostsChange);
|
||||
UserStore.removeChangeListener(this.onUserChange);
|
||||
}
|
||||
|
||||
onChannelChange() {
|
||||
@@ -58,10 +53,6 @@ export default class PostFocusView extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onUserChange() {
|
||||
this.setState({currentUser: UserStore.getCurrentUser()});
|
||||
}
|
||||
|
||||
onPostsChange() {
|
||||
const focusedPostId = PostStore.getFocusedPostId();
|
||||
if (focusedPostId == null) {
|
||||
@@ -105,7 +96,7 @@ export default class PostFocusView extends React.Component {
|
||||
const postsToHighlight = {};
|
||||
postsToHighlight[this.state.scrollPostId] = true;
|
||||
|
||||
if (!this.state.currentUser || !this.state.postList) {
|
||||
if (!this.state.postList) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -125,16 +116,8 @@ export default class PostFocusView extends React.Component {
|
||||
introText={this.getIntroMessage()}
|
||||
messageSeparatorTime={0}
|
||||
postsToHighlight={postsToHighlight}
|
||||
profiles={this.props.profiles}
|
||||
currentUser={this.state.currentUser}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
PostFocusView.defaultProps = {
|
||||
};
|
||||
|
||||
PostFocusView.propTypes = {
|
||||
profiles: React.PropTypes.object
|
||||
};
|
||||
|
||||
@@ -2,19 +2,25 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import $ from 'jquery';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PreferenceStore from 'stores/preference_store.jsx';
|
||||
import * as GlobalActions from 'action_creators/global_actions.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
|
||||
import Post from './post.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
import FloatingTimestamp from './floating_timestamp.jsx';
|
||||
|
||||
import * as GlobalActions from 'action_creators/global_actions.jsx';
|
||||
|
||||
import PreferenceStore from 'stores/preference_store.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import DelayedAction from 'utils/delayed_action.jsx';
|
||||
|
||||
import Constants from 'utils/constants.jsx';
|
||||
const Preferences = Constants.Preferences;
|
||||
|
||||
import {FormattedDate, FormattedMessage} from 'react-intl';
|
||||
|
||||
const Preferences = Constants.Preferences;
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
export default class PostsView extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -31,6 +37,7 @@ export default class PostsView extends React.Component {
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
this.scrollToBottom = this.scrollToBottom.bind(this);
|
||||
this.scrollToBottomAnimated = this.scrollToBottomAnimated.bind(this);
|
||||
this.onUserChange = this.onUserChange.bind(this);
|
||||
|
||||
this.jumpToPostNode = null;
|
||||
this.wasAtBottom = true;
|
||||
@@ -43,7 +50,9 @@ export default class PostsView extends React.Component {
|
||||
centerPosts: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
|
||||
isScrolling: false,
|
||||
topPostId: null,
|
||||
showUnreadMessageAlert: false
|
||||
showUnreadMessageAlert: false,
|
||||
currentUser: UserStore.getCurrentUser(),
|
||||
profiles: UserStore.getProfiles()
|
||||
};
|
||||
}
|
||||
static get SCROLL_TYPE_FREE() {
|
||||
@@ -67,6 +76,9 @@ export default class PostsView extends React.Component {
|
||||
centerPosts: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED
|
||||
});
|
||||
}
|
||||
onUserChange() {
|
||||
this.setState({currentUser: UserStore.getCurrentUser(), profiles: UserStore.getProfiles()});
|
||||
}
|
||||
isAtBottom() {
|
||||
// consider the view to be at the bottom if it's within this many pixels of the bottom
|
||||
const atBottomMargin = 10;
|
||||
@@ -152,8 +164,8 @@ export default class PostsView extends React.Component {
|
||||
createPosts(posts, order) {
|
||||
const postCtls = [];
|
||||
let previousPostDay = new Date(0);
|
||||
const userId = this.props.currentUser.id;
|
||||
const profiles = this.props.profiles || {};
|
||||
const userId = this.state.currentUser.id;
|
||||
const profiles = this.state.profiles || {};
|
||||
|
||||
let renderedLastViewed = false;
|
||||
|
||||
@@ -229,8 +241,8 @@ export default class PostsView extends React.Component {
|
||||
const shouldHighlight = this.props.postsToHighlight && this.props.postsToHighlight.hasOwnProperty(post.id);
|
||||
|
||||
let profile;
|
||||
if (this.props.currentUser.id === post.user_id) {
|
||||
profile = this.props.currentUser;
|
||||
if (userId === post.user_id) {
|
||||
profile = this.state.currentUser;
|
||||
} else {
|
||||
profile = profiles[post.user_id];
|
||||
}
|
||||
@@ -250,7 +262,7 @@ export default class PostsView extends React.Component {
|
||||
onClick={() => GlobalActions.emitPostFocusEvent(post.id)} //eslint-disable-line no-loop-func
|
||||
displayNameType={this.state.displayNameType}
|
||||
user={profile}
|
||||
currentUser={this.props.currentUser}
|
||||
currentUser={this.state.currentUser}
|
||||
center={this.state.centerPosts}
|
||||
/>
|
||||
);
|
||||
@@ -377,13 +389,19 @@ export default class PostsView extends React.Component {
|
||||
if (this.props.postList != null) {
|
||||
this.updateScrolling();
|
||||
}
|
||||
|
||||
if (this.props.isActive) {
|
||||
PreferenceStore.addChangeListener(this.updateState);
|
||||
UserStore.addChangeListener(this.onUserChange);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
PreferenceStore.addChangeListener(this.updateState);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
this.scrollStopAction.cancel();
|
||||
PreferenceStore.removeChangeListener(this.updateState);
|
||||
UserStore.removeChangeListener(this.onUserChange);
|
||||
}
|
||||
componentDidUpdate() {
|
||||
if (this.props.postList != null) {
|
||||
@@ -401,8 +419,10 @@ export default class PostsView extends React.Component {
|
||||
if (!this.props.isActive && nextProps.isActive) {
|
||||
this.updateState();
|
||||
PreferenceStore.addChangeListener(this.updateState);
|
||||
UserStore.addChangeListener(this.onUserChange);
|
||||
} else if (this.props.isActive && !nextProps.isActive) {
|
||||
PreferenceStore.removeChangeListener(this.updateState);
|
||||
UserStore.removeChangeListener(this.onUserChange);
|
||||
}
|
||||
}
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
@@ -436,7 +456,7 @@ export default class PostsView extends React.Component {
|
||||
if (this.state.centerPosts !== nextState.centerPosts) {
|
||||
return true;
|
||||
}
|
||||
if (!Utils.areObjectsEqual(this.props.profiles, nextProps.profiles)) {
|
||||
if (!Utils.areObjectsEqual(this.state.profiles, nextState.profiles)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -497,16 +517,17 @@ export default class PostsView extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
let topPost = null;
|
||||
let topPostCreateAt = 0;
|
||||
if (this.state.topPostId) {
|
||||
topPost = this.props.postList.posts[this.state.topPostId];
|
||||
topPostCreateAt = this.props.postList.posts[this.state.topPostId].create_at;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={activeClass}>
|
||||
<FloatingTimestamp
|
||||
isScrolling={this.state.isScrolling}
|
||||
post={topPost}
|
||||
isMobile={$(window).width() > 768}
|
||||
createAt={topPostCreateAt}
|
||||
/>
|
||||
<ScrollToBottomArrows
|
||||
isScrolling={this.state.isScrolling}
|
||||
@@ -551,7 +572,6 @@ PostsView.defaultProps = {
|
||||
PostsView.propTypes = {
|
||||
isActive: React.PropTypes.bool,
|
||||
postList: React.PropTypes.object,
|
||||
profiles: React.PropTypes.object.isRequired,
|
||||
scrollPostId: React.PropTypes.string,
|
||||
scrollType: React.PropTypes.number,
|
||||
postViewScrolled: React.PropTypes.func.isRequired,
|
||||
@@ -561,47 +581,7 @@ PostsView.propTypes = {
|
||||
showMoreMessagesBottom: React.PropTypes.bool,
|
||||
introText: React.PropTypes.element,
|
||||
messageSeparatorTime: React.PropTypes.number,
|
||||
postsToHighlight: React.PropTypes.object,
|
||||
currentUser: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
function FloatingTimestamp({isScrolling, post}) {
|
||||
// only show on mobile
|
||||
if ($(window).width() > 768) {
|
||||
return <noscript/>;
|
||||
}
|
||||
|
||||
if (!post) {
|
||||
return <noscript/>;
|
||||
}
|
||||
|
||||
const dateString = (
|
||||
<FormattedDate
|
||||
value={post.create_at}
|
||||
weekday='short'
|
||||
day='2-digit'
|
||||
month='short'
|
||||
year='numeric'
|
||||
/>
|
||||
);
|
||||
|
||||
let className = 'post-list__timestamp';
|
||||
if (isScrolling) {
|
||||
className += ' scrolling';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div>
|
||||
<span>{dateString}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
FloatingTimestamp.propTypes = {
|
||||
isScrolling: React.PropTypes.bool.isRequired,
|
||||
post: React.PropTypes.object
|
||||
postsToHighlight: React.PropTypes.object
|
||||
};
|
||||
|
||||
function ScrollToBottomArrows({isScrolling, atBottom, onClick}) {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
import PostsView from './posts_view.jsx';
|
||||
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 GlobalActions from 'action_creators/global_actions.jsx';
|
||||
|
||||
@@ -25,7 +26,6 @@ export default class PostsViewContainer extends React.Component {
|
||||
this.onChannelChange = this.onChannelChange.bind(this);
|
||||
this.onChannelLeave = this.onChannelLeave.bind(this);
|
||||
this.onPostsChange = this.onPostsChange.bind(this);
|
||||
this.onUserChange = this.onUserChange.bind(this);
|
||||
this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
|
||||
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
|
||||
this.handlePostsViewJumpRequest = this.handlePostsViewJumpRequest.bind(this);
|
||||
@@ -33,8 +33,7 @@ export default class PostsViewContainer extends React.Component {
|
||||
const currentChannelId = ChannelStore.getCurrentId();
|
||||
const state = {
|
||||
scrollType: PostsView.SCROLL_TYPE_BOTTOM,
|
||||
scrollPost: null,
|
||||
currentUser: UserStore.getCurrentUser()
|
||||
scrollPost: null
|
||||
};
|
||||
if (currentChannelId) {
|
||||
Object.assign(state, {
|
||||
@@ -60,17 +59,14 @@ export default class PostsViewContainer extends React.Component {
|
||||
ChannelStore.addLeaveListener(this.onChannelLeave);
|
||||
PostStore.addChangeListener(this.onPostsChange);
|
||||
PostStore.addPostsViewJumpListener(this.handlePostsViewJumpRequest);
|
||||
UserStore.addChangeListener(this.onUserChange);
|
||||
$('body').addClass('app__body');
|
||||
}
|
||||
componentWillUnmount() {
|
||||
ChannelStore.removeChangeListener(this.onChannelChange);
|
||||
ChannelStore.removeLeaveListener(this.onChannelLeave);
|
||||
PostStore.removeChangeListener(this.onPostsChange);
|
||||
PostStore.removePostsViewJumpListener(this.handlePostsViewJumpRequest);
|
||||
UserStore.removeChangeListener(this.onUserChange);
|
||||
}
|
||||
onUserChange() {
|
||||
this.setState({currentUser: UserStore.getCurrentUser()});
|
||||
$('body').removeClass('app__body');
|
||||
}
|
||||
handlePostsViewJumpRequest(type, post) {
|
||||
switch (type) {
|
||||
@@ -171,7 +167,7 @@ export default class PostsViewContainer extends React.Component {
|
||||
const currentChannelId = channels[this.state.currentChannelIndex];
|
||||
const channel = ChannelStore.get(currentChannelId);
|
||||
|
||||
if (!this.state.currentUser || !channel) {
|
||||
if (!channel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -194,8 +190,6 @@ export default class PostsViewContainer extends React.Component {
|
||||
showMoreMessagesBottom={false}
|
||||
introText={channel ? createChannelIntroMessage(channel) : null}
|
||||
messageSeparatorTime={this.state.currentLastViewed}
|
||||
profiles={this.props.profiles}
|
||||
currentUser={this.state.currentUser}
|
||||
/>
|
||||
);
|
||||
if (!postLists[i] && isActive) {
|
||||
@@ -215,7 +209,3 @@ export default class PostsViewContainer extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PostsViewContainer.propTypes = {
|
||||
profiles: React.PropTypes.object
|
||||
};
|
||||
|
||||
@@ -1,35 +1,32 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import ReactDOM from 'react-dom';
|
||||
import UserProfile from './user_profile.jsx';
|
||||
import FileAttachmentList from './file_attachment_list.jsx';
|
||||
|
||||
import PostStore from 'stores/post_store.jsx';
|
||||
import ChannelStore from 'stores/channel_store.jsx';
|
||||
import UserProfile from './user_profile.jsx';
|
||||
|
||||
import * as GlobalActions from 'action_creators/global_actions.jsx';
|
||||
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
||||
|
||||
import * as TextFormatting from 'utils/text_formatting.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
import FileAttachmentList from './file_attachment_list.jsx';
|
||||
import Client from 'utils/web_client.jsx';
|
||||
import * as AsyncClient from 'utils/async_client.jsx';
|
||||
var ActionTypes = Constants.ActionTypes;
|
||||
import * as TextFormatting from 'utils/text_formatting.jsx';
|
||||
import twemoji from 'twemoji';
|
||||
import * as GlobalActions from 'action_creators/global_actions.jsx';
|
||||
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedDate} from 'react-intl';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
const ActionTypes = Constants.ActionTypes;
|
||||
|
||||
import {FormattedMessage, FormattedDate} from 'react-intl';
|
||||
|
||||
import loadingGif from 'images/load.gif';
|
||||
|
||||
const holders = defineMessages({
|
||||
comment: {
|
||||
id: 'rhs_comment.comment',
|
||||
defaultMessage: 'Comment'
|
||||
}
|
||||
});
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import twemoji from 'twemoji';
|
||||
|
||||
class RhsComment extends React.Component {
|
||||
export default class RhsComment extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -136,7 +133,7 @@ class RhsComment extends React.Component {
|
||||
data-toggle='modal'
|
||||
data-target='#edit_post'
|
||||
data-refocusid='#reply_textbox'
|
||||
data-title={this.props.intl.formatMessage(holders.comment)}
|
||||
data-title={Utils.localizeMessage('rhs_comment.comment', 'Comment')}
|
||||
data-message={post.message}
|
||||
data-postid={post.id}
|
||||
data-channelid={post.channel_id}
|
||||
@@ -302,14 +299,8 @@ class RhsComment extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
RhsComment.defaultProps = {
|
||||
post: null
|
||||
};
|
||||
RhsComment.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
post: React.PropTypes.object,
|
||||
user: React.PropTypes.object.isRequired,
|
||||
currentUser: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(RhsComment);
|
||||
|
||||
@@ -698,8 +698,3 @@ export default class Sidebar extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Sidebar.defaultProps = {
|
||||
};
|
||||
Sidebar.propTypes = {
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import {Link} from 'react-router';
|
||||
import {createMenuTip} from 'components/tutorial/tutorial_tip.jsx';
|
||||
|
||||
import React from 'react';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
export default class SidebarRightMenu extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -30,6 +31,8 @@ export default class SidebarRightMenu extends React.Component {
|
||||
const state = this.getStateFromStores();
|
||||
state.showUserSettingsModal = false;
|
||||
|
||||
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
|
||||
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,14 @@ import {FormattedRelative, FormattedDate} from 'react-intl';
|
||||
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
|
||||
|
||||
import React from 'react';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
export default class TimeSince extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
|
||||
}
|
||||
componentDidMount() {
|
||||
this.intervalId = setInterval(() => {
|
||||
this.forceUpdate();
|
||||
|
||||
@@ -22,6 +22,25 @@ export default class UserProfile extends React.Component {
|
||||
super(props);
|
||||
this.uniqueId = nextId();
|
||||
}
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextProps.overwriteName !== this.props.overwriteName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextProps.overwriteImage !== this.props.overwriteImage) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextProps.disablePopover !== this.props.disablePopover) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
render() {
|
||||
let name = '...';
|
||||
let email = '';
|
||||
|
||||
@@ -21,15 +21,16 @@
|
||||
"object-assign": "4.0.1",
|
||||
"perfect-scrollbar": "0.6.10",
|
||||
"react": "0.14.7",
|
||||
"react-addons-pure-render-mixin": "0.14.7",
|
||||
"react-bootstrap": "0.28.3",
|
||||
"react-custom-scrollbars": "^4.0.0-beta.1",
|
||||
"react-dom": "0.14.7",
|
||||
"react-intl": "2.0.0-rc-1",
|
||||
"react-router": "2.0.1",
|
||||
"react-textarea-autosize": "3.3.0",
|
||||
"superagent": "1.8.3",
|
||||
"twemoji": "1.4.1",
|
||||
"velocity-animate": "1.2.3",
|
||||
"superagent": "1.8.3"
|
||||
"velocity-animate": "1.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "5.0.0",
|
||||
@@ -55,7 +56,6 @@
|
||||
"style-loader": "0.13.0",
|
||||
"url-loader": "0.5.7",
|
||||
"webpack": "2.1.0-beta.5",
|
||||
|
||||
"mocha": "*",
|
||||
"mocha-webpack": "*",
|
||||
"webpack-node-externals": "*",
|
||||
|
||||
Ссылка в новой задаче
Block a user