Add shortcuts for forward/delete selected/targeted messages

This commit is contained in:
Jamie Kyle 2023-03-24 14:16:48 -07:00 committed by GitHub
parent dd16be13b0
commit d0f17a1398
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 106 additions and 35 deletions

View file

@ -2717,6 +2717,10 @@
}, },
"Keyboard--delete-message": { "Keyboard--delete-message": {
"message": "Delete selected message", "message": "Delete selected message",
"description": "(deleted 03/24/2023) Shown in the shortcuts guide"
},
"Keyboard--delete-messages": {
"message": "Delete selected messages",
"description": "Shown in the shortcuts guide" "description": "Shown in the shortcuts guide"
}, },
"Keyboard--add-newline": { "Keyboard--add-newline": {
@ -4653,15 +4657,27 @@
}, },
"icu:SelectModeActions__confirmDelete--title": { "icu:SelectModeActions__confirmDelete--title": {
"messageformat": "Delete {count, plural, one {# message} other {# messages}}?", "messageformat": "Delete {count, plural, one {# message} other {# messages}}?",
"description": "conversation > in select mode > composition area actions > delete selected messages > confirmation modal > title" "description": "(deleted 03/24/2023) conversation > in select mode > composition area actions > delete selected messages > confirmation modal > title"
},
"icu:ConfirmDeleteForMeModal--title": {
"messageformat": "Delete {count, plural, one {# message} other {# messages}}?",
"description": "delete selected messages > confirmation modal > title"
}, },
"icu:SelectModeActions__confirmDelete--description": { "icu:SelectModeActions__confirmDelete--description": {
"messageformat": "{count, plural, one {This message} other {These messages}} will be deleted from this device.", "messageformat": "{count, plural, one {This message} other {These messages}} will be deleted from this device.",
"description": "conversation > in select mode > composition area actions > delete selected messages > confirmation modal > message" "description": "(deleted 03/24/2023) conversation > in select mode > composition area actions > delete selected messages > confirmation modal > message"
},
"icu:ConfirmDeleteForMeModal--description": {
"messageformat": "{count, plural, one {This message} other {These messages}} will be deleted from this device.",
"description": "delete selected messages > confirmation modal > description"
}, },
"icu:SelectModeActions__confirmDelete--confirm": { "icu:SelectModeActions__confirmDelete--confirm": {
"messageformat": "Delete for me", "messageformat": "Delete for me",
"description": "conversation > in select mode > composition area actions > delete selected messages > confirmation modal > button" "description": "(deleted 03/24/2023) conversation > in select mode > composition area actions > delete selected messages > confirmation modal > button"
},
"icu:ConfirmDeleteForMeModal--confirm": {
"messageformat": "Delete for me",
"description": "delete selected messages > confirmation modal > button"
}, },
"icu:SelectModeActions__toast--TooManyMessagesToForward": { "icu:SelectModeActions__toast--TooManyMessagesToForward": {
"messageformat": "You can only forward up to 30 messages", "messageformat": "You can only forward up to 30 messages",

View file

@ -628,7 +628,7 @@ export async function startApp(): Promise<void> {
onTopOfEverything: true, onTopOfEverything: true,
cancelText: window.i18n('quit'), cancelText: window.i18n('quit'),
confirmStyle: 'negative', confirmStyle: 'negative',
message: window.i18n('deleteOldIndexedDBData'), title: window.i18n('deleteOldIndexedDBData'),
okText: window.i18n('deleteOldData'), okText: window.i18n('deleteOldData'),
reject: () => reject(), reject: () => reject(),
resolve: () => resolve(), resolve: () => resolve(),
@ -1712,21 +1712,32 @@ export async function startApp(): Promise<void> {
shiftKey && shiftKey &&
(key === 'd' || key === 'D') (key === 'd' || key === 'D')
) { ) {
const { targetedMessage } = state.conversations; const { forwardMessagesProps } = state.globalModals;
const { targetedMessage, selectedMessageIds } = state.conversations;
if (targetedMessage) { const messageIds =
selectedMessageIds ??
(targetedMessage != null ? [targetedMessage] : null);
if (forwardMessagesProps == null && messageIds != null) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
showConfirmationDialog({ showConfirmationDialog({
dialogName: 'deleteMessage', dialogName: 'ConfirmDeleteForMeModal',
confirmStyle: 'negative', confirmStyle: 'negative',
message: window.i18n('deleteWarning'), title: window.i18n('icu:ConfirmDeleteForMeModal--title', {
okText: window.i18n('delete'), count: messageIds.length,
}),
description: window.i18n(
'icu:ConfirmDeleteForMeModal--description',
{ count: messageIds.length }
),
okText: window.i18n('icu:ConfirmDeleteForMeModal--confirm'),
resolve: () => { resolve: () => {
window.reduxActions.conversations.deleteMessages({ window.reduxActions.conversations.deleteMessages({
conversationId: conversation.id, conversationId: conversation.id,
messageIds: [targetedMessage], messageIds,
}); });
}, },
}); });

View file

@ -163,7 +163,7 @@ const MESSAGE_SHORTCUTS: Array<ShortcutType> = [
keys: [['commandOrCtrl', 'S']], keys: [['commandOrCtrl', 'S']],
}, },
{ {
description: 'Keyboard--delete-message', description: 'Keyboard--delete-messages',
keys: [['commandOrCtrl', 'shift', 'D']], keys: [['commandOrCtrl', 'shift', 'D']],
}, },
]; ];

View file

@ -6,6 +6,9 @@ import { useEscapeHandling } from '../../hooks/useEscapeHandling';
export type PropsType = { export type PropsType = {
conversationId: string; conversationId: string;
hasOpenModal: boolean;
isSelectMode: boolean;
onExitSelectMode: () => void;
processAttachments: (options: { processAttachments: (options: {
conversationId: string; conversationId: string;
files: ReadonlyArray<File>; files: ReadonlyArray<File>;
@ -14,21 +17,18 @@ export type PropsType = {
renderConversationHeader: () => JSX.Element; renderConversationHeader: () => JSX.Element;
renderTimeline: () => JSX.Element; renderTimeline: () => JSX.Element;
renderPanel: () => JSX.Element | undefined; renderPanel: () => JSX.Element | undefined;
isSelectMode: boolean;
isForwardModalOpen: boolean;
onExitSelectMode: () => void;
}; };
export function ConversationView({ export function ConversationView({
conversationId, conversationId,
hasOpenModal,
isSelectMode,
onExitSelectMode,
processAttachments, processAttachments,
renderCompositionArea, renderCompositionArea,
renderConversationHeader, renderConversationHeader,
renderTimeline, renderTimeline,
renderPanel, renderPanel,
isSelectMode,
isForwardModalOpen,
onExitSelectMode,
}: PropsType): JSX.Element { }: PropsType): JSX.Element {
const onDrop = React.useCallback( const onDrop = React.useCallback(
(event: React.DragEvent<HTMLDivElement>) => { (event: React.DragEvent<HTMLDivElement>) => {
@ -88,7 +88,7 @@ export function ConversationView({
); );
useEscapeHandling( useEscapeHandling(
isSelectMode && !isForwardModalOpen ? onExitSelectMode : undefined isSelectMode && !hasOpenModal ? onExitSelectMode : undefined
); );
return ( return (

View file

@ -103,11 +103,11 @@ export default function SelectModeActions({
onDeleteMessages(); onDeleteMessages();
}, },
style: 'negative', style: 'negative',
text: i18n('icu:SelectModeActions__confirmDelete--confirm'), text: i18n('icu:ConfirmDeleteForMeModal--confirm'),
}, },
]} ]}
dialogName="TimelineMessage/deleteMessage" dialogName="ConfirmDeleteForMeModal"
title={i18n('icu:SelectModeActions__confirmDelete--title', { title={i18n('icu:ConfirmDeleteForMeModal--title', {
count: selectedMessageIds.length, count: selectedMessageIds.length,
})} })}
i18n={i18n} i18n={i18n}
@ -115,7 +115,7 @@ export default function SelectModeActions({
setConfirmDelete(false); setConfirmDelete(false);
}} }}
> >
{i18n('icu:SelectModeActions__confirmDelete--description', { {i18n('icu:ConfirmDeleteForMeModal--description', {
count: selectedMessageIds.length, count: selectedMessageIds.length,
})} })}
</ConfirmationDialog> </ConfirmationDialog>

View file

@ -369,14 +369,19 @@ export function TimelineMessage(props: Props): JSX.Element {
messageIds: [id], messageIds: [id],
}), }),
style: 'negative', style: 'negative',
text: i18n('delete'), text: i18n('icu:ConfirmDeleteForMeModal--confirm'),
}, },
]} ]}
dialogName="TimelineMessage/deleteMessage" dialogName="ConfirmDeleteForMeModal"
i18n={i18n} i18n={i18n}
onClose={() => setHasDeleteConfirmation(false)} onClose={() => setHasDeleteConfirmation(false)}
title={i18n('icu:ConfirmDeleteForMeModal--title', {
count: 1,
})}
> >
{i18n('deleteWarning')} {i18n('icu:ConfirmDeleteForMeModal--description', {
count: 1,
})}
</ConfirmationDialog> </ConfirmationDialog>
)} )}

