Merge pull request #380 from mattermost/mm-319

MM-319 adding pending post functionality to the client
Этот коммит содержится в:
Christopher Speller
2015-08-18 09:19:29 -04:00
родитель 4805608cc9 f4c2a9244c
Коммит 532be141ba
15 изменённых файлов: 945 добавлений и 525 удалений

Просмотреть файл

@@ -14,21 +14,22 @@ const (
)
type Post struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
RootId string `json:"root_id"`
ParentId string `json:"parent_id"`
OriginalId string `json:"original_id"`
Message string `json:"message"`
ImgCount int64 `json:"img_count"`
Type string `json:"type"`
Props StringMap `json:"props"`
Hashtags string `json:"hashtags"`
Filenames StringArray `json:"filenames"`
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
RootId string `json:"root_id"`
ParentId string `json:"parent_id"`
OriginalId string `json:"original_id"`
Message string `json:"message"`
ImgCount int64 `json:"img_count"`
Type string `json:"type"`
Props StringMap `json:"props"`
Hashtags string `json:"hashtags"`
Filenames StringArray `json:"filenames"`
PendingPostId string `json:"pending_post_id" db:"-"`
}
func (o *Post) ToJson() string {

Просмотреть файл

@@ -9,6 +9,7 @@ var BrowserStore = require('../stores/browser_store.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var SocketStore = require('../stores/socket_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var PostStore = require('../stores/post_store.jsx');
var Constants = require('../utils/constants.jsx');
module.exports = React.createClass({
@@ -24,20 +25,24 @@ module.exports = React.createClass({
AsyncClient.getMyTeam();
/* End of async loads */
/* Perform pending post clean-up */
PostStore.clearPendingPosts();
/* End pending post clean-up */
/* Start interval functions */
setInterval(function(){AsyncClient.getStatuses();}, 30000);
setInterval(
function pollStatuses() {
AsyncClient.getStatuses();
}, 30000);
/* End interval functions */
/* Start device tracking setup */
var iOS = /(iPad|iPhone|iPod)/g.test( navigator.userAgent );
var iOS = (/(iPad|iPhone|iPod)/g).test(navigator.userAgent);
if (iOS) {
$("body").addClass("ios");
$('body').addClass('ios');
}
/* End device tracking setup */
/* Start window active tracking setup */
window.isActive = true;
@@ -57,7 +62,7 @@ module.exports = React.createClass({
},
_onSocketChange: function(msg) {
if (msg && msg.user_id) {
UserStore.setStatus(msg.user_id, "online");
UserStore.setStatus(msg.user_id, 'online');
}
},
render: function() {

Просмотреть файл

@@ -1,17 +1,20 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var client = require('../utils/client.jsx');
var AsyncClient =require('../utils/async_client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var SocketStore = require('../stores/socket_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var PostStore = require('../stores/post_store.jsx');
var Textbox = require('./textbox.jsx');
var MsgTyping = require('./msg_typing.jsx');
var FileUpload = require('./file_upload.jsx');
var FilePreview = require('./file_preview.jsx');
var utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
module.exports = React.createClass({
lastTime: 0,
@@ -26,6 +29,8 @@ module.exports = React.createClass({
return;
}
this.setState({submitting: true, serverError: null});
var post = {};
post.filenames = [];
post.message = this.state.messageText;
@@ -39,18 +44,23 @@ module.exports = React.createClass({
return;
}
var user_id = UserStore.getCurrentId();
post.channel_id = this.props.channelId;
post.root_id = this.props.rootId;
post.parent_id = this.props.parentId;
post.parent_id = this.props.rootId;
post.filenames = this.state.previews;
var time = utils.getTimestamp();
post.pending_post_id = user_id + ':'+ time;
post.user_id = user_id;
post.create_at = time;
this.setState({submitting: true, serverError: null});
PostStore.storePendingPost(post);
PostStore.storeCommentDraft(this.props.rootId, null);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
client.createPost(post, ChannelStore.getCurrent(),
function(data) {
PostStore.storeCommentDraft(this.props.rootId, null);
this.setState({messageText: '', submitting: false, postError: null, serverError: null});
this.clearPreviews();
AsyncClient.getPosts(true, this.props.channelId);
var channel = ChannelStore.get(this.props.channelId);
@@ -58,19 +68,27 @@ module.exports = React.createClass({
member.msg_count = channel.total_msg_count;
member.last_viewed_at = Date.now();
ChannelStore.setChannelMember(member);
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_POST,
post: data
});
}.bind(this),
function(err) {
var state = {};
state.serverError = err.message;
state.submitting = false;
if (err.message === 'Invalid RootId parameter') {
if ($('#post_deleted').length > 0) {
$('#post_deleted').modal('show');
}
PostStore.removePendingPost(post.pending_post_id);
} else {
this.setState(state);
post.state = Constants.POST_FAILED;
PostStore.updatePendingPost(post);
}
state.submitting = false;
this.setState(state);
}.bind(this)
);
},
@@ -137,9 +155,6 @@ module.exports = React.createClass({
this.setState({serverError: err});
}
},
clearPreviews: function() {
this.setState({previews: []});
},
removePreview: function(id) {
var previews = this.state.previews;
var uploadsInProgress = this.state.uploadsInProgress;

Просмотреть файл

@@ -65,22 +65,47 @@ module.exports = React.createClass({
post.channel_id = this.state.channelId;
post.filenames = this.state.previews;
client.createPost(post, ChannelStore.getCurrent(),
var time = utils.getTimestamp();
var userId = UserStore.getCurrentId();
post.pending_post_id = userId + ':' + time;
post.user_id = userId;
post.create_at = time;
post.root_id = this.state.rootId;
post.parent_id = this.state.parentId;
var channel = ChannelStore.get(this.state.channelId);
PostStore.storePendingPost(post);
PostStore.storeDraft(channel.id, null);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
client.createPost(post, channel,
function(data) {
PostStore.storeDraft(data.channel_id, null);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
this.resizePostHolder();
AsyncClient.getPosts(true);
var channel = ChannelStore.get(this.state.channelId);
var member = ChannelStore.getMember(this.state.channelId);
var member = ChannelStore.getMember(channel.id);
member.msg_count = channel.total_msg_count;
member.last_viewed_at = Date.now();
ChannelStore.setChannelMember(member);
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_POST,
post: data
});
}.bind(this),
function(err) {
var state = {};
state.serverError = err.message;
if (err.message === 'Invalid RootId parameter') {
if ($('#post_deleted').length > 0) {
$('#post_deleted').modal('show');
}
PostStore.removePendingPost(post.pending_post_id);
} else {
post.state = Constants.POST_FAILED;
PostStore.updatePendingPost(post);
}
state.submitting = false;
this.setState(state);
@@ -195,20 +220,33 @@ module.exports = React.createClass({
var channelId = ChannelStore.getCurrentId();
if (this.state.channelId !== channelId) {
var draft = PostStore.getCurrentDraft();
this.setState({
channelId: channelId, messageText: draft['message'], initialText: draft['message'], submitting: false,
serverError: null, postError: null, previews: draft['previews'], uploadsInProgress: draft['uploadsInProgress']
});
var previews = [];
var messageText = '';
var uploadsInProgress = 0;
if (draft && draft.previews && draft.message) {
previews = draft.previews;
messageText = draft.message;
uploadsInProgress = draft.uploadsInProgress;
}
this.setState({channelId: channelId, messageText: messageText, initialText: messageText, submitting: false, serverError: null, postError: null, previews: previews, uploadsInProgress: uploadsInProgress});
}
},
getInitialState: function() {
PostStore.clearDraftUploads();
var draft = PostStore.getCurrentDraft();
return {
channelId: ChannelStore.getCurrentId(), messageText: draft['message'], uploadsInProgress: draft['uploadsInProgress'],
previews: draft['previews'], submitting: false, initialText: draft['message']
};
var previews = [];
var messageText = '';
var uploadsInProgress = 0;
if (draft && draft.previews && draft.message) {
previews = draft.previews;
messageText = draft.message;
uploadsInProgress = draft.uploadsInProgress;
}
return {channelId: ChannelStore.getCurrentId(), messageText: messageText, uploadsInProgress: uploadsInProgress, previews: previews, submitting: false, initialText: messageText};
},
getFileCount: function(channelId) {
if (channelId === this.state.channelId) {

Просмотреть файл

@@ -7,6 +7,10 @@ var PostInfo = require('./post_info.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx');
var UserStore = require('../stores/user_store.jsx');
var PostStore = require('../stores/post_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var ActionTypes = Constants.ActionTypes;
module.exports = React.createClass({
@@ -32,6 +36,36 @@ module.exports = React.createClass({
this.refs.info.forceUpdate();
this.refs.header.forceUpdate();
},
retryPost: function(e) {
e.preventDefault();
var post = this.props.post;
client.createPost(post, post.channel_id,
function(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(err) {
post.state = Constants.POST_FAILED;
PostStore.updatePendingPost(post);
this.forceUpdate();
}.bind(this)
);
post.state = Constants.POST_LOADING;
PostStore.updatePendingPost(post);
this.forceUpdate();
},
getInitialState: function() {
return { };
},
@@ -40,9 +74,9 @@ module.exports = React.createClass({
var parentPost = this.props.parentPost;
var posts = this.props.posts;
var type = "Post"
if (post.root_id.length > 0) {
type = "Comment"
var type = 'Post';
if (post.root_id && post.root_id.length > 0) {
type = 'Comment';
}
var commentCount = 0;
@@ -79,7 +113,7 @@ module.exports = React.createClass({
: null }
<div className="post__content">
<PostHeader ref="header" post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} isLastComment={this.props.isLastComment} />
<PostBody post={post} sameRoot={this.props.sameRoot} parentPost={parentPost} posts={posts} handleCommentClick={this.handleCommentClick} />
<PostBody post={post} sameRoot={this.props.sameRoot} parentPost={parentPost} posts={posts} handleCommentClick={this.handleCommentClick} retryPost={this.retryPost} />
<PostInfo ref="info" post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} allowReply="true" />
</div>
</div>

Просмотреть файл

@@ -4,15 +4,16 @@
var FileAttachmentList = require('./file_attachment_list.jsx');
var UserStore = require('../stores/user_store.jsx');
var utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx');
module.exports = React.createClass({
componentWillReceiveProps: function(nextProps) {
var linkData = utils.extractLinks(nextProps.post.message);
this.setState({ links: linkData["links"], message: linkData["text"] });
this.setState({links: linkData.links, message: linkData.text});
},
getInitialState: function() {
var linkData = utils.extractLinks(this.props.post.message);
return { links: linkData["links"], message: linkData["text"] };
return {links: linkData.links, message: linkData.text};
},
render: function() {
var post = this.props.post;
@@ -20,43 +21,52 @@ module.exports = React.createClass({
var parentPost = this.props.parentPost;
var inner = utils.textToJsx(this.state.message);
var comment = "";
var reply = "";
var postClass = "";
var comment = '';
var reply = '';
var postClass = '';
if (parentPost) {
var profile = UserStore.getProfile(parentPost.user_id);
var apostrophe = "";
var name = "...";
var apostrophe = '';
var name = '...';
if (profile != null) {
if (profile.username.slice(-1) === 's') {
apostrophe = "'";
apostrophe = '\'';
} else {
apostrophe = "'s";
apostrophe = '\'s';
}
name = <a className="theme" onClick={function(){ utils.searchForTerm(profile.username); }}>{profile.username}</a>;
name = <a className='theme' onClick={function searchName() { utils.searchForTerm(profile.username); }}>{profile.username}</a>;
}
var message = ""
if(parentPost.message) {
message = utils.replaceHtmlEntities(parentPost.message)
var message = '';
if (parentPost.message) {
message = utils.replaceHtmlEntities(parentPost.message);
} else if (parentPost.filenames.length) {
message = parentPost.filenames[0].split('/').pop();
if (parentPost.filenames.length === 2) {
message += " plus 1 other file";
message += ' plus 1 other file';
} else if (parentPost.filenames.length > 2) {
message += " plus " + (parentPost.filenames.length - 1) + " other files";
message += ' plus ' + (parentPost.filenames.length - 1) + ' other files';
}
}
comment = (
<p className="post-link">
<span>Commented on {name}{apostrophe} message: <a className="theme" onClick={this.props.handleCommentClick}>{message}</a></span>
<p className='post-link'>
<span>Commented on {name}{apostrophe} message: <a className='theme' onClick={this.props.handleCommentClick}>{message}</a></span>
</p>
);
postClass += " post-comment";
postClass += ' post-comment';
}
var loading;
if (post.state === Constants.POST_FAILED) {
postClass += ' post-fail';
loading = <a className='theme post-retry pull-right' href='#' onClick={this.props.retryPost}>Retry</a>;
} else if (post.state === Constants.POST_LOADING) {
postClass += ' post-waiting';
loading = <img className='post-loading-gif pull-right' src='/static/images/load.gif'/>;
}
var embed;
@@ -64,18 +74,21 @@ module.exports = React.createClass({
embed = utils.getEmbed(this.state.links[0]);
}
var fileAttachmentHolder = '';
if (filenames && filenames.length > 0) {
fileAttachmentHolder = (<FileAttachmentList
filenames={filenames}
modalId={'view_image_modal_' + post.id}
channelId={post.channel_id}
userId={post.user_id} />);
}
return (
<div className="post-body">
{ comment }
<p key={post.id+"_message"} className={postClass}><span>{inner}</span></p>
{ filenames && filenames.length > 0 ?
<FileAttachmentList
filenames={filenames}
modalId={"view_image_modal_" + post.id}
channelId={post.channel_id}
userId={post.user_id} />
: "" }
{ embed }
<div className='post-body'>
{comment}
<p key={post.id + '_message'} className={postClass}>{loading}<span>{inner}</span></p>
{fileAttachmentHolder}
{embed}
</div>
);
}

Просмотреть файл

@@ -12,41 +12,68 @@ module.exports = React.createClass({
},
render: function() {
var post = this.props.post;
var isOwner = UserStore.getCurrentId() == post.user_id;
var isAdmin = UserStore.getCurrentUser().roles.indexOf("admin") > -1
var isOwner = UserStore.getCurrentId() === post.user_id;
var isAdmin = UserStore.getCurrentUser().roles.indexOf('admin') > -1;
var type = "Post"
if (post.root_id.length > 0) {
type = "Comment"
var type = 'Post';
if (post.root_id && post.root_id.length > 0) {
type = 'Comment';
}
var comments = "";
var lastCommentClass = this.props.isLastComment ? " comment-icon__container__show" : " comment-icon__container__hide";
if (this.props.commentCount >= 1) {
comments = <a href="#" className={"comment-icon__container theme" + lastCommentClass} onClick={this.props.handleCommentClick}><span className="comment-icon" dangerouslySetInnerHTML={{__html: Constants.COMMENT_ICON }} />{this.props.commentCount}</a>;
var comments = '';
var lastCommentClass = ' comment-icon__container__hide';
if (this.props.isLastComment) {
lastCommentClass = ' comment-icon__container__show';
}
if (this.props.commentCount >= 1 && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) {
comments = <a href='#' className={'comment-icon__container theme' + lastCommentClass} onClick={this.props.handleCommentClick}><span className='comment-icon' dangerouslySetInnerHTML={{__html: Constants.COMMENT_ICON}} />{this.props.commentCount}</a>;
}
var showDropdown = isOwner || (this.props.allowReply === 'true' && type !== 'Comment');
if (post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING) {
showDropdown = false;
}
var dropdownContents = [];
var dropdown;
if (showDropdown) {
var dataComments = 0;
if (type === 'Post') {
dataComments = this.props.commentCount;
}
if (isOwner) {
dropdownContents.push(<li role='presentation'><a href='#' role='menuitem' data-toggle='modal' data-target='#edit_post' data-title={type} data-message={post.message} data-postid={post.id} data-channelid={post.channel_id} data-comments={dataComments}>Edit</a></li>);
}
if (isOwner || isAdmin) {
dropdownContents.push(<li role='presentation'><a href='#' role='menuitem' data-toggle='modal' data-target='#delete_post' data-title={type} data-postid={post.id} data-channelid={post.channel_id} data-comments={dataComments}>Delete</a></li>);
}
if (this.props.allowReply === 'true') {
dropdownContents.push(<li role='presentation'><a className='reply-link theme' href='#' onClick={this.props.handleCommentClick}>Reply</a></li>);
}
dropdown = (
<div>
<a href='#' className='dropdown-toggle theme' type='button' data-toggle='dropdown' aria-expanded='false' />
<ul className='dropdown-menu' role='menu'>
{dropdownContents}
</ul>
</div>
);
}
return (
<ul className="post-header post-info">
<li className="post-header-col"><time className="post-profile-time">{ utils.displayDateTime(post.create_at) }</time></li>
<li className="post-header-col post-header__reply">
<div className="dropdown">
{ isOwner || (this.props.allowReply === "true" && type != "Comment") ?
<div>
<a href="#" className="dropdown-toggle theme" type="button" data-toggle="dropdown" aria-expanded="false" />
<ul className="dropdown-menu" role="menu">
{ isOwner ? <li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#edit_post" data-title={type} data-message={post.message} data-postid={post.id} data-channelid={post.channel_id} data-comments={type === "Post" ? this.props.commentCount : 0}>Edit</a></li>
: "" }
{ isOwner || isAdmin ? <li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#delete_post" data-title={type} data-postid={post.id} data-channelid={post.channel_id} data-comments={type === "Post" ? this.props.commentCount : 0}>Delete</a></li>
: "" }
{ this.props.allowReply === "true" ? <li role="presentation"><a className="reply-link theme" href="#" onClick={this.props.handleCommentClick}>Reply</a></li>
: "" }
</ul>
</div>
: "" }
</div>
{ comments }
</li>
<ul className='post-header post-info'>
<li className='post-header-col'><time className='post-profile-time'>{utils.displayDateTime(post.create_at)}</time></li>
<li className='post-header-col post-header__reply'>
<div className='dropdown'>
{dropdown}
</div>
{comments}
</li>
</ul>
);
}

Просмотреть файл

@@ -4,7 +4,7 @@
var PostStore = require('../stores/post_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var UserProfile = require( './user_profile.jsx' );
var UserProfile = require('./user_profile.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var Post = require('./post.jsx');
var LoadingScreen = require('./loading_screen.jsx');
@@ -18,16 +18,28 @@ var ActionTypes = Constants.ActionTypes;
function getStateFromStores() {
var channel = ChannelStore.getCurrent();
if (channel == null) channel = {};
if (channel == null) {
channel = {};
}
var postList = PostStore.getCurrentPosts();
var pendingPostList = PostStore.getPendingPosts(channel.id);
if (pendingPostList) {
postList.order = pendingPostList.order.concat(postList.order);
for (var pid in pendingPostList.posts) {
postList.posts[pid] = pendingPostList.posts[pid];
}
}
return {
post_list: PostStore.getCurrentPosts(),
postList: postList,
channel: channel
};
}
module.exports = React.createClass({
displayName: "PostList",
displayName: 'PostList',
scrollPosition: 0,
preventScrollTrigger: false,
gotMorePosts: false,
@@ -37,74 +49,75 @@ module.exports = React.createClass({
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('.mention-link', 'color: ' + user.props.theme+';');
utils.changeCss('.mention-link', 'color: ' + user.props.theme+';');
utils.changeCss('.search-item-container:hover', 'background: ' + utils.changeOpacity(user.props.theme, 0.05) +';');
utils.changeCss('@media(max-width: 768px){.search-bar__container', 'background: ' + 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) + ';');
}
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) +';');
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) +';');
} 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');
}
PostStore.addChangeListener(this._onChange);
ChannelStore.addChangeListener(this._onChange);
UserStore.addStatusesChangeListener(this._onTimeChange);
SocketStore.addChangeListener(this._onSocketChange);
PostStore.addChangeListener(this.onChange);
ChannelStore.addChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onTimeChange);
SocketStore.addChangeListener(this.onSocketChange);
$(".post-list-holder-by-time").perfectScrollbar();
$('.post-list-holder-by-time').perfectScrollbar();
this.resize();
var post_holder = $(".post-list-holder-by-time")[0];
this.scrollPosition = $(post_holder).scrollTop() + $(post_holder).innerHeight();
this.oldScrollHeight = post_holder.scrollHeight;
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;
$('.modal').on('show.bs.modal', function () {
$('.modal').on('show.bs.modal', function onShow() {
$('.modal-body').css('overflow-y', 'auto');
$('.modal-body').css('max-height', $(window).height() * 0.7);
});
// Timeout exists for the DOM to fully render before making changes
var self = this;
$(window).resize(function(){
$(post_holder).perfectScrollbar('update');
$(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 >= post_holder.scrollHeight || (self.oldScrollHeight != post_holder.scrollHeight && self.scrollPosition >= self.oldScrollHeight) || self.oldZoom != newZoom) self.resize();
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");
$('.post-list-holder-by-time').css('height', height + 'px');
}
});
$(post_holder).scroll(function(e){
$(postHolder).scroll(function scroll() {
if (!self.preventScrollTrigger) {
self.scrollPosition = $(post_holder).scrollTop() + $(post_holder).innerHeight();
self.scrollPosition = $(postHolder).scrollTop() + $(postHolder).innerHeight();
}
self.preventScrollTrigger = false;
});
$('body').on('click.userpopover', function(e){
if ($(e.target).attr('data-toggle') !== 'popover'
&& $(e.target).parents('.popover.in').length === 0) {
$('body').on('click.userpopover', function popOver(e) {
if ($(e.target).attr('data-toggle') !== 'popover' &&
$(e.target).parents('.popover.in').length === 0) {
$('.user-popover').popover('hide');
}
});
@@ -112,67 +125,62 @@ module.exports = React.createClass({
$('.post-list__content div .post').removeClass('post--last');
$('.post-list__content div:last-child .post').addClass('post--last');
$('body').on('mouseenter mouseleave', '.post', function(ev){
if(ev.type === 'mouseenter'){
$('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) {
if (ev.type === 'mouseenter') {
$(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after');
$(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--before');
}
else {
} else {
$(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--after');
$(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--before');
}
});
$('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function(ev){
if(ev.type === 'mouseenter'){
$('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) {
if (ev.type === 'mouseenter') {
$(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment');
$(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--comment');
}
else {
} else {
$(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--comment');
$(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment');
}
});
},
componentDidUpdate: function() {
this.resize();
var post_holder = $(".post-list-holder-by-time")[0];
this.scrollPosition = $(post_holder).scrollTop() + $(post_holder).innerHeight();
this.oldScrollHeight = post_holder.scrollHeight;
var postHolder = $('.post-list-holder-by-time')[0];
this.scrollPosition = $(postHolder).scrollTop() + $(postHolder).innerHeight();
this.oldScrollHeight = postHolder.scrollHeight;
$('.post-list__content div .post').removeClass('post--last');
$('.post-list__content div:last-child .post').addClass('post--last');
},
componentWillUnmount: function() {
PostStore.removeChangeListener(this._onChange);
ChannelStore.removeChangeListener(this._onChange);
UserStore.removeStatusesChangeListener(this._onTimeChange);
SocketStore.removeChangeListener(this._onSocketChange);
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')
$('.modal').off('show.bs.modal');
},
resize: function() {
var post_holder = $(".post-list-holder-by-time")[0];
var postHolder = $('.post-list-holder-by-time')[0];
this.preventScrollTrigger = true;
if (this.gotMorePosts) {
this.gotMorePosts = false;
$(post_holder).scrollTop($(post_holder).scrollTop() + (post_holder.scrollHeight-this.oldScrollHeight) );
$(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;
} else {
if ($("#new_message")[0] && !this.scrolledToNew) {
$(post_holder).scrollTop($(post_holder).scrollTop() + $("#new_message").offset().top - 63);
this.scrolledToNew = true;
} else {
$(post_holder).scrollTop(post_holder.scrollHeight);
}
$(postHolder).scrollTop(postHolder.scrollHeight);
}
$(post_holder).perfectScrollbar('update');
$(postHolder).perfectScrollbar('update');
},
_onChange: function() {
onChange: function() {
var newState = getStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
if (this.state.post_list && this.state.post_list.order) {
if (this.state.channel.id === newState.channel.id && this.state.post_list.order.length != newState.post_list.order.length && newState.post_list.order.length > Constants.POST_CHUNK_SIZE) {
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;
}
}
@@ -182,112 +190,127 @@ module.exports = React.createClass({
this.setState(newState);
}
},
_onSocketChange: function(msg) {
if (msg.action == "posted") {
var post = JSON.parse(msg.props.post);
onSocketChange: function(msg) {
var postList;
var post;
if (msg.action === 'posted') {
post = JSON.parse(msg.props.post);
PostStore.storePost(post);
} else if (msg.action === 'post_edited') {
if (this.state.channel.id === msg.channel_id) {
this.setState({postList: postList});
}
var post_list = PostStore.getPosts(msg.channel_id);
if (!post_list) return;
PostStore.storePosts(post.channel_id, postList);
} 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_list.posts[post.id] = post;
if (post_list.order.indexOf(post.id) === -1) {
post_list.order.unshift(post.id);
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 = '';
if (activeRoot && activeRoot.id.length > 0) {
activeRootPostId = activeRoot.id;
}
if (this.state.channel.id === msg.channel_id) {
this.setState({ post_list: post_list });
};
postList = this.state.postList;
if (!(msg.props.post_id in this.state.postList.posts)) {
return;
}
PostStore.storePosts(post.channel_id, post_list);
} else if (msg.action == "post_edited") {
if (this.state.channel.id == msg.channel_id) {
var post_list = this.state.post_list;
if (!(msg.props.post_id in post_list.posts)) return;
delete postList.posts[msg.props.post_id];
var index = postList.order.indexOf(msg.props.post_id);
if (index > -1) {
postList.order.splice(index, 1);
}
var post = post_list.posts[msg.props.post_id];
post.message = msg.props.message;
this.setState({postList: postList});
post_list.posts[post.id] = post;
this.setState({ post_list: post_list });
PostStore.storePosts(msg.channel_id, post_list);
} else {
AsyncClient.getPosts(true, msg.channel_id);
}
} else if (msg.action == "post_deleted") {
var activeRoot = $(document.activeElement).closest('.comment-create-body')[0];
var activeRootPostId = activeRoot && activeRoot.id.length > 0 ? activeRoot.id : "";
if (this.state.channel.id == msg.channel_id) {
var post_list = this.state.post_list;
if (!(msg.props.post_id in this.state.post_list.posts)) return;
delete post_list.posts[msg.props.post_id];
var index = post_list.order.indexOf(msg.props.post_id);
if (index > -1) post_list.order.splice(index, 1);
this.setState({ post_list: post_list });
PostStore.storePosts(msg.channel_id, post_list);
PostStore.storePosts(msg.channel_id, postList);
} else {
AsyncClient.getPosts(true, msg.channel_id);
}
if (activeRootPostId === msg.props.post_id && UserStore.getCurrentId() != msg.user_id) {
if (activeRootPostId === msg.props.post_id && UserStore.getCurrentId() !== msg.user_id) {
$('#post_deleted').modal('show');
}
} else if (msg.action == "new_user") {
} else if (msg.action === 'new_user') {
AsyncClient.getProfiles();
}
},
_onTimeChange: function() {
if (!this.state.post_list) return;
for (var id in this.state.post_list.posts) {
if (!this.refs[id]) continue;
onTimeChange: function() {
if (!this.state.postList) {
return;
}
for (var id in this.state.postList.posts) {
if (!this.refs[id]) {
continue;
}
this.refs[id].forceUpdateInfo();
}
},
getMorePosts: function(e) {
e.preventDefault();
if (!this.state.post_list) return;
if (!this.state.postList) {
return;
}
var posts = this.state.post_list.posts;
var order = this.state.post_list.order;
var channel_id = this.state.channel.id;
var posts = this.state.postList.posts;
var order = this.state.postList.order;
var channelId = this.state.channel.id;
$(this.refs.loadmore.getDOMNode()).text("Retrieving more messages...");
$(this.refs.loadmore.getDOMNode()).text('Retrieving more messages...');
var self = this;
var currentPos = $(".post-list").scrollTop;
var currentPos = $('.post-list').scrollTop;
Client.getPosts(
channel_id,
channelId,
order.length,
Constants.POST_CHUNK_SIZE,
function(data) {
$(self.refs.loadmore.getDOMNode()).text("Load more messages");
function success(data) {
$(self.refs.loadmore.getDOMNode()).text('Load more messages');
if (!data) return;
if (!data) {
return;
}
if (data.order.length === 0) return;
if (data.order.length === 0) {
return;
}
var post_list = {}
post_list.posts = $.extend(posts, data.posts);
post_list.order = order.concat(data.order);
var postList = {};
postList.posts = $.extend(posts, data.posts);
postList.order = order.concat(data.order);
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_POSTS,
id: channel_id,
post_list: post_list
id: channelId,
postList: postList
});
Client.getProfiles();
$(".post-list").scrollTop(currentPos);
$('.post-list').scrollTop(currentPos);
},
function(err) {
$(self.refs.loadmore.getDOMNode()).text("Load more messages");
AsyncClient.dispatchError(err, "getPosts");
function fail(err) {
$(self.refs.loadmore.getDOMNode()).text('Load more messages');
AsyncClient.dispatchError(err, 'getPosts');
}
);
},
@@ -298,81 +321,86 @@ module.exports = React.createClass({
var order = [];
var posts;
var last_viewed = Number.MAX_VALUE;
var lastViewed = Number.MAX_VALUE;
if (ChannelStore.getCurrentMember() != null)
last_viewed = ChannelStore.getCurrentMember().last_viewed_at;
if (this.state.post_list != null) {
posts = this.state.post_list.posts;
order = this.state.post_list.order;
if (ChannelStore.getCurrentMember() != null) {
lastViewed = ChannelStore.getCurrentMember().lastViewed_at;
}
var rendered_last_viewed = false;
if (this.state.postList != null) {
posts = this.state.postList.posts;
order = this.state.postList.order;
}
var user_id = "";
var renderedLastViewed = false;
var userId = '';
if (UserStore.getCurrentId()) {
user_id = UserStore.getCurrentId();
userId = UserStore.getCurrentId();
} else {
return <div/>;
}
var channel = this.state.channel;
var more_messages = <p className="beginning-messages-text">Beginning of Channel</p>;
var moreMessages = <p className='beginning-messages-text'>Beginning of Channel</p>;
var userStyle = { color: UserStore.getCurrentUser().props.theme }
var userStyle = {color: UserStore.getCurrentUser().props.theme};
if (channel != null) {
if (order.length > 0 && order.length % Constants.POST_CHUNK_SIZE === 0) {
more_messages = <a ref="loadmore" className="more-messages-text theme" href="#" onClick={this.getMorePosts}>Load more messages</a>;
moreMessages = <a ref='loadmore' className='more-messages-text theme' href='#' onClick={this.getMorePosts}>Load more messages</a>;
} else if (channel.type === 'D') {
var teammate = utils.getDirectTeammate(channel.id)
var teammate = utils.getDirectTeammate(channel.id);
if (teammate) {
var teammate_name = teammate.nickname.length > 0 ? teammate.nickname : teammate.username;
more_messages = (
<div className="channel-intro">
<div className="post-profile-img__container channel-intro-img">
<img className="post-profile-img" src={"/api/v1/users/" + teammate.id + "/image?time=" + teammate.update_at} height="50" width="50" />
var teammateName = teammate.username;
if (teammate.nickname.length > 0) {
teammateName = teammate.nickname;
}
moreMessages = (
<div className='channel-intro'>
<div className='post-profile-img__container channel-intro-img'>
<img className='post-profile-img' src={'/api/v1/users/' + teammate.id + '/image?time=' + teammate.update_at} height='50' width='50' />
</div>
<div className="channel-intro-profile">
<div className='channel-intro-profile'>
<strong><UserProfile userId={teammate.id} /></strong>
</div>
<p className="channel-intro-text">
{"This is the start of your private message history with " + teammate_name + "." }<br/>
{"Private messages and files shared here are not shown to people outside this area."}
<p className='channel-intro-text'>
{'This is the start of your private message history with ' + teammateName + '.'}<br/>
{'Private messages and files shared here are not shown to people outside this area.'}
</p>
<a className="intro-links" href="#" style={userStyle} data-toggle="modal" data-target="#edit_channel" data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}><i className="fa fa-pencil"></i>Set a description</a>
<a className='intro-links' href='#' style={userStyle} data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}><i className='fa fa-pencil'></i>Set a description</a>
</div>
);
} else {
more_messages = (
<div className="channel-intro">
<p className="channel-intro-text">{"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."}</p>
moreMessages = (
<div className='channel-intro'>
<p className='channel-intro-text'>{'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.'}</p>
</div>
);
}
} else if (channel.type === 'P' || channel.type === 'O') {
var ui_name = channel.display_name
var uiName = channel.display_name;
var members = ChannelStore.getCurrentExtraInfo().members;
var creator_name = "";
var creatorName = '';
for (var i = 0; i < members.length; i++) {
if (members[i].roles.indexOf('admin') > -1) {
creator_name = members[i].username;
creatorName = members[i].username;
break;
}
}
if (ChannelStore.isDefault(channel)) {
more_messages = (
<div className="channel-intro">
<h4 className="channel-intro__title">Beginning of {ui_name}</h4>
<p className="channel-intro__content">
Welcome to {ui_name}!
moreMessages = (
<div className='channel-intro'>
<h4 className='channel-intro__title'>Beginning of {uiName}</h4>
<p className='channel-intro__content'>
Welcome to {uiName}!
<br/><br/>
{"This is the first channel " + strings.Team + "mates see when they"}
{'This is the first channel ' + strings.Team + 'mates see when they'}
<br/>
sign up - use it for posting updates everyone needs to know.
<br/><br/>
@@ -384,29 +412,44 @@ module.exports = React.createClass({
</div>
);
} else if (channel.name === Constants.OFFTOPIC_CHANNEL) {
more_messages = (
<div className="channel-intro">
<h4 className="channel-intro__title">Beginning of {ui_name}</h4>
<p className="channel-intro__content">
{"This is the start of " + ui_name + ", a channel for non-work-related conversations."}
moreMessages = (
<div className='channel-intro'>
<h4 className='channel-intro__title'>Beginning of {uiName}</h4>
<p className='channel-intro__content'>
{'This is the start of ' + uiName + ', a channel for non-work-related conversations.'}
<br/>
</p>
<a className="intro-links" href="#" style={userStyle} data-toggle="modal" data-target="#edit_channel" data-desc={channel.description} data-title={ui_name} data-channelid={channel.id}><i className="fa fa-pencil"></i>Set a description</a>
<a className='intro-links' href='#' style={userStyle} data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={uiName} data-channelid={channel.id}><i className='fa fa-pencil'></i>Set a description</a>
</div>
);
} else {
var ui_type = channel.type === 'P' ? "private group" : "channel";
more_messages = (
<div className="channel-intro">
<h4 className="channel-intro__title">Beginning of {ui_name}</h4>
<p className="channel-intro__content">
{ creator_name != "" ? "This is the start of the " + ui_name + " " + ui_type + ", created by " + creator_name + " on " + utils.displayDate(channel.create_at) + "."
: "This is the start of the " + ui_name + " " + ui_type + ", created on "+ utils.displayDate(channel.create_at) + "." }
{ channel.type === 'P' ? " Only invited members can see this private group." : " Any member can join and read this channel." }
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 = (
<div className='channel-intro'>
<h4 className='channel-intro__title'>Beginning of {uiName}</h4>
<p className='channel-intro__content'>
{createMessage}
{memberMessage}
<br/>
</p>
<a className="intro-links" href="#" style={userStyle} data-toggle="modal" data-target="#edit_channel" data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}><i className="fa fa-pencil"></i>Set a description</a>
<a className="intro-links" href="#" style={userStyle} data-toggle="modal" data-target="#channel_invite"><i className="fa fa-user-plus"></i>Invite others to this {ui_type}</a>
<a className='intro-links' href='#' style={userStyle} data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}><i className='fa fa-pencil'></i>Set a description</a>
<a className='intro-links' href='#' style={userStyle} data-toggle='modal' data-target='#channel_invite'><i className='fa fa-user-plus'></i>Invite others to this {uiType}</a>
</div>
);
}
@@ -419,17 +462,25 @@ module.exports = React.createClass({
var previousPostDay = new Date(0);
var currentPostDay;
for (var i = order.length-1; i >= 0; i--) {
for (var i = order.length - 1; i >= 0; i--) {
var post = posts[order[i]];
var parentPost = post.parent_id ? posts[post.parent_id] : null;
var parentPost = null;
if (post.parent_id) {
parentPost = posts[post.parent_id];
}
var sameUser = '';
var sameRoot = false;
var hideProfilePic = false;
var prevPost = (i < order.length - 1) ? posts[order[i + 1]] : null;
var prevPost;
if (i < order.length - 1) {
prevPost = posts[order[i + 1]];
}
if (prevPost) {
sameUser = (prevPost.user_id === post.user_id) && (post.create_at - prevPost.create_at <= 1000*60*5) ? "same--user" : "";
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
@@ -438,7 +489,7 @@ module.exports = React.createClass({
// 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 isLastComment = utils.isComment(post) && (i === 0 || posts[order[i - 1]].root_id !== post.root_id);
var postCtl = (
<Post ref={post.id} sameUser={sameUser} sameRoot={sameRoot} post={post} parentPost={parentPost} key={post.id}
@@ -447,21 +498,21 @@ module.exports = React.createClass({
);
currentPostDay = utils.getDateForUnixTicks(post.create_at);
if (currentPostDay.toDateString() != previousPostDay.toDateString()) {
if (currentPostDay.toDateString() !== previousPostDay.toDateString()) {
postCtls.push(
<div key={currentPostDay.toDateString()} className="date-separator">
<hr className="separator__hr" />
<div className="separator__text">{currentPostDay.toDateString()}</div>
<div key={currentPostDay.toDateString()} className='date-separator'>
<hr className='separator__hr' />
<div className='separator__text'>{currentPostDay.toDateString()}</div>
</div>
);
}
if (post.create_at > last_viewed && !rendered_last_viewed) {
rendered_last_viewed = true;
if (post.user_id !== userId && post.create_at > lastViewed && !renderedLastViewed) {
renderedLastViewed = true;
postCtls.push(
<div key="unviewed" className="new-separator">
<hr id="new_message" className="separator__hr" />
<div className="separator__text">New Messages</div>
<div key='unviewed' className='new-separator'>
<hr id='new_message' className='separator__hr' />
<div className='separator__text'>New Messages</div>
</div>
);
}
@@ -469,15 +520,15 @@ module.exports = React.createClass({
previousPostDay = currentPostDay;
}
} else {
postCtls.push(<LoadingScreen position="absolute" />);
postCtls.push(<LoadingScreen position='absolute' />);
}
return (
<div ref="postlist" className="post-list-holder-by-time">
<div className="post-list__table">
<div className="post-list__content">
{ more_messages }
{ postCtls }
<div ref='postlist' className='post-list-holder-by-time'>
<div className='post-list__table'>
<div className='post-list__content'>
{moreMessages}
{postCtls}
</div>
</div>
</div>

Просмотреть файл

@@ -3,15 +3,17 @@
var PostStore = require('../stores/post_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var UserProfile = require( './user_profile.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 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({
@@ -44,12 +46,15 @@ RhsHeaderPost = React.createClass({
});
},
render: function() {
var back = this.props.fromSearch ? <a href="#" onClick={this.handleBack} className="sidebar--right__back"><i className="fa fa-chevron-left"></i></a> : "";
var back;
if (this.props.fromSearch) {
back = <a href='#' onClick={this.handleBack} className='sidebar--right__back'><i className='fa fa-chevron-left'></i></a>;
}
return (
<div className="sidebar--right__header">
<span className="sidebar--right__title">{back}Message Details</span>
<button type="button" className="sidebar--right__close" aria-label="Close" onClick={this.handleClose}></button>
<div className='sidebar--right__header'>
<span className='sidebar--right__title'>{back}Message Details</span>
<button type='button' className='sidebar--right__close' aria-label='Close' onClick={this.handleClose}></button>
</div>
);
}
@@ -59,57 +64,72 @@ RootPost = React.createClass({
render: function() {
var post = this.props.post;
var message = utils.textToJsx(post.message);
var isOwner = UserStore.getCurrentId() == post.user_id;
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";
var type = 'Post';
if (post.root_id.length > 0) {
type = "Comment";
type = 'Comment';
}
var currentUserCss = "";
var currentUserCss = '';
if (UserStore.getCurrentId() === post.user_id) {
currentUserCss = "current--user";
currentUserCss = 'current--user';
}
var channelName;
if (channel) {
channelName = (channel.type === 'D') ? "Private Message" : channel.display_name;
if (channel.type === 'D') {
channelName = 'Private Message';
} else {
channelName = channel.display_name;
}
}
var ownerOptions;
if (isOwner) {
ownerOptions = (
<div>
<a href='#' className='dropdown-toggle theme' type='button' data-toggle='dropdown' aria-expanded='false' />
<ul className='dropdown-menu' role='menu'>
<li role='presentation'><a href='#' role='menuitem' data-toggle='modal' data-target='#edit_post' data-title={type} data-message={post.message} data-postid={post.id} data-channelid={post.channel_id}>Edit</a></li>
<li role='presentation'><a href='#' role='menuitem' data-toggle='modal' data-target='#delete_post' data-title={type} data-postid={post.id} data-channelid={post.channel_id} data-comments={this.props.commentCount}>Delete</a></li>
</ul>
</div>
);
}
var fileAttachment;
if (post.filenames && post.filenames.length > 0) {
fileAttachment = (
<FileAttachmentList
filenames={post.filenames}
modalId={'rhs_view_image_modal_' + post.id}
channelId={post.channel_id}
userId={post.user_id} />
);
}
return (
<div className={"post post--root " + currentUserCss}>
<div className="post-right-channel__name">{ channelName }</div>
<div className="post-profile-img__container">
<img className="post-profile-img" src={"/api/v1/users/" + post.user_id + "/image?time=" + timestamp} height="36" width="36" />
<div className={'post post--root ' + currentUserCss}>
<div className='post-right-channel__name'>{ channelName }</div>
<div className='post-profile-img__container'>
<img className='post-profile-img' src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp} height='36' width='36' />
</div>
<div className="post__content">
<ul className="post-header">
<li className="post-header-col"><strong><UserProfile userId={post.user_id} /></strong></li>
<li className="post-header-col"><time className="post-right-root-time">{ utils.displayDate(post.create_at)+' '+utils.displayTime(post.create_at) }</time></li>
<li className="post-header-col post-header__reply">
<div className="dropdown">
{ isOwner ?
<div>
<a href="#" className="dropdown-toggle theme" type="button" data-toggle="dropdown" aria-expanded="false" />
<ul className="dropdown-menu" role="menu">
<li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#edit_post" data-title={type} data-message={post.message} data-postid={post.id} data-channelid={post.channel_id}>Edit</a></li>
<li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#delete_post" data-title={type} data-postid={post.id} data-channelid={post.channel_id} data-comments={this.props.commentCount}>Delete</a></li>
</ul>
</div>
: "" }
<div className='post__content'>
<ul className='post-header'>
<li className='post-header-col'><strong><UserProfile userId={post.user_id} /></strong></li>
<li className='post-header-col'><time className='post-right-root-time'>{utils.displayDate(post.create_at) + ' ' + utils.displayTime(post.create_at)}</time></li>
<li className='post-header-col post-header__reply'>
<div className='dropdown'>
{ownerOptions}
</div>
</li>
</ul>
<div className="post-body">
<div className='post-body'>
<p>{message}</p>
{ post.filenames && post.filenames.length > 0 ?
<FileAttachmentList
filenames={post.filenames}
modalId={"rhs_view_image_modal_" + post.id}
channelId={post.channel_id}
userId={post.user_id} />
: "" }
{fileAttachment}
</div>
</div>
<hr />
@@ -119,56 +139,104 @@ RootPost = React.createClass({
});
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 commentClass = "post";
var currentUserCss = "";
var currentUserCss = '';
if (UserStore.getCurrentId() === post.user_id) {
currentUserCss = "current--user";
currentUserCss = 'current--user';
}
var isOwner = UserStore.getCurrentId() == post.user_id;
var isOwner = UserStore.getCurrentId() === post.user_id;
var type = "Post"
var type = 'Post';
if (post.root_id.length > 0) {
type = "Comment"
type = 'Comment';
}
var message = utils.textToJsx(post.message);
var timestamp = UserStore.getCurrentUser().update_at;
return (
<div className={commentClass + " " + currentUserCss}>
<div className="post-profile-img__container">
<img className="post-profile-img" src={"/api/v1/users/" + post.user_id + "/image?time=" + timestamp} height="36" width="36" />
var loading;
var postClass = '';
if (post.state === Constants.POST_FAILED) {
postClass += ' post-fail';
loading = <a className='theme post-retry pull-right' href='#' onClick={this.retryComment}>Retry</a>;
} else if (post.state === Constants.POST_LOADING) {
postClass += ' post-waiting';
loading = <img className='post-loading-gif pull-right' src='/static/images/load.gif'/>;
}
var ownerOptions;
if (isOwner && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) {
ownerOptions = (
<div className='dropdown' onClick={function(e){$('.post-list-holder-by-time').scrollTop($('.post-list-holder-by-time').scrollTop() + 50);}}>
<a href='#' className='dropdown-toggle theme' type='button' data-toggle='dropdown' aria-expanded='false' />
<ul className='dropdown-menu' role='menu'>
<li role='presentation'><a href='#' role='menuitem' data-toggle='modal' data-target='#edit_post' data-title={type} data-message={post.message} data-postid={post.id} data-channelid={post.channel_id}>Edit</a></li>
<li role='presentation'><a href='#' role='menuitem' data-toggle='modal' data-target='#delete_post' data-title={type} data-postid={post.id} data-channelid={post.channel_id} data-comments={0}>Delete</a></li>
</ul>
</div>
<div className="post__content">
<ul className="post-header">
<li className="post-header-col"><strong><UserProfile userId={post.user_id} /></strong></li>
<li className="post-header-col"><time className="post-right-comment-time">{ utils.displayDateTime(post.create_at) }</time></li>
<li className="post-header-col post-header__reply">
{ isOwner ?
<div className="dropdown" onClick={function(e){$('.post-list-holder-by-time').scrollTop($(".post-list-holder-by-time").scrollTop() + 50);}}>
<a href="#" className="dropdown-toggle theme" type="button" data-toggle="dropdown" aria-expanded="false" />
<ul className="dropdown-menu" role="menu">
<li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#edit_post" data-title={type} data-message={post.message} data-postid={post.id} data-channelid={post.channel_id}>Edit</a></li>
<li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#delete_post" data-title={type} data-postid={post.id} data-channelid={post.channel_id} data-comments={0}>Delete</a></li>
</ul>
</div>
: "" }
);
}
var fileAttachment;
if (post.filenames && post.filenames.length > 0) {
fileAttachment = (
<FileAttachmentList
filenames={post.filenames}
modalId={'rhs_comment_view_image_modal_' + post.id}
channelId={post.channel_id}
userId={post.user_id} />
);
}
return (
<div className={'post ' + currentUserCss}>
<div className='post-profile-img__container'>
<img className='post-profile-img' src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp} height='36' width='36' />
</div>
<div className='post__content'>
<ul className='post-header'>
<li className='post-header-col'><strong><UserProfile userId={post.user_id} /></strong></li>
<li className='post-header-col'><time className='post-right-comment-time'>{utils.displayDateTime(post.create_at)}</time></li>
<li className='post-header-col post-header__reply'>
{ownerOptions}
</li>
</ul>
<div className="post-body">
<p>{message}</p>
{ post.filenames && post.filenames.length > 0 ?
<FileAttachmentList
filenames={post.filenames}
modalId={"rhs_comment_view_image_modal_" + post.id}
channelId={post.channel_id}
userId={post.user_id} />
: "" }
<div className='post-body'>
<p className={postClass}>{loading}{message}</p>
{fileAttachment}
</div>
</div>
</div>
@@ -177,31 +245,45 @@ CommentPost = React.createClass({
});
function getStateFromStores() {
return { post_list: PostStore.getSelectedPost() };
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);
PostStore.addSelectedPostChangeListener(this.onChange);
PostStore.addChangeListener(this.onChangeAll);
UserStore.addStatusesChangeListener(this.onTimeChange);
this.resize();
var self = this;
$(window).resize(function(){
$(window).resize(function() {
self.resize();
});
},
componentDidUpdate: function() {
$(".post-right__scroll").scrollTop($(".post-right__scroll")[0].scrollHeight);
$(".post-right__scroll").perfectScrollbar('update');
$('.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);
PostStore.removeSelectedPostChangeListener(this.onChange);
PostStore.removeChangeListener(this.onChangeAll);
UserStore.removeStatusesChangeListener(this.onTimeChange);
},
_onChange: function() {
onChange: function() {
if (this.isMounted()) {
var newState = getStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
@@ -209,24 +291,22 @@ module.exports = React.createClass({
}
}
},
_onChangeAll: function() {
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) {
if (!currentSelected || currentSelected.order.length === 0) {
return;
}
var currentPosts = PostStore.getPosts(currentSelected.posts[currentSelected.order[0]].channel_id);
if (!currentPosts || currentPosts.order.length == 0) {
if (!currentPosts || currentPosts.order.length === 0) {
return;
}
if (currentPosts.posts[currentPosts.order[0]].channel_id == currentSelected.posts[currentSelected.order[0]].channel_id) {
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];
@@ -238,9 +318,11 @@ module.exports = React.createClass({
this.setState(getStateFromStores());
}
},
_onTimeChange: function() {
for (var id in this.state.post_list.posts) {
if (!this.refs[id]) continue;
onTimeChange: function() {
for (var id in this.state.postList.posts) {
if (!this.refs[id]) {
continue;
}
this.refs[id].forceUpdate();
}
},
@@ -249,68 +331,70 @@ module.exports = React.createClass({
},
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');
$('.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;
var post_list = this.state.post_list;
if (post_list == null) {
if (postList == null) {
return (
<div></div>
);
}
var selected_post = post_list.posts[post_list.order[0]];
var root_post = null;
var selectedPost = postList.posts[postList.order[0]];
var rootPost = null;
if (selected_post.root_id == "") {
root_post = selected_post;
}
else {
root_post = post_list.posts[selected_post.root_id];
if (selectedPost.root_id === '') {
rootPost = selectedPost;
} else {
rootPost = postList.posts[selectedPost.root_id];
}
var posts_array = [];
var postsArray = [];
for (var postId in post_list.posts) {
var cpost = post_list.posts[postId];
if (cpost.root_id == root_post.id) {
posts_array.push(cpost);
for (var postId in postList.posts) {
var cpost = postList.posts[postId];
if (cpost.root_id === rootPost.id) {
postsArray.push(cpost);
}
}
posts_array.sort(function(a,b) {
if (a.create_at < b.create_at)
postsArray.sort(function postSort(a, b) {
if (a.create_at < b.create_at) {
return -1;
if (a.create_at > b.create_at)
}
if (a.create_at > b.create_at) {
return 1;
}
return 0;
});
var results = this.state.results;
var currentId = UserStore.getCurrentId();
var searchForm = currentId == null ? null : <SearchBox />;
var searchForm;
if (currentId != null) {
searchForm = <SearchBox />;
}
return (
<div className="post-right__container">
<div className='post-right__container'>
<FileUploadOverlay
overlayType='right' />
<div className="search-bar__container sidebar--right__search-header">{searchForm}</div>
<div className="sidebar-right__body">
<div className='search-bar__container sidebar--right__search-header'>{searchForm}</div>
<div className='sidebar-right__body'>
<RhsHeaderPost fromSearch={this.props.fromSearch} isMentionSearch={this.props.isMentionSearch} />
<div className="post-right__scroll">
<RootPost post={root_post} commentCount={posts_array.length}/>
<div className="post-right-comments-container">
{ posts_array.map(function(cpost) {
return <CommentPost ref={cpost.id} key={cpost.id} post={cpost} selected={ (cpost.id == selected_post.id) } />
<div className='post-right__scroll'>
<RootPost post={rootPost} commentCount={postsArray.length}/>
<div className='post-right-comments-container'>
{postsArray.map(function mapPosts(comPost) {
return <CommentPost ref={comPost.id} key={comPost.id} post={comPost} selected={(comPost.id === selectedPost.id)} />;
})}
</div>
<div className="post-create__container">
<CreateComment channelId={root_post.channel_id} rootId={root_post.id} />
<div className='post-create__container'>
<CreateComment channelId={rootPost.channel_id} rootId={rootPost.id} />
</div>
</div>
</div>

Просмотреть файл

@@ -3,7 +3,9 @@
var UserStore;
function getPrefix() {
if (!UserStore) UserStore = require('./user_store.jsx');
if (!UserStore) {
UserStore = require('./user_store.jsx');
}
return UserStore.getCurrentId() + '_';
}
@@ -11,15 +13,15 @@ function getPrefix() {
var BROWSER_STORE_VERSION = '.4';
module.exports = {
_initialized: false,
initialized: false,
_initialize: function() {
var currentVersion = localStorage.getItem("local_storage_version");
initialize: function() {
var currentVersion = localStorage.getItem('local_storage_version');
if (currentVersion !== BROWSER_STORE_VERSION) {
this.clear();
localStorage.setItem("local_storage_version", BROWSER_STORE_VERSION);
localStorage.setItem('local_storage_version', BROWSER_STORE_VERSION);
}
this._initialized = true;
this.initialized = true;
},
getItem: function(name, defaultValue) {
@@ -31,19 +33,25 @@ module.exports = {
},
removeItem: function(name) {
if (!this._initialized) this._initialize();
if (!this.initialized) {
this.initialize();
}
localStorage.removeItem(getPrefix() + name);
},
setGlobalItem: function(name, value) {
if (!this._initialized) this._initialize();
if (!this.initialized) {
this.initialize();
}
localStorage.setItem(name, JSON.stringify(value));
},
getGlobalItem: function(name, defaultValue) {
if (!this._initialized) this._initialize();
if (!this.initialized) {
this.initialize();
}
var result = null;
try {
@@ -58,7 +66,9 @@ module.exports = {
},
removeGlobalItem: function(name) {
if (!this._initialized) this._initialize();
if (!this.initialized) {
this.initialize();
}
localStorage.removeItem(name);
},
@@ -70,10 +80,12 @@ module.exports = {
/**
* Preforms the given action on each item that has the given prefix
* Signiture for action is action(key, value)
* Signature for action is action(key, value)
*/
actionOnItemsWithPrefix: function (prefix, action) {
if (!this._initialized) this._initialize();
actionOnItemsWithPrefix: function(prefix, action) {
if (!this.initialized) {
this.initialize();
}
var globalPrefix = getPrefix();
var globalPrefixiLen = globalPrefix.length;
@@ -87,14 +99,14 @@ module.exports = {
isLocalStorageSupported: function() {
try {
sessionStorage.setItem("testSession", '1');
sessionStorage.removeItem("testSession");
sessionStorage.setItem('testSession', '1');
sessionStorage.removeItem('testSession');
localStorage.setItem("testLocal", '1');
if (localStorage.getItem("testLocal") != '1') {
localStorage.setItem('testLocal', '1');
if (localStorage.getItem('testLocal') !== '1') {
return false;
}
localStorage.removeItem("testLocal", '1');
localStorage.removeItem('testLocal', '1');
return true;
} catch (e) {

Просмотреть файл

@@ -19,7 +19,6 @@ var MENTION_DATA_CHANGE_EVENT = 'mention_data_change';
var ADD_MENTION_EVENT = 'add_mention';
var PostStore = assign({}, EventEmitter.prototype, {
emitChange: function emitChange() {
this.emit(CHANGE_EVENT);
},
@@ -110,6 +109,108 @@ var PostStore = assign({}, EventEmitter.prototype, {
getPosts: function getPosts(channelId) {
return BrowserStore.getItem('posts_' + channelId);
},
storePost: function(post) {
this.pStorePost(post);
this.emitChange();
},
pStorePost: function(post) {
var postList = PostStore.getPosts(post.channel_id);
if (!postList) {
return;
}
if (post.pending_post_id !== '') {
this.removePendingPost(post.channel_id, post.pending_post_id);
}
post.pending_post_id = '';
postList.posts[post.id] = post;
if (postList.order.indexOf(post.id) === -1) {
postList.order.unshift(post.id);
}
this.pStorePosts(post.channel_id, postList);
},
storePendingPost: function(post) {
post.state = Constants.POST_LOADING;
var postList = this.getPendingPosts(post.channel_id);
if (!postList) {
postList = {posts: {}, order: []};
}
postList.posts[post.pending_post_id] = post;
postList.order.unshift(post.pending_post_id);
this._storePendingPosts(post.channel_id, postList);
this.emitChange();
},
_storePendingPosts: function(channelId, postList) {
var posts = postList.posts;
// sort failed posts to the bottom
postList.order.sort(function postSort(a, b) {
if (posts[a].state === Constants.POST_LOADING && posts[b].state === Constants.POST_FAILED) {
return 1;
}
if (posts[a].state === Constants.POST_FAILED && posts[b].state === Constants.POST_LOADING) {
return -1;
}
if (posts[a].create_at > posts[b].create_at) {
return -1;
}
if (posts[a].create_at < posts[b].create_at) {
return 1;
}
return 0;
});
BrowserStore.setItem('pending_posts_' + channelId, postList);
},
getPendingPosts: function(channelId) {
return BrowserStore.getItem('pending_posts_' + channelId);
},
removePendingPost: function(channelId, pendingPostId) {
this._removePendingPost(channelId, pendingPostId);
this.emitChange();
},
_removePendingPost: function(channelId, pendingPostId) {
var postList = this.getPendingPosts(channelId);
if (!postList) {
return;
}
if (pendingPostId in postList.posts) {
delete postList.posts[pendingPostId];
}
var index = postList.order.indexOf(pendingPostId);
if (index >= 0) {
postList.order.splice(index, 1);
}
this._storePendingPosts(channelId, postList);
},
clearPendingPosts: function() {
BrowserStore.actionOnItemsWithPrefix('pending_posts_', function clearPending(key) {
BrowserStore.removeItem(key);
});
},
updatePendingPost: function(post) {
var postList = this.getPendingPosts(post.channel_id);
if (!postList) {
postList = {posts: {}, order: []};
}
if (postList.order.indexOf(post.pending_post_id) === -1) {
return;
}
postList.posts[post.pending_post_id] = post;
this._storePendingPosts(post.channel_id, postList);
this.emitChange();
},
storeSearchResults: function storeSearchResults(results, isMentionSearch) {
BrowserStore.setItem('search_results', results);
BrowserStore.setItem('is_mention_search', Boolean(isMentionSearch));
@@ -181,6 +282,10 @@ PostStore.dispatchToken = AppDispatcher.register(function registry(payload) {
PostStore.pStorePosts(action.id, action.post_list);
PostStore.emitChange();
break;
case ActionTypes.RECIEVED_POST:
PostStore.pStorePost(action.post);
PostStore.emitChange();
break;
case ActionTypes.RECIEVED_SEARCH:
PostStore.storeSearchResults(action.results, action.is_mention_search);
PostStore.emitSearchChange();

Просмотреть файл

@@ -15,72 +15,80 @@ var CHANGE_EVENT = 'change';
var conn;
var SocketStore = assign({}, EventEmitter.prototype, {
initialize: function(self) {
if (!UserStore.getCurrentId()) return;
initialize: function() {
if (!UserStore.getCurrentId()) {
return;
}
if (!self) self = this;
self.setMaxListeners(0);
var self = this;
self.setMaxListeners(0);
if (window["WebSocket"] && !conn) {
var protocol = window.location.protocol == "https:" ? "wss://" : "ws://";
var port = window.location.protocol == "https:" ? ":8443" : "";
var conn_url = protocol + location.host + port + "/api/v1/websocket";
console.log("connecting to " + conn_url);
conn = new WebSocket(conn_url);
if (window.WebSocket && !conn) {
var protocol = 'ws://';
var port = '';
if (window.location.protocol === 'https:') {
protocol = 'wss://';
port = ':8443';
}
var connUrl = protocol + location.host + port + '/api/v1/websocket';
console.log('connecting to ' + connUrl);
conn = new WebSocket(connUrl);
conn.onclose = function(evt) {
console.log("websocket closed");
console.log(evt);
conn = null;
setTimeout(function(){self.initialize(self)}, 3000);
};
conn.onclose = function closeConn(evt) {
console.log('websocket closed');
console.log(evt);
conn = null;
setTimeout(
function reconnect() {
self.initialize();
},
3000
);
};
conn.onerror = function(evt) {
console.log("websocket error");
console.log(evt);
};
conn.onerror = function connError(evt) {
console.log('websocket error');
console.log(evt);
};
conn.onmessage = function(evt) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_MSG,
msg: JSON.parse(evt.data)
});
};
conn.onmessage = function connMessage(evt) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_MSG,
msg: JSON.parse(evt.data)
});
};
}
},
emitChange: function(msg) {
this.emit(CHANGE_EVENT, msg);
},
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
sendMessage: function(msg) {
if (conn && conn.readyState === WebSocket.OPEN) {
conn.send(JSON.stringify(msg));
} else if (!conn || conn.readyState === WebSocket.Closed) {
conn = null;
this.initialize();
}
}
},
emitChange: function(msg) {
this.emit(CHANGE_EVENT, msg);
},
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
sendMessage: function (msg) {
if (conn && conn.readyState === WebSocket.OPEN) {
conn.send(JSON.stringify(msg));
} else if (!conn || conn.readyState === WebSocket.Closed) {
conn = null;
this.initialize();
}
}
});
SocketStore.dispatchToken = AppDispatcher.register(function(payload) {
var action = payload.action;
var action = payload.action;
switch(action.type) {
case ActionTypes.RECIEVED_MSG:
SocketStore.emitChange(action.msg);
break;
default:
}
switch (action.type) {
case ActionTypes.RECIEVED_MSG:
SocketStore.emitChange(action.msg);
break;
default:
}
});
SocketStore.initialize();
module.exports = SocketStore;

Просмотреть файл

@@ -346,24 +346,33 @@ module.exports.search = function(terms) {
module.exports.getPosts = function(force, id, maxPosts) {
if (PostStore.getCurrentPosts() == null || force) {
var channelId = id ? id : ChannelStore.getCurrentId();
var channelId = id;
if (channelId == null) {
channelId = ChannelStore.getCurrentId();
}
if (isCallInProgress('getPosts_'+channelId)) return;
if (isCallInProgress('getPosts_' + channelId)) {
return;
}
var post_list = PostStore.getCurrentPosts();
var postList = PostStore.getCurrentPosts();
if (!maxPosts) { maxPosts = Constants.POST_CHUNK_SIZE * Constants.MAX_POST_CHUNKS };
var max = maxPosts;
if (max == null) {
max = Constants.POST_CHUNK_SIZE * Constants.MAX_POST_CHUNKS;
}
// if we already have more than POST_CHUNK_SIZE posts,
// let's get the amount we have but rounded up to next multiple of POST_CHUNK_SIZE,
// with a max at maxPosts
var numPosts = Math.min(maxPosts, Constants.POST_CHUNK_SIZE);
if (post_list && post_list.order.length > 0) {
numPosts = Math.min(maxPosts, Constants.POST_CHUNK_SIZE * Math.ceil(post_list.order.length / Constants.POST_CHUNK_SIZE));
var numPosts = Math.min(max, Constants.POST_CHUNK_SIZE);
if (postList && postList.order.length > 0) {
numPosts = Math.min(max, Constants.POST_CHUNK_SIZE * Math.ceil(postList.order.length / Constants.POST_CHUNK_SIZE));
}
if (channelId != null) {
callTracker['getPosts_'+channelId] = utils.getTimestamp();
callTracker['getPosts_' + channelId] = utils.getTimestamp();
client.getPosts(
channelId,
0,
@@ -383,7 +392,7 @@ module.exports.getPosts = function(force, id, maxPosts) {
dispatchError(err, 'getPosts');
},
function() {
callTracker['getPosts_'+channelId] = 0;
callTracker['getPosts_' + channelId] = 0;
}
);
}

Просмотреть файл

@@ -15,6 +15,7 @@ module.exports = {
RECIEVED_CHANNEL_EXTRA_INFO: null,
RECIEVED_POSTS: null,
RECIEVED_POST: null,
RECIEVED_SEARCH: null,
RECIEVED_POST_SELECTED: null,
RECIEVED_MENTION_DATA: null,
@@ -62,6 +63,8 @@ module.exports = {
GOOGLE_SERVICE: 'google',
POST_CHUNK_SIZE: 60,
MAX_POST_CHUNKS: 3,
POST_LOADING: "loading",
POST_FAILED: "failed",
RESERVED_TEAM_NAMES: [
"www",
"web",

Просмотреть файл

@@ -315,6 +315,21 @@ body.ios {
font-size: 0.97em;
white-space: pre-wrap;
}
.post-loading-gif {
height:10px;
width:10px;
margin-top:6px;
}
.post-fail {
color: #D58A8A;
}
.post-waiting {
color: #999;
}
.comment-icon__container {
margin-left: 7px;
fill: $primary-color;