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 FileUploadOverlay from 'components/file_upload_overlay.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';
|
||||
|
||||
@@ -70,7 +70,7 @@ export default class ChannelView extends React.Component {
|
||||
<ChannelHeader
|
||||
channelId={this.state.channelId}
|
||||
/>
|
||||
<PostsViewContainer/>
|
||||
<PostViewCache/>
|
||||
<div
|
||||
className='post-create__container'
|
||||
id='post-create'
|
||||
|
||||
@@ -5,7 +5,7 @@ import $ from 'jquery';
|
||||
import React from 'react';
|
||||
|
||||
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 TeamStore from 'stores/team_store.jsx';
|
||||
@@ -70,7 +70,7 @@ export default class PermalinkView extends React.Component {
|
||||
<ChannelHeader
|
||||
channelId={this.state.channelId}
|
||||
/>
|
||||
<PostFocusView/>
|
||||
<PostFocusViewController/>
|
||||
<div
|
||||
id='archive-link-home'
|
||||
>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import PostStore from 'stores/post_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 * as AsyncClient from 'utils/async_client.jsx';
|
||||
@@ -16,7 +16,7 @@ import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default class PendingPostActions extends React.Component {
|
||||
export default class PendingPostOptions extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
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
|
||||
};
|
||||
@@ -8,7 +8,8 @@ import Constants from 'utils/constants.jsx';
|
||||
const ActionTypes = Constants.ActionTypes;
|
||||
|
||||
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';
|
||||
|
||||
@@ -55,7 +56,7 @@ export default class Post extends React.Component {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.getCommentCount(nextProps) !== this.getCommentCount(this.props)) {
|
||||
if (nextProps.commentCount !== this.props.commentCount) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -77,30 +78,9 @@ export default class Post extends React.Component {
|
||||
|
||||
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() {
|
||||
const post = this.props.post;
|
||||
const parentPost = this.props.parentPost;
|
||||
const posts = this.props.posts;
|
||||
const mattermostLogo = Constants.MATTERMOST_ICON_SVG;
|
||||
|
||||
if (!post.props) {
|
||||
@@ -112,7 +92,7 @@ export default class Post extends React.Component {
|
||||
type = 'Comment';
|
||||
}
|
||||
|
||||
const commentCount = this.getCommentCount(this.props);
|
||||
const commentCount = this.props.commentCount;
|
||||
|
||||
let rootUser;
|
||||
if (this.props.sameRoot) {
|
||||
@@ -129,7 +109,7 @@ export default class Post extends React.Component {
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
@@ -151,19 +131,19 @@ export default class Post extends React.Component {
|
||||
}
|
||||
|
||||
let systemMessageClass = '';
|
||||
if (Utils.isSystemMessage(post)) {
|
||||
if (PostUtils.isSystemMessage(post)) {
|
||||
systemMessageClass = 'post--system';
|
||||
}
|
||||
|
||||
let profilePic = (
|
||||
<img
|
||||
src={Utils.getProfilePicSrcForPost(post, timestamp)}
|
||||
src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
|
||||
height='36'
|
||||
width='36'
|
||||
/>
|
||||
);
|
||||
|
||||
if (Utils.isSystemMessage(post)) {
|
||||
if (PostUtils.isSystemMessage(post)) {
|
||||
profilePic = (
|
||||
<span
|
||||
className='icon'
|
||||
@@ -207,7 +187,6 @@ export default class Post extends React.Component {
|
||||
post={post}
|
||||
sameRoot={this.props.sameRoot}
|
||||
parentPost={parentPost}
|
||||
posts={posts}
|
||||
handleCommentClick={this.handleCommentClick}
|
||||
compactDisplay={this.props.compactDisplay}
|
||||
/>
|
||||
@@ -221,7 +200,6 @@ export default class Post extends React.Component {
|
||||
|
||||
Post.propTypes = {
|
||||
post: React.PropTypes.object.isRequired,
|
||||
posts: React.PropTypes.object,
|
||||
parentPost: React.PropTypes.object,
|
||||
user: React.PropTypes.object,
|
||||
sameUser: React.PropTypes.bool,
|
||||
@@ -233,5 +211,6 @@ Post.propTypes = {
|
||||
hasProfiles: React.PropTypes.bool,
|
||||
currentUser: React.PropTypes.object.isRequired,
|
||||
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.
|
||||
// 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 * as Utils from 'utils/utils.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
import * as TextFormatting from 'utils/text_formatting.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';
|
||||
|
||||
@@ -111,7 +111,7 @@ export default class PostBody extends React.Component {
|
||||
let loading;
|
||||
if (post.state === Constants.POST_FAILED) {
|
||||
postClass += ' post--fail';
|
||||
loading = <PendingPostActions post={this.props.post}/>;
|
||||
loading = <PendingPostOptions post={this.props.post}/>;
|
||||
} else if (post.state === Constants.POST_LOADING) {
|
||||
postClass += ' post-waiting';
|
||||
loading = (
|
||||
@@ -4,7 +4,7 @@
|
||||
import PostAttachmentList from './post_attachment_list.jsx';
|
||||
import PostAttachmentOEmbed from './post_attachment_oembed.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 OEmbedProviders from './providers.json';
|
||||
@@ -1,9 +1,10 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// 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 * as Utils from 'utils/utils.jsx';
|
||||
|
||||
import * as PostUtils from 'utils/post_utils.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>;
|
||||
} else if (Utils.isSystemMessage(post)) {
|
||||
} else if (PostUtils.isSystemMessage(post)) {
|
||||
userProfile = (
|
||||
<UserProfile
|
||||
user={{}}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import $ from 'jquery';
|
||||
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 TeamStore from 'stores/team_store.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
@@ -5,30 +5,28 @@ import $ from 'jquery';
|
||||
|
||||
import Post from './post.jsx';
|
||||
import FloatingTimestamp from './floating_timestamp.jsx';
|
||||
import ScrollToBottomArrows from './scroll_to_bottom_arrows.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 * as Utils from 'utils/utils.jsx';
|
||||
import * as PostUtils from 'utils/post_utils.jsx';
|
||||
import DelayedAction from 'utils/delayed_action.jsx';
|
||||
|
||||
import Constants from 'utils/constants.jsx';
|
||||
const Preferences = Constants.Preferences;
|
||||
const ScrollTypes = Constants.ScrollTypes;
|
||||
|
||||
import {FormattedDate, FormattedMessage} from 'react-intl';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
export default class PostsView extends React.Component {
|
||||
export default class PostList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.updateState = this.updateState.bind(this);
|
||||
this.handleScroll = this.handleScroll.bind(this);
|
||||
this.handleScrollStop = this.handleScrollStop.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.scrollToBottom = this.scrollToBottom.bind(this);
|
||||
this.scrollToBottomAnimated = this.scrollToBottomAnimated.bind(this);
|
||||
this.onUserChange = this.onUserChange.bind(this);
|
||||
|
||||
this.jumpToPostNode = null;
|
||||
this.wasAtBottom = true;
|
||||
@@ -47,62 +44,31 @@ export default class PostsView extends React.Component {
|
||||
|
||||
this.scrollStopAction = new DelayedAction(this.handleScrollStop);
|
||||
|
||||
let profiles = UserStore.getProfiles();
|
||||
if (props.channel && props.channel.type === Constants.DM_CHANNEL) {
|
||||
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
|
||||
if (props.channel) {
|
||||
this.introText = createChannelIntroMessage(props.channel);
|
||||
} else {
|
||||
this.introText = this.getArchivesIntroMessage();
|
||||
}
|
||||
|
||||
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,
|
||||
topPostId: null,
|
||||
currentUser: UserStore.getCurrentUser(),
|
||||
profiles
|
||||
topPostId: null
|
||||
};
|
||||
}
|
||||
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() {
|
||||
// consider the view to be at the bottom if it's within this many pixels of the bottom
|
||||
const atBottomMargin = 10;
|
||||
|
||||
return this.refs.postlist.clientHeight + this.refs.postlist.scrollTop >= this.refs.postlist.scrollHeight - atBottomMargin;
|
||||
}
|
||||
|
||||
handleScroll() {
|
||||
// HACK FOR RHS -- REMOVE WHEN RHS DIES
|
||||
const childNodes = this.refs.postlistcontent.childNodes;
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
// 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];
|
||||
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.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() {
|
||||
this.setState({
|
||||
isScrolling: false
|
||||
});
|
||||
}
|
||||
|
||||
updateFloatingTimestamp() {
|
||||
// skip this in non-mobile view since that's when the timestamp is visible
|
||||
if ($(window).width() > 768) {
|
||||
if (!Utils.isMobile()) {
|
||||
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
|
||||
for (let i = 0; i < this.props.postList.order.length; 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) {
|
||||
// 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() {
|
||||
this.props.loadMorePostsTopClicked();
|
||||
GlobalActions.emitLoadMorePostsEvent();
|
||||
}
|
||||
|
||||
loadMorePostsBottom() {
|
||||
this.props.loadMorePostsBottomClicked();
|
||||
GlobalActions.emitLoadMorePostsFocusedBottomEvent();
|
||||
}
|
||||
|
||||
createPosts(posts, order) {
|
||||
const postCtls = [];
|
||||
let previousPostDay = new Date(0);
|
||||
const userId = this.state.currentUser.id;
|
||||
const profiles = this.state.profiles || {};
|
||||
const userId = this.props.currentUser.id;
|
||||
const profiles = this.props.profiles || {};
|
||||
|
||||
let renderedLastViewed = false;
|
||||
|
||||
@@ -185,7 +156,7 @@ export default class PostsView extends React.Component {
|
||||
const post = posts[order[i]];
|
||||
const parentPost = posts[post.parent_id];
|
||||
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 (parentPost && parentPost.state === Constants.POST_DELETED) {
|
||||
@@ -197,11 +168,11 @@ export default class PostsView extends React.Component {
|
||||
let hideProfilePic = false;
|
||||
|
||||
if (prevPost) {
|
||||
const postIsComment = Utils.isComment(post);
|
||||
const prevPostIsComment = Utils.isComment(prevPost);
|
||||
const postIsComment = PostUtils.isComment(post);
|
||||
const prevPostIsComment = PostUtils.isComment(prevPost);
|
||||
const postFromWebhook = Boolean(post.props && post.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:
|
||||
// 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 previous post is not from a webhook
|
||||
if (prevPostUserId === postUserId &&
|
||||
post.create_at - prevPost.create_at <= 1000 * 60 * 5 &&
|
||||
post.create_at - prevPost.create_at <= Constants.POST_COLLAPSE_TIMEOUT &&
|
||||
!postFromWebhook && !prevPostFromWebhook) {
|
||||
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
|
||||
// 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;
|
||||
|
||||
@@ -254,11 +225,24 @@ export default class PostsView extends React.Component {
|
||||
|
||||
let profile;
|
||||
if (userId === post.user_id) {
|
||||
profile = this.state.currentUser;
|
||||
profile = this.props.currentUser;
|
||||
} else {
|
||||
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 = (
|
||||
<Post
|
||||
key={keyPrefix + 'postKey'}
|
||||
@@ -267,16 +251,15 @@ export default class PostsView extends React.Component {
|
||||
sameRoot={sameRoot}
|
||||
post={post}
|
||||
parentPost={parentPost}
|
||||
posts={posts}
|
||||
hideProfilePic={hideProfilePic}
|
||||
isLastComment={isLastComment}
|
||||
shouldHighlight={shouldHighlight}
|
||||
onClick={() => GlobalActions.emitPostFocusEvent(post.id)} //eslint-disable-line no-loop-func
|
||||
displayNameType={this.state.displayNameType}
|
||||
displayNameType={this.props.displayNameType}
|
||||
user={profile}
|
||||
currentUser={this.state.currentUser}
|
||||
center={this.state.centerPosts}
|
||||
compactDisplay={this.state.compactPosts}
|
||||
currentUser={this.props.currentUser}
|
||||
center={this.props.displayPostsInCenter}
|
||||
commentCount={commentCount}
|
||||
compactDisplay={this.props.compactDisplay}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -302,8 +285,8 @@ export default class PostsView extends React.Component {
|
||||
}
|
||||
|
||||
if (postUserId !== userId &&
|
||||
this.props.messageSeparatorTime !== 0 &&
|
||||
post.create_at > this.props.messageSeparatorTime &&
|
||||
this.props.lastViewed !== 0 &&
|
||||
post.create_at > this.props.lastViewed &&
|
||||
!renderedLastViewed) {
|
||||
renderedLastViewed = true;
|
||||
|
||||
@@ -337,10 +320,11 @@ export default class PostsView extends React.Component {
|
||||
|
||||
return postCtls;
|
||||
}
|
||||
|
||||
updateScrolling() {
|
||||
if (this.props.scrollType === PostsView.SCROLL_TYPE_BOTTOM) {
|
||||
if (this.props.scrollType === ScrollTypes.BOTTOM) {
|
||||
this.scrollToBottom();
|
||||
} else if (this.props.scrollType === PostsView.SCROLL_TYPE_NEW_MESSAGE) {
|
||||
} else if (this.props.scrollType === ScrollTypes.NEW_MESSAGE) {
|
||||
window.setTimeout(window.requestAnimationFrame(() => {
|
||||
// If separator exists scroll to it. Otherwise scroll to bottom.
|
||||
if (this.refs.newMessageSeparator) {
|
||||
@@ -350,7 +334,7 @@ export default class PostsView extends React.Component {
|
||||
this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
|
||||
}
|
||||
}), 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(() => {
|
||||
const postNode = ReactDOM.findDOMNode(this.refs[this.props.scrollPostId]);
|
||||
if (postNode == null) {
|
||||
@@ -358,12 +342,12 @@ export default class PostsView extends React.Component {
|
||||
}
|
||||
postNode.scrollIntoView();
|
||||
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 {
|
||||
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 (this.wasAtBottom) {
|
||||
this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
|
||||
@@ -371,9 +355,9 @@ export default class PostsView extends React.Component {
|
||||
window.requestAnimationFrame(() => {
|
||||
this.jumpToPostNode.scrollIntoView();
|
||||
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 {
|
||||
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() {
|
||||
this.updateScrolling();
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
window.requestAnimationFrame(() => {
|
||||
this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
|
||||
});
|
||||
}
|
||||
|
||||
scrollToBottomAnimated() {
|
||||
var postList = $(this.refs.postlist);
|
||||
postList.animate({scrollTop: this.refs.postlist.scrollHeight}, '500');
|
||||
@@ -417,145 +404,76 @@ export default class PostsView extends React.Component {
|
||||
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);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
this.scrollStopAction.cancel();
|
||||
PreferenceStore.removeChangeListener(this.updateState);
|
||||
UserStore.removeChangeListener(this.onUserChange);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.props.postList != null) {
|
||||
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() {
|
||||
let posts = [];
|
||||
let order = [];
|
||||
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 = '';
|
||||
}
|
||||
if (this.props.postList == null) {
|
||||
return <div/>;
|
||||
}
|
||||
|
||||
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;
|
||||
if (this.state.topPostId && this.props.postList.posts[this.state.topPostId]) {
|
||||
topPostCreateAt = this.props.postList.posts[this.state.topPostId].create_at;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={activeClass}>
|
||||
<div>
|
||||
<FloatingTimestamp
|
||||
isScrolling={this.state.isScrolling}
|
||||
isMobile={$(window).width() > 768}
|
||||
isMobile={Utils.isMobile()}
|
||||
createAt={topPostCreateAt}
|
||||
/>
|
||||
<ScrollToBottomArrows
|
||||
@@ -583,48 +501,24 @@ export default class PostsView extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
PostsView.defaultProps = {
|
||||
|
||||
PostList.defaultProps = {
|
||||
lastViewed: 0
|
||||
};
|
||||
|
||||
PostsView.propTypes = {
|
||||
isActive: React.PropTypes.bool,
|
||||
PostList.propTypes = {
|
||||
postList: React.PropTypes.object,
|
||||
profiles: React.PropTypes.object,
|
||||
channel: React.PropTypes.object,
|
||||
currentUser: React.PropTypes.object,
|
||||
scrollPostId: React.PropTypes.string,
|
||||
scrollType: React.PropTypes.number,
|
||||
postViewScrolled: React.PropTypes.func.isRequired,
|
||||
loadMorePostsTopClicked: React.PropTypes.func.isRequired,
|
||||
loadMorePostsBottomClicked: React.PropTypes.func.isRequired,
|
||||
postListScrolled: React.PropTypes.func.isRequired,
|
||||
showMoreMessagesTop: React.PropTypes.bool,
|
||||
showMoreMessagesBottom: React.PropTypes.bool,
|
||||
channel: React.PropTypes.object,
|
||||
messageSeparatorTime: React.PropTypes.number,
|
||||
lastViewed: React.PropTypes.number,
|
||||
postsToHighlight: React.PropTypes.object,
|
||||
displayNameType: React.PropTypes.string,
|
||||
displayPostsInCenter: 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.
|
||||
// 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 UserStore from 'stores/user_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';
|
||||
|
||||
@@ -15,17 +19,24 @@ export default class PostFocusView extends React.Component {
|
||||
|
||||
this.onChannelChange = this.onChannelChange.bind(this);
|
||||
this.onPostsChange = this.onPostsChange.bind(this);
|
||||
this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
|
||||
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
|
||||
this.loadMorePostsBottom = this.loadMorePostsBottom.bind(this);
|
||||
this.onUserChange = this.onUserChange.bind(this);
|
||||
this.onPostListScroll = this.onPostListScroll.bind(this);
|
||||
|
||||
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 = {
|
||||
scrollType: PostsView.SCROLL_TYPE_POST,
|
||||
postList: PostStore.getVisiblePosts(focusedPostId),
|
||||
currentUser: UserStore.getCurrentUser(),
|
||||
profiles,
|
||||
scrollType: ScrollTypes.POST,
|
||||
currentChannel: ChannelStore.getCurrentId().slice(),
|
||||
scrollPostId: focusedPostId,
|
||||
postList: PostStore.getVisiblePosts(focusedPostId),
|
||||
atTop: PostStore.getVisibilityAtTop(focusedPostId),
|
||||
atBottom: PostStore.getVisibilityAtBottom(focusedPostId)
|
||||
};
|
||||
@@ -34,11 +45,13 @@ export default class PostFocusView extends React.Component {
|
||||
componentDidMount() {
|
||||
ChannelStore.addChangeListener(this.onChannelChange);
|
||||
PostStore.addChangeListener(this.onPostsChange);
|
||||
UserStore.addChangeListener(this.onUserChange);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
ChannelStore.removeChangeListener(this.onChannelChange);
|
||||
PostStore.removeChangeListener(this.onPostsChange);
|
||||
UserStore.removeChangeListener(this.onUserChange);
|
||||
}
|
||||
|
||||
onChannelChange() {
|
||||
@@ -46,7 +59,7 @@ export default class PostFocusView extends React.Component {
|
||||
if (this.state.currentChannel !== currentChannel) {
|
||||
this.setState({
|
||||
currentChannel: currentChannel.slice(),
|
||||
scrollType: PostsView.SCROLL_TYPE_POST
|
||||
scrollType: ScrollTypes.POST
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -65,42 +78,50 @@ export default class PostFocusView extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
handlePostsViewScroll() {
|
||||
this.setState({scrollType: PostsView.SCROLL_TYPE_FREE});
|
||||
onUserChange() {
|
||||
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() {
|
||||
GlobalActions.emitLoadMorePostsFocusedTopEvent();
|
||||
}
|
||||
|
||||
loadMorePostsBottom() {
|
||||
GlobalActions.emitLoadMorePostsFocusedBottomEvent();
|
||||
onPostListScroll() {
|
||||
this.setState({scrollType: ScrollTypes.FREE});
|
||||
}
|
||||
|
||||
render() {
|
||||
const postsToHighlight = {};
|
||||
postsToHighlight[this.state.scrollPostId] = true;
|
||||
|
||||
if (!this.state.postList) {
|
||||
return null;
|
||||
let content;
|
||||
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 (
|
||||
<div id='post-list'>
|
||||
<PostsView
|
||||
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}
|
||||
/>
|
||||
{content}
|
||||
</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 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 UserStore from 'stores/user_store.jsx';
|
||||
@@ -168,7 +168,7 @@ export default class RhsComment extends React.Component {
|
||||
|
||||
if (post.state === Constants.POST_FAILED) {
|
||||
postClass += ' post-fail';
|
||||
loading = <PendingPostActions post={this.props.post}/>;
|
||||
loading = <PendingPostOptions post={this.props.post}/>;
|
||||
} else if (post.state === Constants.POST_LOADING) {
|
||||
postClass += ' post-waiting';
|
||||
loading = (
|
||||
|
||||
@@ -6,11 +6,13 @@ import UserProfile from './user_profile.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
import TeamStore from 'stores/team_store.jsx';
|
||||
import * as TextFormatting from 'utils/text_formatting.jsx';
|
||||
import * as Utils from 'utils/utils.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 Utils from 'utils/utils.jsx';
|
||||
import * as PostUtils from 'utils/post_utils.jsx';
|
||||
|
||||
import Constants from 'utils/constants.jsx';
|
||||
|
||||
import {FormattedMessage, FormattedDate} from 'react-intl';
|
||||
@@ -56,7 +58,7 @@ export default class RhsRootPost extends React.Component {
|
||||
}
|
||||
|
||||
var systemMessageClass = '';
|
||||
if (Utils.isSystemMessage(post)) {
|
||||
if (PostUtils.isSystemMessage(post)) {
|
||||
systemMessageClass = 'post--system';
|
||||
}
|
||||
|
||||
@@ -188,7 +190,7 @@ export default class RhsRootPost extends React.Component {
|
||||
}
|
||||
|
||||
botIndicator = <li className='col col__name bot-indicator'>{'BOT'}</li>;
|
||||
} else if (Utils.isSystemMessage(post)) {
|
||||
} else if (PostUtils.isSystemMessage(post)) {
|
||||
userProfile = (
|
||||
<UserProfile
|
||||
user={{}}
|
||||
@@ -202,7 +204,7 @@ export default class RhsRootPost extends React.Component {
|
||||
const profilePic = (
|
||||
<img
|
||||
className='post-profile-img'
|
||||
src={Utils.getProfilePicSrcForPost(post, timestamp)}
|
||||
src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
|
||||
height='36'
|
||||
width='36'
|
||||
/>
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as GlobalActions from 'actions/global_actions.jsx';
|
||||
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
||||
import * as TextFormatting from 'utils/text_formatting.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import * as PostUtils from 'utils/post_utils.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
const ActionTypes = Constants.ActionTypes;
|
||||
|
||||
@@ -93,7 +94,7 @@ export default class SearchResultsItem extends React.Component {
|
||||
<div className='post__content'>
|
||||
<div className='post__img'>
|
||||
<img
|
||||
src={Utils.getProfilePicSrcForPost(post, timestamp)}
|
||||
src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
|
||||
height='36'
|
||||
width='36'
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,7 @@ import Constants from 'utils/constants.jsx';
|
||||
import UserStore from './user_store.jsx';
|
||||
import ChannelStore from './channel_store.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import * as PostUtils from 'utils/post_utils.jsx';
|
||||
const ActionTypes = Constants.ActionTypes;
|
||||
|
||||
const CHANGE_EVENT = 'change';
|
||||
@@ -26,7 +27,7 @@ class NotificationStoreClass extends EventEmitter {
|
||||
|
||||
handleRecievedPost(post, msgProps) {
|
||||
// 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 = [];
|
||||
if (msgProps.mentions) {
|
||||
mentions = JSON.parse(msgProps.mentions);
|
||||
|
||||
@@ -161,6 +161,14 @@ export default {
|
||||
EPHEMERAL_MESSAGE: 'ephemeral_message'
|
||||
},
|
||||
|
||||
ScrollTypes: {
|
||||
FREE: 1,
|
||||
BOTTOM: 2,
|
||||
SIDEBBAR_OPEN: 3,
|
||||
NEW_MESSAGE: 4,
|
||||
POST: 5
|
||||
},
|
||||
|
||||
//SPECIAL_MENTIONS: ['all', 'channel'],
|
||||
SPECIAL_MENTIONS: ['channel'],
|
||||
CHARACTER_LIMIT: 4000,
|
||||
@@ -204,6 +212,9 @@ export default {
|
||||
WEB_VIDEO_HEIGHT: 480,
|
||||
MOBILE_VIDEO_WIDTH: 480,
|
||||
MOBILE_VIDEO_HEIGHT: 360,
|
||||
MOBILE_SCREEN_WIDTH: 768,
|
||||
SCROLL_DELAY: 2000,
|
||||
SCROLL_PAGE_FRACTION: 3,
|
||||
DEFAULT_CHANNEL: 'town-square',
|
||||
DEFAULT_CHANNEL_UI_NAME: 'Town Square',
|
||||
OFFTOPIC_CHANNEL: 'off-topic',
|
||||
@@ -738,5 +749,6 @@ export default {
|
||||
DEFAULT_WEBHOOK_LOGO: logoWebhook,
|
||||
MHPNS: 'https://push.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() {
|
||||
return window.innerWidth <= 768;
|
||||
}
|
||||
|
||||
export function isComment(post) {
|
||||
if ('root_id' in post) {
|
||||
return post.root_id !== '' && post.root_id != null;
|
||||
}
|
||||
return false;
|
||||
return window.innerWidth <= Constants.MOBILE_SCREEN_WIDTH;
|
||||
}
|
||||
|
||||
export function getDirectTeammate(channelId) {
|
||||
@@ -1315,10 +1308,6 @@ export function isFeatureEnabled(feature) {
|
||||
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) {
|
||||
const arr = [];
|
||||
|
||||
@@ -1379,18 +1368,3 @@ export function localizeMessage(id, defaultMessage) {
|
||||
export function mod(a, 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