- {isEditingMessage || !isMessageEditable ? (
+ {isEditingMessage || !isLonelyDraftEditable ? (
) : (
);
}
+
+type ForwardMessageEditorProps = Readonly<{
+ draft: MessageForwardDraft;
+ linkPreview: LinkPreviewType | null | void;
+ removeLinkPreview(): void;
+ RenderCompositionTextArea: (
+ props: SmartCompositionTextAreaProps
+ ) => JSX.Element;
+ onChange: (messageText: string, caretLocation?: number) => unknown;
+ onSubmit: () => unknown;
+ theme: ThemeType;
+ i18n: LocalizerType;
+}>;
+
+function ForwardMessageEditor({
+ draft,
+ linkPreview,
+ i18n,
+ RenderCompositionTextArea,
+ removeLinkPreview,
+ onChange,
+ onSubmit,
+ theme,
+}: ForwardMessageEditorProps): JSX.Element {
+ const [attachmentsToForward, setAttachmentsToForward] = useState<
+ ReadonlyArray
+ >(draft.attachments ?? []);
+
+ return (
+
+ {linkPreview ? (
+
+
+
+ ) : null}
+ {attachmentsToForward && attachmentsToForward.length ? (
+
{
+ const newAttachments = attachmentsToForward.filter(
+ currentAttachment => currentAttachment !== attachment
+ );
+ setAttachmentsToForward(newAttachments);
+ }}
+ />
+ ) : null}
+
+ {
+ onChange(messageText, caretLocation);
+ }}
+ onSubmit={onSubmit}
+ theme={theme}
+ />
+
+ );
+}
diff --git a/ts/components/GlobalModalContainer.tsx b/ts/components/GlobalModalContainer.tsx
index 612a88584887..7bf25ae0c370 100644
--- a/ts/components/GlobalModalContainer.tsx
+++ b/ts/components/GlobalModalContainer.tsx
@@ -4,10 +4,10 @@
import React from 'react';
import type {
ContactModalStateType,
- ForwardMessagePropsType,
UserNotFoundModalStateType,
SafetyNumberChangedBlockingDataType,
AuthorizeArtCreatorDataType,
+ ForwardMessagesPropsType,
} from '../state/ducks/globalModals';
import type { LocalizerType, ThemeType } from '../types/Util';
import { missingCaseError } from '../util/missingCaseError';
@@ -35,8 +35,8 @@ export type PropsType = {
title?: string;
}) => JSX.Element;
// ForwardMessageModal
- forwardMessageProps: ForwardMessagePropsType | undefined;
- renderForwardMessageModal: () => JSX.Element;
+ forwardMessagesProps: ForwardMessagesPropsType | undefined;
+ renderForwardMessagesModal: () => JSX.Element;
// ProfileEditor
isProfileEditorVisible: boolean;
renderProfileEditor: () => JSX.Element;
@@ -86,8 +86,8 @@ export function GlobalModalContainer({
errorModalProps,
renderErrorModal,
// ForwardMessageModal
- forwardMessageProps,
- renderForwardMessageModal,
+ forwardMessagesProps,
+ renderForwardMessagesModal,
// ProfileEditor
isProfileEditorVisible,
renderProfileEditor,
@@ -147,8 +147,8 @@ export function GlobalModalContainer({
return renderContactModal();
}
- if (forwardMessageProps) {
- return renderForwardMessageModal();
+ if (forwardMessagesProps) {
+ return renderForwardMessagesModal();
}
if (isProfileEditorVisible) {
diff --git a/ts/components/Inbox.tsx b/ts/components/Inbox.tsx
index be0a9421d63b..53b160dc0ba0 100644
--- a/ts/components/Inbox.tsx
+++ b/ts/components/Inbox.tsx
@@ -13,7 +13,7 @@ import { ToastStickerPackInstallFailed } from './ToastStickerPackInstallFailed';
import { WhatsNewLink } from './WhatsNewLink';
import { showToast } from '../util/showToast';
import { strictAssert } from '../util/assert';
-import { SelectedMessageSource } from '../state/ducks/conversationsEnums';
+import { TargetedMessageSource } from '../state/ducks/conversationsEnums';
import { usePrevious } from '../hooks/usePrevious';
export type PropsType = {
@@ -28,8 +28,8 @@ export type PropsType = {
renderMiniPlayer: (options: { shouldFlow: boolean }) => JSX.Element;
scrollToMessage: (conversationId: string, messageId: string) => unknown;
selectedConversationId?: string;
- selectedMessage?: string;
- selectedMessageSource?: SelectedMessageSource;
+ targetedMessage?: string;
+ targetedMessageSource?: TargetedMessageSource;
showConversation: ShowConversationType;
showWhatsNewModal: () => unknown;
};
@@ -46,8 +46,8 @@ export function Inbox({
renderMiniPlayer,
scrollToMessage,
selectedConversationId,
- selectedMessage,
- selectedMessageSource,
+ targetedMessage,
+ targetedMessageSource,
showConversation,
showWhatsNewModal,
}: PropsType): JSX.Element {
@@ -67,14 +67,14 @@ export function Inbox({
}
if (selectedConversationId) {
- onConversationOpened(selectedConversationId, selectedMessage);
+ onConversationOpened(selectedConversationId, targetedMessage);
}
} else if (
selectedConversationId &&
- selectedMessage &&
- selectedMessageSource !== SelectedMessageSource.Focus
+ targetedMessage &&
+ targetedMessageSource !== TargetedMessageSource.Focus
) {
- scrollToMessage(selectedConversationId, selectedMessage);
+ scrollToMessage(selectedConversationId, targetedMessage);
}
if (!selectedConversationId) {
@@ -93,8 +93,8 @@ export function Inbox({
prevConversationId,
scrollToMessage,
selectedConversationId,
- selectedMessage,
- selectedMessageSource,
+ targetedMessage,
+ targetedMessageSource,
]);
useEffect(() => {
diff --git a/ts/components/LeftPane.stories.tsx b/ts/components/LeftPane.stories.tsx
index e7ddc3119a44..b0371aa24fc9 100644
--- a/ts/components/LeftPane.stories.tsx
+++ b/ts/components/LeftPane.stories.tsx
@@ -242,7 +242,7 @@ const useProps = (overrideProps: OverridePropsType = {}): PropsType => {
/>
),
selectedConversationId: undefined,
- selectedMessageId: undefined,
+ targetedMessageId: undefined,
savePreferredLeftPaneWidth: action('savePreferredLeftPaneWidth'),
searchInConversation: action('searchInConversation'),
setComposeSearchTerm: action('setComposeSearchTerm'),
diff --git a/ts/components/LeftPane.tsx b/ts/components/LeftPane.tsx
index 17e5fed9fd87..c2f5ba384eb0 100644
--- a/ts/components/LeftPane.tsx
+++ b/ts/components/LeftPane.tsx
@@ -96,7 +96,7 @@ export type PropsType = {
isMacOS: boolean;
preferredWidthFromStorage: number;
selectedConversationId: undefined | string;
- selectedMessageId: undefined | string;
+ targetedMessageId: undefined | string;
regionCode: string | undefined;
challengeStatus: 'idle' | 'required' | 'pending';
setChallengeStatus: (status: 'idle') => void;
@@ -185,7 +185,7 @@ export function LeftPane({
savePreferredLeftPaneWidth,
searchInConversation,
selectedConversationId,
- selectedMessageId,
+ targetedMessageId,
setChallengeStatus,
setComposeGroupAvatar,
setComposeGroupExpireTimer,
@@ -372,7 +372,7 @@ export function LeftPane({
conversationToOpen = helper.getConversationAndMessageInDirection(
toFind,
selectedConversationId,
- selectedMessageId
+ targetedMessageId
);
}
}
@@ -404,7 +404,7 @@ export function LeftPane({
isMacOS,
searchInConversation,
selectedConversationId,
- selectedMessageId,
+ targetedMessageId,
showChooseGroupMembers,
showConversation,
showInbox,
diff --git a/ts/components/Lightbox.stories.tsx b/ts/components/Lightbox.stories.tsx
index 999c971ddda0..5f600473ec16 100644
--- a/ts/components/Lightbox.stories.tsx
+++ b/ts/components/Lightbox.stories.tsx
@@ -68,7 +68,7 @@ const createProps = (overrideProps: Partial = {}): PropsType => {
media,
saveAttachment: action('saveAttachment'),
selectedIndex,
- toggleForwardMessageModal: action('toggleForwardMessageModal'),
+ toggleForwardMessagesModal: action('toggleForwardMessagesModal'),
onMediaPlaybackStart: noop,
onPrevAttachment: () => {
setSelectedIndex(Math.max(0, selectedIndex - 1));
diff --git a/ts/components/Lightbox.tsx b/ts/components/Lightbox.tsx
index 40e85b1d2b86..95ad21578ca7 100644
--- a/ts/components/Lightbox.tsx
+++ b/ts/components/Lightbox.tsx
@@ -34,7 +34,7 @@ export type PropsType = {
media: ReadonlyArray>;
saveAttachment: SaveAttachmentActionCreatorType;
selectedIndex: number;
- toggleForwardMessageModal: (messageId: string) => unknown;
+ toggleForwardMessagesModal: (messageIds: ReadonlyArray) => unknown;
onMediaPlaybackStart: () => void;
onNextAttachment: () => void;
onPrevAttachment: () => void;
@@ -77,7 +77,7 @@ export function Lightbox({
isViewOnce = false,
saveAttachment,
selectedIndex,
- toggleForwardMessageModal,
+ toggleForwardMessagesModal,
onMediaPlaybackStart,
onNextAttachment,
onPrevAttachment,
@@ -186,7 +186,7 @@ export function Lightbox({
closeLightbox();
const mediaItem = media[selectedIndex];
- toggleForwardMessageModal(mediaItem.message.id);
+ toggleForwardMessagesModal([mediaItem.message.id]);
};
const onKeyDown = useCallback(
diff --git a/ts/components/StoryViewsNRepliesModal.tsx b/ts/components/StoryViewsNRepliesModal.tsx
index 63e0511b7f84..a0f685098161 100644
--- a/ts/components/StoryViewsNRepliesModal.tsx
+++ b/ts/components/StoryViewsNRepliesModal.tsx
@@ -43,11 +43,15 @@ import { ConfirmationDialog } from './ConfirmationDialog';
const MESSAGE_DEFAULT_PROPS = {
canDeleteForEveryone: false,
checkForAccount: shouldNeverBeCalled,
- clearSelectedMessage: shouldNeverBeCalled,
+ clearTargetedMessage: shouldNeverBeCalled,
containerWidthBreakpoint: WidthBreakpoint.Medium,
doubleCheckMissingQuoteReference: shouldNeverBeCalled,
isBlocked: false,
isMessageRequestAccepted: true,
+ isSelected: false,
+ isSelectMode: false,
+ onToggleSelect: shouldNeverBeCalled,
+ onReplyToMessage: shouldNeverBeCalled,
kickOffAttachmentDownload: shouldNeverBeCalled,
markAttachmentAsCorrupted: shouldNeverBeCalled,
messageExpanded: shouldNeverBeCalled,
diff --git a/ts/components/ToastManager.tsx b/ts/components/ToastManager.tsx
index db9454099056..73c2e72cc6e3 100644
--- a/ts/components/ToastManager.tsx
+++ b/ts/components/ToastManager.tsx
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
+import { get } from 'lodash';
import type { LocalizerType, ReplacementValuesType } from '../types/Util';
import { SECOND } from '../util/durations';
import { Toast } from './Toast';
@@ -71,6 +72,14 @@ export function ToastManager({
return {i18n('unblockGroupToSend')};
}
+ if (toastType === ToastType.CannotForwardEmptyMessage) {
+ return (
+
+ {i18n('icu:ForwardMessagesModal__toast--CannotForwardEmptyMessage')}
+
+ );
+ }
+
if (toastType === ToastType.CannotMixMultiAndNonMultiAttachments) {
return (
@@ -302,6 +311,16 @@ export function ToastManager({
);
}
+ if (toastType === ToastType.TooManyMessagesToForward) {
+ return (
+
+ {i18n('icu:SelectModeActions__toast--TooManyMessagesToForward', {
+ count: get(toast.parameters, 'count'),
+ })}
+
+ );
+ }
+
if (toastType === ToastType.UnableToLoadAttachment) {
return {i18n('unableToLoadAttachment')};
}
diff --git a/ts/components/conversation/ConversationHeader.stories.tsx b/ts/components/conversation/ConversationHeader.stories.tsx
index 4cf64a095428..d71a9d035769 100644
--- a/ts/components/conversation/ConversationHeader.stories.tsx
+++ b/ts/components/conversation/ConversationHeader.stories.tsx
@@ -48,6 +48,7 @@ const commonProps = {
onArchive: action('onArchive'),
onMarkUnread: action('onMarkUnread'),
+ toggleSelectMode: action('toggleSelectMode'),
onMoveToInbox: action('onMoveToInbox'),
pushPanelForConversation: action('pushPanelForConversation'),
popPanelForConversation: action('popPanelForConversation'),
diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx
index 7e702fae7aa6..452d2ab531a1 100644
--- a/ts/components/conversation/ConversationHeader.tsx
+++ b/ts/components/conversation/ConversationHeader.tsx
@@ -88,6 +88,7 @@ export type PropsActionsType = {
destroyMessages: (conversationId: string) => void;
onArchive: (conversationId: string) => void;
onMarkUnread: (conversationId: string) => void;
+ toggleSelectMode: (on: boolean) => void;
onMoveToInbox: (conversationId: string) => void;
onOutgoingAudioCallInConversation: (conversationId: string) => void;
onOutgoingVideoCallInConversation: (conversationId: string) => void;
@@ -350,6 +351,7 @@ export class ConversationHeader extends React.Component {
muteExpiresAt,
onArchive,
onMarkUnread,
+ toggleSelectMode,
onMoveToInbox,
pushPanelForConversation,
setDisappearingMessages,
@@ -505,6 +507,13 @@ export class ConversationHeader extends React.Component {
{i18n('markUnread')}
) : null}
+
{isArchived ? (