Change design of confirmation modals in web UI (#30884)
Some checks are pending
Check i18n / check-i18n (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Crowdin / Upload translations / upload-translations (push) Waiting to run
Check formatting / lint (push) Waiting to run
CSS Linting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.1) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / Libvips tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / Libvips tests (3.1) (push) Blocked by required conditions
Ruby Testing / Libvips tests (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.1) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.1, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions

Co-authored-by: Renaud Chaput <renchap@gmail.com>
This commit is contained in:
Eugen Rochko 2024-07-25 19:05:54 +02:00 committed by GitHub
parent ff6d2ec343
commit 8818748b90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 554 additions and 489 deletions

View File

@ -3,6 +3,8 @@ import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import LockOpenIcon from '@/material-icons/400-24px/lock_open.svg?react';
import { unblockDomain } from 'mastodon/actions/domain_blocks';
import { useAppDispatch } from 'mastodon/store';
import { IconButton } from './icon_button';
@ -13,17 +15,15 @@ const messages = defineMessages({
},
});
interface Props {
export const Domain: React.FC<{
domain: string;
onUnblockDomain: (domain: string) => void;
}
export const Domain: React.FC<Props> = ({ domain, onUnblockDomain }) => {
}> = ({ domain }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const handleDomainUnblock = useCallback(() => {
onUnblockDomain(domain);
}, [domain, onUnblockDomain]);
dispatch(unblockDomain(domain));
}, [dispatch, domain]);
return (
<div className='domain'>

View File

@ -1,13 +1,9 @@
import { useCallback, useEffect } from 'react';
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import { useIntl, defineMessages } from 'react-intl';
import { useIdentity } from '@/mastodon/identity_context';
import {
fetchRelationships,
followAccount,
unfollowAccount,
} from 'mastodon/actions/accounts';
import { fetchRelationships, followAccount } from 'mastodon/actions/accounts';
import { openModal } from 'mastodon/actions/modal';
import { Button } from 'mastodon/components/button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
@ -60,29 +56,14 @@ export const FollowButton: React.FC<{
if (accountId === me) {
return;
} else if (relationship.following || relationship.requested) {
} else if (account && (relationship.following || relationship.requested)) {
dispatch(
openModal({
modalType: 'CONFIRM',
modalProps: {
message: (
<FormattedMessage
id='confirmations.unfollow.message'
defaultMessage='Are you sure you want to unfollow {name}?'
values={{ name: <strong>@{account?.acct}</strong> }}
/>
),
confirm: intl.formatMessage(messages.unfollow),
onConfirm: () => {
dispatch(unfollowAccount(accountId));
},
},
}),
openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }),
);
} else {
dispatch(followAccount(accountId));
}
}, [dispatch, intl, accountId, relationship, account, signedIn]);
}, [dispatch, accountId, relationship, account, signedIn]);
let label;

View File

@ -1,24 +1,20 @@
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { openModal } from 'mastodon/actions/modal';
import {
followAccount,
unfollowAccount,
blockAccount,
unblockAccount,
muteAccount,
unmuteAccount,
} from '../actions/accounts';
import { openModal } from '../actions/modal';
import { initMuteModal } from '../actions/mutes';
import Account from '../components/account';
import { makeGetAccount } from '../selectors';
const messages = defineMessages({
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
});
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
@ -29,18 +25,11 @@ const makeMapStateToProps = () => {
return mapStateToProps;
};
const mapDispatchToProps = (dispatch, { intl }) => ({
const mapDispatchToProps = (dispatch) => ({
onFollow (account) {
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.unfollowConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
},
}));
dispatch(openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }));
} else {
dispatch(followAccount(account.get('id')));
}

View File

@ -1,36 +0,0 @@
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { blockDomain, unblockDomain } from '../actions/domain_blocks';
import { openModal } from '../actions/modal';
import { Domain } from '../components/domain';
const messages = defineMessages({
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' },
});
const makeMapStateToProps = () => {
const mapStateToProps = () => ({});
return mapStateToProps;
};
const mapDispatchToProps = (dispatch, { intl }) => ({
onBlockDomain (domain) {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />,
confirm: intl.formatMessage(messages.blockDomainConfirm),
onConfirm: () => dispatch(blockDomain(domain)),
},
}));
},
onUnblockDomain (domain) {
dispatch(unblockDomain(domain));
},
});
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Domain));

View File

