PLT-2672 Refactored posts view with caching (#3054)
* Refactored posts view to use view controller design * Add post view caching * Required updates after rebase * Fixed bug where current channel not set yet was causing breakage
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
ca9f348be6
Коммит
6399a94ce2
@@ -7,7 +7,7 @@ import React from 'react';
|
|||||||
import ChannelHeader from 'components/channel_header.jsx';
|
import ChannelHeader from 'components/channel_header.jsx';
|
||||||
import FileUploadOverlay from 'components/file_upload_overlay.jsx';
|
import FileUploadOverlay from 'components/file_upload_overlay.jsx';
|
||||||
import CreatePost from 'components/create_post.jsx';
|
import CreatePost from 'components/create_post.jsx';
|
||||||
import PostsViewContainer from 'components/posts_view_container.jsx';
|
import PostViewCache from 'components/post_view/post_view_cache.jsx';
|
||||||
|
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ export default class ChannelView extends React.Component {
|
|||||||
<ChannelHeader
|
<ChannelHeader
|
||||||
channelId={this.state.channelId}
|
channelId={this.state.channelId}
|
||||||
/>
|
/>
|
||||||
<PostsViewContainer/>
|
<PostViewCache/>
|
||||||
<div
|
<div
|
||||||
className='post-create__container'
|
className='post-create__container'
|
||||||
id='post-create'
|
id='post-create'
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import $ from 'jquery';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import ChannelHeader from 'components/channel_header.jsx';
|
import ChannelHeader from 'components/channel_header.jsx';
|
||||||
import PostFocusView from 'components/post_focus_view.jsx';
|
import PostFocusViewController from 'components/post_view/post_focus_view_controller.jsx';
|
||||||
|
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
@@ -70,7 +70,7 @@ export default class PermalinkView extends React.Component {
|
|||||||
<ChannelHeader
|
<ChannelHeader
|
||||||
channelId={this.state.channelId}
|
channelId={this.state.channelId}
|
||||||
/>
|
/>
|
||||||
<PostFocusView/>
|
<PostFocusViewController/>
|
||||||
<div
|
<div
|
||||||
id='archive-link-home'
|
id='archive-link-home'
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
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 AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
import AppDispatcher from 'dispatcher/app_dispatcher.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';
|
||||||
@@ -16,7 +16,7 @@ import {FormattedMessage} from 'react-intl';
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export default class PendingPostActions extends React.Component {
|
export default class PendingPostOptions extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.retryPost = this.retryPost.bind(this);
|
this.retryPost = this.retryPost.bind(this);
|
||||||
@@ -87,6 +87,6 @@ export default class PendingPostActions extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PendingPostActions.propTypes = {
|
PendingPostOptions.propTypes = {
|
||||||
post: React.PropTypes.object
|
post: React.PropTypes.object
|
||||||
};
|
};
|
||||||
@@ -8,7 +8,8 @@ import Constants from 'utils/constants.jsx';
|
|||||||
const ActionTypes = Constants.ActionTypes;
|
const ActionTypes = Constants.ActionTypes;
|
||||||
|
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
import * as PostUtils from 'utils/post_utils.jsx';
|
||||||
|
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ export default class Post extends React.Component {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.getCommentCount(nextProps) !== this.getCommentCount(this.props)) {
|
if (nextProps.commentCount !== this.props.commentCount) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,30 +78,9 @@ export default class Post extends React.Component {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
getCommentCount(props) {
|
|
||||||
const post = props.post;
|
|
||||||
const parentPost = props.parentPost;
|
|
||||||
const posts = props.posts;
|
|
||||||
|
|
||||||
let commentCount = 0;
|
|
||||||
let commentRootId;
|
|
||||||
if (parentPost) {
|
|
||||||
commentRootId = post.root_id;
|
|
||||||
} else {
|
|
||||||
commentRootId = post.id;
|
|
||||||
}
|
|
||||||
for (const postId in posts) {
|
|
||||||
if (posts[postId].root_id === commentRootId) {
|
|
||||||
commentCount += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return commentCount;
|
|
||||||
}
|
|
||||||
render() {
|
render() {
|
||||||
const post = this.props.post;
|
const post = this.props.post;
|
||||||
const parentPost = this.props.parentPost;
|
const parentPost = this.props.parentPost;
|
||||||
const posts = this.props.posts;
|
|
||||||
const mattermostLogo = Constants.MATTERMOST_ICON_SVG;
|
const mattermostLogo = Constants.MATTERMOST_ICON_SVG;
|
||||||
|
|
||||||
if (!post.props) {
|
if (!post.props) {
|
||||||
@@ -112,7 +92,7 @@ export default class Post extends React.Component {
|
|||||||
type = 'Comment';
|
type = 'Comment';
|
||||||
}
|
}
|
||||||
|
|
||||||
const commentCount = this.getCommentCount(this.props);
|
const commentCount = this.props.commentCount;
|
||||||
|
|
||||||
let rootUser;
|
let rootUser;
|
||||||
if (this.props.sameRoot) {
|
if (this.props.sameRoot) {
|
||||||
@@ -129,7 +109,7 @@ export default class Post extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let currentUserCss = '';
|
let currentUserCss = '';
|
||||||
if (this.props.currentUser.id === post.user_id && !post.props.from_webhook && !Utils.isSystemMessage(post)) {
|
if (this.props.currentUser.id === post.user_id && !post.props.from_webhook && !PostUtils.isSystemMessage(post)) {
|
||||||
currentUserCss = 'current--user';
|
currentUserCss = 'current--user';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,19 +131,19 @@ export default class Post extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let systemMessageClass = '';
|
let systemMessageClass = '';
|
||||||
if (Utils.isSystemMessage(post)) {
|
if (PostUtils.isSystemMessage(post)) {
|
||||||
systemMessageClass = 'post--system';
|
systemMessageClass = 'post--system';
|
||||||
}
|
}
|
||||||
|
|
||||||
let profilePic = (
|
let profilePic = (
|
||||||
<img
|
<img
|
||||||
src={Utils.getProfilePicSrcForPost(post, timestamp)}
|
src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
|
||||||
height='36'
|
height='36'
|
||||||
width='36'
|
width='36'
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (Utils.isSystemMessage(post)) {
|
if (PostUtils.isSystemMessage(post)) {
|
||||||
profilePic = (
|
profilePic = (
|
||||||
<span
|
<span
|
||||||
className='icon'
|
className='icon'
|
||||||
@@ -207,7 +187,6 @@ export default class Post extends React.Component {
|
|||||||
post={post}
|
post={post}
|
||||||
sameRoot={this.props.sameRoot}
|
sameRoot={this.props.sameRoot}
|
||||||
parentPost={parentPost}
|
parentPost={parentPost}
|
||||||
posts={posts}
|
|
||||||
handleCommentClick={this.handleCommentClick}
|
handleCommentClick={this.handleCommentClick}
|
||||||
compactDisplay={this.props.compactDisplay}
|
compactDisplay={this.props.compactDisplay}
|
||||||
/>
|
/>
|
||||||
@@ -221,7 +200,6 @@ export default class Post extends React.Component {
|
|||||||
|
|
||||||
Post.propTypes = {
|
Post.propTypes = {
|
||||||
post: React.PropTypes.object.isRequired,
|
post: React.PropTypes.object.isRequired,
|
||||||
posts: React.PropTypes.object,
|
|
||||||
parentPost: React.PropTypes.object,
|
parentPost: React.PropTypes.object,
|
||||||
user: React.PropTypes.object,
|
user: React.PropTypes.object,
|
||||||
sameUser: React.PropTypes.bool,
|
sameUser: React.PropTypes.bool,
|
||||||
@@ -233,5 +211,6 @@ Post.propTypes = {
|
|||||||
hasProfiles: React.PropTypes.bool,
|
hasProfiles: React.PropTypes.bool,
|
||||||
currentUser: React.PropTypes.object.isRequired,
|
currentUser: React.PropTypes.object.isRequired,
|
||||||
center: React.PropTypes.bool,
|
center: React.PropTypes.bool,
|
||||||
compactDisplay: React.PropTypes.bool
|
compactDisplay: React.PropTypes.bool,
|
||||||
|
commentCount: React.PropTypes.number
|
||||||
};
|
};
|
||||||
@@ -1,13 +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 FileAttachmentList from './file_attachment_list.jsx';
|
import FileAttachmentList from 'components/file_attachment_list.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
import * as TextFormatting from 'utils/text_formatting.jsx';
|
import * as TextFormatting from 'utils/text_formatting.jsx';
|
||||||
import PostBodyAdditionalContent from './post_body_additional_content.jsx';
|
import PostBodyAdditionalContent from './post_body_additional_content.jsx';
|
||||||
import PendingPostActions from './pending_post_actions.jsx';
|
import PendingPostOptions from './pending_post_options.jsx';
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ export default class PostBody extends React.Component {
|
|||||||
let loading;
|
let loading;
|
||||||
if (post.state === Constants.POST_FAILED) {
|
if (post.state === Constants.POST_FAILED) {
|
||||||
postClass += ' post--fail';
|
postClass += ' post--fail';
|
||||||
loading = <PendingPostActions post={this.props.post}/>;
|
loading = <PendingPostOptions post={this.props.post}/>;
|
||||||
} else if (post.state === Constants.POST_LOADING) {
|
} else if (post.state === Constants.POST_LOADING) {
|
||||||
postClass += ' post-waiting';
|
postClass += ' post-waiting';
|
||||||
loading = (
|
loading = (
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
import PostAttachmentList from './post_attachment_list.jsx';
|
import PostAttachmentList from './post_attachment_list.jsx';
|
||||||
import PostAttachmentOEmbed from './post_attachment_oembed.jsx';
|
import PostAttachmentOEmbed from './post_attachment_oembed.jsx';
|
||||||
import PostImage from './post_image.jsx';
|
import PostImage from './post_image.jsx';
|
||||||
import YoutubeVideo from './youtube_video.jsx';
|
import YoutubeVideo from 'components/youtube_video.jsx';
|
||||||
|
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
import OEmbedProviders from './providers.json';
|
import OEmbedProviders from './providers.json';
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
// 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 UserProfile from './user_profile.jsx';
|
import UserProfile from 'components/user_profile.jsx';
|
||||||
import PostInfo from './post_info.jsx';
|
import PostInfo from './post_info.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
|
||||||
|
import * as PostUtils from 'utils/post_utils.jsx';
|
||||||
|
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ export default class PostHeader extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
botIndicator = <li className='col col__name bot-indicator'>{Constants.BOT_NAME}</li>;
|
botIndicator = <li className='col col__name bot-indicator'>{Constants.BOT_NAME}</li>;
|
||||||
} else if (Utils.isSystemMessage(post)) {
|
} else if (PostUtils.isSystemMessage(post)) {
|
||||||
userProfile = (
|
userProfile = (
|
||||||
<UserProfile
|
<UserProfile
|
||||||
user={{}}
|
user={{}}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
import TimeSince from './time_since.jsx';
|
import TimeSince from 'components/time_since.jsx';
|
||||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
@@ -5,30 +5,28 @@ import $ from 'jquery';
|
|||||||
|
|
||||||
import Post from './post.jsx';
|
import Post from './post.jsx';
|
||||||
import FloatingTimestamp from './floating_timestamp.jsx';
|
import FloatingTimestamp from './floating_timestamp.jsx';
|
||||||
|
import ScrollToBottomArrows from './scroll_to_bottom_arrows.jsx';
|
||||||
|
|
||||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||||
|
|
||||||
import PreferenceStore from 'stores/preference_store.jsx';
|
|
||||||
import UserStore from 'stores/user_store.jsx';
|
|
||||||
|
|
||||||
import {createChannelIntroMessage} from 'utils/channel_intro_messages.jsx';
|
import {createChannelIntroMessage} from 'utils/channel_intro_messages.jsx';
|
||||||
|
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
import * as PostUtils from 'utils/post_utils.jsx';
|
||||||
import DelayedAction from 'utils/delayed_action.jsx';
|
import DelayedAction from 'utils/delayed_action.jsx';
|
||||||
|
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
const Preferences = Constants.Preferences;
|
const ScrollTypes = Constants.ScrollTypes;
|
||||||
|
|
||||||
import {FormattedDate, FormattedMessage} from 'react-intl';
|
import {FormattedDate, FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
export default class PostsView extends React.Component {
|
export default class PostList extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.updateState = this.updateState.bind(this);
|
|
||||||
this.handleScroll = this.handleScroll.bind(this);
|
this.handleScroll = this.handleScroll.bind(this);
|
||||||
this.handleScrollStop = this.handleScrollStop.bind(this);
|
this.handleScrollStop = this.handleScrollStop.bind(this);
|
||||||
this.isAtBottom = this.isAtBottom.bind(this);
|
this.isAtBottom = this.isAtBottom.bind(this);
|
||||||
@@ -39,7 +37,6 @@ 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;
|
||||||
@@ -47,62 +44,31 @@ export default class PostsView extends React.Component {
|
|||||||
|
|
||||||
this.scrollStopAction = new DelayedAction(this.handleScrollStop);
|
this.scrollStopAction = new DelayedAction(this.handleScrollStop);
|
||||||
|
|
||||||
let profiles = UserStore.getProfiles();
|
if (props.channel) {
|
||||||
if (props.channel && props.channel.type === Constants.DM_CHANNEL) {
|
this.introText = createChannelIntroMessage(props.channel);
|
||||||
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
|
} else {
|
||||||
|
this.introText = this.getArchivesIntroMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
displayNameType: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false'),
|
|
||||||
centerPosts: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
|
|
||||||
compactPosts: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT,
|
|
||||||
isScrolling: false,
|
isScrolling: false,
|
||||||
topPostId: null,
|
topPostId: null
|
||||||
currentUser: UserStore.getCurrentUser(),
|
|
||||||
profiles
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
static get SCROLL_TYPE_FREE() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
static get SCROLL_TYPE_BOTTOM() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
static get SCROLL_TYPE_SIDEBAR_OPEN() {
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
static get SCROLL_TYPE_NEW_MESSAGE() {
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
static get SCROLL_TYPE_POST() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
updateState() {
|
|
||||||
this.setState({
|
|
||||||
displayNameType: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false'),
|
|
||||||
centerPosts: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
|
|
||||||
compactPosts: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT
|
|
||||||
});
|
|
||||||
}
|
|
||||||
onUserChange() {
|
|
||||||
let profiles = UserStore.getProfiles();
|
|
||||||
if (this.props.channel && this.props.channel.type === Constants.DM_CHANNEL) {
|
|
||||||
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
|
|
||||||
}
|
|
||||||
this.setState({currentUser: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(profiles))});
|
|
||||||
}
|
|
||||||
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;
|
||||||
|
|
||||||
return this.refs.postlist.clientHeight + this.refs.postlist.scrollTop >= this.refs.postlist.scrollHeight - atBottomMargin;
|
return this.refs.postlist.clientHeight + this.refs.postlist.scrollTop >= this.refs.postlist.scrollHeight - atBottomMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScroll() {
|
handleScroll() {
|
||||||
// HACK FOR RHS -- REMOVE WHEN RHS DIES
|
// HACK FOR RHS -- REMOVE WHEN RHS DIES
|
||||||
const childNodes = this.refs.postlistcontent.childNodes;
|
const childNodes = this.refs.postlistcontent.childNodes;
|
||||||
for (let i = 0; i < childNodes.length; i++) {
|
for (let i = 0; i < childNodes.length; i++) {
|
||||||
// If the node is 1/3 down the page
|
// If the node is 1/3 down the page
|
||||||
if (childNodes[i].offsetTop > (this.refs.postlist.scrollTop + (this.refs.postlist.offsetHeight / 3))) {
|
if (childNodes[i].offsetTop > (this.refs.postlist.scrollTop + (this.refs.postlist.offsetHeight / Constants.SCROLL_PAGE_FRACTION))) {
|
||||||
this.jumpToPostNode = childNodes[i];
|
this.jumpToPostNode = childNodes[i];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -114,7 +80,7 @@ export default class PostsView extends React.Component {
|
|||||||
|
|
||||||
// --- --------
|
// --- --------
|
||||||
|
|
||||||
this.props.postViewScrolled(this.isAtBottom());
|
this.props.postListScrolled(this.isAtBottom());
|
||||||
this.prevScrollHeight = this.refs.postlist.scrollHeight;
|
this.prevScrollHeight = this.refs.postlist.scrollHeight;
|
||||||
this.prevOffsetTop = this.jumpToPostNode.offsetTop;
|
this.prevOffsetTop = this.jumpToPostNode.offsetTop;
|
||||||
|
|
||||||
@@ -126,16 +92,18 @@ export default class PostsView extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scrollStopAction.fireAfter(2000);
|
this.scrollStopAction.fireAfter(Constants.SCROLL_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScrollStop() {
|
handleScrollStop() {
|
||||||
this.setState({
|
this.setState({
|
||||||
isScrolling: false
|
isScrolling: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFloatingTimestamp() {
|
updateFloatingTimestamp() {
|
||||||
// skip this in non-mobile view since that's when the timestamp is visible
|
// skip this in non-mobile view since that's when the timestamp is visible
|
||||||
if ($(window).width() > 768) {
|
if (!Utils.isMobile()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +111,7 @@ export default class PostsView extends React.Component {
|
|||||||
// iterate through posts starting at the bottom since users are more likely to be viewing newer posts
|
// iterate through posts starting at the bottom since users are more likely to be viewing newer posts
|
||||||
for (let i = 0; i < this.props.postList.order.length; i++) {
|
for (let i = 0; i < this.props.postList.order.length; i++) {
|
||||||
const id = this.props.postList.order[i];
|
const id = this.props.postList.order[i];
|
||||||
const element = ReactDOM.findDOMNode(this.refs[id]);
|
const element = this.refs[id];
|
||||||
|
|
||||||
if (!element || element.offsetTop + element.clientHeight <= this.refs.postlist.scrollTop) {
|
if (!element || element.offsetTop + element.clientHeight <= this.refs.postlist.scrollTop) {
|
||||||
// this post is off the top of the screen so the last one is at the top of the screen
|
// this post is off the top of the screen so the last one is at the top of the screen
|
||||||
@@ -167,17 +135,20 @@ export default class PostsView extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadMorePostsTop() {
|
loadMorePostsTop() {
|
||||||
this.props.loadMorePostsTopClicked();
|
GlobalActions.emitLoadMorePostsEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
loadMorePostsBottom() {
|
loadMorePostsBottom() {
|
||||||
this.props.loadMorePostsBottomClicked();
|
GlobalActions.emitLoadMorePostsFocusedBottomEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
createPosts(posts, order) {
|
createPosts(posts, order) {
|
||||||
const postCtls = [];
|
const postCtls = [];
|
||||||
let previousPostDay = new Date(0);
|
let previousPostDay = new Date(0);
|
||||||
const userId = this.state.currentUser.id;
|
const userId = this.props.currentUser.id;
|
||||||
const profiles = this.state.profiles || {};
|
const profiles = this.props.profiles || {};
|
||||||
|
|
||||||
let renderedLastViewed = false;
|
let renderedLastViewed = false;
|
||||||
|
|
||||||
@@ -185,7 +156,7 @@ export default class PostsView extends React.Component {
|
|||||||
const post = posts[order[i]];
|
const post = posts[order[i]];
|
||||||
const parentPost = posts[post.parent_id];
|
const parentPost = posts[post.parent_id];
|
||||||
const prevPost = posts[order[i + 1]];
|
const prevPost = posts[order[i + 1]];
|
||||||
const postUserId = Utils.isSystemMessage(post) ? '' : post.user_id;
|
const postUserId = PostUtils.isSystemMessage(post) ? '' : post.user_id;
|
||||||
|
|
||||||
// If the post is a comment whose parent has been deleted, don't add it to the list.
|
// If the post is a comment whose parent has been deleted, don't add it to the list.
|
||||||
if (parentPost && parentPost.state === Constants.POST_DELETED) {
|
if (parentPost && parentPost.state === Constants.POST_DELETED) {
|
||||||
@@ -197,11 +168,11 @@ export default class PostsView extends React.Component {
|
|||||||
let hideProfilePic = false;
|
let hideProfilePic = false;
|
||||||
|
|
||||||
if (prevPost) {
|
if (prevPost) {
|
||||||
const postIsComment = Utils.isComment(post);
|
const postIsComment = PostUtils.isComment(post);
|
||||||
const prevPostIsComment = Utils.isComment(prevPost);
|
const prevPostIsComment = PostUtils.isComment(prevPost);
|
||||||
const postFromWebhook = Boolean(post.props && post.props.from_webhook);
|
const postFromWebhook = Boolean(post.props && post.props.from_webhook);
|
||||||
const prevPostFromWebhook = Boolean(prevPost.props && prevPost.props.from_webhook);
|
const prevPostFromWebhook = Boolean(prevPost.props && prevPost.props.from_webhook);
|
||||||
const prevPostUserId = Utils.isSystemMessage(prevPost) ? '' : prevPost.user_id;
|
const prevPostUserId = PostUtils.isSystemMessage(prevPost) ? '' : prevPost.user_id;
|
||||||
|
|
||||||
// consider posts from the same user if:
|
// consider posts from the same user if:
|
||||||
// the previous post was made by the same user as the current post,
|
// the previous post was made by the same user as the current post,
|
||||||
@@ -209,7 +180,7 @@ export default class PostsView extends React.Component {
|
|||||||
// the current post is not from a webhook
|
// the current post is not from a webhook
|
||||||
// the previous post is not from a webhook
|
// the previous post is not from a webhook
|
||||||
if (prevPostUserId === postUserId &&
|
if (prevPostUserId === postUserId &&
|
||||||
post.create_at - prevPost.create_at <= 1000 * 60 * 5 &&
|
post.create_at - prevPost.create_at <= Constants.POST_COLLAPSE_TIMEOUT &&
|
||||||
!postFromWebhook && !prevPostFromWebhook) {
|
!postFromWebhook && !prevPostFromWebhook) {
|
||||||
sameUser = true;
|
sameUser = true;
|
||||||
}
|
}
|
||||||
@@ -246,7 +217,7 @@ export default class PostsView extends React.Component {
|
|||||||
|
|
||||||
// check if it's the last comment in a consecutive string of comments on the same post
|
// check if it's the last comment in a consecutive string of comments on the same post
|
||||||
// it is the last comment if it is last post in the channel or the next post has a different root post
|
// it is the last comment if it is last post in the channel or the next post has a different root post
|
||||||
const isLastComment = Utils.isComment(post) && (i === 0 || posts[order[i - 1]].root_id !== post.root_id);
|
const isLastComment = PostUtils.isComment(post) && (i === 0 || posts[order[i - 1]].root_id !== post.root_id);
|
||||||
|
|
||||||
const keyPrefix = post.id ? post.id : i;
|
const keyPrefix = post.id ? post.id : i;
|
||||||
|
|
||||||
@@ -254,11 +225,24 @@ export default class PostsView extends React.Component {
|
|||||||
|
|
||||||
let profile;
|
let profile;
|
||||||
if (userId === post.user_id) {
|
if (userId === post.user_id) {
|
||||||
profile = this.state.currentUser;
|
profile = this.props.currentUser;
|
||||||
} else {
|
} else {
|
||||||
profile = profiles[post.user_id];
|
profile = profiles[post.user_id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let commentCount = 0;
|
||||||
|
let commentRootId;
|
||||||
|
if (parentPost) {
|
||||||
|
commentRootId = post.root_id;
|
||||||
|
} else {
|
||||||
|
commentRootId = post.id;
|
||||||
|
}
|
||||||
|
for (const postId in posts) {
|
||||||
|
if (posts[postId].root_id === commentRootId) {
|
||||||
|
commentCount += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const postCtl = (
|
const postCtl = (
|
||||||
<Post
|
<Post
|
||||||
key={keyPrefix + 'postKey'}
|
key={keyPrefix + 'postKey'}
|
||||||
@@ -267,16 +251,15 @@ export default class PostsView extends React.Component {
|
|||||||
sameRoot={sameRoot}
|
sameRoot={sameRoot}
|
||||||
post={post}
|
post={post}
|
||||||
parentPost={parentPost}
|
parentPost={parentPost}
|
||||||
posts={posts}
|
|
||||||
hideProfilePic={hideProfilePic}
|
hideProfilePic={hideProfilePic}
|
||||||
isLastComment={isLastComment}
|
isLastComment={isLastComment}
|
||||||
shouldHighlight={shouldHighlight}
|
shouldHighlight={shouldHighlight}
|
||||||
onClick={() => GlobalActions.emitPostFocusEvent(post.id)} //eslint-disable-line no-loop-func
|
displayNameType={this.props.displayNameType}
|
||||||
displayNameType={this.state.displayNameType}
|
|
||||||
user={profile}
|
user={profile}
|
||||||
currentUser={this.state.currentUser}
|
currentUser={this.props.currentUser}
|
||||||
center={this.state.centerPosts}
|
center={this.props.displayPostsInCenter}
|
||||||
compactDisplay={this.state.compactPosts}
|
commentCount={commentCount}
|
||||||
|
compactDisplay={this.props.compactDisplay}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -302,8 +285,8 @@ export default class PostsView extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (postUserId !== userId &&
|
if (postUserId !== userId &&
|
||||||
this.props.messageSeparatorTime !== 0 &&
|
this.props.lastViewed !== 0 &&
|
||||||
post.create_at > this.props.messageSeparatorTime &&
|
post.create_at > this.props.lastViewed &&
|
||||||
!renderedLastViewed) {
|
!renderedLastViewed) {
|
||||||
renderedLastViewed = true;
|
renderedLastViewed = true;
|
||||||
|
|
||||||
@@ -337,10 +320,11 @@ export default class PostsView extends React.Component {
|
|||||||
|
|
||||||
return postCtls;
|
return postCtls;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateScrolling() {
|
updateScrolling() {
|
||||||
if (this.props.scrollType === PostsView.SCROLL_TYPE_BOTTOM) {
|
if (this.props.scrollType === ScrollTypes.BOTTOM) {
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
} else if (this.props.scrollType === PostsView.SCROLL_TYPE_NEW_MESSAGE) {
|
} else if (this.props.scrollType === ScrollTypes.NEW_MESSAGE) {
|
||||||
window.setTimeout(window.requestAnimationFrame(() => {
|
window.setTimeout(window.requestAnimationFrame(() => {
|
||||||
// If separator exists scroll to it. Otherwise scroll to bottom.
|
// If separator exists scroll to it. Otherwise scroll to bottom.
|
||||||
if (this.refs.newMessageSeparator) {
|
if (this.refs.newMessageSeparator) {
|
||||||
@@ -350,7 +334,7 @@ export default class PostsView extends React.Component {
|
|||||||
this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
|
this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
|
||||||
}
|
}
|
||||||
}), 0);
|
}), 0);
|
||||||
} else if (this.props.scrollType === PostsView.SCROLL_TYPE_POST && this.props.scrollPostId) {
|
} else if (this.props.scrollType === ScrollTypes.POST && this.props.scrollPostId) {
|
||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
const postNode = ReactDOM.findDOMNode(this.refs[this.props.scrollPostId]);
|
const postNode = ReactDOM.findDOMNode(this.refs[this.props.scrollPostId]);
|
||||||
if (postNode == null) {
|
if (postNode == null) {
|
||||||
@@ -358,12 +342,12 @@ export default class PostsView extends React.Component {
|
|||||||
}
|
}
|
||||||
postNode.scrollIntoView();
|
postNode.scrollIntoView();
|
||||||
if (this.refs.postlist.scrollTop === postNode.offsetTop) {
|
if (this.refs.postlist.scrollTop === postNode.offsetTop) {
|
||||||
this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / 3);
|
this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / Constants.SCROLL_PAGE_FRACTION);
|
||||||
} else {
|
} else {
|
||||||
this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / 3) + (this.refs.postlist.scrollTop - postNode.offsetTop);
|
this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / Constants.SCROLL_PAGE_FRACTION) + (this.refs.postlist.scrollTop - postNode.offsetTop);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (this.props.scrollType === PostsView.SCROLL_TYPE_SIDEBAR_OPEN) {
|
} else if (this.props.scrollType === ScrollTypes.SIDEBAR_OPEN) {
|
||||||
// If we are at the bottom then stay there
|
// If we are at the bottom then stay there
|
||||||
if (this.wasAtBottom) {
|
if (this.wasAtBottom) {
|
||||||
this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
|
this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
|
||||||
@@ -371,9 +355,9 @@ export default class PostsView extends React.Component {
|
|||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
this.jumpToPostNode.scrollIntoView();
|
this.jumpToPostNode.scrollIntoView();
|
||||||
if (this.refs.postlist.scrollTop === this.jumpToPostNode.offsetTop) {
|
if (this.refs.postlist.scrollTop === this.jumpToPostNode.offsetTop) {
|
||||||
this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / 3);
|
this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / Constants.SCROLL_PAGE_FRACTION);
|
||||||
} else {
|
} else {
|
||||||
this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / 3) + (this.refs.postlist.scrollTop - this.jumpToPostNode.offsetTop);
|
this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / Constants.SCROLL_PAGE_FRACTION) + (this.refs.postlist.scrollTop - this.jumpToPostNode.offsetTop);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -386,14 +370,17 @@ export default class PostsView extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResize() {
|
handleResize() {
|
||||||
this.updateScrolling();
|
this.updateScrolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToBottom() {
|
scrollToBottom() {
|
||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
|
this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToBottomAnimated() {
|
scrollToBottomAnimated() {
|
||||||
var postList = $(this.refs.postlist);
|
var postList = $(this.refs.postlist);
|
||||||
postList.animate({scrollTop: this.refs.postlist.scrollHeight}, '500');
|
postList.animate({scrollTop: this.refs.postlist.scrollHeight}, '500');
|
||||||
@@ -417,145 +404,76 @@ export default class PostsView extends React.Component {
|
|||||||
this.updateScrolling();
|
this.updateScrolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.isActive) {
|
|
||||||
PreferenceStore.addChangeListener(this.updateState);
|
|
||||||
UserStore.addChangeListener(this.onUserChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.channel) {
|
|
||||||
this.introText = createChannelIntroMessage(this.props.channel);
|
|
||||||
} else {
|
|
||||||
this.introText = this.getArchivesIntroMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('resize', this.handleResize);
|
window.addEventListener('resize', this.handleResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.handleResize);
|
||||||
this.scrollStopAction.cancel();
|
this.scrollStopAction.cancel();
|
||||||
PreferenceStore.removeChangeListener(this.updateState);
|
|
||||||
UserStore.removeChangeListener(this.onUserChange);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if (this.props.postList != null) {
|
if (this.props.postList != null) {
|
||||||
this.updateScrolling();
|
this.updateScrolling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
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) {
|
|
||||||
if (this.props.isActive !== nextProps.isActive) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (this.props.postList !== nextProps.postList) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (this.props.scrollPostId !== nextProps.scrollPostId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (this.props.scrollType !== nextProps.scrollType && nextProps.scrollType !== PostsView.SCROLL_TYPE_FREE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (this.props.messageSeparatorTime !== nextProps.messageSeparatorTime) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!Utils.areObjectsEqual(this.props.postList, nextProps.postList)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextState.displayNameType !== this.state.displayNameType) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (this.state.topPostId !== nextState.topPostId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (this.state.isScrolling !== nextState.isScrolling) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (this.state.centerPosts !== nextState.centerPosts) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (this.state.compactPosts !== nextState.compactPosts) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!Utils.areObjectsEqual(this.state.profiles, nextState.profiles)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
render() {
|
render() {
|
||||||
let posts = [];
|
if (this.props.postList == null) {
|
||||||
let order = [];
|
return <div/>;
|
||||||
let moreMessagesTop;
|
|
||||||
let moreMessagesBottom;
|
|
||||||
let postElements;
|
|
||||||
let activeClass = 'inactive';
|
|
||||||
if (this.props.postList != null) {
|
|
||||||
posts = this.props.postList.posts;
|
|
||||||
order = this.props.postList.order;
|
|
||||||
|
|
||||||
// Create intro message or top loadmore link
|
|
||||||
if (this.props.showMoreMessagesTop) {
|
|
||||||
moreMessagesTop = (
|
|
||||||
<a
|
|
||||||
ref='loadmoretop'
|
|
||||||
className='more-messages-text theme'
|
|
||||||
href='#'
|
|
||||||
onClick={this.loadMorePostsTop}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='posts_view.loadMore'
|
|
||||||
defaultMessage='Load more messages'
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
moreMessagesTop = this.introText;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give option to load more posts at bottom if nessisary
|
|
||||||
if (this.props.showMoreMessagesBottom) {
|
|
||||||
moreMessagesBottom = (
|
|
||||||
<a
|
|
||||||
ref='loadmorebottom'
|
|
||||||
className='more-messages-text theme'
|
|
||||||
href='#'
|
|
||||||
onClick={this.loadMorePostsBottom}
|
|
||||||
>
|
|
||||||
<FormattedMessage id='posts_view.loadMore'/>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
moreMessagesBottom = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create post elements
|
|
||||||
postElements = this.createPosts(posts, order);
|
|
||||||
|
|
||||||
// Show ourselves if we are marked active
|
|
||||||
if (this.props.isActive) {
|
|
||||||
activeClass = '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const posts = this.props.postList.posts;
|
||||||
|
const order = this.props.postList.order;
|
||||||
|
|
||||||
|
// Create intro message or top loadmore link
|
||||||
|
let moreMessagesTop;
|
||||||
|
if (this.props.showMoreMessagesTop) {
|
||||||
|
moreMessagesTop = (
|
||||||
|
<a
|
||||||
|
ref='loadmoretop'
|
||||||
|
className='more-messages-text theme'
|
||||||
|
href='#'
|
||||||
|
onClick={this.loadMorePostsTop}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='posts_view.loadMore'
|
||||||
|
defaultMessage='Load more messages'
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
moreMessagesTop = this.introText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give option to load more posts at bottom if necessary
|
||||||
|
let moreMessagesBottom;
|
||||||
|
if (this.props.showMoreMessagesBottom) {
|
||||||
|
moreMessagesBottom = (
|
||||||
|
<a
|
||||||
|
ref='loadmorebottom'
|
||||||
|
className='more-messages-text theme'
|
||||||
|
href='#'
|
||||||
|
onClick={this.loadMorePostsBottom}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='posts_view.loadMore'/>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create post elements
|
||||||
|
const postElements = this.createPosts(posts, order);
|
||||||
|
|
||||||
let topPostCreateAt = 0;
|
let topPostCreateAt = 0;
|
||||||
if (this.state.topPostId && this.props.postList.posts[this.state.topPostId]) {
|
if (this.state.topPostId && this.props.postList.posts[this.state.topPostId]) {
|
||||||
topPostCreateAt = this.props.postList.posts[this.state.topPostId].create_at;
|
topPostCreateAt = this.props.postList.posts[this.state.topPostId].create_at;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={activeClass}>
|
<div>
|
||||||
<FloatingTimestamp
|
<FloatingTimestamp
|
||||||
isScrolling={this.state.isScrolling}
|
isScrolling={this.state.isScrolling}
|
||||||
isMobile={$(window).width() > 768}
|
isMobile={Utils.isMobile()}
|
||||||
createAt={topPostCreateAt}
|
createAt={topPostCreateAt}
|
||||||
/>
|
/>
|
||||||
<ScrollToBottomArrows
|
<ScrollToBottomArrows
|
||||||
@@ -583,48 +501,24 @@ export default class PostsView extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PostsView.defaultProps = {
|
|
||||||
|
PostList.defaultProps = {
|
||||||
|
lastViewed: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
PostsView.propTypes = {
|
PostList.propTypes = {
|
||||||
isActive: React.PropTypes.bool,
|
|
||||||
postList: React.PropTypes.object,
|
postList: React.PropTypes.object,
|
||||||
|
profiles: React.PropTypes.object,
|
||||||
|
channel: React.PropTypes.object,
|
||||||
|
currentUser: React.PropTypes.object,
|
||||||
scrollPostId: React.PropTypes.string,
|
scrollPostId: React.PropTypes.string,
|
||||||
scrollType: React.PropTypes.number,
|
scrollType: React.PropTypes.number,
|
||||||
postViewScrolled: React.PropTypes.func.isRequired,
|
postListScrolled: React.PropTypes.func.isRequired,
|
||||||
loadMorePostsTopClicked: React.PropTypes.func.isRequired,
|
|
||||||
loadMorePostsBottomClicked: React.PropTypes.func.isRequired,
|
|
||||||
showMoreMessagesTop: React.PropTypes.bool,
|
showMoreMessagesTop: React.PropTypes.bool,
|
||||||
showMoreMessagesBottom: React.PropTypes.bool,
|
showMoreMessagesBottom: React.PropTypes.bool,
|
||||||
channel: React.PropTypes.object,
|
lastViewed: React.PropTypes.number,
|
||||||
messageSeparatorTime: React.PropTypes.number,
|
|
||||||
postsToHighlight: React.PropTypes.object,
|
postsToHighlight: React.PropTypes.object,
|
||||||
|
displayNameType: React.PropTypes.string,
|
||||||
|
displayPostsInCenter: React.PropTypes.bool,
|
||||||
compactDisplay: React.PropTypes.bool
|
compactDisplay: React.PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
function ScrollToBottomArrows({isScrolling, atBottom, onClick}) {
|
|
||||||
// only show on mobile
|
|
||||||
if ($(window).width() > 768) {
|
|
||||||
return <noscript/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
let className = 'post-list__arrows';
|
|
||||||
if (isScrolling && !atBottom) {
|
|
||||||
className += ' scrolling';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={className}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
<span dangerouslySetInnerHTML={{__html: Constants.SCROLL_BOTTOM_ICON}}/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollToBottomArrows.propTypes = {
|
|
||||||
isScrolling: React.PropTypes.bool.isRequired,
|
|
||||||
atBottom: React.PropTypes.bool.isRequired,
|
|
||||||
onClick: React.PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
import Constants from 'utils/constants.jsx';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default class ScrollToBottomArrows extends React.Component {
|
||||||
|
render() {
|
||||||
|
// only show on mobile
|
||||||
|
if ($(window).width() > 768) {
|
||||||
|
return <noscript/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let className = 'post-list__arrows';
|
||||||
|
if (this.props.isScrolling && !this.props.atBottom) {
|
||||||
|
className += ' scrolling';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={className}
|
||||||
|
onClick={this.props.onClick}
|
||||||
|
>
|
||||||
|
<span dangerouslySetInnerHTML={{__html: Constants.SCROLL_BOTTOM_ICON}}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollToBottomArrows.propTypes = {
|
||||||
|
isScrolling: React.PropTypes.bool.isRequired,
|
||||||
|
atBottom: React.PropTypes.bool.isRequired,
|
||||||
|
onClick: React.PropTypes.func.isRequired
|
||||||
|
};
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
// 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 PostsView from './posts_view.jsx';
|
import PostList from './components/post_list.jsx';
|
||||||
|
import LoadingScreen from 'components/loading_screen.jsx';
|
||||||
|
|
||||||
import PostStore from 'stores/post_store.jsx';
|
import PostStore from 'stores/post_store.jsx';
|
||||||
|
import UserStore from 'stores/user_store.jsx';
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
|
||||||
|
import Constants from 'utils/constants.jsx';
|
||||||
|
const ScrollTypes = Constants.ScrollTypes;
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
@@ -15,17 +19,24 @@ 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.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
|
this.onUserChange = this.onUserChange.bind(this);
|
||||||
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
|
this.onPostListScroll = this.onPostListScroll.bind(this);
|
||||||
this.loadMorePostsBottom = this.loadMorePostsBottom.bind(this);
|
|
||||||
|
|
||||||
const focusedPostId = PostStore.getFocusedPostId();
|
const focusedPostId = PostStore.getFocusedPostId();
|
||||||
|
|
||||||
|
const channel = ChannelStore.getCurrent();
|
||||||
|
let profiles = UserStore.getProfiles();
|
||||||
|
if (channel && channel.type === Constants.DM_CHANNEL) {
|
||||||
|
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
scrollType: PostsView.SCROLL_TYPE_POST,
|
postList: PostStore.getVisiblePosts(focusedPostId),
|
||||||
|
currentUser: UserStore.getCurrentUser(),
|
||||||
|
profiles,
|
||||||
|
scrollType: ScrollTypes.POST,
|
||||||
currentChannel: ChannelStore.getCurrentId().slice(),
|
currentChannel: ChannelStore.getCurrentId().slice(),
|
||||||
scrollPostId: focusedPostId,
|
scrollPostId: focusedPostId,
|
||||||
postList: PostStore.getVisiblePosts(focusedPostId),
|
|
||||||
atTop: PostStore.getVisibilityAtTop(focusedPostId),
|
atTop: PostStore.getVisibilityAtTop(focusedPostId),
|
||||||
atBottom: PostStore.getVisibilityAtBottom(focusedPostId)
|
atBottom: PostStore.getVisibilityAtBottom(focusedPostId)
|
||||||
};
|
};
|
||||||
@@ -34,11 +45,13 @@ export default class PostFocusView extends React.Component {
|
|||||||
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() {
|
||||||
@@ -46,7 +59,7 @@ export default class PostFocusView extends React.Component {
|
|||||||
if (this.state.currentChannel !== currentChannel) {
|
if (this.state.currentChannel !== currentChannel) {
|
||||||
this.setState({
|
this.setState({
|
||||||
currentChannel: currentChannel.slice(),
|
currentChannel: currentChannel.slice(),
|
||||||
scrollType: PostsView.SCROLL_TYPE_POST
|
scrollType: ScrollTypes.POST
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,42 +78,50 @@ export default class PostFocusView extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePostsViewScroll() {
|
onUserChange() {
|
||||||
this.setState({scrollType: PostsView.SCROLL_TYPE_FREE});
|
const channel = ChannelStore.getCurrent();
|
||||||
|
let profiles = UserStore.getProfiles();
|
||||||
|
if (channel && channel.type === Constants.DM_CHANNEL) {
|
||||||
|
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
|
||||||
|
}
|
||||||
|
this.setState({currentUser: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(profiles))});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadMorePostsTop() {
|
onPostListScroll() {
|
||||||
GlobalActions.emitLoadMorePostsFocusedTopEvent();
|
this.setState({scrollType: ScrollTypes.FREE});
|
||||||
}
|
|
||||||
|
|
||||||
loadMorePostsBottom() {
|
|
||||||
GlobalActions.emitLoadMorePostsFocusedBottomEvent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const postsToHighlight = {};
|
const postsToHighlight = {};
|
||||||
postsToHighlight[this.state.scrollPostId] = true;
|
postsToHighlight[this.state.scrollPostId] = true;
|
||||||
|
|
||||||
if (!this.state.postList) {
|
let content;
|
||||||
return null;
|
if (this.state.postList == null) {
|
||||||
|
content = (
|
||||||
|
<LoadingScreen
|
||||||
|
position='absolute'
|
||||||
|
key='loading'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
content = (
|
||||||
|
<PostList
|
||||||
|
postList={this.state.postList}
|
||||||
|
currentUser={this.state.currentUser}
|
||||||
|
profiles={this.state.profiles}
|
||||||
|
scrollType={this.state.scrollType}
|
||||||
|
scrollPostId={this.state.scrollPostId}
|
||||||
|
postListScrolled={this.onPostListScroll}
|
||||||
|
showMoreMessagesTop={!this.state.atTop}
|
||||||
|
showMoreMessagesBottom={!this.state.atBottom}
|
||||||
|
postsToHighlight={postsToHighlight}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id='post-list'>
|
<div id='post-list'>
|
||||||
<PostsView
|
{content}
|
||||||
key={'postfocusview'}
|
|
||||||
isActive={true}
|
|
||||||
postList={this.state.postList}
|
|
||||||
scrollType={this.state.scrollType}
|
|
||||||
scrollPostId={this.state.scrollPostId}
|
|
||||||
postViewScrolled={this.handlePostsViewScroll}
|
|
||||||
loadMorePostsTopClicked={this.loadMorePostsTop}
|
|
||||||
loadMorePostsBottomClicked={this.loadMorePostsBottom}
|
|
||||||
showMoreMessagesTop={!this.state.atTop}
|
|
||||||
showMoreMessagesBottom={!this.state.atBottom}
|
|
||||||
messageSeparatorTime={0}
|
|
||||||
postsToHighlight={postsToHighlight}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
85
webapp/components/post_view/post_view_cache.jsx
Обычный файл
85
webapp/components/post_view/post_view_cache.jsx
Обычный файл
@@ -0,0 +1,85 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information
|
||||||
|
|
||||||
|
import PostViewController from './post_view_controller.jsx';
|
||||||
|
|
||||||
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const MAXIMUM_CACHED_VIEWS = 5;
|
||||||
|
|
||||||
|
export default class PostViewCache extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.onChannelChange = this.onChannelChange.bind(this);
|
||||||
|
|
||||||
|
const channel = ChannelStore.getCurrent();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
currentChannelId: channel.id,
|
||||||
|
channels: [channel]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
ChannelStore.addChangeListener(this.onChannelChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
ChannelStore.removeChangeListener(this.onChannelChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChannelChange() {
|
||||||
|
const channels = Object.assign([], this.state.channels);
|
||||||
|
const currentChannel = ChannelStore.getCurrent();
|
||||||
|
|
||||||
|
if (currentChannel == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure current channel really changed
|
||||||
|
if (currentChannel.id === this.state.currentChannelId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channels.length > MAXIMUM_CACHED_VIEWS) {
|
||||||
|
channels.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = channels.map((c) => c.id).indexOf(currentChannel.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
channels.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
channels.push(currentChannel);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
currentChannelId: currentChannel.id,
|
||||||
|
channels
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const channels = this.state.channels;
|
||||||
|
const currentChannelId = this.state.currentChannelId;
|
||||||
|
|
||||||
|
let postViews = [];
|
||||||
|
for (let i = 0; i < channels.length; i++) {
|
||||||
|
postViews.push(
|
||||||
|
<PostViewController
|
||||||
|
key={'postviewcontroller_' + channels[i].id}
|
||||||
|
channel={channels[i]}
|
||||||
|
active={channels[i].id === currentChannelId}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id='post-list'>
|
||||||
|
{postViews}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
264
webapp/components/post_view/post_view_controller.jsx
Обычный файл
264
webapp/components/post_view/post_view_controller.jsx
Обычный файл
@@ -0,0 +1,264 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import PostList from './components/post_list.jsx';
|
||||||
|
import LoadingScreen from 'components/loading_screen.jsx';
|
||||||
|
|
||||||
|
import PreferenceStore from 'stores/preference_store.jsx';
|
||||||
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
import PostStore from 'stores/post_store.jsx';
|
||||||
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
|
|
||||||
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
|
import Constants from 'utils/constants.jsx';
|
||||||
|
const Preferences = Constants.Preferences;
|
||||||
|
const ScrollTypes = Constants.ScrollTypes;
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default class PostViewController extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.onPreferenceChange = this.onPreferenceChange.bind(this);
|
||||||
|
this.onUserChange = this.onUserChange.bind(this);
|
||||||
|
this.onPostsChange = this.onPostsChange.bind(this);
|
||||||
|
this.onPostsViewJumpRequest = this.onPostsViewJumpRequest.bind(this);
|
||||||
|
this.onPostListScroll = this.onPostListScroll.bind(this);
|
||||||
|
this.onActivate = this.onActivate.bind(this);
|
||||||
|
this.onDeactivate = this.onDeactivate.bind(this);
|
||||||
|
|
||||||
|
const channel = props.channel;
|
||||||
|
let profiles = UserStore.getProfiles();
|
||||||
|
if (channel && channel.type === Constants.DM_CHANNEL) {
|
||||||
|
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastViewed = Number.MAX_VALUE;
|
||||||
|
const member = ChannelStore.getMember(channel.id);
|
||||||
|
if (member != null) {
|
||||||
|
lastViewed = member.last_viewed_at;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
channel,
|
||||||
|
postList: PostStore.getVisiblePosts(channel.id),
|
||||||
|
currentUser: UserStore.getCurrentUser(),
|
||||||
|
profiles,
|
||||||
|
atTop: PostStore.getVisibilityAtTop(channel.id),
|
||||||
|
lastViewed,
|
||||||
|
scrollType: ScrollTypes.BOTTOM,
|
||||||
|
displayNameType: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false'),
|
||||||
|
displayPostsInCenter: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
|
||||||
|
compactDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.props.active) {
|
||||||
|
this.onActivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.props.active) {
|
||||||
|
this.onDeactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPreferenceChange() {
|
||||||
|
this.setState({
|
||||||
|
displayNameType: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false'),
|
||||||
|
displayPostsInCenter: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
|
||||||
|
compactDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onUserChange() {
|
||||||
|
const channel = this.state.channel;
|
||||||
|
let profiles = UserStore.getProfiles();
|
||||||
|
if (channel && channel.type === Constants.DM_CHANNEL) {
|
||||||
|
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
|
||||||
|
}
|
||||||
|
this.setState({currentUser: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(profiles))});
|
||||||
|
}
|
||||||
|
|
||||||
|
onPostsChange() {
|
||||||
|
this.setState({
|
||||||
|
postList: JSON.parse(JSON.stringify(PostStore.getVisiblePosts(this.state.channel.id))),
|
||||||
|
atTop: PostStore.getVisibilityAtTop(this.state.channel.id)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onActivate() {
|
||||||
|
PreferenceStore.addChangeListener(this.onPreferenceChange);
|
||||||
|
UserStore.addChangeListener(this.onUserChange);
|
||||||
|
PostStore.addChangeListener(this.onPostsChange);
|
||||||
|
PostStore.addPostsViewJumpListener(this.onPostsViewJumpRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeactivate() {
|
||||||
|
PreferenceStore.removeChangeListener(this.onPreferenceChange);
|
||||||
|
UserStore.removeChangeListener(this.onUserChange);
|
||||||
|
PostStore.removeChangeListener(this.onPostsChange);
|
||||||
|
PostStore.removePostsViewJumpListener(this.onPostsViewJumpRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (this.props.active && !nextProps.active) {
|
||||||
|
this.onDeactivate();
|
||||||
|
} else if (!this.props.active && nextProps.active) {
|
||||||
|
this.onActivate();
|
||||||
|
|
||||||
|
const channel = nextProps.channel;
|
||||||
|
|
||||||
|
let lastViewed = Number.MAX_VALUE;
|
||||||
|
const member = ChannelStore.getMember(channel.id);
|
||||||
|
if (member != null) {
|
||||||
|
lastViewed = member.last_viewed_at;
|
||||||
|
}
|
||||||
|
|
||||||
|
let profiles = UserStore.getProfiles();
|
||||||
|
if (channel && channel.type === Constants.DM_CHANNEL) {
|
||||||
|
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
channel,
|
||||||
|
lastViewed,
|
||||||
|
profiles: JSON.parse(JSON.stringify(profiles)),
|
||||||
|
postList: JSON.parse(JSON.stringify(PostStore.getVisiblePosts(channel.id))),
|
||||||
|
displayNameType: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false'),
|
||||||
|
displayPostsInCenter: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
|
||||||
|
compactDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT,
|
||||||
|
scrollType: ScrollTypes.BOTTOM
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPostsViewJumpRequest(type, postId) {
|
||||||
|
switch (type) {
|
||||||
|
case Constants.PostsViewJumpTypes.BOTTOM:
|
||||||
|
this.setState({scrollType: ScrollTypes.BOTTOM});
|
||||||
|
break;
|
||||||
|
case Constants.PostsViewJumpTypes.POST:
|
||||||
|
this.setState({
|
||||||
|
scrollType: ScrollTypes.POST,
|
||||||
|
scrollPostId: postId
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case Constants.PostsViewJumpTypes.SIDEBAR_OPEN:
|
||||||
|
this.setState({scrollType: ScrollTypes.SIDEBAR_OPEN});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPostListScroll(atBottom) {
|
||||||
|
if (atBottom) {
|
||||||
|
this.setState({scrollType: ScrollTypes.BOTTOM});
|
||||||
|
} else {
|
||||||
|
this.setState({scrollType: ScrollTypes.FREE});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
|
if (nextProps.active !== this.props.active) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextState.atTop !== this.state.atTop) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextState.displayNameType !== this.state.displayNameType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextState.displayPostsInCenter !== this.state.displayPostsInCenter) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextState.compactDisplay !== this.state.compactDisplay) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextState.lastViewed !== this.state.lastViewed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextState.showMoreMessagesTop !== this.state.showMoreMessagesTop) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextState.scrollType !== this.state.scrollType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextState.scrollPostId !== this.state.scrollPostId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextProps.channel.id !== this.props.channel.id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Utils.areObjectsEqual(nextState.currentUser, this.state.currentUser)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Utils.areObjectsEqual(nextState.postList, this.state.postList)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Utils.areObjectsEqual(nextState.profiles, this.state.profiles)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let content;
|
||||||
|
if (this.state.postList == null) {
|
||||||
|
content = (
|
||||||
|
<LoadingScreen
|
||||||
|
position='absolute'
|
||||||
|
key='loading'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
content = (
|
||||||
|
<PostList
|
||||||
|
postList={this.state.postList}
|
||||||
|
profiles={this.state.profiles}
|
||||||
|
channel={this.state.channel}
|
||||||
|
currentUser={this.state.currentUser}
|
||||||
|
showMoreMessagesTop={!this.state.atTop}
|
||||||
|
scrollType={this.state.scrollType}
|
||||||
|
scrollPostId={this.state.scrollPostId}
|
||||||
|
postListScrolled={this.onPostListScroll}
|
||||||
|
displayNameType={this.state.displayNameType}
|
||||||
|
displayPostsInCenter={this.state.displayPostsInCenter}
|
||||||
|
compactDisplay={this.state.compactDisplay}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let activeClass = '';
|
||||||
|
if (!this.props.active) {
|
||||||
|
activeClass = 'inactive';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={activeClass}>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PostViewController.propTypes = {
|
||||||
|
channel: React.PropTypes.object,
|
||||||
|
active: React.PropTypes.bool
|
||||||
|
};
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
// 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 * as GlobalActions from 'actions/global_actions.jsx';
|
|
||||||
|
|
||||||
import Constants from 'utils/constants.jsx';
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const MAXIMUM_CACHED_VIEWS = 3;
|
|
||||||
|
|
||||||
export default class PostsViewContainer extends React.Component {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.onChannelChange = this.onChannelChange.bind(this);
|
|
||||||
this.onChannelLeave = this.onChannelLeave.bind(this);
|
|
||||||
this.onPostsChange = this.onPostsChange.bind(this);
|
|
||||||
this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
|
|
||||||
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
|
|
||||||
this.handlePostsViewJumpRequest = this.handlePostsViewJumpRequest.bind(this);
|
|
||||||
|
|
||||||
const currentChannelId = ChannelStore.getCurrentId();
|
|
||||||
const state = {
|
|
||||||
scrollType: PostsView.SCROLL_TYPE_BOTTOM,
|
|
||||||
scrollPost: null
|
|
||||||
};
|
|
||||||
if (currentChannelId) {
|
|
||||||
let lastViewed = Date.now();
|
|
||||||
const member = ChannelStore.getMember(currentChannelId);
|
|
||||||
if (member) {
|
|
||||||
lastViewed = member.last_viewed_at;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(state, {
|
|
||||||
currentChannelIndex: 0,
|
|
||||||
channels: [currentChannelId],
|
|
||||||
postLists: [this.getChannelPosts(currentChannelId)],
|
|
||||||
atTop: [PostStore.getVisibilityAtTop(currentChannelId)],
|
|
||||||
currentLastViewed: lastViewed
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Object.assign(state, {
|
|
||||||
currentChannelIndex: null,
|
|
||||||
channels: [],
|
|
||||||
postLists: [],
|
|
||||||
atTop: [],
|
|
||||||
currentLastViewed: Date.now()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
state.showInviteModal = false;
|
|
||||||
this.state = state;
|
|
||||||
}
|
|
||||||
componentDidMount() {
|
|
||||||
ChannelStore.addChangeListener(this.onChannelChange);
|
|
||||||
ChannelStore.addLeaveListener(this.onChannelLeave);
|
|
||||||
PostStore.addChangeListener(this.onPostsChange);
|
|
||||||
PostStore.addPostsViewJumpListener(this.handlePostsViewJumpRequest);
|
|
||||||
$('body').addClass('app__body');
|
|
||||||
}
|
|
||||||
componentWillUnmount() {
|
|
||||||
ChannelStore.removeChangeListener(this.onChannelChange);
|
|
||||||
ChannelStore.removeLeaveListener(this.onChannelLeave);
|
|
||||||
PostStore.removeChangeListener(this.onPostsChange);
|
|
||||||
PostStore.removePostsViewJumpListener(this.handlePostsViewJumpRequest);
|
|
||||||
$('body').removeClass('app__body');
|
|
||||||
}
|
|
||||||
handlePostsViewJumpRequest(type, post) {
|
|
||||||
switch (type) {
|
|
||||||
case Constants.PostsViewJumpTypes.BOTTOM:
|
|
||||||
this.setState({scrollType: PostsView.SCROLL_TYPE_BOTTOM});
|
|
||||||
break;
|
|
||||||
case Constants.PostsViewJumpTypes.POST:
|
|
||||||
this.setState({
|
|
||||||
scrollType: PostsView.SCROLL_TYPE_POST,
|
|
||||||
scrollPost: post
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case Constants.PostsViewJumpTypes.SIDEBAR_OPEN:
|
|
||||||
this.setState({scrollType: PostsView.SCROLL_TYPE_SIDEBAR_OPEN});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onChannelChange() {
|
|
||||||
const postLists = this.state.postLists.slice();
|
|
||||||
const atTop = this.state.atTop.slice();
|
|
||||||
const channels = this.state.channels.slice();
|
|
||||||
const channelId = ChannelStore.getCurrentId();
|
|
||||||
|
|
||||||
// Has the channel really changed?
|
|
||||||
if (channelId === channels[this.state.currentChannelIndex]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let lastViewed = Number.MAX_VALUE;
|
|
||||||
const member = ChannelStore.getMember(channelId);
|
|
||||||
if (member != null) {
|
|
||||||
lastViewed = member.last_viewed_at;
|
|
||||||
}
|
|
||||||
|
|
||||||
let newIndex = channels.indexOf(channelId);
|
|
||||||
if (newIndex === -1) {
|
|
||||||
if (channels.length >= MAXIMUM_CACHED_VIEWS) {
|
|
||||||
channels.shift();
|
|
||||||
atTop.shift();
|
|
||||||
postLists.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
newIndex = channels.length;
|
|
||||||
channels.push(channelId);
|
|
||||||
atTop[newIndex] = PostStore.getVisibilityAtTop(channelId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure we have the latest posts from the store
|
|
||||||
postLists[newIndex] = this.getChannelPosts(channelId);
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
currentChannelIndex: newIndex,
|
|
||||||
currentLastViewed: lastViewed,
|
|
||||||
scrollType: PostsView.SCROLL_TYPE_NEW_MESSAGE,
|
|
||||||
channels,
|
|
||||||
postLists,
|
|
||||||
atTop});
|
|
||||||
}
|
|
||||||
onChannelLeave(id) {
|
|
||||||
const postLists = this.state.postLists.slice();
|
|
||||||
const channels = this.state.channels.slice();
|
|
||||||
const atTop = this.state.atTop.slice();
|
|
||||||
const index = channels.indexOf(id);
|
|
||||||
if (index !== -1) {
|
|
||||||
postLists.splice(index, 1);
|
|
||||||
channels.splice(index, 1);
|
|
||||||
atTop.splice(index, 1);
|
|
||||||
}
|
|
||||||
this.setState({channels, postLists, atTop});
|
|
||||||
}
|
|
||||||
onPostsChange() {
|
|
||||||
const channels = this.state.channels;
|
|
||||||
const postLists = this.state.postLists.slice();
|
|
||||||
const atTop = this.state.atTop.slice();
|
|
||||||
const currentChannelId = channels[this.state.currentChannelIndex];
|
|
||||||
const newPostsView = this.getChannelPosts(currentChannelId);
|
|
||||||
|
|
||||||
postLists[this.state.currentChannelIndex] = newPostsView;
|
|
||||||
atTop[this.state.currentChannelIndex] = PostStore.getVisibilityAtTop(currentChannelId);
|
|
||||||
this.setState({postLists, atTop});
|
|
||||||
}
|
|
||||||
getChannelPosts(id) {
|
|
||||||
return PostStore.getVisiblePosts(id);
|
|
||||||
}
|
|
||||||
loadMorePostsTop() {
|
|
||||||
GlobalActions.emitLoadMorePostsEvent();
|
|
||||||
}
|
|
||||||
handlePostsViewScroll(atBottom) {
|
|
||||||
if (atBottom) {
|
|
||||||
this.setState({scrollType: PostsView.SCROLL_TYPE_BOTTOM});
|
|
||||||
} else {
|
|
||||||
this.setState({scrollType: PostsView.SCROLL_TYPE_FREE});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
const postLists = this.state.postLists;
|
|
||||||
const channels = this.state.channels;
|
|
||||||
const currentChannelId = channels[this.state.currentChannelIndex];
|
|
||||||
const channel = ChannelStore.get(currentChannelId);
|
|
||||||
|
|
||||||
if (!channel) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const postListCtls = [];
|
|
||||||
for (let i = 0; i < channels.length; i++) {
|
|
||||||
const isActive = (channels[i] === currentChannelId);
|
|
||||||
postListCtls.push(
|
|
||||||
<PostsView
|
|
||||||
key={'postsviewkey' + channels[i]}
|
|
||||||
isActive={isActive}
|
|
||||||
postList={postLists[i]}
|
|
||||||
scrollType={this.state.scrollType}
|
|
||||||
scrollPostId={this.state.scrollPost}
|
|
||||||
postViewScrolled={this.handlePostsViewScroll}
|
|
||||||
loadMorePostsTopClicked={this.loadMorePostsTop}
|
|
||||||
loadMorePostsBottomClicked={() => {
|
|
||||||
// Do Nothing
|
|
||||||
}}
|
|
||||||
showMoreMessagesTop={!this.state.atTop[this.state.currentChannelIndex]}
|
|
||||||
showMoreMessagesBottom={false}
|
|
||||||
channel={channel}
|
|
||||||
messageSeparatorTime={this.state.currentLastViewed}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
if (!postLists[i] && isActive) {
|
|
||||||
postListCtls.push(
|
|
||||||
<LoadingScreen
|
|
||||||
position='absolute'
|
|
||||||
key='loading'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div id='post-list'>
|
|
||||||
{postListCtls}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import UserProfile from './user_profile.jsx';
|
import UserProfile from './user_profile.jsx';
|
||||||
import FileAttachmentList from './file_attachment_list.jsx';
|
import FileAttachmentList from './file_attachment_list.jsx';
|
||||||
import PendingPostActions from './pending_post_actions.jsx';
|
import PendingPostOptions from 'components/post_view/components/pending_post_options.jsx';
|
||||||
|
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
@@ -168,7 +168,7 @@ export default class RhsComment extends React.Component {
|
|||||||
|
|
||||||
if (post.state === Constants.POST_FAILED) {
|
if (post.state === Constants.POST_FAILED) {
|
||||||
postClass += ' post-fail';
|
postClass += ' post-fail';
|
||||||
loading = <PendingPostActions post={this.props.post}/>;
|
loading = <PendingPostOptions post={this.props.post}/>;
|
||||||
} else if (post.state === Constants.POST_LOADING) {
|
} else if (post.state === Constants.POST_LOADING) {
|
||||||
postClass += ' post-waiting';
|
postClass += ' post-waiting';
|
||||||
loading = (
|
loading = (
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import UserProfile from './user_profile.jsx';
|
|||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
import * as TextFormatting from 'utils/text_formatting.jsx';
|
import * as TextFormatting from 'utils/text_formatting.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
|
||||||
import FileAttachmentList from './file_attachment_list.jsx';
|
import FileAttachmentList from './file_attachment_list.jsx';
|
||||||
import PostBodyAdditionalContent from './post_body_additional_content.jsx';
|
import PostBodyAdditionalContent from 'components/post_view/components/post_body_additional_content.jsx';
|
||||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||||
|
|
||||||
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
import * as PostUtils from 'utils/post_utils.jsx';
|
||||||
|
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
|
|
||||||
import {FormattedMessage, FormattedDate} from 'react-intl';
|
import {FormattedMessage, FormattedDate} from 'react-intl';
|
||||||
@@ -56,7 +58,7 @@ export default class RhsRootPost extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var systemMessageClass = '';
|
var systemMessageClass = '';
|
||||||
if (Utils.isSystemMessage(post)) {
|
if (PostUtils.isSystemMessage(post)) {
|
||||||
systemMessageClass = 'post--system';
|
systemMessageClass = 'post--system';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +190,7 @@ export default class RhsRootPost extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
botIndicator = <li className='col col__name bot-indicator'>{'BOT'}</li>;
|
botIndicator = <li className='col col__name bot-indicator'>{'BOT'}</li>;
|
||||||
} else if (Utils.isSystemMessage(post)) {
|
} else if (PostUtils.isSystemMessage(post)) {
|
||||||
userProfile = (
|
userProfile = (
|
||||||
<UserProfile
|
<UserProfile
|
||||||
user={{}}
|
user={{}}
|
||||||
@@ -202,7 +204,7 @@ export default class RhsRootPost extends React.Component {
|
|||||||
const profilePic = (
|
const profilePic = (
|
||||||
<img
|
<img
|
||||||
className='post-profile-img'
|
className='post-profile-img'
|
||||||
src={Utils.getProfilePicSrcForPost(post, timestamp)}
|
src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
|
||||||
height='36'
|
height='36'
|
||||||
width='36'
|
width='36'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import * as GlobalActions from 'actions/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 TextFormatting from 'utils/text_formatting.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
import * as PostUtils from 'utils/post_utils.jsx';
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
const ActionTypes = Constants.ActionTypes;
|
const ActionTypes = Constants.ActionTypes;
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ export default class SearchResultsItem extends React.Component {
|
|||||||
<div className='post__content'>
|
<div className='post__content'>
|
||||||
<div className='post__img'>
|
<div className='post__img'>
|
||||||
<img
|
<img
|
||||||
src={Utils.getProfilePicSrcForPost(post, timestamp)}
|
src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
|
||||||
height='36'
|
height='36'
|
||||||
width='36'
|
width='36'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Constants from 'utils/constants.jsx';
|
|||||||
import UserStore from './user_store.jsx';
|
import UserStore from './user_store.jsx';
|
||||||
import ChannelStore from './channel_store.jsx';
|
import ChannelStore from './channel_store.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
import * as PostUtils from 'utils/post_utils.jsx';
|
||||||
const ActionTypes = Constants.ActionTypes;
|
const ActionTypes = Constants.ActionTypes;
|
||||||
|
|
||||||
const CHANGE_EVENT = 'change';
|
const CHANGE_EVENT = 'change';
|
||||||
@@ -26,7 +27,7 @@ class NotificationStoreClass extends EventEmitter {
|
|||||||
|
|
||||||
handleRecievedPost(post, msgProps) {
|
handleRecievedPost(post, msgProps) {
|
||||||
// Send desktop notification
|
// Send desktop notification
|
||||||
if ((UserStore.getCurrentId() !== post.user_id || post.props.from_webhook === 'true') && !Utils.isSystemMessage(post)) {
|
if ((UserStore.getCurrentId() !== post.user_id || post.props.from_webhook === 'true') && !PostUtils.isSystemMessage(post)) {
|
||||||
let mentions = [];
|
let mentions = [];
|
||||||
if (msgProps.mentions) {
|
if (msgProps.mentions) {
|
||||||
mentions = JSON.parse(msgProps.mentions);
|
mentions = JSON.parse(msgProps.mentions);
|
||||||
|
|||||||
@@ -161,6 +161,14 @@ export default {
|
|||||||
EPHEMERAL_MESSAGE: 'ephemeral_message'
|
EPHEMERAL_MESSAGE: 'ephemeral_message'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ScrollTypes: {
|
||||||
|
FREE: 1,
|
||||||
|
BOTTOM: 2,
|
||||||
|
SIDEBBAR_OPEN: 3,
|
||||||
|
NEW_MESSAGE: 4,
|
||||||
|
POST: 5
|
||||||
|
},
|
||||||
|
|
||||||
//SPECIAL_MENTIONS: ['all', 'channel'],
|
//SPECIAL_MENTIONS: ['all', 'channel'],
|
||||||
SPECIAL_MENTIONS: ['channel'],
|
SPECIAL_MENTIONS: ['channel'],
|
||||||
CHARACTER_LIMIT: 4000,
|
CHARACTER_LIMIT: 4000,
|
||||||
@@ -204,6 +212,9 @@ export default {
|
|||||||
WEB_VIDEO_HEIGHT: 480,
|
WEB_VIDEO_HEIGHT: 480,
|
||||||
MOBILE_VIDEO_WIDTH: 480,
|
MOBILE_VIDEO_WIDTH: 480,
|
||||||
MOBILE_VIDEO_HEIGHT: 360,
|
MOBILE_VIDEO_HEIGHT: 360,
|
||||||
|
MOBILE_SCREEN_WIDTH: 768,
|
||||||
|
SCROLL_DELAY: 2000,
|
||||||
|
SCROLL_PAGE_FRACTION: 3,
|
||||||
DEFAULT_CHANNEL: 'town-square',
|
DEFAULT_CHANNEL: 'town-square',
|
||||||
DEFAULT_CHANNEL_UI_NAME: 'Town Square',
|
DEFAULT_CHANNEL_UI_NAME: 'Town Square',
|
||||||
OFFTOPIC_CHANNEL: 'off-topic',
|
OFFTOPIC_CHANNEL: 'off-topic',
|
||||||
@@ -738,5 +749,6 @@ export default {
|
|||||||
DEFAULT_WEBHOOK_LOGO: logoWebhook,
|
DEFAULT_WEBHOOK_LOGO: logoWebhook,
|
||||||
MHPNS: 'https://push.mattermost.com',
|
MHPNS: 'https://push.mattermost.com',
|
||||||
MTPNS: 'http://push-test.mattermost.com',
|
MTPNS: 'http://push-test.mattermost.com',
|
||||||
BOT_NAME: 'BOT'
|
BOT_NAME: 'BOT',
|
||||||
|
POST_COLLAPSE_TIMEOUT: 1000 * 60 * 5 // five minutes
|
||||||
};
|
};
|
||||||
|
|||||||
32
webapp/utils/post_utils.jsx
Обычный файл
32
webapp/utils/post_utils.jsx
Обычный файл
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import Client from 'utils/web_client.jsx';
|
||||||
|
|
||||||
|
import Constants from 'utils/constants.jsx';
|
||||||
|
|
||||||
|
export function isSystemMessage(post) {
|
||||||
|
return post.type && (post.type.lastIndexOf(Constants.SYSTEM_MESSAGE_PREFIX) === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isComment(post) {
|
||||||
|
if ('root_id' in post) {
|
||||||
|
return post.root_id !== '' && post.root_id != null;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getProfilePicSrcForPost(post, timestamp) {
|
||||||
|
let src = Client.getUsersRoute() + '/' + post.user_id + '/image?time=' + timestamp;
|
||||||
|
if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') {
|
||||||
|
if (post.props.override_icon_url) {
|
||||||
|
src = post.props.override_icon_url;
|
||||||
|
} else {
|
||||||
|
src = Constants.DEFAULT_WEBHOOK_LOGO;
|
||||||
|
}
|
||||||
|
} else if (isSystemMessage(post)) {
|
||||||
|
src = Constants.SYSTEM_MESSAGE_PROFILE_IMAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return src;
|
||||||
|
}
|
||||||
@@ -909,14 +909,7 @@ export function isValidUsername(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isMobile() {
|
export function isMobile() {
|
||||||
return window.innerWidth <= 768;
|
return window.innerWidth <= Constants.MOBILE_SCREEN_WIDTH;
|
||||||
}
|
|
||||||
|
|
||||||
export function isComment(post) {
|
|
||||||
if ('root_id' in post) {
|
|
||||||
return post.root_id !== '' && post.root_id != null;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDirectTeammate(channelId) {
|
export function getDirectTeammate(channelId) {
|
||||||
@@ -1315,10 +1308,6 @@ export function isFeatureEnabled(feature) {
|
|||||||
return PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, Constants.FeatureTogglePrefix + feature.label);
|
return PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, Constants.FeatureTogglePrefix + feature.label);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSystemMessage(post) {
|
|
||||||
return post.type && (post.type.lastIndexOf(Constants.SYSTEM_MESSAGE_PREFIX) === 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fillArray(value, length) {
|
export function fillArray(value, length) {
|
||||||
const arr = [];
|
const arr = [];
|
||||||
|
|
||||||
@@ -1379,18 +1368,3 @@ export function localizeMessage(id, defaultMessage) {
|
|||||||
export function mod(a, b) {
|
export function mod(a, b) {
|
||||||
return ((a % b) + b) % b;
|
return ((a % b) + b) % b;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProfilePicSrcForPost(post, timestamp) {
|
|
||||||
let src = Client.getUsersRoute() + '/' + post.user_id + '/image?time=' + timestamp;
|
|
||||||
if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') {
|
|
||||||
if (post.props.override_icon_url) {
|
|
||||||
src = post.props.override_icon_url;
|
|
||||||
} else {
|
|
||||||
src = Constants.DEFAULT_WEBHOOK_LOGO;
|
|
||||||
}
|
|
||||||
} else if (isSystemMessage(post)) {
|
|
||||||
src = Constants.SYSTEM_MESSAGE_PROFILE_IMAGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return src;
|
|
||||||
}
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user