diff --git a/app/soapbox/actions/compose.js b/app/soapbox/actions/compose.js index bd91daaae..6c3057436 100644 --- a/app/soapbox/actions/compose.js +++ b/app/soapbox/actions/compose.js @@ -164,6 +164,34 @@ export function submitCompose(routerHistory, group) { return function(dispatch, getState) { if (!isLoggedIn(getState)) return; + function onModalSubmitCompose() { + dispatch(submitComposeRequest()); + dispatch(closeModal()); + + api(getState).post('/api/v1/statuses', { + status, + in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), + media_ids: media.map(item => item.get('id')), + sensitive: getState().getIn(['compose', 'sensitive']), + spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''), + visibility: getState().getIn(['compose', 'privacy']), + content_type: getState().getIn(['compose', 'content_type']), + poll: getState().getIn(['compose', 'poll'], null), + scheduled_at: getState().getIn(['compose', 'schedule'], null), + }, { + headers: { + 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), + }, + }).then(function(response) { + if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) { + routerHistory.push('/messages'); + } + handleComposeSubmit(dispatch, getState, response, status); + }).catch(function(error) { + dispatch(submitComposeFail(error)); + }); + } + const status = getState().getIn(['compose', 'text'], ''); const media = getState().getIn(['compose', 'media_attachments']); @@ -171,31 +199,13 @@ export function submitCompose(routerHistory, group) { return; } - dispatch(submitComposeRequest()); - dispatch(closeModal()); + const missingDescriptionModal = getSettings(getState()).get('missingDescriptionModal'); - api(getState).post('/api/v1/statuses', { - status, - in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), - media_ids: media.map(item => item.get('id')), - sensitive: getState().getIn(['compose', 'sensitive']), - spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''), - visibility: getState().getIn(['compose', 'privacy']), - content_type: getState().getIn(['compose', 'content_type']), - poll: getState().getIn(['compose', 'poll'], null), - scheduled_at: getState().getIn(['compose', 'schedule'], null), - }, { - headers: { - 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), - }, - }).then(function(response) { - if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) { - routerHistory.push('/messages'); - } - handleComposeSubmit(dispatch, getState, response, status); - }).catch(function(error) { - dispatch(submitComposeFail(error)); - }); + if (missingDescriptionModal && media.filter(item => !item.get('description')).size) { + dispatch(openModal('MISSING_DESCRIPTION', { + onContinue: () => onModalSubmitCompose(), + })); + } else onModalSubmitCompose(); }; }; diff --git a/app/soapbox/actions/settings.js b/app/soapbox/actions/settings.js index d3d572715..d857fd1e9 100644 --- a/app/soapbox/actions/settings.js +++ b/app/soapbox/actions/settings.js @@ -21,6 +21,7 @@ export const defaultSettings = ImmutableMap({ unfollowModal: false, boostModal: false, deleteModal: true, + missingDescriptionModal: false, defaultPrivacy: 'public', defaultContentType: 'text/plain', themeMode: 'light', diff --git a/app/soapbox/features/compose/components/schedule_form.js b/app/soapbox/features/compose/components/schedule_form.js index 56fed1780..ad444ec95 100644 --- a/app/soapbox/features/compose/components/schedule_form.js +++ b/app/soapbox/features/compose/components/schedule_form.js @@ -20,41 +20,34 @@ class ScheduleForm extends React.Component { active: PropTypes.bool, }; - setSchedule(date) - { + setSchedule(date) { this.setState({ schedule: date }); this.props.onSchedule(date); } - openDatePicker(datePicker) - { - if (!datePicker) - { + openDatePicker(datePicker) { + if (!datePicker) { return; } datePicker.setOpen(true); } - componentDidMount() - { + componentDidMount() { this.setState({ schedule: this.props.schedule }); } - constructor(props) - { + constructor(props) { super(props); this.setSchedule = this.setSchedule.bind(this); } - isCurrentOrFutureDate(date) - { + isCurrentOrFutureDate(date) { return date && new Date().setHours(0, 0, 0, 0) <= new Date(date).setHours(0, 0, 0, 0); } - isFiveMinutesFromNow(time) - { + isFiveMinutesFromNow(time) { const fiveMinutesFromNow = new Date(new Date().getTime() + 300000); // now, plus five minutes (Pleroma won't schedule posts ) const selectedDate = new Date(time); @@ -62,8 +55,7 @@ class ScheduleForm extends React.Component { }; render() { - if (!this.props.active || !this.state) - { + if (!this.props.active || !this.state) { return null; } diff --git a/app/soapbox/features/preferences/index.js b/app/soapbox/features/preferences/index.js index dd1072c49..7d188afc6 100644 --- a/app/soapbox/features/preferences/index.js +++ b/app/soapbox/features/preferences/index.js @@ -202,6 +202,10 @@ class Preferences extends ImmutablePureComponent { label={} path={['deleteModal']} /> + } + path={['missingDescriptionModal']} + /> diff --git a/app/soapbox/features/ui/components/missing_description_modal.js b/app/soapbox/features/ui/components/missing_description_modal.js new file mode 100644 index 000000000..0cbdad660 --- /dev/null +++ b/app/soapbox/features/ui/components/missing_description_modal.js @@ -0,0 +1,51 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { injectIntl, FormattedMessage } from 'react-intl'; +import Button from '../../../components/button'; + +export default @injectIntl +class MissingDescriptionModal extends React.PureComponent { + + static propTypes = { + onClose: PropTypes.func, + onContinue: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + componentDidMount() { + this.button.focus(); + } + + handleContinue = () => { + this.props.onClose(); + this.props.onContinue(); + } + + handleCancel = () => { + this.props.onClose(); + } + + setRef = (c) => { + this.button = c; + } + + render() { + return ( +
+
+ +
+ +
+ + +
+
+ ); + } + +} diff --git a/app/soapbox/features/ui/components/modal_root.js b/app/soapbox/features/ui/components/modal_root.js index b3f7f8681..657df1a26 100644 --- a/app/soapbox/features/ui/components/modal_root.js +++ b/app/soapbox/features/ui/components/modal_root.js @@ -9,6 +9,7 @@ import MediaModal from './media_modal'; import VideoModal from './video_modal'; import BoostModal from './boost_modal'; import ConfirmationModal from './confirmation_modal'; +import MissingDescriptionModal from './missing_description_modal'; import FocalPointModal from './focal_point_modal'; import HotkeysModal from './hotkeys_modal'; import ComposeModal from './compose_modal'; @@ -28,6 +29,7 @@ const MODAL_COMPONENTS = { 'VIDEO': () => Promise.resolve({ default: VideoModal }), 'BOOST': () => Promise.resolve({ default: BoostModal }), 'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }), + 'MISSING_DESCRIPTION': () => Promise.resolve({ default: MissingDescriptionModal }), 'MUTE': MuteModal, 'REPORT': ReportModal, 'ACTIONS': () => Promise.resolve({ default: ActionsModal }), diff --git a/app/soapbox/locales/pl.json b/app/soapbox/locales/pl.json index 42c75dd6b..532770f18 100644 --- a/app/soapbox/locales/pl.json +++ b/app/soapbox/locales/pl.json @@ -421,6 +421,9 @@ "mfa.setup_otp_title": "Wyłączono OTP", "mfa.setup_recoverycodes": "Kody przywracania", "mfa.setup_warning": "Zapisz te kody gdzieś w bezpiecznym miejscu – jeżeli tego nie zrobisz, już ich nie zobaczysz. Jeśli utracisz dostęp do aplikacji 2FA i tych kodów, stracisz dostęp do swojego konta.", + "missing_description_modal.cancel": "Anuluj", + "missing_description_modal.continue": "Opublikuj", + "missing_description_modal.text": "Nie podałeś(-aś) opisu dla wszystkich załączników. Czy na pewno chcesz kontynuować?", "missing_indicator.label": "Nie znaleziono", "missing_indicator.sublabel": "Nie można odnaleźć tego zasobu", "morefollows.followers_label": "…i {count} więcej {count, plural, one {obserwujący(-a)} few {obserwujących} many {obserwujących} other {obserwujących}} na zdalnych stronach.", @@ -497,6 +500,7 @@ "preferences.fields.halloween_label": "Tryb Halloween", "preferences.fields.language_label": "Język", "preferences.fields.media_display_label": "Wyświetlanie zawartości multimedialnej", + "preferences.fields.missing_description_modal_label": "Pytaj o potwierdzenie przed wysłaniem zawartości multimedialnej bez opisu", "preferences.fields.privacy_label": "Prywatność wpisów", "preferences.fields.reduce_motion_label": "Ogranicz ruch w animacjach", "preferences.fields.system_font_label": "Używaj domyślnej czcionki systemu",