2016-11-12 14:33:21 +01:00
import CharacterCounter from './character_counter' ;
import Button from '../../../components/button' ;
import PureRenderMixin from 'react-addons-pure-render-mixin' ;
2016-08-31 22:58:10 +02:00
import ImmutablePropTypes from 'react-immutable-proptypes' ;
2017-02-22 15:43:07 +01:00
import ReplyIndicatorContainer from '../containers/reply_indicator_container' ;
2016-12-14 18:21:31 +01:00
import AutosuggestTextarea from '../../../components/autosuggest_textarea' ;
2016-11-13 13:13:36 +01:00
import { debounce } from 'react-decoration' ;
2016-11-13 19:08:52 +01:00
import UploadButtonContainer from '../containers/upload_button_container' ;
2016-11-23 22:57:57 +01:00
import { defineMessages , injectIntl , FormattedMessage } from 'react-intl' ;
2016-11-23 18:53:23 +01:00
import Toggle from 'react-toggle' ;
2017-02-13 17:20:18 +01:00
import Collapsable from '../../../components/collapsable' ;
2017-03-25 00:01:43 +01:00
import SpoilerButtonContainer from '../containers/spoiler_button_container' ;
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container' ;
import SensitiveButtonContainer from '../containers/sensitive_button_container' ;
2017-03-02 00:57:55 +01:00
import EmojiPickerDropdown from './emoji_picker_dropdown' ;
2017-03-25 00:01:43 +01:00
import UploadFormContainer from '../containers/upload_form_container' ;
import TextIconButton from './text_icon_button' ;
2016-11-18 15:36:16 +01:00
const messages = defineMessages ( {
placeholder : { id : 'compose_form.placeholder' , defaultMessage : 'What is on your mind?' } ,
2017-01-13 05:54:26 +01:00
spoiler _placeholder : { id : 'compose_form.spoiler_placeholder' , defaultMessage : 'Content warning' } ,
2017-04-13 12:57:41 +02:00
publish : { id : 'compose_form.publish' , defaultMessage : 'Toot' }
2016-11-18 15:36:16 +01:00
} ) ;
2016-10-30 18:13:05 +01:00
2016-09-03 14:01:10 +02:00
const ComposeForm = React . createClass ( {
2016-08-25 19:52:55 +02:00
propTypes : {
2016-12-26 21:52:03 +01:00
intl : React . PropTypes . object . isRequired ,
2016-08-31 16:15:12 +02:00
text : React . PropTypes . string . isRequired ,
2016-11-12 14:33:21 +01:00
suggestion _token : React . PropTypes . string ,
2016-12-14 18:21:31 +01:00
suggestions : ImmutablePropTypes . list ,
2017-01-13 05:54:26 +01:00
spoiler : React . PropTypes . bool ,
2017-03-25 20:24:30 +01:00
privacy : React . PropTypes . string ,
2017-02-26 01:23:44 +01:00
spoiler _text : React . PropTypes . string ,
2017-02-22 15:43:07 +01:00
focusDate : React . PropTypes . instanceOf ( Date ) ,
preselectDate : React . PropTypes . instanceOf ( Date ) ,
2016-08-31 22:58:10 +02:00
is _submitting : React . PropTypes . bool ,
2016-09-27 17:02:30 +02:00
is _uploading : React . PropTypes . bool ,
2017-01-16 16:23:45 +01:00
me : React . PropTypes . number ,
2017-02-13 18:38:00 +01:00
needsPrivacyWarning : React . PropTypes . bool ,
mentionedDomains : React . PropTypes . array . isRequired ,
2016-08-31 16:15:12 +02:00
onChange : React . PropTypes . func . isRequired ,
2016-08-31 22:58:10 +02:00
onSubmit : React . PropTypes . func . isRequired ,
2016-11-12 14:33:21 +01:00
onClearSuggestions : React . PropTypes . func . isRequired ,
onFetchSuggestions : React . PropTypes . func . isRequired ,
2016-11-23 18:53:23 +01:00
onSuggestionSelected : React . PropTypes . func . isRequired ,
2017-01-13 05:54:26 +01:00
onChangeSpoilerText : React . PropTypes . func . isRequired ,
2017-03-01 11:56:15 +01:00
onPaste : React . PropTypes . func . isRequired ,
2017-03-02 00:57:55 +01:00
onPickEmoji : React . PropTypes . func . isRequired
2016-08-25 19:52:55 +02:00
} ,
2016-08-31 16:15:12 +02:00
mixins : [ PureRenderMixin ] ,
2016-08-25 19:52:55 +02:00
handleChange ( e ) {
2016-08-31 16:15:12 +02:00
this . props . onChange ( e . target . value ) ;
2016-08-25 19:52:55 +02:00
} ,
2017-01-05 03:29:43 +01:00
handleKeyDown ( e ) {
2016-12-11 23:54:32 +01:00
if ( e . keyCode === 13 && ( e . ctrlKey || e . metaKey ) ) {
2016-08-31 16:15:12 +02:00
this . props . onSubmit ( ) ;
2016-08-25 19:52:55 +02:00
}
} ,
handleSubmit ( ) {
2016-08-31 16:15:12 +02:00
this . props . onSubmit ( ) ;
2016-08-25 19:52:55 +02:00
} ,
2016-10-30 18:13:05 +01:00
onSuggestionsClearRequested ( ) {
this . props . onClearSuggestions ( ) ;
} ,
2016-11-13 13:13:36 +01:00
@ debounce ( 500 )
2016-12-14 18:21:31 +01:00
onSuggestionsFetchRequested ( token ) {
this . props . onFetchSuggestions ( token ) ;
2016-10-30 18:13:05 +01:00
} ,
2016-12-14 18:21:31 +01:00
onSuggestionSelected ( tokenStart , token , value ) {
2017-03-02 00:57:55 +01:00
this . _restoreCaret = null ;
2016-12-14 18:21:31 +01:00
this . props . onSuggestionSelected ( tokenStart , token , value ) ;
2016-11-12 14:33:21 +01:00
} ,
2017-01-13 05:54:26 +01:00
handleChangeSpoilerText ( e ) {
this . props . onChangeSpoilerText ( e . target . value ) ;
} ,
2017-04-10 21:30:58 +02:00
componentWillReceiveProps ( nextProps ) {
// If this is the update where we've finished uploading,
// save the last caret position so we can restore it below!
if ( ! nextProps . is _uploading && this . props . is _uploading ) {
this . _restoreCaret = this . autosuggestTextarea . textarea . selectionStart ;
}
} ,
2016-12-14 18:21:31 +01:00
componentDidUpdate ( prevProps ) {
2017-04-15 02:57:26 +02:00
// This statement does several things:
2017-04-10 21:30:58 +02:00
// - If we're beginning a reply, and,
// - Replying to zero or one users, places the cursor at the end of the textbox.
// - Replying to more than one user, selects any usernames past the first;
// this provides a convenient shortcut to drop everyone else from the conversation.
// - If we've just finished uploading an image, and have a saved caret position,
// restores the cursor to that position after the text changes!
if ( this . props . focusDate !== prevProps . focusDate || ( prevProps . is _uploading && ! this . props . is _uploading && typeof this . _restoreCaret === 'number' ) ) {
2017-03-02 00:57:55 +01:00
let selectionEnd , selectionStart ;
if ( this . props . preselectDate !== prevProps . preselectDate ) {
selectionEnd = this . props . text . length ;
selectionStart = this . props . text . search ( /\s/ ) + 1 ;
} else if ( typeof this . _restoreCaret === 'number' ) {
selectionStart = this . _restoreCaret ;
selectionEnd = this . _restoreCaret ;
} else {
selectionEnd = this . props . text . length ;
selectionStart = selectionEnd ;
}
2017-01-05 05:04:14 +01:00
2017-01-05 14:06:09 +01:00
this . autosuggestTextarea . textarea . setSelectionRange ( selectionStart , selectionEnd ) ;
2016-12-14 18:21:31 +01:00
this . autosuggestTextarea . textarea . focus ( ) ;
}
} ,
setAutosuggestTextarea ( c ) {
this . autosuggestTextarea = c ;
} ,
2017-03-02 00:57:55 +01:00
handleEmojiPick ( data ) {
const position = this . autosuggestTextarea . textarea . selectionStart ;
this . _restoreCaret = position + data . shortname . length + 1 ;
this . props . onPickEmoji ( position , data ) ;
} ,
2016-08-25 19:52:55 +02:00
render ( ) {
2017-03-01 13:57:30 +01:00
const { intl , needsPrivacyWarning , mentionedDomains , onPaste } = this . props ;
2017-04-10 21:30:58 +02:00
const disabled = this . props . is _submitting ;
2017-04-18 19:35:55 +02:00
const text = [ this . props . spoiler _text , this . props . text ] . join ( '' ) ;
2017-02-13 18:38:00 +01:00
let publishText = '' ;
let privacyWarning = '' ;
2017-02-22 15:43:07 +01:00
let reply _to _other = false ;
2016-08-31 22:58:10 +02:00
2017-02-13 18:38:00 +01:00
if ( needsPrivacyWarning ) {
privacyWarning = (
< div className = 'compose-form__warning' >
< FormattedMessage
id = 'compose_form.privacy_disclaimer'
defaultMessage = 'Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}} to not leak your status?'
values = { { domains : < strong > { mentionedDomains . join ( ', ' ) } < / strong > , domainsCount : mentionedDomains . length } }
/ >
< / div >
) ;
}
2017-01-16 16:23:45 +01:00
2017-03-25 20:24:30 +01:00
if ( this . props . privacy === 'private' || this . props . privacy === 'direct' ) {
2017-02-06 23:16:20 +01:00
publishText = < span > < i className = 'fa fa-lock' / > { intl . formatMessage ( messages . publish ) } < / span > ;
} else {
2017-03-25 20:24:30 +01:00
publishText = intl . formatMessage ( messages . publish ) + ( this . props . privacy !== 'unlisted' ? '!' : '' ) ;
2017-02-06 23:16:20 +01:00
}
2016-08-25 19:52:55 +02:00
return (
2016-09-07 18:17:15 +02:00
< div style = { { padding : '10px' } } >
2017-02-13 17:20:18 +01:00
< Collapsable isVisible = { this . props . spoiler } fullHeight = { 50 } >
< div className = "spoiler-input" >
2017-03-26 13:08:15 +02:00
< input placeholder = { intl . formatMessage ( messages . spoiler _placeholder ) } value = { this . props . spoiler _text } onChange = { this . handleChangeSpoilerText } onKeyDown = { this . handleKeyDown } type = "text" className = "spoiler-input__input" / >
2017-02-13 17:20:18 +01:00
< / div >
< / Collapsable >
2017-01-13 05:54:26 +01:00
2017-02-13 18:38:00 +01:00
{ privacyWarning }
2017-02-22 15:43:07 +01:00
< ReplyIndicatorContainer / >
2016-08-31 22:58:10 +02:00
2017-03-25 00:01:43 +01:00
< div style = { { position : 'relative' } } >
< AutosuggestTextarea
ref = { this . setAutosuggestTextarea }
placeholder = { intl . formatMessage ( messages . placeholder ) }
disabled = { disabled }
value = { this . props . text }
onChange = { this . handleChange }
suggestions = { this . props . suggestions }
onKeyDown = { this . handleKeyDown }
onSuggestionsFetchRequested = { this . onSuggestionsFetchRequested }
onSuggestionsClearRequested = { this . onSuggestionsClearRequested }
onSuggestionSelected = { this . onSuggestionSelected }
onPaste = { onPaste }
/ >
< EmojiPickerDropdown onPickEmoji = { this . handleEmojiPick } / >
< / div >
< div className = 'compose-form__modifiers' >
< UploadFormContainer / >
< / div >
2017-03-26 13:08:15 +02:00
< div style = { { display : 'flex' , justifyContent : 'space-between' } } >
2017-03-25 00:01:43 +01:00
< div className = 'compose-form__buttons' >
2017-03-02 00:57:55 +01:00
< UploadButtonContainer / >
2017-03-25 00:01:43 +01:00
< PrivacyDropdownContainer / >
< SensitiveButtonContainer / >
< SpoilerButtonContainer / >
2017-03-02 00:57:55 +01:00
< / div >
2016-11-23 18:53:23 +01:00
2017-04-19 19:21:23 +02:00
< div style = { { display : 'flex' , minWidth : 0 } } >
2017-04-18 19:35:55 +02:00
< div style = { { paddingTop : '10px' , marginRight : '16px' , lineHeight : '36px' } } > < CharacterCounter max = { 500 } text = { text } / > < / div >
2017-04-19 19:21:23 +02:00
< div style = { { paddingTop : '10px' , overflow : 'hidden' } } > < Button text = { publishText } onClick = { this . handleSubmit } disabled = { disabled || text . replace ( /[\uD800-\uDBFF][\uDC00-\uDFFF]/g , "_" ) . length > 500 } block / > < / div >
2017-03-26 13:08:15 +02:00
< / div >
2017-03-25 00:01:43 +01:00
< / div >
2016-08-25 19:52:55 +02:00
< / div >
) ;
}
} ) ;
2016-11-16 17:20:52 +01:00
export default injectIntl ( ComposeForm ) ;