View file

@ -29,6 +29,9 @@ import type { ShowToastActionType } from './toast';
// State // State
export type ConfirmDeleteForMeModalProps = ReadonlyDeep<{
count: number;
}>;
export type ForwardMessagePropsType = ReadonlyDeep< export type ForwardMessagePropsType = ReadonlyDeep<
Omit<PropsForMessage, 'renderingContext' | 'menu' | 'contextMenu'> Omit<PropsForMessage, 'renderingContext' | 'menu' | 'contextMenu'>
>; >;
@ -60,6 +63,7 @@ export type GlobalModalsStateType = ReadonlyDeep<{
}; };
forwardMessagesProps?: ForwardMessagesPropsType; forwardMessagesProps?: ForwardMessagesPropsType;
gv2MigrationProps?: MigrateToGV2PropsType; gv2MigrationProps?: MigrateToGV2PropsType;
hasConfirmationModal: boolean;
isProfileEditorVisible: boolean; isProfileEditorVisible: boolean;
isSignalConnectionsVisible: boolean; isSignalConnectionsVisible: boolean;
isShortcutGuideModalVisible: boolean; isShortcutGuideModalVisible: boolean;
@ -105,6 +109,7 @@ const SHOW_ERROR_MODAL = 'globalModals/SHOW_ERROR_MODAL';
const CLOSE_SHORTCUT_GUIDE_MODAL = 'globalModals/CLOSE_SHORTCUT_GUIDE_MODAL'; const CLOSE_SHORTCUT_GUIDE_MODAL = 'globalModals/CLOSE_SHORTCUT_GUIDE_MODAL';
const SHOW_SHORTCUT_GUIDE_MODAL = 'globalModals/SHOW_SHORTCUT_GUIDE_MODAL'; const SHOW_SHORTCUT_GUIDE_MODAL = 'globalModals/SHOW_SHORTCUT_GUIDE_MODAL';
const SHOW_AUTH_ART_CREATOR = 'globalModals/SHOW_AUTH_ART_CREATOR'; const SHOW_AUTH_ART_CREATOR = 'globalModals/SHOW_AUTH_ART_CREATOR';
const TOGGLE_CONFIRMATION_MODAL = 'globalModals/TOGGLE_CONFIRMATION_MODAL';
const CANCEL_AUTH_ART_CREATOR = 'globalModals/CANCEL_AUTH_ART_CREATOR'; const CANCEL_AUTH_ART_CREATOR = 'globalModals/CANCEL_AUTH_ART_CREATOR';
const CONFIRM_AUTH_ART_CREATOR_PENDING = const CONFIRM_AUTH_ART_CREATOR_PENDING =
'globalModals/CONFIRM_AUTH_ART_CREATOR_PENDING'; 'globalModals/CONFIRM_AUTH_ART_CREATOR_PENDING';
@ -180,6 +185,11 @@ type ToggleSignalConnectionsModalActionType = ReadonlyDeep<{
type: typeof TOGGLE_SIGNAL_CONNECTIONS_MODAL; type: typeof TOGGLE_SIGNAL_CONNECTIONS_MODAL;
}>; }>;
type ToggleConfirmationModalActionType = ReadonlyDeep<{
type: typeof TOGGLE_CONFIRMATION_MODAL;
payload: boolean;
}>;
type ShowStoriesSettingsActionType = ReadonlyDeep<{ type ShowStoriesSettingsActionType = ReadonlyDeep<{
type: typeof SHOW_STORIES_SETTINGS; type: typeof SHOW_STORIES_SETTINGS;
}>; }>;
@ -283,6 +293,7 @@ export type GlobalModalsActionType = ReadonlyDeep<
| ToggleSafetyNumberModalActionType | ToggleSafetyNumberModalActionType
| ToggleAddUserToAnotherGroupModalActionType | ToggleAddUserToAnotherGroupModalActionType
| ToggleSignalConnectionsModalActionType | ToggleSignalConnectionsModalActionType
| ToggleConfirmationModalActionType
>; >;
// Action Creators // Action Creators
@ -304,6 +315,7 @@ export const actions = {
toggleSafetyNumberModal, toggleSafetyNumberModal,
toggleAddUserToAnotherGroupModal, toggleAddUserToAnotherGroupModal,
toggleSignalConnectionsModal, toggleSignalConnectionsModal,
toggleConfirmationModal,
showGV2MigrationDialog, showGV2MigrationDialog,
closeGV2MigrationDialog, closeGV2MigrationDialog,
showStickerPackPreview, showStickerPackPreview,
@ -500,6 +512,15 @@ function toggleSignalConnectionsModal(): ToggleSignalConnectionsModalActionType
}; };
} }
function toggleConfirmationModal(
isOpen: boolean
): ToggleConfirmationModalActionType {
return {
type: TOGGLE_CONFIRMATION_MODAL,
payload: isOpen,
};
}
function showBlockingSafetyNumberChangeDialog( function showBlockingSafetyNumberChangeDialog(
untrustedByConversation: RecipientsByConversation, untrustedByConversation: RecipientsByConversation,
explodedPromise: ExplodePromiseResultType<boolean>, explodedPromise: ExplodePromiseResultType<boolean>,
@ -663,6 +684,7 @@ export function confirmAuthorizeArtCreator(): ThunkAction<
export function getEmptyState(): GlobalModalsStateType { export function getEmptyState(): GlobalModalsStateType {
return { return {
hasConfirmationModal: false,
isProfileEditorVisible: false, isProfileEditorVisible: false,
isShortcutGuideModalVisible: false, isShortcutGuideModalVisible: false,
isSignalConnectionsVisible: false, isSignalConnectionsVisible: false,
@ -776,6 +798,13 @@ export function reducer(
}; };
} }
if (action.type === TOGGLE_CONFIRMATION_MODAL) {
return {
...state,
hasConfirmationModal: action.payload,
};
}
if (action.type === SHOW_SEND_ANYWAY_DIALOG) { if (action.type === SHOW_SEND_ANYWAY_DIALOG) {
const { promiseUuid, source } = action.payload; const { promiseUuid, source } = action.payload;

View file

@ -48,13 +48,21 @@ export function SmartConversationView(): JSX.Element {
const { processAttachments } = useComposerActions(); const { processAttachments } = useComposerActions();
const i18n = useSelector(getIntl); const i18n = useSelector(getIntl);
const isForwardModalOpen = useSelector((state: StateType) => { const hasOpenModal = useSelector((state: StateType) => {
return state.globalModals.forwardMessagesProps != null; return (
state.globalModals.forwardMessagesProps != null ||
state.globalModals.hasConfirmationModal
);
}); });
return ( return (
<ConversationView <ConversationView
conversationId={conversationId} conversationId={conversationId}
hasOpenModal={hasOpenModal}
isSelectMode={isSelectMode}
onExitSelectMode={() => {
toggleSelectMode(false);
}}
processAttachments={processAttachments} processAttachments={processAttachments}
renderCompositionArea={() => <SmartCompositionArea id={conversationId} />} renderCompositionArea={() => <SmartCompositionArea id={conversationId} />}
renderConversationHeader={() => ( renderConversationHeader={() => (
@ -179,11 +187,6 @@ export function SmartConversationView(): JSX.Element {
return undefined; return undefined;
}} }}
isSelectMode={isSelectMode}
isForwardModalOpen={isForwardModalOpen}
onExitSelectMode={() => {
toggleSelectMode(false);
}}
/> />
); );
} }

View file

@ -10,7 +10,8 @@ type ConfirmationDialogViewProps = {
dialogName: string; dialogName: string;
cancelText?: string; cancelText?: string;
confirmStyle?: 'affirmative' | 'negative'; confirmStyle?: 'affirmative' | 'negative';
message: string; title: string;
description?: string;
okText: string; okText: string;
reject?: (error: Error) => void; reject?: (error: Error) => void;
resolve: () => void; resolve: () => void;
@ -24,6 +25,8 @@ function removeConfirmationDialog() {
return; return;
} }
window.reduxActions.globalModals.toggleConfirmationModal(false);
unmountComponentAtNode(confirmationDialogViewNode); unmountComponentAtNode(confirmationDialogViewNode);
document.body.removeChild(confirmationDialogViewNode); document.body.removeChild(confirmationDialogViewNode);
@ -43,6 +46,8 @@ export function showConfirmationDialog(
removeConfirmationDialog(); removeConfirmationDialog();
} }
window.reduxActions.globalModals.toggleConfirmationModal(true);
confirmationDialogViewNode = document.createElement('div'); confirmationDialogViewNode = document.createElement('div');
document.body.appendChild(confirmationDialogViewNode); document.body.appendChild(confirmationDialogViewNode);
@ -71,8 +76,10 @@ export function showConfirmationDialog(
onClose={() => { onClose={() => {
removeConfirmationDialog(); removeConfirmationDialog();
}} }}
title={options.message} title={options.title}
/>, >
{options.description}
</ConfirmationDialog>,
confirmationDialogViewNode confirmationDialogViewNode
); );
} }