diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index 707c21c380..07f60b9270 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -251,9 +251,9 @@ export function submitCompose(routerHistory) { return; } - // To make the app more responsive, immediately get the status into the columns - - const insertIfOnline = (timelineId) => { + // To make the app more responsive, immediately push the status + // into the columns + const insertIfOnline = timelineId => { const timeline = getState().getIn(['timelines', timelineId]); if (timeline && timeline.get('items').size > 0 && timeline.getIn(['items', 0]) !== null && timeline.get('online')) { @@ -662,8 +662,9 @@ export function selectComposeSuggestion(position, token, suggestion, path) { return (dispatch, getState) => { let completion; if (suggestion.type === 'emoji') { - dispatch(useEmoji(suggestion)); completion = suggestion.native || suggestion.colons; + + dispatch(useEmoji(suggestion)); } else if (suggestion.type === 'hashtag') { completion = `#${suggestion.name}`; } else if (suggestion.type === 'account') { diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx b/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx index 47b070da24..4ec2b81d11 100644 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx @@ -56,14 +56,14 @@ class ComposeForm extends ImmutablePureComponent { isChangingUpload: PropTypes.bool, isEditing: 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, + onChange: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + onClearSuggestions: PropTypes.func.isRequired, + onFetchSuggestions: PropTypes.func.isRequired, + onSuggestionSelected: PropTypes.func.isRequired, + onChangeSpoilerText: PropTypes.func.isRequired, + onPaste: PropTypes.func.isRequired, + onPickEmoji: PropTypes.func.isRequired, showSearch: PropTypes.bool, anyMedia: PropTypes.bool, isInReply: PropTypes.bool, @@ -77,9 +77,9 @@ class ComposeForm extends ImmutablePureComponent { spoilersAlwaysOn: PropTypes.bool, mediaDescriptionConfirmation: PropTypes.bool, preselectOnReply: PropTypes.bool, - onChangeSpoilerness: PropTypes.func, - onChangeVisibility: PropTypes.func, - onMediaDescriptionConfirm: PropTypes.func, + onChangeSpoilerness: PropTypes.func.isRequired, + onChangeVisibility: PropTypes.func.isRequired, + onMediaDescriptionConfirm: PropTypes.func.isRequired, ...WithOptionalRouterPropTypes }; @@ -100,6 +100,16 @@ class ComposeForm extends ImmutablePureComponent { this.props.onChange(e.target.value); }; + handleKeyDown = (e) => { + if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { + this.handleSubmit(); + } + + if (e.keyCode === 13 && e.altKey) { + this.handleSecondarySubmit(); + } + }; + getFulltextForCharacterCounting = () => { return [ this.props.spoiler? this.props.spoilerText: '', @@ -116,14 +126,6 @@ class ComposeForm extends ImmutablePureComponent { }; handleSubmit = (overriddenVisibility = null) => { - const { - onSubmit, - media, - mediaDescriptionConfirmation, - onMediaDescriptionConfirm, - onChangeVisibility, - } = this.props; - if (this.props.text !== this.textareaRef.current.value) { // Something changed the text inside the textarea (e.g. browser extensions like Grammarly) // Update the state to match the current text @@ -135,35 +137,14 @@ class ComposeForm extends ImmutablePureComponent { } // Submit unless there are media with missing descriptions - if (mediaDescriptionConfirmation && onMediaDescriptionConfirm && media && media.some(item => !item.get('description'))) { - const firstWithoutDescription = media.find(item => !item.get('description')); - onMediaDescriptionConfirm(this.props.history || null, firstWithoutDescription.get('id'), overriddenVisibility); - } else if (onSubmit) { - if (onChangeVisibility && overriddenVisibility) { - onChangeVisibility(overriddenVisibility); + if (this.props.mediaDescriptionConfirmation && this.props.media && this.props.media.some(item => !item.get('description'))) { + const firstWithoutDescription = this.props.media.find(item => !item.get('description')); + this.props.onMediaDescriptionConfirm(this.props.history || null, firstWithoutDescription.get('id'), overriddenVisibility); + } else { + if (overriddenVisibility) { + this.props.onChangeVisibility(overriddenVisibility); } - onSubmit(this.props.history || null); - } - }; - - // Changes the text value of the spoiler. - handleChangeSpoiler = ({ target: { value } }) => { - const { onChangeSpoilerText } = this.props; - if (onChangeSpoilerText) { - onChangeSpoilerText(value); - } - }; - - setRef = c => { - this.composeForm = c; - }; - - // Inserts an emoji at the caret. - handleEmojiPick = (data) => { - const position = this.textareaRef.current.selectionStart; - - if (this.props.onPickEmoji) { - this.props.onPickEmoji(position, data); + this.props.onSubmit(this.props.history || null); } }; @@ -175,30 +156,24 @@ class ComposeForm extends ImmutablePureComponent { this.handleSubmit(sideArm === 'none' ? null : sideArm); }; - // Selects a suggestion from the autofill. - handleSuggestionSelected = (tokenStart, token, value) => { + onSuggestionsClearRequested = () => { + this.props.onClearSuggestions(); + }; + + onSuggestionsFetchRequested = (token) => { + this.props.onFetchSuggestions(token); + }; + + onSuggestionSelected = (tokenStart, token, value) => { this.props.onSuggestionSelected(tokenStart, token, value, ['text']); }; - handleSpoilerSuggestionSelected = (tokenStart, token, value) => { + onSpoilerSuggestionSelected = (tokenStart, token, value) => { this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text']); }; - handleKeyDown = (e) => { - if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { - this.handleSubmit(); - } - - if (e.keyCode === 13 && e.altKey) { - this.handleSecondarySubmit(); - } - }; - - // Sets a reference to the CW field. - handleRefSpoilerText = (spoilerComponent) => { - if (spoilerComponent) { - this.spoilerText = spoilerComponent.input; - } + handleChangeSpoilerText = (e) => { + this.props.onChangeSpoilerText(e.target.value); }; handleFocus = () => { @@ -222,120 +197,99 @@ class ComposeForm extends ImmutablePureComponent { this._updateFocusAndSelection(prevProps); } - // 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. _updateFocusAndSelection = (prevProps) => { - const { - spoilerText, - } = this; - const { - focusDate, - caretPosition, - isSubmitting, - preselectDate, - text, - preselectOnReply, - singleColumn, - } = this.props; - let selectionEnd, selectionStart; + // 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. + if (this.props.focusDate && this.props.focusDate !== prevProps.focusDate) { + let selectionEnd, selectionStart; - // Caret/selection handling. - if (focusDate !== prevProps.focusDate) { - switch (true) { - case preselectDate !== prevProps.preselectDate && this.props.isInReply && preselectOnReply: - selectionStart = text.search(/\s/) + 1; - selectionEnd = text.length; - break; - case !isNaN(caretPosition) && caretPosition !== null: - selectionStart = selectionEnd = caretPosition; - break; - default: - selectionStart = selectionEnd = text.length; - } - if (this.textareaRef.current) { - // Because of the wicg-inert polyfill, the activeElement may not be - // immediately selectable, we have to wait for observers to run, as - // described in https://github.com/WICG/inert#performance-and-gotchas - Promise.resolve().then(() => { - this.textareaRef.current.setSelectionRange(selectionStart, selectionEnd); - this.textareaRef.current.focus(); - if (!singleColumn) this.textareaRef.current.scrollIntoView(); - this.setState({ highlighted: true }); - this.timeout = setTimeout(() => this.setState({ highlighted: false }), 700); - }).catch(console.error); + if (this.props.preselectDate !== prevProps.preselectDate && this.props.isInReply && this.props.preselectOnReply) { + selectionEnd = this.props.text.length; + selectionStart = this.props.text.search(/\s/) + 1; + } else if (typeof this.props.caretPosition === 'number') { + selectionStart = this.props.caretPosition; + selectionEnd = this.props.caretPosition; + } else { + selectionEnd = this.props.text.length; + selectionStart = selectionEnd; } - // Refocuses the textarea after submitting. - } else if (this.textareaRef.current && prevProps.isSubmitting && !isSubmitting) { + // Because of the wicg-inert polyfill, the activeElement may not be + // immediately selectable, we have to wait for observers to run, as + // described in https://github.com/WICG/inert#performance-and-gotchas + Promise.resolve().then(() => { + this.textareaRef.current.setSelectionRange(selectionStart, selectionEnd); + this.textareaRef.current.focus(); + if (!this.props.singleColumn) this.textareaRef.current.scrollIntoView(); + this.setState({ highlighted: true }); + this.timeout = setTimeout(() => this.setState({ highlighted: false }), 700); + }).catch(console.error); + } else if(prevProps.isSubmitting && !this.props.isSubmitting) { this.textareaRef.current.focus(); } else if (this.props.spoiler !== prevProps.spoiler) { if (this.props.spoiler) { - if (spoilerText) { - spoilerText.focus(); - } - } else { - if (this.textareaRef.current) { - this.textareaRef.current.focus(); - } + this.spoilerText.input.focus(); + } else if (prevProps.spoiler) { + this.textareaRef.current.focus(); } } }; + setSpoilerText = (c) => { + this.spoilerText = c; + }; + + setRef = c => { + this.composeForm = c; + }; + + handleEmojiPick = (data) => { + const position = this.textareaRef.current.selectionStart; + + this.props.onPickEmoji(position, data); + }; render () { const { - handleEmojiPick, - handleSecondarySubmit, - handleSubmit, - } = this; - const { - advancedOptions, intl, + advancedOptions, isSubmitting, layout, onChangeSpoilerness, - onClearSuggestions, - onFetchSuggestions, onPaste, privacy, sensitive, showSearch, sideArm, - spoiler, - spoilerText, - suggestions, spoilersAlwaysOn, isEditing, } = this.props; const { highlighted } = this.state; - - const countText = this.getFulltextForCharacterCounting(); + const disabled = this.props.isSubmitting; return ( -
+
-
+
- +
0)} - spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler} + sensitive={sensitive || (spoilersAlwaysOn && this.props.spoilerText && this.props.spoilerText.length > 0)} + spoiler={spoilersAlwaysOn ? (this.props.spoilerText && this.props.spoilerText.length > 0) : this.props.spoiler} />
- +
-
+ ); } diff --git a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.jsx b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.jsx index 14270a7d09..85f97a5c91 100644 --- a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.jsx @@ -30,9 +30,11 @@ export default class NavigationBar extends ImmutablePureComponent {
- - @{username} - + + + @{username} + + { profileLink !== undefined && ( { const { onUpload } = this.props; - if (files.length && onUpload) { + if (files.length) { onUpload(files); } }; - // Handles attachment clicks. handleClickAttach = (name) => { const { fileElement } = this; const { onDoodleOpen } = this.props; - // We switch over the name of the option. switch (name) { case 'upload': if (fileElement) { @@ -167,14 +156,11 @@ class ComposerOptions extends ImmutablePureComponent { } return; case 'doodle': - if (onDoodleOpen) { - onDoodleOpen(); - } + onDoodleOpen(); return; } }; - // Handles a ref to the file input. handleRefFileElement = (fileElement) => { this.fileElement = fileElement; }; @@ -186,7 +172,6 @@ class ComposerOptions extends ImmutablePureComponent { return ; }; - // Rendering. render () { const { acceptContentTypes, @@ -290,7 +275,7 @@ class ComposerOptions extends ImmutablePureComponent { {onToggleSpoiler && ( - {sideArm && !isEditing && sideArm !== 'none' ? ( +
+ {sideArm && !isEditing && sideArm !== 'none' && (
- ) : null} + )}
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 adc68840b3..9e0c62be00 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 @@ -37,9 +37,7 @@ const messages = defineMessages({ }, }); -// State mapping. -function mapStateToProps (state) { - const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']); +const sideArmPrivacy = state => { const inReplyTo = state.getIn(['compose', 'in_reply_to']); const replyPrivacy = inReplyTo ? state.getIn(['statuses', inReplyTo, 'visibility']) : null; const sideArmBasePrivacy = state.getIn(['local_settings', 'side_arm']); @@ -53,67 +51,67 @@ function mapStateToProps (state) { sideArmPrivacy = sideArmRestrictedPrivacy; break; } - sideArmPrivacy = sideArmPrivacy || sideArmBasePrivacy; - return { - advancedOptions: state.getIn(['compose', 'advanced_options']), - focusDate: state.getIn(['compose', 'focusDate']), - caretPosition: state.getIn(['compose', 'caretPosition']), - isSubmitting: state.getIn(['compose', 'is_submitting']), - isEditing: state.getIn(['compose', 'id']) !== null, - isChangingUpload: state.getIn(['compose', 'is_changing_upload']), - isUploading: state.getIn(['compose', 'is_uploading']), - layout: state.getIn(['local_settings', 'layout']), - media: state.getIn(['compose', 'media_attachments']), - preselectDate: state.getIn(['compose', 'preselectDate']), - privacy: state.getIn(['compose', 'privacy']), - sideArm: sideArmPrivacy, - sensitive: state.getIn(['compose', 'sensitive']), - showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), - spoiler: spoilersAlwaysOn || state.getIn(['compose', 'spoiler']), - spoilerText: state.getIn(['compose', 'spoiler_text']), - suggestions: state.getIn(['compose', 'suggestions']), - text: state.getIn(['compose', 'text']), - anyMedia: state.getIn(['compose', 'media_attachments']).size > 0, - spoilersAlwaysOn: spoilersAlwaysOn, - mediaDescriptionConfirmation: state.getIn(['local_settings', 'confirm_missing_media_description']), - preselectOnReply: state.getIn(['local_settings', 'preselect_on_reply']), - isInReply: state.getIn(['compose', 'in_reply_to']) !== null, - lang: state.getIn(['compose', 'language']), - }; -} + return sideArmPrivacy || sideArmBasePrivacy; +}; + +const mapStateToProps = state => ({ + text: state.getIn(['compose', 'text']), + suggestions: state.getIn(['compose', 'suggestions']), + spoiler: state.getIn(['local_settings', 'always_show_spoilers_field']) || state.getIn(['compose', 'spoiler']), + spoilerText: state.getIn(['compose', 'spoiler_text']), + privacy: state.getIn(['compose', 'privacy']), + focusDate: state.getIn(['compose', 'focusDate']), + caretPosition: state.getIn(['compose', 'caretPosition']), + preselectDate: state.getIn(['compose', 'preselectDate']), + isSubmitting: state.getIn(['compose', 'is_submitting']), + isEditing: state.getIn(['compose', 'id']) !== null, + isChangingUpload: state.getIn(['compose', 'is_changing_upload']), + isUploading: state.getIn(['compose', 'is_uploading']), + anyMedia: state.getIn(['compose', 'media_attachments']).size > 0, + isInReply: state.getIn(['compose', 'in_reply_to']) !== null, + lang: state.getIn(['compose', 'language']), + advancedOptions: state.getIn(['compose', 'advanced_options']), + layout: state.getIn(['local_settings', 'layout']), + media: state.getIn(['compose', 'media_attachments']), + sideArm: sideArmPrivacy(state), + sensitive: state.getIn(['compose', 'sensitive']), + showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), + spoilersAlwaysOn: state.getIn(['local_settings', 'always_show_spoilers_field']), + mediaDescriptionConfirmation: state.getIn(['local_settings', 'confirm_missing_media_description']), + preselectOnReply: state.getIn(['local_settings', 'preselect_on_reply']), +}); -// Dispatch mapping. const mapDispatchToProps = (dispatch, { intl }) => ({ - onChange(text) { + onChange (text) { dispatch(changeCompose(text)); }, - onSubmit(routerHistory) { - dispatch(submitCompose(routerHistory)); + onSubmit (router) { + dispatch(submitCompose(router)); }, - onClearSuggestions() { + onClearSuggestions () { dispatch(clearComposeSuggestions()); }, - onFetchSuggestions(token) { + onFetchSuggestions (token) { dispatch(fetchComposeSuggestions(token)); }, - onSuggestionSelected(position, token, suggestion, path) { + onSuggestionSelected (position, token, suggestion, path) { dispatch(selectComposeSuggestion(position, token, suggestion, path)); }, - onChangeSpoilerText(text) { + onChangeSpoilerText (text) { dispatch(changeComposeSpoilerText(text)); }, - onPaste(files) { + onPaste (files) { dispatch(uploadCompose(files)); }, - onPickEmoji(position, emoji) { + onPickEmoji (position, emoji) { dispatch(insertEmojiCompose(position, emoji)); },