+
{ !this.props.hideProfilePic ?
-
-
+
+
: null }
-
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 7748f5c2ad..865a22dbd0 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -15,124 +15,116 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-function getStateFromStores() {
- var channel = ChannelStore.getCurrent();
+export default class PostList extends React.Component {
+ constructor() {
+ super();
- if (channel == null) {
- channel = {};
+ this.gotMorePosts = false;
+ this.scrolled = false;
+ this.prevScrollTop = 0;
+ this.seenNewMessages = false;
+ this.isUserScroll = true;
+ this.userHasSeenNew = false;
+
+ this.onChange = this.onChange.bind(this);
+ this.onTimeChange = this.onTimeChange.bind(this);
+ this.onSocketChange = this.onSocketChange.bind(this);
+ this.createChannelIntroMessage = this.createChannelIntroMessage.bind(this);
+ this.loadMorePosts = this.loadMorePosts.bind(this);
+
+ this.state = this.getStateFromStores();
+ this.state.numToDisplay = Constants.POST_CHUNK_SIZE;
}
+ getStateFromStores() {
+ var channel = ChannelStore.getCurrent();
- var postList = PostStore.getCurrentPosts();
- var deletedPosts = PostStore.getUnseenDeletedPosts(channel.id);
-
- if (deletedPosts && Object.keys(deletedPosts).length > 0) {
- for (var pid in deletedPosts) {
- postList.posts[pid] = deletedPosts[pid];
- postList.order.unshift(pid);
+ if (channel == null) {
+ channel = {};
}
- postList.order.sort(function postSort(a, b) {
- if (postList.posts[a].create_at > postList.posts[b].create_at) {
- return -1;
+ var postList = PostStore.getCurrentPosts();
+
+ if (postList != null) {
+ var deletedPosts = PostStore.getUnseenDeletedPosts(channel.id);
+
+ if (deletedPosts && Object.keys(deletedPosts).length > 0) {
+ for (var pid in deletedPosts) {
+ postList.posts[pid] = deletedPosts[pid];
+ postList.order.unshift(pid);
+ }
+
+ postList.order.sort(function postSort(a, b) {
+ if (postList.posts[a].create_at > postList.posts[b].create_at) {
+ return -1;
+ }
+ if (postList.posts[a].create_at < postList.posts[b].create_at) {
+ return 1;
+ }
+ return 0;
+ });
}
- if (postList.posts[a].create_at < postList.posts[b].create_at) {
- return 1;
+
+ var pendingPostList = PostStore.getPendingPosts(channel.id);
+
+ if (pendingPostList) {
+ postList.order = pendingPostList.order.concat(postList.order);
+ for (var ppid in pendingPostList.posts) {
+ postList.posts[ppid] = pendingPostList.posts[ppid];
+ }
}
- return 0;
- });
+ }
+
+ var lastViewed = Number.MAX_VALUE;
+
+ if (ChannelStore.getCurrentMember() != null) {
+ lastViewed = ChannelStore.getCurrentMember().last_viewed_at;
+ }
+
+ return {
+ postList: postList,
+ channel: channel,
+ lastViewed: lastViewed
+ };
}
-
- var pendingPostList = PostStore.getPendingPosts(channel.id);
-
- if (pendingPostList) {
- postList.order = pendingPostList.order.concat(postList.order);
- for (var ppid in pendingPostList.posts) {
- postList.posts[ppid] = pendingPostList.posts[ppid];
- }
- }
-
- return {
- postList: postList,
- channel: channel
- };
-}
-
-module.exports = React.createClass({
- displayName: 'PostList',
- scrollPosition: 0,
- preventScrollTrigger: false,
- gotMorePosts: false,
- oldScrollHeight: 0,
- oldZoom: 0,
- scrolledToNew: false,
- componentDidMount: function() {
- var user = UserStore.getCurrentUser();
- if (user.props && user.props.theme) {
- utils.changeCss('div.theme', 'background-color:' + user.props.theme + ';');
- utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme + ';');
- utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme + ';');
- utils.changeCss('.mention', 'background: ' + user.props.theme + ';');
- utils.changeCss('.mention-link', 'color: ' + user.props.theme + ';');
- utils.changeCss('@media(max-width: 768px){.search-bar__container', 'background: ' + user.props.theme + ';}');
- utils.changeCss('.search-item-container:hover', 'background: ' + utils.changeOpacity(user.props.theme, 0.05) + ';');
- utils.changeCss('.nav-pills__unread-indicator', 'background: ' + utils.changeOpacity(user.props.theme, 0.05) + ';');
- }
-
- if (user.props.theme !== '#000000' && user.props.theme !== '#585858') {
- utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, -10) + ';');
- utils.changeCss('a.theme', 'color:' + user.props.theme + '; fill:' + user.props.theme + '!important;');
- } else if (user.props.theme === '#000000') {
- utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, +50) + ';');
- $('.team__header').addClass('theme--black');
- } else if (user.props.theme === '#585858') {
- utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, +10) + ';');
- $('.team__header').addClass('theme--gray');
- }
-
+ componentDidMount() {
PostStore.addChangeListener(this.onChange);
ChannelStore.addChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onTimeChange);
SocketStore.addChangeListener(this.onSocketChange);
- $('.post-list-holder-by-time').perfectScrollbar();
-
- this.resize();
-
- var postHolder = $('.post-list-holder-by-time')[0];
- this.scrollPosition = $(postHolder).scrollTop() + $(postHolder).innerHeight();
- this.oldScrollHeight = postHolder.scrollHeight;
- this.oldZoom = (window.outerWidth - 8) / window.innerWidth;
+ var postHolder = $('.post-list-holder-by-time');
$('.modal').on('show.bs.modal', function onShow() {
$('.modal-body').css('overflow-y', 'auto');
$('.modal-body').css('max-height', $(window).height() * 0.7);
});
- var self = this;
$(window).resize(function resize() {
- $(postHolder).perfectScrollbar('update');
-
- // this only kind of works, detecting zoom in browsers is a nightmare
- var newZoom = (window.outerWidth - 8) / window.innerWidth;
-
- if (self.scrollPosition >= postHolder.scrollHeight || (self.oldScrollHeight !== postHolder.scrollHeight && self.scrollPosition >= self.oldScrollHeight) || self.oldZoom !== newZoom) {
- self.resize();
- }
-
- self.oldZoom = newZoom;
-
if ($('#create_post').length > 0) {
var height = $(window).height() - $('#create_post').height() - $('#error_bar').outerHeight() - 50;
- $('.post-list-holder-by-time').css('height', height + 'px');
+ postHolder.css('height', height + 'px');
}
- });
- $(postHolder).scroll(function scroll() {
- if (!self.preventScrollTrigger) {
- self.scrollPosition = $(postHolder).scrollTop() + $(postHolder).innerHeight();
+ if (!this.scrolled) {
+ this.scrollToBottom();
}
- self.preventScrollTrigger = false;
- });
+ }.bind(this));
+
+ postHolder.scroll(function scroll() {
+ var position = postHolder.scrollTop() + postHolder.height() + 14;
+ var bottom = postHolder[0].scrollHeight;
+
+ if (position >= bottom) {
+ this.scrolled = false;
+ } else {
+ this.scrolled = true;
+ }
+
+ if (this.isUserScroll) {
+ this.userHasSeenNew = true;
+ }
+ this.isUserScroll = true;
+ }.bind(this));
$('body').on('click.userpopover', function popOver(e) {
if ($(e.target).attr('data-toggle') !== 'popover' &&
@@ -163,76 +155,101 @@ module.exports = React.createClass({
$(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment');
}
});
- },
- componentDidUpdate: function() {
- this.resize();
- var postHolder = $('.post-list-holder-by-time')[0];
- this.scrollPosition = $(postHolder).scrollTop() + $(postHolder).innerHeight();
- this.oldScrollHeight = postHolder.scrollHeight;
+
+ this.scrollToBottom();
+ setTimeout(this.scrollToBottom, 100);
+ }
+ componentDidUpdate(prevProps, prevState) {
$('.post-list__content div .post').removeClass('post--last');
$('.post-list__content div:last-child .post').addClass('post--last');
- },
- componentWillUnmount: function() {
+
+ if (this.state.postList == null || prevState.postList == null) {
+ this.scrollToBottom();
+ return;
+ }
+
+ var order = this.state.postList.order || [];
+ var posts = this.state.postList.posts || {};
+ var oldOrder = prevState.postList.order || [];
+ var oldPosts = prevState.postList.posts || {};
+ var userId = UserStore.getCurrentId();
+ var firstPost = posts[order[0]] || {};
+ var isNewPost = oldOrder.indexOf(order[0]) === -1;
+
+ if (this.state.channel.id !== prevState.channel.id) {
+ this.scrollToBottom();
+ } else if (oldOrder.length === 0) {
+ this.scrollToBottom();
+
+ // the user is scrolled to the bottom
+ } else if (!this.scrolled) {
+ this.scrollToBottom();
+
+ // there's a new post and
+ // it's by the user and not a comment
+ } else if (isNewPost &&
+ userId === firstPost.user_id &&
+ !utils.isComment(firstPost)) {
+ this.state.lastViewed = utils.getTimestamp();
+ this.scrollToBottom(true);
+
+ // the user clicked 'load more messages'
+ } else if (this.gotMorePosts) {
+ var lastPost = oldPosts[oldOrder[prevState.numToDisplay]];
+ $('#' + lastPost.id)[0].scrollIntoView();
+ } else {
+ this.scrollTo(this.prevScrollTop);
+ }
+ }
+ componentWillUpdate() {
+ var postHolder = $('.post-list-holder-by-time');
+ this.prevScrollTop = postHolder.scrollTop();
+ }
+ componentWillUnmount() {
PostStore.removeChangeListener(this.onChange);
ChannelStore.removeChangeListener(this.onChange);
UserStore.removeStatusesChangeListener(this.onTimeChange);
SocketStore.removeChangeListener(this.onSocketChange);
$('body').off('click.userpopover');
$('.modal').off('show.bs.modal');
- },
- resize: function() {
- var postHolder = $('.post-list-holder-by-time')[0];
- this.preventScrollTrigger = true;
- if (this.gotMorePosts) {
- this.gotMorePosts = false;
- $(postHolder).scrollTop($(postHolder).scrollTop() + (postHolder.scrollHeight - this.oldScrollHeight));
- } else if ($('#new_message')[0] && !this.scrolledToNew) {
- $(postHolder).scrollTop($(postHolder).scrollTop() + $('#new_message').offset().top - 63);
- this.scrolledToNew = true;
+ }
+ scrollTo(val) {
+ this.isUserScroll = false;
+ var postHolder = $('.post-list-holder-by-time');
+ postHolder[0].scrollTop = val;
+ }
+ scrollToBottom(force) {
+ this.isUserScroll = false;
+ var postHolder = $('.post-list-holder-by-time');
+ if ($('#new_message')[0] && !this.userHasSeenNew && !force) {
+ $('#new_message')[0].scrollIntoView();
} else {
- $(postHolder).scrollTop(postHolder.scrollHeight);
+ postHolder.addClass('hide-scroll');
+ postHolder[0].scrollTop = postHolder[0].scrollHeight;
+ postHolder.removeClass('hide-scroll');
}
- $(postHolder).perfectScrollbar('update');
- },
- onChange: function() {
- var newState = getStateFromStores();
+ }
+ onChange() {
+ var newState = this.getStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
- if (this.state.postList && this.state.postList.order) {
- if (this.state.channel.id === newState.channel.id && this.state.postList.order.length !== newState.postList.order.length && newState.postList.order.length > Constants.POST_CHUNK_SIZE) {
- this.gotMorePosts = true;
- }
- }
if (this.state.channel.id !== newState.channel.id) {
PostStore.clearUnseenDeletedPosts(this.state.channel.id);
- this.scrolledToNew = false;
+ this.userHasSeenNew = false;
+ newState.numToDisplay = Constants.POST_CHUNK_SIZE;
+ } else {
+ newState.lastViewed = this.state.lastViewed;
}
+
this.setState(newState);
}
- },
- onSocketChange: function(msg) {
+ }
+ onSocketChange(msg) {
var postList;
var post;
- if (msg.action === 'posted') {
+ if (msg.action === 'posted' || msg.action === 'post_edited') {
post = JSON.parse(msg.props.post);
PostStore.storePost(post);
- } else if (msg.action === 'post_edited') {
- if (this.state.channel.id === msg.channel_id) {
- postList = this.state.postList;
- if (!(msg.props.post_id in postList.posts)) {
- return;
- }
-
- post = postList.posts[msg.props.post_id];
- post.message = msg.props.message;
-
- postList.posts[post.id] = post;
- this.setState({postList: postList});
-
- PostStore.storePosts(msg.channel_id, postList);
- } else {
- AsyncClient.getPosts(true, msg.channel_id);
- }
} else if (msg.action === 'post_deleted') {
var activeRoot = $(document.activeElement).closest('.comment-create-body')[0];
var activeRootPostId = '';
@@ -244,16 +261,8 @@ module.exports = React.createClass({
postList = this.state.postList;
PostStore.storeUnseenDeletedPost(post);
-
- if (postList.posts[post.id]) {
- delete postList.posts[post.id];
- var index = postList.order.indexOf(post.id);
- if (index > -1) {
- postList.order.splice(index, 1);
- }
-
- PostStore.storePosts(msg.channel_id, postList);
- }
+ PostStore.removePost(post, true);
+ PostStore.emitChange();
if (activeRootPostId === msg.props.post_id && UserStore.getCurrentId() !== msg.user_id) {
$('#post_deleted').modal('show');
@@ -261,8 +270,8 @@ module.exports = React.createClass({
} else if (msg.action === 'new_user') {
AsyncClient.getProfiles();
}
- },
- onTimeChange: function() {
+ }
+ onTimeChange() {
if (!this.state.postList) {
return;
}
@@ -273,11 +282,256 @@ module.exports = React.createClass({
}
this.refs[id].forceUpdateInfo();
}
- },
- getMorePosts: function(e) {
- e.preventDefault();
+ }
+ createDMIntroMessage(channel) {
+ var teammate = utils.getDirectTeammate(channel.id);
- if (!this.state.postList) {
+ if (teammate) {
+ var teammateName = teammate.username;
+ if (teammate.nickname.length > 0) {
+ teammateName = teammate.nickname;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ {'This is the start of your private message history with ' + teammateName + '.'}
+ {'Private messages and files shared here are not shown to people outside this area.'}
+
+
+ Set a description
+
+
+ );
+ }
+
+ return (
+
+
{'This is the start of your private message history with this ' + strings.Team + 'mate. Private messages and files shared here are not shown to people outside this area.'}
+
+ );
+ }
+ createChannelIntroMessage(channel) {
+ if (channel.type === 'D') {
+ return this.createDMIntroMessage(channel);
+ } else if (ChannelStore.isDefault(channel)) {
+ return this.createDefaultIntroMessage(channel);
+ } else if (channel.name === Constants.OFFTOPIC_CHANNEL) {
+ return this.createOffTopicIntroMessage(channel);
+ } else if (channel.type === 'O' || channel.type === 'P') {
+ return this.createStandardIntroMessage(channel);
+ }
+ }
+ createDefaultIntroMessage(channel) {
+ return (
+
+
Beginning of {channel.display_name}
+
+ Welcome to {channel.display_name}!
+
+ This is the first channel {strings.Team}mates see when they
+
+ sign up - use it for posting updates everyone needs to know.
+
+ To create a new channel or join an existing one, go to
+
+ the Left Hand Sidebar under “Channels” and click “More…”.
+
+
+
+ );
+ }
+ createOffTopicIntroMessage(channel) {
+ return (
+
+
Beginning of {channel.display_name}
+
+ {'This is the start of ' + channel.display_name + ', a channel for non-work-related conversations.'}
+
+
+
+ Set a description
+
+
+ );
+ }
+ getChannelCreator(channel) {
+ if (channel.creator_id.length > 0) {
+ var creator = UserStore.getProfile(channel.creator_id);
+ if (creator) {
+ return creator.username;
+ }
+ }
+
+ var members = ChannelStore.getCurrentExtraInfo().members;
+ for (var i = 0; i < members.length; i++) {
+ if (members[i].roles.indexOf('admin') > -1) {
+ return members[i].username;
+ }
+ }
+ }
+ createStandardIntroMessage(channel) {
+ var uiName = channel.display_name;
+ var creatorName = '';
+
+ var uiType;
+ var memberMessage;
+ if (channel.type === 'P') {
+ uiType = 'private group';
+ memberMessage = ' Only invited members can see this private group.';
+ } else {
+ uiType = 'channel';
+ memberMessage = ' Any member can join and read this channel.';
+ }
+
+ var createMessage;
+ if (creatorName !== '') {
+ createMessage = (
This is the start of the {uiName} {uiType}, created by {creatorName} on {utils.displayDate(channel.create_at)} );
+ } else {
+ createMessage = 'This is the start of the ' + uiName + ' ' + uiType + ', created on ' + utils.displayDate(channel.create_at) + '.';
+ }
+
+ return (
+
+ );
+ }
+ createPosts(posts, order) {
+ var postCtls = [];
+ var previousPostDay = new Date(0);
+ var userId = UserStore.getCurrentId();
+
+ var renderedLastViewed = false;
+
+ var numToDisplay = this.state.numToDisplay;
+ if (order.length - 1 < numToDisplay) {
+ numToDisplay = order.length - 1;
+ }
+
+ for (var i = numToDisplay; i >= 0; i--) {
+ var post = posts[order[i]];
+ var parentPost = posts[post.parent_id];
+
+ var sameUser = false;
+ var sameRoot = false;
+ var hideProfilePic = false;
+ var prevPost = posts[order[i + 1]];
+
+ if (prevPost) {
+ sameUser = prevPost.user_id === post.user_id && post.create_at - prevPost.create_at <= 1000 * 60 * 5;
+
+ sameRoot = utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id);
+
+ // we only hide the profile pic if the previous post is not a comment, the current post is not a comment, and the previous post was made by the same user as the current post
+ hideProfilePic = (prevPost.user_id === post.user_id) && !utils.isComment(prevPost) && !utils.isComment(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
+ var isLastComment = utils.isComment(post) && (i === 0 || posts[order[i - 1]].root_id !== post.root_id);
+
+ var postCtl = (
+
+ );
+
+ let currentPostDay = utils.getDateForUnixTicks(post.create_at);
+ if (currentPostDay.toDateString() !== previousPostDay.toDateString()) {
+ postCtls.push(
+
+
+
{currentPostDay.toDateString()}
+
+ );
+ }
+
+ if (post.user_id !== userId && post.create_at > this.state.lastViewed && !renderedLastViewed) {
+ renderedLastViewed = true;
+ postCtls.push(
+
+ );
+ }
+ postCtls.push(postCtl);
+ previousPostDay = currentPostDay;
+ }
+
+ return postCtls;
+ }
+ loadMorePosts() {
+ if (this.state.postList == null) {
return;
}
@@ -287,257 +541,72 @@ module.exports = React.createClass({
$(this.refs.loadmore.getDOMNode()).text('Retrieving more messages...');
- var self = this;
- var currentPos = $('.post-list').scrollTop;
+ Client.getPostsPage(
+ channelId,
+ order.length,
+ Constants.POST_CHUNK_SIZE,
+ function success(data) {
+ $(this.refs.loadmore.getDOMNode()).text('Load more messages');
+ this.gotMorePosts = true;
+ this.setState({numToDisplay: this.state.numToDisplay + Constants.POST_CHUNK_SIZE});
- Client.getPosts(
- channelId,
- order.length,
- Constants.POST_CHUNK_SIZE,
- function success(data) {
- $(self.refs.loadmore.getDOMNode()).text('Load more messages');
-
- if (!data) {
- return;
- }
-
- if (data.order.length === 0) {
- return;
- }
-
- var postList = {};
- postList.posts = $.extend(posts, data.posts);
- postList.order = order.concat(data.order);
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_POSTS,
- id: channelId,
- postList: postList
- });
-
- Client.getProfiles();
- $('.post-list').scrollTop(currentPos);
- },
- function fail(err) {
- $(self.refs.loadmore.getDOMNode()).text('Load more messages');
- AsyncClient.dispatchError(err, 'getPosts');
+ if (!data) {
+ return;
}
- );
- },
- getInitialState: function() {
- return getStateFromStores();
- },
- render: function() {
+
+ if (data.order.length === 0) {
+ return;
+ }
+
+ var postList = {};
+ postList.posts = $.extend(posts, data.posts);
+ postList.order = order.concat(data.order);
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_POSTS,
+ id: channelId,
+ post_list: postList
+ });
+
+ Client.getProfiles();
+ }.bind(this),
+ function fail(err) {
+ $(this.refs.loadmore.getDOMNode()).text('Load more messages');
+ AsyncClient.dispatchError(err, 'getPosts');
+ }.bind(this)
+ );
+ }
+ render() {
var order = [];
var posts;
-
- var lastViewed = Number.MAX_VALUE;
-
- if (ChannelStore.getCurrentMember() != null) {
- lastViewed = ChannelStore.getCurrentMember().last_viewed_at;
- }
+ var channel = this.state.channel;
if (this.state.postList != null) {
posts = this.state.postList.posts;
order = this.state.postList.order;
}
- var renderedLastViewed = false;
-
- var userId = '';
- if (UserStore.getCurrentId()) {
- userId = UserStore.getCurrentId();
- } else {
- return
;
- }
-
- var channel = this.state.channel;
-
var moreMessages =
Beginning of Channel
;
-
- var userStyle = {color: UserStore.getCurrentUser().props.theme};
-
if (channel != null) {
- if (order.length > 0 && order.length % Constants.POST_CHUNK_SIZE === 0) {
- moreMessages =
Load more messages ;
- } else if (channel.type === 'D') {
- var teammate = utils.getDirectTeammate(channel.id);
-
- if (teammate) {
- var teammateName = teammate.username;
- if (teammate.nickname.length > 0) {
- teammateName = teammate.nickname;
- }
-
- moreMessages = (
-
-
-
-
-
-
-
-
- This is the start of your private message history with {teammateName} .
- Private messages and files shared here are not shown to people outside this area.
-
-
Set a description
-
- );
- } else {
- moreMessages = (
-
-
{'This is the start of your private message history with this ' + strings.Team + 'mate. Private messages and files shared here are not shown to people outside this area.'}
-
- );
- }
- } else if (channel.type === 'P' || channel.type === 'O') {
- var uiName = channel.display_name;
- var creatorName = '';
-
- if (channel.creator_id.length > 0) {
- var creator = UserStore.getProfile(channel.creator_id);
- if (creator) {
- creatorName = creator.username;
- }
- }
-
- if (creatorName === '') {
- var members = ChannelStore.getCurrentExtraInfo().members;
- for (var i = 0; i < members.length; i++) {
- if (members[i].roles.indexOf('admin') > -1) {
- creatorName = members[i].username;
- break;
- }
- }
- }
-
- if (ChannelStore.isDefault(channel)) {
- moreMessages = (
-
-
Beginning of {uiName}
-
- Welcome to {uiName} !
-
- This is the first channel {strings.Team}mates see when they
-
- sign up - use it for posting updates everyone needs to know.
-
- To create a new channel or join an existing one, go to
-
- the Left Hand Sidebar under “Channels” and click “More…”.
-
-
-
- );
- } else if (channel.name === Constants.OFFTOPIC_CHANNEL) {
- moreMessages = (
-
-
Beginning of {uiName}
-
- This is the start of {uiName} , a channel for non-work-related conversations.
-
-
-
Set a description
-
- );
- } else {
- var uiType;
- var memberMessage;
- if (channel.type === 'P') {
- uiType = 'private group';
- memberMessage = ' Only invited members can see this private group.';
- } else {
- uiType = 'channel';
- memberMessage = ' Any member can join and read this channel.';
- }
-
- var createMessage;
- if (creatorName !== '') {
- createMessage = (
This is the start of the {uiName} {uiType}, created by {creatorName} on {utils.displayDate(channel.create_at)} );
- } else {
- createMessage = 'This is the start of the ' + uiName + ' ' + uiType + ', created on ' + utils.displayDate(channel.create_at) + '.';
- }
-
- moreMessages = (
-
- );
- }
+ if (order.length > this.state.numToDisplay) {
+ moreMessages = (
+
+ Load more messages
+
+ );
+ } else {
+ moreMessages = this.createChannelIntroMessage(channel);
}
}
var postCtls = [];
-
if (posts) {
- var previousPostDay = new Date(0);
- var currentPostDay;
-
- for (var i = order.length - 1; i >= 0; i--) {
- var post = posts[order[i]];
- var parentPost = null;
- if (post.parent_id) {
- parentPost = posts[post.parent_id];
- }
-
- var sameUser = '';
- var sameRoot = false;
- var hideProfilePic = false;
- var prevPost;
- if (i < order.length - 1) {
- prevPost = posts[order[i + 1]];
- }
-
- if (prevPost) {
- if ((prevPost.user_id === post.user_id) && (post.create_at - prevPost.create_at <= 1000 * 60 * 5)) {
- sameUser = 'same--user';
- }
- sameRoot = utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id);
-
- // we only hide the profile pic if the previous post is not a comment, the current post is not a comment, and the previous post was made by the same user as the current post
- hideProfilePic = (prevPost.user_id === post.user_id) && !utils.isComment(prevPost) && !utils.isComment(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
- var isLastComment = utils.isComment(post) && (i === 0 || posts[order[i - 1]].root_id !== post.root_id);
-
- var postCtl = (
-
- );
-
- currentPostDay = utils.getDateForUnixTicks(post.create_at);
- if (currentPostDay.toDateString() !== previousPostDay.toDateString()) {
- postCtls.push(
-
-
-
{currentPostDay.toDateString()}
-
- );
- }
-
- if (post.user_id !== userId && post.create_at > lastViewed && !renderedLastViewed) {
- renderedLastViewed = true;
- postCtls.push(
-
- );
- }
- postCtls.push(postCtl);
- previousPostDay = currentPostDay;
- }
+ postCtls = this.createPosts(posts, order);
} else {
postCtls.push(
);
}
@@ -553,4 +622,4 @@ module.exports = React.createClass({
);
}
-});
+}
diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx
deleted file mode 100644
index fb45ad28e9..0000000000
--- a/web/react/components/post_right.jsx
+++ /dev/null
@@ -1,411 +0,0 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-var PostStore = require('../stores/post_store.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var UserProfile = require('./user_profile.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var utils = require('../utils/utils.jsx');
-var SearchBox = require('./search_bar.jsx');
-var CreateComment = require('./create_comment.jsx');
-var Constants = require('../utils/constants.jsx');
-var FileAttachmentList = require('./file_attachment_list.jsx');
-var FileUploadOverlay = require('./file_upload_overlay.jsx');
-var client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var ActionTypes = Constants.ActionTypes;
-
-RhsHeaderPost = React.createClass({
- handleClose: function(e) {
- e.preventDefault();
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_SEARCH,
- results: null
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_SEARCH_TERM,
- term: null,
- do_search: false,
- is_mention_search: false
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_POST_SELECTED,
- results: null
- });
- },
- handleBack: function(e) {
- e.preventDefault();
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_SEARCH_TERM,
- term: this.props.fromSearch,
- do_search: true,
- is_mention_search: this.props.isMentionSearch
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_POST_SELECTED,
- results: null
- });
- },
- render: function() {
- var back;
- if (this.props.fromSearch) {
- back =
;
- }
-
- return (
-
- {back}Message Details
-
-
- );
- }
-});
-
-RootPost = React.createClass({
- render: function() {
- var post = this.props.post;
- var message = utils.textToJsx(post.message);
- var isOwner = UserStore.getCurrentId() === post.user_id;
- var timestamp = UserStore.getProfile(post.user_id).update_at;
- var channel = ChannelStore.get(post.channel_id);
-
- var type = 'Post';
- if (post.root_id.length > 0) {
- type = 'Comment';
- }
-
- var currentUserCss = '';
- if (UserStore.getCurrentId() === post.user_id) {
- currentUserCss = 'current--user';
- }
-
- var channelName;
- if (channel) {
- if (channel.type === 'D') {
- channelName = 'Private Message';
- } else {
- channelName = channel.display_name;
- }
- }
-
- var ownerOptions;
- if (isOwner) {
- ownerOptions = (
-
- );
- }
-
- var fileAttachment;
- if (post.filenames && post.filenames.length > 0) {
- fileAttachment = (
-
- );
- }
-
- return (
-
-
{ channelName }
-
-
-
-
-
-
- {utils.displayCommentDateTime(post.create_at)}
-
-
- {ownerOptions}
-
-
-
-
-
{message}
- {fileAttachment}
-
-
-
-
- );
- }
-});
-
-CommentPost = React.createClass({
- retryComment: function(e) {
- e.preventDefault();
-
- var post = this.props.post;
- client.createPost(post, post.channel_id,
- function success(data) {
- AsyncClient.getPosts(true);
-
- var channel = ChannelStore.get(post.channel_id);
- var member = ChannelStore.getMember(post.channel_id);
- member.msg_count = channel.total_msg_count;
- member.last_viewed_at = (new Date()).getTime();
- ChannelStore.setChannelMember(member);
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_POST,
- post: data
- });
- }.bind(this),
- function fail() {
- post.state = Constants.POST_FAILED;
- PostStore.updatePendingPost(post);
- this.forceUpdate();
- }.bind(this)
- );
-
- post.state = Constants.POST_LOADING;
- PostStore.updatePendingPost(post);
- this.forceUpdate();
- },
- render: function() {
- var post = this.props.post;
-
- var currentUserCss = '';
- if (UserStore.getCurrentId() === post.user_id) {
- currentUserCss = 'current--user';
- }
-
- var isOwner = UserStore.getCurrentId() === post.user_id;
-
- var type = 'Post';
- if (post.root_id.length > 0) {
- type = 'Comment';
- }
-
- var message = utils.textToJsx(post.message);
- var timestamp = UserStore.getCurrentUser().update_at;
-
- var loading;
- var postClass = '';
- if (post.state === Constants.POST_FAILED) {
- postClass += ' post-fail';
- loading =
Retry ;
- } else if (post.state === Constants.POST_LOADING) {
- postClass += ' post-waiting';
- loading =
;
- }
-
- var ownerOptions;
- if (isOwner && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) {
- ownerOptions = (
-
- );
- }
-
- var fileAttachment;
- if (post.filenames && post.filenames.length > 0) {
- fileAttachment = (
-
- );
- }
-
- return (
-
-
-
-
-
-
-
- {utils.displayCommentDateTime(post.create_at)}
-
- {ownerOptions}
-
-
-
-
{loading}{message}
- {fileAttachment}
-
-
-
- );
- }
-});
-
-function getStateFromStores() {
- var postList = PostStore.getSelectedPost();
- if (!postList || postList.order.length < 1) {
- return {postList: {}};
- }
-
- var channelId = postList.posts[postList.order[0]].channel_id;
- var pendingPostList = PostStore.getPendingPosts(channelId);
-
- if (pendingPostList) {
- for (var pid in pendingPostList.posts) {
- postList.posts[pid] = pendingPostList.posts[pid];
- }
- }
-
- return {postList: postList};
-}
-
-module.exports = React.createClass({
- componentDidMount: function() {
- PostStore.addSelectedPostChangeListener(this.onChange);
- PostStore.addChangeListener(this.onChangeAll);
- UserStore.addStatusesChangeListener(this.onTimeChange);
- this.resize();
- var self = this;
- $(window).resize(function() {
- self.resize();
- });
- },
- componentDidUpdate: function() {
- $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight);
- $('.post-right__scroll').perfectScrollbar('update');
- this.resize();
- },
- componentWillUnmount: function() {
- PostStore.removeSelectedPostChangeListener(this.onChange);
- PostStore.removeChangeListener(this.onChangeAll);
- UserStore.removeStatusesChangeListener(this.onTimeChange);
- },
- onChange: function() {
- if (this.isMounted()) {
- var newState = getStateFromStores();
- if (!utils.areStatesEqual(newState, this.state)) {
- this.setState(newState);
- }
- }
- },
- onChangeAll: function() {
- if (this.isMounted()) {
- // if something was changed in the channel like adding a
- // comment or post then lets refresh the sidebar list
- var currentSelected = PostStore.getSelectedPost();
- if (!currentSelected || currentSelected.order.length === 0) {
- return;
- }
-
- var currentPosts = PostStore.getPosts(currentSelected.posts[currentSelected.order[0]].channel_id);
-
- if (!currentPosts || currentPosts.order.length === 0) {
- return;
- }
-
- if (currentPosts.posts[currentPosts.order[0]].channel_id === currentSelected.posts[currentSelected.order[0]].channel_id) {
- currentSelected.posts = {};
- for (var postId in currentPosts.posts) {
- currentSelected.posts[postId] = currentPosts.posts[postId];
- }
-
- PostStore.storeSelectedPost(currentSelected);
- }
-
- this.setState(getStateFromStores());
- }
- },
- onTimeChange: function() {
- for (var id in this.state.postList.posts) {
- if (!this.refs[id]) {
- continue;
- }
- this.refs[id].forceUpdate();
- }
- },
- getInitialState: function() {
- return getStateFromStores();
- },
- resize: function() {
- var height = $(window).height() - $('#error_bar').outerHeight() - 100;
- $('.post-right__scroll').css('height', height + 'px');
- $('.post-right__scroll').scrollTop(100000);
- $('.post-right__scroll').perfectScrollbar();
- $('.post-right__scroll').perfectScrollbar('update');
- },
- render: function() {
- var postList = this.state.postList;
-
- if (postList == null) {
- return (
-
- );
- }
-
- var selectedPost = postList.posts[postList.order[0]];
- var rootPost = null;
-
- if (selectedPost.root_id === '') {
- rootPost = selectedPost;
- } else {
- rootPost = postList.posts[selectedPost.root_id];
- }
-
- var postsArray = [];
-
- for (var postId in postList.posts) {
- var cpost = postList.posts[postId];
- if (cpost.root_id === rootPost.id) {
- postsArray.push(cpost);
- }
- }
-
- postsArray.sort(function postSort(a, b) {
- if (a.create_at < b.create_at) {
- return -1;
- }
- if (a.create_at > b.create_at) {
- return 1;
- }
- return 0;
- });
-
- var currentId = UserStore.getCurrentId();
- var searchForm;
- if (currentId != null) {
- searchForm =
;
- }
-
- return (
-
-
-
{searchForm}
-
-
-
-
-
- {postsArray.map(function mapPosts(comPost) {
- return ;
- })}
-
-
-
-
-
-
-
- );
- }
-});
diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx
new file mode 100644
index 0000000000..7df2fed9eb
--- /dev/null
+++ b/web/react/components/rhs_comment.jsx
@@ -0,0 +1,207 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var PostStore = require('../stores/post_store.jsx');
+var ChannelStore = require('../stores/channel_store.jsx');
+var UserProfile = require('./user_profile.jsx');
+var UserStore = require('../stores/user_store.jsx');
+var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+var utils = require('../utils/utils.jsx');
+var Constants = require('../utils/constants.jsx');
+var FileAttachmentList = require('./file_attachment_list.jsx');
+var client = require('../utils/client.jsx');
+var AsyncClient = require('../utils/async_client.jsx');
+var ActionTypes = Constants.ActionTypes;
+
+export default class RhsComment extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.retryComment = this.retryComment.bind(this);
+
+ this.state = {};
+ }
+ retryComment(e) {
+ e.preventDefault();
+
+ var post = this.props.post;
+ client.createPost(post, post.channel_id,
+ function success(data) {
+ AsyncClient.getPosts(post.channel_id);
+
+ var channel = ChannelStore.get(post.channel_id);
+ var member = ChannelStore.getMember(post.channel_id);
+ member.msg_count = channel.total_msg_count;
+ member.last_viewed_at = (new Date()).getTime();
+ ChannelStore.setChannelMember(member);
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_POST,
+ post: data
+ });
+ },
+ function fail() {
+ post.state = Constants.POST_FAILED;
+ PostStore.updatePendingPost(post);
+ this.forceUpdate();
+ }.bind(this)
+ );
+
+ post.state = Constants.POST_LOADING;
+ PostStore.updatePendingPost(post);
+ this.forceUpdate();
+ }
+ shouldComponentUpdate(nextProps) {
+ if (!utils.areStatesEqual(nextProps.post, this.props.post)) {
+ return true;
+ }
+
+ return false;
+ }
+ render() {
+ var post = this.props.post;
+
+ var currentUserCss = '';
+ if (UserStore.getCurrentId() === post.user_id) {
+ currentUserCss = 'current--user';
+ }
+
+ var isOwner = UserStore.getCurrentId() === post.user_id;
+
+ var type = 'Post';
+ if (post.root_id.length > 0) {
+ type = 'Comment';
+ }
+
+ var message = utils.textToJsx(post.message);
+ var timestamp = UserStore.getCurrentUser().update_at;
+
+ var loading;
+ var postClass = '';
+ if (post.state === Constants.POST_FAILED) {
+ postClass += ' post-fail';
+ loading = (
+
+ Retry
+
+ );
+ } else if (post.state === Constants.POST_LOADING) {
+ postClass += ' post-waiting';
+ loading = (
+
+ );
+ }
+
+ var ownerOptions;
+ if (isOwner && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) {
+ ownerOptions = (
+
+ );
+ }
+
+ var fileAttachment;
+ if (post.filenames && post.filenames.length > 0) {
+ fileAttachment = (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {utils.displayCommentDateTime(post.create_at)}
+
+
+
+ {ownerOptions}
+
+
+
+
{loading}{message}
+ {fileAttachment}
+
+
+
+ );
+ }
+}
+
+RhsComment.defaultProps = {
+ post: null
+};
+RhsComment.propTypes = {
+ post: React.PropTypes.object
+};
diff --git a/web/react/components/rhs_header_post.jsx b/web/react/components/rhs_header_post.jsx
new file mode 100644
index 0000000000..4cf4231e91
--- /dev/null
+++ b/web/react/components/rhs_header_post.jsx
@@ -0,0 +1,81 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+var Constants = require('../utils/constants.jsx');
+var ActionTypes = Constants.ActionTypes;
+
+export default class RhsHeaderPost extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleClose = this.handleClose.bind(this);
+ this.handleBack = this.handleBack.bind(this);
+
+ this.state = {};
+ }
+ handleClose(e) {
+ e.preventDefault();
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_SEARCH,
+ results: null
+ });
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_POST_SELECTED,
+ results: null
+ });
+ }
+ handleBack(e) {
+ e.preventDefault();
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_SEARCH_TERM,
+ term: this.props.fromSearch,
+ do_search: true,
+ is_mention_search: this.props.isMentionSearch
+ });
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_POST_SELECTED,
+ results: null
+ });
+ }
+ render() {
+ var back;
+ if (this.props.fromSearch) {
+ back = (
+
+
+
+ );
+ }
+
+ return (
+
+ {back}Message Details
+
+
+
+ );
+ }
+}
+
+RhsHeaderPost.defaultProps = {
+ isMentionSearch: false,
+ fromSearch: ''
+};
+RhsHeaderPost.propTypes = {
+ isMentionSearch: React.PropTypes.bool,
+ fromSearch: React.PropTypes.string
+};
diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx
new file mode 100644
index 0000000000..a407e6470d
--- /dev/null
+++ b/web/react/components/rhs_root_post.jsx
@@ -0,0 +1,145 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var ChannelStore = require('../stores/channel_store.jsx');
+var UserProfile = require('./user_profile.jsx');
+var UserStore = require('../stores/user_store.jsx');
+var utils = require('../utils/utils.jsx');
+var FileAttachmentList = require('./file_attachment_list.jsx');
+
+export default class RhsRootPost extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ }
+ shouldComponentUpdate(nextProps) {
+ if (!utils.areStatesEqual(nextProps.post, this.props.post)) {
+ return true;
+ }
+
+ return false;
+ }
+ render() {
+ var post = this.props.post;
+ var message = utils.textToJsx(post.message);
+ var isOwner = UserStore.getCurrentId() === post.user_id;
+ var timestamp = UserStore.getProfile(post.user_id).update_at;
+ var channel = ChannelStore.get(post.channel_id);
+
+ var type = 'Post';
+ if (post.root_id.length > 0) {
+ type = 'Comment';
+ }
+
+ var currentUserCss = '';
+ if (UserStore.getCurrentId() === post.user_id) {
+ currentUserCss = 'current--user';
+ }
+
+ var channelName;
+ if (channel) {
+ if (channel.type === 'D') {
+ channelName = 'Private Message';
+ } else {
+ channelName = channel.display_name;
+ }
+ }
+
+ var ownerOptions;
+ if (isOwner) {
+ ownerOptions = (
+
+ );
+ }
+
+ var fileAttachment;
+ if (post.filenames && post.filenames.length > 0) {
+ fileAttachment = (
+
+ );
+ }
+
+ return (
+
+
{channelName}
+
+
+
+
+
+
+ {utils.displayCommentDateTime(post.create_at)}
+
+
+ {ownerOptions}
+
+
+
+
+
{message}
+ {fileAttachment}
+
+
+
+
+ );
+ }
+}
+
+RhsRootPost.defaultProps = {
+ post: null,
+ commentCount: 0
+};
+RhsRootPost.propTypes = {
+ post: React.PropTypes.object,
+ commentCount: React.PropTypes.number
+};
diff --git a/web/react/components/rhs_thread.jsx b/web/react/components/rhs_thread.jsx
new file mode 100644
index 0000000000..adddeccf07
--- /dev/null
+++ b/web/react/components/rhs_thread.jsx
@@ -0,0 +1,215 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var PostStore = require('../stores/post_store.jsx');
+var UserStore = require('../stores/user_store.jsx');
+var utils = require('../utils/utils.jsx');
+var SearchBox = require('./search_bar.jsx');
+var CreateComment = require('./create_comment.jsx');
+var RhsHeaderPost = require('./rhs_header_post.jsx');
+var RootPost = require('./rhs_root_post.jsx');
+var Comment = require('./rhs_comment.jsx');
+var Constants = require('../utils/constants.jsx');
+var FileUploadOverlay = require('./file_upload_overlay.jsx');
+
+export default class RhsThread extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onChange = this.onChange.bind(this);
+ this.onChangeAll = this.onChangeAll.bind(this);
+ this.onTimeChange = this.onTimeChange.bind(this);
+
+ this.state = this.getStateFromStores();
+ }
+ getStateFromStores() {
+ var postList = PostStore.getSelectedPost();
+ if (!postList || postList.order.length < 1) {
+ return {postList: {}};
+ }
+
+ var channelId = postList.posts[postList.order[0]].channel_id;
+ var pendingPostList = PostStore.getPendingPosts(channelId);
+
+ if (pendingPostList) {
+ for (var pid in pendingPostList.posts) {
+ postList.posts[pid] = pendingPostList.posts[pid];
+ }
+ }
+
+ return {postList: postList};
+ }
+ componentDidMount() {
+ PostStore.addSelectedPostChangeListener(this.onChange);
+ PostStore.addChangeListener(this.onChangeAll);
+ UserStore.addStatusesChangeListener(this.onTimeChange);
+ this.resize();
+ $(window).resize(function resize() {
+ this.resize();
+ }.bind(this));
+ }
+ componentDidUpdate() {
+ $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight);
+ $('.post-right__scroll').perfectScrollbar('update');
+ this.resize();
+ }
+ componentWillUnmount() {
+ PostStore.removeSelectedPostChangeListener(this.onChange);
+ PostStore.removeChangeListener(this.onChangeAll);
+ UserStore.removeStatusesChangeListener(this.onTimeChange);
+ }
+ onChange() {
+ var newState = this.getStateFromStores();
+ if (!utils.areStatesEqual(newState, this.state)) {
+ this.setState(newState);
+ }
+ }
+ onChangeAll() {
+ // if something was changed in the channel like adding a
+ // comment or post then lets refresh the sidebar list
+ var currentSelected = PostStore.getSelectedPost();
+ if (!currentSelected || currentSelected.order.length === 0) {
+ return;
+ }
+
+ var currentPosts = PostStore.getPosts(currentSelected.posts[currentSelected.order[0]].channel_id);
+
+ if (!currentPosts || currentPosts.order.length === 0) {
+ return;
+ }
+
+ if (currentPosts.posts[currentPosts.order[0]].channel_id === currentSelected.posts[currentSelected.order[0]].channel_id) {
+ currentSelected.posts = {};
+ for (var postId in currentPosts.posts) {
+ currentSelected.posts[postId] = currentPosts.posts[postId];
+ }
+
+ PostStore.storeSelectedPost(currentSelected);
+ }
+
+ var newState = this.getStateFromStores();
+ if (!utils.areStatesEqual(newState, this.state)) {
+ this.setState(newState);
+ }
+ }
+ onTimeChange() {
+ for (var id in this.state.postList.posts) {
+ if (!this.refs[id]) {
+ continue;
+ }
+ this.refs[id].forceUpdate();
+ }
+ }
+ resize() {
+ var height = $(window).height() - $('#error_bar').outerHeight() - 100;
+ $('.post-right__scroll').css('height', height + 'px');
+ $('.post-right__scroll').scrollTop(100000);
+ $('.post-right__scroll').perfectScrollbar();
+ $('.post-right__scroll').perfectScrollbar('update');
+ }
+ render() {
+ var postList = this.state.postList;
+
+ if (postList == null) {
+ return (
+
+ );
+ }
+
+ var selectedPost = postList.posts[postList.order[0]];
+ var rootPost = null;
+
+ if (selectedPost.root_id === '') {
+ rootPost = selectedPost;
+ } else {
+ rootPost = postList.posts[selectedPost.root_id];
+ }
+
+ var postsArray = [];
+
+ for (var postId in postList.posts) {
+ var cpost = postList.posts[postId];
+ if (cpost.root_id === rootPost.id) {
+ postsArray.push(cpost);
+ }
+ }
+
+ // sort failed posts to bottom, followed by pending, and then regular posts
+ postsArray.sort(function postSort(a, b) {
+ if ((a.state === Constants.POST_LOADING || a.state === Constants.POST_FAILED) && (b.state !== Constants.POST_LOADING && b.state !== Constants.POST_FAILED)) {
+ return 1;
+ }
+ if ((a.state !== Constants.POST_LOADING && a.state !== Constants.POST_FAILED) && (b.state === Constants.POST_LOADING || b.state === Constants.POST_FAILED)) {
+ return -1;
+ }
+
+ if (a.state === Constants.POST_LOADING && b.state === Constants.POST_FAILED) {
+ return -1;
+ }
+ if (a.state === Constants.POST_FAILED && b.state === Constants.POST_LOADING) {
+ return 1;
+ }
+
+ if (a.create_at < b.create_at) {
+ return -1;
+ }
+ if (a.create_at > b.create_at) {
+ return 1;
+ }
+ return 0;
+ });
+
+ var currentId = UserStore.getCurrentId();
+ var searchForm;
+ if (currentId != null) {
+ searchForm =
;
+ }
+
+ return (
+
+
+
{searchForm}
+
+
+
+
+
+ {postsArray.map(function mapPosts(comPost) {
+ return (
+
+ );
+ })}
+
+
+
+
+
+
+
+ );
+ }
+}
+
+RhsThread.defaultProps = {
+ fromSearch: '',
+ isMentionSearch: false
+};
+RhsThread.propTypes = {
+ fromSearch: React.PropTypes.string,
+ isMentionSearch: React.PropTypes.bool
+};
diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx
index 8334b345b0..df75e3adf6 100644
--- a/web/react/components/sidebar_right.jsx
+++ b/web/react/components/sidebar_right.jsx
@@ -1,11 +1,9 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-
-var SearchResults =require('./search_results.jsx');
-var PostRight =require('./post_right.jsx');
+var SearchResults = require('./search_results.jsx');
+var RhsThread = require('./rhs_thread.jsx');
var PostStore = require('../stores/post_store.jsx');
-var Constants = require('../utils/constants.jsx');
var utils = require('../utils/utils.jsx');
function getStateFromStores(from_search) {
@@ -39,8 +37,8 @@ module.exports = React.createClass({
}
},
resize: function() {
- $(".post-list-holder-by-time").scrollTop(100000);
- $(".post-list-holder-by-time").perfectScrollbar('update');
+ var postHolder = $('.post-list-holder-by-time');
+ postHolder[0].scrollTop = postHolder[0].scrollHeight - 224;
},
getInitialState: function() {
return getStateFromStores();
@@ -72,7 +70,7 @@ module.exports = React.createClass({
content =
;
}
else if (this.state.post_right_visible) {
- content =
;
+ content =
;
}
return (
diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx
index 2fffb17d0e..4038814d2a 100644
--- a/web/react/stores/post_store.jsx
+++ b/web/react/stores/post_store.jsx
@@ -99,8 +99,52 @@ var PostStore = assign({}, EventEmitter.prototype, {
}
return null;
},
- storePosts: function storePosts(channelId, posts) {
- this.pStorePosts(channelId, posts);
+ storePosts: function storePosts(channelId, newPostList) {
+ if (isPostListNull(newPostList)) {
+ return;
+ }
+
+ var postList = makePostListNonNull(PostStore.getPosts(channelId));
+
+ for (var pid in newPostList.posts) {
+ var np = newPostList.posts[pid];
+ if (np.delete_at === 0) {
+ postList.posts[pid] = np;
+ if (postList.order.indexOf(pid) === -1) {
+ postList.order.push(pid);
+ }
+ } else {
+ if (pid in postList.posts) {
+ delete postList.posts[pid];
+ }
+
+ var index = postList.order.indexOf(pid);
+ if (index !== -1) {
+ postList.order.splice(index, 1);
+ }
+ }
+ }
+
+ postList.order.sort(function postSort(a, b) {
+ if (postList.posts[a].create_at > postList.posts[b].create_at) {
+ return -1;
+ }
+ if (postList.posts[a].create_at < postList.posts[b].create_at) {
+ return 1;
+ }
+
+ return 0;
+ });
+
+ var latestUpdate = 0;
+ for (var pid in postList.posts) {
+ if (postList.posts[pid].update_at > latestUpdate) {
+ latestUpdate = postList.posts[pid].update_at;
+ }
+ }
+
+ this.storeLatestUpdate(channelId, latestUpdate);
+ this.pStorePosts(channelId, postList);
this.emitChange();
},
pStorePosts: function pStorePosts(channelId, posts) {
@@ -115,9 +159,7 @@ var PostStore = assign({}, EventEmitter.prototype, {
},
pStorePost: function(post) {
var postList = PostStore.getPosts(post.channel_id);
- if (!postList) {
- return;
- }
+ postList = makePostListNonNull(postList);
if (post.pending_post_id !== '') {
this.removePendingPost(post.channel_id, post.pending_post_id);
@@ -132,13 +174,28 @@ var PostStore = assign({}, EventEmitter.prototype, {
this.pStorePosts(post.channel_id, postList);
},
+ removePost: function(postId, channelId) {
+ var postList = PostStore.getPosts(channelId);
+ if (isPostListNull(postList)) {
+ return;
+ }
+
+ if (postId in postList.posts) {
+ delete postList.posts[postId];
+ }
+
+ var index = postList.order.indexOf(postId);
+ if (index !== -1) {
+ postList.order.splice(index, 1);
+ }
+
+ this.pStorePosts(channelId, postList);
+ },
storePendingPost: function(post) {
post.state = Constants.POST_LOADING;
var postList = this.getPendingPosts(post.channel_id);
- if (!postList) {
- postList = {posts: {}, order: []};
- }
+ postList = makePostListNonNull(postList);
postList.posts[post.pending_post_id] = post;
postList.order.unshift(post.pending_post_id);
@@ -200,15 +257,13 @@ var PostStore = assign({}, EventEmitter.prototype, {
},
_removePendingPost: function(channelId, pendingPostId) {
var postList = this.getPendingPosts(channelId);
- if (!postList) {
- return;
- }
+ postList = makePostListNonNull(postList);
if (pendingPostId in postList.posts) {
delete postList.posts[pendingPostId];
}
var index = postList.order.indexOf(pendingPostId);
- if (index >= 0) {
+ if (index !== -1) {
postList.order.splice(index, 1);
}
@@ -221,9 +276,7 @@ var PostStore = assign({}, EventEmitter.prototype, {
},
updatePendingPost: function(post) {
var postList = this.getPendingPosts(post.channel_id);
- if (!postList) {
- postList = {posts: {}, order: []};
- }
+ postList = makePostListNonNull(postList);
if (postList.order.indexOf(post.pending_post_id) === -1) {
return;
@@ -293,6 +346,12 @@ var PostStore = assign({}, EventEmitter.prototype, {
BrowserStore.setItem(key, value);
}
});
+ },
+ storeLatestUpdate: function(channelId, time) {
+ BrowserStore.setItem('latest_post_' + channelId, time);
+ },
+ getLatestUpdate: function(channelId) {
+ return BrowserStore.getItem('latest_post_' + channelId, 0);
}
});
@@ -301,8 +360,7 @@ PostStore.dispatchToken = AppDispatcher.register(function registry(payload) {
switch (action.type) {
case ActionTypes.RECIEVED_POSTS:
- PostStore.pStorePosts(action.id, action.post_list);
- PostStore.emitChange();
+ PostStore.storePosts(action.id, makePostListNonNull(action.post_list));
break;
case ActionTypes.RECIEVED_POST:
PostStore.pStorePost(action.post);
@@ -331,3 +389,36 @@ PostStore.dispatchToken = AppDispatcher.register(function registry(payload) {
});
module.exports = PostStore;
+
+function makePostListNonNull(pl) {
+ var postList = pl;
+ if (postList == null) {
+ postList = {order: [], posts: {}};
+ }
+
+ if (postList.order == null) {
+ postList.order = [];
+ }
+
+ if (postList.posts == null) {
+ postList.posts = {};
+ }
+
+ return postList;
+}
+
+function isPostListNull(pl) {
+ if (pl == null) {
+ return true;
+ }
+
+ if (pl.posts == null) {
+ return true;
+ }
+
+ if (pl.order == null) {
+ return true;
+ }
+
+ return false;
+}
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index 349fe90213..4b0b90dc79 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -344,14 +344,14 @@ module.exports.search = function(terms) {
);
}
-module.exports.getPosts = function(force, id, maxPosts) {
+module.exports.getPostsPage = function(force, id, maxPosts) {
if (PostStore.getCurrentPosts() == null || force) {
var channelId = id;
if (channelId == null) {
channelId = ChannelStore.getCurrentId();
}
- if (isCallInProgress('getPosts_' + channelId)) {
+ if (isCallInProgress('getPostsPage_' + channelId)) {
return;
}
@@ -371,9 +371,9 @@ module.exports.getPosts = function(force, id, maxPosts) {
}
if (channelId != null) {
- callTracker['getPosts_' + channelId] = utils.getTimestamp();
+ callTracker['getPostsPage_' + channelId] = utils.getTimestamp();
- client.getPosts(
+ client.getPostsPage(
channelId,
0,
numPosts,
@@ -389,15 +389,58 @@ module.exports.getPosts = function(force, id, maxPosts) {
module.exports.getProfiles();
},
function(err) {
- dispatchError(err, 'getPosts');
+ dispatchError(err, 'getPostsPage');
},
function() {
- callTracker['getPosts_' + channelId] = 0;
+ callTracker['getPostsPage_' + channelId] = 0;
}
);
}
}
+};
+
+function getPosts(id) {
+ var channelId = id;
+ if (channelId == null) {
+ if (ChannelStore.getCurrentId() == null) {
+ return;
+ }
+ channelId = ChannelStore.getCurrentId();
+ }
+
+ if (isCallInProgress('getPosts_' + channelId)) {
+ return;
+ }
+
+ var latestUpdate = PostStore.getLatestUpdate(channelId);
+
+ callTracker['getPosts_' + channelId] = utils.getTimestamp();
+
+ client.getPosts(
+ channelId,
+ latestUpdate,
+ function success(data, textStatus, xhr) {
+ if (xhr.status === 304 || !data) {
+ return;
+ }
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_POSTS,
+ id: channelId,
+ post_list: data
+ });
+
+ module.exports.getProfiles();
+ },
+ function fail(err) {
+ dispatchError(err, 'getPosts');
+ },
+ function complete() {
+ callTracker['getPosts_' + channelId] = 0;
+ }
+ );
}
+module.exports.getPosts = getPosts;
function getMe() {
if (isCallInProgress('getMe')) {
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 13d6c3f547..70220c71e5 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -653,7 +653,7 @@ module.exports.executeCommand = function(channelId, command, suggest, success, e
});
};
-module.exports.getPosts = function(channelId, offset, limit, success, error, complete) {
+module.exports.getPostsPage = function(channelId, offset, limit, success, error, complete) {
$.ajax({
cache: false,
url: '/api/v1/channels/' + channelId + '/posts/' + offset + '/' + limit,
@@ -669,6 +669,21 @@ module.exports.getPosts = function(channelId, offset, limit, success, error, com
});
};
+module.exports.getPosts = function(channelId, since, success, error, complete) {
+ $.ajax({
+ url: '/api/v1/channels/' + channelId + '/posts/' + since,
+ dataType: 'json',
+ type: 'GET',
+ ifModified: true,
+ success: success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('getPosts', xhr, status, err);
+ error(e);
+ },
+ complete: complete
+ });
+};
+
module.exports.getPost = function(channelId, postId, success, error) {
$.ajax({
cache: false,
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index f19dd2b47d..13989ad82f 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -765,7 +765,7 @@ function switchChannel(channel, teammateName) {
AsyncClient.getChannels(true, true, true);
AsyncClient.getChannelExtraInfo(true);
- AsyncClient.getPosts(true, channel.id, Constants.POST_CHUNK_SIZE);
+ AsyncClient.getPosts(channel.id);
$('.inner__wrap').removeClass('move--right');
$('.sidebar--left').removeClass('move--right');
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index e665be6b9c..0605e9c3bf 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -139,6 +139,9 @@ body.ios {
width: 100%;
padding: 1em 0 0;
position: relative;
+ &.hide-scroll::-webkit-scrollbar {
+ width: 0px !important;
+ }
}
.post-list__table {
display: table;