summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--model/post.go31
-rw-r--r--web/react/components/create_post.jsx29
-rw-r--r--web/react/components/post.jsx31
-rw-r--r--web/react/components/post_body.jsx32
-rw-r--r--web/react/components/post_list.jsx26
-rw-r--r--web/react/stores/post_store.jsx57
-rw-r--r--web/react/utils/async_client.jsx2
-rw-r--r--web/sass-files/sass/partials/_post.scss18
8 files changed, 190 insertions, 36 deletions
diff --git a/model/post.go b/model/post.go
index f6f33b1e8..0c035d4e7 100644
--- a/model/post.go
+++ b/model/post.go
@@ -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 {
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 377e7bd34..76f2bf262 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -65,10 +65,17 @@ module.exports = React.createClass({
post.channel_id = this.state.channelId;
post.filenames = this.state.previews;
- client.createPost(post, ChannelStore.getCurrent(),
+ var time = utils.getTimestamp();
+ post.pending_post_id = user_id + ":"+ time;
+ post.user_id = user_id;
+ post.create_at = time;
+ post.root_id = this.state.rootId;
+ post.parent_id = this.state.parentId;
+
+ var channel = ChannelStore.getCurrent();
+
+ 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);
@@ -79,13 +86,25 @@ module.exports = React.createClass({
ChannelStore.setChannelMember(member);
}.bind(this),
function(err) {
- var state = {};
- state.serverError = err.message;
+ var state = {}
+
+ if (err.message === "Invalid RootId parameter") {
+ if ($('#post_deleted').length > 0) $('#post_deleted').modal('show');
+ PostStore.removePendingPost(post.pending_post_id);
+ } else {
+ post.did_fail = true;
+ PostStore.updatePendingPost(post);
+ }
state.submitting = false;
this.setState(state);
}.bind(this)
);
+
+ post.is_loading = true;
+ PostStore.storePendingPost(post);
+ PostStore.storeDraft(channel.id, user_id, null);
+ this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
}
$('.post-list-holder-by-time').perfectScrollbar('update');
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index f099c67ab..c985eaeb2 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -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,31 @@ 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 member = ChannelStore.getMember(post.channel_id);
+ member.msg_count = channel.total_msg_count;
+ member.last_viewed_at = (new Date).getTime();
+ ChannelStore.setChannelMember(member);
+ }.bind(this),
+ function(err) {
+ post.did_fail = true;
+ PostStore.updatePendingPost(post);
+ this.forceUpdate();
+ }.bind(this)
+ );
+
+ post.did_fail = false;
+ post.is_loading = true;
+ PostStore.updatePendingPost(post);
+ this.forceUpdate();
+ },
getInitialState: function() {
return { };
},
@@ -79,7 +108,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>
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index 860c96d84..65e045344 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -59,23 +59,35 @@ module.exports = React.createClass({
postClass += " post-comment";
}
+ var loading;
+ if (post.did_fail) {
+ postClass += " post-fail";
+ loading = <a className="post-retry pull-right" href="#" onClick={this.props.retryPost}>Retry</a>;
+ } else if (post.is_loading) {
+ postClass += " post-waiting";
+ loading = <img className="post-loading-gif pull-right" src="/static/images/load.gif"/>;
+ }
+
var embed;
if (filenames.length === 0 && this.state.links) {
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 }
+ {comment}
+ <p key={post.id+"_message"} className={postClass}>{loading}<span>{inner}</span></p>
+ {fileAttachmentHolder}
+ {embed}
</div>
);
}
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 8c76eb82c..834d7b0ff 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -20,8 +20,16 @@ function getStateFromStores() {
if (channel == null) channel = {};
+ var post_list = PostStore.getCurrentPosts();
+ var pending_post_list = PostStore.getPendingPosts(channel.id);
+
+ if (pending_post_list) {
+ post_list.order = pending_post_list.order.concat(post_list.order);
+ for (var pid in pending_post_list.posts) { post_list.posts[pid] = pending_post_list.posts[pid] };
+ }
+
return {
- post_list: PostStore.getCurrentPosts(),
+ post_list: post_list,
channel: channel
};
}
@@ -186,8 +194,16 @@ module.exports = React.createClass({
if (msg.action == "posted") {
var post = JSON.parse(msg.props.post);
- var post_list = PostStore.getPosts(msg.channel_id);
- if (!post_list) return;
+ if (post.pending_post_id !== "") {
+ PostStore.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);
+ }
post_list.posts[post.id] = post;
if (post_list.order.indexOf(post.id) === -1) {
@@ -456,7 +472,9 @@ module.exports = React.createClass({
);
}
- if (post.create_at > last_viewed && !rendered_last_viewed) {
+ var userId = UserStore.getCurrentId();
+
+ if (post.user_id !== userId && post.create_at > last_viewed && !rendered_last_viewed) {
rendered_last_viewed = true;
postCtls.push(
<div key="unviewed" className="new-separator">
diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx
index 9ebdf734c..40c69d782 100644
--- a/web/react/stores/post_store.jsx
+++ b/web/react/stores/post_store.jsx
@@ -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);
},
@@ -104,6 +103,62 @@ var PostStore = assign({}, EventEmitter.prototype, {
this.pStorePosts(channelId, posts);
this.emitChange();
},
+ storePendingPost: function(post) {
+ var post_list = this.getPendingPosts(post.channel_id);
+ if (!post_list) {
+ post_list = {posts: {}, order: []};
+ }
+
+ post_list.posts[post.pending_post_id] = post;
+ post_list.order.unshift(post.pending_post_id);
+ this._storePendingPosts(post.channel_id, post_list);
+ this.emitChange();
+ },
+ _storePendingPosts: function(channelId, posts) {
+ BrowserStore.setItem("pending_posts_" + channelId, posts);
+ },
+ getPendingPosts: function(channelId) {
+ return BrowserStore.getItem("pending_posts_" + channelId);
+ },
+ removePendingPost: function(channelId, pending_post_id) {
+ this._removePendingPost(channelId, pending_post_id);
+ this.emitChange();
+ },
+ _removePendingPost: function(channelId, pending_post_id) {
+ var post_list = this.getPendingPosts(channelId);
+ if (!post_list) return;
+
+ if (pending_post_id in post_list.posts) delete post_list.posts[pending_post_id];
+ var index = post_list.order.indexOf(pending_post_id);
+ if (index >= 0) post_list.order.splice(index, 1);
+
+ this._storePendingPosts(channelId, post_list);
+ },
+ clearPendingPosts: function(channelId) {
+ BrowserStore.removeItem("pending_posts_" + channelId)
+ },
+ removeNonFailedPendingPosts: function(channelId) {
+ var post_list = this.getPendingPosts(channelId);
+ if (!post_list) return;
+
+ var posts = post_list.posts;
+
+ for (var id in posts) {
+ if (!posts[id].did_fail) this._removePendingPost(channelId, id);
+ }
+ },
+ updatePendingPost: function(post) {
+ var post_list = this.getPendingPosts(post.channel_id);
+ if (!post_list) {
+ post_list = {posts: {}, order: []};
+ }
+
+ if (post_list.order.indexOf(post.pending_post_id) === -1) return;
+
+ post_list.posts[post.pending_post_id] = post;
+ this._storePendingPosts(post.channel_id, post_list);
+ this.emitChange();
+ },
pStorePosts: function pStorePosts(channelId, posts) {
BrowserStore.setItem('posts_' + channelId, posts);
},
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index 8b6d821d6..8fa022cbc 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -377,6 +377,8 @@ module.exports.getPosts = function(force, id, maxPosts) {
post_list: data
});
+ PostStore.removeNonFailedPendingPosts(channelId);
+
module.exports.getProfiles();
},
function(err) {
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index c7add21a2..1beebeac6 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -461,3 +461,21 @@ body.ios {
}
}
}
+
+.post-waiting {
+ color:lightgrey;
+}
+
+.post-loading-gif {
+ height:10px;
+ width:10px;
+ margin-top:6px;
+}
+
+.post-fail {
+ color:red;
+}
+
+.post-retry {
+ color:darkgrey;
+}