summaryrefslogtreecommitdiffstats
path: root/webapp
diff options
context:
space:
mode:
authorDavid Lu <david.lu@hotmail.com>2016-06-06 10:01:35 -0700
committerHarrison Healey <harrisonmhealey@gmail.com>2016-06-06 13:01:35 -0400
commit2c42294bbcab3cd5cfdce9604e5872fe4a12e538 (patch)
tree60b20100acbd0efd6ed6293d88b222ef3f904088 /webapp
parent96e8fc165fab315b48f55e9ca54e689b34119967 (diff)
downloadchat-2c42294bbcab3cd5cfdce9604e5872fe4a12e538.tar.gz
chat-2c42294bbcab3cd5cfdce9604e5872fe4a12e538.tar.bz2
chat-2c42294bbcab3cd5cfdce9604e5872fe4a12e538.zip
PLT-3101 Added message history (#3205)
* Added message history * Minor logical changes * Fixed indexes resetting * Fixed double messages * Fixed resetting main history when RHS opened
Diffstat (limited to 'webapp')
-rw-r--r--webapp/components/create_comment.jsx42
-rw-r--r--webapp/components/create_post.jsx47
-rw-r--r--webapp/components/edit_post_modal.jsx3
-rw-r--r--webapp/stores/message_history_store.jsx79
-rw-r--r--webapp/utils/constants.jsx1
-rw-r--r--webapp/utils/post_utils.jsx1
6 files changed, 147 insertions, 26 deletions
diff --git a/webapp/components/create_comment.jsx b/webapp/components/create_comment.jsx
index 6cd2054a1..8c80ad993 100644
--- a/webapp/components/create_comment.jsx
+++ b/webapp/components/create_comment.jsx
@@ -11,6 +11,7 @@ import UserStore from 'stores/user_store.jsx';
import PostDeletedModal from './post_deleted_modal.jsx';
import PostStore from 'stores/post_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
+import MessageHistoryStore from 'stores/message_history_store.jsx';
import Textbox from './textbox.jsx';
import MsgTyping from './msg_typing.jsx';
import FileUpload from './file_upload.jsx';
@@ -68,11 +69,11 @@ class CreateComment extends React.Component {
this.hidePostDeletedModal = this.hidePostDeletedModal.bind(this);
PostStore.clearCommentDraftUploads();
+ MessageHistoryStore.resetHistoryIndex('comment');
const draft = PostStore.getCommentDraft(this.props.rootId);
this.state = {
messageText: draft.message,
- lastMessage: '',
uploadsInProgress: draft.uploadsInProgress,
previews: draft.previews,
submitting: false,
@@ -80,18 +81,22 @@ class CreateComment extends React.Component {
showPostDeletedModal: false
};
}
+
componentDidMount() {
PreferenceStore.addChangeListener(this.onPreferenceChange);
this.focusTextbox();
}
+
componentWillUnmount() {
PreferenceStore.removeChangeListener(this.onPreferenceChange);
}
+
onPreferenceChange() {
this.setState({
ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter')
});
}
+
componentDidUpdate(prevProps, prevState) {
if (prevState.uploadsInProgress < this.state.uploadsInProgress) {
$('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight);
@@ -101,6 +106,7 @@ class CreateComment extends React.Component {
this.focusTextbox();
}
}
+
handleSubmit(e) {
e.preventDefault();
@@ -125,6 +131,8 @@ class CreateComment extends React.Component {
return;
}
+ MessageHistoryStore.storeMessageInHistory(this.state.messageText);
+
const userId = UserStore.getCurrentId();
post.channel_id = this.props.channelId;
@@ -173,13 +181,13 @@ class CreateComment extends React.Component {
this.setState({
messageText: '',
- lastMessage: this.state.messageText,
submitting: false,
postError: null,
previews: [],
serverError: null
});
}
+
commentMsgKeyPress(e) {
if (this.state.ctrlSend && e.ctrlKey || !this.state.ctrlSend) {
if (e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
@@ -191,6 +199,7 @@ class CreateComment extends React.Component {
GlobalActions.emitLocalUserTypingEvent(this.props.channelId, this.props.rootId);
}
+
handleUserInput(messageText) {
const draft = PostStore.getCommentDraft(this.props.rootId);
draft.message = messageText;
@@ -199,6 +208,7 @@ class CreateComment extends React.Component {
$('.post-right__scroll').parent().scrollTop($('.post-right__scroll')[0].scrollHeight);
this.setState({messageText: messageText});
}
+
handleKeyDown(e) {
if (this.state.ctrlSend && e.keyCode === KeyCodes.ENTER && e.ctrlKey === true) {
this.commentMsgKeyPress(e);
@@ -224,22 +234,21 @@ class CreateComment extends React.Component {
});
}
- if ((e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && e.keyCode === KeyCodes.UP) {
- const lastPost = PostStore.getCurrentUsersLatestPost(this.props.channelId, this.props.rootId);
- if (!lastPost || this.state.messageText !== '') {
- return;
- }
- e.preventDefault();
- let message = lastPost.message;
- if (this.state.lastMessage !== '') {
- message = this.state.lastMessage;
+ if ((e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && (e.keyCode === Constants.KeyCodes.UP || e.keyCode === Constants.KeyCodes.DOWN)) {
+ const lastMessage = MessageHistoryStore.nextMessageInHistory(e.keyCode, this.state.messageText, 'comment');
+ if (lastMessage !== null) {
+ e.preventDefault();
+ this.setState({
+ messageText: lastMessage
+ });
}
- this.setState({messageText: message});
}
}
+
handleUploadClick() {
this.focusTextbox();
}
+
handleUploadStart(clientIds) {
const draft = PostStore.getCommentDraft(this.props.rootId);
@@ -252,6 +261,7 @@ class CreateComment extends React.Component {
// but this also resets the focus after a drag and drop
this.focusTextbox();
}
+
handleFileUploadComplete(filenames, clientIds) {
const draft = PostStore.getCommentDraft(this.props.rootId);
@@ -269,6 +279,7 @@ class CreateComment extends React.Component {
this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
}
+
handleUploadError(err, clientId) {
if (clientId === -1) {
this.setState({serverError: err});
@@ -285,6 +296,7 @@ class CreateComment extends React.Component {
this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err});
}
}
+
removePreview(id) {
const previews = this.state.previews;
const uploadsInProgress = this.state.uploadsInProgress;
@@ -309,30 +321,36 @@ class CreateComment extends React.Component {
this.setState({previews: previews, uploadsInProgress: uploadsInProgress});
}
+
componentWillReceiveProps(newProps) {
if (newProps.rootId !== this.props.rootId) {
const draft = PostStore.getCommentDraft(newProps.rootId);
this.setState({messageText: draft.message, uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
}
}
+
getFileCount() {
return this.state.previews.length + this.state.uploadsInProgress.length;
}
+
focusTextbox() {
if (!Utils.isMobile()) {
this.refs.textbox.focus();
}
}
+
showPostDeletedModal() {
this.setState({
showPostDeletedModal: true
});
}
+
hidePostDeletedModal() {
this.setState({
showPostDeletedModal: false
});
}
+
render() {
let serverError = null;
if (this.state.serverError) {
diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx
index 4c14b03de..ea4459d7d 100644
--- a/webapp/components/create_post.jsx
+++ b/webapp/components/create_post.jsx
@@ -17,6 +17,7 @@ import * as ChannelActions from 'actions/channel_actions.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import PostStore from 'stores/post_store.jsx';
+import MessageHistoryStore from 'stores/message_history_store.jsx';
import UserStore from 'stores/user_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
@@ -79,7 +80,6 @@ class CreatePost extends React.Component {
this.state = {
channelId: ChannelStore.getCurrentId(),
messageText: draft.messageText,
- lastMessage: '',
uploadsInProgress: draft.uploadsInProgress,
previews: draft.previews,
submitting: false,
@@ -90,6 +90,7 @@ class CreatePost extends React.Component {
showPostDeletedModal: false
};
}
+
getCurrentDraft() {
const draft = PostStore.getCurrentDraft();
const safeDraft = {previews: [], messageText: '', uploadsInProgress: []};
@@ -108,6 +109,7 @@ class CreatePost extends React.Component {
return safeDraft;
}
+
handleSubmit(e) {
e.preventDefault();
@@ -128,8 +130,10 @@ class CreatePost extends React.Component {
return;
}
+ MessageHistoryStore.storeMessageInHistory(this.state.messageText);
+
this.setState({submitting: true, serverError: null});
- this.setState({lastMessage: this.state.messageText});
+
if (post.message.indexOf('/') === 0) {
ChannelActions.executeCommand(
this.state.channelId,
@@ -158,6 +162,7 @@ class CreatePost extends React.Component {
this.sendMessage(post);
}
}
+
sendMessage(post) {
post.channel_id = this.state.channelId;
post.filenames = this.state.previews;
@@ -193,11 +198,13 @@ class CreatePost extends React.Component {
}
);
}
+
focusTextbox() {
if (!Utils.isMobile()) {
this.refs.textbox.focus();
}
}
+
postMsgKeyPress(e) {
if (this.state.ctrlSend && e.ctrlKey || !this.state.ctrlSend) {
if (e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
@@ -209,6 +216,7 @@ class CreatePost extends React.Component {
GlobalActions.emitLocalUserTypingEvent(this.state.channelId, '');
}
+
handleUserInput(messageText) {
this.setState({messageText});
@@ -216,9 +224,11 @@ class CreatePost extends React.Component {
draft.message = messageText;
PostStore.storeCurrentDraft(draft);
}
+
handleUploadClick() {
this.focusTextbox();
}
+
handleUploadStart(clientIds, channelId) {
const draft = PostStore.getDraft(channelId);
@@ -231,6 +241,7 @@ class CreatePost extends React.Component {
// but this also resets the focus after a drag and drop
this.focusTextbox();
}
+
handleFileUploadComplete(filenames, clientIds, channelId) {
const draft = PostStore.getDraft(channelId);
@@ -250,6 +261,7 @@ class CreatePost extends React.Component {
this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
}
}
+
handleUploadError(err, clientId, channelId) {
let message = err;
if (message && typeof message !== 'string') {
@@ -274,6 +286,7 @@ class CreatePost extends React.Component {
this.setState({serverError: message});
}
+
removePreview(id) {
const previews = Object.assign([], this.state.previews);
const uploadsInProgress = this.state.uploadsInProgress;
@@ -298,6 +311,7 @@ class CreatePost extends React.Component {
this.setState({previews, uploadsInProgress});
}
+
componentWillMount() {
const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
@@ -308,6 +322,7 @@ class CreatePost extends React.Component {
showTutorialTip: tutorialStep === TutorialSteps.POST_POPOVER
});
}
+
componentDidMount() {
ChannelStore.addChangeListener(this.onChange);
PreferenceStore.addChangeListener(this.onPreferenceChange);
@@ -315,11 +330,13 @@ class CreatePost extends React.Component {
this.focusTextbox();
document.addEventListener('keydown', this.showShortcuts);
}
+
componentDidUpdate(prevProps, prevState) {
if (prevState.channelId !== this.state.channelId) {
this.focusTextbox();
}
}
+
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChange);
PreferenceStore.removeChangeListener(this.onPreferenceChange);
@@ -342,6 +359,7 @@ class CreatePost extends React.Component {
);
}
}
+
onChange() {
const channelId = ChannelStore.getCurrentId();
if (this.state.channelId !== channelId) {
@@ -350,6 +368,7 @@ class CreatePost extends React.Component {
this.setState({channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, previews: draft.previews, uploadsInProgress: draft.uploadsInProgress});
}
}
+
onPreferenceChange() {
const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
this.setState({
@@ -358,6 +377,7 @@ class CreatePost extends React.Component {
centerTextbox: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED
});
}
+
getFileCount(channelId) {
if (channelId === this.state.channelId) {
return this.state.previews.length + this.state.uploadsInProgress.length;
@@ -366,6 +386,7 @@ class CreatePost extends React.Component {
const draft = PostStore.getDraft(channelId);
return draft.previews.length + draft.uploadsInProgress.length;
}
+
handleKeyDown(e) {
if (this.state.ctrlSend && e.keyCode === KeyCodes.ENTER && e.ctrlKey === true) {
this.postMsgKeyPress(e);
@@ -394,30 +415,29 @@ class CreatePost extends React.Component {
});
}
- if ((e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && e.keyCode === KeyCodes.UP) {
- const channelId = ChannelStore.getCurrentId();
- const lastPost = PostStore.getCurrentUsersLatestPost(channelId);
- if (!lastPost || this.state.messageText !== '') {
- return;
- }
- e.preventDefault();
- let message = lastPost.message;
- if (this.state.lastMessage !== '') {
- message = this.state.lastMessage;
+ if ((e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && (e.keyCode === Constants.KeyCodes.UP || e.keyCode === Constants.KeyCodes.DOWN)) {
+ const lastMessage = MessageHistoryStore.nextMessageInHistory(e.keyCode, this.state.messageText, 'post');
+ if (lastMessage !== null) {
+ e.preventDefault();
+ this.setState({
+ messageText: lastMessage
+ });
}
- this.setState({messageText: message});
}
}
+
showPostDeletedModal() {
this.setState({
showPostDeletedModal: true
});
}
+
hidePostDeletedModal() {
this.setState({
showPostDeletedModal: false
});
}
+
createTutorialTip() {
const screens = [];
@@ -438,6 +458,7 @@ class CreatePost extends React.Component {
/>
);
}
+
render() {
let serverError = null;
if (this.state.serverError) {
diff --git a/webapp/components/edit_post_modal.jsx b/webapp/components/edit_post_modal.jsx
index ac82cf036..0c7b8d672 100644
--- a/webapp/components/edit_post_modal.jsx
+++ b/webapp/components/edit_post_modal.jsx
@@ -9,6 +9,7 @@ import * as GlobalActions from 'actions/global_actions.jsx';
import Textbox from './textbox.jsx';
import BrowserStore from 'stores/browser_store.jsx';
import PostStore from 'stores/post_store.jsx';
+import MessageHistoryStore from 'stores/message_history_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import Constants from 'utils/constants.jsx';
@@ -49,6 +50,8 @@ class EditPostModal extends React.Component {
return;
}
+ MessageHistoryStore.storeMessageInHistory(updatedPost.message);
+
if (updatedPost.message.length === 0) {
var tempState = this.state;
Reflect.deleteProperty(tempState, 'editText');
diff --git a/webapp/stores/message_history_store.jsx b/webapp/stores/message_history_store.jsx
new file mode 100644
index 000000000..51f673d9e
--- /dev/null
+++ b/webapp/stores/message_history_store.jsx
@@ -0,0 +1,79 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import Constants from 'utils/constants.jsx';
+
+const TYPE_POST = 'post';
+const TYPE_COMMENT = 'comment';
+
+class MessageHistoryStoreClass {
+ constructor() {
+ this.messageHistory = [];
+ this.index = [];
+ this.index[TYPE_POST] = 0;
+ this.index[TYPE_COMMENT] = 0;
+ }
+
+ getMessageInHistory(type) {
+ if (this.index[type] >= this.messageHistory.length) {
+ return '';
+ } else if (this.index[type] < 0) {
+ return null;
+ }
+
+ return this.messageHistory[this.index[type]];
+ }
+
+ getHistoryLength() {
+ if (this.messageHistory === null) {
+ return 0;
+ }
+ return this.messageHistory.length;
+ }
+
+ storeMessageInHistory(message) {
+ this.messageHistory.push(message);
+ this.resetAllHistoryIndex();
+ if (this.messageHistory.length > Constants.MAX_PREV_MSGS) {
+ this.messageHistory = this.messageHistory.slice(1, Constants.MAX_PREV_MSGS + 1);
+ }
+ }
+
+ storeMessageInHistoryByIndex(index, message) {
+ this.messageHistory[index] = message;
+ }
+
+ resetAllHistoryIndex() {
+ this.index[TYPE_POST] = this.messageHistory.length;
+ this.index[TYPE_COMMENT] = this.messageHistory.length;
+ }
+
+ resetHistoryIndex(type) {
+ this.index[type] = this.messageHistory.length;
+ }
+
+ nextMessageInHistory(keyCode, messageText, type) {
+ if (messageText !== '' && messageText !== this.getMessageInHistory(type)) {
+ return null;
+ }
+
+ if (keyCode === Constants.KeyCodes.UP) {
+ this.index[type]--;
+ } else if (keyCode === Constants.KeyCodes.DOWN) {
+ this.index[type]++;
+ }
+
+ if (this.index[type] < 0) {
+ this.index[type] = 0;
+ return null;
+ } else if (this.index[type] >= this.getHistoryLength()) {
+ this.index[type] = this.getHistoryLength();
+ }
+
+ return this.getMessageInHistory(type);
+ }
+}
+
+var MessageHistoryStore = new MessageHistoryStoreClass();
+
+export default MessageHistoryStore; \ No newline at end of file
diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx
index 4e293c50e..216131eae 100644
--- a/webapp/utils/constants.jsx
+++ b/webapp/utils/constants.jsx
@@ -752,6 +752,7 @@ export default {
MHPNS: 'https://push.mattermost.com',
MTPNS: 'http://push-test.mattermost.com',
BOT_NAME: 'BOT',
+ MAX_PREV_MSGS: 100,
POST_COLLAPSE_TIMEOUT: 1000 * 60 * 5, // five minutes
LICENSE_EXPIRY_NOTIFICATION: 1000 * 60 * 60 * 24 * 15, // 15 days
LICENSE_GRACE_PERIOD: 1000 * 60 * 60 * 24 * 15 // 15 days
diff --git a/webapp/utils/post_utils.jsx b/webapp/utils/post_utils.jsx
index 73538c26b..ee723a246 100644
--- a/webapp/utils/post_utils.jsx
+++ b/webapp/utils/post_utils.jsx
@@ -2,7 +2,6 @@
// See License.txt for license information.
import Client from 'utils/web_client.jsx';
-
import Constants from 'utils/constants.jsx';
export function isSystemMessage(post) {