diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index 31866d2233..a88dba1b13 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -61,7 +61,7 @@ export function replyCompose(status, router) { status: status, }); - if (!getState().getIn(['compose', 'mounted'])) { + if (router && !getState().getIn(['compose', 'mounted'])) { router.push('/statuses/new'); } }; @@ -118,6 +118,11 @@ export function submitCompose() { }).then(function (response) { dispatch(submitComposeSuccess({ ...response.data })); + // If the response has no data then we can't do anything else. + if (!response.data) { + return; + } + // To make the app more responsive, immediately get the status into the columns const insertOrRefresh = (timelineId, refreshAction) => { @@ -341,10 +346,11 @@ export function unmountCompose() { }; }; -export function toggleComposeAdvancedOption(option) { +export function changeComposeAdvancedOption(option, value) { return { + option, type: COMPOSE_ADVANCED_OPTIONS_CHANGE, - option: option, + value, }; } diff --git a/app/javascript/flavours/glitch/features/composer/index.js b/app/javascript/flavours/glitch/features/composer/index.js index 3582dedfe0..cae9bf9f2a 100644 --- a/app/javascript/flavours/glitch/features/composer/index.js +++ b/app/javascript/flavours/glitch/features/composer/index.js @@ -7,6 +7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { cancelReplyCompose, changeCompose, + changeComposeAdvancedOption, changeComposeSensitivity, changeComposeSpoilerText, changeComposeSpoilerness, @@ -18,7 +19,6 @@ import { mountCompose, selectComposeSuggestion, submitCompose, - toggleComposeAdvancedOption, undoUploadCompose, unmountCompose, uploadCompose, @@ -49,8 +49,8 @@ function mapStateToProps (state) { const inReplyTo = state.getIn(['compose', 'in_reply_to']); return { acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray().join(','), + advancedOptions: state.getIn(['compose', 'advanced_options']), amUnlocked: !state.getIn(['accounts', me, 'locked']), - doNotFederate: state.getIn(['compose', 'advanced_options', 'do_not_federate']), focusDate: state.getIn(['compose', 'focusDate']), isSubmitting: state.getIn(['compose', 'is_submitting']), isUploading: state.getIn(['compose', 'is_uploading']), @@ -76,6 +76,7 @@ function mapStateToProps (state) { // Dispatch mapping. const mapDispatchToProps = { onCancelReply: cancelReplyCompose, + onChangeAdvancedOption: changeComposeAdvancedOption, onChangeDescription: changeUploadCompose, onChangeSensitivity: changeComposeSensitivity, onChangeSpoilerText: changeComposeSpoilerText, @@ -91,7 +92,6 @@ const mapDispatchToProps = { onOpenDoodleModal: openModal.bind(null, 'DOODLE', { noEsc: true }), onSelectSuggestion: selectComposeSuggestion, onSubmit: submitCompose, - onToggleAdvancedOption: toggleComposeAdvancedOption, onUndoUpload: undoUploadCompose, onUnmount: unmountCompose, onUpload: uploadCompose, @@ -267,14 +267,15 @@ class Composer extends React.Component { } = this.handlers; const { acceptContentTypes, + advancedOptions, amUnlocked, - doNotFederate, intl, isSubmitting, isUploading, layout, media, onCancelReply, + onChangeAdvancedOption, onChangeDescription, onChangeSensitivity, onChangeSpoilerness, @@ -285,7 +286,6 @@ class Composer extends React.Component { onFetchSuggestions, onOpenActionsModal, onOpenDoodleModal, - onToggleAdvancedOption, onUndoUpload, onUpload, privacy, @@ -321,6 +321,7 @@ class Composer extends React.Component { /> ) : null} = 4 || media.some( item => item.get('type') === 'video' )} hasMedia={!!media.size} intl={intl} + onChangeAdvancedOption={onChangeAdvancedOption} onChangeSensitivity={onChangeSensitivity} onChangeVisibility={onChangeVisibility} onDoodleOpen={onOpenDoodleModal} onModalClose={onCloseModal} onModalOpen={onOpenActionsModal} - onToggleAdvancedOption={onToggleAdvancedOption} onToggleSpoiler={onChangeSpoilerness} onUpload={onUpload} privacy={privacy} @@ -368,7 +369,7 @@ class Composer extends React.Component { spoiler={spoiler} /> - , - name: 'do_not_federate', - on: doNotFederate, - text: , - }, - ]} - onChange={onToggleAdvancedOption} - onModalClose={onModalClose} - onModalOpen={onModalOpen} - title={intl.formatMessage(messages.advanced_options_icon_title)} - /> + {advancedOptions ? ( + !!value)} + disabled={disabled} + icon='ellipsis-h' + items={[ + { + meta: , + name: 'do_not_federate', + on: advancedOptions.get('do_not_federate'), + text: , + }, + { + meta: , + name: 'threaded_mode', + on: advancedOptions.get('threaded_mode'), + text: , + }, + ]} + onChange={onChangeAdvancedOption} + onModalClose={onModalClose} + onModalOpen={onModalOpen} + title={intl.formatMessage(messages.advanced_options_icon_title)} + /> + ) : null} ); } @@ -309,17 +326,17 @@ export default class ComposerOptions extends React.PureComponent { // Props. ComposerOptions.propTypes = { acceptContentTypes: PropTypes.string, + advancedOptions: ImmutablePropTypes.map, disabled: PropTypes.bool, - doNotFederate: PropTypes.bool, full: PropTypes.bool, hasMedia: PropTypes.bool, intl: PropTypes.object.isRequired, + onChangeAdvancedOption: PropTypes.func, onChangeSensitivity: PropTypes.func, onChangeVisibility: PropTypes.func, onDoodleOpen: PropTypes.func, onModalClose: PropTypes.func, onModalOpen: PropTypes.func, - onToggleAdvancedOption: PropTypes.func, onToggleSpoiler: PropTypes.func, onUpload: PropTypes.func, privacy: PropTypes.string, diff --git a/app/javascript/flavours/glitch/features/composer/textarea/icons/index.js b/app/javascript/flavours/glitch/features/composer/textarea/icons/index.js new file mode 100644 index 0000000000..049cdd5cdf --- /dev/null +++ b/app/javascript/flavours/glitch/features/composer/textarea/icons/index.js @@ -0,0 +1,60 @@ +// Package imports. +import PropTypes from 'prop-types'; +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { defineMessages } from 'react-intl'; + +// Components. +import Icon from 'flavours/glitch/components/icon'; + +// Messages. +const messages = defineMessages({ + localOnly: { + defaultMessage: 'This post is local-only', + id: 'advanced_options.local-only.tooltip', + }, + threadedMode: { + defaultMessage: 'Threaded mode enabled', + id: 'advanced_options.threaded_mode.tooltip', + }, +}); + +// We use an array of tuples here instead of an object because it +// preserves order. +const iconMap = [ + ['do_not_federate', 'home', messages.localOnly], + ['threaded_mode', 'comments', messages.threadedMode], +]; + +// The component. +export default function ComposerTextareaIcons ({ + advancedOptions, + intl, +}) { + + // The result. We just map every active option to its icon. + return ( +
+ {advancedOptions ? iconMap.map( + ([key, icon, message]) => advancedOptions.get(key) ? ( + + + + ) : null + ) : null} +
+ ); +} + +// Props. +ComposerTextareaIcons.propTypes = { + advancedOptions: ImmutablePropTypes.map, + intl: PropTypes.object.isRequired, +}; diff --git a/app/javascript/flavours/glitch/features/composer/textarea/index.js b/app/javascript/flavours/glitch/features/composer/textarea/index.js index 2e0b3e3d73..0f5fd4d4d7 100644 --- a/app/javascript/flavours/glitch/features/composer/textarea/index.js +++ b/app/javascript/flavours/glitch/features/composer/textarea/index.js @@ -10,6 +10,7 @@ import Textarea from 'react-textarea-autosize'; // Components. import EmojiPicker from 'flavours/glitch/features/emoji_picker'; +import ComposerTextareaIcons from './icons'; import ComposerTextareaSuggestions from './suggestions'; // Utils. @@ -232,6 +233,7 @@ export default class ComposerTextarea extends React.Component { handleRefTextarea, } = this.handlers; const { + advancedOptions, autoFocus, disabled, intl, @@ -249,6 +251,10 @@ export default class ComposerTextarea extends React.Component {