From e32aee8977bf99b0f5ca446cb028b04c25e2b918 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Thu, 16 Jul 2015 15:16:11 -0400 Subject: Added events for when the active thread (ie the message thread that a user is responding to) changes --- web/react/stores/post_store.jsx | 16 ++++++++++++++++ web/react/utils/constants.jsx | 1 + 2 files changed, 17 insertions(+) (limited to 'web/react') diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx index e773bb688..1f07dca62 100644 --- a/web/react/stores/post_store.jsx +++ b/web/react/stores/post_store.jsx @@ -18,6 +18,7 @@ var SEARCH_TERM_CHANGE_EVENT = 'search_term_change'; var SELECTED_POST_CHANGE_EVENT = 'selected_post_change'; var MENTION_DATA_CHANGE_EVENT = 'mention_data_change'; var ADD_MENTION_EVENT = 'add_mention'; +var ACTIVE_THREAD_CHANGED_EVENT = 'active_thread_changed'; var PostStore = assign({}, EventEmitter.prototype, { @@ -93,6 +94,18 @@ var PostStore = assign({}, EventEmitter.prototype, { this.removeListener(ADD_MENTION_EVENT, callback); }, + emitActiveThreadChanged: function(rootId, parentId) { + this.emit(ACTIVE_THREAD_CHANGED_EVENT, rootId, parentId); + }, + + addActiveThreadChangedListener: function(callback) { + this.on(ACTIVE_THREAD_CHANGED_EVENT, callback); + }, + + removeActiveThreadChangedListener: function(callback) { + this.removeListener(ACTIVE_THREAD_CHANGED_EVENT, callback); + }, + getCurrentPosts: function() { var currentId = ChannelStore.getCurrentId(); @@ -186,6 +199,9 @@ PostStore.dispatchToken = AppDispatcher.register(function(payload) { case ActionTypes.RECIEVED_ADD_MENTION: PostStore.emitAddMention(action.id, action.username); break; + case ActionTypes.RECEIVED_ACTIVE_THREAD_CHANGED: + PostStore.emitActiveThreadChanged(action.root_id, action.parent_id); + break; default: } diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 3aadfb4b0..943787630 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -18,6 +18,7 @@ module.exports = { RECIEVED_POST_SELECTED: null, RECIEVED_MENTION_DATA: null, RECIEVED_ADD_MENTION: null, + RECEIVED_ACTIVE_THREAD_CHANGED: null, RECIEVED_PROFILES: null, RECIEVED_ME: null, -- cgit v1.2.3-1-g7c22 From 25b2e75dc664bfb80470713331c33c88e726dcf5 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Thu, 16 Jul 2015 15:25:28 -0400 Subject: Added function to reply to earlier messages by prefixing a message with carets --- web/react/components/create_post.jsx | 68 ++++++++++++++++++++++++++++++++++++ web/react/components/post.jsx | 2 +- web/react/components/post_list.jsx | 17 +++++++-- 3 files changed, 84 insertions(+), 3 deletions(-) (limited to 'web/react') diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index d38a6798f..681ca252f 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -68,6 +68,9 @@ module.exports = React.createClass({ post.channel_id = this.state.channel_id; post.filenames = this.state.previews; + post.root_id = this.state.rootId; + post.parent_id = this.state.parentId; + client.createPost(post, ChannelStore.getCurrent(), function(data) { PostStore.storeDraft(data.channel_id, data.user_id, null); @@ -92,6 +95,17 @@ module.exports = React.createClass({ } $(".post-list-holder-by-time").perfectScrollbar('update'); + + if (this.state.rootId || this.state.parentId) { + this.setState({rootId: "", parentId: ""}); + + // clear the active thread since we've now sent our message + AppDispatcher.handleViewAction({ + type: ActionTypes.RECEIVED_ACTIVE_THREAD_CHANGED, + root_id: "", + parent_id: "" + }); + } }, componentDidUpdate: function() { this.resizePostHolder(); @@ -112,6 +126,60 @@ module.exports = React.createClass({ handleUserInput: function(messageText) { this.resizePostHolder(); this.setState({messageText: messageText}); + + // look to see if the message begins with any carets to indicate that it's a reply + var replyMatch = messageText.match(/^\^+/g); + if (replyMatch) { + // the number of carets indicates how many message threads back we're replying to + var caretCount = replyMatch[0].length; + + var posts = PostStore.getCurrentPosts(); + + var rootId = ""; + + // find the nth most recent post that isn't a comment on another (ie it has no parent) where n is caretCount + for (var i = 0; i < posts.order.length; i++) { + var postId = posts.order[i]; + + if (posts.posts[postId].parent_id === "") { + if (caretCount == 1) { + rootId = postId; + break; + } else { + caretCount -= 1; + } + } + } + + if (rootId) { + // set the parent id to match the root id so that we're replying to the first post in the thread + var parentId = rootId; + + // alert the post list so that it can display the active thread + AppDispatcher.handleViewAction({ + type: ActionTypes.RECEIVED_ACTIVE_THREAD_CHANGED, + root_id: rootId, + parent_id: parentId + }); + + // save these so that we don't need to recalculate them when we send this post + this.setState({rootId: rootId, parentId: parentId}); + } else { + // we couldn't find a post to respond to + this.setState({rootId: "", parentId: ""}); + } + } else { + if (this.state.rootId || this.state.parentId) { + this.setState({rootId: "", parentId: ""}); + + AppDispatcher.handleViewAction({ + type: ActionTypes.RECEIVED_ACTIVE_THREAD_CHANGED, + root_id: "", + parent_id: "" + }); + } + } + var draft = PostStore.getCurrentDraft(); if (!draft) { draft = {} diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index e72a2d001..e3586ecde 100644 --- a/web/react/components/post.jsx +++ b/web/react/components/post.jsx @@ -83,7 +83,7 @@ module.exports = React.createClass({ : null } -
+
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index c058455ba..0fe668aef 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -22,7 +22,8 @@ function getStateFromStores() { return { post_list: PostStore.getCurrentPosts(), - channel: channel + channel: channel, + activeThreadRootId: "" }; } @@ -51,6 +52,7 @@ module.exports = React.createClass({ ChannelStore.addChangeListener(this._onChange); UserStore.addStatusesChangeListener(this._onTimeChange); SocketStore.addChangeListener(this._onSocketChange); + PostStore.addActiveThreadChangedListener(this._onActiveThreadChanged); $(".post-list-holder-by-time").perfectScrollbar(); @@ -131,6 +133,7 @@ module.exports = React.createClass({ ChannelStore.removeChangeListener(this._onChange); UserStore.removeStatusesChangeListener(this._onTimeChange); SocketStore.removeChangeListener(this._onSocketChange); + PostStore.removeActiveThreadChangedListener(this._onActiveThreadChanged); $('body').off('click.userpopover'); }, resize: function() { @@ -228,6 +231,9 @@ module.exports = React.createClass({ this.refs[id].forceUpdateInfo(); } }, + _onActiveThreadChanged: function(rootId, parentId) { + this.setState({"activeThreadRootId": rootId}); + }, getMorePosts: function(e) { e.preventDefault(); @@ -419,7 +425,14 @@ module.exports = React.createClass({ // it is the last comment if it is last post in the channel or the next post has a different root post var isLastComment = utils.isComment(post) && (i === 0 || posts[order[i-1]].root_id != post.root_id); - var postCtl = ; + // check if this is part of the thread that we're currently replying to + var isActiveThread = this.state.activeThreadRootId && (post.id === this.state.activeThreadRootId || post.root_id === this.state.activeThreadRootId); + + var postCtl = ( + + ); currentPostDay = utils.getDateForUnixTicks(post.create_at); if (currentPostDay.toDateString() != previousPostDay.toDateString()) { -- cgit v1.2.3-1-g7c22 From 644c78755cb7cf8ef818106b9629df1018d0d736 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Thu, 16 Jul 2015 16:03:06 -0400 Subject: Change the create_post component to track the active thread using the event dispatcher so that it stays in sync with the post_list --- web/react/components/create_post.jsx | 38 +++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) (limited to 'web/react') diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 681ca252f..7bc5e2481 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -152,26 +152,29 @@ module.exports = React.createClass({ } if (rootId) { - // set the parent id to match the root id so that we're replying to the first post in the thread - var parentId = rootId; - - // alert the post list so that it can display the active thread + // only dispatch an event if something changed + if (rootId != this.state.rootId) { + // set the parent id to match the root id so that we're replying to the first post in the thread + var parentId = rootId; + + // alert the post list so that it can display the active thread + AppDispatcher.handleViewAction({ + type: ActionTypes.RECEIVED_ACTIVE_THREAD_CHANGED, + root_id: rootId, + parent_id: parentId + }); + } + } else { + // we couldn't find a post to respond to so clear the active thread AppDispatcher.handleViewAction({ type: ActionTypes.RECEIVED_ACTIVE_THREAD_CHANGED, - root_id: rootId, - parent_id: parentId + root_id: "", + parent_id: "" }); - - // save these so that we don't need to recalculate them when we send this post - this.setState({rootId: rootId, parentId: parentId}); - } else { - // we couldn't find a post to respond to - this.setState({rootId: "", parentId: ""}); } } else { if (this.state.rootId || this.state.parentId) { - this.setState({rootId: "", parentId: ""}); - + // clear the active thread since there no longer is one AppDispatcher.handleViewAction({ type: ActionTypes.RECEIVED_ACTIVE_THREAD_CHANGED, root_id: "", @@ -242,10 +245,12 @@ module.exports = React.createClass({ }, componentDidMount: function() { ChannelStore.addChangeListener(this._onChange); + PostStore.addActiveThreadChangedListener(this._onActiveThreadChanged); this.resizePostHolder(); }, componentWillUnmount: function() { ChannelStore.removeChangeListener(this._onChange); + PostStore.removeActiveThreadChangedListener(this._onActiveThreadChanged); }, _onChange: function() { var channel_id = ChannelStore.getCurrentId(); @@ -262,6 +267,11 @@ module.exports = React.createClass({ this.setState({ channel_id: channel_id, messageText: messageText, initialText: messageText, submitting: false, post_error: null, previews: previews, uploadsInProgress: uploadsInProgress }); } }, + _onActiveThreadChanged: function(rootId, parentId) { + // note that we register for our own events and set the state from there so we don't need to manually set + // our state and dispatch an event each time the active thread changes + this.setState({"rootId": rootId, "parentId": parentId}); + }, getInitialState: function() { PostStore.clearDraftUploads(); -- cgit v1.2.3-1-g7c22 From 5b917dd4a6fc4259cd17ad0b2e106f3b71254ef2 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Thu, 16 Jul 2015 16:58:05 -0400 Subject: Trim the carets from the beginning of reply messages when they're posted --- web/react/components/create_post.jsx | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'web/react') diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 7bc5e2481..a3e354599 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -71,6 +71,11 @@ module.exports = React.createClass({ post.root_id = this.state.rootId; post.parent_id = this.state.parentId; + // if this is a reply, trim off any carets from the beginning of a message + if (post.root_id && post.message.startsWith("^")) { + post.message = post.message.replace(/^\^+\s*/g, ""); + } + client.createPost(post, ChannelStore.getCurrent(), function(data) { PostStore.storeDraft(data.channel_id, data.user_id, null); -- cgit v1.2.3-1-g7c22 From fafce8cb72cc8a10b98fab22c6b3547c77d75cc9 Mon Sep 17 00:00:00 2001 From: nickago Date: Tue, 21 Jul 2015 08:03:10 -0700 Subject: Upon changing to a new comment thread, fixes the the bug of the RHS scrolling to whitespace below the pane --- web/react/components/post_right.jsx | 2 ++ 1 file changed, 2 insertions(+) (limited to 'web/react') diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx index 581a1abe9..ad521adba 100644 --- a/web/react/components/post_right.jsx +++ b/web/react/components/post_right.jsx @@ -294,6 +294,8 @@ module.exports = React.createClass({ }); }, componentDidUpdate: function() { + $(".post-right__scroll").scrollTop($(".post-right__scroll")[0].scrollHeight); + $(".post-right__scroll").perfectScrollbar('update'); this.resize(); }, componentWillUnmount: function() { -- cgit v1.2.3-1-g7c22 From 7b28880294865c7441ce8b4b3efc14b1417cb5e5 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Tue, 21 Jul 2015 15:54:47 -0400 Subject: Track caretCount as part of createPost's state so that we don't unnecessarily search for a thread to reply to when the user is typing --- web/react/components/create_post.jsx | 44 ++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 22 deletions(-) (limited to 'web/react') diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index a3e354599..7b7e38ac2 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -102,7 +102,7 @@ module.exports = React.createClass({ $(".post-list-holder-by-time").perfectScrollbar('update'); if (this.state.rootId || this.state.parentId) { - this.setState({rootId: "", parentId: ""}); + this.setState({rootId: "", parentId: "", caretCount: 0}); // clear the active thread since we've now sent our message AppDispatcher.handleViewAction({ @@ -138,25 +138,30 @@ module.exports = React.createClass({ // the number of carets indicates how many message threads back we're replying to var caretCount = replyMatch[0].length; - var posts = PostStore.getCurrentPosts(); + // note that if someone else replies to this thread while a user is typing a reply, the message to which they're replying + // won't change unless they change the number of carets. this is probably the desired behaviour since we don't want the + // active message thread to change without the user noticing + if (caretCount != this.state.caretCount) { + this.setState({caretCount: caretCount}); - var rootId = ""; + var posts = PostStore.getCurrentPosts(); - // find the nth most recent post that isn't a comment on another (ie it has no parent) where n is caretCount - for (var i = 0; i < posts.order.length; i++) { - var postId = posts.order[i]; + var rootId = ""; - if (posts.posts[postId].parent_id === "") { - if (caretCount == 1) { - rootId = postId; - break; - } else { + // find the nth most recent post that isn't a comment on another (ie it has no parent) where n is caretCount + for (var i = 0; i < posts.order.length; i++) { + var postId = posts.order[i]; + + if (posts.posts[postId].parent_id === "") { caretCount -= 1; + + if (caretCount < 1) { + rootId = postId; + break; + } } } - } - if (rootId) { // only dispatch an event if something changed if (rootId != this.state.rootId) { // set the parent id to match the root id so that we're replying to the first post in the thread @@ -169,16 +174,11 @@ module.exports = React.createClass({ parent_id: parentId }); } - } else { - // we couldn't find a post to respond to so clear the active thread - AppDispatcher.handleViewAction({ - type: ActionTypes.RECEIVED_ACTIVE_THREAD_CHANGED, - root_id: "", - parent_id: "" - }); } } else { - if (this.state.rootId || this.state.parentId) { + if (this.state.caretCount > 0) { + this.setState({caretCount: 0}); + // clear the active thread since there no longer is one AppDispatcher.handleViewAction({ type: ActionTypes.RECEIVED_ACTIVE_THREAD_CHANGED, @@ -287,7 +287,7 @@ module.exports = React.createClass({ previews = draft['previews']; messageText = draft['message']; } - return { channel_id: ChannelStore.getCurrentId(), messageText: messageText, uploadsInProgress: 0, previews: previews, submitting: false, initialText: messageText }; + return { channel_id: ChannelStore.getCurrentId(), messageText: messageText, uploadsInProgress: 0, previews: previews, submitting: false, initialText: messageText, caretCount: 0 }; }, setUploads: function(val) { var oldInProgress = this.state.uploadsInProgress -- cgit v1.2.3-1-g7c22 From d4fd000ccfc4f9c128cf178755dd3b1df0cf3894 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Tue, 21 Jul 2015 15:55:34 -0400 Subject: Added handling for when the user is ^ responding to a post that gets deleted --- web/react/components/create_post.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'web/react') diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 7b7e38ac2..efb5efd80 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -92,7 +92,13 @@ module.exports = React.createClass({ }.bind(this), function(err) { var state = {} - state.server_error = err.message; + + if (err.message === "Invalid RootId parameter") { + if ($('#post_deleted').length > 0) $('#post_deleted').modal('show'); + } else { + state.server_error = err.message; + } + state.submitting = false; this.setState(state); }.bind(this) -- cgit v1.2.3-1-g7c22 From 0967a131a152e056c1cb971f895b2d1f8df4d0ed Mon Sep 17 00:00:00 2001 From: hmhealey Date: Tue, 21 Jul 2015 16:01:30 -0400 Subject: Prevent users from sending empty ^ reply messages --- web/react/components/create_post.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'web/react') diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index efb5efd80..a2448b569 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -31,6 +31,11 @@ module.exports = React.createClass({ post.message = this.state.messageText; + // if this is a reply, trim off any carets from the beginning of a message + if (this.state.rootId && post.message.startsWith("^")) { + post.message = post.message.replace(/^\^+\s*/g, ""); + } + if (post.message.trim().length === 0 && this.state.previews.length === 0) { return; } @@ -71,11 +76,6 @@ module.exports = React.createClass({ post.root_id = this.state.rootId; post.parent_id = this.state.parentId; - // if this is a reply, trim off any carets from the beginning of a message - if (post.root_id && post.message.startsWith("^")) { - post.message = post.message.replace(/^\^+\s*/g, ""); - } - client.createPost(post, ChannelStore.getCurrent(), function(data) { PostStore.storeDraft(data.channel_id, data.user_id, null); -- cgit v1.2.3-1-g7c22 From f5837c1b64994a15537a0b8df109bed504d0d20a Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Tue, 21 Jul 2015 16:15:24 -0400 Subject: Old files are saved with full paths, this changes so that new files are not saved with absolute paths and detects old files saved and fixes them. --- web/react/components/post_body.jsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'web/react') diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index d9678df30..96b441c0e 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -28,6 +28,12 @@ module.exports = React.createClass({ var type = utils.getFileType(fileInfo.ext); + // This is a temporary patch to fix issue with old files using absolute paths + if (fileInfo.path.indexOf("/api/v1/files/get") == -1) { + fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; + } + fileInfo.path = window.location.origin + "/api/v1/files/get" + fileInfo.path; + if (type === "image") { $('').attr('src', fileInfo.path+'_thumb.jpg').load(function(path, name){ return function() { $(this).remove(); @@ -102,6 +108,12 @@ module.exports = React.createClass({ var type = utils.getFileType(fileInfo.ext); + // This is a temporary patch to fix issue with old files using absolute paths + if (fileInfo.path.indexOf("/api/v1/files/get") == -1) { + fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; + } + fileInfo.path = window.location.origin + "/api/v1/files/get" + fileInfo.path; + if (type === "image") { if (i < Constants.MAX_DISPLAY_FILES) { postFiles.push( -- cgit v1.2.3-1-g7c22 From 1c08a33b92f72d77d6c5f1fce916e2cd7c655ff0 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Tue, 21 Jul 2015 16:54:24 -0400 Subject: Fixing file uploads and previews for new /team/ URL structure --- web/react/components/file_preview.jsx | 1 + web/react/components/post_body.jsx | 4 ++-- web/react/components/view_image.jsx | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) (limited to 'web/react') diff --git a/web/react/components/file_preview.jsx b/web/react/components/file_preview.jsx index 17a1e2bc2..553293d2b 100644 --- a/web/react/components/file_preview.jsx +++ b/web/react/components/file_preview.jsx @@ -19,6 +19,7 @@ module.exports = React.createClass({ var filenameSplit = filename.split('.'); var ext = filenameSplit[filenameSplit.length-1]; var type = utils.getFileType(ext); + filename = window.location.origin + "/api/v1/files/get" + filename; if (type === "image") { previews.push( diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index 96b441c0e..7871f52b7 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -29,7 +29,7 @@ module.exports = React.createClass({ var type = utils.getFileType(fileInfo.ext); // This is a temporary patch to fix issue with old files using absolute paths - if (fileInfo.path.indexOf("/api/v1/files/get") == -1) { + if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; } fileInfo.path = window.location.origin + "/api/v1/files/get" + fileInfo.path; @@ -109,7 +109,7 @@ module.exports = React.createClass({ var type = utils.getFileType(fileInfo.ext); // This is a temporary patch to fix issue with old files using absolute paths - if (fileInfo.path.indexOf("/api/v1/files/get") == -1) { + if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; } fileInfo.path = window.location.origin + "/api/v1/files/get" + fileInfo.path; diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index 2274f3f2e..4675269d5 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -36,6 +36,11 @@ module.exports = React.createClass({ src = this.props.filenames[id]; } else { var fileInfo = utils.splitFileLocation(this.props.filenames[id]); + // This is a temporary patch to fix issue with old files using absolute paths + if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { + fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; + } + fileInfo.path = window.location.origin + "/api/v1/files/get" + fileInfo.path; src = fileInfo['path'] + '_preview.jpg'; } -- cgit v1.2.3-1-g7c22 From 73ee387319d59dca073e4cd0c1b9b60b392b137d Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Tue, 21 Jul 2015 17:49:08 -0400 Subject: Fixing issues with files trasitioning to relative paths --- web/react/components/file_preview.jsx | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'web/react') diff --git a/web/react/components/file_preview.jsx b/web/react/components/file_preview.jsx index 553293d2b..e69607206 100644 --- a/web/react/components/file_preview.jsx +++ b/web/react/components/file_preview.jsx @@ -19,6 +19,10 @@ module.exports = React.createClass({ var filenameSplit = filename.split('.'); var ext = filenameSplit[filenameSplit.length-1]; var type = utils.getFileType(ext); + // This is a temporary patch to fix issue with old files using absolute paths + if (filename.indexOf("/api/v1/files/get") != -1) { + filename = filename.split("/api/v1/files/get")[1]; + } filename = window.location.origin + "/api/v1/files/get" + filename; if (type === "image") { -- cgit v1.2.3-1-g7c22 From 2d51e93d8e2b455fc325ab10c0b13fa9a497bb9a Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Tue, 21 Jul 2015 18:37:00 -0400 Subject: Fixing image previews --- web/react/components/view_image.jsx | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'web/react') diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index 4675269d5..4d5d54e7f 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -144,6 +144,11 @@ module.exports = React.createClass({ if (this.props.imgCount > 0) { preview_filename = this.props.filenames[this.state.imgId]; } else { + // This is a temporary patch to fix issue with old files using absolute paths + if (info.path.indexOf("/api/v1/files/get") != -1) { + info.path = info.path.split("/api/v1/files/get")[1]; + } + info.path = window.location.origin + "/api/v1/files/get" + info.path; preview_filename = info['path'] + '_preview.jpg'; } -- cgit v1.2.3-1-g7c22 From 5886e695e41a500d80b5121d1b6c5beb4e087780 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Tue, 21 Jul 2015 19:29:40 -0400 Subject: fix click through on the image viewer modal to work with new team domain changes --- web/react/components/view_image.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'web/react') diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index 4d5d54e7f..ac0ecf299 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -155,7 +155,7 @@ module.exports = React.createClass({ var imgClass = "hidden"; if (this.state.loaded[id] && this.state.imgId == id) imgClass = ""; - img[info['path']] = ; + img[info['path']] = ; } } -- cgit v1.2.3-1-g7c22 From d58398708ee4c47ba403bc56ee5b14fc0941f797 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Tue, 21 Jul 2015 23:34:20 -0400 Subject: fixed download link in image viewer --- web/react/components/view_image.jsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'web/react') diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index ac0ecf299..c107de4d7 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -37,7 +37,7 @@ module.exports = React.createClass({ } else { var fileInfo = utils.splitFileLocation(this.props.filenames[id]); // This is a temporary patch to fix issue with old files using absolute paths - if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { + if (fileInfo.path.indexOf("/api/v1/files/get") !== -1) { fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; } fileInfo.path = window.location.origin + "/api/v1/files/get" + fileInfo.path; @@ -145,7 +145,7 @@ module.exports = React.createClass({ preview_filename = this.props.filenames[this.state.imgId]; } else { // This is a temporary patch to fix issue with old files using absolute paths - if (info.path.indexOf("/api/v1/files/get") != -1) { + if (info.path.indexOf("/api/v1/files/get") !== -1) { info.path = info.path.split("/api/v1/files/get")[1]; } info.path = window.location.origin + "/api/v1/files/get" + info.path; @@ -161,6 +161,13 @@ module.exports = React.createClass({ var imgFragment = React.addons.createFragment(img); + // This is a temporary patch to fix issue with old files using absolute paths + var download_link = this.props.filenames[this.state.imgId]; + if (download_link.indexOf("/api/v1/files/get") !== -1) { + download_link = download_link.split("/api/v1/files/get")[1]; + } + download_link = window.location.origin + "/api/v1/files/get" + download_link; + return (
{loading} -- cgit v1.2.3-1-g7c22 From 29dd714d8254de13cddff133dfa00f2bb23ea7ec Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 22 Jul 2015 07:53:54 -0400 Subject: fixed thumbnails and files in RHS --- web/react/components/post_right.jsx | 49 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 25 deletions(-) (limited to 'web/react') diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx index 581a1abe9..7315266e6 100644 --- a/web/react/components/post_right.jsx +++ b/web/react/components/post_right.jsx @@ -91,28 +91,27 @@ RootPost = React.createClass({ var re2 = new RegExp('\\(', 'g'); var re3 = new RegExp('\\)', 'g'); for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) { - var fileSplit = filenames[i].split('.'); - if (fileSplit.length < 2) continue; + var fileInfo = utils.splitFileLocation(filenames[i]); + var ftype = utils.getFileType(fileInfo.ext); - var ext = fileSplit[fileSplit.length-1]; - fileSplit.splice(fileSplit.length-1,1); - var filePath = fileSplit.join('.'); - var filename = filePath.split('/')[filePath.split('/').length-1]; - - var ftype = utils.getFileType(ext); + // This is a temporary patch to fix issue with old files using absolute paths + if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { + fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; + } + fileInfo.path = window.location.origin + "/api/v1/files/get" + fileInfo.path; if (ftype === "image") { - var url = filePath.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29'); + var url = fileInfo.path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29'); postFiles.push( -
-
+
+
); images.push(filenames[i]); } else { postFiles.push( -
- +
+ @@ -201,28 +200,28 @@ CommentPost = React.createClass({ var re2 = new RegExp('\\(', 'g'); var re3 = new RegExp('\\)', 'g'); for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) { - var fileSplit = filenames[i].split('.'); - if (fileSplit.length < 2) continue; - var ext = fileSplit[fileSplit.length-1]; - fileSplit.splice(fileSplit.length-1,1) - var filePath = fileSplit.join('.'); - var filename = filePath.split('/')[filePath.split('/').length-1]; + var fileInfo = utils.splitFileLocation(filenames[i]); + var type = utils.getFileType(fileInfo.ext); - var type = utils.getFileType(ext); + // This is a temporary patch to fix issue with old files using absolute paths + if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { + fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; + } + fileInfo.path = window.location.origin + "/api/v1/files/get" + fileInfo.path; if (type === "image") { - var url = filePath.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29'); + var url = fileInfo.path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29'); postFiles.push( -
-
+
+
); images.push(filenames[i]); } else { postFiles.push( -
- +
+ -- cgit v1.2.3-1-g7c22 From 2182953be5ba86ef46a512c189cf7d5f4dc2eada Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 22 Jul 2015 08:01:10 -0400 Subject: fix the ability to remove file previews once uploaded --- web/react/components/file_preview.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'web/react') diff --git a/web/react/components/file_preview.jsx b/web/react/components/file_preview.jsx index e69607206..fdd12feec 100644 --- a/web/react/components/file_preview.jsx +++ b/web/react/components/file_preview.jsx @@ -16,6 +16,7 @@ module.exports = React.createClass({ var previews = []; this.props.files.forEach(function(filename) { + var originalFilename = filename; var filenameSplit = filename.split('.'); var ext = filenameSplit[filenameSplit.length-1]; var type = utils.getFileType(ext); @@ -27,14 +28,14 @@ module.exports = React.createClass({ if (type === "image") { previews.push( -
+
); } else { previews.push( -
+
-- cgit v1.2.3-1-g7c22 From 03cd1cc7d6581975cc85cacee678c90499e0f0aa Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Wed, 22 Jul 2015 09:51:42 -0400 Subject: Small patch for a null TypeError in post_list updating code --- web/react/components/post_list.jsx | 1 + 1 file changed, 1 insertion(+) (limited to 'web/react') diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index c058455ba..a2b2ae03f 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -223,6 +223,7 @@ module.exports = React.createClass({ } }, _onTimeChange: function() { + if (!this.state.post_list) return; for (var id in this.state.post_list.posts) { if (!this.refs[id]) continue; this.refs[id].forceUpdateInfo(); -- cgit v1.2.3-1-g7c22 From b3b01339306a93d227c4f29337750c4730dd25f1 Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Wed, 1 Jul 2015 18:40:20 -0700 Subject: Adding back Access History and Active Devices --- web/react/components/access_history_modal.jsx | 100 +++++++++++++++ web/react/components/activity_log_modal.jsx | 116 +++++++++++++++++ web/react/components/user_settings.jsx | 172 ++------------------------ web/react/components/user_settings_modal.jsx | 2 - web/react/pages/channel.jsx | 12 ++ 5 files changed, 240 insertions(+), 162 deletions(-) create mode 100644 web/react/components/access_history_modal.jsx create mode 100644 web/react/components/activity_log_modal.jsx (limited to 'web/react') diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx new file mode 100644 index 000000000..b23b3213f --- /dev/null +++ b/web/react/components/access_history_modal.jsx @@ -0,0 +1,100 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var UserStore = require('../stores/user_store.jsx'); +var AsyncClient = require('../utils/async_client.jsx'); +var Utils = require('../utils/utils.jsx'); + +function getStateFromStoresForAudits() { + return { + audits: UserStore.getAudits() + }; +} + +module.exports = React.createClass({ + componentDidMount: function() { + UserStore.addAuditsChangeListener(this._onChange); + AsyncClient.getAudits(); + + var self = this; + $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) { + self.setState({ moreInfo: [] }); + }); + }, + componentWillUnmount: function() { + UserStore.removeAuditsChangeListener(this._onChange); + }, + _onChange: function() { + this.setState(getStateFromStoresForAudits()); + }, + handleMoreInfo: function(index) { + var newMoreInfo = this.state.moreInfo; + newMoreInfo[index] = true; + this.setState({ moreInfo: newMoreInfo }); + }, + getInitialState: function() { + var initialState = getStateFromStoresForAudits(); + initialState.moreInfo = []; + return initialState; + }, + render: function() { + var accessList = []; + var currentHistoryDate = null; + + for (var i = 0; i < this.state.audits.length; i++) { + var currentAudit = this.state.audits[i]; + var newHistoryDate = new Date(currentAudit.create_at); + var newDate = null; + + if (!currentHistoryDate || currentHistoryDate.toLocaleDateString() !== newHistoryDate.toLocaleDateString()) { + currentHistoryDate = newHistoryDate; + newDate = (
{currentHistoryDate.toDateString()}
); + } + + accessList[i] = ( +
+
{newDate}
+
+
{newHistoryDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute:'2-digit'})}
+
+
{"IP: " + currentAudit.ip_address}
+ { this.state.moreInfo[i] ? +
+
{"Session ID: " + currentAudit.session_id}
+
{"URL: " + currentAudit.action.replace("/api/v1", "")}
+
+ : + More info + } +
+ {i < this.state.audits.length - 1 ? +
+ : + null + } +
+
+ ); + } + + return ( +
+ +
+ ); + } +}); diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx new file mode 100644 index 000000000..d6f8f40eb --- /dev/null +++ b/web/react/components/activity_log_modal.jsx @@ -0,0 +1,116 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var UserStore = require('../stores/user_store.jsx'); +var Client = require('../utils/client.jsx'); +var AsyncClient = require('../utils/async_client.jsx'); + +function getStateFromStoresForSessions() { + return { + sessions: UserStore.getSessions(), + server_error: null, + client_error: null + }; +} + +module.exports = React.createClass({ + submitRevoke: function(altId) { + var self = this; + Client.revokeSession(altId, + function(data) { + AsyncClient.getSessions(); + }.bind(this), + function(err) { + state = getStateFromStoresForSessions(); + state.server_error = err; + this.setState(state); + }.bind(this) + ); + }, + componentDidMount: function() { + UserStore.addSessionsChangeListener(this._onChange); + AsyncClient.getSessions(); + + var self = this; + $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) { + self.setState({ moreInfo: [] }); + }); + }, + componentWillUnmount: function() { + UserStore.removeSessionsChangeListener(this._onChange); + }, + _onChange: function() { + this.setState(getStateFromStoresForSessions()); + }, + handleMoreInfo: function(index) { + var newMoreInfo = this.state.moreInfo; + newMoreInfo[index] = true; + this.setState({ moreInfo: newMoreInfo }); + }, + getInitialState: function() { + var initialState = getStateFromStoresForSessions(); + initialState.moreInfo = []; + return initialState; + }, + render: function() { + var activityList = []; + var server_error = this.state.server_error ? this.state.server_error : null; + + for (var i = 0; i < this.state.sessions.length; i++) { + var currentSession = this.state.sessions[i]; + var lastAccessTime = new Date(currentSession.last_activity_at); + var firstAccessTime = new Date(currentSession.create_at); + var devicePicture = ""; + + if (currentSession.props.platform === "Windows") { + devicePicture = "fa fa-windows"; + } + else if (currentSession.props.platform === "Macintosh" || currentSession.props.platform === "iPhone") { + devicePicture = "fa fa-apple"; + } + + activityList[i] = ( +
+
+
{currentSession.props.platform}
+
+
{"Last activity: " + lastAccessTime.toDateString() + ", " + lastAccessTime.toLocaleTimeString()}
+ { this.state.moreInfo[i] ? +
+
{"First time active: " + firstAccessTime.toDateString() + ", " + lastAccessTime.toLocaleTimeString()}
+
{"OS: " + currentSession.props.os}
+
{"Browser: " + currentSession.props.browser}
+
{"Session ID: " + currentSession.alt_id}
+
+ : + More info + } +
+
+
+
+ ); + } + + return ( +
+ +
+ ); + } +}); diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx index 59c97c309..ad890334e 100644 --- a/web/react/components/user_settings.jsx +++ b/web/react/components/user_settings.jsx @@ -5,6 +5,8 @@ var UserStore = require('../stores/user_store.jsx'); var SettingItemMin = require('./setting_item_min.jsx'); var SettingItemMax = require('./setting_item_max.jsx'); var SettingPicture = require('./setting_picture.jsx'); +var AccessHistoryModal = require('./access_history_modal.jsx'); +var ActivityLogModal = require('./activity_log_modal.jsx'); var client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var utils = require('../utils/utils.jsx'); @@ -443,149 +445,6 @@ var NotificationsTab = React.createClass({ } }); -function getStateFromStoresForSessions() { - return { - sessions: UserStore.getSessions(), - server_error: null, - client_error: null - }; -} - -var SessionsTab = React.createClass({ - submitRevoke: function(altId) { - client.revokeSession(altId, - function(data) { - AsyncClient.getSessions(); - }.bind(this), - function(err) { - state = this.getStateFromStoresForSessions(); - state.server_error = err; - this.setState(state); - }.bind(this) - ); - }, - componentDidMount: function() { - UserStore.addSessionsChangeListener(this._onChange); - AsyncClient.getSessions(); - }, - componentWillUnmount: function() { - UserStore.removeSessionsChangeListener(this._onChange); - }, - _onChange: function() { - this.setState(getStateFromStoresForSessions()); - }, - getInitialState: function() { - return getStateFromStoresForSessions(); - }, - render: function() { - var server_error = this.state.server_error ? this.state.server_error : null; - - return ( -
-
- -

Sessions

-
-
-

Sessions

-
- { server_error } -
- - - - - - { - this.state.sessions.map(function(value, index) { - return ( - - - - - - - - - - ); - }, this) - } - -
IdPlatformOSBrowserCreatedLast ActivityRevoke
{ value.alt_id }{value.props.platform}{value.props.os}{value.props.browser}{ new Date(value.create_at).toLocaleString() }{ new Date(value.last_activity_at).toLocaleString() }
-
-
-
-
- ); - } -}); - -function getStateFromStoresForAudits() { - return { - audits: UserStore.getAudits() - }; -} - -var AuditTab = React.createClass({ - componentDidMount: function() { - UserStore.addAuditsChangeListener(this._onChange); - AsyncClient.getAudits(); - }, - componentWillUnmount: function() { - UserStore.removeAuditsChangeListener(this._onChange); - }, - _onChange: function() { - this.setState(getStateFromStoresForAudits()); - }, - getInitialState: function() { - return getStateFromStoresForAudits(); - }, - render: function() { - return ( -
-
- -

Activity Log

-
-
-

Activity Log

-
-
- - - - - - - - - - - - { - this.state.audits.map(function(value, index) { - return ( - - - - - - - - ); - }, this) - } - -
TimeActionIP AddressSessionOther Info
{ new Date(value.create_at).toLocaleString() }{ value.action.replace("/api/v1", "") }{ value.ip_address }{ value.session_id }{ value.extra_info }
-
-
-
-
- ); - } -}); - var SecurityTab = React.createClass({ submitPassword: function(e) { e.preventDefault(); @@ -637,6 +496,12 @@ var SecurityTab = React.createClass({ updateConfirmPassword: function(e) { this.setState({ confirm_password: e.target.value }); }, + handleHistoryOpen: function() { + $("#user_settings1").modal('hide'); + }, + handleDevicesOpen: function() { + $("#user_settings1").modal('hide'); + }, getInitialState: function() { return { current_password: '', new_password: '', confirm_password: '' }; }, @@ -711,6 +576,10 @@ var SecurityTab = React.createClass({ ); @@ -1225,23 +1094,6 @@ module.exports = React.createClass({
); - - /* Temporarily removing sessions and activity_log tabs - - } else if (this.props.activeTab === 'sessions') { - return ( -
- -
- ); - } else if (this.props.activeTab === 'activity_log') { - return ( -
- -
- ); - */ - } else if (this.props.activeTab === 'appearance') { return (
diff --git a/web/react/components/user_settings_modal.jsx b/web/react/components/user_settings_modal.jsx index 1761e575a..421027244 100644 --- a/web/react/components/user_settings_modal.jsx +++ b/web/react/components/user_settings_modal.jsx @@ -30,8 +30,6 @@ module.exports = React.createClass({ tabs.push({name: "security", ui_name: "Security", icon: "glyphicon glyphicon-lock"}); tabs.push({name: "notifications", ui_name: "Notifications", icon: "glyphicon glyphicon-exclamation-sign"}); tabs.push({name: "appearance", ui_name: "Appearance", icon: "glyphicon glyphicon-wrench"}); - //tabs.push({name: "sessions", ui_name: "Sessions", icon: "glyphicon glyphicon-globe"}); - //tabs.push({name: "activity_log", ui_name: "Activity Log", icon: "glyphicon glyphicon-time"}); return (