diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js index 2b9d2dca67..88dce5098c 100644 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js @@ -1,23 +1,19 @@ import React from 'react'; -import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import ReplyIndicatorContainer from '../containers/reply_indicator_container'; +import AutosuggestTextarea from '../../../components/autosuggest_textarea'; import { defineMessages, injectIntl } from 'react-intl'; +import EmojiPicker from 'flavours/glitch/features/emoji_picker'; +import PollFormContainer from '../containers/poll_form_container'; +import UploadFormContainer from '../containers/upload_form_container'; +import WarningContainer from '../containers/warning_container'; +import { isMobile } from 'flavours/glitch/util/is_mobile'; import ImmutablePureComponent from 'react-immutable-pure-component'; - -// Components. +import { countableText } from 'flavours/glitch/util/counter'; import OptionsContainer from '../containers/options_container'; import Publisher from './publisher'; import TextareaIcons from './textarea_icons'; -import UploadFormContainer from '../containers/upload_form_container'; -import PollFormContainer from '../containers/poll_form_container'; -import WarningContainer from '../containers/warning_container'; -import ReplyIndicatorContainer from '../containers/reply_indicator_container'; -import EmojiPicker from 'flavours/glitch/features/emoji_picker'; -import AutosuggestTextarea from '../../../components/autosuggest_textarea'; - -// Utils. -import { countableText } from 'flavours/glitch/util/counter'; -import { isMobile } from 'flavours/glitch/util/is_mobile'; const messages = defineMessages({ placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' }, @@ -37,84 +33,50 @@ class ComposeForm extends ImmutablePureComponent { static propTypes = { intl: PropTypes.object.isRequired, - - // State props. - advancedOptions: ImmutablePropTypes.map, - amUnlocked: PropTypes.bool, + text: PropTypes.string, + suggestions: ImmutablePropTypes.list, + spoiler: PropTypes.bool, + privacy: PropTypes.string, + spoilerText: PropTypes.string, focusDate: PropTypes.instanceOf(Date), caretPosition: PropTypes.number, + preselectDate: PropTypes.instanceOf(Date), isSubmitting: PropTypes.bool, isChangingUpload: PropTypes.bool, isUploading: PropTypes.bool, + onChange: PropTypes.func, + onSubmit: PropTypes.func, + onClearSuggestions: PropTypes.func, + onFetchSuggestions: PropTypes.func, + onSuggestionSelected: PropTypes.func, + onChangeSpoilerText: PropTypes.func, + onPaste: PropTypes.func, + onPickEmoji: PropTypes.func, + showSearch: PropTypes.bool, + anyMedia: PropTypes.bool, + + advancedOptions: ImmutablePropTypes.map, layout: PropTypes.string, media: ImmutablePropTypes.list, - preselectDate: PropTypes.instanceOf(Date), - privacy: PropTypes.string, sideArm: PropTypes.string, sensitive: PropTypes.bool, - showSearch: PropTypes.bool, - spoiler: PropTypes.bool, - spoilerText: PropTypes.string, - suggestionToken: PropTypes.string, - suggestions: ImmutablePropTypes.list, - text: PropTypes.string, - anyMedia: PropTypes.bool, spoilersAlwaysOn: PropTypes.bool, mediaDescriptionConfirmation: PropTypes.bool, preselectOnReply: PropTypes.bool, - - // Dispatch props. - onChangeSpoilerText: PropTypes.func, onChangeSpoilerness: PropTypes.func, - onChangeText: PropTypes.func, onChangeVisibility: PropTypes.func, - onClearSuggestions: PropTypes.func, - onFetchSuggestions: PropTypes.func, - onInsertEmoji: PropTypes.func, onMount: PropTypes.func, - onSelectSuggestion: PropTypes.func, - onSubmit: PropTypes.func, onUnmount: PropTypes.func, - onUpload: PropTypes.func, + onPaste: PropTypes.func, onMediaDescriptionConfirm: PropTypes.func, }; - // Changes the text value of the spoiler. - handleChangeSpoiler = ({ target: { value } }) => { - const { onChangeSpoilerText } = this.props; - if (onChangeSpoilerText) { - onChangeSpoilerText(value); - } - } + static defaultProps = { + showSearch: false, + }; - // Inserts an emoji at the caret. - handleEmoji = (data) => { - const { textarea: { selectionStart } } = this; - const { onInsertEmoji } = this.props; - if (onInsertEmoji) { - onInsertEmoji(selectionStart, data); - } - } - - // Handles the secondary submit button. - handleSecondarySubmit = () => { - const { handleSubmit } = this.handlers; - const { - onChangeVisibility, - sideArm, - } = this.props; - if (sideArm !== 'none' && onChangeVisibility) { - onChangeVisibility(sideArm); - } - handleSubmit(); - } - - // Selects a suggestion from the autofill. - handleSelect = (tokenStart, token, value) => { - const { onSelectSuggestion } = this.props; - if (onSelectSuggestion) { - onSelectSuggestion(tokenStart, token, value); - } + handleChange = (e) => { + this.props.onChange(e.target.value); } handleKeyDown = ({ ctrlKey, keyCode, metaKey, altKey }) => { @@ -129,18 +91,10 @@ class ComposeForm extends ImmutablePureComponent { } } - // When the escape key is released, we focus the UI. - handleKeyUp = ({ key }) => { - if (key === 'Escape') { - document.querySelector('.ui').parentElement.focus(); - } - } - - // Submits the status. handleSubmit = () => { const { textarea: { value }, uploadForm } = this; const { - onChangeText, + onChange, onSubmit, isSubmitting, isChangingUpload, @@ -154,8 +108,8 @@ class ComposeForm extends ImmutablePureComponent { // If something changes inside the textarea, then we update the // state before submitting. - if (onChangeText && text !== value) { - onChangeText(value); + if (onChange && text !== value) { + onChange(value); } // Submit disabled: @@ -178,6 +132,48 @@ class ComposeForm extends ImmutablePureComponent { } } + // Changes the text value of the spoiler. + handleChangeSpoiler = ({ target: { value } }) => { + const { onChangeSpoilerText } = this.props; + if (onChangeSpoilerText) { + onChangeSpoilerText(value); + } + } + + // Inserts an emoji at the caret. + handleEmoji = (data) => { + const { textarea: { selectionStart } } = this; + const { onPickEmoji } = this.props; + if (onPickEmoji) { + onPickEmoji(selectionStart, data); + } + } + + // Handles the secondary submit button. + handleSecondarySubmit = () => { + const { handleSubmit } = this.handlers; + const { + onChangeVisibility, + sideArm, + } = this.props; + if (sideArm !== 'none' && onChangeVisibility) { + onChangeVisibility(sideArm); + } + handleSubmit(); + } + + // Selects a suggestion from the autofill. + onSuggestionSelected = (tokenStart, token, value) => { + this.props.onSuggestionSelected(tokenStart, token, value); + } + + // When the escape key is released, we focus the UI. + handleKeyUp = ({ key }) => { + if (key === 'Escape') { + document.querySelector('.ui').parentElement.focus(); + } + } + // Sets a reference to the textarea. setAutosuggestTextarea = (textareaComponent) => { if (textareaComponent) { @@ -265,9 +261,6 @@ class ComposeForm extends ImmutablePureComponent { } } - handleChange = (e) => { - this.props.onChangeText(e.target.value); - } render () { const { @@ -279,7 +272,6 @@ class ComposeForm extends ImmutablePureComponent { } = this; const { advancedOptions, - amUnlocked, anyMedia, intl, isSubmitting, @@ -288,11 +280,10 @@ class ComposeForm extends ImmutablePureComponent { layout, media, onChangeSpoilerness, - onChangeText, onChangeVisibility, onClearSuggestions, onFetchSuggestions, - onUpload, + onPaste, privacy, sensitive, showSearch, @@ -343,8 +334,8 @@ class ComposeForm extends ImmutablePureComponent { onKeyDown={this.handleKeyDown} onSuggestionsFetchRequested={onFetchSuggestions} onSuggestionsClearRequested={onClearSuggestions} - onSuggestionSelected={this.handleSelect} - onPaste={onUpload} + onSuggestionSelected={this.onSuggestionSelected} + onPaste={onPaste} autoFocus={!showSearch && !isMobile(window.innerWidth, layout)} /> @@ -361,7 +352,7 @@ class ComposeForm extends ImmutablePureComponent { disabled={isSubmitting} onChangeVisibility={onChangeVisibility} onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness} - onUpload={onUpload} + onPaste={onPaste} privacy={privacy} sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)} spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler} diff --git a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js index f4161aa8fb..5458e0919e 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js @@ -1,4 +1,5 @@ import { connect } from 'react-redux'; +import { defineMessages } from 'react-intl'; import ComposeForm from '../components/compose_form'; import { changeCompose, @@ -20,7 +21,6 @@ import { import { changeLocalSetting } from 'flavours/glitch/actions/local_settings'; import { privacyPreference } from 'flavours/glitch/util/privacy_preference'; -import { me } from 'flavours/glitch/util/initial_state'; const messages = defineMessages({ missingDescriptionMessage: { id: 'confirmations.missing_media_description.message', @@ -28,7 +28,6 @@ const messages = defineMessages({ missingDescriptionConfirm: { id: 'confirmations.missing_media_description.confirm', defaultMessage: 'Send anyway' }, }); -import { defineMessages } from 'react-intl'; // State mapping. function mapStateToProps (state) { @@ -49,7 +48,6 @@ function mapStateToProps (state) { sideArmPrivacy = sideArmPrivacy || sideArmBasePrivacy; return { advancedOptions: state.getIn(['compose', 'advanced_options']), - amUnlocked: !state.getIn(['accounts', me, 'locked']), focusDate: state.getIn(['compose', 'focusDate']), caretPosition: state.getIn(['compose', 'caretPosition']), isSubmitting: state.getIn(['compose', 'is_submitting']), @@ -64,7 +62,6 @@ function mapStateToProps (state) { showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), spoiler: spoilersAlwaysOn || state.getIn(['compose', 'spoiler']), spoilerText: state.getIn(['compose', 'spoiler_text']), - suggestionToken: state.getIn(['compose', 'suggestion_token']), suggestions: state.getIn(['compose', 'suggestions']), text: state.getIn(['compose', 'text']), anyMedia: state.getIn(['compose', 'media_attachments']).size > 0, @@ -76,33 +73,55 @@ function mapStateToProps (state) { // Dispatch mapping. const mapDispatchToProps = (dispatch, { intl }) => ({ - onChangeSpoilerText(text) { - dispatch(changeComposeSpoilerText(text)); - }, - onChangeSpoilerness() { - dispatch(changeComposeSpoilerness()); - }, - onChangeText(text) { + + onChange(text) { dispatch(changeCompose(text)); }, - onChangeVisibility(value) { - dispatch(changeComposeVisibility(value)); + + onSubmit(routerHistory) { + dispatch(submitCompose(routerHistory)); }, + onClearSuggestions() { dispatch(clearComposeSuggestions()); }, + onFetchSuggestions(token) { dispatch(fetchComposeSuggestions(token)); }, - onInsertEmoji(position, emoji) { + + onSuggestionSelected(position, token, suggestion) { + dispatch(selectComposeSuggestion(position, token, suggestion)); + }, + + onChangeSpoilerText(text) { + dispatch(changeComposeSpoilerText(text)); + }, + + onPaste(files) { + dispatch(uploadCompose(files)); + }, + + onPickEmoji(position, emoji) { dispatch(insertEmojiCompose(position, emoji)); }, + + onChangeSpoilerness() { + dispatch(changeComposeSpoilerness()); + }, + + onChangeVisibility(value) { + dispatch(changeComposeVisibility(value)); + }, + onMount() { dispatch(mountCompose()); }, - onSelectSuggestion(position, token, suggestion) { - dispatch(selectComposeSuggestion(position, token, suggestion)); + + onUnmount() { + dispatch(unmountCompose()); }, + onMediaDescriptionConfirm(routerHistory) { dispatch(openModal('CONFIRM', { message: intl.formatMessage(messages.missingDescriptionMessage), @@ -111,15 +130,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_missing_media_description'], false)), })); }, - onSubmit(routerHistory) { - dispatch(submitCompose(routerHistory)); - }, - onUnmount() { - dispatch(unmountCompose()); - }, - onUpload(files) { - dispatch(uploadCompose(files)); - }, + }); export default connect(mapStateToProps, mapDispatchToProps)(ComposeForm); diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js index 01e7d1906f..a7795a04d9 100644 --- a/app/javascript/flavours/glitch/features/compose/index.js +++ b/app/javascript/flavours/glitch/features/compose/index.js @@ -1,63 +1,46 @@ -// Package imports. import React from 'react'; -import { connect } from 'react-redux'; +import ComposeFormContainer from './containers/compose_form_container'; +import NavigationContainer from './containers/navigation_container'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { connect } from 'react-redux'; import { injectIntl, defineMessages } from 'react-intl'; import classNames from 'classnames'; - -// Actions. -import { cycleElefriendCompose } from 'flavours/glitch/actions/compose'; - -// Components. -import ComposeFormContainer from './containers/compose_form_container'; -import HeaderContainer from './containers/header_container'; import SearchContainer from './containers/search_container'; -import SearchResultsContainer from './containers/search_results_container'; -import NavigationContainer from './containers/navigation_container'; -import spring from 'react-motion/lib/spring'; - -// Utils. -import { me, mascot } from 'flavours/glitch/util/initial_state'; import Motion from 'flavours/glitch/util/optional_motion'; +import spring from 'react-motion/lib/spring'; +import SearchResultsContainer from './containers/search_results_container'; +import { me, mascot } from 'flavours/glitch/util/initial_state'; +import { cycleElefriendCompose } from 'flavours/glitch/actions/compose'; +import HeaderContainer from './containers/header_container'; -// Messages. const messages = defineMessages({ compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' }, }); -// State mapping. const mapStateToProps = (state, ownProps) => ({ elefriend: state.getIn(['compose', 'elefriend']), showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage, }); -// Dispatch mapping. const mapDispatchToProps = (dispatch, { intl }) => ({ onClickElefriend () { dispatch(cycleElefriendCompose()); }, }); -// The component. export default @connect(mapStateToProps, mapDispatchToProps) @injectIntl class Compose extends React.PureComponent { static propTypes = { - intl: PropTypes.object.isRequired, - isSearchPage: PropTypes.bool, multiColumn: PropTypes.bool, showSearch: PropTypes.bool, - - // State props. + isSearchPage: PropTypes.bool, elefriend: PropTypes.number, - unreadNotifications: PropTypes.number, - - // Dispatch props. onClickElefriend: PropTypes.func, + intl: PropTypes.object.isRequired, }; - // Rendering. render () { const { elefriend, @@ -69,7 +52,6 @@ class Compose extends React.PureComponent { } = this.props; const computedClass = classNames('drawer', `mbstobon-${elefriend}`); - // The result. return (