Add shortcuts for forward/delete selected/targeted messages
This commit is contained in:
parent
dd16be13b0
commit
d0f17a1398
9 changed files with 106 additions and 35 deletions
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -163,7 +163,7 @@ const MESSAGE_SHORTCUTS: Array<ShortcutType> = [
|
|||
keys: [['commandOrCtrl', 'S']],
|
||||
},
|
||||
{
|
||||
description: 'Keyboard--delete-message',
|
||||
description: 'Keyboard--delete-messages',
|
||||
keys: [['commandOrCtrl', 'shift', 'D']],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue