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';
|
import React from 'react';
|
||||||
|
|
||||||
export default class ChannelInfoModal extends React.Component {
|
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() {
|
render() {
|
||||||
let channel = this.props.channel;
|
let channel = this.props.channel;
|
||||||
if (!channel) {
|
if (!channel) {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import PostsViewContainer from 'components/posts_view_container.jsx';
|
|||||||
|
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
|
|
||||||
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
export default class ChannelView extends React.Component {
|
export default class ChannelView extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -47,6 +49,17 @@ export default class ChannelView extends React.Component {
|
|||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.setState(this.getStateFromStores(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() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -57,7 +70,7 @@ export default class ChannelView extends React.Component {
|
|||||||
<ChannelHeader
|
<ChannelHeader
|
||||||
channelId={this.state.channelId}
|
channelId={this.state.channelId}
|
||||||
/>
|
/>
|
||||||
<PostsViewContainer profiles={this.props.profiles}/>
|
<PostsViewContainer/>
|
||||||
<div
|
<div
|
||||||
className='post-create__container'
|
className='post-create__container'
|
||||||
id='post-create'
|
id='post-create'
|
||||||
@@ -72,6 +85,5 @@ ChannelView.defaultProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ChannelView.propTypes = {
|
ChannelView.propTypes = {
|
||||||
params: React.PropTypes.object.isRequired,
|
params: React.PropTypes.object.isRequired
|
||||||
profiles: React.PropTypes.object
|
|
||||||
};
|
};
|
||||||
|
|||||||
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.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import Constants from 'utils/constants.jsx';
|
|
||||||
import GetLinkModal from './get_link_modal.jsx';
|
import GetLinkModal from './get_link_modal.jsx';
|
||||||
import ModalStore from 'stores/modal_store.jsx';
|
import ModalStore from 'stores/modal_store.jsx';
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
|
|
||||||
import {intlShape, injectIntl, defineMessages} from 'react-intl';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
import Constants from 'utils/constants.jsx';
|
||||||
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 React from 'react';
|
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) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleToggle = this.handleToggle.bind(this);
|
this.handleToggle = this.handleToggle.bind(this);
|
||||||
|
|
||||||
this.hide = this.hide.bind(this);
|
this.hide = this.hide.bind(this);
|
||||||
|
|
||||||
|
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
show: false,
|
show: false,
|
||||||
post: {}
|
post: {}
|
||||||
@@ -57,22 +48,14 @@ class GetPostLinkModal extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {formatMessage} = this.props.intl;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GetLinkModal
|
<GetLinkModal
|
||||||
show={this.state.show}
|
show={this.state.show}
|
||||||
onHide={this.hide}
|
onHide={this.hide}
|
||||||
title={formatMessage(holders.title)}
|
title={Utils.localizeMessage('get_post_link_modal.title', 'Copy Permalink')}
|
||||||
helpText={formatMessage(holders.help)}
|
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}
|
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.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import Constants from 'utils/constants.jsx';
|
|
||||||
import GetLinkModal from './get_link_modal.jsx';
|
import GetLinkModal from './get_link_modal.jsx';
|
||||||
import ModalStore from 'stores/modal_store.jsx';
|
import ModalStore from 'stores/modal_store.jsx';
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
|
|
||||||
import {intlShape, injectIntl, defineMessages} from 'react-intl';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
import Constants from 'utils/constants.jsx';
|
||||||
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 React from 'react';
|
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) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleToggle = this.handleToggle.bind(this);
|
this.handleToggle = this.handleToggle.bind(this);
|
||||||
|
|
||||||
|
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
show: false
|
show: false
|
||||||
};
|
};
|
||||||
@@ -51,28 +39,21 @@ class GetTeamInviteLinkModal extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {formatMessage} = this.props.intl;
|
let helpText;
|
||||||
|
|
||||||
let helpText = formatMessage(holders.helpDisabled);
|
|
||||||
|
|
||||||
if (global.window.mm_config.EnableUserCreation === 'true') {
|
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 (
|
return (
|
||||||
<GetLinkModal
|
<GetLinkModal
|
||||||
show={this.state.show}
|
show={this.state.show}
|
||||||
onHide={() => this.setState({show: false})}
|
onHide={() => this.setState({show: false})}
|
||||||
title={formatMessage(holders.title)}
|
title={Utils.localizeMessage('get_team_invite_link_modal.title', 'Team Invite Link')}
|
||||||
helpText={helpText}
|
helpText={helpText}
|
||||||
link={TeamStore.getCurrentInviteLink()}
|
link={TeamStore.getCurrentInviteLink()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GetTeamInviteLinkModal.propTypes = {
|
|
||||||
intl: intlShape.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(GetTeamInviteLinkModal);
|
|
||||||
|
|||||||
@@ -2,17 +2,22 @@
|
|||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import $ from 'jquery';
|
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 LoadingScreen from './loading_screen.jsx';
|
||||||
import NewChannelFlow from './new_channel_flow.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 {FormattedMessage} from 'react-intl';
|
||||||
import {browserHistory} from 'react-router';
|
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';
|
import loadingGif from 'images/load.gif';
|
||||||
|
|
||||||
function getStateFromStores() {
|
function getStateFromStores() {
|
||||||
@@ -22,8 +27,6 @@ function getStateFromStores() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export default class MoreChannels extends React.Component {
|
export default class MoreChannels extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -33,6 +36,8 @@ export default class MoreChannels extends React.Component {
|
|||||||
this.handleNewChannel = this.handleNewChannel.bind(this);
|
this.handleNewChannel = this.handleNewChannel.bind(this);
|
||||||
this.createChannelRow = this.createChannelRow.bind(this);
|
this.createChannelRow = this.createChannelRow.bind(this);
|
||||||
|
|
||||||
|
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
|
||||||
|
|
||||||
var initState = getStateFromStores();
|
var initState = getStateFromStores();
|
||||||
initState.channelType = '';
|
initState.channelType = '';
|
||||||
initState.joiningChannel = -1;
|
initState.joiningChannel = -1;
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export default class MoreDirectChannels extends React.Component {
|
|||||||
this.handleHide = this.handleHide.bind(this);
|
this.handleHide = this.handleHide.bind(this);
|
||||||
this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
|
this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
|
||||||
this.handleUserChange = this.handleUserChange.bind(this);
|
this.handleUserChange = this.handleUserChange.bind(this);
|
||||||
|
|
||||||
this.createJoinDirectChannelButton = this.createJoinDirectChannelButton.bind(this);
|
this.createJoinDirectChannelButton = this.createJoinDirectChannelButton.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -52,6 +51,26 @@ export default class MoreDirectChannels extends React.Component {
|
|||||||
UserStore.removeChangeListener(this.handleUserChange);
|
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() {
|
handleHide() {
|
||||||
if (this.props.onModalDismissed) {
|
if (this.props.onModalDismissed) {
|
||||||
this.props.onModalDismissed();
|
this.props.onModalDismissed();
|
||||||
|
|||||||
@@ -42,20 +42,18 @@ export default class NeedsTeam extends React.Component {
|
|||||||
this.onChanged = this.onChanged.bind(this);
|
this.onChanged = this.onChanged.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
profiles: UserStore.getProfiles(),
|
|
||||||
team: TeamStore.getCurrent()
|
team: TeamStore.getCurrent()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onChanged() {
|
onChanged() {
|
||||||
this.setState({
|
this.setState({
|
||||||
profiles: UserStore.getProfiles(),
|
|
||||||
team: TeamStore.getCurrent()
|
team: TeamStore.getCurrent()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
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);
|
const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
|
||||||
if (tutorialStep <= TutorialSteps.INTRO_SCREENS) {
|
if (tutorialStep <= TutorialSteps.INTRO_SCREENS) {
|
||||||
browserHistory.push(Utils.getTeamURLNoOriginFromAddressBar() + '/tutorial');
|
browserHistory.push(Utils.getTeamURLNoOriginFromAddressBar() + '/tutorial');
|
||||||
@@ -63,7 +61,6 @@ export default class NeedsTeam extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
UserStore.addChangeListener(this.onChanged);
|
|
||||||
TeamStore.addChangeListener(this.onChanged);
|
TeamStore.addChangeListener(this.onChanged);
|
||||||
|
|
||||||
// Emit view action
|
// Emit view action
|
||||||
@@ -84,7 +81,6 @@ export default class NeedsTeam extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
UserStore.removeChangeListener(this.onChanged);
|
|
||||||
TeamStore.removeChangeListener(this.onChanged);
|
TeamStore.removeChangeListener(this.onChanged);
|
||||||
$(window).off('focus');
|
$(window).off('focus');
|
||||||
$(window).off('blur');
|
$(window).off('blur');
|
||||||
@@ -114,7 +110,6 @@ export default class NeedsTeam extends React.Component {
|
|||||||
<div className='row main'>
|
<div className='row main'>
|
||||||
{React.cloneElement(this.props.center, {
|
{React.cloneElement(this.props.center, {
|
||||||
user: this.props.user,
|
user: this.props.user,
|
||||||
profiles: this.state.profiles,
|
|
||||||
team: this.state.team
|
team: this.state.team
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export default class PermalinkView extends React.Component {
|
|||||||
<ChannelHeader
|
<ChannelHeader
|
||||||
channelId={this.state.channelId}
|
channelId={this.state.channelId}
|
||||||
/>
|
/>
|
||||||
<PostFocusView profiles={this.props.profiles}/>
|
<PostFocusView/>
|
||||||
<div
|
<div
|
||||||
id='archive-link-home'
|
id='archive-link-home'
|
||||||
>
|
>
|
||||||
@@ -89,10 +89,6 @@ export default class PermalinkView extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PermalinkView.defaultProps = {
|
|
||||||
};
|
|
||||||
|
|
||||||
PermalinkView.propTypes = {
|
PermalinkView.propTypes = {
|
||||||
params: React.PropTypes.object.isRequired,
|
params: React.PropTypes.object.isRequired
|
||||||
profiles: React.PropTypes.object
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,24 +10,13 @@ import * as TextFormatting from 'utils/text_formatting.jsx';
|
|||||||
import twemoji from 'twemoji';
|
import twemoji from 'twemoji';
|
||||||
import PostBodyAdditionalContent from './post_body_additional_content.jsx';
|
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';
|
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';
|
import React from 'react';
|
||||||
|
|
||||||
class PostBody extends React.Component {
|
export default class PostBody extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@@ -61,8 +50,27 @@ class PostBody extends React.Component {
|
|||||||
this.parseEmojis();
|
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() {
|
render() {
|
||||||
const {formatMessage} = this.props.intl;
|
|
||||||
const post = this.props.post;
|
const post = this.props.post;
|
||||||
const filenames = this.props.post.filenames;
|
const filenames = this.props.post.filenames;
|
||||||
const parentPost = this.props.parentPost;
|
const parentPost = this.props.parentPost;
|
||||||
@@ -106,9 +114,9 @@ class PostBody extends React.Component {
|
|||||||
message = parentPost.filenames[0].split('/').pop();
|
message = parentPost.filenames[0].split('/').pop();
|
||||||
|
|
||||||
if (parentPost.filenames.length === 2) {
|
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) {
|
} 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'
|
id='post_body.commentedOn'
|
||||||
defaultMessage='Commented on {name}{apostrophe} message: '
|
defaultMessage='Commented on {name}{apostrophe} message: '
|
||||||
values={{
|
values={{
|
||||||
name: (name),
|
name,
|
||||||
apostrophe: apostrophe
|
apostrophe
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<a
|
<a
|
||||||
@@ -213,11 +221,8 @@ class PostBody extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PostBody.propTypes = {
|
PostBody.propTypes = {
|
||||||
intl: intlShape.isRequired,
|
|
||||||
post: React.PropTypes.object.isRequired,
|
post: React.PropTypes.object.isRequired,
|
||||||
parentPost: React.PropTypes.object,
|
parentPost: React.PropTypes.object,
|
||||||
retryPost: React.PropTypes.func.isRequired,
|
retryPost: React.PropTypes.func.isRequired,
|
||||||
handleCommentClick: 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 PostStore from 'stores/post_store.jsx';
|
||||||
import ChannelStore from 'stores/channel_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 * as GlobalActions from 'action_creators/global_actions.jsx';
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
@@ -18,7 +17,6 @@ export default class PostFocusView extends React.Component {
|
|||||||
|
|
||||||
this.onChannelChange = this.onChannelChange.bind(this);
|
this.onChannelChange = this.onChannelChange.bind(this);
|
||||||
this.onPostsChange = this.onPostsChange.bind(this);
|
this.onPostsChange = this.onPostsChange.bind(this);
|
||||||
this.onUserChange = this.onUserChange.bind(this);
|
|
||||||
this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
|
this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
|
||||||
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
|
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
|
||||||
this.loadMorePostsBottom = this.loadMorePostsBottom.bind(this);
|
this.loadMorePostsBottom = this.loadMorePostsBottom.bind(this);
|
||||||
@@ -31,21 +29,18 @@ export default class PostFocusView extends React.Component {
|
|||||||
scrollPostId: focusedPostId,
|
scrollPostId: focusedPostId,
|
||||||
postList: PostStore.getVisiblePosts(focusedPostId),
|
postList: PostStore.getVisiblePosts(focusedPostId),
|
||||||
atTop: PostStore.getVisibilityAtTop(focusedPostId),
|
atTop: PostStore.getVisibilityAtTop(focusedPostId),
|
||||||
atBottom: PostStore.getVisibilityAtBottom(focusedPostId),
|
atBottom: PostStore.getVisibilityAtBottom(focusedPostId)
|
||||||
currentUser: UserStore.getCurrentUser()
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
ChannelStore.addChangeListener(this.onChannelChange);
|
ChannelStore.addChangeListener(this.onChannelChange);
|
||||||
PostStore.addChangeListener(this.onPostsChange);
|
PostStore.addChangeListener(this.onPostsChange);
|
||||||
UserStore.addChangeListener(this.onUserChange);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ChannelStore.removeChangeListener(this.onChannelChange);
|
ChannelStore.removeChangeListener(this.onChannelChange);
|
||||||
PostStore.removeChangeListener(this.onPostsChange);
|
PostStore.removeChangeListener(this.onPostsChange);
|
||||||
UserStore.removeChangeListener(this.onUserChange);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onChannelChange() {
|
onChannelChange() {
|
||||||
@@ -58,10 +53,6 @@ export default class PostFocusView extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onUserChange() {
|
|
||||||
this.setState({currentUser: UserStore.getCurrentUser()});
|
|
||||||
}
|
|
||||||
|
|
||||||
onPostsChange() {
|
onPostsChange() {
|
||||||
const focusedPostId = PostStore.getFocusedPostId();
|
const focusedPostId = PostStore.getFocusedPostId();
|
||||||
if (focusedPostId == null) {
|
if (focusedPostId == null) {
|
||||||
@@ -105,7 +96,7 @@ export default class PostFocusView extends React.Component {
|
|||||||
const postsToHighlight = {};
|
const postsToHighlight = {};
|
||||||
postsToHighlight[this.state.scrollPostId] = true;
|
postsToHighlight[this.state.scrollPostId] = true;
|
||||||
|
|
||||||
if (!this.state.currentUser || !this.state.postList) {
|
if (!this.state.postList) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,16 +116,8 @@ export default class PostFocusView extends React.Component {
|
|||||||
introText={this.getIntroMessage()}
|
introText={this.getIntroMessage()}
|
||||||
messageSeparatorTime={0}
|
messageSeparatorTime={0}
|
||||||
postsToHighlight={postsToHighlight}
|
postsToHighlight={postsToHighlight}
|
||||||
profiles={this.props.profiles}
|
|
||||||
currentUser={this.state.currentUser}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PostFocusView.defaultProps = {
|
|
||||||
};
|
|
||||||
|
|
||||||
PostFocusView.propTypes = {
|
|
||||||
profiles: React.PropTypes.object
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -2,19 +2,25 @@
|
|||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import $ from 'jquery';
|
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 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 DelayedAction from 'utils/delayed_action.jsx';
|
||||||
|
|
||||||
|
import Constants from 'utils/constants.jsx';
|
||||||
|
const Preferences = Constants.Preferences;
|
||||||
|
|
||||||
import {FormattedDate, FormattedMessage} from 'react-intl';
|
import {FormattedDate, FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
const Preferences = Constants.Preferences;
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
export default class PostsView extends React.Component {
|
export default class PostsView extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -31,6 +37,7 @@ export default class PostsView extends React.Component {
|
|||||||
this.handleResize = this.handleResize.bind(this);
|
this.handleResize = this.handleResize.bind(this);
|
||||||
this.scrollToBottom = this.scrollToBottom.bind(this);
|
this.scrollToBottom = this.scrollToBottom.bind(this);
|
||||||
this.scrollToBottomAnimated = this.scrollToBottomAnimated.bind(this);
|
this.scrollToBottomAnimated = this.scrollToBottomAnimated.bind(this);
|
||||||
|
this.onUserChange = this.onUserChange.bind(this);
|
||||||
|
|
||||||
this.jumpToPostNode = null;
|
this.jumpToPostNode = null;
|
||||||
this.wasAtBottom = true;
|
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,
|
centerPosts: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
|
||||||
isScrolling: false,
|
isScrolling: false,
|
||||||
topPostId: null,
|
topPostId: null,
|
||||||
showUnreadMessageAlert: false
|
showUnreadMessageAlert: false,
|
||||||
|
currentUser: UserStore.getCurrentUser(),
|
||||||
|
profiles: UserStore.getProfiles()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
static get SCROLL_TYPE_FREE() {
|
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
|
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() {
|
isAtBottom() {
|
||||||
// consider the view to be at the bottom if it's within this many pixels of the bottom
|
// consider the view to be at the bottom if it's within this many pixels of the bottom
|
||||||
const atBottomMargin = 10;
|
const atBottomMargin = 10;
|
||||||
@@ -152,8 +164,8 @@ export default class PostsView extends React.Component {
|
|||||||
createPosts(posts, order) {
|
createPosts(posts, order) {
|
||||||
const postCtls = [];
|
const postCtls = [];
|
||||||
let previousPostDay = new Date(0);
|
let previousPostDay = new Date(0);
|
||||||
const userId = this.props.currentUser.id;
|
const userId = this.state.currentUser.id;
|
||||||
const profiles = this.props.profiles || {};
|
const profiles = this.state.profiles || {};
|
||||||
|
|
||||||
let renderedLastViewed = false;
|
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);
|
const shouldHighlight = this.props.postsToHighlight && this.props.postsToHighlight.hasOwnProperty(post.id);
|
||||||
|
|
||||||
let profile;
|
let profile;
|
||||||
if (this.props.currentUser.id === post.user_id) {
|
if (userId === post.user_id) {
|
||||||
profile = this.props.currentUser;
|
profile = this.state.currentUser;
|
||||||
} else {
|
} else {
|
||||||
profile = profiles[post.user_id];
|
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
|
onClick={() => GlobalActions.emitPostFocusEvent(post.id)} //eslint-disable-line no-loop-func
|
||||||
displayNameType={this.state.displayNameType}
|
displayNameType={this.state.displayNameType}
|
||||||
user={profile}
|
user={profile}
|
||||||
currentUser={this.props.currentUser}
|
currentUser={this.state.currentUser}
|
||||||
center={this.state.centerPosts}
|
center={this.state.centerPosts}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -377,13 +389,19 @@ export default class PostsView extends React.Component {
|
|||||||
if (this.props.postList != null) {
|
if (this.props.postList != null) {
|
||||||
this.updateScrolling();
|
this.updateScrolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.isActive) {
|
||||||
|
PreferenceStore.addChangeListener(this.updateState);
|
||||||
|
UserStore.addChangeListener(this.onUserChange);
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('resize', this.handleResize);
|
window.addEventListener('resize', this.handleResize);
|
||||||
PreferenceStore.addChangeListener(this.updateState);
|
|
||||||
}
|
}
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.handleResize);
|
||||||
this.scrollStopAction.cancel();
|
this.scrollStopAction.cancel();
|
||||||
PreferenceStore.removeChangeListener(this.updateState);
|
PreferenceStore.removeChangeListener(this.updateState);
|
||||||
|
UserStore.removeChangeListener(this.onUserChange);
|
||||||
}
|
}
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if (this.props.postList != null) {
|
if (this.props.postList != null) {
|
||||||
@@ -401,8 +419,10 @@ export default class PostsView extends React.Component {
|
|||||||
if (!this.props.isActive && nextProps.isActive) {
|
if (!this.props.isActive && nextProps.isActive) {
|
||||||
this.updateState();
|
this.updateState();
|
||||||
PreferenceStore.addChangeListener(this.updateState);
|
PreferenceStore.addChangeListener(this.updateState);
|
||||||
|
UserStore.addChangeListener(this.onUserChange);
|
||||||
} else if (this.props.isActive && !nextProps.isActive) {
|
} else if (this.props.isActive && !nextProps.isActive) {
|
||||||
PreferenceStore.removeChangeListener(this.updateState);
|
PreferenceStore.removeChangeListener(this.updateState);
|
||||||
|
UserStore.removeChangeListener(this.onUserChange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
@@ -436,7 +456,7 @@ export default class PostsView extends React.Component {
|
|||||||
if (this.state.centerPosts !== nextState.centerPosts) {
|
if (this.state.centerPosts !== nextState.centerPosts) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!Utils.areObjectsEqual(this.props.profiles, nextProps.profiles)) {
|
if (!Utils.areObjectsEqual(this.state.profiles, nextState.profiles)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,16 +517,17 @@ export default class PostsView extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let topPost = null;
|
let topPostCreateAt = 0;
|
||||||
if (this.state.topPostId) {
|
if (this.state.topPostId) {
|
||||||
topPost = this.props.postList.posts[this.state.topPostId];
|
topPostCreateAt = this.props.postList.posts[this.state.topPostId].create_at;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={activeClass}>
|
<div className={activeClass}>
|
||||||
<FloatingTimestamp
|
<FloatingTimestamp
|
||||||
isScrolling={this.state.isScrolling}
|
isScrolling={this.state.isScrolling}
|
||||||
post={topPost}
|
isMobile={$(window).width() > 768}
|
||||||
|
createAt={topPostCreateAt}
|
||||||
/>
|
/>
|
||||||
<ScrollToBottomArrows
|
<ScrollToBottomArrows
|
||||||
isScrolling={this.state.isScrolling}
|
isScrolling={this.state.isScrolling}
|
||||||
@@ -551,7 +572,6 @@ PostsView.defaultProps = {
|
|||||||
PostsView.propTypes = {
|
PostsView.propTypes = {
|
||||||
isActive: React.PropTypes.bool,
|
isActive: React.PropTypes.bool,
|
||||||
postList: React.PropTypes.object,
|
postList: React.PropTypes.object,
|
||||||
profiles: React.PropTypes.object.isRequired,
|
|
||||||
scrollPostId: React.PropTypes.string,
|
scrollPostId: React.PropTypes.string,
|
||||||
scrollType: React.PropTypes.number,
|
scrollType: React.PropTypes.number,
|
||||||
postViewScrolled: React.PropTypes.func.isRequired,
|
postViewScrolled: React.PropTypes.func.isRequired,
|
||||||
@@ -561,47 +581,7 @@ PostsView.propTypes = {
|
|||||||
showMoreMessagesBottom: React.PropTypes.bool,
|
showMoreMessagesBottom: React.PropTypes.bool,
|
||||||
introText: React.PropTypes.element,
|
introText: React.PropTypes.element,
|
||||||
messageSeparatorTime: React.PropTypes.number,
|
messageSeparatorTime: React.PropTypes.number,
|
||||||
postsToHighlight: React.PropTypes.object,
|
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
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function ScrollToBottomArrows({isScrolling, atBottom, onClick}) {
|
function ScrollToBottomArrows({isScrolling, atBottom, onClick}) {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
import PostsView from './posts_view.jsx';
|
import PostsView from './posts_view.jsx';
|
||||||
import LoadingScreen from './loading_screen.jsx';
|
import LoadingScreen from './loading_screen.jsx';
|
||||||
|
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
import PostStore from 'stores/post_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';
|
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.onChannelChange = this.onChannelChange.bind(this);
|
||||||
this.onChannelLeave = this.onChannelLeave.bind(this);
|
this.onChannelLeave = this.onChannelLeave.bind(this);
|
||||||
this.onPostsChange = this.onPostsChange.bind(this);
|
this.onPostsChange = this.onPostsChange.bind(this);
|
||||||
this.onUserChange = this.onUserChange.bind(this);
|
|
||||||
this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
|
this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
|
||||||
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
|
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
|
||||||
this.handlePostsViewJumpRequest = this.handlePostsViewJumpRequest.bind(this);
|
this.handlePostsViewJumpRequest = this.handlePostsViewJumpRequest.bind(this);
|
||||||
@@ -33,8 +33,7 @@ export default class PostsViewContainer extends React.Component {
|
|||||||
const currentChannelId = ChannelStore.getCurrentId();
|
const currentChannelId = ChannelStore.getCurrentId();
|
||||||
const state = {
|
const state = {
|
||||||
scrollType: PostsView.SCROLL_TYPE_BOTTOM,
|
scrollType: PostsView.SCROLL_TYPE_BOTTOM,
|
||||||
scrollPost: null,
|
scrollPost: null
|
||||||
currentUser: UserStore.getCurrentUser()
|
|
||||||
};
|
};
|
||||||
if (currentChannelId) {
|
if (currentChannelId) {
|
||||||
Object.assign(state, {
|
Object.assign(state, {
|
||||||
@@ -60,17 +59,14 @@ export default class PostsViewContainer extends React.Component {
|
|||||||
ChannelStore.addLeaveListener(this.onChannelLeave);
|
ChannelStore.addLeaveListener(this.onChannelLeave);
|
||||||
PostStore.addChangeListener(this.onPostsChange);
|
PostStore.addChangeListener(this.onPostsChange);
|
||||||
PostStore.addPostsViewJumpListener(this.handlePostsViewJumpRequest);
|
PostStore.addPostsViewJumpListener(this.handlePostsViewJumpRequest);
|
||||||
UserStore.addChangeListener(this.onUserChange);
|
$('body').addClass('app__body');
|
||||||
}
|
}
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ChannelStore.removeChangeListener(this.onChannelChange);
|
ChannelStore.removeChangeListener(this.onChannelChange);
|
||||||
ChannelStore.removeLeaveListener(this.onChannelLeave);
|
ChannelStore.removeLeaveListener(this.onChannelLeave);
|
||||||
PostStore.removeChangeListener(this.onPostsChange);
|
PostStore.removeChangeListener(this.onPostsChange);
|
||||||
PostStore.removePostsViewJumpListener(this.handlePostsViewJumpRequest);
|
PostStore.removePostsViewJumpListener(this.handlePostsViewJumpRequest);
|
||||||
UserStore.removeChangeListener(this.onUserChange);
|
$('body').removeClass('app__body');
|
||||||
}
|
|
||||||
onUserChange() {
|
|
||||||
this.setState({currentUser: UserStore.getCurrentUser()});
|
|
||||||
}
|
}
|
||||||
handlePostsViewJumpRequest(type, post) {
|
handlePostsViewJumpRequest(type, post) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -171,7 +167,7 @@ export default class PostsViewContainer extends React.Component {
|
|||||||
const currentChannelId = channels[this.state.currentChannelIndex];
|
const currentChannelId = channels[this.state.currentChannelIndex];
|
||||||
const channel = ChannelStore.get(currentChannelId);
|
const channel = ChannelStore.get(currentChannelId);
|
||||||
|
|
||||||
if (!this.state.currentUser || !channel) {
|
if (!channel) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,8 +190,6 @@ export default class PostsViewContainer extends React.Component {
|
|||||||
showMoreMessagesBottom={false}
|
showMoreMessagesBottom={false}
|
||||||
introText={channel ? createChannelIntroMessage(channel) : null}
|
introText={channel ? createChannelIntroMessage(channel) : null}
|
||||||
messageSeparatorTime={this.state.currentLastViewed}
|
messageSeparatorTime={this.state.currentLastViewed}
|
||||||
profiles={this.props.profiles}
|
|
||||||
currentUser={this.state.currentUser}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
if (!postLists[i] && isActive) {
|
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.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// 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 PostStore from 'stores/post_store.jsx';
|
||||||
import ChannelStore from 'stores/channel_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 AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
||||||
|
|
||||||
|
import * as TextFormatting from 'utils/text_formatting.jsx';
|
||||||
import * as Utils from 'utils/utils.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 Client from 'utils/web_client.jsx';
|
||||||
import * as AsyncClient from 'utils/async_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';
|
import loadingGif from 'images/load.gif';
|
||||||
|
|
||||||
const holders = defineMessages({
|
|
||||||
comment: {
|
|
||||||
id: 'rhs_comment.comment',
|
|
||||||
defaultMessage: 'Comment'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
import React from 'react';
|
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) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@@ -136,7 +133,7 @@ class RhsComment extends React.Component {
|
|||||||
data-toggle='modal'
|
data-toggle='modal'
|
||||||
data-target='#edit_post'
|
data-target='#edit_post'
|
||||||
data-refocusid='#reply_textbox'
|
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-message={post.message}
|
||||||
data-postid={post.id}
|
data-postid={post.id}
|
||||||
data-channelid={post.channel_id}
|
data-channelid={post.channel_id}
|
||||||
@@ -302,14 +299,8 @@ class RhsComment extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RhsComment.defaultProps = {
|
|
||||||
post: null
|
|
||||||
};
|
|
||||||
RhsComment.propTypes = {
|
RhsComment.propTypes = {
|
||||||
intl: intlShape.isRequired,
|
|
||||||
post: React.PropTypes.object,
|
post: React.PropTypes.object,
|
||||||
user: React.PropTypes.object.isRequired,
|
user: React.PropTypes.object.isRequired,
|
||||||
currentUser: 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 {createMenuTip} from 'components/tutorial/tutorial_tip.jsx';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||||
|
|
||||||
export default class SidebarRightMenu extends React.Component {
|
export default class SidebarRightMenu extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -30,6 +31,8 @@ export default class SidebarRightMenu extends React.Component {
|
|||||||
const state = this.getStateFromStores();
|
const state = this.getStateFromStores();
|
||||||
state.showUserSettingsModal = false;
|
state.showUserSettingsModal = false;
|
||||||
|
|
||||||
|
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
|
||||||
|
|
||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,14 @@ import {FormattedRelative, FormattedDate} from 'react-intl';
|
|||||||
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
|
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||||
|
|
||||||
export default class TimeSince extends React.Component {
|
export default class TimeSince extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
|
||||||
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.intervalId = setInterval(() => {
|
this.intervalId = setInterval(() => {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
|
|||||||
@@ -22,6 +22,25 @@ export default class UserProfile extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.uniqueId = nextId();
|
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() {
|
render() {
|
||||||
let name = '...';
|
let name = '...';
|
||||||
let email = '';
|
let email = '';
|
||||||
|
|||||||
@@ -21,15 +21,16 @@
|
|||||||
"object-assign": "4.0.1",
|
"object-assign": "4.0.1",
|
||||||
"perfect-scrollbar": "0.6.10",
|
"perfect-scrollbar": "0.6.10",
|
||||||
"react": "0.14.7",
|
"react": "0.14.7",
|
||||||
|
"react-addons-pure-render-mixin": "0.14.7",
|
||||||
"react-bootstrap": "0.28.3",
|
"react-bootstrap": "0.28.3",
|
||||||
"react-custom-scrollbars": "^4.0.0-beta.1",
|
"react-custom-scrollbars": "^4.0.0-beta.1",
|
||||||
"react-dom": "0.14.7",
|
"react-dom": "0.14.7",
|
||||||
"react-intl": "2.0.0-rc-1",
|
"react-intl": "2.0.0-rc-1",
|
||||||
"react-router": "2.0.1",
|
"react-router": "2.0.1",
|
||||||
"react-textarea-autosize": "3.3.0",
|
"react-textarea-autosize": "3.3.0",
|
||||||
|
"superagent": "1.8.3",
|
||||||
"twemoji": "1.4.1",
|
"twemoji": "1.4.1",
|
||||||
"velocity-animate": "1.2.3",
|
"velocity-animate": "1.2.3"
|
||||||
"superagent": "1.8.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-eslint": "5.0.0",
|
"babel-eslint": "5.0.0",
|
||||||
@@ -55,7 +56,6 @@
|
|||||||
"style-loader": "0.13.0",
|
"style-loader": "0.13.0",
|
||||||
"url-loader": "0.5.7",
|
"url-loader": "0.5.7",
|
||||||
"webpack": "2.1.0-beta.5",
|
"webpack": "2.1.0-beta.5",
|
||||||
|
|
||||||
"mocha": "*",
|
"mocha": "*",
|
||||||
"mocha-webpack": "*",
|
"mocha-webpack": "*",
|
||||||
"webpack-node-externals": "*",
|
"webpack-node-externals": "*",
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user