diff --git a/model/post.go b/model/post.go index f6f33b1e86..0c035d4e79 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/channel_loader.jsx b/web/react/components/channel_loader.jsx index 6b80f6012c..525b67b5c0 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -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() { diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 885efab7a8..1de768872d 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -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; diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 377e7bd34e..3714560eac 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -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) { diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index f099c67abb..b798dc7ca5 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,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 }
- Commented on {name}{apostrophe} message: {message} +
+ Commented on {name}{apostrophe} message: {message}
); - postClass += " post-comment"; + postClass += ' post-comment'; + } + + var loading; + if (post.state === Constants.POST_FAILED) { + postClass += ' post-fail'; + loading = Retry; + } else if (post.state === Constants.POST_LOADING) { + postClass += ' post-waiting'; + loading =
;
}
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 = ({inner}
- { filenames && filenames.length > 0 ? -{loading}{inner}
+ {fileAttachmentHolder} + {embed}Beginning of Channel
; + var moreMessages =Beginning of Channel
; - 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 = Load more messages; + moreMessages = Load more messages; } 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 = ( -
- {"This is the start of your private message history with " + teammate_name + "." }
- {"Private messages and files shared here are not shown to people outside this area."}
+
+ {'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.'}
{"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."}
+ 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.'}
- Welcome to {ui_name}! + moreMessages = ( +
+ Welcome to {uiName}!
- {"This is the first channel " + strings.Team + "mates see when they"}
+ {'This is the first channel ' + strings.Team + 'mates see when they'}
sign up - use it for posting updates everyone needs to know.
@@ -384,29 +412,44 @@ module.exports = React.createClass({
- {"This is the start of " + ui_name + ", a channel for non-work-related conversations."} + moreMessages = ( +
+ {'This is the start of ' + uiName + ', a channel for non-work-related conversations.'}
- { 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 = ( +
+ {createMessage}
+ {memberMessage}
{message}
- { post.filenames && post.filenames.length > 0 ? -
;
+ }
+
+ var ownerOptions;
+ if (isOwner && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) {
+ ownerOptions = (
+
- {message}
- { post.filenames && post.filenames.length > 0 ? -{loading}{message}
+ {fileAttachment}