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": {
|
"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",
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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']],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue