ConversationView: Pull various functions out of getMessageActions

This commit is contained in:
Scott Nonnenberg 2022-12-19 17:04:47 -08:00 committed by GitHub
parent 5a98fc2f4c
commit 1e282ee5d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 440 additions and 570 deletions

View file

@ -49,7 +49,7 @@ export default {
queueStoryDownload: { queueStoryDownload: {
action: true, action: true,
}, },
retrySend: { retryMessageSend: {
action: true, action: true,
}, },
viewStory: { action: true }, viewStory: { action: true },

View file

@ -27,7 +27,7 @@ export type PropsType = {
onForward: (storyId: string) => unknown; onForward: (storyId: string) => unknown;
onSave: (story: StoryViewType) => unknown; onSave: (story: StoryViewType) => unknown;
queueStoryDownload: (storyId: string) => unknown; queueStoryDownload: (storyId: string) => unknown;
retrySend: (messageId: string) => unknown; retryMessageSend: (messageId: string) => unknown;
viewStory: ViewStoryActionCreatorType; viewStory: ViewStoryActionCreatorType;
hasViewReceiptSetting: boolean; hasViewReceiptSetting: boolean;
}; };
@ -40,7 +40,7 @@ export function MyStories({
onForward, onForward,
onSave, onSave,
queueStoryDownload, queueStoryDownload,
retrySend, retryMessageSend,
viewStory, viewStory,
hasViewReceiptSetting, hasViewReceiptSetting,
}: PropsType): JSX.Element { }: PropsType): JSX.Element {
@ -95,7 +95,7 @@ export function MyStories({
onForward={onForward} onForward={onForward}
onSave={onSave} onSave={onSave}
queueStoryDownload={queueStoryDownload} queueStoryDownload={queueStoryDownload}
retrySend={retrySend} retryMessageSend={retryMessageSend}
setConfirmDeleteStory={setConfirmDeleteStory} setConfirmDeleteStory={setConfirmDeleteStory}
story={story} story={story}
viewStory={viewStory} viewStory={viewStory}
@ -120,7 +120,7 @@ type StorySentPropsType = Pick<
| 'onForward' | 'onForward'
| 'onSave' | 'onSave'
| 'queueStoryDownload' | 'queueStoryDownload'
| 'retrySend' | 'retryMessageSend'
| 'viewStory' | 'viewStory'
> & { > & {
setConfirmDeleteStory: (_: StoryViewType | undefined) => unknown; setConfirmDeleteStory: (_: StoryViewType | undefined) => unknown;
@ -133,7 +133,7 @@ function StorySent({
onForward, onForward,
onSave, onSave,
queueStoryDownload, queueStoryDownload,
retrySend, retryMessageSend,
setConfirmDeleteStory, setConfirmDeleteStory,
story, story,
viewStory, viewStory,
@ -155,7 +155,7 @@ function StorySent({
sendStatus === ResolvedSendStatus.PartiallySent) sendStatus === ResolvedSendStatus.PartiallySent)
) { ) {
setWasManuallyRetried(true); setWasManuallyRetried(true);
retrySend(story.messageId); retryMessageSend(story.messageId);
return; return;
} }

View file

@ -45,7 +45,7 @@ export default {
}, },
queueStoryDownload: { action: true }, queueStoryDownload: { action: true },
renderStoryCreator: { action: true }, renderStoryCreator: { action: true },
retrySend: { action: true }, retryMessageSend: { action: true },
showConversation: { action: true }, showConversation: { action: true },
showStoriesSettings: { action: true }, showStoriesSettings: { action: true },
showToast: { action: true }, showToast: { action: true },

View file

@ -42,7 +42,7 @@ export type PropsType = {
preferredWidthFromStorage: number; preferredWidthFromStorage: number;
queueStoryDownload: (storyId: string) => unknown; queueStoryDownload: (storyId: string) => unknown;
renderStoryCreator: () => JSX.Element; renderStoryCreator: () => JSX.Element;
retrySend: (messageId: string) => unknown; retryMessageSend: (messageId: string) => unknown;
setAddStoryData: (data: AddStoryData) => unknown; setAddStoryData: (data: AddStoryData) => unknown;
showConversation: ShowConversationType; showConversation: ShowConversationType;
showStoriesSettings: () => unknown; showStoriesSettings: () => unknown;
@ -70,7 +70,7 @@ export function Stories({
preferredWidthFromStorage, preferredWidthFromStorage,
queueStoryDownload, queueStoryDownload,
renderStoryCreator, renderStoryCreator,
retrySend, retryMessageSend,
setAddStoryData, setAddStoryData,
showConversation, showConversation,
showStoriesSettings, showStoriesSettings,
@ -111,7 +111,7 @@ export function Stories({
onForward={onForwardStory} onForward={onForwardStory}
onSave={onSaveStory} onSave={onSaveStory}
queueStoryDownload={queueStoryDownload} queueStoryDownload={queueStoryDownload}
retrySend={retrySend} retryMessageSend={retryMessageSend}
viewStory={viewStory} viewStory={viewStory}
/> />
) : ( ) : (

View file

@ -56,7 +56,7 @@ export default {
}, },
queueStoryDownload: { action: true }, queueStoryDownload: { action: true },
renderEmojiPicker: { action: true }, renderEmojiPicker: { action: true },
retrySend: { action: true }, retryMessageSend: { action: true },
showToast: { action: true }, showToast: { action: true },
skinTone: { skinTone: {
defaultValue: 0, defaultValue: 0,

View file

@ -101,7 +101,7 @@ export type PropsType = {
recentEmojis?: Array<string>; recentEmojis?: Array<string>;
renderEmojiPicker: (props: RenderEmojiPickerProps) => JSX.Element; renderEmojiPicker: (props: RenderEmojiPickerProps) => JSX.Element;
replyState?: ReplyStateType; replyState?: ReplyStateType;
retrySend: (messageId: string) => unknown; retryMessageSend: (messageId: string) => unknown;
saveAttachment: SaveAttachmentActionCreatorType; saveAttachment: SaveAttachmentActionCreatorType;
setHasAllStoriesUnmuted: (isUnmuted: boolean) => unknown; setHasAllStoriesUnmuted: (isUnmuted: boolean) => unknown;
showToast: ShowToastActionCreatorType; showToast: ShowToastActionCreatorType;
@ -153,7 +153,7 @@ export function StoryViewer({
recentEmojis, recentEmojis,
renderEmojiPicker, renderEmojiPicker,
replyState, replyState,
retrySend, retryMessageSend,
saveAttachment, saveAttachment,
setHasAllStoriesUnmuted, setHasAllStoriesUnmuted,
showToast, showToast,
@ -555,7 +555,7 @@ export function StoryViewer({
]; ];
} }
function doRetrySend() { function doRetryMessageSend() {
if (wasManuallyRetried) { if (wasManuallyRetried) {
return; return;
} }
@ -568,7 +568,7 @@ export function StoryViewer({
} }
setWasManuallyRetried(true); setWasManuallyRetried(true);
retrySend(messageId); retryMessageSend(messageId);
} }
return ( return (
@ -800,7 +800,7 @@ export function StoryViewer({
{sendStatus === ResolvedSendStatus.Failed && !wasManuallyRetried && ( {sendStatus === ResolvedSendStatus.Failed && !wasManuallyRetried && (
<button <button
className="StoryViewer__actions__failed" className="StoryViewer__actions__failed"
onClick={doRetrySend} onClick={doRetryMessageSend}
type="button" type="button"
> >
{i18n('StoryViewer__failed')} {i18n('StoryViewer__failed')}
@ -810,7 +810,7 @@ export function StoryViewer({
!wasManuallyRetried && ( !wasManuallyRetried && (
<button <button
className="StoryViewer__actions__failed" className="StoryViewer__actions__failed"
onClick={doRetrySend} onClick={doRetryMessageSend}
type="button" type="button"
> >
{i18n('StoryViewer__partial-fail')} {i18n('StoryViewer__partial-fail')}

View file

@ -54,7 +54,6 @@ const MESSAGE_DEFAULT_PROPS = {
isMessageRequestAccepted: true, isMessageRequestAccepted: true,
kickOffAttachmentDownload: shouldNeverBeCalled, kickOffAttachmentDownload: shouldNeverBeCalled,
markAttachmentAsCorrupted: shouldNeverBeCalled, markAttachmentAsCorrupted: shouldNeverBeCalled,
markViewed: shouldNeverBeCalled,
messageExpanded: shouldNeverBeCalled, messageExpanded: shouldNeverBeCalled,
openGiftBadge: shouldNeverBeCalled, openGiftBadge: shouldNeverBeCalled,
openLink: shouldNeverBeCalled, openLink: shouldNeverBeCalled,

View file

@ -1,24 +0,0 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import type { LocalizerType } from '../types/Util';
import { Toast } from './Toast';
export type ToastPropsType = {
i18n: LocalizerType;
isIncoming: boolean;
onClose: () => unknown;
};
export function ToastCannotOpenGiftBadge({
i18n,
isIncoming,
onClose,
}: ToastPropsType): JSX.Element {
const key = `message--giftBadge--unopened--toast--${
isIncoming ? 'incoming' : 'outgoing'
}`;
return <Toast onClose={onClose}>{i18n(key)}</Toast>;
}

View file

@ -70,6 +70,20 @@ CannotMixMultiAndNonMultiAttachments.args = {
}, },
}; };
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({}); export const CannotStartGroupCall = Template.bind({});
CannotStartGroupCall.args = { CannotStartGroupCall.args = {
toast: { toast: {
@ -182,6 +196,13 @@ PinnedConversationsFull.args = {
}, },
}; };
export const ReactionFailed = Template.bind({});
ReactionFailed.args = {
toast: {
toastType: ToastType.ReactionFailed,
},
};
export const ReportedSpamAndBlocked = Template.bind({}); export const ReportedSpamAndBlocked = Template.bind({});
ReportedSpamAndBlocked.args = { ReportedSpamAndBlocked.args = {
toast: { toast: {
@ -231,6 +252,20 @@ StoryVideoUnsupported.args = {
}, },
}; };
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({}); export const UnableToLoadAttachment = Template.bind({});
UnableToLoadAttachment.args = { UnableToLoadAttachment.args = {
toast: { toast: {

View file

@ -60,6 +60,22 @@ export function ToastManager({
); );
} }
if (toastType === ToastType.CannotOpenGiftBadgeIncoming) {
return (
<Toast onClose={hideToast}>
{i18n('message--giftBadge--unopened--toast--incoming')}
</Toast>
);
}
if (toastType === ToastType.CannotOpenGiftBadgeOutgoing) {
return (
<Toast onClose={hideToast}>
{i18n('message--giftBadge--unopened--toast--outgoing')}
</Toast>
);
}
if (toastType === ToastType.CannotStartGroupCall) { if (toastType === ToastType.CannotStartGroupCall) {
return ( return (
<Toast onClose={hideToast}> <Toast onClose={hideToast}>
@ -173,6 +189,10 @@ export function ToastManager({
return <Toast onClose={hideToast}>{i18n('pinnedConversationsFull')}</Toast>; return <Toast onClose={hideToast}>{i18n('pinnedConversationsFull')}</Toast>;
} }
if (toastType === ToastType.ReactionFailed) {
return <Toast onClose={hideToast}>{i18n('Reactions--error')}</Toast>;
}
if (toastType === ToastType.StoryMuted) { if (toastType === ToastType.StoryMuted) {
return ( return (
<Toast onClose={hideToast} timeout={SHORT_TIMEOUT}> <Toast onClose={hideToast} timeout={SHORT_TIMEOUT}>
@ -221,6 +241,22 @@ export function ToastManager({
); );
} }
if (toastType === ToastType.TapToViewExpiredIncoming) {
return (
<Toast onClose={hideToast}>
{i18n('Message--tap-to-view--incoming--expired-toast')}
</Toast>
);
}
if (toastType === ToastType.TapToViewExpiredOutgoing) {
return (
<Toast onClose={hideToast}>
{i18n('Message--tap-to-view--outgoing--expired-toast')}
</Toast>
);
}
if (toastType === ToastType.UnableToLoadAttachment) { if (toastType === ToastType.UnableToLoadAttachment) {
return <Toast onClose={hideToast}>{i18n('unableToLoadAttachment')}</Toast>; return <Toast onClose={hideToast}>{i18n('unableToLoadAttachment')}</Toast>;
} }

View file

@ -1,28 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { action } from '@storybook/addon-actions';
import { ToastReactionFailed } from './ToastReactionFailed';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const defaultProps = {
i18n,
onClose: action('onClose'),
};
export default {
title: 'Components/ToastReactionFailed',
};
export const _ToastReactionFailed = (): JSX.Element => (
<ToastReactionFailed {...defaultProps} />
);
_ToastReactionFailed.story = {
name: 'ToastReactionFailed',
};

View file

@ -1,15 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import type { LocalizerType } from '../types/Util';
import { Toast } from './Toast';
type PropsType = {
i18n: LocalizerType;
onClose: () => unknown;
};
export function ToastReactionFailed({ i18n, onClose }: PropsType): JSX.Element {
return <Toast onClose={onClose}>{i18n('Reactions--error')}</Toast>;
}

View file

@ -1,28 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { action } from '@storybook/addon-actions';
import { ToastTapToViewExpiredIncoming } from './ToastTapToViewExpiredIncoming';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const defaultProps = {
i18n,
onClose: action('onClose'),
};
export default {
title: 'Components/ToastTapToViewExpiredIncoming',
};
export const _ToastTapToViewExpiredIncoming = (): JSX.Element => (
<ToastTapToViewExpiredIncoming {...defaultProps} />
);
_ToastTapToViewExpiredIncoming.story = {
name: 'ToastTapToViewExpiredIncoming',
};

View file

@ -1,22 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import type { LocalizerType } from '../types/Util';
import { Toast } from './Toast';
type PropsType = {
i18n: LocalizerType;
onClose: () => unknown;
};
export function ToastTapToViewExpiredIncoming({
i18n,
onClose,
}: PropsType): JSX.Element {
return (
<Toast onClose={onClose}>
{i18n('Message--tap-to-view--incoming--expired-toast')}
</Toast>
);
}

View file

@ -1,28 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { action } from '@storybook/addon-actions';
import { ToastTapToViewExpiredOutgoing } from './ToastTapToViewExpiredOutgoing';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const defaultProps = {
i18n,
onClose: action('onClose'),
};
export default {
title: 'Components/ToastTapToViewExpiredOutgoing',
};
export const _ToastTapToViewExpiredOutgoing = (): JSX.Element => (
<ToastTapToViewExpiredOutgoing {...defaultProps} />
);
_ToastTapToViewExpiredOutgoing.story = {
name: 'ToastTapToViewExpiredOutgoing',
};

View file

@ -1,22 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import type { LocalizerType } from '../types/Util';
import { Toast } from './Toast';
type PropsType = {
i18n: LocalizerType;
onClose: () => unknown;
};
export function ToastTapToViewExpiredOutgoing({
i18n,
onClose,
}: PropsType): JSX.Element {
return (
<Toast onClose={onClose}>
{i18n('Message--tap-to-view--outgoing--expired-toast')}
</Toast>
);
}

View file

@ -91,6 +91,7 @@ import type { AnyPaymentEvent } from '../../types/Payment';
import { Emojify } from './Emojify'; import { Emojify } from './Emojify';
import { getPaymentEventDescription } from '../../messages/helpers'; import { getPaymentEventDescription } from '../../messages/helpers';
import { PanelType } from '../../types/Panels'; import { PanelType } from '../../types/Panels';
import { openLinkInWebBrowser } from '../../util/openLinkInWebBrowser';
const GUESS_METADATA_WIDTH_TIMESTAMP_SIZE = 16; const GUESS_METADATA_WIDTH_TIMESTAMP_SIZE = 16;
const GUESS_METADATA_WIDTH_EXPIRE_TIMER_SIZE = 18; const GUESS_METADATA_WIDTH_EXPIRE_TIMER_SIZE = 18;
@ -317,7 +318,6 @@ export type PropsActions = {
attachment: AttachmentType; attachment: AttachmentType;
messageId: string; messageId: string;
}) => void; }) => void;
markViewed(messageId: string): void;
saveAttachment: SaveAttachmentActionCreatorType; saveAttachment: SaveAttachmentActionCreatorType;
showLightbox: (options: { showLightbox: (options: {
attachment: AttachmentType; attachment: AttachmentType;
@ -325,7 +325,6 @@ export type PropsActions = {
}) => void; }) => void;
showLightboxForViewOnceMedia: (messageId: string) => unknown; showLightboxForViewOnceMedia: (messageId: string) => unknown;
openLink: (url: string) => void;
scrollToQuotedMessage: (options: { scrollToQuotedMessage: (options: {
authorId: string; authorId: string;
sentAt: number; sentAt: number;
@ -1072,7 +1071,6 @@ export class Message extends React.PureComponent<Props, State> {
i18n, i18n,
id, id,
kickOffAttachmentDownload, kickOffAttachmentDownload,
openLink,
previews, previews,
quote, quote,
shouldCollapseAbove, shouldCollapseAbove,
@ -1123,7 +1121,7 @@ export class Message extends React.PureComponent<Props, State> {
}); });
return; return;
} }
openLink(first.url); openLinkInWebBrowser(first.url);
} }
: noop; : noop;
const contents = ( const contents = (
@ -1208,14 +1206,14 @@ export class Message extends React.PureComponent<Props, State> {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
openLink(first.url); openLinkInWebBrowser(first.url);
} }
}} }}
onClick={(event: React.MouseEvent) => { onClick={(event: React.MouseEvent) => {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
openLink(first.url); openLinkInWebBrowser(first.url);
}} }}
> >
{contents} {contents}
@ -2277,15 +2275,8 @@ export class Message extends React.PureComponent<Props, State> {
return; return;
} }
if (attachments && !isDownloaded(attachments[0])) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
kickOffAttachmentDownload({
attachment: attachments[0],
messageId: id,
});
return;
}
if (isTapToViewExpired) { if (isTapToViewExpired) {
const action = const action =
@ -2293,12 +2284,20 @@ export class Message extends React.PureComponent<Props, State> {
? showExpiredOutgoingTapToViewToast ? showExpiredOutgoingTapToViewToast
: showExpiredIncomingTapToViewToast; : showExpiredIncomingTapToViewToast;
action(); action();
} else {
event.preventDefault(); return;
event.stopPropagation(); }
if (attachments && !isDownloaded(attachments[0])) {
kickOffAttachmentDownload({
attachment: attachments[0],
messageId: id,
});
return;
}
showLightboxForViewOnceMedia(id); showLightboxForViewOnceMedia(id);
}
return; return;
} }

View file

@ -77,10 +77,8 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
doubleCheckMissingQuoteReference: action('doubleCheckMissingQuoteReference'), doubleCheckMissingQuoteReference: action('doubleCheckMissingQuoteReference'),
kickOffAttachmentDownload: action('kickOffAttachmentDownload'), kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'), markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
markViewed: action('markViewed'),
showConversation: action('showConversation'), showConversation: action('showConversation'),
openGiftBadge: action('openGiftBadge'), openGiftBadge: action('openGiftBadge'),
openLink: action('openLink'),
renderAudioAttachment: () => <div>*AudioAttachment*</div>, renderAudioAttachment: () => <div>*AudioAttachment*</div>,
saveAttachment: action('saveAttachment'), saveAttachment: action('saveAttachment'),
pushPanelForConversation: action('pushPanelForConversation'), pushPanelForConversation: action('pushPanelForConversation'),

View file

@ -68,7 +68,6 @@ export type PropsData = {
i18n: LocalizerType; i18n: LocalizerType;
theme: ThemeType; theme: ThemeType;
getPreferredBadge: PreferredBadgeSelectorType; getPreferredBadge: PreferredBadgeSelectorType;
markViewed: (messageId: string) => void;
} & Pick< } & Pick<
MessagePropsType, MessagePropsType,
| 'getPreferredBadge' | 'getPreferredBadge'
@ -79,14 +78,7 @@ export type PropsData = {
export type PropsBackboneActions = Pick< export type PropsBackboneActions = Pick<
MessagePropsType, MessagePropsType,
| 'kickOffAttachmentDownload' 'renderAudioAttachment' | 'startConversation'
| 'markAttachmentAsCorrupted'
| 'openGiftBadge'
| 'openLink'
| 'renderAudioAttachment'
| 'showExpiredIncomingTapToViewToast'
| 'showExpiredOutgoingTapToViewToast'
| 'startConversation'
>; >;
export type PropsReduxActions = Pick< export type PropsReduxActions = Pick<
@ -94,10 +86,15 @@ export type PropsReduxActions = Pick<
| 'checkForAccount' | 'checkForAccount'
| 'clearSelectedMessage' | 'clearSelectedMessage'
| 'doubleCheckMissingQuoteReference' | 'doubleCheckMissingQuoteReference'
| 'kickOffAttachmentDownload'
| 'markAttachmentAsCorrupted'
| 'openGiftBadge'
| 'pushPanelForConversation' | 'pushPanelForConversation'
| 'saveAttachment' | 'saveAttachment'
| 'showContactModal' | 'showContactModal'
| 'showConversation' | 'showConversation'
| 'showExpiredIncomingTapToViewToast'
| 'showExpiredOutgoingTapToViewToast'
| 'showLightbox' | 'showLightbox'
| 'showLightboxForViewOnceMedia' | 'showLightboxForViewOnceMedia'
| 'viewStory' | 'viewStory'
@ -289,9 +286,7 @@ export class MessageDetail extends React.Component<Props> {
interactionMode, interactionMode,
kickOffAttachmentDownload, kickOffAttachmentDownload,
markAttachmentAsCorrupted, markAttachmentAsCorrupted,
markViewed,
openGiftBadge, openGiftBadge,
openLink,
pushPanelForConversation, pushPanelForConversation,
renderAudioAttachment, renderAudioAttachment,
saveAttachment, saveAttachment,
@ -334,11 +329,9 @@ export class MessageDetail extends React.Component<Props> {
interactionMode={interactionMode} interactionMode={interactionMode}
kickOffAttachmentDownload={kickOffAttachmentDownload} kickOffAttachmentDownload={kickOffAttachmentDownload}
markAttachmentAsCorrupted={markAttachmentAsCorrupted} markAttachmentAsCorrupted={markAttachmentAsCorrupted}
markViewed={markViewed}
messageExpanded={noop} messageExpanded={noop}
showConversation={showConversation} showConversation={showConversation}
openGiftBadge={openGiftBadge} openGiftBadge={openGiftBadge}
openLink={openLink}
pushPanelForConversation={pushPanelForConversation} pushPanelForConversation={pushPanelForConversation}
renderAudioAttachment={renderAudioAttachment} renderAudioAttachment={renderAudioAttachment}
saveAttachment={saveAttachment} saveAttachment={saveAttachment}

View file

@ -110,11 +110,9 @@ const defaultMessageProps: TimelineMessagesProps = {
isMessageRequestAccepted: true, isMessageRequestAccepted: true,
kickOffAttachmentDownload: action('default--kickOffAttachmentDownload'), kickOffAttachmentDownload: action('default--kickOffAttachmentDownload'),
markAttachmentAsCorrupted: action('default--markAttachmentAsCorrupted'), markAttachmentAsCorrupted: action('default--markAttachmentAsCorrupted'),
markViewed: action('default--markViewed'),
messageExpanded: action('default--message-expanded'), messageExpanded: action('default--message-expanded'),
showConversation: action('default--showConversation'), showConversation: action('default--showConversation'),
openGiftBadge: action('openGiftBadge'), openGiftBadge: action('openGiftBadge'),
openLink: action('default--openLink'),
previews: [], previews: [],
reactToMessage: action('default--reactToMessage'), reactToMessage: action('default--reactToMessage'),
readStatus: ReadStatus.Read, readStatus: ReadStatus.Read,
@ -122,7 +120,7 @@ const defaultMessageProps: TimelineMessagesProps = {
renderReactionPicker: () => <div />, renderReactionPicker: () => <div />,
renderAudioAttachment: () => <div>*AudioAttachment*</div>, renderAudioAttachment: () => <div>*AudioAttachment*</div>,
setQuoteByMessageId: action('default--setQuoteByMessageId'), setQuoteByMessageId: action('default--setQuoteByMessageId'),
retrySend: action('default--retrySend'), retryMessageSend: action('default--retryMessageSend'),
retryDeleteForEveryone: action('default--retryDeleteForEveryone'), retryDeleteForEveryone: action('default--retryDeleteForEveryone'),
saveAttachment: action('saveAttachment'), saveAttachment: action('saveAttachment'),
scrollToQuotedMessage: action('default--scrollToQuotedMessage'), scrollToQuotedMessage: action('default--scrollToQuotedMessage'),

View file

@ -278,23 +278,22 @@ const actions = () => ({
reactToMessage: action('reactToMessage'), reactToMessage: action('reactToMessage'),
setQuoteByMessageId: action('setQuoteByMessageId'), setQuoteByMessageId: action('setQuoteByMessageId'),
retryDeleteForEveryone: action('retryDeleteForEveryone'), retryDeleteForEveryone: action('retryDeleteForEveryone'),
retrySend: action('retrySend'), retryMessageSend: action('retryMessageSend'),
deleteMessage: action('deleteMessage'), deleteMessage: action('deleteMessage'),
deleteMessageForEveryone: action('deleteMessageForEveryone'), deleteMessageForEveryone: action('deleteMessageForEveryone'),
showMessageDetail: action('showMessageDetail'), showMessageDetail: action('showMessageDetail'),
saveAttachment: action('saveAttachment'), saveAttachment: action('saveAttachment'),
pushPanelForConversation: action('pushPanelForConversation'), pushPanelForConversation: action('pushPanelForConversation'),
showContactDetail: action('showContactDetail'),
showContactModal: action('showContactModal'), showContactModal: action('showContactModal'),
showConversation: action('showConversation'), showConversation: action('showConversation'),
kickOffAttachmentDownload: action('kickOffAttachmentDownload'), kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'), markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
markViewed: action('markViewed'),
messageExpanded: action('messageExpanded'), messageExpanded: action('messageExpanded'),
showLightbox: action('showLightbox'), showLightbox: action('showLightbox'),
showLightboxForViewOnceMedia: action('showLightboxForViewOnceMedia'), showLightboxForViewOnceMedia: action('showLightboxForViewOnceMedia'),
doubleCheckMissingQuoteReference: action('doubleCheckMissingQuoteReference'), doubleCheckMissingQuoteReference: action('doubleCheckMissingQuoteReference'),
openLink: action('openLink'),
openGiftBadge: action('openGiftBadge'), openGiftBadge: action('openGiftBadge'),
scrollToQuotedMessage: action('scrollToQuotedMessage'), scrollToQuotedMessage: action('scrollToQuotedMessage'),
showExpiredIncomingTapToViewToast: action( showExpiredIncomingTapToViewToast: action(
@ -307,8 +306,6 @@ const actions = () => ({
toggleSafetyNumberModal: action('toggleSafetyNumberModal'), toggleSafetyNumberModal: action('toggleSafetyNumberModal'),
downloadNewVersion: action('downloadNewVersion'),
startCallingLobby: action('startCallingLobby'), startCallingLobby: action('startCallingLobby'),
startConversation: action('startConversation'), startConversation: action('startConversation'),
returnToActiveCall: action('returnToActiveCall'), returnToActiveCall: action('returnToActiveCall'),

View file

@ -19,11 +19,8 @@ import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary';
import { WidthBreakpoint } from '../_util'; import { WidthBreakpoint } from '../_util';
import type { PropsActions as MessageActionsType } from './TimelineMessage'; import type { PropsActions as MessageActionsType } from './TimelineMessage';
import type { PropsActions as UnsupportedMessageActionsType } from './UnsupportedMessage';
import type { PropsActionsType as ChatSessionRefreshedNotificationActionsType } from './ChatSessionRefreshedNotification'; import type { PropsActionsType as ChatSessionRefreshedNotificationActionsType } from './ChatSessionRefreshedNotification';
import type { PropsActionsType as GroupV2ChangeActionsType } from './GroupV2Change';
import { ErrorBoundary } from './ErrorBoundary'; import { ErrorBoundary } from './ErrorBoundary';
import type { PropsActions as SafetyNumberActionsType } from './SafetyNumberNotification';
import { Intl } from '../Intl'; import { Intl } from '../Intl';
import { TimelineWarning } from './TimelineWarning'; import { TimelineWarning } from './TimelineWarning';
import { TimelineWarnings } from './TimelineWarnings'; import { TimelineWarnings } from './TimelineWarnings';
@ -47,6 +44,8 @@ import {
} from '../../util/scrollUtil'; } from '../../util/scrollUtil';
import { LastSeenIndicator } from './LastSeenIndicator'; import { LastSeenIndicator } from './LastSeenIndicator';
import { MINUTE } from '../../util/durations'; import { MINUTE } from '../../util/durations';
import type { PropsActionsType as DeliveryIssueNotificationActionsType } from './DeliveryIssueNotification';
import type { PropsActionsType as GroupV2ChangeActionsType } from './GroupV2Change';
const AT_BOTTOM_THRESHOLD = 15; const AT_BOTTOM_THRESHOLD = 15;
const AT_BOTTOM_DETECTOR_STYLE = { height: AT_BOTTOM_THRESHOLD }; const AT_BOTTOM_DETECTOR_STYLE = { height: AT_BOTTOM_THRESHOLD };
@ -125,7 +124,7 @@ type PropsHousekeepingType = {
theme: ThemeType; theme: ThemeType;
renderItem: (props: { renderItem: (props: {
actionProps: PropsActionsType; actionProps: PropsActionsFromBackboneForChildrenType;
containerElementRef: RefObject<HTMLElement>; containerElementRef: RefObject<HTMLElement>;
containerWidthBreakpoint: WidthBreakpoint; containerWidthBreakpoint: WidthBreakpoint;
conversationId: string; conversationId: string;
@ -146,41 +145,47 @@ type PropsHousekeepingType = {
) => JSX.Element; ) => JSX.Element;
}; };
export type PropsActionsFromBackboneForChildrenType = Pick<
MessageActionsType,
'scrollToQuotedMessage' | 'showMessageDetail' | 'startConversation'
> &
ChatSessionRefreshedNotificationActionsType &
DeliveryIssueNotificationActionsType &
GroupV2ChangeActionsType;
export type PropsActionsType = { export type PropsActionsType = {
// From Backbone
acknowledgeGroupMemberNameCollisions: ( acknowledgeGroupMemberNameCollisions: (
groupNameCollisions: Readonly<GroupNameCollisionsWithIdsByTitle> groupNameCollisions: Readonly<GroupNameCollisionsWithIdsByTitle>
) => void; ) => void;
loadOlderMessages: (messageId: string) => unknown;
loadNewerMessages: (messageId: string) => unknown;
loadNewestMessages: (messageId: string, setFocus?: boolean) => unknown;
markMessageRead: (messageId: string) => unknown;
removeMember: (conversationId: string) => unknown;
unblurAvatar: () => void;
updateSharedGroups: () => unknown;
// From Redux
acceptConversation: (conversationId: string) => unknown;
blockConversation: (conversationId: string) => unknown;
blockAndReportSpam: (conversationId: string) => unknown;
clearInvitedUuidsForNewlyCreatedGroup: () => void; clearInvitedUuidsForNewlyCreatedGroup: () => void;
clearSelectedMessage: () => unknown;
closeContactSpoofingReview: () => void; closeContactSpoofingReview: () => void;
deleteConversation: (conversationId: string) => unknown;
setIsNearBottom: (conversationId: string, isNearBottom: boolean) => unknown; setIsNearBottom: (conversationId: string, isNearBottom: boolean) => unknown;
peekGroupCallForTheFirstTime: (conversationId: string) => unknown;
peekGroupCallIfItHasMembers: (conversationId: string) => unknown;
reviewGroupMemberNameCollision: (groupConversationId: string) => void; reviewGroupMemberNameCollision: (groupConversationId: string) => void;
reviewMessageRequestNameCollision: ( reviewMessageRequestNameCollision: (
_: Readonly<{ _: Readonly<{
safeConversationId: string; safeConversationId: string;
}> }>
) => void; ) => void;
learnMoreAboutDeliveryIssue: () => unknown;
loadOlderMessages: (messageId: string) => unknown;
loadNewerMessages: (messageId: string) => unknown;
loadNewestMessages: (messageId: string, setFocus?: boolean) => unknown;
markMessageRead: (messageId: string) => unknown;
blockConversation: (conversationId: string) => unknown;
blockAndReportSpam: (conversationId: string) => unknown;
deleteConversation: (conversationId: string) => unknown;
acceptConversation: (conversationId: string) => unknown;
peekGroupCallForTheFirstTime: (conversationId: string) => unknown;
peekGroupCallIfItHasMembers: (conversationId: string) => unknown;
removeMember: (conversationId: string) => unknown;
selectMessage: (messageId: string, conversationId: string) => unknown; selectMessage: (messageId: string, conversationId: string) => unknown;
clearSelectedMessage: () => unknown; showContactModal: (contactId: string, conversationId?: string) => void;
unblurAvatar: () => void; } & PropsActionsFromBackboneForChildrenType;
updateSharedGroups: () => unknown;
} & MessageActionsType &
SafetyNumberActionsType &
UnsupportedMessageActionsType &
GroupV2ChangeActionsType &
ChatSessionRefreshedNotificationActionsType;
export type PropsType = PropsDataType & export type PropsType = PropsDataType &
PropsHousekeepingType & PropsHousekeepingType &
@ -209,69 +214,29 @@ const getActions = createSelector(
// use `createSelector` to memoize them by the last seen `props` object. // use `createSelector` to memoize them by the last seen `props` object.
(props: PropsType) => props, (props: PropsType) => props,
(props: PropsType): PropsActionsType => { (props: PropsType): PropsActionsFromBackboneForChildrenType => {
// Note: Because TimelineItem is smart, we only need to include action creators here
// which are passed in from backbone and not available via mapDispatchToProps
const unsafe = pick(props, [ const unsafe = pick(props, [
'acknowledgeGroupMemberNameCollisions', // MessageActionsType
'blockGroupLinkRequests',
'clearInvitedUuidsForNewlyCreatedGroup',
'closeContactSpoofingReview',
'setIsNearBottom',
'reviewGroupMemberNameCollision',
'reviewMessageRequestNameCollision',
'learnMoreAboutDeliveryIssue',
'loadOlderMessages',
'loadNewerMessages',
'loadNewestMessages',
'markMessageRead',
'markViewed',
'acceptConversation',
'blockAndReportSpam',
'blockConversation',
'deleteConversation',
'peekGroupCallForTheFirstTime',
'peekGroupCallIfItHasMembers',
'removeMember',
'selectMessage',
'clearSelectedMessage',
'unblurAvatar',
'updateSharedGroups',
'doubleCheckMissingQuoteReference',
'checkForAccount',
'reactToMessage',
'retryDeleteForEveryone',
'retrySend',
'toggleForwardMessageModal',
'deleteMessage',
'deleteMessageForEveryone',
'showConversation',
'showMessageDetail',
'openGiftBadge',
'setQuoteByMessageId',
'showContactModal',
'kickOffAttachmentDownload',
'markAttachmentAsCorrupted',
'messageExpanded',
'saveAttachment',
'showLightbox',
'showLightboxForViewOnceMedia',
'openLink',
'pushPanelForConversation',
'scrollToQuotedMessage', 'scrollToQuotedMessage',
'showExpiredIncomingTapToViewToast', 'showMessageDetail',
'showExpiredOutgoingTapToViewToast',
'startConversation', 'startConversation',
'toggleSafetyNumberModal', // ChatSessionRefreshedNotificationActionsType
'downloadNewVersion',
'contactSupport', 'contactSupport',
'viewStory', // DeliveryIssueNotificationActionsType
'learnMoreAboutDeliveryIssue',
// GroupV2ChangeActionsType
'blockGroupLinkRequests',
]); ]);
const safe: AssertProps<PropsActionsType, typeof unsafe> = unsafe; const safe: AssertProps<
PropsActionsFromBackboneForChildrenType,
typeof unsafe
> = unsafe;
return safe; return safe;
} }

View file

@ -68,14 +68,13 @@ const getDefaultProps = () => ({
contactSupport: action('contactSupport'), contactSupport: action('contactSupport'),
setQuoteByMessageId: action('setQuoteByMessageId'), setQuoteByMessageId: action('setQuoteByMessageId'),
retryDeleteForEveryone: action('retryDeleteForEveryone'), retryDeleteForEveryone: action('retryDeleteForEveryone'),
retrySend: action('retrySend'), retryMessageSend: action('retryMessageSend'),
blockGroupLinkRequests: action('blockGroupLinkRequests'), blockGroupLinkRequests: action('blockGroupLinkRequests'),
deleteMessage: action('deleteMessage'), deleteMessage: action('deleteMessage'),
deleteMessageForEveryone: action('deleteMessageForEveryone'), deleteMessageForEveryone: action('deleteMessageForEveryone'),
kickOffAttachmentDownload: action('kickOffAttachmentDownload'), kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
learnMoreAboutDeliveryIssue: action('learnMoreAboutDeliveryIssue'), learnMoreAboutDeliveryIssue: action('learnMoreAboutDeliveryIssue'),
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'), markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
markViewed: action('markViewed'),
messageExpanded: action('messageExpanded'), messageExpanded: action('messageExpanded'),
showMessageDetail: action('showMessageDetail'), showMessageDetail: action('showMessageDetail'),
showConversation: action('showConversation'), showConversation: action('showConversation'),
@ -93,9 +92,7 @@ const getDefaultProps = () => ({
showExpiredOutgoingTapToViewToast: action( showExpiredOutgoingTapToViewToast: action(
'showExpiredIncomingTapToViewToast' 'showExpiredIncomingTapToViewToast'
), ),
openLink: action('openLink'),
scrollToQuotedMessage: action('scrollToQuotedMessage'), scrollToQuotedMessage: action('scrollToQuotedMessage'),
downloadNewVersion: action('downloadNewVersion'),
toggleSafetyNumberModal: action('toggleSafetyNumberModal'), toggleSafetyNumberModal: action('toggleSafetyNumberModal'),
startCallingLobby: action('startCallingLobby'), startCallingLobby: action('startCallingLobby'),
startConversation: action('startConversation'), startConversation: action('startConversation'),

View file

@ -26,10 +26,7 @@ import type { PropsData as ChangeNumberNotificationProps } from './ChangeNumberN
import { ChangeNumberNotification } from './ChangeNumberNotification'; import { ChangeNumberNotification } from './ChangeNumberNotification';
import type { CallingNotificationType } from '../../util/callingNotification'; import type { CallingNotificationType } from '../../util/callingNotification';
import { InlineNotificationWrapper } from './InlineNotificationWrapper'; import { InlineNotificationWrapper } from './InlineNotificationWrapper';
import type { import type { PropsData as UnsupportedMessageProps } from './UnsupportedMessage';
PropsActions as UnsupportedMessageActionsType,
PropsData as UnsupportedMessageProps,
} from './UnsupportedMessage';
import { UnsupportedMessage } from './UnsupportedMessage'; import { UnsupportedMessage } from './UnsupportedMessage';
import type { PropsData as TimerNotificationProps } from './TimerNotification'; import type { PropsData as TimerNotificationProps } from './TimerNotification';
import { TimerNotification } from './TimerNotification'; import { TimerNotification } from './TimerNotification';
@ -177,7 +174,6 @@ type PropsActionsType = MessageActionsType &
DeliveryIssueActionProps & DeliveryIssueActionProps &
GroupV2ChangeActionsType & GroupV2ChangeActionsType &
PropsChatSessionRefreshedActionsType & PropsChatSessionRefreshedActionsType &
UnsupportedMessageActionsType &
SafetyNumberActionsType; SafetyNumberActionsType;
export type PropsType = PropsLocalType & export type PropsType = PropsLocalType &

View file

@ -277,11 +277,9 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
isTapToViewExpired: overrideProps.isTapToViewExpired, isTapToViewExpired: overrideProps.isTapToViewExpired,
kickOffAttachmentDownload: action('kickOffAttachmentDownload'), kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'), markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
markViewed: action('markViewed'),
messageExpanded: action('messageExpanded'), messageExpanded: action('messageExpanded'),
showConversation: action('showConversation'), showConversation: action('showConversation'),
openGiftBadge: action('openGiftBadge'), openGiftBadge: action('openGiftBadge'),
openLink: action('openLink'),
previews: overrideProps.previews || [], previews: overrideProps.previews || [],
quote: overrideProps.quote || undefined, quote: overrideProps.quote || undefined,
reactions: overrideProps.reactions, reactions: overrideProps.reactions,
@ -295,7 +293,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
renderAudioAttachment, renderAudioAttachment,
saveAttachment: action('saveAttachment'), saveAttachment: action('saveAttachment'),
setQuoteByMessageId: action('setQuoteByMessageId'), setQuoteByMessageId: action('setQuoteByMessageId'),
retrySend: action('retrySend'), retryMessageSend: action('retryMessageSend'),
retryDeleteForEveryone: action('retryDeleteForEveryone'), retryDeleteForEveryone: action('retryDeleteForEveryone'),
scrollToQuotedMessage: action('scrollToQuotedMessage'), scrollToQuotedMessage: action('scrollToQuotedMessage'),
selectMessage: action('selectMessage'), selectMessage: action('selectMessage'),

View file

@ -49,7 +49,7 @@ export type PropsActions = {
id: string, id: string,
{ emoji, remove }: { emoji: string; remove: boolean } { emoji, remove }: { emoji: string; remove: boolean }
) => void; ) => void;
retrySend: (id: string) => void; retryMessageSend: (id: string) => void;
retryDeleteForEveryone: (id: string) => void; retryDeleteForEveryone: (id: string) => void;
setQuoteByMessageId: (conversationId: string, messageId: string) => void; setQuoteByMessageId: (conversationId: string, messageId: string) => void;
} & MessagePropsActions; } & MessagePropsActions;
@ -99,7 +99,7 @@ export function TimelineMessage(props: Props): JSX.Element {
setQuoteByMessageId, setQuoteByMessageId,
renderReactionPicker, renderReactionPicker,
renderEmojiPicker, renderEmojiPicker,
retrySend, retryMessageSend,
retryDeleteForEveryone, retryDeleteForEveryone,
selectedReaction, selectedReaction,
toggleForwardMessageModal, toggleForwardMessageModal,
@ -395,7 +395,7 @@ export function TimelineMessage(props: Props): JSX.Element {
onDownload={handleDownload} onDownload={handleDownload}
onReplyToMessage={handleReplyToMessage} onReplyToMessage={handleReplyToMessage}
onReact={handleReact} onReact={handleReact}
onRetrySend={canRetry ? () => retrySend(id) : undefined} onRetryMessageSend={canRetry ? () => retryMessageSend(id) : undefined}
onRetryDeleteForEveryone={ onRetryDeleteForEveryone={
canRetryDeleteForEveryone canRetryDeleteForEveryone
? () => retryDeleteForEveryone(id) ? () => retryDeleteForEveryone(id)
@ -575,7 +575,7 @@ type MessageContextProps = {
onDownload: (() => void) | undefined; onDownload: (() => void) | undefined;
onReplyToMessage: (() => void) | undefined; onReplyToMessage: (() => void) | undefined;
onReact: (() => void) | undefined; onReact: (() => void) | undefined;
onRetrySend: (() => void) | undefined; onRetryMessageSend: (() => void) | undefined;
onRetryDeleteForEveryone: (() => void) | undefined; onRetryDeleteForEveryone: (() => void) | undefined;
onForward: (() => void) | undefined; onForward: (() => void) | undefined;
onDeleteForMe: () => void; onDeleteForMe: () => void;
@ -591,7 +591,7 @@ const MessageContextMenu = ({
onReplyToMessage, onReplyToMessage,
onReact, onReact,
onMoreInfo, onMoreInfo,
onRetrySend, onRetryMessageSend,
onRetryDeleteForEveryone, onRetryDeleteForEveryone,
onForward, onForward,
onDeleteForMe, onDeleteForMe,
@ -660,7 +660,7 @@ const MessageContextMenu = ({
> >
{i18n('moreInfo')} {i18n('moreInfo')}
</MenuItem> </MenuItem>
{onRetrySend && ( {onRetryMessageSend && (
<MenuItem <MenuItem
attributes={{ attributes={{
className: className:
@ -670,7 +670,7 @@ const MessageContextMenu = ({
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
onRetrySend(); onRetryMessageSend();
}} }}
> >
{i18n('retrySend')} {i18n('retrySend')}

View file

@ -3,7 +3,6 @@
import * as React from 'react'; import * as React from 'react';
import { boolean, text } from '@storybook/addon-knobs'; import { boolean, text } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import { setupI18n } from '../../util/setupI18n'; import { setupI18n } from '../../util/setupI18n';
import enMessages from '../../../_locales/en/messages.json'; import enMessages from '../../../_locales/en/messages.json';
@ -26,7 +25,6 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
i18n, i18n,
canProcessNow: boolean('canProcessNow', overrideProps.canProcessNow || false), canProcessNow: boolean('canProcessNow', overrideProps.canProcessNow || false),
contact: overrideProps.contact || ({} as ContactType), contact: overrideProps.contact || ({} as ContactType),
downloadNewVersion: action('downloadNewVersion'),
}); });
export function FromSomeone(): JSX.Element { export function FromSomeone(): JSX.Element {

View file

@ -8,6 +8,7 @@ import { Button, ButtonSize, ButtonVariant } from '../Button';
import { ContactName } from './ContactName'; import { ContactName } from './ContactName';
import { Intl } from '../Intl'; import { Intl } from '../Intl';
import type { LocalizerType } from '../../types/Util'; import type { LocalizerType } from '../../types/Util';
import { openLinkInWebBrowser } from '../../util/openLinkInWebBrowser';
export type ContactType = { export type ContactType = {
id: string; id: string;
@ -23,21 +24,16 @@ export type PropsData = {
contact: ContactType; contact: ContactType;
}; };
export type PropsActions = {
downloadNewVersion: () => unknown;
};
type PropsHousekeeping = { type PropsHousekeeping = {
i18n: LocalizerType; i18n: LocalizerType;
}; };
export type Props = PropsData & PropsHousekeeping & PropsActions; export type Props = PropsData & PropsHousekeeping;
export function UnsupportedMessage({ export function UnsupportedMessage({
canProcessNow, canProcessNow,
contact, contact,
i18n, i18n,
downloadNewVersion,
}: Props): JSX.Element { }: Props): JSX.Element {
const { isMe } = contact; const { isMe } = contact;
@ -75,7 +71,7 @@ export function UnsupportedMessage({
<div className="SystemMessage__line"> <div className="SystemMessage__line">
<Button <Button
onClick={() => { onClick={() => {
downloadNewVersion(); openLinkInWebBrowser('https://signal.org/download');
}} }}
size={ButtonSize.Small} size={ButtonSize.Small}
variant={ButtonVariant.SystemMessage} variant={ButtonVariant.SystemMessage}

View file

@ -67,6 +67,7 @@ import { writeDraftAttachment } from '../../util/writeDraftAttachment';
import { getMessageById } from '../../messages/getMessageById'; import { getMessageById } from '../../messages/getMessageById';
import { canReply } from '../selectors/message'; import { canReply } from '../selectors/message';
import { getConversationSelector } from '../selectors/conversations'; import { getConversationSelector } from '../selectors/conversations';
import { enqueueReactionForSend } from '../../reactions/enqueueReactionForSend';
import { useBoundActions } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions';
// State // State
@ -143,6 +144,7 @@ export const actions = {
addPendingAttachment, addPendingAttachment,
onEditorStateChange, onEditorStateChange,
processAttachments, processAttachments,
reactToMessage,
removeAttachment, removeAttachment,
replaceAttachments, replaceAttachments,
resetComposer, resetComposer,
@ -810,6 +812,44 @@ function replaceAttachments(
}; };
} }
function reactToMessage(
messageId: string,
reaction: { emoji: string; remove: boolean }
): ThunkAction<
void,
RootStateType,
unknown,
NoopActionType | ShowToastActionType
> {
return async dispatch => {
const { emoji, remove } = reaction;
try {
await enqueueReactionForSend({
messageId,
emoji,
remove,
});
dispatch({
type: 'NOOP',
payload: null,
});
} catch (error) {
log.error(
'reactToMessage: Error sending reaction',
error,
messageId,
reaction
);
dispatch({
type: SHOW_TOAST,
payload: {
toastType: ToastType.ReactionFailed,
},
});
}
};
}
function resetComposer(): ResetComposerActionType { function resetComposer(): ResetComposerActionType {
return { return {
type: RESET_COMPOSER, type: RESET_COMPOSER,

View file

@ -90,7 +90,10 @@ import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
import { useBoundActions } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions';
import type { NoopActionType } from './noop'; import type { NoopActionType } from './noop';
import { conversationJobQueue } from '../../jobs/conversationJobQueue'; import {
conversationJobQueue,
conversationQueueJobEnum,
} from '../../jobs/conversationJobQueue';
import type { TimelineMessageLoadingState } from '../../util/timelineUtil'; import type { TimelineMessageLoadingState } from '../../util/timelineUtil';
import { import {
isDirectConversation, isDirectConversation,
@ -121,6 +124,9 @@ import {
} from '../../groups'; } from '../../groups';
import { getMessageById } from '../../messages/getMessageById'; import { getMessageById } from '../../messages/getMessageById';
import type { PanelRenderType } from '../../types/Panels'; import type { PanelRenderType } from '../../types/Panels';
import type { ConversationQueueJobData } from '../../jobs/conversationJobQueue';
import { isOlderThan } from '../../util/timestamp';
import { DAY } from '../../util/durations';
import { isNotNil } from '../../util/isNotNil'; import { isNotNil } from '../../util/isNotNil';
// State // State
@ -893,14 +899,17 @@ export const actions = {
generateNewGroupLink, generateNewGroupLink,
getProfilesForConversation, getProfilesForConversation,
initiateMigrationToGroupV2, initiateMigrationToGroupV2,
kickOffAttachmentDownload,
leaveGroup, leaveGroup,
loadRecentMediaItems, loadRecentMediaItems,
markAttachmentAsCorrupted,
messageChanged, messageChanged,
messageDeleted, messageDeleted,
messageExpanded, messageExpanded,
messagesAdded, messagesAdded,
messagesReset, messagesReset,
myProfileChanged, myProfileChanged,
openGiftBadge,
popPanelForConversation, popPanelForConversation,
pushPanelForConversation, pushPanelForConversation,
removeAllConversations, removeAllConversations,
@ -910,6 +919,8 @@ export const actions = {
repairOldestMessage, repairOldestMessage,
replaceAvatar, replaceAvatar,
resetAllChatColors, resetAllChatColors,
retryDeleteForEveryone,
retryMessageSend,
reviewGroupMemberNameCollision, reviewGroupMemberNameCollision,
reviewMessageRequestNameCollision, reviewMessageRequestNameCollision,
revokePendingMembershipsFromGroupV2, revokePendingMembershipsFromGroupV2,
@ -938,6 +949,8 @@ export const actions = {
showArchivedConversations, showArchivedConversations,
showChooseGroupMembers, showChooseGroupMembers,
showConversation, showConversation,
showExpiredIncomingTapToViewToast,
showExpiredOutgoingTapToViewToast,
showInbox, showInbox,
startComposing, startComposing,
startSettingGroupMetadata, startSettingGroupMetadata,
@ -1561,6 +1574,136 @@ function resetAllChatColors(): ThunkAction<
}; };
} }
function kickOffAttachmentDownload(
options: Readonly<{ messageId: string }>
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
return async dispatch => {
const message = await getMessageById(options.messageId);
if (!message) {
throw new Error(
`kickOffAttachmentDownload: Message ${options.messageId} missing!`
);
}
await message.queueAttachmentDownloads();
dispatch({
type: 'NOOP',
payload: null,
});
};
}
type AttachmentOptions = {
messageId: string;
attachment: AttachmentType;
};
function markAttachmentAsCorrupted(
options: AttachmentOptions
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
return async dispatch => {
const message = await getMessageById(options.messageId);
if (!message) {
throw new Error(
`markAttachmentAsCorrupted: Message ${options.messageId} missing!`
);
}
message.markAttachmentAsCorrupted(options.attachment);
dispatch({
type: 'NOOP',
payload: null,
});
};
}
function openGiftBadge(
messageId: string
): ThunkAction<void, RootStateType, unknown, ShowToastActionType> {
return async dispatch => {
const message = await getMessageById(messageId);
if (!message) {
throw new Error(`openGiftBadge: Message ${messageId} missing!`);
}
dispatch({
type: SHOW_TOAST,
payload: {
toastType: isIncoming(message.attributes)
? ToastType.CannotOpenGiftBadgeIncoming
: ToastType.CannotOpenGiftBadgeOutgoing,
},
});
};
}
function retryMessageSend(
messageId: string
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
return async dispatch => {
const message = await getMessageById(messageId);
if (!message) {
throw new Error(`retryMessageSend: Message ${messageId} missing!`);
}
await message.retrySend();
dispatch({
type: 'NOOP',
payload: null,
});
};
}
export function retryDeleteForEveryone(
messageId: string
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
return async dispatch => {
const message = await getMessageById(messageId);
if (!message) {
throw new Error(`retryDeleteForEveryone: Message ${messageId} missing!`);
}
if (isOlderThan(message.get('sent_at'), DAY)) {
throw new Error(
'retryDeleteForEveryone: Message too old to retry delete for everyone!'
);
}
try {
const conversation = message.getConversation();
if (!conversation) {
throw new Error(
`retryDeleteForEveryone: Conversation for ${messageId} missing!`
);
}
const jobData: ConversationQueueJobData = {
type: conversationQueueJobEnum.enum.DeleteForEveryone,
conversationId: conversation.id,
messageId,
recipients: conversation.getRecipients(),
revision: conversation.get('revision'),
targetTimestamp: message.get('sent_at'),
};
log.info(
`retryDeleteForEveryone: Adding job for message ${message.idForLogging()}!`
);
await conversationJobQueue.add(jobData);
dispatch({
type: 'NOOP',
payload: null,
});
} catch (error) {
log.error(
'retryDeleteForEveryone: Failed to queue delete for everyone',
Errors.toLogFormat(error)
);
}
};
}
// update the conversation voice note playback rate preference for the conversation // update the conversation voice note playback rate preference for the conversation
export function setVoiceNotePlaybackRate({ export function setVoiceNotePlaybackRate({
conversationId, conversationId,
@ -2990,6 +3133,27 @@ function updateConversationModelSharedGroups(
}; };
} }
function showExpiredIncomingTapToViewToast(): ShowToastActionType {
log.info(
'showExpiredIncomingTapToViewToastShowing expired tap-to-view toast for an incoming message'
);
return {
type: SHOW_TOAST,
payload: {
toastType: ToastType.TapToViewExpiredIncoming,
},
};
}
function showExpiredOutgoingTapToViewToast(): ShowToastActionType {
log.info('Showing expired tap-to-view toast for an outgoing message');
return {
type: SHOW_TOAST,
payload: {
toastType: ToastType.TapToViewExpiredOutgoing,
},
};
}
function showInbox(): ShowInboxActionType { function showInbox(): ShowInboxActionType {
return { return {
type: 'SHOW_INBOX', type: 'SHOW_INBOX',

View file

@ -24,7 +24,6 @@ import dataInterface from '../../sql/Client';
import { ReadStatus } from '../../messages/MessageReadStatus'; import { ReadStatus } from '../../messages/MessageReadStatus';
import { SafetyNumberChangeSource } from '../../components/SafetyNumberChangeDialog'; import { SafetyNumberChangeSource } from '../../components/SafetyNumberChangeDialog';
import { StoryViewDirectionType, StoryViewModeType } from '../../types/Stories'; import { StoryViewDirectionType, StoryViewModeType } from '../../types/Stories';
import { ToastReactionFailed } from '../../components/ToastReactionFailed';
import { assertDev } from '../../util/assert'; import { assertDev } from '../../util/assert';
import { blockSendUntilConversationsAreVerified } from '../../util/blockSendUntilConversationsAreVerified'; import { blockSendUntilConversationsAreVerified } from '../../util/blockSendUntilConversationsAreVerified';
import { deleteStoryForEveryone as doDeleteStoryForEveryone } from '../../util/deleteStoryForEveryone'; import { deleteStoryForEveryone as doDeleteStoryForEveryone } from '../../util/deleteStoryForEveryone';
@ -35,7 +34,6 @@ import { markOnboardingStoryAsRead } from '../../util/markOnboardingStoryAsRead'
import { markViewed } from '../../services/MessageUpdater'; import { markViewed } from '../../services/MessageUpdater';
import { queueAttachmentDownloads } from '../../util/queueAttachmentDownloads'; import { queueAttachmentDownloads } from '../../util/queueAttachmentDownloads';
import { replaceIndex } from '../../util/replaceIndex'; import { replaceIndex } from '../../util/replaceIndex';
import { showToast } from '../../util/showToast';
import type { DurationInSeconds } from '../../util/durations'; import type { DurationInSeconds } from '../../util/durations';
import { hasFailed, isDownloaded, isDownloading } from '../../types/Attachment'; import { hasFailed, isDownloaded, isDownloading } from '../../types/Attachment';
import { import {
@ -57,6 +55,9 @@ import { verifyStoryListMembers as doVerifyStoryListMembers } from '../../util/v
import { viewSyncJobQueue } from '../../jobs/viewSyncJobQueue'; import { viewSyncJobQueue } from '../../jobs/viewSyncJobQueue';
import { viewedReceiptsJobQueue } from '../../jobs/viewedReceiptsJobQueue'; import { viewedReceiptsJobQueue } from '../../jobs/viewedReceiptsJobQueue';
import { getOwn } from '../../util/getOwn'; import { getOwn } from '../../util/getOwn';
import { SHOW_TOAST } from './toast';
import { ToastType } from '../../types/Toast';
import type { ShowToastActionType } from './toast';
export type StoryDataType = { export type StoryDataType = {
attachment?: AttachmentType; attachment?: AttachmentType;
@ -492,7 +493,12 @@ function queueStoryDownload(
function reactToStory( function reactToStory(
nextReaction: string, nextReaction: string,
messageId: string messageId: string
): ThunkAction<void, RootStateType, unknown, NoopActionType> { ): ThunkAction<
void,
RootStateType,
unknown,
ShowToastActionType | NoopActionType
> {
return async dispatch => { return async dispatch => {
try { try {
await enqueueReactionForSend({ await enqueueReactionForSend({
@ -500,15 +506,19 @@ function reactToStory(
emoji: nextReaction, emoji: nextReaction,
remove: false, remove: false,
}); });
} catch (error) {
log.error('Error enqueuing reaction', error, messageId, nextReaction);
showToast(ToastReactionFailed);
}
dispatch({ dispatch({
type: 'NOOP', type: 'NOOP',
payload: null, payload: null,
}); });
} catch (error) {
log.error('Error enqueuing reaction', error, messageId, nextReaction);
dispatch({
type: SHOW_TOAST,
payload: {
toastType: ToastType.ReactionFailed,
},
});
}
}; };
} }

View file

@ -8,6 +8,7 @@ import type { NoopActionType } from './noop';
import type { ReplacementValuesType } from '../../types/Util'; import type { ReplacementValuesType } from '../../types/Util';
import { useBoundActions } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions';
import type { ToastType } from '../../types/Toast'; import type { ToastType } from '../../types/Toast';
// State // State
export type ToastStateType = { export type ToastStateType = {

View file

@ -12,7 +12,7 @@ import { SmartMessageDetail } from '../smart/MessageDetail';
export const createMessageDetail = ( export const createMessageDetail = (
store: Store, store: Store,
props: Omit<OwnProps, 'markViewed'> props: OwnProps
): ReactElement => ( ): ReactElement => (
<Provider store={store}> <Provider store={store}>
<SmartMessageDetail {...props} /> <SmartMessageDetail {...props} />

View file

@ -12,7 +12,6 @@ import { getPreferredBadgeSelector } from '../selectors/badges';
import { getIntl, getInteractionMode, getTheme } from '../selectors/user'; import { getIntl, getInteractionMode, getTheme } from '../selectors/user';
import { renderAudioAttachment } from './renderAudioAttachment'; import { renderAudioAttachment } from './renderAudioAttachment';
import { getContactNameColorSelector } from '../selectors/conversations'; import { getContactNameColorSelector } from '../selectors/conversations';
import { markViewed } from '../ducks/conversations';
export { Contact } from '../../components/conversation/MessageDetail'; export { Contact } from '../../components/conversation/MessageDetail';
export type OwnProps = Omit< export type OwnProps = Omit<
@ -26,7 +25,6 @@ export type OwnProps = Omit<
| 'theme' | 'theme'
| 'showContactModal' | 'showContactModal'
| 'showConversation' | 'showConversation'
| 'markViewed'
>; >;
const mapStateToProps = ( const mapStateToProps = (
@ -40,12 +38,6 @@ const mapStateToProps = (
receivedAt, receivedAt,
sentAt, sentAt,
kickOffAttachmentDownload,
markAttachmentAsCorrupted,
openGiftBadge,
openLink,
showExpiredIncomingTapToViewToast,
showExpiredOutgoingTapToViewToast,
startConversation, startConversation,
} = props; } = props;
@ -72,14 +64,7 @@ const mapStateToProps = (
interactionMode: getInteractionMode(state), interactionMode: getInteractionMode(state),
theme: getTheme(state), theme: getTheme(state),
kickOffAttachmentDownload,
markAttachmentAsCorrupted,
markViewed,
openGiftBadge,
openLink,
renderAudioAttachment, renderAudioAttachment,
showExpiredIncomingTapToViewToast,
showExpiredOutgoingTapToViewToast,
startConversation, startConversation,
}; };
}; };

View file

@ -21,7 +21,6 @@ import {
getStories, getStories,
shouldShowStoriesView, shouldShowStoriesView,
} from '../selectors/stories'; } from '../selectors/stories';
import { retryMessageSend } from '../../util/retryMessageSend';
import { useConversationsActions } from '../ducks/conversations'; import { useConversationsActions } from '../ducks/conversations';
import { useGlobalModalActions } from '../ducks/globalModals'; import { useGlobalModalActions } from '../ducks/globalModals';
import { useStoriesActions } from '../ducks/stories'; import { useStoriesActions } from '../ducks/stories';
@ -33,8 +32,12 @@ function renderStoryCreator(): JSX.Element {
export function SmartStories(): JSX.Element | null { export function SmartStories(): JSX.Element | null {
const storiesActions = useStoriesActions(); const storiesActions = useStoriesActions();
const { saveAttachment, showConversation, toggleHideStories } = const {
useConversationsActions(); retryMessageSend,
saveAttachment,
showConversation,
toggleHideStories,
} = useConversationsActions();
const { showStoriesSettings, toggleForwardMessageModal } = const { showStoriesSettings, toggleForwardMessageModal } =
useGlobalModalActions(); useGlobalModalActions();
const { showToast } = useToastActions(); const { showToast } = useToastActions();
@ -83,7 +86,7 @@ export function SmartStories(): JSX.Element | null {
}} }}
preferredWidthFromStorage={preferredWidthFromStorage} preferredWidthFromStorage={preferredWidthFromStorage}
renderStoryCreator={renderStoryCreator} renderStoryCreator={renderStoryCreator}
retrySend={retryMessageSend} retryMessageSend={retryMessageSend}
showConversation={showConversation} showConversation={showConversation}
showStoriesSettings={showStoriesSettings} showStoriesSettings={showStoriesSettings}
showToast={showToast} showToast={showToast}

View file

@ -29,7 +29,6 @@ import {
import { isInFullScreenCall } from '../selectors/calling'; import { isInFullScreenCall } from '../selectors/calling';
import { isSignalConversation } from '../../util/isSignalConversation'; import { isSignalConversation } from '../../util/isSignalConversation';
import { renderEmojiPicker } from './renderEmojiPicker'; import { renderEmojiPicker } from './renderEmojiPicker';
import { retryMessageSend } from '../../util/retryMessageSend';
import { strictAssert } from '../../util/assert'; import { strictAssert } from '../../util/assert';
import { asyncShouldNeverBeCalled } from '../../util/shouldNeverBeCalled'; import { asyncShouldNeverBeCalled } from '../../util/shouldNeverBeCalled';
import { useActions as useEmojisActions } from '../ducks/emojis'; import { useActions as useEmojisActions } from '../ducks/emojis';
@ -42,8 +41,12 @@ import { useIsWindowActive } from '../../hooks/useIsWindowActive';
export function SmartStoryViewer(): JSX.Element | null { export function SmartStoryViewer(): JSX.Element | null {
const storiesActions = useStoriesActions(); const storiesActions = useStoriesActions();
const { onUseEmoji } = useEmojisActions(); const { onUseEmoji } = useEmojisActions();
const { saveAttachment, showConversation, toggleHideStories } = const {
useConversationsActions(); retryMessageSend,
saveAttachment,
showConversation,
toggleHideStories,
} = useConversationsActions();
const { onSetSkinTone } = useItemsActions(); const { onSetSkinTone } = useItemsActions();
const { showToast } = useToastActions(); const { showToast } = useToastActions();
@ -135,7 +138,7 @@ export function SmartStoryViewer(): JSX.Element | null {
recentEmojis={recentEmojis} recentEmojis={recentEmojis}
renderEmojiPicker={renderEmojiPicker} renderEmojiPicker={renderEmojiPicker}
replyState={replyState} replyState={replyState}
retrySend={retryMessageSend} retryMessageSend={retryMessageSend}
showToast={showToast} showToast={showToast}
skinTone={skinTone} skinTone={skinTone}
story={storyView} story={storyView}

View file

@ -8,10 +8,10 @@ import { connect } from 'react-redux';
import { mapDispatchToProps } from '../actions'; import { mapDispatchToProps } from '../actions';
import type { import type {
PropsActionsType as TimelineActionsType,
ContactSpoofingReviewPropType, ContactSpoofingReviewPropType,
WarningType as TimelineWarningType, WarningType as TimelineWarningType,
PropsType as ComponentPropsType, PropsType as ComponentPropsType,
PropsActionsFromBackboneForChildrenType,
} from '../../components/conversation/Timeline'; } from '../../components/conversation/Timeline';
import { Timeline } from '../../components/conversation/Timeline'; import { Timeline } from '../../components/conversation/Timeline';
import type { StateType } from '../reducer'; import type { StateType } from '../reducer';
@ -62,30 +62,31 @@ type ExternalProps = {
export type TimelinePropsType = ExternalProps & export type TimelinePropsType = ExternalProps &
Pick< Pick<
ComponentPropsType, ComponentPropsType,
// All of these are the ones we need from backbone
// Used by Timeline itself
| 'acknowledgeGroupMemberNameCollisions' | 'acknowledgeGroupMemberNameCollisions'
| 'contactSupport' | 'loadOlderMessages'
| 'blockGroupLinkRequests'
| 'downloadNewVersion'
| 'kickOffAttachmentDownload'
| 'learnMoreAboutDeliveryIssue'
| 'loadNewerMessages' | 'loadNewerMessages'
| 'loadNewestMessages' | 'loadNewestMessages'
| 'loadOlderMessages'
| 'markAttachmentAsCorrupted'
| 'markMessageRead' | 'markMessageRead'
| 'openGiftBadge'
| 'openLink'
| 'reactToMessage'
| 'removeMember' | 'removeMember'
| 'retryDeleteForEveryone'
| 'retrySend'
| 'scrollToQuotedMessage'
| 'showExpiredIncomingTapToViewToast'
| 'showExpiredOutgoingTapToViewToast'
| 'showMessageDetail'
| 'startConversation'
| 'unblurAvatar' | 'unblurAvatar'
| 'updateSharedGroups' | 'updateSharedGroups'
// MessageActionsType
| 'scrollToQuotedMessage'
| 'showMessageDetail'
| 'startConversation'
// ChatSessionRefreshedNotificationActionsType
| 'contactSupport'
// DeliveryIssueNotificationActionsType
| 'learnMoreAboutDeliveryIssue'
// GroupV2ChangeActionsType
| 'blockGroupLinkRequests'
>; >;
function renderItem({ function renderItem({
@ -99,7 +100,7 @@ function renderItem({
previousMessageId, previousMessageId,
unreadIndicatorPlacement, unreadIndicatorPlacement,
}: { }: {
actionProps: TimelineActionsType; actionProps: PropsActionsFromBackboneForChildrenType;
containerElementRef: RefObject<HTMLElement>; containerElementRef: RefObject<HTMLElement>;
containerWidthBreakpoint: WidthBreakpoint; containerWidthBreakpoint: WidthBreakpoint;
conversationId: string; conversationId: string;
@ -269,7 +270,7 @@ const getContactSpoofingReview = (
} }
}; };
const mapStateToProps = (state: StateType, props: ExternalProps) => { const mapStateToProps = (state: StateType, props: TimelinePropsType) => {
const { id, ...actions } = props; const { id, ...actions } = props;
const conversation = getConversationSelector(state)(id); const conversation = getConversationSelector(state)(id);

View file

@ -6,6 +6,8 @@ export enum ToastType {
Blocked = 'Blocked', Blocked = 'Blocked',
BlockedGroup = 'BlockedGroup', BlockedGroup = 'BlockedGroup',
CannotMixMultiAndNonMultiAttachments = 'CannotMixMultiAndNonMultiAttachments', CannotMixMultiAndNonMultiAttachments = 'CannotMixMultiAndNonMultiAttachments',
CannotOpenGiftBadgeIncoming = 'CannotOpenGiftBadgeIncoming',
CannotOpenGiftBadgeOutgoing = 'CannotOpenGiftBadgeOutgoing',
CannotStartGroupCall = 'CannotStartGroupCall', CannotStartGroupCall = 'CannotStartGroupCall',
CopiedUsername = 'CopiedUsername', CopiedUsername = 'CopiedUsername',
CopiedUsernameLink = 'CopiedUsernameLink', CopiedUsernameLink = 'CopiedUsernameLink',
@ -21,6 +23,7 @@ export enum ToastType {
MaxAttachments = 'MaxAttachments', MaxAttachments = 'MaxAttachments',
MessageBodyTooLong = 'MessageBodyTooLong', MessageBodyTooLong = 'MessageBodyTooLong',
PinnedConversationsFull = 'PinnedConversationsFull', PinnedConversationsFull = 'PinnedConversationsFull',
ReactionFailed = 'ReactionFailed',
ReportedSpamAndBlocked = 'ReportedSpamAndBlocked', ReportedSpamAndBlocked = 'ReportedSpamAndBlocked',
StoryMuted = 'StoryMuted', StoryMuted = 'StoryMuted',
StoryReact = 'StoryReact', StoryReact = 'StoryReact',
@ -28,6 +31,8 @@ export enum ToastType {
StoryVideoError = 'StoryVideoError', StoryVideoError = 'StoryVideoError',
StoryVideoTooLong = 'StoryVideoTooLong', StoryVideoTooLong = 'StoryVideoTooLong',
StoryVideoUnsupported = 'StoryVideoUnsupported', StoryVideoUnsupported = 'StoryVideoUnsupported',
TapToViewExpiredIncoming = 'TapToViewExpiredIncoming',
TapToViewExpiredOutgoing = 'TapToViewExpiredOutgoing',
UnableToLoadAttachment = 'UnableToLoadAttachment', UnableToLoadAttachment = 'UnableToLoadAttachment',
UnsupportedMultiAttachment = 'UnsupportedMultiAttachment', UnsupportedMultiAttachment = 'UnsupportedMultiAttachment',
UserAddedToGroup = 'UserAddedToGroup', UserAddedToGroup = 'UserAddedToGroup',

View file

@ -1,55 +0,0 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as log from '../logging/log';
import * as Errors from '../types/errors';
import type { ConversationQueueJobData } from '../jobs/conversationJobQueue';
import {
conversationJobQueue,
conversationQueueJobEnum,
} from '../jobs/conversationJobQueue';
import { isOlderThan } from './timestamp';
import { DAY } from './durations';
export async function retryDeleteForEveryone(messageId: string): Promise<void> {
const message = window.MessageController.getById(messageId);
if (!message) {
throw new Error(`retryDeleteForEveryone: Message ${messageId} missing!`);
}
if (isOlderThan(message.get('sent_at'), DAY)) {
throw new Error(
'retryDeleteForEveryone: Message too old to retry delete for everyone!'
);
}
try {
const conversation = message.getConversation();
if (!conversation) {
throw new Error(
`retryDeleteForEveryone: Conversation for ${messageId} missing!`
);
}
const jobData: ConversationQueueJobData = {
type: conversationQueueJobEnum.enum.DeleteForEveryone,
conversationId: conversation.id,
messageId,
recipients: conversation.getRecipients(),
revision: conversation.get('revision'),
targetTimestamp: message.get('sent_at'),
};
log.info(
`retryDeleteForEveryone: Adding job for message ${message.idForLogging()}!`
);
await conversationJobQueue.add(jobData);
} catch (error) {
log.error(
'retryDeleteForEveryone: Failed to queue delete for everyone',
Errors.toLogFormat(error)
);
throw error;
}
}

View file

@ -1,12 +0,0 @@
// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { getMessageById } from '../messages/getMessageById';
export async function retryMessageSend(messageId: string): Promise<void> {
const message = await getMessageById(messageId);
if (!message) {
throw new Error(`retryMessageSend: Message ${messageId} missing!`);
}
await message.retrySend();
}

View file

@ -6,10 +6,6 @@ import { render, unmountComponentAtNode } from 'react-dom';
import type { ToastAlreadyGroupMember } from '../components/ToastAlreadyGroupMember'; import type { ToastAlreadyGroupMember } from '../components/ToastAlreadyGroupMember';
import type { ToastAlreadyRequestedToJoin } from '../components/ToastAlreadyRequestedToJoin'; import type { ToastAlreadyRequestedToJoin } from '../components/ToastAlreadyRequestedToJoin';
import type {
ToastCannotOpenGiftBadge,
ToastPropsType as ToastCannotOpenGiftBadgePropsType,
} from '../components/ToastCannotOpenGiftBadge';
import type { ToastCaptchaFailed } from '../components/ToastCaptchaFailed'; import type { ToastCaptchaFailed } from '../components/ToastCaptchaFailed';
import type { ToastCaptchaSolved } from '../components/ToastCaptchaSolved'; import type { ToastCaptchaSolved } from '../components/ToastCaptchaSolved';
import type { import type {
@ -32,19 +28,12 @@ import type { ToastLoadingFullLogs } from '../components/ToastLoadingFullLogs';
import type { ToastMessageBodyTooLong } from '../components/ToastMessageBodyTooLong'; import type { ToastMessageBodyTooLong } from '../components/ToastMessageBodyTooLong';
import type { ToastOriginalMessageNotFound } from '../components/ToastOriginalMessageNotFound'; import type { ToastOriginalMessageNotFound } from '../components/ToastOriginalMessageNotFound';
import type { ToastReactionFailed } from '../components/ToastReactionFailed';
import type { ToastStickerPackInstallFailed } from '../components/ToastStickerPackInstallFailed'; import type { ToastStickerPackInstallFailed } from '../components/ToastStickerPackInstallFailed';
import type { ToastTapToViewExpiredIncoming } from '../components/ToastTapToViewExpiredIncoming';
import type { ToastTapToViewExpiredOutgoing } from '../components/ToastTapToViewExpiredOutgoing';
import type { ToastVoiceNoteLimit } from '../components/ToastVoiceNoteLimit'; import type { ToastVoiceNoteLimit } from '../components/ToastVoiceNoteLimit';
import type { ToastVoiceNoteMustBeOnlyAttachment } from '../components/ToastVoiceNoteMustBeOnlyAttachment'; import type { ToastVoiceNoteMustBeOnlyAttachment } from '../components/ToastVoiceNoteMustBeOnlyAttachment';
export function showToast(Toast: typeof ToastAlreadyGroupMember): void; export function showToast(Toast: typeof ToastAlreadyGroupMember): void;
export function showToast(Toast: typeof ToastAlreadyRequestedToJoin): void; export function showToast(Toast: typeof ToastAlreadyRequestedToJoin): void;
export function showToast(
Toast: typeof ToastCannotOpenGiftBadge,
props: Omit<ToastCannotOpenGiftBadgePropsType, 'i18n' | 'onClose'>
): void;
export function showToast(Toast: typeof ToastCaptchaFailed): void; export function showToast(Toast: typeof ToastCaptchaFailed): void;
export function showToast(Toast: typeof ToastCaptchaSolved): void; export function showToast(Toast: typeof ToastCaptchaSolved): void;
export function showToast( export function showToast(
@ -66,10 +55,7 @@ export function showToast(Toast: typeof ToastLinkCopied): void;
export function showToast(Toast: typeof ToastLoadingFullLogs): void; export function showToast(Toast: typeof ToastLoadingFullLogs): void;
export function showToast(Toast: typeof ToastMessageBodyTooLong): void; export function showToast(Toast: typeof ToastMessageBodyTooLong): void;
export function showToast(Toast: typeof ToastOriginalMessageNotFound): void; export function showToast(Toast: typeof ToastOriginalMessageNotFound): void;
export function showToast(Toast: typeof ToastReactionFailed): void;
export function showToast(Toast: typeof ToastStickerPackInstallFailed): void; export function showToast(Toast: typeof ToastStickerPackInstallFailed): void;
export function showToast(Toast: typeof ToastTapToViewExpiredIncoming): void;
export function showToast(Toast: typeof ToastTapToViewExpiredOutgoing): void;
export function showToast(Toast: typeof ToastVoiceNoteLimit): void; export function showToast(Toast: typeof ToastVoiceNoteLimit): void;
export function showToast( export function showToast(
Toast: typeof ToastVoiceNoteMustBeOnlyAttachment Toast: typeof ToastVoiceNoteMustBeOnlyAttachment

View file

@ -16,10 +16,8 @@ import type { MediaItemType } from '../types/MediaItem';
import { getMessageById } from '../messages/getMessageById'; import { getMessageById } from '../messages/getMessageById';
import { getContactId } from '../messages/helpers'; import { getContactId } from '../messages/helpers';
import { strictAssert } from '../util/assert'; import { strictAssert } from '../util/assert';
import { enqueueReactionForSend } from '../reactions/enqueueReactionForSend';
import type { GroupNameCollisionsWithIdsByTitle } from '../util/groupMemberNameCollisions'; import type { GroupNameCollisionsWithIdsByTitle } from '../util/groupMemberNameCollisions';
import { isGroup } from '../util/whatTypeOfConversation'; import { isGroup } from '../util/whatTypeOfConversation';
import { isIncoming } from '../state/selectors/message';
import { getActiveCallState } from '../state/selectors/calling'; import { getActiveCallState } from '../state/selectors/calling';
import { ReactWrapperView } from './ReactWrapperView'; import { ReactWrapperView } from './ReactWrapperView';
import * as log from '../logging/log'; import * as log from '../logging/log';
@ -29,17 +27,11 @@ import { ToastConversationMarkedUnread } from '../components/ToastConversationMa
import { ToastConversationUnarchived } from '../components/ToastConversationUnarchived'; import { ToastConversationUnarchived } from '../components/ToastConversationUnarchived';
import { ToastMessageBodyTooLong } from '../components/ToastMessageBodyTooLong'; import { ToastMessageBodyTooLong } from '../components/ToastMessageBodyTooLong';
import { ToastOriginalMessageNotFound } from '../components/ToastOriginalMessageNotFound'; import { ToastOriginalMessageNotFound } from '../components/ToastOriginalMessageNotFound';
import { ToastReactionFailed } from '../components/ToastReactionFailed';
import { ToastTapToViewExpiredIncoming } from '../components/ToastTapToViewExpiredIncoming';
import { ToastTapToViewExpiredOutgoing } from '../components/ToastTapToViewExpiredOutgoing';
import { ToastCannotOpenGiftBadge } from '../components/ToastCannotOpenGiftBadge';
import { retryMessageSend } from '../util/retryMessageSend';
import { isNotNil } from '../util/isNotNil'; import { isNotNil } from '../util/isNotNil';
import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser'; import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser';
import { showToast } from '../util/showToast'; import { showToast } from '../util/showToast';
import { UUIDKind } from '../types/UUID'; import { UUIDKind } from '../types/UUID';
import type { UUIDStringType } from '../types/UUID'; import type { UUIDStringType } from '../types/UUID';
import { retryDeleteForEveryone } from '../util/retryDeleteForEveryone';
import { MediaGallery } from '../components/conversation/media-gallery/MediaGallery'; import { MediaGallery } from '../components/conversation/media-gallery/MediaGallery';
import type { ItemClickEvent } from '../components/conversation/media-gallery/types/ItemClickEvent'; import type { ItemClickEvent } from '../components/conversation/media-gallery/types/ItemClickEvent';
import { import {
@ -53,11 +45,6 @@ import { clearConversationDraftAttachments } from '../util/clearConversationDraf
import type { BackbonePanelRenderType, PanelRenderType } from '../types/Panels'; import type { BackbonePanelRenderType, PanelRenderType } from '../types/Panels';
import { PanelType, isPanelHandledByReact } from '../types/Panels'; import { PanelType, isPanelHandledByReact } from '../types/Panels';
type AttachmentOptions = {
messageId: string;
attachment: AttachmentType;
};
const { Message } = window.Signal.Types; const { Message } = window.Signal.Types;
type BackbonePanelType = { panelType: PanelType; view: Backbone.View }; type BackbonePanelType = { panelType: PanelType; view: Backbone.View };
@ -68,21 +55,6 @@ const { getAbsoluteAttachmentPath, upgradeMessageSchema } =
const { getMessagesBySentAt } = window.Signal.Data; const { getMessagesBySentAt } = window.Signal.Data;
type MessageActionsType = { type MessageActionsType = {
downloadNewVersion: () => unknown;
kickOffAttachmentDownload: (
options: Readonly<{ messageId: string }>
) => unknown;
markAttachmentAsCorrupted: (options: AttachmentOptions) => unknown;
openGiftBadge: (messageId: string) => unknown;
openLink: (url: string) => unknown;
reactToMessage: (
messageId: string,
reaction: { emoji: string; remove: boolean }
) => unknown;
retrySend: (messageId: string) => unknown;
retryDeleteForEveryone: (messageId: string) => unknown;
showExpiredIncomingTapToViewToast: () => unknown;
showExpiredOutgoingTapToViewToast: () => unknown;
showMessageDetail: (messageId: string) => unknown; showMessageDetail: (messageId: string) => unknown;
startConversation: (e164: string, uuid: UUIDStringType) => unknown; startConversation: (e164: string, uuid: UUIDStringType) => unknown;
}; };
@ -372,82 +344,11 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
} }
getMessageActions(): MessageActionsType { getMessageActions(): MessageActionsType {
const reactToMessage = async (
messageId: string,
reaction: { emoji: string; remove: boolean }
) => {
const { emoji, remove } = reaction;
try {
await enqueueReactionForSend({
messageId,
emoji,
remove,
});
} catch (error) {
log.error('Error sending reaction', error, messageId, reaction);
showToast(ToastReactionFailed);
}
};
const retrySend = retryMessageSend;
const showMessageDetail = (messageId: string) => { const showMessageDetail = (messageId: string) => {
this.showMessageDetail(messageId); this.showMessageDetail(messageId);
}; };
const kickOffAttachmentDownload = async (
options: Readonly<{ messageId: string }>
) => {
const message = window.MessageController.getById(options.messageId);
if (!message) {
throw new Error(
`kickOffAttachmentDownload: Message ${options.messageId} missing!`
);
}
await message.queueAttachmentDownloads();
};
const markAttachmentAsCorrupted = (options: AttachmentOptions) => {
const message = window.MessageController.getById(options.messageId);
if (!message) {
throw new Error(
`markAttachmentAsCorrupted: Message ${options.messageId} missing!`
);
}
message.markAttachmentAsCorrupted(options.attachment);
};
const openGiftBadge = (messageId: string): void => {
const message = window.MessageController.getById(messageId);
if (!message) {
throw new Error(`openGiftBadge: Message ${messageId} missing!`);
}
showToast(ToastCannotOpenGiftBadge, {
isIncoming: isIncoming(message.attributes),
});
};
const openLink = openLinkInWebBrowser;
const downloadNewVersion = () => {
openLinkInWebBrowser('https://signal.org/download');
};
const showExpiredIncomingTapToViewToast = () => {
log.info('Showing expired tap-to-view toast for an incoming message');
showToast(ToastTapToViewExpiredIncoming);
};
const showExpiredOutgoingTapToViewToast = () => {
log.info('Showing expired tap-to-view toast for an outgoing message');
showToast(ToastTapToViewExpiredOutgoing);
};
return { return {
downloadNewVersion,
kickOffAttachmentDownload,
markAttachmentAsCorrupted,
openGiftBadge,
openLink,
reactToMessage,
retrySend,
retryDeleteForEveryone,
showExpiredIncomingTapToViewToast,
showExpiredOutgoingTapToViewToast,
showMessageDetail, showMessageDetail,
startConversation, startConversation,
}; };