@ -1,4 +1,4 @@
import { defineMessages, injectIntl } from 'react-intl';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
@ -46,18 +46,6 @@ import Status from '../components/status';
import { deleteModal } from '../initial_state';
import { makeGetStatus, makeGetPictureInPicture } from '../selectors';
const messages = defineMessages({
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.' },
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
editConfirm: { id: 'confirmations.edit.confirm', defaultMessage: 'Edit' },
editMessage: { id: 'confirmations.edit.message', defaultMessage: 'Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' },
});
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const getPictureInPicture = makeGetPictureInPicture();
@ -71,20 +59,14 @@ const makeMapStateToProps = () => {
return mapStateToProps;
};
const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
const mapDispatchToProps = (dispatch, { contextType }) => ({
onReply (status) {
dispatch((_, getState) => {
let state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(messages.replyMessage),
confirm: intl.formatMessage(messages.replyConfirm),
onConfirm: () => dispatch(replyCompose(status)) },
}));
dispatch(openModal({ modalType: 'CONFIRM_REPLY', modalProps: { status } }));
} else {
dispatch(replyCompose(status));
}
@ -129,14 +111,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
if (!deleteModal) {
dispatch(deleteStatus(status.get('id'), withRedraft));
} else {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
onConfirm: () => dispatch(deleteStatus(status.get('id'), withRedraft)),
},
}));
dispatch(openModal({ modalType: 'CONFIRM_DELETE_STATUS', modalProps: { statusId: status.get('id'), withRedraft } }));
}
},
@ -144,14 +119,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
dispatch((_, getState) => {
let state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(messages.editMessage),
confirm: intl.formatMessage(messages.editConfirm),
onConfirm: () => dispatch(editStatus(status.get('id'))),
},
}));
dispatch(openModal({ modalType: 'CONFIRM_EDIT_STATUS', modalProps: { statusId: status.get('id') } }));
} else {
dispatch(editStatus(status.get('id')));
}

View File

@ -1,4 +1,4 @@
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
@ -6,7 +6,6 @@ import { openURL } from 'mastodon/actions/search';
import {
followAccount,
unfollowAccount,
unblockAccount,
unmuteAccount,
pinAccount,
@ -24,11 +23,6 @@ import { initReport } from '../../../actions/reports';
import { makeGetAccount, getAccountHidden } from '../../../selectors';
import Header from '../components/header';
const messages = defineMessages({
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' },
});
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
@ -41,18 +35,11 @@ const makeMapStateToProps = () => {
return mapStateToProps;
};
const mapDispatchToProps = (dispatch, { intl }) => ({
const mapDispatchToProps = (dispatch) => ({
onFollow (account) {
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.unfollowConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
},
}));
dispatch(openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }));
} else {
dispatch(followAccount(account.get('id')));
}

View File

@ -7,7 +7,6 @@ import { useDispatch } from 'react-redux';
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
import { openModal } from 'mastodon/actions/modal';
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
import { logOut } from 'mastodon/utils/log_out';
const messages = defineMessages({
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
@ -23,8 +22,6 @@ const messages = defineMessages({
filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
});
export const ActionBar = () => {
@ -32,16 +29,8 @@ export const ActionBar = () => {
const intl = useIntl();
const handleLogoutClick = useCallback(() => {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(messages.logoutMessage),
confirm: intl.formatMessage(messages.logoutConfirm),
closeWhenConfirm: false,
onConfirm: () => logOut(),
},
}));
}, [dispatch, intl]);
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
}, [dispatch]);
let menu = [];

View File

@ -21,7 +21,6 @@ import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react';
import { openModal } from 'mastodon/actions/modal';
import Column from 'mastodon/components/column';
import { Icon } from 'mastodon/components/icon';
import { logOut } from 'mastodon/utils/log_out';
import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
import { changeComposing, mountCompose, unmountCompose } from '../../actions/compose';
@ -42,8 +41,6 @@ const messages = defineMessages({
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new post' },
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
});
const mapStateToProps = (state, ownProps) => ({
@ -72,20 +69,12 @@ class Compose extends PureComponent {
}
handleLogoutClick = e => {
const { dispatch, intl } = this.props;
const { dispatch } = this.props;
e.preventDefault();
e.stopPropagation();
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(messages.logoutMessage),
confirm: intl.formatMessage(messages.logoutConfirm),
closeWhenConfirm: false,
onConfirm: () => logOut(),
},
}));
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
return false;
};

View File

