2017-12-24 00:16:45 -06:00
// Package imports.
import PropTypes from 'prop-types' ;
import React from 'react' ;
import ImmutablePropTypes from 'react-immutable-proptypes' ;
2018-08-29 08:26:24 -05:00
import { defineMessages } from 'react-intl' ;
2017-12-24 00:16:45 -06:00
2018-01-19 04:34:48 -06:00
const APPROX _HASHTAG _RE = /(?:^|[^\/\)\w])#(\S+)/i ;
2017-12-24 00:16:45 -06:00
// Actions.
import {
cancelReplyCompose ,
changeCompose ,
2018-01-05 20:23:06 -06:00
changeComposeAdvancedOption ,
2017-12-24 00:16:45 -06:00
changeComposeSensitivity ,
changeComposeSpoilerText ,
changeComposeSpoilerness ,
changeComposeVisibility ,
changeUploadCompose ,
clearComposeSuggestions ,
fetchComposeSuggestions ,
insertEmojiCompose ,
2018-01-05 20:30:06 -06:00
mountCompose ,
2017-12-24 00:16:45 -06:00
selectComposeSuggestion ,
submitCompose ,
undoUploadCompose ,
2018-01-05 20:30:06 -06:00
unmountCompose ,
2017-12-24 00:16:45 -06:00
uploadCompose ,
} from 'flavours/glitch/actions/compose' ;
import {
closeModal ,
openModal ,
} from 'flavours/glitch/actions/modal' ;
2018-11-27 10:31:50 -06:00
import { changeLocalSetting } from 'flavours/glitch/actions/local_settings' ;
2019-03-06 05:30:11 -06:00
import { addPoll , removePoll } from 'flavours/glitch/actions/compose' ;
2017-12-24 00:16:45 -06:00
// Components.
import ComposerOptions from './options' ;
import ComposerPublisher from './publisher' ;
import ComposerReply from './reply' ;
import ComposerSpoiler from './spoiler' ;
import ComposerTextarea from './textarea' ;
import ComposerUploadForm from './upload_form' ;
2019-03-06 05:30:11 -06:00
import ComposerPollForm from './poll_form' ;
2017-12-24 00:16:45 -06:00
import ComposerWarning from './warning' ;
2018-01-19 04:34:48 -06:00
import ComposerHashtagWarning from './hashtag_warning' ;
2018-03-29 14:13:47 -05:00
import ComposerDirectWarning from './direct_warning' ;
2017-12-24 00:16:45 -06:00
// Utils.
import { countableText } from 'flavours/glitch/util/counter' ;
import { me } from 'flavours/glitch/util/initial_state' ;
import { isMobile } from 'flavours/glitch/util/is_mobile' ;
import { assignHandlers } from 'flavours/glitch/util/react_helpers' ;
2017-12-26 18:54:28 -06:00
import { wrap } from 'flavours/glitch/util/redux_helpers' ;
2018-07-12 05:06:42 -05:00
import { privacyPreference } from 'flavours/glitch/util/privacy_preference' ;
2017-12-24 00:16:45 -06:00
2018-08-29 08:26:24 -05:00
const messages = defineMessages ( {
missingDescriptionMessage : { id : 'confirmations.missing_media_description.message' ,
defaultMessage : 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' } ,
missingDescriptionConfirm : { id : 'confirmations.missing_media_description.confirm' ,
defaultMessage : 'Send anyway' } ,
} ) ;
2017-12-24 00:16:45 -06:00
// State mapping.
function mapStateToProps ( state ) {
2018-08-22 08:58:57 -05:00
const spoilersAlwaysOn = state . getIn ( [ 'local_settings' , 'always_show_spoilers_field' ] ) ;
2017-12-24 00:16:45 -06:00
const inReplyTo = state . getIn ( [ 'compose' , 'in_reply_to' ] ) ;
2018-07-11 15:49:07 -05:00
const replyPrivacy = inReplyTo ? state . getIn ( [ 'statuses' , inReplyTo , 'visibility' ] ) : null ;
const sideArmBasePrivacy = state . getIn ( [ 'local_settings' , 'side_arm' ] ) ;
2018-07-12 05:06:42 -05:00
const sideArmRestrictedPrivacy = replyPrivacy ? privacyPreference ( replyPrivacy , sideArmBasePrivacy ) : null ;
let sideArmPrivacy = null ;
switch ( state . getIn ( [ 'local_settings' , 'side_arm_reply_mode' ] ) ) {
case 'copy' :
sideArmPrivacy = replyPrivacy ;
break ;
case 'restrict' :
sideArmPrivacy = sideArmRestrictedPrivacy ;
break ;
}
sideArmPrivacy = sideArmPrivacy || sideArmBasePrivacy ;
2017-12-24 00:16:45 -06:00
return {
acceptContentTypes : state . getIn ( [ 'media_attachments' , 'accept_content_types' ] ) . toArray ( ) . join ( ',' ) ,
2018-01-05 20:23:06 -06:00
advancedOptions : state . getIn ( [ 'compose' , 'advanced_options' ] ) ,
2017-12-24 00:16:45 -06:00
amUnlocked : ! state . getIn ( [ 'accounts' , me , 'locked' ] ) ,
focusDate : state . getIn ( [ 'compose' , 'focusDate' ] ) ,
2018-05-22 14:09:07 -05:00
caretPosition : state . getIn ( [ 'compose' , 'caretPosition' ] ) ,
2017-12-24 00:16:45 -06:00
isSubmitting : state . getIn ( [ 'compose' , 'is_submitting' ] ) ,
2018-12-16 08:26:04 -06:00
isChangingUpload : state . getIn ( [ 'compose' , 'is_changing_upload' ] ) ,
2017-12-24 00:16:45 -06:00
isUploading : state . getIn ( [ 'compose' , 'is_uploading' ] ) ,
2018-01-03 14:36:21 -06:00
layout : state . getIn ( [ 'local_settings' , 'layout' ] ) ,
2017-12-24 00:16:45 -06:00
media : state . getIn ( [ 'compose' , 'media_attachments' ] ) ,
preselectDate : state . getIn ( [ 'compose' , 'preselectDate' ] ) ,
privacy : state . getIn ( [ 'compose' , 'privacy' ] ) ,
progress : state . getIn ( [ 'compose' , 'progress' ] ) ,
2018-07-28 16:08:38 -05:00
inReplyTo : inReplyTo ? state . getIn ( [ 'statuses' , inReplyTo ] ) : null ,
2018-01-05 22:04:13 -06:00
replyAccount : inReplyTo ? state . getIn ( [ 'statuses' , inReplyTo , 'account' ] ) : null ,
2017-12-24 00:16:45 -06:00
replyContent : inReplyTo ? state . getIn ( [ 'statuses' , inReplyTo , 'contentHtml' ] ) : null ,
resetFileKey : state . getIn ( [ 'compose' , 'resetFileKey' ] ) ,
2018-07-11 15:49:07 -05:00
sideArm : sideArmPrivacy ,
2017-12-24 00:16:45 -06:00
sensitive : state . getIn ( [ 'compose' , 'sensitive' ] ) ,
showSearch : state . getIn ( [ 'search' , 'submitted' ] ) && ! state . getIn ( [ 'search' , 'hidden' ] ) ,
2018-08-22 08:58:57 -05:00
spoiler : spoilersAlwaysOn || state . getIn ( [ 'compose' , 'spoiler' ] ) ,
2017-12-24 00:16:45 -06:00
spoilerText : state . getIn ( [ 'compose' , 'spoiler_text' ] ) ,
suggestionToken : state . getIn ( [ 'compose' , 'suggestion_token' ] ) ,
suggestions : state . getIn ( [ 'compose' , 'suggestions' ] ) ,
text : state . getIn ( [ 'compose' , 'text' ] ) ,
2018-03-12 12:39:07 -05:00
anyMedia : state . getIn ( [ 'compose' , 'media_attachments' ] ) . size > 0 ,
2019-03-06 05:30:11 -06:00
poll : state . getIn ( [ 'compose' , 'poll' ] ) ,
2018-08-22 08:58:57 -05:00
spoilersAlwaysOn : spoilersAlwaysOn ,
2018-08-29 08:26:24 -05:00
mediaDescriptionConfirmation : state . getIn ( [ 'local_settings' , 'confirm_missing_media_description' ] ) ,
2018-09-28 13:58:03 -05:00
preselectOnReply : state . getIn ( [ 'local_settings' , 'preselect_on_reply' ] ) ,
2017-12-24 00:16:45 -06:00
} ;
} ;
// Dispatch mapping.
2018-08-29 08:26:24 -05:00
const mapDispatchToProps = ( dispatch , { intl } ) => ( {
2018-08-18 03:32:45 -05:00
onCancelReply ( ) {
dispatch ( cancelReplyCompose ( ) ) ;
} ,
onChangeAdvancedOption ( option , value ) {
dispatch ( changeComposeAdvancedOption ( option , value ) ) ;
} ,
onChangeDescription ( id , description ) {
2018-08-18 04:01:53 -05:00
dispatch ( changeUploadCompose ( id , { description } ) ) ;
2018-08-18 03:32:45 -05:00
} ,
onChangeSensitivity ( ) {
dispatch ( changeComposeSensitivity ( ) ) ;
} ,
onChangeSpoilerText ( text ) {
dispatch ( changeComposeSpoilerText ( text ) ) ;
} ,
onChangeSpoilerness ( ) {
dispatch ( changeComposeSpoilerness ( ) ) ;
} ,
onChangeText ( text ) {
dispatch ( changeCompose ( text ) ) ;
} ,
onChangeVisibility ( value ) {
dispatch ( changeComposeVisibility ( value ) ) ;
} ,
2019-03-06 05:30:11 -06:00
onTogglePoll ( ) {
dispatch ( ( _ , getState ) => {
if ( getState ( ) . getIn ( [ 'compose' , 'poll' ] ) ) {
dispatch ( removePoll ( ) ) ;
} else {
dispatch ( addPoll ( ) ) ;
}
} ) ;
} ,
2018-08-18 03:32:45 -05:00
onClearSuggestions ( ) {
dispatch ( clearComposeSuggestions ( ) ) ;
} ,
onCloseModal ( ) {
dispatch ( closeModal ( ) ) ;
} ,
onFetchSuggestions ( token ) {
dispatch ( fetchComposeSuggestions ( token ) ) ;
} ,
onInsertEmoji ( position , emoji ) {
dispatch ( insertEmojiCompose ( position , emoji ) ) ;
} ,
onMount ( ) {
dispatch ( mountCompose ( ) ) ;
} ,
2018-09-11 14:34:07 -05:00
onOpenActionsModal ( props ) {
2018-08-18 03:32:45 -05:00
dispatch ( openModal ( 'ACTIONS' , props ) ) ;
} ,
onOpenDoodleModal ( ) {
dispatch ( openModal ( 'DOODLE' , { noEsc : true } ) ) ;
} ,
2018-08-18 04:01:53 -05:00
onOpenFocalPointModal ( id ) {
dispatch ( openModal ( 'FOCAL_POINT' , { id } ) ) ;
} ,
2018-08-18 03:32:45 -05:00
onSelectSuggestion ( position , token , suggestion ) {
dispatch ( selectComposeSuggestion ( position , token , suggestion ) ) ;
} ,
2018-12-13 09:50:37 -06:00
onMediaDescriptionConfirm ( routerHistory ) {
2018-08-29 08:26:24 -05:00
dispatch ( openModal ( 'CONFIRM' , {
message : intl . formatMessage ( messages . missingDescriptionMessage ) ,
confirm : intl . formatMessage ( messages . missingDescriptionConfirm ) ,
2018-12-13 09:50:37 -06:00
onConfirm : ( ) => dispatch ( submitCompose ( routerHistory ) ) ,
2018-11-27 10:31:50 -06:00
onDoNotAsk : ( ) => dispatch ( changeLocalSetting ( [ 'confirm_missing_media_description' ] , false ) ) ,
2018-08-29 08:26:24 -05:00
} ) ) ;
} ,
2018-12-13 09:50:37 -06:00
onSubmit ( routerHistory ) {
dispatch ( submitCompose ( routerHistory ) ) ;
2018-08-18 03:32:45 -05:00
} ,
onUndoUpload ( id ) {
dispatch ( undoUploadCompose ( id ) ) ;
} ,
onUnmount ( ) {
dispatch ( unmountCompose ( ) ) ;
} ,
onUpload ( files ) {
dispatch ( uploadCompose ( files ) ) ;
} ,
} ) ;
2017-12-24 00:16:45 -06:00
// Handlers.
const handlers = {
// Changes the text value of the spoiler.
2018-01-03 14:36:21 -06:00
handleChangeSpoiler ( { target : { value } } ) {
const { onChangeSpoilerText } = this . props ;
if ( onChangeSpoilerText ) {
onChangeSpoilerText ( value ) ;
2017-12-24 00:16:45 -06:00
}
} ,
// Inserts an emoji at the caret.
2018-01-03 14:36:21 -06:00
handleEmoji ( data ) {
2017-12-24 00:16:45 -06:00
const { textarea : { selectionStart } } = this ;
2018-01-03 14:36:21 -06:00
const { onInsertEmoji } = this . props ;
if ( onInsertEmoji ) {
onInsertEmoji ( selectionStart , data ) ;
2017-12-24 00:16:45 -06:00
}
} ,
// Handles the secondary submit button.
2018-01-03 14:36:21 -06:00
handleSecondarySubmit ( ) {
const { handleSubmit } = this . handlers ;
2017-12-24 00:16:45 -06:00
const {
2018-01-03 14:36:21 -06:00
onChangeVisibility ,
sideArm ,
2017-12-24 00:16:45 -06:00
} = this . props ;
2018-01-03 14:36:21 -06:00
if ( sideArm !== 'none' && onChangeVisibility ) {
onChangeVisibility ( sideArm ) ;
2017-12-24 00:16:45 -06:00
}
2018-01-03 14:36:21 -06:00
handleSubmit ( ) ;
2017-12-24 00:16:45 -06:00
} ,
// Selects a suggestion from the autofill.
2018-01-03 14:36:21 -06:00
handleSelect ( tokenStart , token , value ) {
const { onSelectSuggestion } = this . props ;
if ( onSelectSuggestion ) {
onSelectSuggestion ( tokenStart , token , value ) ;
2017-12-24 00:16:45 -06:00
}
} ,
// Submits the status.
2018-01-03 14:36:21 -06:00
handleSubmit ( ) {
2018-08-30 04:46:45 -05:00
const { textarea : { value } , uploadForm } = this ;
2017-12-24 00:16:45 -06:00
const {
2018-01-03 14:36:21 -06:00
onChangeText ,
onSubmit ,
2018-04-02 10:22:32 -05:00
isSubmitting ,
2018-12-16 08:26:04 -06:00
isChangingUpload ,
2018-04-02 10:22:32 -05:00
isUploading ,
2018-08-29 08:26:24 -05:00
media ,
2018-04-02 10:22:32 -05:00
anyMedia ,
2018-01-03 14:36:21 -06:00
text ,
2018-08-29 08:26:24 -05:00
mediaDescriptionConfirmation ,
onMediaDescriptionConfirm ,
2017-12-24 00:16:45 -06:00
} = this . props ;
// If something changes inside the textarea, then we update the
// state before submitting.
2018-01-03 14:36:21 -06:00
if ( onChangeText && text !== value ) {
onChangeText ( value ) ;
2017-12-24 00:16:45 -06:00
}
2018-04-02 10:22:32 -05:00
// Submit disabled:
2018-12-16 08:26:04 -06:00
if ( isSubmitting || isUploading || isChangingUpload || ( ! text . trim ( ) . length && ! anyMedia ) ) {
2018-04-02 10:22:32 -05:00
return ;
}
2018-08-29 08:26:24 -05:00
// Submit unless there are media with missing descriptions
if ( mediaDescriptionConfirmation && onMediaDescriptionConfirm && media && media . some ( item => ! item . get ( 'description' ) ) ) {
const firstWithoutDescription = media . findIndex ( item => ! item . get ( 'description' ) ) ;
2018-08-30 04:46:45 -05:00
if ( uploadForm ) {
const inputs = uploadForm . querySelectorAll ( '.composer--upload_form--item input' ) ;
if ( inputs . length == media . size && firstWithoutDescription !== - 1 ) {
inputs [ firstWithoutDescription ] . focus ( ) ;
}
2018-08-29 08:26:24 -05:00
}
2018-12-13 09:50:37 -06:00
onMediaDescriptionConfirm ( this . context . router ? this . context . router . history : null ) ;
2018-08-29 08:26:24 -05:00
} else if ( onSubmit ) {
2018-12-13 09:50:37 -06:00
onSubmit ( this . context . router ? this . context . router . history : null ) ;
2017-12-24 00:16:45 -06:00
}
} ,
2018-08-30 04:46:45 -05:00
// Sets a reference to the upload form.
handleRefUploadForm ( uploadFormComponent ) {
this . uploadForm = uploadFormComponent ;
} ,
2017-12-24 00:16:45 -06:00
// Sets a reference to the textarea.
2018-01-03 14:36:21 -06:00
handleRefTextarea ( textareaComponent ) {
if ( textareaComponent ) {
this . textarea = textareaComponent . textarea ;
}
2017-12-24 00:16:45 -06:00
} ,
2018-08-18 13:53:46 -05:00
// Sets a reference to the CW field.
handleRefSpoilerText ( spoilerComponent ) {
if ( spoilerComponent ) {
this . spoilerText = spoilerComponent . spoilerText ;
}
}
2017-12-24 00:16:45 -06:00
} ;
// The component.
2017-12-26 18:54:28 -06:00
class Composer extends React . Component {
2017-12-24 00:16:45 -06:00
// Constructor.
constructor ( props ) {
super ( props ) ;
assignHandlers ( this , handlers ) ;
// Instance variables.
this . textarea = null ;
2018-08-18 13:53:46 -05:00
this . spoilerText = null ;
2017-12-24 00:16:45 -06:00
}
2018-01-05 20:30:06 -06:00
// Tells our state the composer has been mounted.
componentDidMount ( ) {
const { onMount } = this . props ;
if ( onMount ) {
onMount ( ) ;
}
}
// Tells our state the composer has been unmounted.
componentWillUnmount ( ) {
const { onUnmount } = this . props ;
if ( onUnmount ) {
onUnmount ( ) ;
}
}
2017-12-24 00:16:45 -06:00
// This statement does several things:
// - 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.
componentDidUpdate ( prevProps ) {
const {
textarea ,
2018-08-18 13:53:46 -05:00
spoilerText ,
2017-12-24 00:16:45 -06:00
} = this ;
const {
2018-01-03 14:36:21 -06:00
focusDate ,
2018-05-22 14:09:07 -05:00
caretPosition ,
2018-01-03 14:36:21 -06:00
isSubmitting ,
preselectDate ,
text ,
2018-09-28 13:58:03 -05:00
preselectOnReply ,
2017-12-24 00:16:45 -06:00
} = this . props ;
let selectionEnd , selectionStart ;
// Caret/selection handling.
2018-05-22 13:21:09 -05:00
if ( focusDate !== prevProps . focusDate ) {
2017-12-24 00:16:45 -06:00
switch ( true ) {
2018-09-28 13:58:03 -05:00
case preselectDate !== prevProps . preselectDate && preselectOnReply :
2017-12-24 00:16:45 -06:00
selectionStart = text . search ( /\s/ ) + 1 ;
selectionEnd = text . length ;
break ;
2018-05-22 14:09:07 -05:00
case ! isNaN ( caretPosition ) && caretPosition !== null :
selectionStart = selectionEnd = caretPosition ;
2017-12-24 00:16:45 -06:00
break ;
default :
selectionStart = selectionEnd = text . length ;
}
2018-01-03 14:36:21 -06:00
if ( textarea ) {
textarea . setSelectionRange ( selectionStart , selectionEnd ) ;
textarea . focus ( ) ;
2018-09-28 11:44:55 -05:00
textarea . scrollIntoView ( ) ;
2018-01-03 14:36:21 -06:00
}
2017-12-24 00:16:45 -06:00
// Refocuses the textarea after submitting.
2018-01-03 14:36:21 -06:00
} else if ( textarea && prevProps . isSubmitting && ! isSubmitting ) {
2017-12-24 00:16:45 -06:00
textarea . focus ( ) ;
2018-08-18 13:53:46 -05:00
} else if ( this . props . spoiler !== prevProps . spoiler ) {
if ( this . props . spoiler ) {
if ( spoilerText ) {
spoilerText . focus ( ) ;
}
} else {
if ( textarea ) {
textarea . focus ( ) ;
}
}
2017-12-24 00:16:45 -06:00
}
}
render ( ) {
const {
2018-01-03 14:36:21 -06:00
handleChangeSpoiler ,
handleEmoji ,
handleSecondarySubmit ,
handleSelect ,
handleSubmit ,
2018-08-30 04:46:45 -05:00
handleRefUploadForm ,
2018-01-03 14:36:21 -06:00
handleRefTextarea ,
2018-08-18 13:53:46 -05:00
handleRefSpoilerText ,
2017-12-24 00:16:45 -06:00
} = this . handlers ;
const {
2018-01-03 14:36:21 -06:00
acceptContentTypes ,
2018-01-05 20:23:06 -06:00
advancedOptions ,
2018-01-03 14:36:21 -06:00
amUnlocked ,
2018-03-12 12:39:07 -05:00
anyMedia ,
2017-12-24 00:16:45 -06:00
intl ,
2018-01-03 14:36:21 -06:00
isSubmitting ,
2018-12-16 08:26:04 -06:00
isChangingUpload ,
2018-01-03 14:36:21 -06:00
isUploading ,
layout ,
media ,
2019-03-06 05:30:11 -06:00
poll ,
2018-01-03 14:36:21 -06:00
onCancelReply ,
2018-01-05 20:23:06 -06:00
onChangeAdvancedOption ,
2018-01-03 14:36:21 -06:00
onChangeDescription ,
onChangeSensitivity ,
onChangeSpoilerness ,
onChangeText ,
onChangeVisibility ,
2019-03-06 05:30:11 -06:00
onTogglePoll ,
2018-01-03 14:36:21 -06:00
onClearSuggestions ,
onCloseModal ,
onFetchSuggestions ,
onOpenActionsModal ,
onOpenDoodleModal ,
2018-08-18 04:01:53 -05:00
onOpenFocalPointModal ,
2018-01-03 14:36:21 -06:00
onUndoUpload ,
onUpload ,
privacy ,
progress ,
2018-07-28 16:08:38 -05:00
inReplyTo ,
2018-01-03 14:36:21 -06:00
resetFileKey ,
sensitive ,
showSearch ,
sideArm ,
spoiler ,
spoilerText ,
suggestions ,
text ,
2018-08-22 08:58:57 -05:00
spoilersAlwaysOn ,
2017-12-24 00:16:45 -06:00
} = this . props ;
2018-12-16 08:26:04 -06:00
let disabledButton = isSubmitting || isUploading || isChangingUpload || ( ! text . trim ( ) . length && ! anyMedia ) ;
2018-03-12 12:39:07 -05:00
2017-12-24 00:16:45 -06:00
return (
2018-01-03 14:36:21 -06:00
< div className = 'composer' >
2018-03-29 14:13:47 -05:00
{ privacy === 'direct' ? < ComposerDirectWarning / > : null }
2017-12-24 00:16:45 -06:00
{ privacy === 'private' && amUnlocked ? < ComposerWarning / > : null }
2018-01-19 04:34:48 -06:00
{ privacy !== 'public' && APPROX _HASHTAG _RE . test ( text ) ? < ComposerHashtagWarning / > : null }
2018-07-28 16:08:38 -05:00
{ inReplyTo && (
2017-12-24 00:16:45 -06:00
< ComposerReply
2018-07-28 16:08:38 -05:00
status = { inReplyTo }
2017-12-24 00:16:45 -06:00
intl = { intl }
2018-01-03 14:36:21 -06:00
onCancel = { onCancelReply }
2017-12-24 00:16:45 -06:00
/ >
2018-07-10 14:58:47 -05:00
) }
2018-07-27 12:57:24 -05:00
< ComposerSpoiler
hidden = { ! spoiler }
intl = { intl }
onChange = { handleChangeSpoiler }
onSubmit = { handleSubmit }
2018-10-14 04:38:38 -05:00
onSecondarySubmit = { handleSecondarySubmit }
2018-07-27 12:57:24 -05:00
text = { spoilerText }
2018-08-18 13:53:46 -05:00
ref = { handleRefSpoilerText }
2018-07-27 12:57:24 -05:00
/ >
2017-12-24 00:16:45 -06:00
< ComposerTextarea
2018-01-05 20:23:06 -06:00
advancedOptions = { advancedOptions }
2018-01-03 14:36:21 -06:00
autoFocus = { ! showSearch && ! isMobile ( window . innerWidth , layout ) }
2017-12-24 00:16:45 -06:00
disabled = { isSubmitting }
intl = { intl }
2018-01-03 14:36:21 -06:00
onChange = { onChangeText }
onPaste = { onUpload }
onPickEmoji = { handleEmoji }
onSubmit = { handleSubmit }
2018-07-09 07:47:53 -05:00
onSecondarySubmit = { handleSecondarySubmit }
2018-01-03 14:36:21 -06:00
onSuggestionsClearRequested = { onClearSuggestions }
onSuggestionsFetchRequested = { onFetchSuggestions }
onSuggestionSelected = { handleSelect }
ref = { handleRefTextarea }
2017-12-24 00:16:45 -06:00
suggestions = { suggestions }
value = { text }
/ >
2019-03-06 05:30:11 -06:00
< div className = 'compose-form__modifiers' >
{ isUploading || media && media . size ? (
< ComposerUploadForm
intl = { intl }
media = { media }
onChangeDescription = { onChangeDescription }
onOpenFocalPointModal = { onOpenFocalPointModal }
onRemove = { onUndoUpload }
progress = { progress }
uploading = { isUploading }
handleRef = { handleRefUploadForm }
/ >
) : null }
{ ! ! poll && (
< ComposerPollForm / >
) }
< / d i v >
2017-12-24 00:16:45 -06:00
< ComposerOptions
acceptContentTypes = { acceptContentTypes }
2018-01-05 20:23:06 -06:00
advancedOptions = { advancedOptions }
2017-12-24 00:16:45 -06:00
disabled = { isSubmitting }
2019-03-06 05:30:11 -06:00
allowMedia = { ! poll && ( media ? media . size < 4 && ! media . some (
item => item . get ( 'type' ) === 'video'
) : true ) }
2018-01-14 16:13:24 -06:00
hasMedia = { media && ! ! media . size }
2019-03-06 05:30:11 -06:00
allowPoll = { ! ( media && ! ! media . size ) }
hasPoll = { ! ! poll }
2017-12-24 00:16:45 -06:00
intl = { intl }
2018-01-05 20:23:06 -06:00
onChangeAdvancedOption = { onChangeAdvancedOption }
2018-01-03 14:36:21 -06:00
onChangeSensitivity = { onChangeSensitivity }
onChangeVisibility = { onChangeVisibility }
2019-03-06 05:30:11 -06:00
onTogglePoll = { onTogglePoll }
2018-01-03 14:36:21 -06:00
onDoodleOpen = { onOpenDoodleModal }
onModalClose = { onCloseModal }
onModalOpen = { onOpenActionsModal }
2018-08-22 08:58:57 -05:00
onToggleSpoiler = { spoilersAlwaysOn ? null : onChangeSpoilerness }
2018-01-03 14:36:21 -06:00
onUpload = { onUpload }
2017-12-24 00:16:45 -06:00
privacy = { privacy }
resetFileKey = { resetFileKey }
2018-09-04 11:25:28 -05:00
sensitive = { sensitive || ( spoilersAlwaysOn && spoilerText && spoilerText . length > 0 ) }
spoiler = { spoilersAlwaysOn ? ( spoilerText && spoilerText . length > 0 ) : spoiler }
2017-12-24 00:16:45 -06:00
/ >
< ComposerPublisher
2018-01-14 16:13:24 -06:00
countText = { ` ${ spoilerText } ${ countableText ( text ) } ${ advancedOptions && advancedOptions . get ( 'do_not_federate' ) ? ' 👁️' : '' } ` }
2018-03-12 12:39:07 -05:00
disabled = { disabledButton }
2017-12-24 00:16:45 -06:00
intl = { intl }
2018-01-03 14:36:21 -06:00
onSecondarySubmit = { handleSecondarySubmit }
onSubmit = { handleSubmit }
2017-12-24 00:16:45 -06:00
privacy = { privacy }
sideArm = { sideArm }
/ >
< / d i v >
) ;
}
}
// Props.
Composer . propTypes = {
intl : PropTypes . object . isRequired ,
2017-12-26 18:54:28 -06:00
2018-01-03 14:36:21 -06:00
// State props.
acceptContentTypes : PropTypes . string ,
2018-01-05 20:23:06 -06:00
advancedOptions : ImmutablePropTypes . map ,
2018-01-03 14:36:21 -06:00
amUnlocked : PropTypes . bool ,
focusDate : PropTypes . instanceOf ( Date ) ,
2018-05-22 14:09:07 -05:00
caretPosition : PropTypes . number ,
2018-01-03 14:36:21 -06:00
isSubmitting : PropTypes . bool ,
2018-12-16 08:26:04 -06:00
isChangingUpload : PropTypes . bool ,
2018-01-03 14:36:21 -06:00
isUploading : PropTypes . bool ,
layout : PropTypes . string ,
media : ImmutablePropTypes . list ,
preselectDate : PropTypes . instanceOf ( Date ) ,
privacy : PropTypes . string ,
progress : PropTypes . number ,
2018-07-28 16:08:38 -05:00
inReplyTo : ImmutablePropTypes . map ,
2018-01-03 14:36:21 -06:00
resetFileKey : PropTypes . number ,
sideArm : PropTypes . string ,
sensitive : PropTypes . bool ,
showSearch : PropTypes . bool ,
spoiler : PropTypes . bool ,
spoilerText : PropTypes . string ,
suggestionToken : PropTypes . string ,
suggestions : ImmutablePropTypes . list ,
text : PropTypes . string ,
2018-08-29 08:26:24 -05:00
anyMedia : PropTypes . bool ,
spoilersAlwaysOn : PropTypes . bool ,
mediaDescriptionConfirmation : PropTypes . bool ,
2018-09-28 13:58:03 -05:00
preselectOnReply : PropTypes . bool ,
2018-01-03 14:36:21 -06:00
// Dispatch props.
onCancelReply : PropTypes . func ,
2018-01-05 20:23:06 -06:00
onChangeAdvancedOption : PropTypes . func ,
2018-01-03 14:36:21 -06:00
onChangeDescription : PropTypes . func ,
onChangeSensitivity : PropTypes . func ,
onChangeSpoilerText : PropTypes . func ,
onChangeSpoilerness : PropTypes . func ,
onChangeText : PropTypes . func ,
onChangeVisibility : PropTypes . func ,
onClearSuggestions : PropTypes . func ,
onCloseModal : PropTypes . func ,
onFetchSuggestions : PropTypes . func ,
onInsertEmoji : PropTypes . func ,
2018-01-05 20:30:06 -06:00
onMount : PropTypes . func ,
2018-01-03 14:36:21 -06:00
onOpenActionsModal : PropTypes . func ,
onOpenDoodleModal : PropTypes . func ,
onSelectSuggestion : PropTypes . func ,
onSubmit : PropTypes . func ,
onUndoUpload : PropTypes . func ,
2018-01-05 20:30:06 -06:00
onUnmount : PropTypes . func ,
2018-01-03 14:36:21 -06:00
onUpload : PropTypes . func ,
2018-08-29 08:26:24 -05:00
onMediaDescriptionConfirm : PropTypes . func ,
2017-12-27 16:28:41 -06:00
} ;
2018-12-13 09:50:37 -06:00
Composer . contextTypes = {
router : PropTypes . object ,
} ;
2017-12-26 18:54:28 -06:00
// Connecting and export.
export { Composer as WrappedComponent } ;
export default wrap ( Composer , mapStateToProps , mapDispatchToProps , true ) ;