2017-04-12 08:27:57 -04:00
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2015-06-14 23:53:32 -08:00
// See License.txt for license information.
2016-03-14 08:50:46 -04:00
import ReactDOM from 'react-dom' ;
2015-11-19 21:12:56 -05:00
import MsgTyping from './msg_typing.jsx' ;
import Textbox from './textbox.jsx' ;
import FileUpload from './file_upload.jsx' ;
import FilePreview from './file_preview.jsx' ;
2016-02-18 10:31:44 -05:00
import PostDeletedModal from './post_deleted_modal.jsx' ;
2015-11-19 21:12:56 -05:00
import TutorialTip from './tutorial/tutorial_tip.jsx' ;
2017-03-24 09:09:51 -04:00
import EmojiPicker from './emoji_picker/emoji_picker.jsx' ;
2015-10-30 11:35:16 -04:00
2016-11-30 13:55:49 -05:00
import AppDispatcher from 'dispatcher/app_dispatcher.jsx' ;
2016-05-26 09:46:18 -04:00
import * as GlobalActions from 'actions/global_actions.jsx' ;
2016-03-14 08:50:46 -04:00
import * as Utils from 'utils/utils.jsx' ;
2016-08-19 12:52:48 -04:00
import * as UserAgent from 'utils/user_agent.jsx' ;
2016-06-02 15:43:00 -07:00
import * as ChannelActions from 'actions/channel_actions.jsx' ;
2016-11-30 13:55:49 -05:00
import * as PostActions from 'actions/post_actions.jsx' ;
2015-10-30 11:35:16 -04:00
2016-03-14 08:50:46 -04:00
import ChannelStore from 'stores/channel_store.jsx' ;
2016-11-30 13:55:49 -05:00
import EmojiStore from 'stores/emoji_store.jsx' ;
2016-03-14 08:50:46 -04:00
import PostStore from 'stores/post_store.jsx' ;
2016-06-06 10:01:35 -07:00
import MessageHistoryStore from 'stores/message_history_store.jsx' ;
2016-03-14 08:50:46 -04:00
import UserStore from 'stores/user_store.jsx' ;
import PreferenceStore from 'stores/preference_store.jsx' ;
2017-05-30 22:46:18 +02:00
import ConfirmModal from './confirm_modal.jsx' ;
2015-08-31 11:31:55 -04:00
2016-03-14 08:50:46 -04:00
import Constants from 'utils/constants.jsx' ;
2015-11-18 17:29:06 -05:00
2017-05-30 22:46:18 +02:00
import { FormattedHTMLMessage , FormattedMessage } from 'react-intl' ;
2017-06-13 14:35:45 -04:00
import { RootCloseWrapper } from 'react-overlays' ;
2016-06-22 10:30:01 -04:00
import { browserHistory } from 'react-router/es6' ;
2016-02-03 00:46:56 -03:00
2015-10-30 11:35:16 -04:00
const Preferences = Constants . Preferences ;
const TutorialSteps = Constants . TutorialSteps ;
2015-08-31 11:31:55 -04:00
const ActionTypes = Constants . ActionTypes ;
2015-10-15 02:13:48 +02:00
const KeyCodes = Constants . KeyCodes ;
2015-08-31 11:31:55 -04:00
2016-03-14 08:50:46 -04:00
import React from 'react' ;
2016-11-30 13:55:49 -05:00
export const REACTION _PATTERN = /^(\+|-):([^:\s]+):\s*$/ ;
2017-03-24 09:09:51 -04:00
export const EMOJI _PATTERN = /:[A-Za-z-_0-9]*:/g ;
2016-11-30 13:55:49 -05:00
2016-07-15 10:37:51 -04:00
export default class CreatePost extends React . Component {
2015-08-31 11:31:55 -04:00
constructor ( props ) {
super ( props ) ;
this . lastTime = 0 ;
2017-05-30 22:46:18 +02:00
this . doSubmit = this . doSubmit . bind ( this ) ;
2015-08-31 11:31:55 -04:00
this . handleSubmit = this . handleSubmit . bind ( this ) ;
this . postMsgKeyPress = this . postMsgKeyPress . bind ( this ) ;
2016-10-25 10:11:34 -04:00
this . handleChange = this . handleChange . bind ( this ) ;
2017-02-23 17:57:41 +09:00
this . handleFileUploadChange = this . handleFileUploadChange . bind ( this ) ;
2015-08-31 11:31:55 -04:00
this . handleUploadStart = this . handleUploadStart . bind ( this ) ;
this . handleFileUploadComplete = this . handleFileUploadComplete . bind ( this ) ;
this . handleUploadError = this . handleUploadError . bind ( this ) ;
this . removePreview = this . removePreview . bind ( this ) ;
this . onChange = this . onChange . bind ( this ) ;
2015-10-30 11:35:16 -04:00
this . onPreferenceChange = this . onPreferenceChange . bind ( this ) ;
2015-08-31 11:31:55 -04:00
this . getFileCount = this . getFileCount . bind ( this ) ;
2017-04-04 12:25:55 -04:00
this . getFileUploadTarget = this . getFileUploadTarget . bind ( this ) ;
2015-10-26 22:05:26 +01:00
this . handleKeyDown = this . handleKeyDown . bind ( this ) ;
2016-11-04 06:14:19 -07:00
this . handleBlur = this . handleBlur . bind ( this ) ;
2015-10-25 23:55:50 +01:00
this . sendMessage = this . sendMessage . bind ( this ) ;
2016-02-05 12:22:39 -05:00
this . focusTextbox = this . focusTextbox . bind ( this ) ;
2016-02-18 10:31:44 -05:00
this . showPostDeletedModal = this . showPostDeletedModal . bind ( this ) ;
this . hidePostDeletedModal = this . hidePostDeletedModal . bind ( this ) ;
2016-06-02 15:43:00 -07:00
this . showShortcuts = this . showShortcuts . bind ( this ) ;
2017-03-24 09:09:51 -04:00
this . handleEmojiClick = this . handleEmojiClick . bind ( this ) ;
2016-12-26 09:25:50 -05:00
this . handlePostError = this . handlePostError . bind ( this ) ;
2017-05-30 22:46:18 +02:00
this . hideNotifyAllModal = this . hideNotifyAllModal . bind ( this ) ;
this . showNotifyAllModal = this . showNotifyAllModal . bind ( this ) ;
this . handleNotifyModalCancel = this . handleNotifyModalCancel . bind ( this ) ;
this . handleNotifyAllConfirmation = this . handleNotifyAllConfirmation . bind ( this ) ;
2015-08-31 11:31:55 -04:00
PostStore . clearDraftUploads ( ) ;
2017-05-23 10:17:06 -04:00
const channelId = ChannelStore . getCurrentId ( ) ;
2017-06-18 14:42:32 -04:00
const draft = PostStore . getDraft ( channelId ) ;
2015-08-31 11:31:55 -04:00
2017-05-30 22:46:18 +02:00
const stats = ChannelStore . getCurrentStats ( ) ;
const members = stats . member _count - 1 ;
2015-08-31 11:31:55 -04:00
this . state = {
2017-05-23 10:17:06 -04:00
channelId ,
2016-11-01 18:46:41 -04:00
message : draft . message ,
2015-09-22 16:00:44 -07:00
uploadsInProgress : draft . uploadsInProgress ,
2016-09-30 11:06:30 -04:00
fileInfos : draft . fileInfos ,
2015-08-31 11:31:55 -04:00
submitting : false ,
2016-06-04 23:23:07 -07:00
ctrlSend : PreferenceStore . getBool ( Constants . Preferences . CATEGORY _ADVANCED _SETTINGS , 'send_on_ctrl_enter' ) ,
2016-07-06 00:46:36 +05:00
fullWidthTextBox : PreferenceStore . get ( Preferences . CATEGORY _DISPLAY _SETTINGS , Preferences . CHANNEL _DISPLAY _MODE , Preferences . CHANNEL _DISPLAY _MODE _DEFAULT ) === Preferences . CHANNEL _DISPLAY _MODE _FULL _SCREEN ,
2016-02-18 10:31:44 -05:00
showTutorialTip : false ,
2017-02-08 00:13:07 +09:00
showPostDeletedModal : false ,
2017-03-24 09:09:51 -04:00
enableSendButton : false ,
showEmojiPicker : false ,
2017-05-30 22:46:18 +02:00
emojiPickerEnabled : Utils . isFeatureEnabled ( Constants . PRE _RELEASE _FEATURES . EMOJI _PICKER _PREVIEW ) ,
showConfirmModal : false ,
totalMembers : members
2015-08-31 11:31:55 -04:00
} ;
2017-01-10 09:08:37 -05:00
this . lastBlurAt = 0 ;
2015-10-26 22:05:26 +01:00
}
2016-06-06 10:01:35 -07:00
2016-12-26 09:25:50 -05:00
handlePostError ( postError ) {
this . setState ( { postError } ) ;
}
2017-06-13 14:35:45 -04:00
toggleEmojiPicker = ( ) => {
this . setState ( { showEmojiPicker : ! this . state . showEmojiPicker } ) ;
2017-03-24 09:09:51 -04:00
}
2017-05-30 22:46:18 +02:00
doSubmit ( e ) {
2017-06-06 17:24:08 -04:00
if ( e ) {
e . preventDefault ( ) ;
}
2015-06-14 23:53:32 -08:00
2017-06-15 11:05:43 -04:00
if ( this . state . uploadsInProgress . length > 0 || this . state . submitting ) {
return ;
}
2015-09-25 11:41:08 -04:00
const post = { } ;
2016-09-30 11:06:30 -04:00
post . file _ids = [ ] ;
2016-11-01 18:46:41 -04:00
post . message = this . state . message ;
2015-06-14 23:53:32 -08:00
2016-09-30 11:06:30 -04:00
if ( post . message . trim ( ) . length === 0 && this . state . fileInfos . length === 0 ) {
2015-06-14 23:53:32 -08:00
return ;
}
2016-12-06 18:49:36 -05:00
if ( this . state . postError ) {
this . setState ( { errorClass : 'animation--highlight' } ) ;
setTimeout ( ( ) => {
this . setState ( { errorClass : null } ) ;
2016-12-22 15:30:37 -05:00
} , Constants . ANIMATION _TIMEOUT ) ;
2015-06-14 23:53:32 -08:00
return ;
}
2016-11-01 18:46:41 -04:00
MessageHistoryStore . storeMessageInHistory ( this . state . message ) ;
2016-06-06 10:01:35 -07:00
2016-07-15 10:37:51 -04:00
this . setState ( { submitting : true , serverError : null } ) ;
2016-06-06 10:01:35 -07:00
2016-11-30 13:55:49 -05:00
const isReaction = REACTION _PATTERN . exec ( post . message ) ;
2015-08-10 14:01:11 -04:00
if ( post . message . indexOf ( '/' ) === 0 ) {
2017-06-18 14:42:32 -04:00
PostStore . storeDraft ( this . state . channelId , null ) ;
2017-02-08 00:13:07 +09:00
this . setState ( { message : '' , postError : null , fileInfos : [ ] , enableSendButton : false } ) ;
2016-08-12 07:41:17 -04:00
2016-12-10 13:35:16 +09:00
const args = { } ;
args . channel _id = this . state . channelId ;
2016-06-02 15:43:00 -07:00
ChannelActions . executeCommand (
2015-06-14 23:53:32 -08:00
post . message ,
2016-12-10 13:35:16 +09:00
args ,
2015-09-25 11:41:08 -04:00
( data ) => {
2016-08-12 07:41:17 -04:00
this . setState ( { submitting : false } ) ;
2015-06-14 23:53:32 -08:00
2016-12-30 13:12:43 -05:00
if ( post . message . trim ( ) === '/logout' ) {
GlobalActions . clientLogout ( data . goto _location ) ;
return ;
}
2016-01-08 12:41:26 -06:00
if ( data . goto _location && data . goto _location . length > 0 ) {
2017-03-06 14:00:40 +01:00
if ( data . goto _location . startsWith ( '/' ) || data . goto _location . includes ( window . location . hostname ) ) {
2017-02-28 10:36:45 +01:00
browserHistory . push ( data . goto _location ) ;
} else {
window . open ( data . goto _location ) ;
}
2015-06-14 23:53:32 -08:00
}
2015-09-25 11:41:08 -04:00
} ,
( err ) => {
2015-10-25 23:55:50 +01:00
if ( err . sendMessage ) {
this . sendMessage ( post ) ;
2015-07-23 09:39:29 -04:00
} else {
2015-10-25 23:55:50 +01:00
const state = { } ;
state . serverError = err . message ;
state . submitting = false ;
2016-11-30 13:55:49 -05:00
this . setState ( { state } ) ;
2015-07-23 09:39:29 -04:00
}
2015-09-25 11:41:08 -04:00
}
2015-06-14 23:53:32 -08:00
) ;
2016-11-30 13:55:49 -05:00
} else if ( isReaction && EmojiStore . has ( isReaction [ 2 ] ) ) {
this . sendReaction ( isReaction ) ;
2015-10-25 23:55:50 +01:00
} else {
this . sendMessage ( post ) ;
2015-06-14 23:53:32 -08:00
}
2016-11-04 06:14:19 -07:00
2017-02-08 00:13:07 +09:00
this . setState ( {
message : '' ,
submitting : false ,
postError : null ,
fileInfos : [ ] ,
serverError : null ,
enableSendButton : false
} ) ;
2016-11-30 13:55:49 -05:00
2016-11-04 06:14:19 -07:00
const fasterThanHumanWillClick = 150 ;
2017-01-10 09:08:37 -05:00
const forceFocus = ( Date . now ( ) - this . lastBlurAt < fasterThanHumanWillClick ) ;
2016-11-04 06:14:19 -07:00
this . focusTextbox ( forceFocus ) ;
2015-08-31 11:31:55 -04:00
}
2016-06-06 10:01:35 -07:00
2017-05-30 22:46:18 +02:00
handleNotifyAllConfirmation ( e ) {
this . hideNotifyAllModal ( ) ;
this . doSubmit ( e ) ;
}
hideNotifyAllModal ( ) {
this . setState ( { showConfirmModal : false } ) ;
}
showNotifyAllModal ( ) {
this . setState ( { showConfirmModal : true } ) ;
}
handleSubmit ( e ) {
const stats = ChannelStore . getCurrentStats ( ) ;
const members = stats . member _count - 1 ;
if ( ( this . state . message . includes ( '@all' ) || this . state . message . includes ( '@channel' ) ) && members >= Constants . NOTIFY _ALL _MEMBERS ) {
this . setState ( { totalMembers : members } ) ;
this . showNotifyAllModal ( ) ;
return ;
}
this . doSubmit ( e ) ;
}
handleNotifyModalCancel ( ) {
this . setState ( { showConfirmModal : false } ) ;
}
2015-10-25 23:55:50 +01:00
sendMessage ( post ) {
post . channel _id = this . state . channelId ;
const time = Utils . getTimestamp ( ) ;
const userId = UserStore . getCurrentId ( ) ;
post . pending _post _id = ` ${ userId } : ${ time } ` ;
post . user _id = userId ;
post . create _at = time ;
post . parent _id = this . state . parentId ;
2016-02-08 07:26:10 -05:00
GlobalActions . emitUserPostedEvent ( post ) ;
2015-10-25 23:55:50 +01:00
2017-03-24 09:09:51 -04:00
// parse message and emit emoji event
const emojiResult = post . message . match ( EMOJI _PATTERN ) ;
if ( emojiResult ) {
emojiResult . forEach ( ( emoji ) => {
PostActions . emitEmojiPosted ( emoji ) ;
} ) ;
}
2017-06-18 14:42:32 -04:00
PostActions . createPost ( post , this . state . fileInfos , null ,
2015-10-25 23:55:50 +01:00
( err ) => {
2016-01-22 08:04:02 -06:00
if ( err . id === 'api.post.create_post.root_id.app_error' ) {
2016-02-18 10:31:44 -05:00
// this should never actually happen since you can't reply from this textbox
this . showPostDeletedModal ( ) ;
2015-10-25 23:55:50 +01:00
} else {
2016-12-05 18:59:12 +01:00
this . forceUpdate ( ) ;
2015-10-25 23:55:50 +01:00
}
2016-02-18 10:31:44 -05:00
this . setState ( {
submitting : false
} ) ;
2015-10-25 23:55:50 +01:00
}
) ;
}
2016-06-06 10:01:35 -07:00
2016-11-30 13:55:49 -05:00
sendReaction ( isReaction ) {
const action = isReaction [ 1 ] ;
const emojiName = isReaction [ 2 ] ;
2017-06-18 14:42:32 -04:00
const postId = PostStore . getLatestPostId ( this . state . channelId ) ;
2016-11-30 13:55:49 -05:00
2017-05-31 23:55:53 +08:00
if ( postId && action === '+' ) {
2016-11-30 13:55:49 -05:00
PostActions . addReaction ( this . state . channelId , postId , emojiName ) ;
2017-05-31 23:55:53 +08:00
} else if ( postId && action === '-' ) {
2016-11-30 13:55:49 -05:00
PostActions . removeReaction ( this . state . channelId , postId , emojiName ) ;
}
2017-06-18 14:42:32 -04:00
PostStore . storeDraft ( this . state . channelId , null ) ;
2016-11-30 13:55:49 -05:00
}
2016-11-04 06:14:19 -07:00
focusTextbox ( keepFocus = false ) {
if ( keepFocus || ! Utils . isMobile ( ) ) {
2016-02-05 12:22:39 -05:00
this . refs . textbox . focus ( ) ;
}
}
2016-06-06 10:01:35 -07:00
2015-08-31 11:31:55 -04:00
postMsgKeyPress ( e ) {
2016-10-04 14:38:19 -04:00
if ( ! UserAgent . isMobile ( ) && ( ( this . state . ctrlSend && e . ctrlKey ) || ! this . state . ctrlSend ) ) {
2015-10-26 22:05:26 +01:00
if ( e . which === KeyCodes . ENTER && ! e . shiftKey && ! e . altKey ) {
e . preventDefault ( ) ;
ReactDOM . findDOMNode ( this . refs . textbox ) . blur ( ) ;
this . handleSubmit ( e ) ;
}
2015-06-14 23:53:32 -08:00
}
2016-03-23 10:20:52 -04:00
GlobalActions . emitLocalUserTypingEvent ( this . state . channelId , '' ) ;
2015-08-31 11:31:55 -04:00
}
2016-06-06 10:01:35 -07:00
2016-10-25 10:11:34 -04:00
handleChange ( e ) {
2016-11-01 18:46:41 -04:00
const message = e . target . value ;
2017-02-08 00:13:07 +09:00
const enableSendButton = this . handleEnableSendButton ( message , this . state . fileInfos ) ;
this . setState ( {
message ,
enableSendButton
} ) ;
2015-07-16 15:25:28 -04:00
2017-06-18 14:42:32 -04:00
const draft = PostStore . getDraft ( this . state . channelId ) ;
2016-11-01 18:46:41 -04:00
draft . message = message ;
2017-06-18 14:42:32 -04:00
PostStore . storeDraft ( this . state . channelId , draft ) ;
2015-08-31 11:31:55 -04:00
}
2016-06-06 10:01:35 -07:00
2017-02-23 17:57:41 +09:00
handleFileUploadChange ( ) {
this . focusTextbox ( true ) ;
2015-08-31 11:31:55 -04:00
}
2016-06-06 10:01:35 -07:00
2015-08-31 11:31:55 -04:00
handleUploadStart ( clientIds , channelId ) {
2017-06-18 14:42:32 -04:00
const draft = PostStore . getDraft ( channelId ) ;
2015-06-14 23:53:32 -08:00
2015-08-31 11:31:55 -04:00
draft . uploadsInProgress = draft . uploadsInProgress . concat ( clientIds ) ;
2017-06-18 14:42:32 -04:00
PostStore . storeDraft ( channelId , draft ) ;
2015-06-14 23:53:32 -08:00
2015-08-31 11:31:55 -04:00
this . setState ( { uploadsInProgress : draft . uploadsInProgress } ) ;
2016-02-04 11:32:39 -05:00
// this is a bit redundant with the code that sets focus when the file input is clicked,
// but this also resets the focus after a drag and drop
2016-02-05 12:22:39 -05:00
this . focusTextbox ( ) ;
2015-08-31 11:31:55 -04:00
}
2016-06-06 10:01:35 -07:00
2016-09-30 11:06:30 -04:00
handleFileUploadComplete ( fileInfos , clientIds , channelId ) {
2017-06-18 14:42:32 -04:00
const draft = PostStore . getDraft ( channelId ) ;
2015-06-14 23:53:32 -08:00
2015-08-07 16:24:13 -04:00
// remove each finished file from uploads
2015-08-31 11:31:55 -04:00
for ( let i = 0 ; i < clientIds . length ; i ++ ) {
const index = draft . uploadsInProgress . indexOf ( clientIds [ i ] ) ;
2015-08-07 16:24:13 -04:00
2015-08-10 14:01:11 -04:00
if ( index !== - 1 ) {
2015-08-31 11:31:55 -04:00
draft . uploadsInProgress . splice ( index , 1 ) ;
2015-08-07 16:24:13 -04:00
}
2015-06-14 23:53:32 -08:00
}
2015-08-07 16:24:13 -04:00
2016-09-30 11:06:30 -04:00
draft . fileInfos = draft . fileInfos . concat ( fileInfos ) ;
2017-06-18 14:42:32 -04:00
PostStore . storeDraft ( channelId , draft ) ;
2015-08-07 16:24:13 -04:00
2016-05-03 11:00:06 -04:00
if ( channelId === this . state . channelId ) {
2017-02-08 00:13:07 +09:00
this . setState ( {
uploadsInProgress : draft . uploadsInProgress ,
fileInfos : draft . fileInfos ,
enableSendButton : true
} ) ;
2016-05-03 11:00:06 -04:00
}
2015-08-31 11:31:55 -04:00
}
2016-06-06 10:01:35 -07:00
2016-05-03 11:00:06 -04:00
handleUploadError ( err , clientId , channelId ) {
2015-10-26 14:15:07 -04:00
let message = err ;
if ( message && typeof message !== 'string' ) {
// err is an AppError from the server
message = err . message ;
}
2016-03-07 11:11:08 -05:00
if ( clientId !== - 1 ) {
2017-06-18 14:42:32 -04:00
const draft = PostStore . getDraft ( channelId ) ;
2015-08-10 13:15:01 -04:00
2015-08-31 11:31:55 -04:00
const index = draft . uploadsInProgress . indexOf ( clientId ) ;
2015-08-13 15:44:38 -07:00
if ( index !== - 1 ) {
2015-08-31 11:31:55 -04:00
draft . uploadsInProgress . splice ( index , 1 ) ;
2015-08-13 15:44:38 -07:00
}
2015-08-10 13:15:01 -04:00
2017-06-18 14:42:32 -04:00
PostStore . storeDraft ( channelId , draft ) ;
2015-08-10 13:15:01 -04:00
2016-05-03 11:00:06 -04:00
if ( channelId === this . state . channelId ) {
this . setState ( { uploadsInProgress : draft . uploadsInProgress } ) ;
}
2015-08-13 15:44:38 -07:00
}
2016-03-07 11:11:08 -05:00
this . setState ( { serverError : message } ) ;
2015-08-31 11:31:55 -04:00
}
2016-06-06 10:01:35 -07:00
2015-08-31 11:31:55 -04:00
removePreview ( id ) {
2016-09-30 11:06:30 -04:00
const fileInfos = Object . assign ( [ ] , this . state . fileInfos ) ;
2015-09-25 11:41:08 -04:00
const uploadsInProgress = this . state . uploadsInProgress ;
2015-08-10 08:41:18 -04:00
2017-01-19 00:00:21 +03:00
// Clear previous errors
this . handleUploadError ( null ) ;
2016-09-30 11:06:30 -04:00
// id can either be the id of an uploaded file or the client id of an in progress upload
let index = fileInfos . findIndex ( ( info ) => info . id === id ) ;
2015-10-05 09:58:42 -04:00
if ( index === - 1 ) {
2015-08-10 12:05:45 -04:00
index = uploadsInProgress . indexOf ( id ) ;
2015-08-10 08:41:18 -04:00
if ( index !== - 1 ) {
uploadsInProgress . splice ( index , 1 ) ;
2016-02-17 14:32:25 -05:00
this . refs . fileUpload . getWrappedInstance ( ) . cancelUpload ( id ) ;
2015-06-14 23:53:32 -08:00
}
2015-10-05 09:58:42 -04:00
} else {
2016-09-30 11:06:30 -04:00
fileInfos . splice ( index , 1 ) ;
2015-06-14 23:53:32 -08:00
}
2015-08-10 08:41:18 -04:00
2017-06-18 14:42:32 -04:00
const draft = PostStore . getDraft ( this . state . channelId ) ;
2016-09-30 11:06:30 -04:00
draft . fileInfos = fileInfos ;
2015-08-31 11:31:55 -04:00
draft . uploadsInProgress = uploadsInProgress ;
2017-06-18 14:42:32 -04:00
PostStore . storeDraft ( this . state . channelId , draft ) ;
2017-02-08 00:13:07 +09:00
const enableSendButton = this . handleEnableSendButton ( this . state . message , fileInfos ) ;
2015-08-10 08:41:18 -04:00
2017-02-08 00:13:07 +09:00
this . setState ( { fileInfos , uploadsInProgress , enableSendButton } ) ;
2017-02-23 17:57:41 +09:00
this . handleFileUploadChange ( ) ;
2015-08-31 11:31:55 -04:00
}
2016-06-06 10:01:35 -07:00
2015-12-15 11:47:52 -05:00
componentWillMount ( ) {
const tutorialStep = PreferenceStore . getInt ( Preferences . TUTORIAL _STEP , UserStore . getCurrentId ( ) , 999 ) ;
2017-02-08 00:13:07 +09:00
const enableSendButton = this . handleEnableSendButton ( this . state . message , this . state . fileInfos ) ;
2015-12-15 11:47:52 -05:00
// wait to load these since they may have changed since the component was constructed (particularly in the case of skipping the tutorial)
this . setState ( {
2016-04-21 09:56:11 -04:00
ctrlSend : PreferenceStore . getBool ( Preferences . CATEGORY _ADVANCED _SETTINGS , 'send_on_ctrl_enter' ) ,
2016-07-06 00:46:36 +05:00
fullWidthTextBox : PreferenceStore . get ( Preferences . CATEGORY _DISPLAY _SETTINGS , Preferences . CHANNEL _DISPLAY _MODE , Preferences . CHANNEL _DISPLAY _MODE _DEFAULT ) === Preferences . CHANNEL _DISPLAY _MODE _FULL _SCREEN ,
2017-02-08 00:13:07 +09:00
showTutorialTip : tutorialStep === TutorialSteps . POST _POPOVER ,
enableSendButton
2015-12-15 11:47:52 -05:00
} ) ;
}
2016-06-06 10:01:35 -07:00
2015-08-31 11:31:55 -04:00
componentDidMount ( ) {
ChannelStore . addChangeListener ( this . onChange ) ;
2015-10-30 11:35:16 -04:00
PreferenceStore . addChangeListener ( this . onPreferenceChange ) ;
2016-02-05 12:22:39 -05:00
this . focusTextbox ( ) ;
2016-06-02 15:43:00 -07:00
document . addEventListener ( 'keydown' , this . showShortcuts ) ;
2016-02-04 11:05:59 -05:00
}
2016-06-06 10:01:35 -07:00
2016-02-04 11:05:59 -05:00
componentDidUpdate ( prevProps , prevState ) {
if ( prevState . channelId !== this . state . channelId ) {
2016-02-05 12:22:39 -05:00
this . focusTextbox ( ) ;
2016-02-04 11:05:59 -05:00
}
2015-08-31 11:31:55 -04:00
}
2016-06-06 10:01:35 -07:00
2015-08-31 11:31:55 -04:00
componentWillUnmount ( ) {
ChannelStore . removeChangeListener ( this . onChange ) ;
2015-10-30 11:35:16 -04:00
PreferenceStore . removeChangeListener ( this . onPreferenceChange ) ;
2016-06-02 15:43:00 -07:00
document . removeEventListener ( 'keydown' , this . showShortcuts ) ;
}
2016-09-30 11:06:30 -04:00
2016-06-02 15:43:00 -07:00
showShortcuts ( e ) {
if ( ( e . ctrlKey || e . metaKey ) && e . keyCode === Constants . KeyCodes . FORWARD _SLASH ) {
e . preventDefault ( ) ;
2016-12-10 13:35:16 +09:00
const args = { } ;
args . channel _id = this . state . channelId ;
2016-06-02 15:43:00 -07:00
ChannelActions . executeCommand (
2016-12-10 13:35:16 +09:00
'/shortcuts' ,
args ,
2016-06-02 15:43:00 -07:00
null ,
( err ) => {
this . setState ( {
serverError : err . message ,
submitting : false
} ) ;
}
) ;
}
2015-08-31 11:31:55 -04:00
}
2016-06-06 10:01:35 -07:00
2015-08-31 11:31:55 -04:00
onChange ( ) {
const channelId = ChannelStore . getCurrentId ( ) ;
2015-08-10 14:01:11 -04:00
if ( this . state . channelId !== channelId ) {
2017-06-18 14:42:32 -04:00
const draft = PostStore . getDraft ( channelId ) ;
2015-07-23 12:45:08 -04:00
2016-11-01 18:46:41 -04:00
this . setState ( { channelId , message : draft . message , submitting : false , serverError : null , postError : null , fileInfos : draft . fileInfos , uploadsInProgress : draft . uploadsInProgress } ) ;
2015-06-14 23:53:32 -08:00
}
2015-08-31 11:31:55 -04:00
}
2016-06-06 10:01:35 -07:00
2015-10-30 11:35:16 -04:00
onPreferenceChange ( ) {
2015-12-15 11:36:14 -05:00
const tutorialStep = PreferenceStore . getInt ( Preferences . TUTORIAL _STEP , UserStore . getCurrentId ( ) , 999 ) ;
2015-10-30 11:35:16 -04:00
this . setState ( {
2015-12-15 11:36:14 -05:00
showTutorialTip : tutorialStep === TutorialSteps . POST _POPOVER ,
2016-04-21 09:56:11 -04:00
ctrlSend : PreferenceStore . getBool ( Preferences . CATEGORY _ADVANCED _SETTINGS , 'send_on_ctrl_enter' ) ,
2017-03-24 09:09:51 -04:00
fullWidthTextBox : PreferenceStore . get ( Preferences . CATEGORY _DISPLAY _SETTINGS , Preferences . CHANNEL _DISPLAY _MODE , Preferences . CHANNEL _DISPLAY _MODE _DEFAULT ) === Preferences . CHANNEL _DISPLAY _MODE _FULL _SCREEN ,
emojiPickerEnabled : Utils . isFeatureEnabled ( Constants . PRE _RELEASE _FEATURES . EMOJI _PICKER _PREVIEW )
2015-10-30 11:35:16 -04:00
} ) ;
}
2016-06-06 10:01:35 -07:00
2015-08-31 11:31:55 -04:00
getFileCount ( channelId ) {
2015-08-10 14:01:11 -04:00
if ( channelId === this . state . channelId ) {
2016-09-30 11:06:30 -04:00
return this . state . fileInfos . length + this . state . uploadsInProgress . length ;
2015-06-14 23:53:32 -08:00
}
2015-08-31 11:31:55 -04:00
2017-06-18 14:42:32 -04:00
const draft = PostStore . getDraft ( channelId ) ;
2016-09-30 11:06:30 -04:00
return draft . fileInfos . length + draft . uploadsInProgress . length ;
2015-08-31 11:31:55 -04:00
}
2016-06-06 10:01:35 -07:00
2017-04-04 12:25:55 -04:00
getFileUploadTarget ( ) {
return this . refs . textbox ;
}
2015-10-26 22:05:26 +01:00
handleKeyDown ( e ) {
2015-12-15 11:36:14 -05:00
if ( this . state . ctrlSend && e . keyCode === KeyCodes . ENTER && e . ctrlKey === true ) {
2015-10-26 22:05:26 +01:00
this . postMsgKeyPress ( e ) ;
return ;
}
2017-06-15 11:05:43 -04:00
const latestNonEphemeralPost = PostStore . getLatestNonEphemeralPost ( this . state . channelId ) ;
const latestNonEphemeralPostId = latestNonEphemeralPost == null ? '' : latestNonEphemeralPost . id ;
const lastPostEl = document . getElementById ( ` commentIcon_ ${ this . state . channelId } _ ${ latestNonEphemeralPostId } ` ) ;
2017-06-05 23:09:43 +08:00
2016-11-01 18:46:41 -04:00
if ( ! e . ctrlKey && ! e . metaKey && ! e . altKey && ! e . shiftKey && e . keyCode === KeyCodes . UP && this . state . message === '' ) {
2015-10-15 02:13:48 +02:00
e . preventDefault ( ) ;
2017-06-05 23:09:43 +08:00
const lastPost = PostStore . getCurrentUsersLatestPost ( this . state . channelId ) ;
2015-10-15 21:22:08 +02:00
if ( ! lastPost ) {
return ;
}
2016-07-15 10:37:51 -04:00
let type ;
if ( lastPost . root _id && lastPost . root _id . length > 0 ) {
type = Utils . localizeMessage ( 'create_post.comment' , 'Comment' ) ;
} else {
type = Utils . localizeMessage ( 'create_post.post' , 'Post' ) ;
}
2015-10-15 02:13:48 +02:00
AppDispatcher . handleViewAction ( {
2016-02-08 10:33:59 -05:00
type : ActionTypes . RECEIVED _EDIT _POST ,
2015-10-15 21:22:08 +02:00
refocusId : '#post_textbox' ,
2015-10-15 02:13:48 +02:00
title : type ,
message : lastPost . message ,
2015-10-15 21:22:08 +02:00
postId : lastPost . id ,
2015-11-13 12:20:33 -05:00
channelId : lastPost . channel _id ,
comments : PostStore . getCommentCount ( lastPost )
2015-10-15 02:13:48 +02:00
} ) ;
2017-06-05 23:09:43 +08:00
} else if ( ! e . ctrlKey && ! e . metaKey && ! e . altKey && e . shiftKey && e . keyCode === KeyCodes . UP && this . state . message === '' && lastPostEl ) {
2017-06-03 00:11:03 +08:00
e . preventDefault ( ) ;
if ( document . createEvent ) {
2017-06-05 23:09:43 +08:00
const evt = document . createEvent ( 'MouseEvents' ) ;
2017-06-03 00:11:03 +08:00
evt . initMouseEvent ( 'click' , true , true , window , 0 , 0 , 0 , 0 , 0 , false , false , false , false , 0 , null ) ;
2017-06-05 23:09:43 +08:00
lastPostEl . dispatchEvent ( evt ) ;
2017-06-03 00:11:03 +08:00
} else if ( document . createEventObject ) {
2017-06-05 23:09:43 +08:00
const evObj = document . createEventObject ( ) ;
lastPostEl . fireEvent ( 'onclick' , evObj ) ;
2017-06-03 00:11:03 +08:00
}
2015-10-15 02:13:48 +02:00
}
2016-05-12 07:48:29 -04:00
2016-06-06 10:01:35 -07:00
if ( ( e . ctrlKey || e . metaKey ) && ! e . altKey && ! e . shiftKey && ( e . keyCode === Constants . KeyCodes . UP || e . keyCode === Constants . KeyCodes . DOWN ) ) {
2016-11-01 18:46:41 -04:00
const lastMessage = MessageHistoryStore . nextMessageInHistory ( e . keyCode , this . state . message , 'post' ) ;
2016-06-06 10:01:35 -07:00
if ( lastMessage !== null ) {
e . preventDefault ( ) ;
this . setState ( {
2016-11-01 18:46:41 -04:00
message : lastMessage
2016-06-06 10:01:35 -07:00
} ) ;
2016-05-12 07:48:29 -04:00
}
}
2015-10-15 02:13:48 +02:00
}
2016-06-06 10:01:35 -07:00
2016-11-04 06:14:19 -07:00
handleBlur ( ) {
2017-01-10 09:08:37 -05:00
this . lastBlurAt = Date . now ( ) ;
2016-11-04 06:14:19 -07:00
}
2016-02-18 10:31:44 -05:00
showPostDeletedModal ( ) {
this . setState ( {
showPostDeletedModal : true
} ) ;
}
2016-06-06 10:01:35 -07:00
2016-02-18 10:31:44 -05:00
hidePostDeletedModal ( ) {
this . setState ( {
showPostDeletedModal : false
} ) ;
}
2016-06-06 10:01:35 -07:00
2017-03-24 09:09:51 -04:00
handleEmojiClick ( emoji ) {
const emojiAlias = emoji . name || emoji . aliases [ 0 ] ;
if ( ! emojiAlias ) {
//Oops.. There went something wrong
return ;
}
if ( this . state . message === '' ) {
2017-06-13 14:35:45 -04:00
this . setState ( { message : ':' + emojiAlias + ': ' } ) ;
2017-03-24 09:09:51 -04:00
} else {
//check whether there is already a blank at the end of the current message
const newMessage = ( /\s+$/ . test ( this . state . message ) ) ?
this . state . message + ':' + emojiAlias + ': ' : this . state . message + ' :' + emojiAlias + ': ' ;
2017-06-13 14:35:45 -04:00
this . setState ( { message : newMessage } ) ;
2017-03-24 09:09:51 -04:00
}
2017-06-13 14:35:45 -04:00
this . setState ( { showEmojiPicker : false } ) ;
2017-03-24 09:09:51 -04:00
2017-06-13 14:35:45 -04:00
this . focusTextbox ( ) ;
2017-03-24 09:09:51 -04:00
}
2015-10-30 11:35:16 -04:00
createTutorialTip ( ) {
const screens = [ ] ;
screens . push (
< div >
2016-02-03 00:46:56 -03:00
< FormattedHTMLMessage
id = 'create_post.tutorialTip'
defaultMessage = '<h4>Sending Messages</h4><p>Type here to write a message and press <strong>Enter</strong> to post it.</p><p>Click the <strong>Attachment</strong> button to upload an image or a file.</p>'
/ >
2015-10-30 11:35:16 -04:00
< / div >
) ;
return (
< TutorialTip
placement = 'top'
screens = { screens }
2015-11-02 09:21:08 -05:00
overlayClass = 'tip-overlay--chat'
2017-02-24 17:34:21 +00:00
diagnosticsTag = 'tutorial_tip_1_sending_messages'
2015-10-30 11:35:16 -04:00
/ >
) ;
}
2016-06-06 10:01:35 -07:00
2017-02-08 00:13:07 +09:00
handleEnableSendButton ( message , fileInfos ) {
return message . trim ( ) . length !== 0 || fileInfos . length !== 0 ;
}
2015-08-31 11:31:55 -04:00
render ( ) {
2017-05-30 22:46:18 +02:00
const notifyAllTitle = (
< FormattedMessage
id = 'notify_all.title.confirm'
defaultMessage = 'Confirm sending notifications to entire channel'
/ >
) ;
const notifyAllConfirm = (
< FormattedMessage
id = 'notify_all.confirm'
defaultMessage = 'Confirm'
/ >
) ;
const notifyAllMessage = (
< FormattedMessage
id = 'notify_all.question'
defaultMessage = 'By using @all or @channel you are about to send notifications to {totalMembers} people. Are you sure you want to do this?'
values = { {
totalMembers : this . state . totalMembers
} }
/ >
) ;
2015-08-31 11:31:55 -04:00
let serverError = null ;
2015-08-10 14:01:11 -04:00
if ( this . state . serverError ) {
serverError = (
< div className = 'has-error' >
< label className = 'control-label' > { this . state . serverError } < / label >
< / div >
) ;
}
2015-06-14 23:53:32 -08:00
2015-08-31 11:31:55 -04:00
let postError = null ;
2015-08-10 14:01:11 -04:00
if ( this . state . postError ) {
2016-12-21 09:37:07 -05:00
const postErrorClass = 'post-error' + ( this . state . errorClass ? ( ' ' + this . state . errorClass ) : '' ) ;
2016-12-06 18:49:36 -05:00
postError = < label className = { postErrorClass } > { this . state . postError } < / label > ;
2015-08-10 14:01:11 -04:00
}
2015-08-31 11:31:55 -04:00
let preview = null ;
2016-09-30 11:06:30 -04:00
if ( this . state . fileInfos . length > 0 || this . state . uploadsInProgress . length > 0 ) {
2015-06-14 23:53:32 -08:00
preview = (
< FilePreview
2016-09-30 11:06:30 -04:00
fileInfos = { this . state . fileInfos }
2015-06-14 23:53:32 -08:00
onRemove = { this . removePreview }
2015-09-03 10:49:36 -04:00
uploadsInProgress = { this . state . uploadsInProgress }
/ >
2015-06-14 23:53:32 -08:00
) ;
}
2015-08-31 11:31:55 -04:00
let postFooterClassName = 'post-create-footer' ;
2015-08-10 14:01:11 -04:00
if ( postError ) {
postFooterClassName += ' has-error' ;
}
2015-10-30 11:35:16 -04:00
let tutorialTip = null ;
if ( this . state . showTutorialTip ) {
tutorialTip = this . createTutorialTip ( ) ;
}
2016-04-21 09:56:11 -04:00
let centerClass = '' ;
2016-07-06 00:46:36 +05:00
if ( ! this . state . fullWidthTextBox ) {
2016-04-21 09:56:11 -04:00
centerClass = 'center' ;
}
2017-02-08 00:13:07 +09:00
let sendButtonClass = 'send-button theme' ;
if ( ! this . state . enableSendButton ) {
sendButtonClass += ' disabled' ;
}
2017-06-13 14:35:45 -04:00
2017-03-24 09:09:51 -04:00
let emojiPicker = null ;
if ( this . state . showEmojiPicker ) {
emojiPicker = (
2017-06-13 14:35:45 -04:00
< RootCloseWrapper onRootClose = { this . toggleEmojiPicker } >
< EmojiPicker
onHide = { this . toggleEmojiPicker }
onEmojiClick = { this . handleEmojiClick }
/ >
< / RootCloseWrapper >
2017-03-24 09:09:51 -04:00
) ;
}
2017-02-08 00:13:07 +09:00
2017-05-04 15:45:19 -04:00
let attachmentsDisabled = '' ;
if ( global . window . mm _config . EnableFileAttachments === 'false' ) {
attachmentsDisabled = ' post-create--attachment-disabled' ;
}
2015-06-14 23:53:32 -08:00
return (
2015-08-31 11:31:55 -04:00
< form
id = 'create_post'
ref = 'topDiv'
role = 'form'
2016-04-21 09:56:11 -04:00
className = { centerClass }
2015-08-31 11:31:55 -04:00
onSubmit = { this . handleSubmit }
>
2017-05-04 15:45:19 -04:00
< div className = { 'post-create' + attachmentsDisabled } >
2015-08-10 14:01:11 -04:00
< div className = 'post-create-body' >
2015-09-08 15:47:15 -04:00
< div className = 'post-body__cell' >
< Textbox
2016-10-25 10:11:34 -04:00
onChange = { this . handleChange }
2015-09-08 15:47:15 -04:00
onKeyPress = { this . postMsgKeyPress }
2015-10-26 22:05:26 +01:00
onKeyDown = { this . handleKeyDown }
2016-12-26 09:25:50 -05:00
handlePostError = { this . handlePostError }
2016-11-01 18:46:41 -04:00
value = { this . state . message }
2016-11-04 06:14:19 -07:00
onBlur = { this . handleBlur }
2017-03-24 09:09:51 -04:00
emojiEnabled = { this . state . emojiPickerEnabled }
2016-07-15 10:37:51 -04:00
createMessage = { Utils . localizeMessage ( 'create_post.write' , 'Write a message...' ) }
2015-09-08 15:47:15 -04:00
channelId = { this . state . channelId }
id = 'post_textbox'
ref = 'textbox'
/ >
2017-03-24 09:09:51 -04:00
< FileUpload
ref = 'fileUpload'
getFileCount = { this . getFileCount }
2017-04-04 12:25:55 -04:00
getTarget = { this . getFileUploadTarget }
2017-03-24 09:09:51 -04:00
onFileUploadChange = { this . handleFileUploadChange }
onUploadStart = { this . handleUploadStart }
onFileUpload = { this . handleFileUploadComplete }
onUploadError = { this . handleUploadError }
postType = 'post'
channelId = ''
2017-06-13 14:35:45 -04:00
onEmojiClick = { this . toggleEmojiPicker }
2017-03-24 09:09:51 -04:00
emojiEnabled = { this . state . emojiPickerEnabled }
navBarName = 'main'
/ >
{ emojiPicker }
2015-09-08 15:47:15 -04:00
< / div >
< a
2017-02-08 00:13:07 +09:00
className = { sendButtonClass }
2015-09-08 15:47:15 -04:00
onClick = { this . handleSubmit }
>
2016-02-22 08:31:10 -05:00
< i className = 'fa fa-paper-plane' / >
2015-09-08 15:47:15 -04:00
< / a >
2015-10-30 11:35:16 -04:00
{ tutorialTip }
2015-06-14 23:53:32 -08:00
< / div >
2015-08-10 14:01:11 -04:00
< div className = { postFooterClassName } >
2015-08-31 11:31:55 -04:00
< MsgTyping
channelId = { this . state . channelId }
parentId = ''
/ >
2015-12-08 22:42:59 +05:00
{ postError }
2016-12-06 18:49:36 -05:00
{ preview }
2015-12-08 22:42:59 +05:00
{ serverError }
2015-06-14 23:53:32 -08:00
< / div >
< / div >
2016-02-18 10:31:44 -05:00
< PostDeletedModal
show = { this . state . showPostDeletedModal }
onHide = { this . hidePostDeletedModal }
/ >
2017-05-30 22:46:18 +02:00
< ConfirmModal
title = { notifyAllTitle }
message = { notifyAllMessage }
2017-06-15 11:05:43 -04:00
confirmButtonText = { notifyAllConfirm }
2017-05-30 22:46:18 +02:00
show = { this . state . showConfirmModal }
onConfirm = { this . handleNotifyAllConfirmation }
onCancel = { this . handleNotifyModalCancel }
/ >
2015-06-14 23:53:32 -08:00
< / form >
) ;
}
2016-11-04 06:14:19 -07:00
}