@ -36,8 +36,6 @@ const messages = defineMessages({
delete: { id: 'conversation.delete', defaultMessage: 'Delete conversation' },
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
});
const getAccounts = createSelector(
@ -103,19 +101,12 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown })
let state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(messages.replyMessage),
confirm: intl.formatMessage(messages.replyConfirm),
onConfirm: () => dispatch(replyCompose(lastStatus)),
},
}));
dispatch(openModal({ modalType: 'CONFIRM_REPLY', modalProps: { status: lastStatus } }));
} else {
dispatch(replyCompose(lastStatus));
}
});
}, [dispatch, lastStatus, intl]);
}, [dispatch, lastStatus]);
const handleDelete = useCallback(() => {
dispatch(deleteConversation(id));

View File

@ -8,7 +8,6 @@ import { Link } from 'react-router-dom';
import {
followAccount,
unfollowAccount,
unblockAccount,
unmuteAccount,
} from 'mastodon/actions/accounts';
@ -29,20 +28,12 @@ const messages = defineMessages({
id: 'account.cancel_follow_request',
defaultMessage: 'Withdraw follow request',
},
cancelFollowRequestConfirm: {
id: 'confirmations.cancel_follow_request.confirm',
defaultMessage: 'Withdraw request',
},
requested: {
id: 'account.requested',
defaultMessage: 'Awaiting approval. Click to cancel follow request',
},
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
unfollowConfirm: {
id: 'confirmations.unfollow.confirm',
defaultMessage: 'Unfollow',
},
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
});
@ -89,48 +80,17 @@ export const AccountCard: React.FC<{ accountId: string }> = ({ accountId }) => {
const handleFollow = useCallback(() => {
if (!account) return;
if (account.getIn(['relationship', 'following'])) {
if (
account.getIn(['relationship', 'following']) ||
account.getIn(['relationship', 'requested'])
) {
dispatch(
openModal({
modalType: 'CONFIRM',
modalProps: {
message: (
<FormattedMessage
id='confirmations.unfollow.message'
defaultMessage='Are you sure you want to unfollow {name}?'
values={{ name: <strong>@{account.get('acct')}</strong> }}
/>
),
confirm: intl.formatMessage(messages.unfollowConfirm),
onConfirm: () => {
dispatch(unfollowAccount(account.get('id')));
},
},
}),
);
} else if (account.getIn(['relationship', 'requested'])) {
dispatch(
openModal({
modalType: 'CONFIRM',
modalProps: {
message: (
<FormattedMessage
id='confirmations.cancel_follow_request.message'
defaultMessage='Are you sure you want to withdraw your request to follow {name}?'
values={{ name: <strong>@{account.get('acct')}</strong> }}
/>
),
confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
onConfirm: () => {
dispatch(unfollowAccount(account.get('id')));
},
},
}),
openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }),
);
} else {
dispatch(followAccount(account.get('id')));
}
}, [account, dispatch, intl]);
}, [account, dispatch]);
const handleBlock = useCallback(() => {
if (account?.relationship?.blocking) {

View File

@ -11,16 +11,15 @@ import { connect } from 'react-redux';
import { debounce } from 'lodash';
import BlockIcon from '@/material-icons/400-24px/block-fill.svg?react';
import { Domain } from 'mastodon/components/domain';
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list';
import DomainContainer from '../../containers/domain_container';
import Column from '../ui/components/column';
const messages = defineMessages({
heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
});
const mapStateToProps = state => ({
@ -70,7 +69,7 @@ class Blocks extends ImmutablePureComponent {
bindToDocument={!multiColumn}
>
{domains.map(domain =>
<DomainContainer key={domain} domain={domain} />,
<Domain key={domain} domain={domain} />,
)}
</ScrollableList>

View File

@ -15,7 +15,7 @@ import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
import { fetchList, deleteList, updateList } from 'mastodon/actions/lists';
import { fetchList, updateList } from 'mastodon/actions/lists';
import { openModal } from 'mastodon/actions/modal';
import { connectListStream } from 'mastodon/actions/streaming';
import { expandListTimeline } from 'mastodon/actions/timelines';
@ -29,8 +29,6 @@ import StatusListContainer from 'mastodon/features/ui/containers/status_list_con
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
const messages = defineMessages({
deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
deleteConfirm: { id: 'confirmations.delete_list.confirm', defaultMessage: 'Delete' },
followed: { id: 'lists.replies_policy.followed', defaultMessage: 'Any followed user' },
none: { id: 'lists.replies_policy.none', defaultMessage: 'No one' },
list: { id: 'lists.replies_policy.list', defaultMessage: 'Members of the list' },
@ -125,25 +123,10 @@ class ListTimeline extends PureComponent {
};
handleDeleteClick = () => {
const { dispatch, columnId, intl } = this.props;
const { dispatch, columnId } = this.props;
const { id } = this.props.params;
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(messages.deleteMessage),
confirm: intl.formatMessage(messages.deleteConfirm),
onConfirm: () => {
dispatch(deleteList(id));
if (columnId) {
dispatch(removeColumn(columnId));
} else {
this.props.history.push('/lists');
}
},
},
}));
dispatch(openModal({ modalType: 'CONFIRM_DELETE_LIST', modalProps: { listId: id, columnId } }));
};
handleRepliesPolicyChange = ({ target }) => {

View File

@ -2,11 +2,10 @@ import { defineMessages, injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { openModal } from 'mastodon/actions/modal';
import { initializeNotifications } from 'mastodon/actions/notifications_migration';
import { showAlert } from '../../../actions/alerts';
import { openModal } from '../../../actions/modal';
import { clearNotifications } from '../../../actions/notification_groups';
import { updateNotificationsPolicy } from '../../../actions/notification_policies';
import { setFilter, requestBrowserPermission } from '../../../actions/notifications';
import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications';
@ -14,8 +13,6 @@ import { changeSetting } from '../../../actions/settings';
import ColumnSettings from '../components/column_settings';
const messages = defineMessages({
clearMessage: { id: 'notifications.clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all your notifications?' },
clearConfirm: { id: 'notifications.clear', defaultMessage: 'Clear notifications' },
permissionDenied: { id: 'notifications.permission_denied_alert', defaultMessage: 'Desktop notifications can\'t be enabled, as browser permission has been denied before' },
});
@ -31,7 +28,7 @@ const mapStateToProps = state => ({
notificationPolicy: state.notificationPolicy,
});
const mapDispatchToProps = (dispatch, { intl }) => ({
const mapDispatchToProps = (dispatch) => ({
onChange (path, checked) {
if (path[0] === 'push') {
@ -70,14 +67,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
},
onClear () {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(messages.clearMessage),
confirm: intl.formatMessage(messages.clearConfirm),
onConfirm: () => dispatch(clearNotifications()),
},
}));
dispatch(openModal({ modalType: 'CONFIRM_CLEAR_NOTIFICATIONS' }));
},
onRequestNotificationPermission () {

View File

@ -31,8 +31,6 @@ const messages = defineMessages({
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
open: { id: 'status.open', defaultMessage: 'Expand this status' },
});
@ -71,19 +69,13 @@ class Footer extends ImmutablePureComponent {
};
handleReplyClick = () => {
const { dispatch, askReplyConfirmation, status, intl } = this.props;
const { dispatch, askReplyConfirmation, status, onClose } = this.props;
const { signedIn } = this.props.identity;
if (signedIn) {
if (askReplyConfirmation) {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(messages.replyMessage),
confirm: intl.formatMessage(messages.replyConfirm),
onConfirm: this._performReply,
},
}));
onClose(true);
dispatch(openModal({ modalType: 'CONFIRM_REPLY', modalProps: { status } }));
} else {
this._performReply();
}

View File

@ -1,4 +1,4 @@
import { defineMessages, injectIntl } from 'react-intl';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
@ -28,15 +28,6 @@ import { deleteModal } from '../../../initial_state';
import { makeGetStatus, makeGetPictureInPicture } from '../../../selectors';
import DetailedStatus from '../components/detailed_status';
const messages = defineMessages({
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.' },
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
});
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const getPictureInPicture = makeGetPictureInPicture();
@ -50,20 +41,13 @@ const makeMapStateToProps = () => {
return mapStateToProps;
};
const mapDispatchToProps = (dispatch, { intl }) => ({
const mapDispatchToProps = (dispatch) => ({
onReply (status) {
dispatch((_, getState) => {
let state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(messages.replyMessage),
confirm: intl.formatMessage(messages.replyConfirm),
onConfirm: () => dispatch(replyCompose(status)),
},
}));
dispatch(openModal({ modalType: 'CONFIRM_REPLY', modalProps: { status } }));
} else {
dispatch(replyCompose(status));
}
@ -100,14 +84,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
if (!deleteModal) {
dispatch(deleteStatus(status.get('id'), withRedraft));
} else {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
onConfirm: () => dispatch(deleteStatus(status.get('id'), withRedraft)),
},
}));
dispatch(openModal({ modalType: 'CONFIRM_DELETE_STATUS', modalProps: { statusId: status.get('id'), withRedraft } }));
}
},

View File

@ -72,17 +72,10 @@ import DetailedStatus from './components/detailed_status';
const messages = defineMessages({
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.' },
revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
statusTitleWithAttachments: { id: 'status.title.with_attachments', defaultMessage: '{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}' },
detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' },
});
const makeMapStateToProps = () => {
@ -264,19 +257,12 @@ class Status extends ImmutablePureComponent {
};
handleReplyClick = (status) => {
const { askReplyConfirmation, dispatch, intl } = this.props;
const { askReplyConfirmation, dispatch } = this.props;
const { signedIn } = this.props.identity;
if (signedIn) {
if (askReplyConfirmation) {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(messages.replyMessage),
confirm: intl.formatMessage(messages.replyConfirm),
onConfirm: () => dispatch(replyCompose(status)),
},
}));
dispatch(openModal({ modalType: 'CONFIRM_REPLY', modalProps: { status } }));
} else {
dispatch(replyCompose(status));
}
@ -319,24 +305,23 @@ class Status extends ImmutablePureComponent {
};
handleDeleteClick = (status, withRedraft = false) => {
const { dispatch, intl } = this.props;
const { dispatch } = this.props;
if (!deleteModal) {
dispatch(deleteStatus(status.get('id'), withRedraft));
} else {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
onConfirm: () => dispatch(deleteStatus(status.get('id'), withRedraft)),
},
}));
dispatch(openModal({ modalType: 'CONFIRM_DELETE_STATUS', modalProps: { statusId: status.get('id'), withRedraft } }));
}
};
handleEditClick = (status) => {
this.props.dispatch(editStatus(status.get('id')));
const { dispatch, askReplyConfirmation } = this.props;
if (askReplyConfirmation) {
dispatch(openModal({ modalType: 'CONFIRM_EDIT_STATUS', modalProps: { statusId: status.get('id') } }));
} else {
dispatch(editStatus(status.get('id')));
}
};
handleDirectClick = (account) => {

View File

@ -1,65 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { injectIntl, FormattedMessage } from 'react-intl';
import { Button } from '../../../components/button';
class ConfirmationModal extends PureComponent {
static propTypes = {
message: PropTypes.node.isRequired,
confirm: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onConfirm: PropTypes.func.isRequired,
secondary: PropTypes.string,
onSecondary: PropTypes.func,
closeWhenConfirm: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
static defaultProps = {
closeWhenConfirm: true,
};
handleClick = () => {
if (this.props.closeWhenConfirm) {
this.props.onClose();
}
this.props.onConfirm();
};
handleSecondary = () => {
this.props.onClose();
this.props.onSecondary();
};
handleCancel = () => {
this.props.onClose();
};
render () {
const { message, confirm, secondary } = this.props;
return (
<div className='modal-root__modal confirmation-modal'>
<div className='confirmation-modal__container'>
{message}
</div>
<div className='confirmation-modal__action-bar'>
<Button onClick={this.handleCancel} className='confirmation-modal__cancel-button'>
<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
</Button>
{secondary !== undefined && (
<Button text={secondary} onClick={this.handleSecondary} className='confirmation-modal__secondary-button' />
)}
<Button text={confirm} onClick={this.handleClick} autoFocus />
</div>
</div>
);
}
}
export default injectIntl(ConfirmationModal);

View File

@ -0,0 +1,46 @@
import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { clearNotifications } from 'mastodon/actions/notification_groups';
import { useAppDispatch } from 'mastodon/store';
import type { BaseConfirmationModalProps } from './confirmation_modal';
import { ConfirmationModal } from './confirmation_modal';
const messages = defineMessages({
clearTitle: {
id: 'notifications.clear_title',
defaultMessage: 'Clear notifications?',
},
clearMessage: {
id: 'notifications.clear_confirmation',
defaultMessage:
'Are you sure you want to permanently clear all your notifications?',
},
clearConfirm: {
id: 'notifications.clear',
defaultMessage: 'Clear notifications',
},
});
export const ConfirmClearNotificationsModal: React.FC<
BaseConfirmationModalProps
> = ({ onClose }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const onConfirm = useCallback(() => {
void dispatch(clearNotifications());
}, [dispatch]);
return (
<ConfirmationModal
title={intl.formatMessage(messages.clearTitle)}
message={intl.formatMessage(messages.clearMessage)}
confirm={intl.formatMessage(messages.clearConfirm)}
onConfirm={onConfirm}
onClose={onClose}
/>
);
};

View File

@ -0,0 +1,79 @@
import { useCallback } from 'react';
import { FormattedMessage } from 'react-intl';
import { Button } from 'mastodon/components/button';
export interface BaseConfirmationModalProps {
onClose: () => void;
}
export const ConfirmationModal: React.FC<
{
title: React.ReactNode;
message: React.ReactNode;
confirm: React.ReactNode;
secondary?: React.ReactNode;
onSecondary?: () => void;
onConfirm: () => void;
closeWhenConfirm?: boolean;
} & BaseConfirmationModalProps
> = ({
title,
message,
confirm,
onClose,
onConfirm,
secondary,
onSecondary,
closeWhenConfirm = true,
}) => {
const handleClick = useCallback(() => {
if (closeWhenConfirm) {
onClose();
}
onConfirm();
}, [onClose, onConfirm, closeWhenConfirm]);
const handleSecondary = useCallback(() => {
onClose();
onSecondary?.();
}, [onClose, onSecondary]);
const handleCancel = useCallback(() => {
onClose();
}, [onClose]);
return (
<div className='modal-root__modal safety-action-modal'>
<div className='safety-action-modal__top'>
<div className='safety-action-modal__confirmation'>
<h1>{title}</h1>
<p>{message}</p>
</div>
</div>
<div className='safety-action-modal__bottom'>
<div className='safety-action-modal__actions'>
{secondary && (
<>
<Button onClick={handleSecondary}>{secondary}</Button>
<div className='spacer' />
</>
)}
<button onClick={handleCancel} className='link-button'>
<FormattedMessage
id='confirmation_modal.cancel'
defaultMessage='Cancel'
/>
</button>
<Button onClick={handleClick}>{confirm}</Button>
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,58 @@
import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useHistory } from 'react-router';
import { removeColumn } from 'mastodon/actions/columns';
import { deleteList } from 'mastodon/actions/lists';
import { useAppDispatch } from 'mastodon/store';
import type { BaseConfirmationModalProps } from './confirmation_modal';
import { ConfirmationModal } from './confirmation_modal';
const messages = defineMessages({
deleteListTitle: {
id: 'confirmations.delete_list.title',
defaultMessage: 'Delete list?',
},
deleteListMessage: {
id: 'confirmations.delete_list.message',
defaultMessage: 'Are you sure you want to permanently delete this list?',
},
deleteListConfirm: {
id: 'confirmations.delete_list.confirm',
defaultMessage: 'Delete',
},
});
export const ConfirmDeleteListModal: React.FC<
{
listId: string;
columnId: string;
} & BaseConfirmationModalProps
> = ({ listId, columnId, onClose }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const history = useHistory();
const onConfirm = useCallback(() => {
dispatch(deleteList(listId));
if (columnId) {
dispatch(removeColumn(columnId));
} else {
history.push('/lists');
}
}, [dispatch, history, columnId, listId]);
return (
<ConfirmationModal
title={intl.formatMessage(messages.deleteListTitle)}
message={intl.formatMessage(messages.deleteListMessage)}
confirm={intl.formatMessage(messages.deleteListConfirm)}
onConfirm={onConfirm}
onClose={onClose}
/>
);
};

View File

@ -0,0 +1,67 @@
import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { deleteStatus } from 'mastodon/actions/statuses';
import { useAppDispatch } from 'mastodon/store';
import type { BaseConfirmationModalProps } from './confirmation_modal';
import { ConfirmationModal } from './confirmation_modal';
const messages = defineMessages({
deleteAndRedraftTitle: {
id: 'confirmations.redraft.title',
defaultMessage: 'Delete & redraft post?',
},
deleteAndRedraftMessage: {
id: 'confirmations.redraft.message',
defaultMessage:
'Are you sure you want to delete this status and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.',
},
deleteAndRedraftConfirm: {
id: 'confirmations.redraft.confirm',
defaultMessage: 'Delete & redraft',
},
deleteTitle: {
id: 'confirmations.delete.title',
defaultMessage: 'Delete post?',
},
deleteMessage: {
id: 'confirmations.delete.message',
defaultMessage: 'Are you sure you want to delete this status?',
},
deleteConfirm: {
id: 'confirmations.delete.confirm',
defaultMessage: 'Delete',
},
});
export const ConfirmDeleteStatusModal: React.FC<
{
statusId: string;
withRedraft: boolean;
} & BaseConfirmationModalProps
> = ({ statusId, withRedraft, onClose }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const onConfirm = useCallback(() => {
dispatch(deleteStatus(statusId, withRedraft));
}, [dispatch, statusId, withRedraft]);
return (
<ConfirmationModal
title={intl.formatMessage(
withRedraft ? messages.deleteAndRedraftTitle : messages.deleteTitle,
)}
message={intl.formatMessage(
withRedraft ? messages.deleteAndRedraftMessage : messages.deleteMessage,
)}
confirm={intl.formatMessage(
withRedraft ? messages.deleteAndRedraftConfirm : messages.deleteConfirm,
)}
onConfirm={onConfirm}
onClose={onClose}
/>
);
};

View File

@ -0,0 +1,45 @@
import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { editStatus } from 'mastodon/actions/statuses';
import { useAppDispatch } from 'mastodon/store';
import type { BaseConfirmationModalProps } from './confirmation_modal';
import { ConfirmationModal } from './confirmation_modal';
const messages = defineMessages({
editTitle: {
id: 'confirmations.edit.title',
defaultMessage: 'Overwrite post?',
},
editConfirm: { id: 'confirmations.edit.confirm', defaultMessage: 'Edit' },
editMessage: {
id: 'confirmations.edit.message',
defaultMessage:
'Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?',
},
});
export const ConfirmEditStatusModal: React.FC<
{
statusId: string;
} & BaseConfirmationModalProps
> = ({ statusId, onClose }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const onConfirm = useCallback(() => {
dispatch(editStatus(statusId));
}, [dispatch, statusId]);
return (
<ConfirmationModal
title={intl.formatMessage(messages.editTitle)}
message={intl.formatMessage(messages.editMessage)}
confirm={intl.formatMessage(messages.editConfirm)}
onConfirm={onConfirm}
onClose={onClose}
/>
);
};

View File

@ -0,0 +1,8 @@
export { ConfirmationModal } from './confirmation_modal';
export { ConfirmDeleteStatusModal } from './delete_status';
export { ConfirmDeleteListModal } from './delete_list';
export { ConfirmReplyModal } from './reply';
export { ConfirmEditStatusModal } from './edit_status';
export { ConfirmUnfollowModal } from './unfollow';
export { ConfirmClearNotificationsModal } from './clear_notifications';
export { ConfirmLogOutModal } from './log_out';

View File

@ -0,0 +1,40 @@
import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { logOut } from 'mastodon/utils/log_out';
import type { BaseConfirmationModalProps } from './confirmation_modal';
import { ConfirmationModal } from './confirmation_modal';
const messages = defineMessages({
logoutTitle: { id: 'confirmations.logout.title', defaultMessage: 'Log out?' },
logoutMessage: {
id: 'confirmations.logout.message',
defaultMessage: 'Are you sure you want to log out?',
},
logoutConfirm: {
id: 'confirmations.logout.confirm',
defaultMessage: 'Log out',
},
});
export const ConfirmLogOutModal: React.FC<BaseConfirmationModalProps> = ({
onClose,
}) => {
const intl = useIntl();
const onConfirm = useCallback(() => {
logOut();
}, []);
return (
<ConfirmationModal
title={intl.formatMessage(messages.logoutTitle)}
message={intl.formatMessage(messages.logoutMessage)}
confirm={intl.formatMessage(messages.logoutConfirm)}
onConfirm={onConfirm}
onClose={onClose}
/>
);
};

View File

@ -0,0 +1,46 @@
import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { replyCompose } from 'mastodon/actions/compose';
import type { Status } from 'mastodon/models/status';
import { useAppDispatch } from 'mastodon/store';
import type { BaseConfirmationModalProps } from './confirmation_modal';
import { ConfirmationModal } from './confirmation_modal';
const messages = defineMessages({
replyTitle: {
id: 'confirmations.reply.title',
defaultMessage: 'Overwrite post?',
},
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: {
id: 'confirmations.reply.message',
defaultMessage:
'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?',
},
});
export const ConfirmReplyModal: React.FC<
{
status: Status;
} & BaseConfirmationModalProps
> = ({ status, onClose }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const onConfirm = useCallback(() => {
dispatch(replyCompose(status));
}, [dispatch, status]);
return (
<ConfirmationModal
title={intl.formatMessage(messages.replyTitle)}
message={intl.formatMessage(messages.replyMessage)}
confirm={intl.formatMessage(messages.replyConfirm)}
onConfirm={onConfirm}
onClose={onClose}
/>
);
};

View File

@ -0,0 +1,50 @@
import { useCallback } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { unfollowAccount } from 'mastodon/actions/accounts';
import type { Account } from 'mastodon/models/account';
import { useAppDispatch } from 'mastodon/store';
import type { BaseConfirmationModalProps } from './confirmation_modal';
import { ConfirmationModal } from './confirmation_modal';
const messages = defineMessages({
unfollowTitle: {
id: 'confirmations.unfollow.title',
defaultMessage: 'Unfollow user?',
},
unfollowConfirm: {
id: 'confirmations.unfollow.confirm',
defaultMessage: 'Unfollow',
},
});
export const ConfirmUnfollowModal: React.FC<
{
account: Account;
} & BaseConfirmationModalProps
> = ({ account, onClose }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const onConfirm = useCallback(() => {
dispatch(unfollowAccount(account.id));
}, [dispatch, account.id]);
return (
<ConfirmationModal
title={intl.formatMessage(messages.unfollowTitle)}
message={
<FormattedMessage
id='confirmations.unfollow.message'
defaultMessage='Are you sure you want to unfollow {name}?'
values={{ name: <strong>@{account.acct}</strong> }}
/>
}
confirm={intl.formatMessage(messages.unfollowConfirm)}
onConfirm={onConfirm}
onClose={onClose}
/>
);
};

View File

@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { FormattedMessage, injectIntl } from 'react-intl';
import { Link } from 'react-router-dom';
@ -9,29 +9,15 @@ import { connect } from 'react-redux';
import { openModal } from 'mastodon/actions/modal';
import { disabledAccountId, movedToAccountId, domain } from 'mastodon/initial_state';
import { logOut } from 'mastodon/utils/log_out';
const messages = defineMessages({
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
});
const mapStateToProps = (state) => ({
disabledAcct: state.getIn(['accounts', disabledAccountId, 'acct']),
movedToAcct: movedToAccountId ? state.getIn(['accounts', movedToAccountId, 'acct']) : undefined,
});
const mapDispatchToProps = (dispatch, { intl }) => ({
const mapDispatchToProps = (dispatch) => ({
onLogout () {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(messages.logoutMessage),
confirm: intl.formatMessage(messages.logoutConfirm),
closeWhenConfirm: false,
onConfirm: () => logOut(),
},
}));
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
},
});

View File

@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { FormattedMessage, injectIntl } from 'react-intl';
import { Link } from 'react-router-dom';
@ -11,24 +11,11 @@ import { openModal } from 'mastodon/actions/modal';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'mastodon/initial_state';
import { PERMISSION_INVITE_USERS } from 'mastodon/permissions';
import { logOut } from 'mastodon/utils/log_out';
const messages = defineMessages({
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
});
const mapDispatchToProps = (dispatch, { intl }) => ({
const mapDispatchToProps = (dispatch) => ({
onLogout () {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(messages.logoutMessage),
confirm: intl.formatMessage(messages.logoutConfirm),
closeWhenConfirm: false,
onConfirm: () => logOut(),
},
}));
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
},
});

