Merge branch 'develop' into 'develop'

Confirmation modals about missing image descriptions (configurable)

See merge request soapbox-pub/soapbox-fe!544
datepicker-css
Alex Gleason 2021-06-21 20:39:06 +00:00
commit 58f3107151
7 zmienionych plików z 104 dodań i 40 usunięć

Wyświetl plik

@ -164,6 +164,34 @@ export function submitCompose(routerHistory, group) {
return function(dispatch, getState) { return function(dispatch, getState) {
if (!isLoggedIn(getState)) return; 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 status = getState().getIn(['compose', 'text'], '');
const media = getState().getIn(['compose', 'media_attachments']); const media = getState().getIn(['compose', 'media_attachments']);
@ -171,31 +199,13 @@ export function submitCompose(routerHistory, group) {
return; return;
} }
dispatch(submitComposeRequest()); const missingDescriptionModal = getSettings(getState()).get('missingDescriptionModal');
dispatch(closeModal());
api(getState).post('/api/v1/statuses', { if (missingDescriptionModal && media.filter(item => !item.get('description')).size) {
status, dispatch(openModal('MISSING_DESCRIPTION', {
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), onContinue: () => onModalSubmitCompose(),
media_ids: media.map(item => item.get('id')), }));
sensitive: getState().getIn(['compose', 'sensitive']), } else onModalSubmitCompose();
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));
});
}; };
}; };

Wyświetl plik

@ -21,6 +21,7 @@ export const defaultSettings = ImmutableMap({
unfollowModal: false, unfollowModal: false,
boostModal: false, boostModal: false,
deleteModal: true, deleteModal: true,
missingDescriptionModal: false,
defaultPrivacy: 'public', defaultPrivacy: 'public',
defaultContentType: 'text/plain', defaultContentType: 'text/plain',
themeMode: 'light', themeMode: 'light',

Wyświetl plik

@ -20,41 +20,34 @@ class ScheduleForm extends React.Component {
active: PropTypes.bool, active: PropTypes.bool,
}; };
setSchedule(date) setSchedule(date) {
{
this.setState({ schedule: date }); this.setState({ schedule: date });
this.props.onSchedule(date); this.props.onSchedule(date);
} }
openDatePicker(datePicker) openDatePicker(datePicker) {
{ if (!datePicker) {
if (!datePicker)
{
return; return;
} }
datePicker.setOpen(true); datePicker.setOpen(true);
} }
componentDidMount() componentDidMount() {
{
this.setState({ schedule: this.props.schedule }); this.setState({ schedule: this.props.schedule });
} }
constructor(props) constructor(props) {
{
super(props); super(props);
this.setSchedule = this.setSchedule.bind(this); 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); 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 fiveMinutesFromNow = new Date(new Date().getTime() + 300000); // now, plus five minutes (Pleroma won't schedule posts )
const selectedDate = new Date(time); const selectedDate = new Date(time);
@ -62,8 +55,7 @@ class ScheduleForm extends React.Component {
}; };
render() { render() {
if (!this.props.active || !this.state) if (!this.props.active || !this.state) {
{
return null; return null;
} }

Wyświetl plik

@ -202,6 +202,10 @@ class Preferences extends ImmutablePureComponent {
label={<FormattedMessage id='preferences.fields.delete_modal_label' defaultMessage='Show confirmation dialog before deleting a post' />} label={<FormattedMessage id='preferences.fields.delete_modal_label' defaultMessage='Show confirmation dialog before deleting a post' />}
path={['deleteModal']} path={['deleteModal']}
/> />
<SettingsCheckbox
label={<FormattedMessage id='preferences.fields.missing_description_modal_label' defaultMessage='Show confirmation dialog before sending a post without media descriptions' />}
path={['missingDescriptionModal']}
/>
</FieldsGroup> </FieldsGroup>
<FieldsGroup> <FieldsGroup>

Wyświetl plik

@ -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 (
<div className='modal-root__modal confirmation-modal'>
<div className='confirmation-modal__container'>
<FormattedMessage id='missing_description_modal.text' defaultMessage='You have not entered a description for all attachments. Continue anyway?' />
</div>
<div className='confirmation-modal__action-bar'>
<Button onClick={this.handleCancel} className='confirmation-modal__cancel-button' ref={this.setRef}>
<FormattedMessage id='missing_description_modal.cancel' defaultMessage='Cancel' />
</Button>
<Button onClick={this.handleContinue}>
<FormattedMessage id='missing_description_modal.continue' defaultMessage='Post' />
</Button>
</div>
</div>
);
}
}

Wyświetl plik

@ -9,6 +9,7 @@ import MediaModal from './media_modal';
import VideoModal from './video_modal'; import VideoModal from './video_modal';
import BoostModal from './boost_modal'; import BoostModal from './boost_modal';
import ConfirmationModal from './confirmation_modal'; import ConfirmationModal from './confirmation_modal';
import MissingDescriptionModal from './missing_description_modal';
import FocalPointModal from './focal_point_modal'; import FocalPointModal from './focal_point_modal';
import HotkeysModal from './hotkeys_modal'; import HotkeysModal from './hotkeys_modal';
import ComposeModal from './compose_modal'; import ComposeModal from './compose_modal';
@ -28,6 +29,7 @@ const MODAL_COMPONENTS = {
'VIDEO': () => Promise.resolve({ default: VideoModal }), 'VIDEO': () => Promise.resolve({ default: VideoModal }),
'BOOST': () => Promise.resolve({ default: BoostModal }), 'BOOST': () => Promise.resolve({ default: BoostModal }),
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }), 'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
'MISSING_DESCRIPTION': () => Promise.resolve({ default: MissingDescriptionModal }),
'MUTE': MuteModal, 'MUTE': MuteModal,
'REPORT': ReportModal, 'REPORT': ReportModal,
'ACTIONS': () => Promise.resolve({ default: ActionsModal }), 'ACTIONS': () => Promise.resolve({ default: ActionsModal }),

Wyświetl plik

@ -421,6 +421,9 @@
"mfa.setup_otp_title": "Wyłączono OTP", "mfa.setup_otp_title": "Wyłączono OTP",
"mfa.setup_recoverycodes": "Kody przywracania", "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.", "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.label": "Nie znaleziono",
"missing_indicator.sublabel": "Nie można odnaleźć tego zasobu", "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.", "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.halloween_label": "Tryb Halloween",
"preferences.fields.language_label": "Język", "preferences.fields.language_label": "Język",
"preferences.fields.media_display_label": "Wyświetlanie zawartości multimedialnej", "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.privacy_label": "Prywatność wpisów",
"preferences.fields.reduce_motion_label": "Ogranicz ruch w animacjach", "preferences.fields.reduce_motion_label": "Ogranicz ruch w animacjach",
"preferences.fields.system_font_label": "Używaj domyślnej czcionki systemu", "preferences.fields.system_font_label": "Używaj domyślnej czcionki systemu",