Ensure consistency in forwarding logic
This commit is contained in:
parent
38666fe0a4
commit
15263c2d16
9 changed files with 72 additions and 9 deletions
|
@ -199,6 +199,7 @@ export type OwnProps = Readonly<{
|
|||
props: SmartCompositionRecordingDraftProps
|
||||
) => JSX.Element | null;
|
||||
selectedMessageIds: ReadonlyArray<string> | undefined;
|
||||
areSelectedMessagesForwardable: boolean | undefined;
|
||||
toggleSelectMode: (on: boolean) => void;
|
||||
toggleForwardMessagesModal: (
|
||||
payload: ForwardMessagesPayload,
|
||||
|
@ -367,6 +368,7 @@ export const CompositionArea = memo(function CompositionArea({
|
|||
renderSmartCompositionRecordingDraft,
|
||||
// Selected messages
|
||||
selectedMessageIds,
|
||||
areSelectedMessagesForwardable,
|
||||
toggleSelectMode,
|
||||
toggleForwardMessagesModal,
|
||||
// DraftGifMessageSendModal
|
||||
|
@ -906,6 +908,7 @@ export const CompositionArea = memo(function CompositionArea({
|
|||
<SelectModeActions
|
||||
i18n={i18n}
|
||||
selectedMessageIds={selectedMessageIds}
|
||||
areSelectedMessagesForwardable={areSelectedMessagesForwardable === true}
|
||||
onExitSelectMode={() => {
|
||||
toggleSelectMode(false);
|
||||
}}
|
||||
|
|
|
@ -74,6 +74,7 @@ const defaultMessageProps: TimelineMessagesProps = {
|
|||
}),
|
||||
canCopy: true,
|
||||
canEditMessage: true,
|
||||
canForward: true,
|
||||
canReact: true,
|
||||
canReply: true,
|
||||
canRetry: true,
|
||||
|
|
|
@ -12,6 +12,7 @@ const MAX_FORWARD_COUNT = 30;
|
|||
|
||||
type SelectModeActionsProps = Readonly<{
|
||||
selectedMessageIds: ReadonlyArray<string>;
|
||||
areSelectedMessagesForwardable: boolean;
|
||||
onExitSelectMode: () => void;
|
||||
onDeleteMessages: () => void;
|
||||
onForwardMessages: () => void;
|
||||
|
@ -21,6 +22,7 @@ type SelectModeActionsProps = Readonly<{
|
|||
|
||||
export default function SelectModeActions({
|
||||
selectedMessageIds,
|
||||
areSelectedMessagesForwardable,
|
||||
onExitSelectMode,
|
||||
onDeleteMessages,
|
||||
onForwardMessages,
|
||||
|
@ -31,7 +33,10 @@ export default function SelectModeActions({
|
|||
const tooManyMessagesToForward =
|
||||
selectedMessageIds.length > MAX_FORWARD_COUNT;
|
||||
|
||||
const canForward = hasSelectedMessages && !tooManyMessagesToForward;
|
||||
const canForward =
|
||||
hasSelectedMessages &&
|
||||
areSelectedMessagesForwardable &&
|
||||
!tooManyMessagesToForward;
|
||||
const canDelete = hasSelectedMessages;
|
||||
|
||||
return (
|
||||
|
|
|
@ -51,6 +51,7 @@ function mockMessageTimelineItem(
|
|||
canDeleteForEveryone: false,
|
||||
canDownload: true,
|
||||
canEditMessage: true,
|
||||
canForward: true,
|
||||
canReact: true,
|
||||
canReply: true,
|
||||
canRetry: true,
|
||||
|
|
|
@ -255,6 +255,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
canReply: true,
|
||||
canDownload: true,
|
||||
canDeleteForEveryone: overrideProps.canDeleteForEveryone || false,
|
||||
canForward: true,
|
||||
canRetry: overrideProps.canRetry || false,
|
||||
canRetryDeleteForEveryone: overrideProps.canRetryDeleteForEveryone || false,
|
||||
checkForAccount: action('checkForAccount'),
|
||||
|
@ -266,7 +267,6 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
conversationId: overrideProps.conversationId ?? '',
|
||||
conversationType: overrideProps.conversationType || 'direct',
|
||||
contact: overrideProps.contact,
|
||||
deletedForEveryone: overrideProps.deletedForEveryone,
|
||||
// disableMenu: overrideProps.disableMenu,
|
||||
disableScroll: overrideProps.disableScroll,
|
||||
direction: overrideProps.direction || 'incoming',
|
||||
|
@ -796,11 +796,13 @@ export function Deleted(): JSX.Element {
|
|||
const propsSent = createProps({
|
||||
conversationType: 'direct',
|
||||
deletedForEveryone: true,
|
||||
canForward: false,
|
||||
status: 'sent',
|
||||
});
|
||||
const propsSending = createProps({
|
||||
conversationType: 'direct',
|
||||
deletedForEveryone: true,
|
||||
canForward: false,
|
||||
status: 'sending',
|
||||
});
|
||||
|
||||
|
@ -817,6 +819,7 @@ DeletedWithExpireTimer.args = {
|
|||
timestamp: Date.now() - 60 * 1000,
|
||||
conversationType: 'group',
|
||||
deletedForEveryone: true,
|
||||
canForward: false,
|
||||
expirationLength: 5 * 60 * 1000,
|
||||
expirationTimestamp: Date.now() + 3 * 60 * 1000,
|
||||
status: 'sent',
|
||||
|
@ -2265,6 +2268,7 @@ const fullContact = {
|
|||
export const EmbeddedContactFullContact = Template.bind({});
|
||||
EmbeddedContactFullContact.args = {
|
||||
contact: fullContact,
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
export const EmbeddedContactAvatarUndownloaded = Template.bind({});
|
||||
|
@ -2279,6 +2283,7 @@ EmbeddedContactAvatarUndownloaded.args = {
|
|||
isProfile: true,
|
||||
},
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
export const EmbeddedContactAvatarDownloading = Template.bind({});
|
||||
EmbeddedContactAvatarDownloading.args = {
|
||||
|
@ -2295,6 +2300,7 @@ EmbeddedContactAvatarDownloading.args = {
|
|||
isProfile: true,
|
||||
},
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
export const EmbeddedContactAvatarTransientError = Template.bind({});
|
||||
EmbeddedContactAvatarTransientError.args = {
|
||||
|
@ -2316,6 +2322,7 @@ EmbeddedContactAvatarTransientError.args = {
|
|||
isProfile: true,
|
||||
},
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
export const EmbeddedContactAvatarPermanentError = Template.bind({});
|
||||
EmbeddedContactAvatarPermanentError.args = {
|
||||
|
@ -2335,6 +2342,7 @@ EmbeddedContactAvatarPermanentError.args = {
|
|||
isProfile: true,
|
||||
},
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
export const EmbeddedContactWithSendMessage = Template.bind({});
|
||||
|
@ -2345,6 +2353,7 @@ EmbeddedContactWithSendMessage.args = {
|
|||
serviceId: generateAci(),
|
||||
},
|
||||
direction: 'incoming',
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
export const EmbeddedContactOnlyEmail = Template.bind({});
|
||||
|
@ -2352,6 +2361,7 @@ EmbeddedContactOnlyEmail.args = {
|
|||
contact: {
|
||||
email: fullContact.email,
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
export const EmbeddedContactGivenName = Template.bind({});
|
||||
|
@ -2361,6 +2371,7 @@ EmbeddedContactGivenName.args = {
|
|||
givenName: 'Jerry',
|
||||
},
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
export const EmbeddedContactOrganization = Template.bind({});
|
||||
|
@ -2368,6 +2379,7 @@ EmbeddedContactOrganization.args = {
|
|||
contact: {
|
||||
organization: 'Company 5',
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
export const EmbeddedContactGivenFamilyName = Template.bind({});
|
||||
|
@ -2378,6 +2390,7 @@ EmbeddedContactGivenFamilyName.args = {
|
|||
familyName: 'FamilyName',
|
||||
},
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
export const EmbeddedContactFamilyName = Template.bind({});
|
||||
|
@ -2387,6 +2400,7 @@ EmbeddedContactFamilyName.args = {
|
|||
familyName: 'FamilyName',
|
||||
},
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
export const GiftBadgeUnopened = Template.bind({});
|
||||
|
@ -2397,6 +2411,7 @@ GiftBadgeUnopened.args = {
|
|||
level: 3,
|
||||
state: GiftBadgeStates.Unopened,
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
export const GiftBadgeFailed = Template.bind({});
|
||||
|
@ -2404,6 +2419,7 @@ GiftBadgeFailed.args = {
|
|||
giftBadge: {
|
||||
state: GiftBadgeStates.Failed,
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
const getPreferredBadge = () => ({
|
||||
|
@ -2430,6 +2446,7 @@ GiftBadgeRedeemed30Days.args = {
|
|||
level: 3,
|
||||
state: GiftBadgeStates.Redeemed,
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
export const GiftBadgeRedeemed24Hours = Template.bind({});
|
||||
|
@ -2441,6 +2458,7 @@ GiftBadgeRedeemed24Hours.args = {
|
|||
level: 3,
|
||||
state: GiftBadgeStates.Redeemed,
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
export const GiftBadgeOpened60Minutes = Template.bind({});
|
||||
|
@ -2452,6 +2470,7 @@ GiftBadgeOpened60Minutes.args = {
|
|||
level: 3,
|
||||
state: GiftBadgeStates.Opened,
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
export const GiftBadgeRedeemed1Minute = Template.bind({});
|
||||
|
@ -2463,6 +2482,7 @@ GiftBadgeRedeemed1Minute.args = {
|
|||
level: 3,
|
||||
state: GiftBadgeStates.Redeemed,
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
export const GiftBadgeOpenedExpired = Template.bind({});
|
||||
|
@ -2474,6 +2494,7 @@ GiftBadgeOpenedExpired.args = {
|
|||
level: 3,
|
||||
state: GiftBadgeStates.Opened,
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
export const GiftBadgeMissingBadge = Template.bind({});
|
||||
|
@ -2485,12 +2506,14 @@ GiftBadgeMissingBadge.args = {
|
|||
level: 3,
|
||||
state: GiftBadgeStates.Redeemed,
|
||||
},
|
||||
canForward: false,
|
||||
};
|
||||
|
||||
export const PaymentNotification = Template.bind({});
|
||||
PaymentNotification.args = {
|
||||
canReply: false,
|
||||
canReact: false,
|
||||
canForward: false,
|
||||
payment: {
|
||||
kind: PaymentEventKind.Notification,
|
||||
note: 'Hello there',
|
||||
|
|
|
@ -48,6 +48,7 @@ export type PropsData = {
|
|||
canDownload: boolean;
|
||||
canCopy: boolean;
|
||||
canEditMessage: boolean;
|
||||
canForward: boolean;
|
||||
canRetry: boolean;
|
||||
canRetryDeleteForEveryone: boolean;
|
||||
canReact: boolean;
|
||||
|
@ -96,6 +97,7 @@ export function TimelineMessage(props: Props): JSX.Element {
|
|||
canDownload,
|
||||
canCopy,
|
||||
canEditMessage,
|
||||
canForward,
|
||||
canReact,
|
||||
canReply,
|
||||
canRetry,
|
||||
|
@ -103,15 +105,11 @@ export function TimelineMessage(props: Props): JSX.Element {
|
|||
containerElementRef,
|
||||
containerWidthBreakpoint,
|
||||
conversationId,
|
||||
deletedForEveryone,
|
||||
direction,
|
||||
giftBadge,
|
||||
i18n,
|
||||
id,
|
||||
isTargeted,
|
||||
isTapToView,
|
||||
kickOffAttachmentDownload,
|
||||
payment,
|
||||
copyMessageText,
|
||||
pushPanelForConversation,
|
||||
reactToMessage,
|
||||
|
@ -255,8 +253,6 @@ export function TimelineMessage(props: Props): JSX.Element {
|
|||
);
|
||||
|
||||
const handleContextMenu = useHandleMessageContextMenu(menuTriggerRef);
|
||||
const canForward =
|
||||
!isTapToView && !deletedForEveryone && !giftBadge && !payment;
|
||||
|
||||
const shouldShowAdditional =
|
||||
doesMessageBodyOverflow(text || '') || !isWindowWidthNotNarrow;
|
||||
|
|
|
@ -754,6 +754,7 @@ export const getPropsForMessage = (
|
|||
canEditMessage: canEditMessage(message),
|
||||
canDeleteForEveryone: canDeleteForEveryone(message, conversation.isMe),
|
||||
canDownload: canDownload(message, conversationSelector),
|
||||
canForward: canForward(message),
|
||||
canReact: canReact(message, ourConversationId, conversationSelector),
|
||||
canReply: canReply(message, ourConversationId, conversationSelector),
|
||||
canRetry: hasErrors(message),
|
||||
|
@ -2104,6 +2105,15 @@ export function canDownload(
|
|||
return false;
|
||||
}
|
||||
|
||||
export function canForward(message: ReadonlyMessageAttributesType): boolean {
|
||||
return (
|
||||
!isTapToView(message) &&
|
||||
!message.deletedForEveryone &&
|
||||
!message.giftBadge &&
|
||||
!getPayment(message)
|
||||
);
|
||||
}
|
||||
|
||||
export function getLastChallengeError(
|
||||
message: Pick<MessageWithUIFieldsType, 'errors'>
|
||||
): ShallowChallengeError | undefined {
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
getGroupAdminsSelector,
|
||||
getHasPanelOpen,
|
||||
getLastEditableMessageId,
|
||||
getMessages,
|
||||
getSelectedMessageIds,
|
||||
isMissingRequiredProfileSharing,
|
||||
} from '../selectors/conversations';
|
||||
|
@ -37,7 +38,7 @@ import {
|
|||
getShowStickersIntroduction,
|
||||
getTextFormattingEnabled,
|
||||
} from '../selectors/items';
|
||||
import { getPropsForQuote } from '../selectors/message';
|
||||
import { canForward, getPropsForQuote } from '../selectors/message';
|
||||
import {
|
||||
getBlessedStickerPacks,
|
||||
getInstalledStickerPacks,
|
||||
|
@ -96,6 +97,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
|
|||
const emojiSkinToneDefault = useSelector(getEmojiSkinToneDefault);
|
||||
const recentEmojis = useSelector(selectRecentEmojis);
|
||||
const selectedMessageIds = useSelector(getSelectedMessageIds);
|
||||
const messageLookup = useSelector(getMessages);
|
||||
const isFormattingEnabled = useSelector(getTextFormattingEnabled);
|
||||
const lastEditableMessageId = useSelector(getLastEditableMessageId);
|
||||
const receivedPacks = useSelector(getReceivedStickerPacks);
|
||||
|
@ -133,6 +135,16 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
|
|||
shouldSendHighQualityAttachments,
|
||||
} = composerState;
|
||||
|
||||
const areSelectedMessagesForwardable = useMemo(() => {
|
||||
return selectedMessageIds?.every(messageId => {
|
||||
const message = messageLookup[messageId];
|
||||
if (!message) {
|
||||
return false;
|
||||
}
|
||||
return canForward(message);
|
||||
});
|
||||
}, [messageLookup, selectedMessageIds]);
|
||||
|
||||
const isActive = useMemo(() => {
|
||||
return !hasGlobalModalOpen && !hasPanelOpen;
|
||||
}, [hasGlobalModalOpen, hasPanelOpen]);
|
||||
|
@ -365,6 +377,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
|
|||
sortedGroupMembers={conversation.sortedGroupMembers ?? null}
|
||||
// Select Mode
|
||||
selectedMessageIds={selectedMessageIds}
|
||||
areSelectedMessagesForwardable={areSelectedMessagesForwardable}
|
||||
toggleSelectMode={toggleSelectMode}
|
||||
toggleForwardMessagesModal={toggleForwardMessagesModal}
|
||||
// DraftGifMessageSendModal
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
sortByMessageOrder,
|
||||
type ForwardMessageData,
|
||||
} from '../types/ForwardDraft';
|
||||
import { canForward } from '../state/selectors/message';
|
||||
|
||||
export async function maybeForwardMessages(
|
||||
messages: Array<ForwardMessageData>,
|
||||
|
@ -36,6 +37,16 @@ export async function maybeForwardMessages(
|
|||
.map(id => window.ConversationController.get(id))
|
||||
.filter(isNotNil);
|
||||
|
||||
const areAllMessagesForwardable = messages.every(msg =>
|
||||
msg.originalMessage ? canForward(msg.originalMessage) : true
|
||||
);
|
||||
|
||||
if (!areAllMessagesForwardable) {
|
||||
throw new Error(
|
||||
'maybeForwardMessage: Attempting to forward unforwardable message(s)'
|
||||
);
|
||||
}
|
||||
|
||||
const cannotSend = conversations.some(
|
||||
conversation =>
|
||||
conversation?.get('announcementsOnly') && !conversation.areWeAdmin()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue