Merge delete for me/everyone into one modal
This commit is contained in:
parent
c956c0e025
commit
822b162136
43 changed files with 658 additions and 672 deletions
|
@ -2383,7 +2383,7 @@
|
||||||
},
|
},
|
||||||
"icu:deleteMessage": {
|
"icu:deleteMessage": {
|
||||||
"messageformat": "Delete message for me",
|
"messageformat": "Delete message for me",
|
||||||
"description": "Shown on the drop-down menu for an individual message, deletes single message"
|
"description": "(deleted 04/06/2023) Shown on the drop-down menu for an individual message, deletes single message"
|
||||||
},
|
},
|
||||||
"deleteMessageForEveryone": {
|
"deleteMessageForEveryone": {
|
||||||
"message": "Delete message for everyone",
|
"message": "Delete message for everyone",
|
||||||
|
@ -2391,7 +2391,11 @@
|
||||||
},
|
},
|
||||||
"icu:deleteMessageForEveryone": {
|
"icu:deleteMessageForEveryone": {
|
||||||
"messageformat": "Delete message for everyone",
|
"messageformat": "Delete message for everyone",
|
||||||
"description": "Shown on the drop-down menu for an individual message, deletes single message for everyone"
|
"description": "(deleted 04/06/2023) Shown on the drop-down menu for an individual message, deletes single message for everyone"
|
||||||
|
},
|
||||||
|
"icu:MessageContextMenu__deleteMessage": {
|
||||||
|
"messageformat": "Delete message",
|
||||||
|
"description": "Show on the drop-down menu for an individual message, opens a modal to select if you want to 'delete for me' or 'delete for everyone'"
|
||||||
},
|
},
|
||||||
"deleteMessages": {
|
"deleteMessages": {
|
||||||
"message": "Delete",
|
"message": "Delete",
|
||||||
|
@ -8953,6 +8957,10 @@
|
||||||
},
|
},
|
||||||
"icu:ConfirmDeleteForMeModal--title": {
|
"icu:ConfirmDeleteForMeModal--title": {
|
||||||
"messageformat": "Delete {count, plural, one {# message} other {# messages}}?",
|
"messageformat": "Delete {count, plural, one {# message} other {# messages}}?",
|
||||||
|
"description": "(deleted 04/06/2023) delete selected messages > confirmation modal > title"
|
||||||
|
},
|
||||||
|
"icu:DeleteMessagesModal--title": {
|
||||||
|
"messageformat": "Delete {count, plural, one {message} other {# messages}}?",
|
||||||
"description": "delete selected messages > confirmation modal > title"
|
"description": "delete selected messages > confirmation modal > title"
|
||||||
},
|
},
|
||||||
"icu:SelectModeActions__confirmDelete--description": {
|
"icu:SelectModeActions__confirmDelete--description": {
|
||||||
|
@ -8961,6 +8969,10 @@
|
||||||
},
|
},
|
||||||
"icu:ConfirmDeleteForMeModal--description": {
|
"icu:ConfirmDeleteForMeModal--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": "(deleted 04/06/2023) delete selected messages > confirmation modal > description"
|
||||||
|
},
|
||||||
|
"icu:DeleteMessagesModal--description": {
|
||||||
|
"messageformat": "Who would you like to delete {count, plural, one {this message} other {these messages}} for?",
|
||||||
"description": "delete selected messages > confirmation modal > description"
|
"description": "delete selected messages > confirmation modal > description"
|
||||||
},
|
},
|
||||||
"icu:SelectModeActions__confirmDelete--confirm": {
|
"icu:SelectModeActions__confirmDelete--confirm": {
|
||||||
|
@ -8969,7 +8981,19 @@
|
||||||
},
|
},
|
||||||
"icu:ConfirmDeleteForMeModal--confirm": {
|
"icu:ConfirmDeleteForMeModal--confirm": {
|
||||||
"messageformat": "Delete for me",
|
"messageformat": "Delete for me",
|
||||||
"description": "delete selected messages > confirmation modal > button"
|
"description": "(deleted 03/24/2023) delete selected messages > confirmation modal > button"
|
||||||
|
},
|
||||||
|
"icu:DeleteMessagesModal--deleteForMe": {
|
||||||
|
"messageformat": "Delete for me",
|
||||||
|
"description": "delete selected messages > confirmation modal > delete for me"
|
||||||
|
},
|
||||||
|
"icu:DeleteMessagesModal--deleteForEveryone": {
|
||||||
|
"messageformat": "Delete for everyone",
|
||||||
|
"description": "delete selected messages > confirmation modal > delete for everyone"
|
||||||
|
},
|
||||||
|
"icu:DeleteMessagesModal__toast--TooManyMessagesToDeleteForEveryone": {
|
||||||
|
"messageformat": "You can only select up to {count, plural, one {# message} other {# messages}} to delete for everyone",
|
||||||
|
"description": "delete selected messages > confirmation modal > deleted for everyone (disabled) > toast > too many messages to 'delete for everyone'"
|
||||||
},
|
},
|
||||||
"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",
|
||||||
|
|
6
stylesheets/components/DeleteMessagesModal.scss
Normal file
6
stylesheets/components/DeleteMessagesModal.scss
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
.DeleteMessagesModal__ModalHost__width-container {
|
||||||
|
min-width: fit-content;
|
||||||
|
}
|
|
@ -71,6 +71,7 @@
|
||||||
@import './components/CustomColorEditor.scss';
|
@import './components/CustomColorEditor.scss';
|
||||||
@import './components/CustomizingPreferredReactionsModal.scss';
|
@import './components/CustomizingPreferredReactionsModal.scss';
|
||||||
@import './components/DebugLogWindow.scss';
|
@import './components/DebugLogWindow.scss';
|
||||||
|
@import './components/DeleteMessagesModal.scss';
|
||||||
@import './components/DisappearingTimeDialog.scss';
|
@import './components/DisappearingTimeDialog.scss';
|
||||||
@import './components/DisappearingTimerSelect.scss';
|
@import './components/DisappearingTimerSelect.scss';
|
||||||
@import './components/EditConversationAttributesModal.scss';
|
@import './components/EditConversationAttributesModal.scss';
|
||||||
|
|
|
@ -1730,22 +1730,13 @@ export async function startApp(): Promise<void> {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
showConfirmationDialog({
|
window.reduxActions.globalModals.toggleDeleteMessagesModal({
|
||||||
dialogName: 'ConfirmDeleteForMeModal',
|
conversationId: conversation.id,
|
||||||
confirmStyle: 'negative',
|
messageIds,
|
||||||
title: window.i18n('icu:ConfirmDeleteForMeModal--title', {
|
onDelete() {
|
||||||
count: messageIds.length,
|
if (selectedMessageIds != null) {
|
||||||
}),
|
window.reduxActions.conversations.toggleSelectMode(false);
|
||||||
description: window.i18n(
|
}
|
||||||
'icu:ConfirmDeleteForMeModal--description',
|
|
||||||
{ count: messageIds.length }
|
|
||||||
),
|
|
||||||
okText: window.i18n('icu:ConfirmDeleteForMeModal--confirm'),
|
|
||||||
resolve: () => {
|
|
||||||
window.reduxActions.conversations.deleteMessages({
|
|
||||||
conversationId: conversation.id,
|
|
||||||
messageIds,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1771,7 +1762,12 @@ export async function startApp(): Promise<void> {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
window.reduxActions.globalModals.toggleForwardMessagesModal(
|
window.reduxActions.globalModals.toggleForwardMessagesModal(
|
||||||
messageIds
|
messageIds,
|
||||||
|
() => {
|
||||||
|
if (selectedMessageIds != null) {
|
||||||
|
window.reduxActions.conversations.toggleSelectMode(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -8,11 +8,7 @@ import Measure from 'react-measure';
|
||||||
import type { ListRowProps } from 'react-virtualized';
|
import type { ListRowProps } from 'react-virtualized';
|
||||||
|
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type {
|
import type { LocalizerType, ThemeType } from '../types/Util';
|
||||||
LocalizerType,
|
|
||||||
ReplacementValuesType,
|
|
||||||
ThemeType,
|
|
||||||
} from '../types/Util';
|
|
||||||
import { ToastType } from '../types/Toast';
|
import { ToastType } from '../types/Toast';
|
||||||
import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations';
|
import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations';
|
||||||
import { ConfirmationDialog } from './ConfirmationDialog';
|
import { ConfirmationDialog } from './ConfirmationDialog';
|
||||||
|
@ -26,6 +22,7 @@ import { SearchInput } from './SearchInput';
|
||||||
import { useRestoreFocus } from '../hooks/useRestoreFocus';
|
import { useRestoreFocus } from '../hooks/useRestoreFocus';
|
||||||
import { ListView } from './ListView';
|
import { ListView } from './ListView';
|
||||||
import { ListTile } from './ListTile';
|
import { ListTile } from './ListTile';
|
||||||
|
import type { ShowToastAction } from '../state/ducks/toast';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
@ -45,7 +42,7 @@ type DispatchProps = {
|
||||||
onFailure?: () => unknown;
|
onFailure?: () => unknown;
|
||||||
}
|
}
|
||||||
) => void;
|
) => void;
|
||||||
showToast: (toastType: ToastType, parameters?: ReplacementValuesType) => void;
|
showToast: ShowToastAction;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Props = OwnProps & DispatchProps;
|
export type Props = OwnProps & DispatchProps;
|
||||||
|
@ -225,14 +222,20 @@ export function AddUserToAnotherGroupModal({
|
||||||
text: i18n('icu:AddUserToAnotherGroupModal__confirm-add'),
|
text: i18n('icu:AddUserToAnotherGroupModal__confirm-add'),
|
||||||
style: 'affirmative',
|
style: 'affirmative',
|
||||||
action: () => {
|
action: () => {
|
||||||
showToast(ToastType.AddingUserToGroup, {
|
showToast({
|
||||||
contact: contact.title,
|
toastType: ToastType.AddingUserToGroup,
|
||||||
|
parameters: {
|
||||||
|
contact: contact.title,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
addMembersToGroup(selectedGroupId, [contact.id], {
|
addMembersToGroup(selectedGroupId, [contact.id], {
|
||||||
onSuccess: () =>
|
onSuccess: () =>
|
||||||
showToast(ToastType.UserAddedToGroup, {
|
showToast({
|
||||||
contact: contact.title,
|
toastType: ToastType.UserAddedToGroup,
|
||||||
group: selectedGroup.title,
|
parameters: {
|
||||||
|
contact: contact.title,
|
||||||
|
group: selectedGroup.title,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
toggleAddUserToAnotherGroupModal(undefined);
|
toggleAddUserToAnotherGroupModal(undefined);
|
||||||
|
|
|
@ -7,9 +7,9 @@ import classNames from 'classnames';
|
||||||
|
|
||||||
import type { ExecuteMenuRoleType } from './TitleBarContainer';
|
import type { ExecuteMenuRoleType } from './TitleBarContainer';
|
||||||
import type { MenuOptionsType, MenuActionType } from '../types/menu';
|
import type { MenuOptionsType, MenuActionType } from '../types/menu';
|
||||||
import type { ToastType } from '../types/Toast';
|
import type { AnyToast } from '../types/Toast';
|
||||||
import type { ViewStoryActionCreatorType } from '../state/ducks/stories';
|
import type { ViewStoryActionCreatorType } from '../state/ducks/stories';
|
||||||
import type { LocalizerType, ReplacementValuesType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import { ThemeType } from '../types/Util';
|
import { ThemeType } from '../types/Util';
|
||||||
import { AppViewType } from '../state/ducks/app';
|
import { AppViewType } from '../state/ducks/app';
|
||||||
import { SmartInstallScreen } from '../state/smart/InstallScreen';
|
import { SmartInstallScreen } from '../state/smart/InstallScreen';
|
||||||
|
@ -51,10 +51,7 @@ type PropsType = {
|
||||||
executeMenuAction: (action: MenuActionType) => void;
|
executeMenuAction: (action: MenuActionType) => void;
|
||||||
hideToast: () => unknown;
|
hideToast: () => unknown;
|
||||||
titleBarDoubleClick: () => void;
|
titleBarDoubleClick: () => void;
|
||||||
toast?: {
|
toast?: AnyToast;
|
||||||
toastType: ToastType;
|
|
||||||
parameters?: ReplacementValuesType;
|
|
||||||
};
|
|
||||||
scrollToMessage: (conversationId: string, messageId: string) => unknown;
|
scrollToMessage: (conversationId: string, messageId: string) => unknown;
|
||||||
toggleStoriesView: () => unknown;
|
toggleStoriesView: () => unknown;
|
||||||
viewStory: ViewStoryActionCreatorType;
|
viewStory: ViewStoryActionCreatorType;
|
||||||
|
|
|
@ -132,7 +132,6 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
renderSmartCompositionRecordingDraft: _ => <div>RECORDING DRAFT</div>,
|
renderSmartCompositionRecordingDraft: _ => <div>RECORDING DRAFT</div>,
|
||||||
// Select mode
|
// Select mode
|
||||||
selectedMessageIds: undefined,
|
selectedMessageIds: undefined,
|
||||||
lastSelectedMessage: undefined,
|
|
||||||
toggleSelectMode: action('toggleSelectMode'),
|
toggleSelectMode: action('toggleSelectMode'),
|
||||||
toggleForwardMessagesModal: action('toggleForwardMessagesModal'),
|
toggleForwardMessagesModal: action('toggleForwardMessagesModal'),
|
||||||
});
|
});
|
||||||
|
|
|
@ -39,7 +39,6 @@ import { AudioCapture } from './conversation/AudioCapture';
|
||||||
import { CompositionUpload } from './CompositionUpload';
|
import { CompositionUpload } from './CompositionUpload';
|
||||||
import type {
|
import type {
|
||||||
ConversationType,
|
ConversationType,
|
||||||
MessageTimestamps,
|
|
||||||
PushPanelForConversationActionType,
|
PushPanelForConversationActionType,
|
||||||
ShowConversationType,
|
ShowConversationType,
|
||||||
} from '../state/ducks/conversations';
|
} from '../state/ducks/conversations';
|
||||||
|
@ -149,7 +148,6 @@ export type OwnProps = Readonly<{
|
||||||
props: SmartCompositionRecordingDraftProps
|
props: SmartCompositionRecordingDraftProps
|
||||||
) => JSX.Element | null;
|
) => JSX.Element | null;
|
||||||
selectedMessageIds: ReadonlyArray<string> | undefined;
|
selectedMessageIds: ReadonlyArray<string> | undefined;
|
||||||
lastSelectedMessage: MessageTimestamps | undefined;
|
|
||||||
toggleSelectMode: (on: boolean) => void;
|
toggleSelectMode: (on: boolean) => void;
|
||||||
toggleForwardMessagesModal: (
|
toggleForwardMessagesModal: (
|
||||||
messageIds: ReadonlyArray<string>,
|
messageIds: ReadonlyArray<string>,
|
||||||
|
@ -287,7 +285,6 @@ export function CompositionArea({
|
||||||
renderSmartCompositionRecordingDraft,
|
renderSmartCompositionRecordingDraft,
|
||||||
// Selected messages
|
// Selected messages
|
||||||
selectedMessageIds,
|
selectedMessageIds,
|
||||||
lastSelectedMessage,
|
|
||||||
toggleSelectMode,
|
toggleSelectMode,
|
||||||
toggleForwardMessagesModal,
|
toggleForwardMessagesModal,
|
||||||
}: Props): JSX.Element | null {
|
}: Props): JSX.Element | null {
|
||||||
|
@ -560,12 +557,13 @@ export function CompositionArea({
|
||||||
toggleSelectMode(false);
|
toggleSelectMode(false);
|
||||||
}}
|
}}
|
||||||
onDeleteMessages={() => {
|
onDeleteMessages={() => {
|
||||||
window.reduxActions.conversations.deleteMessages({
|
window.reduxActions.globalModals.toggleDeleteMessagesModal({
|
||||||
conversationId,
|
conversationId,
|
||||||
lastSelectedMessage,
|
|
||||||
messageIds: selectedMessageIds,
|
messageIds: selectedMessageIds,
|
||||||
|
onDelete() {
|
||||||
|
toggleSelectMode(false);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
toggleSelectMode(false);
|
|
||||||
}}
|
}}
|
||||||
onForwardMessages={() => {
|
onForwardMessages={() => {
|
||||||
if (selectedMessageIds.length > 0) {
|
if (selectedMessageIds.length > 0) {
|
||||||
|
|
|
@ -16,6 +16,8 @@ export type ActionSpec = {
|
||||||
action: () => unknown;
|
action: () => unknown;
|
||||||
style?: 'affirmative' | 'negative';
|
style?: 'affirmative' | 'negative';
|
||||||
autoClose?: boolean;
|
autoClose?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
'aria-disabled'?: boolean;
|
||||||
} & (
|
} & (
|
||||||
| {
|
| {
|
||||||
text: string;
|
text: string;
|
||||||
|
@ -130,7 +132,8 @@ export const ConfirmationDialog = React.memo(function ConfirmationDialogInner({
|
||||||
? action.id ?? action.text
|
? action.id ?? action.text
|
||||||
: action.id
|
: action.id
|
||||||
}
|
}
|
||||||
disabled={isSpinning}
|
disabled={action.disabled || isSpinning}
|
||||||
|
aria-disabled={action['aria-disabled']}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
action.action();
|
action.action();
|
||||||
if (action.autoClose !== false) {
|
if (action.autoClose !== false) {
|
||||||
|
@ -165,6 +168,9 @@ export const ConfirmationDialog = React.memo(function ConfirmationDialogInner({
|
||||||
onTopOfEverything={onTopOfEverything}
|
onTopOfEverything={onTopOfEverything}
|
||||||
overlayStyles={overlayStyles}
|
overlayStyles={overlayStyles}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
moduleClassName={
|
||||||
|
moduleClassName ? `${moduleClassName}__ModalHost` : undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<animated.div style={modalStyles}>
|
<animated.div style={modalStyles}>
|
||||||
<ModalPage
|
<ModalPage
|
||||||
|
|
76
ts/components/DeleteMessagesModal.tsx
Normal file
76
ts/components/DeleteMessagesModal.tsx
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import type { ActionSpec } from './ConfirmationDialog';
|
||||||
|
import { ConfirmationDialog } from './ConfirmationDialog';
|
||||||
|
import type { LocalizerType } from '../types/Util';
|
||||||
|
import type { ShowToastAction } from '../state/ducks/toast';
|
||||||
|
import { ToastType } from '../types/Toast';
|
||||||
|
|
||||||
|
type DeleteMessagesModalProps = Readonly<{
|
||||||
|
canDeleteForEveryone: boolean;
|
||||||
|
i18n: LocalizerType;
|
||||||
|
messageCount: number;
|
||||||
|
onClose: () => void;
|
||||||
|
onDeleteForMe: () => void;
|
||||||
|
onDeleteForEveryone: () => void;
|
||||||
|
showToast: ShowToastAction;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const MAX_DELETE_FOR_EVERYONE = 30;
|
||||||
|
|
||||||
|
export default function DeleteMessagesModal({
|
||||||
|
canDeleteForEveryone,
|
||||||
|
i18n,
|
||||||
|
messageCount,
|
||||||
|
onClose,
|
||||||
|
onDeleteForMe,
|
||||||
|
onDeleteForEveryone,
|
||||||
|
showToast,
|
||||||
|
}: DeleteMessagesModalProps): JSX.Element {
|
||||||
|
const actions: Array<ActionSpec> = [];
|
||||||
|
|
||||||
|
actions.push({
|
||||||
|
action: onDeleteForMe,
|
||||||
|
style: 'negative',
|
||||||
|
text: i18n('icu:DeleteMessagesModal--deleteForMe'),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canDeleteForEveryone) {
|
||||||
|
const tooManyMessages = messageCount > MAX_DELETE_FOR_EVERYONE;
|
||||||
|
actions.push({
|
||||||
|
'aria-disabled': tooManyMessages,
|
||||||
|
autoClose: !tooManyMessages,
|
||||||
|
action: () => {
|
||||||
|
if (tooManyMessages) {
|
||||||
|
showToast({
|
||||||
|
toastType: ToastType.TooManyMessagesToDeleteForEveryone,
|
||||||
|
parameters: { count: MAX_DELETE_FOR_EVERYONE },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
onDeleteForEveryone();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: 'negative',
|
||||||
|
text: i18n('icu:DeleteMessagesModal--deleteForEveryone'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfirmationDialog
|
||||||
|
actions={actions}
|
||||||
|
dialogName="ConfirmDeleteForMeModal"
|
||||||
|
i18n={i18n}
|
||||||
|
onClose={onClose}
|
||||||
|
title={i18n('icu:DeleteMessagesModal--title', {
|
||||||
|
count: messageCount,
|
||||||
|
})}
|
||||||
|
moduleClassName="DeleteMessagesModal"
|
||||||
|
>
|
||||||
|
{i18n('icu:DeleteMessagesModal--description', {
|
||||||
|
count: messageCount,
|
||||||
|
})}
|
||||||
|
</ConfirmationDialog>
|
||||||
|
);
|
||||||
|
}
|
|
@ -38,7 +38,7 @@ export class ErrorBoundary extends React.PureComponent<Props, State> {
|
||||||
`\nerrorInfo: ${errorInfo.componentStack}`
|
`\nerrorInfo: ${errorInfo.componentStack}`
|
||||||
);
|
);
|
||||||
if (window.reduxActions) {
|
if (window.reduxActions) {
|
||||||
window.reduxActions.toast.showToast(ToastType.Error);
|
window.reduxActions.toast.showToast({ toastType: ToastType.Error });
|
||||||
}
|
}
|
||||||
if (closeView) {
|
if (closeView) {
|
||||||
closeView();
|
closeView();
|
||||||
|
|
|
@ -127,7 +127,7 @@ export function ForwardMessagesModal({
|
||||||
|
|
||||||
const forwardMessages = React.useCallback(() => {
|
const forwardMessages = React.useCallback(() => {
|
||||||
if (!canForwardMessages) {
|
if (!canForwardMessages) {
|
||||||
showToast(ToastType.CannotForwardEmptyMessage);
|
showToast({ toastType: ToastType.CannotForwardEmptyMessage });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const conversationIds = selectedContacts.map(contact => contact.id);
|
const conversationIds = selectedContacts.map(contact => contact.id);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import React from 'react';
|
||||||
import type {
|
import type {
|
||||||
AuthorizeArtCreatorDataType,
|
AuthorizeArtCreatorDataType,
|
||||||
ContactModalStateType,
|
ContactModalStateType,
|
||||||
|
DeleteMessagesPropsType,
|
||||||
EditHistoryMessagesType,
|
EditHistoryMessagesType,
|
||||||
ForwardMessagesPropsType,
|
ForwardMessagesPropsType,
|
||||||
SafetyNumberChangedBlockingDataType,
|
SafetyNumberChangedBlockingDataType,
|
||||||
|
@ -38,6 +39,9 @@ export type PropsType = {
|
||||||
description?: string;
|
description?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
}) => JSX.Element;
|
}) => JSX.Element;
|
||||||
|
// DeleteMessageModal
|
||||||
|
deleteMessagesProps: DeleteMessagesPropsType | undefined;
|
||||||
|
renderDeleteMessagesModal: () => JSX.Element;
|
||||||
// ForwardMessageModal
|
// ForwardMessageModal
|
||||||
forwardMessagesProps: ForwardMessagesPropsType | undefined;
|
forwardMessagesProps: ForwardMessagesPropsType | undefined;
|
||||||
renderForwardMessagesModal: () => JSX.Element;
|
renderForwardMessagesModal: () => JSX.Element;
|
||||||
|
@ -92,6 +96,9 @@ export function GlobalModalContainer({
|
||||||
// ErrorModal
|
// ErrorModal
|
||||||
errorModalProps,
|
errorModalProps,
|
||||||
renderErrorModal,
|
renderErrorModal,
|
||||||
|
// DeleteMessageModal
|
||||||
|
deleteMessagesProps,
|
||||||
|
renderDeleteMessagesModal,
|
||||||
// ForwardMessageModal
|
// ForwardMessageModal
|
||||||
forwardMessagesProps,
|
forwardMessagesProps,
|
||||||
renderForwardMessagesModal,
|
renderForwardMessagesModal,
|
||||||
|
@ -158,6 +165,10 @@ export function GlobalModalContainer({
|
||||||
return renderEditHistoryMessagesModal();
|
return renderEditHistoryMessagesModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (deleteMessagesProps) {
|
||||||
|
return renderDeleteMessagesModal();
|
||||||
|
}
|
||||||
|
|
||||||
if (forwardMessagesProps) {
|
if (forwardMessagesProps) {
|
||||||
return renderForwardMessagesModal();
|
return renderForwardMessagesModal();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import classNames from 'classnames';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import type { MyStoryType, StoryViewType } from '../types/Stories';
|
import type { MyStoryType, StoryViewType } from '../types/Stories';
|
||||||
import type { ShowToastActionCreatorType } from '../state/ducks/toast';
|
import type { ShowToastAction } from '../state/ducks/toast';
|
||||||
import { Avatar, AvatarSize } from './Avatar';
|
import { Avatar, AvatarSize } from './Avatar';
|
||||||
import { HasStories, ResolvedSendStatus } from '../types/Stories';
|
import { HasStories, ResolvedSendStatus } from '../types/Stories';
|
||||||
import { MessageTimestamp } from './conversation/MessageTimestamp';
|
import { MessageTimestamp } from './conversation/MessageTimestamp';
|
||||||
|
@ -24,7 +24,7 @@ export type PropsType = {
|
||||||
onClick: () => unknown;
|
onClick: () => unknown;
|
||||||
onMediaPlaybackStart: () => void;
|
onMediaPlaybackStart: () => void;
|
||||||
queueStoryDownload: (storyId: string) => unknown;
|
queueStoryDownload: (storyId: string) => unknown;
|
||||||
showToast: ShowToastActionCreatorType;
|
showToast: ShowToastAction;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getNewestMyStory(story: MyStoryType): StoryViewType {
|
function getNewestMyStory(story: MyStoryType): StoryViewType {
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { PanelRow } from './conversation/conversation-details/PanelRow';
|
||||||
import type { ProfileDataType } from '../state/ducks/conversations';
|
import type { ProfileDataType } from '../state/ducks/conversations';
|
||||||
import { UsernameEditState } from '../state/ducks/usernameEnums';
|
import { UsernameEditState } from '../state/ducks/usernameEnums';
|
||||||
import { ToastType } from '../types/Toast';
|
import { ToastType } from '../types/Toast';
|
||||||
import type { ShowToastActionCreatorType } from '../state/ducks/toast';
|
import type { ShowToastAction } from '../state/ducks/toast';
|
||||||
import { getEmojiData, unifiedToEmoji } from './emoji/lib';
|
import { getEmojiData, unifiedToEmoji } from './emoji/lib';
|
||||||
import { assertDev } from '../util/assert';
|
import { assertDev } from '../util/assert';
|
||||||
import { missingCaseError } from '../util/missingCaseError';
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
|
@ -85,7 +85,7 @@ type PropsActionType = {
|
||||||
saveAvatarToDisk: SaveAvatarToDiskActionType;
|
saveAvatarToDisk: SaveAvatarToDiskActionType;
|
||||||
setUsernameEditState: (editState: UsernameEditState) => void;
|
setUsernameEditState: (editState: UsernameEditState) => void;
|
||||||
deleteUsername: () => void;
|
deleteUsername: () => void;
|
||||||
showToast: ShowToastActionCreatorType;
|
showToast: ShowToastAction;
|
||||||
openUsernameReservationModal: () => void;
|
openUsernameReservationModal: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -525,7 +525,7 @@ export function ProfileEditor({
|
||||||
'Should not be visible without username'
|
'Should not be visible without username'
|
||||||
);
|
);
|
||||||
void window.navigator.clipboard.writeText(username);
|
void window.navigator.clipboard.writeText(username);
|
||||||
showToast(ToastType.CopiedUsername);
|
showToast({ toastType: ToastType.CopiedUsername });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -540,7 +540,7 @@ export function ProfileEditor({
|
||||||
void window.navigator.clipboard.writeText(
|
void window.navigator.clipboard.writeText(
|
||||||
generateUsernameLink(username)
|
generateUsernameLink(username)
|
||||||
);
|
);
|
||||||
showToast(ToastType.CopiedUsernameLink);
|
showToast({ toastType: ToastType.CopiedUsernameLink });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,7 +14,7 @@ import type {
|
||||||
} from '../types/Stories';
|
} from '../types/Stories';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||||
import type { ShowToastActionCreatorType } from '../state/ducks/toast';
|
import type { ShowToastAction } from '../state/ducks/toast';
|
||||||
import type {
|
import type {
|
||||||
AddStoryData,
|
AddStoryData,
|
||||||
ViewUserStoriesActionCreatorType,
|
ViewUserStoriesActionCreatorType,
|
||||||
|
@ -48,7 +48,7 @@ export type PropsType = {
|
||||||
setAddStoryData: (data: AddStoryData) => unknown;
|
setAddStoryData: (data: AddStoryData) => unknown;
|
||||||
showConversation: ShowConversationType;
|
showConversation: ShowConversationType;
|
||||||
showStoriesSettings: () => unknown;
|
showStoriesSettings: () => unknown;
|
||||||
showToast: ShowToastActionCreatorType;
|
showToast: ShowToastAction;
|
||||||
stories: Array<ConversationStoryType>;
|
stories: Array<ConversationStoryType>;
|
||||||
toggleHideStories: (conversationId: string) => unknown;
|
toggleHideStories: (conversationId: string) => unknown;
|
||||||
toggleStoriesView: () => unknown;
|
toggleStoriesView: () => unknown;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import type { ReactNode } from 'react';
|
||||||
import React, { useState, useCallback } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
|
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import type { ShowToastActionCreatorType } from '../state/ducks/toast';
|
import type { ShowToastAction } from '../state/ducks/toast';
|
||||||
import { ContextMenu } from './ContextMenu';
|
import { ContextMenu } from './ContextMenu';
|
||||||
import { Theme } from '../util/theme';
|
import { Theme } from '../util/theme';
|
||||||
import { ToastType } from '../types/Toast';
|
import { ToastType } from '../types/Toast';
|
||||||
|
@ -22,7 +22,7 @@ export type PropsType = {
|
||||||
moduleClassName?: string;
|
moduleClassName?: string;
|
||||||
onAddStory: (file?: File) => unknown;
|
onAddStory: (file?: File) => unknown;
|
||||||
onContextMenuShowingChanged?: (value: boolean) => void;
|
onContextMenuShowingChanged?: (value: boolean) => void;
|
||||||
showToast: ShowToastActionCreatorType;
|
showToast: ShowToastAction;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function StoriesAddStoryButton({
|
export function StoriesAddStoryButton({
|
||||||
|
@ -55,7 +55,7 @@ export function StoriesAddStoryButton({
|
||||||
result.reason === ReasonVideoNotGood.UnsupportedCodec ||
|
result.reason === ReasonVideoNotGood.UnsupportedCodec ||
|
||||||
result.reason === ReasonVideoNotGood.UnsupportedContainer
|
result.reason === ReasonVideoNotGood.UnsupportedContainer
|
||||||
) {
|
) {
|
||||||
showToast(ToastType.StoryVideoUnsupported);
|
showToast({ toastType: ToastType.StoryVideoUnsupported });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ export function StoriesAddStoryButton({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.reason !== ReasonVideoNotGood.AllGoodNevermind) {
|
if (result.reason !== ReasonVideoNotGood.AllGoodNevermind) {
|
||||||
showToast(ToastType.StoryVideoError);
|
showToast({ toastType: ToastType.StoryVideoError });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import type {
|
||||||
import type { ConversationStoryType, MyStoryType } from '../types/Stories';
|
import type { ConversationStoryType, MyStoryType } from '../types/Stories';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||||
import type { ShowToastActionCreatorType } from '../state/ducks/toast';
|
import type { ShowToastAction } from '../state/ducks/toast';
|
||||||
import type { ViewUserStoriesActionCreatorType } from '../state/ducks/stories';
|
import type { ViewUserStoriesActionCreatorType } from '../state/ducks/stories';
|
||||||
import { ContextMenu } from './ContextMenu';
|
import { ContextMenu } from './ContextMenu';
|
||||||
import { MyStoryButton } from './MyStoryButton';
|
import { MyStoryButton } from './MyStoryButton';
|
||||||
|
@ -68,7 +68,7 @@ export type PropsType = {
|
||||||
onMediaPlaybackStart: () => void;
|
onMediaPlaybackStart: () => void;
|
||||||
queueStoryDownload: (storyId: string) => unknown;
|
queueStoryDownload: (storyId: string) => unknown;
|
||||||
showConversation: ShowConversationType;
|
showConversation: ShowConversationType;
|
||||||
showToast: ShowToastActionCreatorType;
|
showToast: ShowToastAction;
|
||||||
stories: Array<ConversationStoryType>;
|
stories: Array<ConversationStoryType>;
|
||||||
toggleHideStories: (conversationId: string) => unknown;
|
toggleHideStories: (conversationId: string) => unknown;
|
||||||
toggleStoriesView: () => unknown;
|
toggleStoriesView: () => unknown;
|
||||||
|
|
|
@ -21,7 +21,7 @@ import type { EmojiPickDataType } from './emoji/EmojiPicker';
|
||||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||||
import type { RenderEmojiPickerProps } from './conversation/ReactionPicker';
|
import type { RenderEmojiPickerProps } from './conversation/ReactionPicker';
|
||||||
import type { ReplyStateType, StoryViewType } from '../types/Stories';
|
import type { ReplyStateType, StoryViewType } from '../types/Stories';
|
||||||
import type { ShowToastActionCreatorType } from '../state/ducks/toast';
|
import type { ShowToastAction } from '../state/ducks/toast';
|
||||||
import type { ViewStoryActionCreatorType } from '../state/ducks/stories';
|
import type { ViewStoryActionCreatorType } from '../state/ducks/stories';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { AnimatedEmojiGalore } from './AnimatedEmojiGalore';
|
import { AnimatedEmojiGalore } from './AnimatedEmojiGalore';
|
||||||
|
@ -113,7 +113,7 @@ export type PropsType = {
|
||||||
saveAttachment: SaveAttachmentActionCreatorType;
|
saveAttachment: SaveAttachmentActionCreatorType;
|
||||||
setHasAllStoriesUnmuted: (isUnmuted: boolean) => unknown;
|
setHasAllStoriesUnmuted: (isUnmuted: boolean) => unknown;
|
||||||
showContactModal: (contactId: string, conversationId?: string) => void;
|
showContactModal: (contactId: string, conversationId?: string) => void;
|
||||||
showToast: ShowToastActionCreatorType;
|
showToast: ShowToastAction;
|
||||||
skinTone?: number;
|
skinTone?: number;
|
||||||
story: StoryViewType;
|
story: StoryViewType;
|
||||||
storyViewMode: StoryViewModeType;
|
storyViewMode: StoryViewModeType;
|
||||||
|
@ -786,7 +786,7 @@ export function StoryViewer({
|
||||||
onClick={
|
onClick={
|
||||||
hasAudio
|
hasAudio
|
||||||
? () => setHasAllStoriesUnmuted(!hasAllStoriesUnmuted)
|
? () => setHasAllStoriesUnmuted(!hasAllStoriesUnmuted)
|
||||||
: () => showToast(ToastType.StoryMuted)
|
: () => showToast({ toastType: ToastType.StoryMuted })
|
||||||
}
|
}
|
||||||
type="button"
|
type="button"
|
||||||
/>
|
/>
|
||||||
|
@ -940,14 +940,14 @@ export function StoryViewer({
|
||||||
onReactToStory(emoji, story);
|
onReactToStory(emoji, story);
|
||||||
if (!isGroupStory) {
|
if (!isGroupStory) {
|
||||||
setCurrentViewTarget(null);
|
setCurrentViewTarget(null);
|
||||||
showToast(ToastType.StoryReact);
|
showToast({ toastType: ToastType.StoryReact });
|
||||||
}
|
}
|
||||||
setReactionEmoji(emoji);
|
setReactionEmoji(emoji);
|
||||||
}}
|
}}
|
||||||
onReply={(message, mentions, replyTimestamp) => {
|
onReply={(message, mentions, replyTimestamp) => {
|
||||||
if (!isGroupStory) {
|
if (!isGroupStory) {
|
||||||
setCurrentViewTarget(null);
|
setCurrentViewTarget(null);
|
||||||
showToast(ToastType.StoryReply);
|
showToast({ toastType: ToastType.StoryReply });
|
||||||
}
|
}
|
||||||
onReplyToStory(message, mentions, replyTimestamp, story);
|
onReplyToStory(message, mentions, replyTimestamp, story);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -4,14 +4,136 @@
|
||||||
import type { Meta, Story } from '@storybook/react';
|
import type { Meta, Story } from '@storybook/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import type { PropsType } from './ToastManager';
|
|
||||||
import enMessages from '../../_locales/en/messages.json';
|
import enMessages from '../../_locales/en/messages.json';
|
||||||
import { ToastManager } from './ToastManager';
|
import { ToastManager } from './ToastManager';
|
||||||
|
import type { AnyToast } from '../types/Toast';
|
||||||
import { ToastType } from '../types/Toast';
|
import { ToastType } from '../types/Toast';
|
||||||
import { setupI18n } from '../util/setupI18n';
|
import { setupI18n } from '../util/setupI18n';
|
||||||
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
|
import type { PropsType } from './ToastManager';
|
||||||
|
|
||||||
const i18n = setupI18n('en', enMessages);
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
function getToast(toastType: ToastType): AnyToast {
|
||||||
|
switch (toastType) {
|
||||||
|
case ToastType.AddingUserToGroup:
|
||||||
|
return { toastType, parameters: { contact: 'Sam Mirete' } };
|
||||||
|
case ToastType.AlreadyGroupMember:
|
||||||
|
return { toastType: ToastType.AlreadyGroupMember };
|
||||||
|
case ToastType.AlreadyRequestedToJoin:
|
||||||
|
return { toastType: ToastType.AlreadyRequestedToJoin };
|
||||||
|
case ToastType.Blocked:
|
||||||
|
return { toastType: ToastType.Blocked };
|
||||||
|
case ToastType.BlockedGroup:
|
||||||
|
return { toastType: ToastType.BlockedGroup };
|
||||||
|
case ToastType.CannotForwardEmptyMessage:
|
||||||
|
return { toastType: ToastType.CannotForwardEmptyMessage };
|
||||||
|
case ToastType.CannotMixMultiAndNonMultiAttachments:
|
||||||
|
return { toastType: ToastType.CannotMixMultiAndNonMultiAttachments };
|
||||||
|
case ToastType.CannotOpenGiftBadgeIncoming:
|
||||||
|
return { toastType: ToastType.CannotOpenGiftBadgeIncoming };
|
||||||
|
case ToastType.CannotOpenGiftBadgeOutgoing:
|
||||||
|
return { toastType: ToastType.CannotOpenGiftBadgeOutgoing };
|
||||||
|
case ToastType.CannotStartGroupCall:
|
||||||
|
return { toastType: ToastType.CannotStartGroupCall };
|
||||||
|
case ToastType.ConversationArchived:
|
||||||
|
return {
|
||||||
|
toastType: ToastType.ConversationArchived,
|
||||||
|
parameters: { conversationId: 'some-conversation-id' },
|
||||||
|
};
|
||||||
|
case ToastType.ConversationMarkedUnread:
|
||||||
|
return { toastType: ToastType.ConversationMarkedUnread };
|
||||||
|
case ToastType.ConversationRemoved:
|
||||||
|
return {
|
||||||
|
toastType: ToastType.ConversationRemoved,
|
||||||
|
parameters: { title: 'Alice' },
|
||||||
|
};
|
||||||
|
case ToastType.ConversationUnarchived:
|
||||||
|
return { toastType: ToastType.ConversationUnarchived };
|
||||||
|
case ToastType.CopiedUsername:
|
||||||
|
return { toastType: ToastType.CopiedUsername };
|
||||||
|
case ToastType.CopiedUsernameLink:
|
||||||
|
return { toastType: ToastType.CopiedUsernameLink };
|
||||||
|
case ToastType.DangerousFileType:
|
||||||
|
return { toastType: ToastType.DangerousFileType };
|
||||||
|
case ToastType.DeleteForEveryoneFailed:
|
||||||
|
return { toastType: ToastType.DeleteForEveryoneFailed };
|
||||||
|
case ToastType.Error:
|
||||||
|
return { toastType: ToastType.Error };
|
||||||
|
case ToastType.Expired:
|
||||||
|
return { toastType: ToastType.Expired };
|
||||||
|
case ToastType.FailedToDeleteUsername:
|
||||||
|
return { toastType: ToastType.FailedToDeleteUsername };
|
||||||
|
case ToastType.FileSaved:
|
||||||
|
return {
|
||||||
|
toastType: ToastType.FileSaved,
|
||||||
|
parameters: { fullPath: '/image.png' },
|
||||||
|
};
|
||||||
|
case ToastType.FileSize:
|
||||||
|
return {
|
||||||
|
toastType: ToastType.FileSize,
|
||||||
|
parameters: { limit: 100, units: 'MB' },
|
||||||
|
};
|
||||||
|
case ToastType.InvalidConversation:
|
||||||
|
return { toastType: ToastType.InvalidConversation };
|
||||||
|
case ToastType.LeftGroup:
|
||||||
|
return { toastType: ToastType.LeftGroup };
|
||||||
|
case ToastType.MaxAttachments:
|
||||||
|
return { toastType: ToastType.MaxAttachments };
|
||||||
|
case ToastType.MessageBodyTooLong:
|
||||||
|
return { toastType: ToastType.MessageBodyTooLong };
|
||||||
|
case ToastType.OriginalMessageNotFound:
|
||||||
|
return { toastType: ToastType.OriginalMessageNotFound };
|
||||||
|
case ToastType.PinnedConversationsFull:
|
||||||
|
return { toastType: ToastType.PinnedConversationsFull };
|
||||||
|
case ToastType.ReactionFailed:
|
||||||
|
return { toastType: ToastType.ReactionFailed };
|
||||||
|
case ToastType.ReportedSpamAndBlocked:
|
||||||
|
return { toastType: ToastType.ReportedSpamAndBlocked };
|
||||||
|
case ToastType.StoryMuted:
|
||||||
|
return { toastType: ToastType.StoryMuted };
|
||||||
|
case ToastType.StoryReact:
|
||||||
|
return { toastType: ToastType.StoryReact };
|
||||||
|
case ToastType.StoryReply:
|
||||||
|
return { toastType: ToastType.StoryReply };
|
||||||
|
case ToastType.StoryVideoError:
|
||||||
|
return { toastType: ToastType.StoryVideoError };
|
||||||
|
case ToastType.StoryVideoUnsupported:
|
||||||
|
return { toastType: ToastType.StoryVideoUnsupported };
|
||||||
|
case ToastType.TapToViewExpiredIncoming:
|
||||||
|
return { toastType: ToastType.TapToViewExpiredIncoming };
|
||||||
|
case ToastType.TapToViewExpiredOutgoing:
|
||||||
|
return { toastType: ToastType.TapToViewExpiredOutgoing };
|
||||||
|
case ToastType.TooManyMessagesToDeleteForEveryone:
|
||||||
|
return {
|
||||||
|
toastType: ToastType.TooManyMessagesToDeleteForEveryone,
|
||||||
|
parameters: { count: 30 },
|
||||||
|
};
|
||||||
|
case ToastType.TooManyMessagesToForward:
|
||||||
|
return { toastType: ToastType.TooManyMessagesToForward };
|
||||||
|
case ToastType.UnableToLoadAttachment:
|
||||||
|
return { toastType: ToastType.UnableToLoadAttachment };
|
||||||
|
case ToastType.UnsupportedMultiAttachment:
|
||||||
|
return { toastType: ToastType.UnsupportedMultiAttachment };
|
||||||
|
case ToastType.UnsupportedOS:
|
||||||
|
return { toastType: ToastType.UnsupportedOS };
|
||||||
|
case ToastType.UserAddedToGroup:
|
||||||
|
return {
|
||||||
|
toastType: ToastType.UserAddedToGroup,
|
||||||
|
parameters: {
|
||||||
|
contact: 'Sam Mirete',
|
||||||
|
group: 'Hike Group 🏔',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw missingCaseError(toastType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Args = Omit<PropsType, 'toast'> & {
|
||||||
|
toastType: ToastType;
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Components/ToastManager',
|
title: 'Components/ToastManager',
|
||||||
component: ToastManager,
|
component: ToastManager,
|
||||||
|
@ -22,331 +144,26 @@ export default {
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultValue: i18n,
|
defaultValue: i18n,
|
||||||
},
|
},
|
||||||
toast: {
|
toastType: {
|
||||||
defaultValue: undefined,
|
defaultValue: ToastType.AddingUserToGroup,
|
||||||
|
options: ToastType,
|
||||||
|
control: { type: 'select' },
|
||||||
},
|
},
|
||||||
OS: {
|
OS: {
|
||||||
defaultValue: 'macOS',
|
defaultValue: 'macOS',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as Meta;
|
} as Meta<Args>;
|
||||||
|
|
||||||
// eslint-disable-next-line react/function-component-definition
|
// eslint-disable-next-line react/function-component-definition
|
||||||
const Template: Story<PropsType> = args => <ToastManager {...args} />;
|
const Template: Story<Args> = args => {
|
||||||
|
const { toastType, ...rest } = args;
|
||||||
export const UndefinedToast = Template.bind({});
|
return (
|
||||||
UndefinedToast.args = {};
|
<>
|
||||||
|
<p>Select a toast type in controls</p>
|
||||||
export const InvalidToast = Template.bind({});
|
<ToastManager toast={getToast(toastType)} {...rest} />
|
||||||
InvalidToast.args = {
|
</>
|
||||||
toast: {
|
);
|
||||||
toastType: 'this is a toast that does not exist' as ToastType,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AddingUserToGroup = Template.bind({});
|
export const BasicUsage = Template.bind({});
|
||||||
AddingUserToGroup.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.AddingUserToGroup,
|
|
||||||
parameters: {
|
|
||||||
contact: 'Sam Mirete',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AlreadyGroupMember = Template.bind({});
|
|
||||||
AlreadyGroupMember.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.AlreadyGroupMember,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AlreadyRequestedToJoin = Template.bind({});
|
|
||||||
AlreadyRequestedToJoin.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.AlreadyRequestedToJoin,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Blocked = Template.bind({});
|
|
||||||
Blocked.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.Blocked,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BlockedGroup = Template.bind({});
|
|
||||||
BlockedGroup.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.BlockedGroup,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CannotMixMultiAndNonMultiAttachments = Template.bind({});
|
|
||||||
CannotMixMultiAndNonMultiAttachments.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.CannotMixMultiAndNonMultiAttachments,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CannotOpenGiftBadgeIncoming = Template.bind({});
|
|
||||||
CannotOpenGiftBadgeIncoming.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.CannotOpenGiftBadgeIncoming,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CannotOpenGiftBadgeOutgoing = Template.bind({});
|
|
||||||
CannotOpenGiftBadgeOutgoing.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.CannotOpenGiftBadgeOutgoing,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CannotStartGroupCall = Template.bind({});
|
|
||||||
CannotStartGroupCall.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.CannotStartGroupCall,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ConversationArchived = Template.bind({});
|
|
||||||
ConversationArchived.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.ConversationArchived,
|
|
||||||
parameters: {
|
|
||||||
conversationId: 'some-conversation-id',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ConversationMarkedUnread = Template.bind({});
|
|
||||||
ConversationMarkedUnread.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.ConversationMarkedUnread,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ConversationRemoved = Template.bind({});
|
|
||||||
ConversationRemoved.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.ConversationRemoved,
|
|
||||||
parameters: {
|
|
||||||
title: 'Alice',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ConversationUnarchived = Template.bind({});
|
|
||||||
ConversationUnarchived.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.ConversationUnarchived,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CopiedUsername = Template.bind({});
|
|
||||||
CopiedUsername.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.CopiedUsername,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CopiedUsernameLink = Template.bind({});
|
|
||||||
CopiedUsernameLink.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.CopiedUsernameLink,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DangerousFileType = Template.bind({});
|
|
||||||
DangerousFileType.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.DangerousFileType,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DeleteForEveryoneFailed = Template.bind({});
|
|
||||||
DeleteForEveryoneFailed.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.DeleteForEveryoneFailed,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Error = Template.bind({});
|
|
||||||
Error.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.Error,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Expired = Template.bind({});
|
|
||||||
Expired.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.Expired,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FailedToDeleteUsername = Template.bind({});
|
|
||||||
FailedToDeleteUsername.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.FailedToDeleteUsername,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FileSaved = Template.bind({});
|
|
||||||
FileSaved.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.FileSaved,
|
|
||||||
parameters: {
|
|
||||||
fullPath: '/image.png',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FileSize = Template.bind({});
|
|
||||||
FileSize.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.FileSize,
|
|
||||||
parameters: {
|
|
||||||
limit: '100',
|
|
||||||
units: 'MB',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const InvalidConversation = Template.bind({});
|
|
||||||
InvalidConversation.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.InvalidConversation,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LeftGroup = Template.bind({});
|
|
||||||
LeftGroup.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.LeftGroup,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MaxAttachments = Template.bind({});
|
|
||||||
MaxAttachments.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.MaxAttachments,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OriginalMessageNotFound = Template.bind({});
|
|
||||||
OriginalMessageNotFound.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.OriginalMessageNotFound,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MessageBodyTooLong = Template.bind({});
|
|
||||||
MessageBodyTooLong.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.MessageBodyTooLong,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PinnedConversationsFull = Template.bind({});
|
|
||||||
PinnedConversationsFull.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.PinnedConversationsFull,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ReactionFailed = Template.bind({});
|
|
||||||
ReactionFailed.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.ReactionFailed,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ReportedSpamAndBlocked = Template.bind({});
|
|
||||||
ReportedSpamAndBlocked.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.ReportedSpamAndBlocked,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StoryMuted = Template.bind({});
|
|
||||||
StoryMuted.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.StoryMuted,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StoryReact = Template.bind({});
|
|
||||||
StoryReact.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.StoryReact,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StoryReply = Template.bind({});
|
|
||||||
StoryReply.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.StoryReply,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StoryVideoError = Template.bind({});
|
|
||||||
StoryVideoError.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.StoryVideoError,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StoryVideoUnsupported = Template.bind({});
|
|
||||||
StoryVideoUnsupported.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.StoryVideoUnsupported,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TapToViewExpiredIncoming = Template.bind({});
|
|
||||||
TapToViewExpiredIncoming.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.TapToViewExpiredIncoming,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TapToViewExpiredOutgoing = Template.bind({});
|
|
||||||
TapToViewExpiredOutgoing.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.TapToViewExpiredOutgoing,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UnableToLoadAttachment = Template.bind({});
|
|
||||||
UnableToLoadAttachment.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.UnableToLoadAttachment,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UnsupportedMultiAttachment = Template.bind({});
|
|
||||||
UnsupportedMultiAttachment.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.UnsupportedMultiAttachment,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UnsupportedOS = Template.bind({});
|
|
||||||
UnsupportedOS.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.UnsupportedOS,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UserAddedToGroup = Template.bind({});
|
|
||||||
UserAddedToGroup.args = {
|
|
||||||
toast: {
|
|
||||||
toastType: ToastType.UserAddedToGroup,
|
|
||||||
parameters: {
|
|
||||||
contact: 'Sam Mirete',
|
|
||||||
group: 'Hike Group 🏔',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { LocalizerType, ReplacementValuesType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import { SECOND } from '../util/durations';
|
import { SECOND } from '../util/durations';
|
||||||
import { Toast } from './Toast';
|
import { Toast } from './Toast';
|
||||||
import { missingCaseError } from '../util/missingCaseError';
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
|
import type { AnyToast } from '../types/Toast';
|
||||||
import { ToastType } from '../types/Toast';
|
import { ToastType } from '../types/Toast';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
|
@ -14,10 +15,7 @@ export type PropsType = {
|
||||||
openFileInFolder: (target: string) => unknown;
|
openFileInFolder: (target: string) => unknown;
|
||||||
OS: string;
|
OS: string;
|
||||||
onUndoArchive: (conversaetionId: string) => unknown;
|
onUndoArchive: (conversaetionId: string) => unknown;
|
||||||
toast?: {
|
toast?: AnyToast;
|
||||||
toastType: ToastType;
|
|
||||||
parameters?: ReplacementValuesType;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const SHORT_TIMEOUT = 3 * SECOND;
|
const SHORT_TIMEOUT = 3 * SECOND;
|
||||||
|
@ -40,7 +38,7 @@ export function ToastManager({
|
||||||
return (
|
return (
|
||||||
<Toast onClose={hideToast} timeout={SHORT_TIMEOUT}>
|
<Toast onClose={hideToast} timeout={SHORT_TIMEOUT}>
|
||||||
{i18n('icu:AddUserToAnotherGroupModal__toast--adding-user-to-group', {
|
{i18n('icu:AddUserToAnotherGroupModal__toast--adding-user-to-group', {
|
||||||
contact: toast.parameters?.contact,
|
contact: toast.parameters.contact,
|
||||||
})}
|
})}
|
||||||
</Toast>
|
</Toast>
|
||||||
);
|
);
|
||||||
|
@ -117,9 +115,7 @@ export function ToastManager({
|
||||||
toastAction={{
|
toastAction={{
|
||||||
label: i18n('icu:conversationArchivedUndo'),
|
label: i18n('icu:conversationArchivedUndo'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (toast.parameters && 'conversationId' in toast.parameters) {
|
onUndoArchive(String(toast.parameters.conversationId));
|
||||||
onUndoArchive(String(toast.parameters.conversationId));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -138,7 +134,7 @@ export function ToastManager({
|
||||||
return (
|
return (
|
||||||
<Toast onClose={hideToast}>
|
<Toast onClose={hideToast}>
|
||||||
{i18n('icu:Toast--ConversationRemoved', {
|
{i18n('icu:Toast--ConversationRemoved', {
|
||||||
title: toast?.parameters?.title ?? '',
|
title: toast.parameters.title,
|
||||||
})}
|
})}
|
||||||
</Toast>
|
</Toast>
|
||||||
);
|
);
|
||||||
|
@ -212,9 +208,7 @@ export function ToastManager({
|
||||||
toastAction={{
|
toastAction={{
|
||||||
label: i18n('icu:attachmentSavedShow'),
|
label: i18n('icu:attachmentSavedShow'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (toast.parameters && 'fullPath' in toast.parameters) {
|
openFileInFolder(toast.parameters.fullPath);
|
||||||
openFileInFolder(String(toast.parameters.fullPath));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -227,8 +221,8 @@ export function ToastManager({
|
||||||
return (
|
return (
|
||||||
<Toast onClose={hideToast}>
|
<Toast onClose={hideToast}>
|
||||||
{i18n('icu:fileSizeWarning', {
|
{i18n('icu:fileSizeWarning', {
|
||||||
limit: toast.parameters?.limit,
|
limit: toast.parameters.limit,
|
||||||
units: toast.parameters?.units,
|
units: toast.parameters.units,
|
||||||
})}
|
})}
|
||||||
</Toast>
|
</Toast>
|
||||||
);
|
);
|
||||||
|
@ -330,6 +324,17 @@ export function ToastManager({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (toastType === ToastType.TooManyMessagesToDeleteForEveryone) {
|
||||||
|
return (
|
||||||
|
<Toast onClose={hideToast}>
|
||||||
|
{i18n(
|
||||||
|
'icu:DeleteMessagesModal__toast--TooManyMessagesToDeleteForEveryone',
|
||||||
|
{ count: toast.parameters.count }
|
||||||
|
)}
|
||||||
|
</Toast>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (toastType === ToastType.TooManyMessagesToForward) {
|
if (toastType === ToastType.TooManyMessagesToForward) {
|
||||||
return (
|
return (
|
||||||
<Toast onClose={hideToast}>
|
<Toast onClose={hideToast}>
|
||||||
|
@ -364,8 +369,8 @@ export function ToastManager({
|
||||||
return (
|
return (
|
||||||
<Toast onClose={hideToast}>
|
<Toast onClose={hideToast}>
|
||||||
{i18n('icu:AddUserToAnotherGroupModal__toast--user-added-to-group', {
|
{i18n('icu:AddUserToAnotherGroupModal__toast--user-added-to-group', {
|
||||||
contact: toast.parameters?.contact,
|
contact: toast.parameters.contact,
|
||||||
group: toast.parameters?.group,
|
group: toast.parameters.group,
|
||||||
})}
|
})}
|
||||||
</Toast>
|
</Toast>
|
||||||
);
|
);
|
||||||
|
|
|
@ -97,8 +97,6 @@ const defaultMessageProps: TimelineMessagesProps = {
|
||||||
conversationId: 'conversationId',
|
conversationId: 'conversationId',
|
||||||
conversationTitle: 'Conversation Title',
|
conversationTitle: 'Conversation Title',
|
||||||
conversationType: 'direct', // override
|
conversationType: 'direct', // override
|
||||||
deleteMessages: action('default--deleteMessages'),
|
|
||||||
deleteMessageForEveryone: action('default--deleteMessageForEveryone'),
|
|
||||||
direction: 'incoming',
|
direction: 'incoming',
|
||||||
showLightboxForViewOnceMedia: action('default--showLightboxForViewOnceMedia'),
|
showLightboxForViewOnceMedia: action('default--showLightboxForViewOnceMedia'),
|
||||||
doubleCheckMissingQuoteReference: action(
|
doubleCheckMissingQuoteReference: action(
|
||||||
|
@ -145,6 +143,7 @@ const defaultMessageProps: TimelineMessagesProps = {
|
||||||
showExpiredOutgoingTapToViewToast: action(
|
showExpiredOutgoingTapToViewToast: action(
|
||||||
'showExpiredOutgoingTapToViewToast'
|
'showExpiredOutgoingTapToViewToast'
|
||||||
),
|
),
|
||||||
|
toggleDeleteMessagesModal: action('default--toggleDeleteMessagesModal'),
|
||||||
toggleForwardMessagesModal: action('default--toggleForwardMessagesModal'),
|
toggleForwardMessagesModal: action('default--toggleForwardMessagesModal'),
|
||||||
showLightbox: action('default--showLightbox'),
|
showLightbox: action('default--showLightbox'),
|
||||||
startConversation: action('default--startConversation'),
|
startConversation: action('default--startConversation'),
|
||||||
|
|
|
@ -2,11 +2,10 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import type { ShowToastAction } from '../../state/ducks/toast';
|
import type { ShowToastAction } from '../../state/ducks/toast';
|
||||||
import { ToastType } from '../../types/Toast';
|
import { ToastType } from '../../types/Toast';
|
||||||
import type { LocalizerType } from '../../types/Util';
|
import type { LocalizerType } from '../../types/Util';
|
||||||
import { ConfirmationDialog } from '../ConfirmationDialog';
|
|
||||||
|
|
||||||
// Keep this in sync with iOS and Android
|
// Keep this in sync with iOS and Android
|
||||||
const MAX_FORWARD_COUNT = 30;
|
const MAX_FORWARD_COUNT = 30;
|
||||||
|
@ -28,8 +27,6 @@ export default function SelectModeActions({
|
||||||
showToast,
|
showToast,
|
||||||
i18n,
|
i18n,
|
||||||
}: SelectModeActionsProps): JSX.Element {
|
}: SelectModeActionsProps): JSX.Element {
|
||||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
||||||
|
|
||||||
const hasSelectedMessages = selectedMessageIds.length >= 1;
|
const hasSelectedMessages = selectedMessageIds.length >= 1;
|
||||||
const tooManyMessagesToForward =
|
const tooManyMessagesToForward =
|
||||||
selectedMessageIds.length > MAX_FORWARD_COUNT;
|
selectedMessageIds.length > MAX_FORWARD_COUNT;
|
||||||
|
@ -38,88 +35,57 @@ export default function SelectModeActions({
|
||||||
const canDelete = hasSelectedMessages;
|
const canDelete = hasSelectedMessages;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="SelectModeActions">
|
||||||
<div className="SelectModeActions">
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
className="SelectModeActions__button"
|
||||||
className="SelectModeActions__button"
|
onClick={onExitSelectMode}
|
||||||
onClick={onExitSelectMode}
|
aria-label={i18n('icu:SelectModeActions--exitSelectMode')}
|
||||||
aria-label={i18n('icu:SelectModeActions--exitSelectMode')}
|
>
|
||||||
>
|
<span
|
||||||
<span
|
role="presentation"
|
||||||
role="presentation"
|
className="SelectModeActions__icon SelectModeActions__icon--exitSelectMode"
|
||||||
className="SelectModeActions__icon SelectModeActions__icon--exitSelectMode"
|
/>
|
||||||
/>
|
</button>
|
||||||
</button>
|
<div className="SelectModeActions__selectedMessages">
|
||||||
<div className="SelectModeActions__selectedMessages">
|
{i18n('icu:SelectModeActions--selectedMessages', {
|
||||||
{i18n('icu:SelectModeActions--selectedMessages', {
|
count: selectedMessageIds.length,
|
||||||
count: selectedMessageIds.length,
|
})}
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames('SelectModeActions__button', {
|
|
||||||
'SelectModeActions__button--disabled': !canDelete,
|
|
||||||
})}
|
|
||||||
disabled={!canDelete}
|
|
||||||
onClick={() => {
|
|
||||||
setConfirmDelete(true);
|
|
||||||
}}
|
|
||||||
aria-label={i18n('icu:SelectModeActions--deleteSelectedMessages')}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
role="presentation"
|
|
||||||
className="SelectModeActions__icon SelectModeActions__icon--deleteSelectedMessages"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames('SelectModeActions__button', {
|
|
||||||
'SelectModeActions__button--disabled': !canForward,
|
|
||||||
})}
|
|
||||||
aria-disabled={!canForward}
|
|
||||||
onClick={() => {
|
|
||||||
if (canForward) {
|
|
||||||
onForwardMessages();
|
|
||||||
} else if (tooManyMessagesToForward) {
|
|
||||||
showToast(ToastType.TooManyMessagesToForward, {
|
|
||||||
count: MAX_FORWARD_COUNT,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
aria-label={i18n('icu:SelectModeActions--forwardSelectedMessages')}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
role="presentation"
|
|
||||||
className="SelectModeActions__icon SelectModeActions__icon--forwardSelectedMessages"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
{confirmDelete && (
|
<button
|
||||||
<ConfirmationDialog
|
type="button"
|
||||||
actions={[
|
className={classNames('SelectModeActions__button', {
|
||||||
{
|
'SelectModeActions__button--disabled': !canDelete,
|
||||||
action: () => {
|
})}
|
||||||
onDeleteMessages();
|
disabled={!canDelete}
|
||||||
},
|
onClick={onDeleteMessages}
|
||||||
style: 'negative',
|
aria-label={i18n('icu:SelectModeActions--deleteSelectedMessages')}
|
||||||
text: i18n('icu:ConfirmDeleteForMeModal--confirm'),
|
>
|
||||||
},
|
<span
|
||||||
]}
|
role="presentation"
|
||||||
dialogName="ConfirmDeleteForMeModal"
|
className="SelectModeActions__icon SelectModeActions__icon--deleteSelectedMessages"
|
||||||
title={i18n('icu:ConfirmDeleteForMeModal--title', {
|
/>
|
||||||
count: selectedMessageIds.length,
|
</button>
|
||||||
})}
|
<button
|
||||||
i18n={i18n}
|
type="button"
|
||||||
onClose={() => {
|
className={classNames('SelectModeActions__button', {
|
||||||
setConfirmDelete(false);
|
'SelectModeActions__button--disabled': !canForward,
|
||||||
}}
|
})}
|
||||||
>
|
aria-disabled={!canForward}
|
||||||
{i18n('icu:ConfirmDeleteForMeModal--description', {
|
onClick={() => {
|
||||||
count: selectedMessageIds.length,
|
if (canForward) {
|
||||||
})}
|
onForwardMessages();
|
||||||
</ConfirmationDialog>
|
} else if (tooManyMessagesToForward) {
|
||||||
)}
|
showToast({ toastType: ToastType.TooManyMessagesToForward });
|
||||||
</>
|
}
|
||||||
|
}}
|
||||||
|
aria-label={i18n('icu:SelectModeActions--forwardSelectedMessages')}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
role="presentation"
|
||||||
|
className="SelectModeActions__icon SelectModeActions__icon--forwardSelectedMessages"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -282,8 +282,6 @@ const actions = () => ({
|
||||||
setQuoteByMessageId: action('setQuoteByMessageId'),
|
setQuoteByMessageId: action('setQuoteByMessageId'),
|
||||||
retryDeleteForEveryone: action('retryDeleteForEveryone'),
|
retryDeleteForEveryone: action('retryDeleteForEveryone'),
|
||||||
retryMessageSend: action('retryMessageSend'),
|
retryMessageSend: action('retryMessageSend'),
|
||||||
deleteMessages: action('deleteMessages'),
|
|
||||||
deleteMessageForEveryone: action('deleteMessageForEveryone'),
|
|
||||||
saveAttachment: action('saveAttachment'),
|
saveAttachment: action('saveAttachment'),
|
||||||
pushPanelForConversation: action('pushPanelForConversation'),
|
pushPanelForConversation: action('pushPanelForConversation'),
|
||||||
showContactDetail: action('showContactDetail'),
|
showContactDetail: action('showContactDetail'),
|
||||||
|
@ -305,6 +303,7 @@ const actions = () => ({
|
||||||
showExpiredOutgoingTapToViewToast: action(
|
showExpiredOutgoingTapToViewToast: action(
|
||||||
'showExpiredOutgoingTapToViewToast'
|
'showExpiredOutgoingTapToViewToast'
|
||||||
),
|
),
|
||||||
|
toggleDeleteMessagesModal: action('toggleDeleteMessagesModal'),
|
||||||
toggleForwardMessagesModal: action('toggleForwardMessagesModal'),
|
toggleForwardMessagesModal: action('toggleForwardMessagesModal'),
|
||||||
|
|
||||||
toggleSafetyNumberModal: action('toggleSafetyNumberModal'),
|
toggleSafetyNumberModal: action('toggleSafetyNumberModal'),
|
||||||
|
|
|
@ -71,8 +71,6 @@ const getDefaultProps = () => ({
|
||||||
retryDeleteForEveryone: action('retryDeleteForEveryone'),
|
retryDeleteForEveryone: action('retryDeleteForEveryone'),
|
||||||
retryMessageSend: action('retryMessageSend'),
|
retryMessageSend: action('retryMessageSend'),
|
||||||
blockGroupLinkRequests: action('blockGroupLinkRequests'),
|
blockGroupLinkRequests: action('blockGroupLinkRequests'),
|
||||||
deleteMessages: action('deleteMessages'),
|
|
||||||
deleteMessageForEveryone: action('deleteMessageForEveryone'),
|
|
||||||
kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
|
kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
|
||||||
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
|
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
|
||||||
messageExpanded: action('messageExpanded'),
|
messageExpanded: action('messageExpanded'),
|
||||||
|
@ -82,6 +80,7 @@ const getDefaultProps = () => ({
|
||||||
pushPanelForConversation: action('pushPanelForConversation'),
|
pushPanelForConversation: action('pushPanelForConversation'),
|
||||||
showContactModal: action('showContactModal'),
|
showContactModal: action('showContactModal'),
|
||||||
showLightbox: action('showLightbox'),
|
showLightbox: action('showLightbox'),
|
||||||
|
toggleDeleteMessagesModal: action('toggleDeleteMessagesModal'),
|
||||||
toggleForwardMessagesModal: action('toggleForwardMessagesModal'),
|
toggleForwardMessagesModal: action('toggleForwardMessagesModal'),
|
||||||
showLightboxForViewOnceMedia: action('showLightboxForViewOnceMedia'),
|
showLightboxForViewOnceMedia: action('showLightboxForViewOnceMedia'),
|
||||||
doubleCheckMissingQuoteReference: action('doubleCheckMissingQuoteReference'),
|
doubleCheckMissingQuoteReference: action('doubleCheckMissingQuoteReference'),
|
||||||
|
|
|
@ -265,8 +265,6 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
conversationType: overrideProps.conversationType || 'direct',
|
conversationType: overrideProps.conversationType || 'direct',
|
||||||
contact: overrideProps.contact,
|
contact: overrideProps.contact,
|
||||||
deletedForEveryone: overrideProps.deletedForEveryone,
|
deletedForEveryone: overrideProps.deletedForEveryone,
|
||||||
deleteMessages: action('deleteMessages'),
|
|
||||||
deleteMessageForEveryone: action('deleteMessageForEveryone'),
|
|
||||||
// disableMenu: overrideProps.disableMenu,
|
// disableMenu: overrideProps.disableMenu,
|
||||||
disableScroll: overrideProps.disableScroll,
|
disableScroll: overrideProps.disableScroll,
|
||||||
direction: overrideProps.direction || 'incoming',
|
direction: overrideProps.direction || 'incoming',
|
||||||
|
@ -350,6 +348,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
showExpiredOutgoingTapToViewToast: action(
|
showExpiredOutgoingTapToViewToast: action(
|
||||||
'showExpiredOutgoingTapToViewToast'
|
'showExpiredOutgoingTapToViewToast'
|
||||||
),
|
),
|
||||||
|
toggleDeleteMessagesModal: action('toggleDeleteMessagesModal'),
|
||||||
toggleForwardMessagesModal: action('toggleForwardMessagesModal'),
|
toggleForwardMessagesModal: action('toggleForwardMessagesModal'),
|
||||||
showLightbox: action('showLightbox'),
|
showLightbox: action('showLightbox'),
|
||||||
startConversation: action('startConversation'),
|
startConversation: action('startConversation'),
|
||||||
|
|
|
@ -26,9 +26,9 @@ import type {
|
||||||
import type { PushPanelForConversationActionType } from '../../state/ducks/conversations';
|
import type { PushPanelForConversationActionType } from '../../state/ducks/conversations';
|
||||||
import { doesMessageBodyOverflow } from './MessageBodyReadMore';
|
import { doesMessageBodyOverflow } from './MessageBodyReadMore';
|
||||||
import type { Props as ReactionPickerProps } from './ReactionPicker';
|
import type { Props as ReactionPickerProps } from './ReactionPicker';
|
||||||
import { ConfirmationDialog } from '../ConfirmationDialog';
|
|
||||||
import { useToggleReactionPicker } from '../../hooks/useKeyboardShortcuts';
|
import { useToggleReactionPicker } from '../../hooks/useKeyboardShortcuts';
|
||||||
import { PanelType } from '../../types/Panels';
|
import { PanelType } from '../../types/Panels';
|
||||||
|
import type { DeleteMessagesPropsType } from '../../state/ducks/globalModals';
|
||||||
|
|
||||||
export type PropsData = {
|
export type PropsData = {
|
||||||
canDownload: boolean;
|
canDownload: boolean;
|
||||||
|
@ -41,13 +41,9 @@ export type PropsData = {
|
||||||
} & Omit<MessagePropsData, 'renderingContext' | 'menu'>;
|
} & Omit<MessagePropsData, 'renderingContext' | 'menu'>;
|
||||||
|
|
||||||
export type PropsActions = {
|
export type PropsActions = {
|
||||||
deleteMessages: (options: {
|
|
||||||
conversationId: string;
|
|
||||||
messageIds: ReadonlyArray<string>;
|
|
||||||
}) => void;
|
|
||||||
deleteMessageForEveryone: (id: string) => void;
|
|
||||||
pushPanelForConversation: PushPanelForConversationActionType;
|
pushPanelForConversation: PushPanelForConversationActionType;
|
||||||
toggleForwardMessagesModal: (id: Array<string>) => void;
|
toggleDeleteMessagesModal: (props: DeleteMessagesPropsType) => void;
|
||||||
|
toggleForwardMessagesModal: (messageIds: Array<string>) => void;
|
||||||
reactToMessage: (
|
reactToMessage: (
|
||||||
id: string,
|
id: string,
|
||||||
{ emoji, remove }: { emoji: string; remove: boolean }
|
{ emoji, remove }: { emoji: string; remove: boolean }
|
||||||
|
@ -83,7 +79,6 @@ export function TimelineMessage(props: Props): JSX.Element {
|
||||||
const {
|
const {
|
||||||
attachments,
|
attachments,
|
||||||
author,
|
author,
|
||||||
canDeleteForEveryone,
|
|
||||||
canDownload,
|
canDownload,
|
||||||
canReact,
|
canReact,
|
||||||
canReply,
|
canReply,
|
||||||
|
@ -93,8 +88,6 @@ export function TimelineMessage(props: Props): JSX.Element {
|
||||||
containerElementRef,
|
containerElementRef,
|
||||||
containerWidthBreakpoint,
|
containerWidthBreakpoint,
|
||||||
conversationId,
|
conversationId,
|
||||||
deleteMessages,
|
|
||||||
deleteMessageForEveryone,
|
|
||||||
deletedForEveryone,
|
deletedForEveryone,
|
||||||
direction,
|
direction,
|
||||||
giftBadge,
|
giftBadge,
|
||||||
|
@ -116,6 +109,7 @@ export function TimelineMessage(props: Props): JSX.Element {
|
||||||
setQuoteByMessageId,
|
setQuoteByMessageId,
|
||||||
text,
|
text,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
toggleDeleteMessagesModal,
|
||||||
toggleForwardMessagesModal,
|
toggleForwardMessagesModal,
|
||||||
toggleSelectMessage,
|
toggleSelectMessage,
|
||||||
} = props;
|
} = props;
|
||||||
|
@ -259,9 +253,6 @@ export function TimelineMessage(props: Props): JSX.Element {
|
||||||
}
|
}
|
||||||
}, [canReact, toggleReactionPicker]);
|
}, [canReact, toggleReactionPicker]);
|
||||||
|
|
||||||
const [hasDOEConfirmation, setHasDOEConfirmation] = useState(false);
|
|
||||||
const [hasDeleteConfirmation, setHasDeleteConfirmation] = useState(false);
|
|
||||||
|
|
||||||
const toggleReactionPickerKeyboard = useToggleReactionPicker(
|
const toggleReactionPickerKeyboard = useToggleReactionPicker(
|
||||||
handleReact || noop
|
handleReact || noop
|
||||||
);
|
);
|
||||||
|
@ -343,48 +334,6 @@ export function TimelineMessage(props: Props): JSX.Element {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hasDOEConfirmation && canDeleteForEveryone && (
|
|
||||||
<ConfirmationDialog
|
|
||||||
actions={[
|
|
||||||
{
|
|
||||||
action: () => deleteMessageForEveryone(id),
|
|
||||||
style: 'negative',
|
|
||||||
text: i18n('icu:delete'),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
dialogName="TimelineMessage/deleteMessageForEveryone"
|
|
||||||
i18n={i18n}
|
|
||||||
onClose={() => setHasDOEConfirmation(false)}
|
|
||||||
>
|
|
||||||
{i18n('icu:deleteForEveryoneWarning')}
|
|
||||||
</ConfirmationDialog>
|
|
||||||
)}
|
|
||||||
{hasDeleteConfirmation && (
|
|
||||||
<ConfirmationDialog
|
|
||||||
actions={[
|
|
||||||
{
|
|
||||||
action: () =>
|
|
||||||
deleteMessages({
|
|
||||||
conversationId,
|
|
||||||
messageIds: [id],
|
|
||||||
}),
|
|
||||||
style: 'negative',
|
|
||||||
text: i18n('icu:ConfirmDeleteForMeModal--confirm'),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
dialogName="ConfirmDeleteForMeModal"
|
|
||||||
i18n={i18n}
|
|
||||||
onClose={() => setHasDeleteConfirmation(false)}
|
|
||||||
title={i18n('icu:ConfirmDeleteForMeModal--title', {
|
|
||||||
count: 1,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{i18n('icu:ConfirmDeleteForMeModal--description', {
|
|
||||||
count: 1,
|
|
||||||
})}
|
|
||||||
</ConfirmationDialog>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Message
|
<Message
|
||||||
{...props}
|
{...props}
|
||||||
renderingContext="conversation/TimelineItem"
|
renderingContext="conversation/TimelineItem"
|
||||||
|
@ -413,10 +362,12 @@ export function TimelineMessage(props: Props): JSX.Element {
|
||||||
onForward={
|
onForward={
|
||||||
canForward ? () => toggleForwardMessagesModal([id]) : undefined
|
canForward ? () => toggleForwardMessagesModal([id]) : undefined
|
||||||
}
|
}
|
||||||
onDeleteForMe={() => setHasDeleteConfirmation(true)}
|
onDeleteMessage={() => {
|
||||||
onDeleteForEveryone={
|
toggleDeleteMessagesModal({
|
||||||
canDeleteForEveryone ? () => setHasDOEConfirmation(true) : undefined
|
conversationId,
|
||||||
}
|
messageIds: [id],
|
||||||
|
});
|
||||||
|
}}
|
||||||
onMoreInfo={() =>
|
onMoreInfo={() =>
|
||||||
pushPanelForConversation({
|
pushPanelForConversation({
|
||||||
type: PanelType.MessageDetails,
|
type: PanelType.MessageDetails,
|
||||||
|
@ -594,8 +545,7 @@ type MessageContextProps = {
|
||||||
onRetryMessageSend: (() => void) | undefined;
|
onRetryMessageSend: (() => void) | undefined;
|
||||||
onRetryDeleteForEveryone: (() => void) | undefined;
|
onRetryDeleteForEveryone: (() => void) | undefined;
|
||||||
onForward: (() => void) | undefined;
|
onForward: (() => void) | undefined;
|
||||||
onDeleteForMe: () => void;
|
onDeleteMessage: () => void;
|
||||||
onDeleteForEveryone: (() => void) | undefined;
|
|
||||||
onMoreInfo: () => void;
|
onMoreInfo: () => void;
|
||||||
onSelect: () => void;
|
onSelect: () => void;
|
||||||
};
|
};
|
||||||
|
@ -612,8 +562,7 @@ const MessageContextMenu = ({
|
||||||
onRetryMessageSend,
|
onRetryMessageSend,
|
||||||
onRetryDeleteForEveryone,
|
onRetryDeleteForEveryone,
|
||||||
onForward,
|
onForward,
|
||||||
onDeleteForMe,
|
onDeleteMessage,
|
||||||
onDeleteForEveryone,
|
|
||||||
}: MessageContextProps): JSX.Element => {
|
}: MessageContextProps): JSX.Element => {
|
||||||
const menu = (
|
const menu = (
|
||||||
<ContextMenu id={triggerId}>
|
<ContextMenu id={triggerId}>
|
||||||
|
@ -746,27 +695,11 @@ const MessageContextMenu = ({
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
onDeleteForMe();
|
onDeleteMessage();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18n('icu:deleteMessage')}
|
{i18n('icu:MessageContextMenu__deleteMessage')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{onDeleteForEveryone && (
|
|
||||||
<MenuItem
|
|
||||||
attributes={{
|
|
||||||
className:
|
|
||||||
'module-message__context--icon module-message__context__delete-message-for-everyone',
|
|
||||||
}}
|
|
||||||
onClick={(event: React.MouseEvent) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
onDeleteForEveryone();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18n('icu:deleteMessageForEveryone')}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,9 @@ export async function joinViaLink(hash: string): Promise<void> {
|
||||||
window.reduxActions.conversations.showConversation({
|
window.reduxActions.conversations.showConversation({
|
||||||
conversationId: existingConversation.id,
|
conversationId: existingConversation.id,
|
||||||
});
|
});
|
||||||
window.reduxActions.toast.showToast(ToastType.AlreadyGroupMember);
|
window.reduxActions.toast.showToast({
|
||||||
|
toastType: ToastType.AlreadyGroupMember,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +168,9 @@ export async function joinViaLink(hash: string): Promise<void> {
|
||||||
conversationId: existingConversation.id,
|
conversationId: existingConversation.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
window.reduxActions.toast.showToast(ToastType.AlreadyRequestedToJoin);
|
window.reduxActions.toast.showToast({
|
||||||
|
toastType: ToastType.AlreadyRequestedToJoin,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ import type {
|
||||||
} from '../../types/Attachment';
|
} from '../../types/Attachment';
|
||||||
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||||
import type { DraftBodyRangeMention } from '../../types/BodyRange';
|
import type { DraftBodyRangeMention } from '../../types/BodyRange';
|
||||||
import type { ReplacementValuesType } from '../../types/Util';
|
|
||||||
import type { LinkPreviewType } from '../../types/message/LinkPreviews';
|
import type { LinkPreviewType } from '../../types/message/LinkPreviews';
|
||||||
import type { MessageAttributesType } from '../../model-types.d';
|
import type { MessageAttributesType } from '../../model-types.d';
|
||||||
import type { NoopActionType } from './noop';
|
import type { NoopActionType } from './noop';
|
||||||
|
@ -35,6 +34,7 @@ import { LinkPreviewSourceType } from '../../types/LinkPreview';
|
||||||
import { completeRecording } from './audioRecorder';
|
import { completeRecording } from './audioRecorder';
|
||||||
import { RecordingState } from '../../types/AudioRecorder';
|
import { RecordingState } from '../../types/AudioRecorder';
|
||||||
import { SHOW_TOAST } from './toast';
|
import { SHOW_TOAST } from './toast';
|
||||||
|
import type { AnyToast } from '../../types/Toast';
|
||||||
import { ToastType } from '../../types/Toast';
|
import { ToastType } from '../../types/Toast';
|
||||||
import { SafetyNumberChangeSource } from '../../components/SafetyNumberChangeDialog';
|
import { SafetyNumberChangeSource } from '../../components/SafetyNumberChangeDialog';
|
||||||
import { UUID } from '../../types/UUID';
|
import { UUID } from '../../types/UUID';
|
||||||
|
@ -436,13 +436,11 @@ function sendMultiMediaMessage(
|
||||||
|
|
||||||
conversation.clearTypingTimers();
|
conversation.clearTypingTimers();
|
||||||
|
|
||||||
const toastType = shouldShowInvalidMessageToast(conversation.attributes);
|
const toast = shouldShowInvalidMessageToast(conversation.attributes);
|
||||||
if (toastType) {
|
if (toast != null) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SHOW_TOAST,
|
type: SHOW_TOAST,
|
||||||
payload: {
|
payload: toast,
|
||||||
toastType,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
dispatch(setComposerDisabledState(conversationId, false));
|
dispatch(setComposerDisabledState(conversationId, false));
|
||||||
return;
|
return;
|
||||||
|
@ -561,13 +559,11 @@ function sendStickerMessage(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const toastType = shouldShowInvalidMessageToast(conversation.attributes);
|
const toast = shouldShowInvalidMessageToast(conversation.attributes);
|
||||||
if (toastType) {
|
if (toast != null) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SHOW_TOAST,
|
type: SHOW_TOAST,
|
||||||
payload: {
|
payload: toast,
|
||||||
toastType,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -906,9 +902,7 @@ function processAttachments({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let toastToShow:
|
let toastToShow: AnyToast | undefined;
|
||||||
| { toastType: ToastType; parameters?: ReplacementValuesType }
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
const nextDraftAttachments = (
|
const nextDraftAttachments = (
|
||||||
conversation.get('draftAttachments') || []
|
conversation.get('draftAttachments') || []
|
||||||
|
@ -986,7 +980,7 @@ function processAttachments({
|
||||||
function preProcessAttachment(
|
function preProcessAttachment(
|
||||||
file: File,
|
file: File,
|
||||||
draftAttachments: Array<AttachmentDraftType>
|
draftAttachments: Array<AttachmentDraftType>
|
||||||
): { toastType: ToastType; parameters?: ReplacementValuesType } | undefined {
|
): AnyToast | undefined {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1001,7 +1001,7 @@ export const actions = {
|
||||||
deleteAvatarFromDisk,
|
deleteAvatarFromDisk,
|
||||||
deleteConversation,
|
deleteConversation,
|
||||||
deleteMessages,
|
deleteMessages,
|
||||||
deleteMessageForEveryone,
|
deleteMessagesForEveryone,
|
||||||
destroyMessages,
|
destroyMessages,
|
||||||
discardMessages,
|
discardMessages,
|
||||||
doubleCheckMissingQuoteReference,
|
doubleCheckMissingQuoteReference,
|
||||||
|
@ -2857,8 +2857,8 @@ function popPanelForConversation(): ThunkAction<
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteMessageForEveryone(
|
function deleteMessagesForEveryone(
|
||||||
messageId: string
|
messageIds: ReadonlyArray<string>
|
||||||
): ThunkAction<
|
): ThunkAction<
|
||||||
void,
|
void,
|
||||||
RootStateType,
|
RootStateType,
|
||||||
|
@ -2866,39 +2866,50 @@ function deleteMessageForEveryone(
|
||||||
NoopActionType | ShowToastActionType
|
NoopActionType | ShowToastActionType
|
||||||
> {
|
> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const message = window.MessageController.getById(messageId);
|
let hasError = false;
|
||||||
if (!message) {
|
|
||||||
throw new Error(
|
|
||||||
`deleteMessageForEveryone: Message ${messageId} missing!`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const conversation = message.getConversation();
|
await Promise.all(
|
||||||
if (!conversation) {
|
messageIds.map(async messageId => {
|
||||||
throw new Error('deleteMessageForEveryone: no conversation');
|
try {
|
||||||
}
|
const message = window.MessageController.getById(messageId);
|
||||||
|
if (!message) {
|
||||||
|
throw new Error(
|
||||||
|
`deleteMessageForEveryone: Message ${messageId} missing!`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
const conversation = message.getConversation();
|
||||||
await sendDeleteForEveryoneMessage(conversation.attributes, {
|
if (!conversation) {
|
||||||
id: message.id,
|
throw new Error('deleteMessageForEveryone: no conversation');
|
||||||
timestamp: message.get('sent_at'),
|
}
|
||||||
});
|
|
||||||
dispatch({
|
await sendDeleteForEveryoneMessage(conversation.attributes, {
|
||||||
type: 'NOOP',
|
id: message.id,
|
||||||
payload: null,
|
timestamp: message.get('sent_at'),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(
|
hasError = true;
|
||||||
'Error sending delete-for-everyone',
|
log.error(
|
||||||
Errors.toLogFormat(error),
|
'Error queuing delete-for-everyone job',
|
||||||
messageId
|
Errors.toLogFormat(error),
|
||||||
);
|
messageId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasError) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SHOW_TOAST,
|
type: SHOW_TOAST,
|
||||||
payload: {
|
payload: {
|
||||||
toastType: ToastType.DeleteForEveryoneFailed,
|
toastType: ToastType.DeleteForEveryoneFailed,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
dispatch({
|
||||||
|
type: 'NOOP',
|
||||||
|
payload: null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,8 +45,10 @@ import type { ShowToastActionType } from './toast';
|
||||||
export type EditHistoryMessagesType = ReadonlyDeep<
|
export type EditHistoryMessagesType = ReadonlyDeep<
|
||||||
Array<MessageAttributesType>
|
Array<MessageAttributesType>
|
||||||
>;
|
>;
|
||||||
export type ConfirmDeleteForMeModalProps = ReadonlyDeep<{
|
export type DeleteMessagesPropsType = ReadonlyDeep<{
|
||||||
count: number;
|
conversationId: string;
|
||||||
|
messageIds: ReadonlyArray<string>;
|
||||||
|
onDelete?: () => void;
|
||||||
}>;
|
}>;
|
||||||
export type ForwardMessagePropsType = ReadonlyDeep<MessagePropsType>;
|
export type ForwardMessagePropsType = ReadonlyDeep<MessagePropsType>;
|
||||||
export type ForwardMessagesPropsType = ReadonlyDeep<{
|
export type ForwardMessagesPropsType = ReadonlyDeep<{
|
||||||
|
@ -76,6 +78,7 @@ export type GlobalModalsStateType = ReadonlyDeep<{
|
||||||
description?: string;
|
description?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
|
deleteMessagesProps?: DeleteMessagesPropsType;
|
||||||
forwardMessagesProps?: ForwardMessagesPropsType;
|
forwardMessagesProps?: ForwardMessagesPropsType;
|
||||||
gv2MigrationProps?: MigrateToGV2PropsType;
|
gv2MigrationProps?: MigrateToGV2PropsType;
|
||||||
hasConfirmationModal: boolean;
|
hasConfirmationModal: boolean;
|
||||||
|
@ -103,6 +106,8 @@ const HIDE_UUID_NOT_FOUND_MODAL = 'globalModals/HIDE_UUID_NOT_FOUND_MODAL';
|
||||||
const SHOW_UUID_NOT_FOUND_MODAL = 'globalModals/SHOW_UUID_NOT_FOUND_MODAL';
|
const SHOW_UUID_NOT_FOUND_MODAL = 'globalModals/SHOW_UUID_NOT_FOUND_MODAL';
|
||||||
const SHOW_STORIES_SETTINGS = 'globalModals/SHOW_STORIES_SETTINGS';
|
const SHOW_STORIES_SETTINGS = 'globalModals/SHOW_STORIES_SETTINGS';
|
||||||
const HIDE_STORIES_SETTINGS = 'globalModals/HIDE_STORIES_SETTINGS';
|
const HIDE_STORIES_SETTINGS = 'globalModals/HIDE_STORIES_SETTINGS';
|
||||||
|
const TOGGLE_DELETE_MESSAGES_MODAL =
|
||||||
|
'globalModals/TOGGLE_DELETE_MESSAGES_MODAL';
|
||||||
const TOGGLE_FORWARD_MESSAGES_MODAL =
|
const TOGGLE_FORWARD_MESSAGES_MODAL =
|
||||||
'globalModals/TOGGLE_FORWARD_MESSAGES_MODAL';
|
'globalModals/TOGGLE_FORWARD_MESSAGES_MODAL';
|
||||||
const TOGGLE_PROFILE_EDITOR = 'globalModals/TOGGLE_PROFILE_EDITOR';
|
const TOGGLE_PROFILE_EDITOR = 'globalModals/TOGGLE_PROFILE_EDITOR';
|
||||||
|
@ -175,6 +180,11 @@ export type ShowUserNotFoundModalActionType = ReadonlyDeep<{
|
||||||
payload: UserNotFoundModalStateType;
|
payload: UserNotFoundModalStateType;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
type ToggleDeleteMessagesModalActionType = ReadonlyDeep<{
|
||||||
|
type: typeof TOGGLE_DELETE_MESSAGES_MODAL;
|
||||||
|
payload: DeleteMessagesPropsType | undefined;
|
||||||
|
}>;
|
||||||
|
|
||||||
type ToggleForwardMessagesModalActionType = ReadonlyDeep<{
|
type ToggleForwardMessagesModalActionType = ReadonlyDeep<{
|
||||||
type: typeof TOGGLE_FORWARD_MESSAGES_MODAL;
|
type: typeof TOGGLE_FORWARD_MESSAGES_MODAL;
|
||||||
payload: ForwardMessagesPropsType | undefined;
|
payload: ForwardMessagesPropsType | undefined;
|
||||||
|
@ -321,6 +331,7 @@ export type GlobalModalsActionType = ReadonlyDeep<
|
||||||
| ShowWhatsNewModalActionType
|
| ShowWhatsNewModalActionType
|
||||||
| StartMigrationToGV2ActionType
|
| StartMigrationToGV2ActionType
|
||||||
| ToggleAddUserToAnotherGroupModalActionType
|
| ToggleAddUserToAnotherGroupModalActionType
|
||||||
|
| ToggleDeleteMessagesModalActionType
|
||||||
| ToggleForwardMessagesModalActionType
|
| ToggleForwardMessagesModalActionType
|
||||||
| ToggleProfileEditorActionType
|
| ToggleProfileEditorActionType
|
||||||
| ToggleProfileEditorErrorActionType
|
| ToggleProfileEditorErrorActionType
|
||||||
|
@ -357,6 +368,7 @@ export const actions = {
|
||||||
showWhatsNewModal,
|
showWhatsNewModal,
|
||||||
toggleAddUserToAnotherGroupModal,
|
toggleAddUserToAnotherGroupModal,
|
||||||
toggleConfirmationModal,
|
toggleConfirmationModal,
|
||||||
|
toggleDeleteMessagesModal,
|
||||||
toggleForwardMessagesModal,
|
toggleForwardMessagesModal,
|
||||||
toggleProfileEditor,
|
toggleProfileEditor,
|
||||||
toggleProfileEditorHasError,
|
toggleProfileEditorHasError,
|
||||||
|
@ -473,6 +485,15 @@ function closeGV2MigrationDialog(): CloseGV2MigrationDialogActionType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleDeleteMessagesModal(
|
||||||
|
props: DeleteMessagesPropsType | undefined
|
||||||
|
): ToggleDeleteMessagesModalActionType {
|
||||||
|
return {
|
||||||
|
type: TOGGLE_DELETE_MESSAGES_MODAL,
|
||||||
|
payload: props,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function toggleForwardMessagesModal(
|
function toggleForwardMessagesModal(
|
||||||
messageIds?: ReadonlyArray<string>,
|
messageIds?: ReadonlyArray<string>,
|
||||||
onForward?: () => void
|
onForward?: () => void
|
||||||
|
@ -855,6 +876,13 @@ export function reducer(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action.type === TOGGLE_DELETE_MESSAGES_MODAL) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
deleteMessagesProps: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (action.type === TOGGLE_FORWARD_MESSAGES_MODAL) {
|
if (action.type === TOGGLE_FORWARD_MESSAGES_MODAL) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -6,18 +6,14 @@ import { ipcRenderer } from 'electron';
|
||||||
import type { ReadonlyDeep } from 'type-fest';
|
import type { ReadonlyDeep } from 'type-fest';
|
||||||
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||||
import type { NoopActionType } from './noop';
|
import type { NoopActionType } from './noop';
|
||||||
import type { ReplacementValuesType } from '../../types/Util';
|
|
||||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||||
import type { ToastType } from '../../types/Toast';
|
import type { AnyToast } from '../../types/Toast';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||||
export type ToastStateType = {
|
export type ToastStateType = {
|
||||||
toast?: {
|
toast?: AnyToast;
|
||||||
toastType: ToastType;
|
|
||||||
parameters?: ReplacementValuesType;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
@ -32,10 +28,7 @@ type HideToastActionType = ReadonlyDeep<{
|
||||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||||
export type ShowToastActionType = {
|
export type ShowToastActionType = {
|
||||||
type: typeof SHOW_TOAST;
|
type: typeof SHOW_TOAST;
|
||||||
payload: {
|
payload: AnyToast;
|
||||||
toastType: ToastType;
|
|
||||||
parameters?: ReplacementValuesType;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||||
|
@ -57,29 +50,14 @@ function openFileInFolder(target: string): NoopActionType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ShowToastActionCreatorType = ReadonlyDeep<
|
export type ShowToastAction = ReadonlyDeep<(toast: AnyToast) => void>;
|
||||||
(
|
|
||||||
toastType: ToastType,
|
|
||||||
parameters?: ReplacementValuesType
|
|
||||||
) => ShowToastActionType
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type ShowToastAction = ReadonlyDeep<
|
export function showToast(toast: AnyToast): ShowToastActionType {
|
||||||
(toastType: ToastType, parameters?: ReplacementValuesType) => void
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const showToast: ShowToastActionCreatorType = (
|
|
||||||
toastType,
|
|
||||||
parameters
|
|
||||||
) => {
|
|
||||||
return {
|
return {
|
||||||
type: SHOW_TOAST,
|
type: SHOW_TOAST,
|
||||||
payload: {
|
payload: toast,
|
||||||
toastType,
|
|
||||||
parameters,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
hideToast,
|
hideToast,
|
||||||
|
|
|
@ -244,7 +244,7 @@ export function deleteUsername({
|
||||||
try {
|
try {
|
||||||
await doDeleteUsername(username);
|
await doDeleteUsername(username);
|
||||||
} catch {
|
} catch {
|
||||||
dispatch(showToast(ToastType.FailedToDeleteUsername));
|
dispatch(showToast({ toastType: ToastType.FailedToDeleteUsername }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,7 @@ import {
|
||||||
getSelectedMessageIds,
|
getSelectedMessageIds,
|
||||||
getTargetedMessage,
|
getTargetedMessage,
|
||||||
isMissingRequiredProfileSharing,
|
isMissingRequiredProfileSharing,
|
||||||
|
getMessages,
|
||||||
} from './conversations';
|
} from './conversations';
|
||||||
import {
|
import {
|
||||||
getIntl,
|
getIntl,
|
||||||
|
@ -1799,6 +1800,16 @@ export function canDeleteForEveryone(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const canDeleteMessagesForEveryone = createSelector(
|
||||||
|
[getMessages, (_state, messageIds: ReadonlyArray<string>) => messageIds],
|
||||||
|
(messagesLookup, messageIds) => {
|
||||||
|
return messageIds.every(messageId => {
|
||||||
|
const message = getOwn(messagesLookup, messageId);
|
||||||
|
return message != null && canDeleteForEveryone(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export function canRetryDeleteForEveryone(
|
export function canRetryDeleteForEveryone(
|
||||||
message: Pick<
|
message: Pick<
|
||||||
MessageWithUIFieldsType,
|
MessageWithUIFieldsType,
|
||||||
|
|
|
@ -19,7 +19,6 @@ import { getEmojiSkinTone } from '../selectors/items';
|
||||||
import {
|
import {
|
||||||
getConversationSelector,
|
getConversationSelector,
|
||||||
getGroupAdminsSelector,
|
getGroupAdminsSelector,
|
||||||
getLastSelectedMessage,
|
|
||||||
getSelectedMessageIds,
|
getSelectedMessageIds,
|
||||||
isMissingRequiredProfileSharing,
|
isMissingRequiredProfileSharing,
|
||||||
} from '../selectors/conversations';
|
} from '../selectors/conversations';
|
||||||
|
@ -93,7 +92,6 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||||
const recentEmojis = selectRecentEmojis(state);
|
const recentEmojis = selectRecentEmojis(state);
|
||||||
|
|
||||||
const selectedMessageIds = getSelectedMessageIds(state);
|
const selectedMessageIds = getSelectedMessageIds(state);
|
||||||
const lastSelectedMessage = getLastSelectedMessage(state);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Base
|
// Base
|
||||||
|
@ -170,7 +168,6 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||||
|
|
||||||
// Select Mode
|
// Select Mode
|
||||||
selectedMessageIds,
|
selectedMessageIds,
|
||||||
lastSelectedMessage,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ export function SmartConversationView(): JSX.Element {
|
||||||
const hasOpenModal = useSelector((state: StateType) => {
|
const hasOpenModal = useSelector((state: StateType) => {
|
||||||
return (
|
return (
|
||||||
state.globalModals.forwardMessagesProps != null ||
|
state.globalModals.forwardMessagesProps != null ||
|
||||||
|
state.globalModals.deleteMessagesProps != null ||
|
||||||
state.globalModals.hasConfirmationModal
|
state.globalModals.hasConfirmationModal
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
61
ts/state/smart/DeleteMessagesModal.tsx
Normal file
61
ts/state/smart/DeleteMessagesModal.tsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import type { DeleteMessagesPropsType } from '../ducks/globalModals';
|
||||||
|
import type { StateType } from '../reducer';
|
||||||
|
import { getIntl } from '../selectors/user';
|
||||||
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
import DeleteMessagesModal from '../../components/DeleteMessagesModal';
|
||||||
|
import { strictAssert } from '../../util/assert';
|
||||||
|
import { canDeleteMessagesForEveryone } from '../selectors/message';
|
||||||
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
|
import { useToastActions } from '../ducks/toast';
|
||||||
|
|
||||||
|
export function SmartDeleteMessagesModal(): JSX.Element | null {
|
||||||
|
const deleteMessagesProps = useSelector<
|
||||||
|
StateType,
|
||||||
|
DeleteMessagesPropsType | undefined
|
||||||
|
>(state => state.globalModals.deleteMessagesProps);
|
||||||
|
strictAssert(
|
||||||
|
deleteMessagesProps != null,
|
||||||
|
'Cannot render delete messages modal without messages'
|
||||||
|
);
|
||||||
|
const { conversationId, messageIds, onDelete } = deleteMessagesProps;
|
||||||
|
const canDeleteForEveryone = useSelector((state: StateType) => {
|
||||||
|
return canDeleteMessagesForEveryone(state, messageIds);
|
||||||
|
});
|
||||||
|
const lastSelectedMessage = useSelector((state: StateType) => {
|
||||||
|
return state.conversations.lastSelectedMessage;
|
||||||
|
});
|
||||||
|
const i18n = useSelector(getIntl);
|
||||||
|
const { toggleDeleteMessagesModal } = useGlobalModalActions();
|
||||||
|
const { deleteMessages, deleteMessagesForEveryone } =
|
||||||
|
useConversationsActions();
|
||||||
|
const { showToast } = useToastActions();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DeleteMessagesModal
|
||||||
|
canDeleteForEveryone={canDeleteForEveryone}
|
||||||
|
i18n={i18n}
|
||||||
|
messageCount={deleteMessagesProps.messageIds.length}
|
||||||
|
onClose={() => {
|
||||||
|
toggleDeleteMessagesModal(undefined);
|
||||||
|
}}
|
||||||
|
onDeleteForMe={() => {
|
||||||
|
deleteMessages({
|
||||||
|
conversationId,
|
||||||
|
messageIds,
|
||||||
|
lastSelectedMessage,
|
||||||
|
});
|
||||||
|
onDelete?.();
|
||||||
|
}}
|
||||||
|
onDeleteForEveryone={() => {
|
||||||
|
deleteMessagesForEveryone(messageIds);
|
||||||
|
onDelete?.();
|
||||||
|
}}
|
||||||
|
showToast={showToast}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ import { SmartStoriesSettingsModal } from './StoriesSettingsModal';
|
||||||
import { getConversationsStoppingSend } from '../selectors/conversations';
|
import { getConversationsStoppingSend } from '../selectors/conversations';
|
||||||
import { getIntl, getTheme } from '../selectors/user';
|
import { getIntl, getTheme } from '../selectors/user';
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
import { SmartDeleteMessagesModal } from './DeleteMessagesModal';
|
||||||
|
|
||||||
function renderEditHistoryMessagesModal(): JSX.Element {
|
function renderEditHistoryMessagesModal(): JSX.Element {
|
||||||
return <SmartEditHistoryMessagesModal />;
|
return <SmartEditHistoryMessagesModal />;
|
||||||
|
@ -34,6 +35,10 @@ function renderContactModal(): JSX.Element {
|
||||||
return <SmartContactModal />;
|
return <SmartContactModal />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderDeleteMessagesModal(): JSX.Element {
|
||||||
|
return <SmartDeleteMessagesModal />;
|
||||||
|
}
|
||||||
|
|
||||||
function renderForwardMessagesModal(): JSX.Element {
|
function renderForwardMessagesModal(): JSX.Element {
|
||||||
return <SmartForwardMessagesModal />;
|
return <SmartForwardMessagesModal />;
|
||||||
}
|
}
|
||||||
|
@ -62,6 +67,7 @@ export function SmartGlobalModalContainer(): JSX.Element {
|
||||||
contactModalState,
|
contactModalState,
|
||||||
editHistoryMessages,
|
editHistoryMessages,
|
||||||
errorModalProps,
|
errorModalProps,
|
||||||
|
deleteMessagesProps,
|
||||||
forwardMessagesProps,
|
forwardMessagesProps,
|
||||||
isProfileEditorVisible,
|
isProfileEditorVisible,
|
||||||
isShortcutGuideModalVisible,
|
isShortcutGuideModalVisible,
|
||||||
|
@ -128,6 +134,7 @@ export function SmartGlobalModalContainer(): JSX.Element {
|
||||||
contactModalState={contactModalState}
|
contactModalState={contactModalState}
|
||||||
editHistoryMessages={editHistoryMessages}
|
editHistoryMessages={editHistoryMessages}
|
||||||
errorModalProps={errorModalProps}
|
errorModalProps={errorModalProps}
|
||||||
|
deleteMessagesProps={deleteMessagesProps}
|
||||||
forwardMessagesProps={forwardMessagesProps}
|
forwardMessagesProps={forwardMessagesProps}
|
||||||
hasSafetyNumberChangeModal={hasSafetyNumberChangeModal}
|
hasSafetyNumberChangeModal={hasSafetyNumberChangeModal}
|
||||||
hideUserNotFoundModal={hideUserNotFoundModal}
|
hideUserNotFoundModal={hideUserNotFoundModal}
|
||||||
|
@ -142,6 +149,7 @@ export function SmartGlobalModalContainer(): JSX.Element {
|
||||||
renderContactModal={renderContactModal}
|
renderContactModal={renderContactModal}
|
||||||
renderEditHistoryMessagesModal={renderEditHistoryMessagesModal}
|
renderEditHistoryMessagesModal={renderEditHistoryMessagesModal}
|
||||||
renderErrorModal={renderErrorModal}
|
renderErrorModal={renderErrorModal}
|
||||||
|
renderDeleteMessagesModal={renderDeleteMessagesModal}
|
||||||
renderForwardMessagesModal={renderForwardMessagesModal}
|
renderForwardMessagesModal={renderForwardMessagesModal}
|
||||||
renderProfileEditor={renderProfileEditor}
|
renderProfileEditor={renderProfileEditor}
|
||||||
renderSafetyNumber={renderSafetyNumber}
|
renderSafetyNumber={renderSafetyNumber}
|
||||||
|
|
|
@ -139,7 +139,9 @@ export function SmartStoryViewer(): JSX.Element | null {
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
onSetSkinTone={onSetSkinTone}
|
onSetSkinTone={onSetSkinTone}
|
||||||
onTextTooLong={() => showToast(ToastType.MessageBodyTooLong)}
|
onTextTooLong={() => {
|
||||||
|
showToast({ toastType: ToastType.MessageBodyTooLong });
|
||||||
|
}}
|
||||||
onUseEmoji={onUseEmoji}
|
onUseEmoji={onUseEmoji}
|
||||||
onMediaPlaybackStart={pauseVoiceNotePlayer}
|
onMediaPlaybackStart={pauseVoiceNotePlayer}
|
||||||
preferredReactionEmoji={preferredReactionEmoji}
|
preferredReactionEmoji={preferredReactionEmoji}
|
||||||
|
|
|
@ -112,8 +112,6 @@ export function SmartTimelineItem(props: ExternalProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
blockGroupLinkRequests,
|
blockGroupLinkRequests,
|
||||||
clearTargetedMessage: clearSelectedMessage,
|
clearTargetedMessage: clearSelectedMessage,
|
||||||
deleteMessages,
|
|
||||||
deleteMessageForEveryone,
|
|
||||||
doubleCheckMissingQuoteReference,
|
doubleCheckMissingQuoteReference,
|
||||||
kickOffAttachmentDownload,
|
kickOffAttachmentDownload,
|
||||||
markAttachmentAsCorrupted,
|
markAttachmentAsCorrupted,
|
||||||
|
@ -138,6 +136,7 @@ export function SmartTimelineItem(props: ExternalProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
showContactModal,
|
showContactModal,
|
||||||
showEditHistoryModal,
|
showEditHistoryModal,
|
||||||
|
toggleDeleteMessagesModal,
|
||||||
toggleForwardMessagesModal,
|
toggleForwardMessagesModal,
|
||||||
toggleSafetyNumberModal,
|
toggleSafetyNumberModal,
|
||||||
} = useGlobalModalActions();
|
} = useGlobalModalActions();
|
||||||
|
@ -177,8 +176,6 @@ export function SmartTimelineItem(props: ExternalProps): JSX.Element {
|
||||||
blockGroupLinkRequests={blockGroupLinkRequests}
|
blockGroupLinkRequests={blockGroupLinkRequests}
|
||||||
checkForAccount={checkForAccount}
|
checkForAccount={checkForAccount}
|
||||||
clearTargetedMessage={clearSelectedMessage}
|
clearTargetedMessage={clearSelectedMessage}
|
||||||
deleteMessages={deleteMessages}
|
|
||||||
deleteMessageForEveryone={deleteMessageForEveryone}
|
|
||||||
doubleCheckMissingQuoteReference={doubleCheckMissingQuoteReference}
|
doubleCheckMissingQuoteReference={doubleCheckMissingQuoteReference}
|
||||||
kickOffAttachmentDownload={kickOffAttachmentDownload}
|
kickOffAttachmentDownload={kickOffAttachmentDownload}
|
||||||
markAttachmentAsCorrupted={markAttachmentAsCorrupted}
|
markAttachmentAsCorrupted={markAttachmentAsCorrupted}
|
||||||
|
@ -202,6 +199,7 @@ export function SmartTimelineItem(props: ExternalProps): JSX.Element {
|
||||||
showSpoiler={showSpoiler}
|
showSpoiler={showSpoiler}
|
||||||
startCallingLobby={startCallingLobby}
|
startCallingLobby={startCallingLobby}
|
||||||
startConversation={startConversation}
|
startConversation={startConversation}
|
||||||
|
toggleDeleteMessagesModal={toggleDeleteMessagesModal}
|
||||||
toggleForwardMessagesModal={toggleForwardMessagesModal}
|
toggleForwardMessagesModal={toggleForwardMessagesModal}
|
||||||
toggleSafetyNumberModal={toggleSafetyNumberModal}
|
toggleSafetyNumberModal={toggleSafetyNumberModal}
|
||||||
viewStory={viewStory}
|
viewStory={viewStory}
|
||||||
|
|
|
@ -441,7 +441,6 @@ describe('electron/state/ducks/username', () => {
|
||||||
type: 'toast/SHOW_TOAST',
|
type: 'toast/SHOW_TOAST',
|
||||||
payload: {
|
payload: {
|
||||||
toastType: ToastType.FailedToDeleteUsername,
|
toastType: ToastType.FailedToDeleteUsername,
|
||||||
parameters: undefined,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,9 +40,68 @@ export enum ToastType {
|
||||||
StoryVideoUnsupported = 'StoryVideoUnsupported',
|
StoryVideoUnsupported = 'StoryVideoUnsupported',
|
||||||
TapToViewExpiredIncoming = 'TapToViewExpiredIncoming',
|
TapToViewExpiredIncoming = 'TapToViewExpiredIncoming',
|
||||||
TapToViewExpiredOutgoing = 'TapToViewExpiredOutgoing',
|
TapToViewExpiredOutgoing = 'TapToViewExpiredOutgoing',
|
||||||
|
TooManyMessagesToDeleteForEveryone = 'TooManyMessagesToDeleteForEveryone',
|
||||||
TooManyMessagesToForward = 'TooManyMessagesToForward',
|
TooManyMessagesToForward = 'TooManyMessagesToForward',
|
||||||
UnableToLoadAttachment = 'UnableToLoadAttachment',
|
UnableToLoadAttachment = 'UnableToLoadAttachment',
|
||||||
UnsupportedMultiAttachment = 'UnsupportedMultiAttachment',
|
UnsupportedMultiAttachment = 'UnsupportedMultiAttachment',
|
||||||
UnsupportedOS = 'UnsupportedOS',
|
UnsupportedOS = 'UnsupportedOS',
|
||||||
UserAddedToGroup = 'UserAddedToGroup',
|
UserAddedToGroup = 'UserAddedToGroup',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AnyToast =
|
||||||
|
| { toastType: ToastType.AddingUserToGroup; parameters: { contact: string } }
|
||||||
|
| { toastType: ToastType.AlreadyGroupMember }
|
||||||
|
| { toastType: ToastType.AlreadyRequestedToJoin }
|
||||||
|
| { toastType: ToastType.Blocked }
|
||||||
|
| { toastType: ToastType.BlockedGroup }
|
||||||
|
| { toastType: ToastType.CannotForwardEmptyMessage }
|
||||||
|
| { toastType: ToastType.CannotMixMultiAndNonMultiAttachments }
|
||||||
|
| { toastType: ToastType.CannotOpenGiftBadgeIncoming }
|
||||||
|
| { toastType: ToastType.CannotOpenGiftBadgeOutgoing }
|
||||||
|
| { toastType: ToastType.CannotStartGroupCall }
|
||||||
|
| {
|
||||||
|
toastType: ToastType.ConversationArchived;
|
||||||
|
parameters: { conversationId: string };
|
||||||
|
}
|
||||||
|
| { toastType: ToastType.ConversationMarkedUnread }
|
||||||
|
| { toastType: ToastType.ConversationRemoved; parameters: { title: string } }
|
||||||
|
| { toastType: ToastType.ConversationUnarchived }
|
||||||
|
| { toastType: ToastType.CopiedUsername }
|
||||||
|
| { toastType: ToastType.CopiedUsernameLink }
|
||||||
|
| { toastType: ToastType.DangerousFileType }
|
||||||
|
| { toastType: ToastType.DeleteForEveryoneFailed }
|
||||||
|
| { toastType: ToastType.Error }
|
||||||
|
| { toastType: ToastType.Expired }
|
||||||
|
| { toastType: ToastType.FailedToDeleteUsername }
|
||||||
|
| { toastType: ToastType.FileSaved; parameters: { fullPath: string } }
|
||||||
|
| {
|
||||||
|
toastType: ToastType.FileSize;
|
||||||
|
parameters: { limit: number; units: string };
|
||||||
|
}
|
||||||
|
| { toastType: ToastType.InvalidConversation }
|
||||||
|
| { toastType: ToastType.LeftGroup }
|
||||||
|
| { toastType: ToastType.MaxAttachments }
|
||||||
|
| { toastType: ToastType.MessageBodyTooLong }
|
||||||
|
| { toastType: ToastType.OriginalMessageNotFound }
|
||||||
|
| { toastType: ToastType.PinnedConversationsFull }
|
||||||
|
| { toastType: ToastType.ReactionFailed }
|
||||||
|
| { toastType: ToastType.ReportedSpamAndBlocked }
|
||||||
|
| { toastType: ToastType.StoryMuted }
|
||||||
|
| { toastType: ToastType.StoryReact }
|
||||||
|
| { toastType: ToastType.StoryReply }
|
||||||
|
| { toastType: ToastType.StoryVideoError }
|
||||||
|
| { toastType: ToastType.StoryVideoUnsupported }
|
||||||
|
| { toastType: ToastType.TapToViewExpiredIncoming }
|
||||||
|
| { toastType: ToastType.TapToViewExpiredOutgoing }
|
||||||
|
| {
|
||||||
|
toastType: ToastType.TooManyMessagesToDeleteForEveryone;
|
||||||
|
parameters: { count: number };
|
||||||
|
}
|
||||||
|
| { toastType: ToastType.TooManyMessagesToForward }
|
||||||
|
| { toastType: ToastType.UnableToLoadAttachment }
|
||||||
|
| { toastType: ToastType.UnsupportedMultiAttachment }
|
||||||
|
| { toastType: ToastType.UnsupportedOS }
|
||||||
|
| {
|
||||||
|
toastType: ToastType.UserAddedToGroup;
|
||||||
|
parameters: { contact: string; group: string };
|
||||||
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@ import type { ConversationAttributesType } from '../model-types';
|
||||||
import { hasExpired } from '../state/selectors/expiration';
|
import { hasExpired } from '../state/selectors/expiration';
|
||||||
import { isOSUnsupported } from '../state/selectors/updates';
|
import { isOSUnsupported } from '../state/selectors/updates';
|
||||||
|
|
||||||
|
import type { AnyToast } from '../types/Toast';
|
||||||
import { ToastType } from '../types/Toast';
|
import { ToastType } from '../types/Toast';
|
||||||
import {
|
import {
|
||||||
isDirectConversation,
|
isDirectConversation,
|
||||||
|
@ -17,13 +18,13 @@ const MAX_MESSAGE_BODY_LENGTH = 64 * 1024;
|
||||||
export function shouldShowInvalidMessageToast(
|
export function shouldShowInvalidMessageToast(
|
||||||
conversationAttributes: ConversationAttributesType,
|
conversationAttributes: ConversationAttributesType,
|
||||||
messageText?: string
|
messageText?: string
|
||||||
): ToastType | undefined {
|
): AnyToast | undefined {
|
||||||
const state = window.reduxStore.getState();
|
const state = window.reduxStore.getState();
|
||||||
if (hasExpired(state)) {
|
if (hasExpired(state)) {
|
||||||
if (isOSUnsupported(state)) {
|
if (isOSUnsupported(state)) {
|
||||||
return ToastType.UnsupportedOS;
|
return { toastType: ToastType.UnsupportedOS };
|
||||||
}
|
}
|
||||||
return ToastType.Expired;
|
return { toastType: ToastType.Expired };
|
||||||
}
|
}
|
||||||
|
|
||||||
const isValid =
|
const isValid =
|
||||||
|
@ -32,7 +33,7 @@ export function shouldShowInvalidMessageToast(
|
||||||
isGroupV2(conversationAttributes);
|
isGroupV2(conversationAttributes);
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
return ToastType.InvalidConversation;
|
return { toastType: ToastType.InvalidConversation };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { e164, uuid } = conversationAttributes;
|
const { e164, uuid } = conversationAttributes;
|
||||||
|
@ -41,7 +42,7 @@ export function shouldShowInvalidMessageToast(
|
||||||
((e164 && window.storage.blocked.isBlocked(e164)) ||
|
((e164 && window.storage.blocked.isBlocked(e164)) ||
|
||||||
(uuid && window.storage.blocked.isUuidBlocked(uuid)))
|
(uuid && window.storage.blocked.isUuidBlocked(uuid)))
|
||||||
) {
|
) {
|
||||||
return ToastType.Blocked;
|
return { toastType: ToastType.Blocked };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { groupId } = conversationAttributes;
|
const { groupId } = conversationAttributes;
|
||||||
|
@ -50,18 +51,18 @@ export function shouldShowInvalidMessageToast(
|
||||||
groupId &&
|
groupId &&
|
||||||
window.storage.blocked.isGroupBlocked(groupId)
|
window.storage.blocked.isGroupBlocked(groupId)
|
||||||
) {
|
) {
|
||||||
return ToastType.BlockedGroup;
|
return { toastType: ToastType.BlockedGroup };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isDirectConversation(conversationAttributes) &&
|
!isDirectConversation(conversationAttributes) &&
|
||||||
conversationAttributes.left
|
conversationAttributes.left
|
||||||
) {
|
) {
|
||||||
return ToastType.LeftGroup;
|
return { toastType: ToastType.LeftGroup };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (messageText && messageText.length > MAX_MESSAGE_BODY_LENGTH) {
|
if (messageText && messageText.length > MAX_MESSAGE_BODY_LENGTH) {
|
||||||
return ToastType.MessageBodyTooLong;
|
return { toastType: ToastType.MessageBodyTooLong };
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
Loading…
Reference in a new issue