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": {
"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"
},
"Keyboard--add-newline": {
@ -4653,15 +4657,27 @@
},
"icu:SelectModeActions__confirmDelete--title": {
"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": {
"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": {
"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": {
"messageformat": "You can only forward up to 30 messages",

View file

@ -628,7 +628,7 @@ export async function startApp(): Promise<void> {
onTopOfEverything: true,
cancelText: window.i18n('quit'),
confirmStyle: 'negative',
message: window.i18n('deleteOldIndexedDBData'),
title: window.i18n('deleteOldIndexedDBData'),
okText: window.i18n('deleteOldData'),
reject: () => reject(),
resolve: () => resolve(),
@ -1712,21 +1712,32 @@ export async function startApp(): Promise<void> {
shiftKey &&
(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.stopPropagation();
showConfirmationDialog({
dialogName: 'deleteMessage',
dialogName: 'ConfirmDeleteForMeModal',
confirmStyle: 'negative',
message: window.i18n('deleteWarning'),
okText: window.i18n('delete'),
title: window.i18n('icu:ConfirmDeleteForMeModal--title', {
count: messageIds.length,
}),
description: window.i18n(
'icu:ConfirmDeleteForMeModal--description',
{ count: messageIds.length }
),
okText: window.i18n('icu:ConfirmDeleteForMeModal--confirm'),
resolve: () => {
window.reduxActions.conversations.deleteMessages({
conversationId: conversation.id,
messageIds: [targetedMessage],
messageIds,
});
},
});

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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