View File

@ -26,7 +26,16 @@ import ActionsModal from './actions_modal';
import AudioModal from './audio_modal';
import { BoostModal } from './boost_modal';
import BundleModalError from './bundle_modal_error';
import ConfirmationModal from './confirmation_modal';
import {
ConfirmationModal,
ConfirmDeleteStatusModal,
ConfirmDeleteListModal,
ConfirmReplyModal,
ConfirmEditStatusModal,
ConfirmUnfollowModal,
ConfirmClearNotificationsModal,
ConfirmLogOutModal,
} from './confirmation_modals';
import FocalPointModal from './focal_point_modal';
import ImageModal from './image_modal';
import MediaModal from './media_modal';
@ -40,6 +49,13 @@ export const MODAL_COMPONENTS = {
'IMAGE': () => Promise.resolve({ default: ImageModal }),
'BOOST': () => Promise.resolve({ default: BoostModal }),
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
'CONFIRM_DELETE_STATUS': () => Promise.resolve({ default: ConfirmDeleteStatusModal }),
'CONFIRM_DELETE_LIST': () => Promise.resolve({ default: ConfirmDeleteListModal }),
'CONFIRM_REPLY': () => Promise.resolve({ default: ConfirmReplyModal }),
'CONFIRM_EDIT_STATUS': () => Promise.resolve({ default: ConfirmEditStatusModal }),
'CONFIRM_UNFOLLOW': () => Promise.resolve({ default: ConfirmUnfollowModal }),
'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }),
'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }),
'MUTE': MuteModal,
'BLOCK': BlockModal,
'DOMAIN_BLOCK': DomainBlockModal,

View File

@ -169,27 +169,30 @@
"compose_form.spoiler_placeholder": "Content warning (optional)",
"confirmation_modal.cancel": "Cancel",
"confirmations.block.confirm": "Block",
"confirmations.cancel_follow_request.confirm": "Withdraw request",
"confirmations.cancel_follow_request.message": "Are you sure you want to withdraw your request to follow {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this post?",
"confirmations.delete.title": "Delete post?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.delete_list.title": "Delete list?",
"confirmations.discard_edit_media.confirm": "Discard",
"confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
"confirmations.domain_block.confirm": "Block server",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
"confirmations.edit.confirm": "Edit",
"confirmations.edit.message": "Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?",
"confirmations.edit.title": "Overwrite post?",
"confirmations.logout.confirm": "Log out",
"confirmations.logout.message": "Are you sure you want to log out?",
"confirmations.logout.title": "Log out?",
"confirmations.mute.confirm": "Mute",
"confirmations.redraft.confirm": "Delete & redraft",
"confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.",
"confirmations.redraft.title": "Delete & redraft post?",
"confirmations.reply.confirm": "Reply",
"confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
"confirmations.reply.title": "Overwrite post?",
"confirmations.unfollow.confirm": "Unfollow",
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
"confirmations.unfollow.title": "Unfollow user?",
"conversation.delete": "Delete conversation",
"conversation.mark_as_read": "Mark as read",
"conversation.open": "View conversation",
@ -507,6 +510,7 @@
"notification_requests.title": "Filtered notifications",
"notifications.clear": "Clear notifications",
"notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
"notifications.clear_title": "Clear notifications?",
"notifications.column_settings.admin.report": "New reports:",
"notifications.column_settings.admin.sign_up": "New sign-ups:",
"notifications.column_settings.alert": "Desktop notifications",

View File

@ -6055,6 +6055,25 @@ a.status-card {
}
}
&__confirmation {
font-size: 14px;
line-height: 20px;
color: $darker-text-color;
h1 {
font-size: 16px;
line-height: 24px;
color: $primary-text-color;
font-weight: 500;
margin-bottom: 8px;
}
strong {
font-weight: 700;
color: $primary-text-color;
}
}
&__bullet-points {
display: flex;
flex-direction: column;
@ -6140,11 +6159,8 @@ a.status-card {
}
.boost-modal,
.confirmation-modal,
.report-modal,
.actions-modal,
.mute-modal,
.block-modal,
.compare-history-modal {
background: lighten($ui-secondary-color, 8%);
color: $inverted-text-color;
@ -6166,10 +6182,7 @@ a.status-card {
}
}
.boost-modal__action-bar,
.confirmation-modal__action-bar,
.mute-modal__action-bar,
.block-modal__action-bar {
.boost-modal__action-bar {
display: flex;
justify-content: space-between;
align-items: center;
@ -6192,16 +6205,6 @@ a.status-card {
}
}
.mute-modal,
.block-modal {
line-height: 24px;
}
.mute-modal .react-toggle,
.block-modal .react-toggle {
vertical-align: middle;
}
.report-modal {
width: 90vw;
max-width: 700px;
@ -6596,34 +6599,6 @@ a.status-card {
}
}
.confirmation-modal__action-bar,
.mute-modal__action-bar,
.block-modal__action-bar {
.confirmation-modal__secondary-button {
flex-shrink: 1;
}
}
.confirmation-modal__secondary-button,
.confirmation-modal__cancel-button,
.mute-modal__cancel-button,
.block-modal__cancel-button {
background-color: transparent;
color: $lighter-text-color;
font-size: 14px;
font-weight: 500;
&:hover,
&:focus,
&:active {
color: darken($lighter-text-color, 4%);
background-color: transparent;
}
}
.confirmation-modal__container,
.mute-modal__container,
.block-modal__container,
.report-modal__target {
padding: 30px;
font-size: 16px;
@ -6657,31 +6632,10 @@ a.status-card {
}
}
.confirmation-modal__container,
.report-modal__target {
text-align: center;
}
.block-modal,
.mute-modal {
&__explanation {
margin-top: 20px;
}
.setting-toggle {
margin-top: 20px;
margin-bottom: 24px;
display: flex;
align-items: center;
&__label {
color: $inverted-text-color;
margin: 0;
margin-inline-start: 8px;
}
}
}
.report-modal__target {
padding: 15px;