ConversationView: Pull various functions out of getMessageActions
This commit is contained in:
parent
5a98fc2f4c
commit
1e282ee5d0
42 changed files with 440 additions and 570 deletions
|
@ -49,7 +49,7 @@ export default {
|
|||
queueStoryDownload: {
|
||||
action: true,
|
||||
},
|
||||
retrySend: {
|
||||
retryMessageSend: {
|
||||
action: true,
|
||||
},
|
||||
viewStory: { action: true },
|
||||
|
|
|
@ -27,7 +27,7 @@ export type PropsType = {
|
|||
onForward: (storyId: string) => unknown;
|
||||
onSave: (story: StoryViewType) => unknown;
|
||||
queueStoryDownload: (storyId: string) => unknown;
|
||||
retrySend: (messageId: string) => unknown;
|
||||
retryMessageSend: (messageId: string) => unknown;
|
||||
viewStory: ViewStoryActionCreatorType;
|
||||
hasViewReceiptSetting: boolean;
|
||||
};
|
||||
|
@ -40,7 +40,7 @@ export function MyStories({
|
|||
onForward,
|
||||
onSave,
|
||||
queueStoryDownload,
|
||||
retrySend,
|
||||
retryMessageSend,
|
||||
viewStory,
|
||||
hasViewReceiptSetting,
|
||||
}: PropsType): JSX.Element {
|
||||
|
@ -95,7 +95,7 @@ export function MyStories({
|
|||
onForward={onForward}
|
||||
onSave={onSave}
|
||||
queueStoryDownload={queueStoryDownload}
|
||||
retrySend={retrySend}
|
||||
retryMessageSend={retryMessageSend}
|
||||
setConfirmDeleteStory={setConfirmDeleteStory}
|
||||
story={story}
|
||||
viewStory={viewStory}
|
||||
|
@ -120,7 +120,7 @@ type StorySentPropsType = Pick<
|
|||
| 'onForward'
|
||||
| 'onSave'
|
||||
| 'queueStoryDownload'
|
||||
| 'retrySend'
|
||||
| 'retryMessageSend'
|
||||
| 'viewStory'
|
||||
> & {
|
||||
setConfirmDeleteStory: (_: StoryViewType | undefined) => unknown;
|
||||
|
@ -133,7 +133,7 @@ function StorySent({
|
|||
onForward,
|
||||
onSave,
|
||||
queueStoryDownload,
|
||||
retrySend,
|
||||
retryMessageSend,
|
||||
setConfirmDeleteStory,
|
||||
story,
|
||||
viewStory,
|
||||
|
@ -155,7 +155,7 @@ function StorySent({
|
|||
sendStatus === ResolvedSendStatus.PartiallySent)
|
||||
) {
|
||||
setWasManuallyRetried(true);
|
||||
retrySend(story.messageId);
|
||||
retryMessageSend(story.messageId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ export default {
|
|||
},
|
||||
queueStoryDownload: { action: true },
|
||||
renderStoryCreator: { action: true },
|
||||
retrySend: { action: true },
|
||||
retryMessageSend: { action: true },
|
||||
showConversation: { action: true },
|
||||
showStoriesSettings: { action: true },
|
||||
showToast: { action: true },
|
||||
|
|
|
@ -42,7 +42,7 @@ export type PropsType = {
|
|||
preferredWidthFromStorage: number;
|
||||
queueStoryDownload: (storyId: string) => unknown;
|
||||
renderStoryCreator: () => JSX.Element;
|
||||
retrySend: (messageId: string) => unknown;
|
||||
retryMessageSend: (messageId: string) => unknown;
|
||||
setAddStoryData: (data: AddStoryData) => unknown;
|
||||
showConversation: ShowConversationType;
|
||||
showStoriesSettings: () => unknown;
|
||||
|
@ -70,7 +70,7 @@ export function Stories({
|
|||
preferredWidthFromStorage,
|
||||
queueStoryDownload,
|
||||
renderStoryCreator,
|
||||
retrySend,
|
||||
retryMessageSend,
|
||||
setAddStoryData,
|
||||
showConversation,
|
||||
showStoriesSettings,
|
||||
|
@ -111,7 +111,7 @@ export function Stories({
|
|||
onForward={onForwardStory}
|
||||
onSave={onSaveStory}
|
||||
queueStoryDownload={queueStoryDownload}
|
||||
retrySend={retrySend}
|
||||
retryMessageSend={retryMessageSend}
|
||||
viewStory={viewStory}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
@ -56,7 +56,7 @@ export default {
|
|||
},
|
||||
queueStoryDownload: { action: true },
|
||||
renderEmojiPicker: { action: true },
|
||||
retrySend: { action: true },
|
||||
retryMessageSend: { action: true },
|
||||
showToast: { action: true },
|
||||
skinTone: {
|
||||
defaultValue: 0,
|
||||
|
|
|
@ -101,7 +101,7 @@ export type PropsType = {
|
|||
recentEmojis?: Array<string>;
|
||||
renderEmojiPicker: (props: RenderEmojiPickerProps) => JSX.Element;
|
||||
replyState?: ReplyStateType;
|
||||
retrySend: (messageId: string) => unknown;
|
||||
retryMessageSend: (messageId: string) => unknown;
|
||||
saveAttachment: SaveAttachmentActionCreatorType;
|
||||
setHasAllStoriesUnmuted: (isUnmuted: boolean) => unknown;
|
||||
showToast: ShowToastActionCreatorType;
|
||||
|
@ -153,7 +153,7 @@ export function StoryViewer({
|
|||
recentEmojis,
|
||||
renderEmojiPicker,
|
||||
replyState,
|
||||
retrySend,
|
||||
retryMessageSend,
|
||||
saveAttachment,
|
||||
setHasAllStoriesUnmuted,
|
||||
showToast,
|
||||
|
@ -555,7 +555,7 @@ export function StoryViewer({
|
|||
];
|
||||
}
|
||||
|
||||
function doRetrySend() {
|
||||
function doRetryMessageSend() {
|
||||
if (wasManuallyRetried) {
|
||||
return;
|
||||
}
|
||||
|
@ -568,7 +568,7 @@ export function StoryViewer({
|
|||
}
|
||||
|
||||
setWasManuallyRetried(true);
|
||||
retrySend(messageId);
|
||||
retryMessageSend(messageId);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -800,7 +800,7 @@ export function StoryViewer({
|
|||
{sendStatus === ResolvedSendStatus.Failed && !wasManuallyRetried && (
|
||||
<button
|
||||
className="StoryViewer__actions__failed"
|
||||
onClick={doRetrySend}
|
||||
onClick={doRetryMessageSend}
|
||||
type="button"
|
||||
>
|
||||
{i18n('StoryViewer__failed')}
|
||||
|
@ -810,7 +810,7 @@ export function StoryViewer({
|
|||
!wasManuallyRetried && (
|
||||
<button
|
||||
className="StoryViewer__actions__failed"
|
||||
onClick={doRetrySend}
|
||||
onClick={doRetryMessageSend}
|
||||
type="button"
|
||||
>
|
||||
{i18n('StoryViewer__partial-fail')}
|
||||
|
|
|
@ -54,7 +54,6 @@ const MESSAGE_DEFAULT_PROPS = {
|
|||
isMessageRequestAccepted: true,
|
||||
kickOffAttachmentDownload: shouldNeverBeCalled,
|
||||
markAttachmentAsCorrupted: shouldNeverBeCalled,
|
||||
markViewed: shouldNeverBeCalled,
|
||||
messageExpanded: shouldNeverBeCalled,
|
||||
openGiftBadge: shouldNeverBeCalled,
|
||||
openLink: shouldNeverBeCalled,
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -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({});
|
||||
CannotStartGroupCall.args = {
|
||||
toast: {
|
||||
|
@ -182,6 +196,13 @@ PinnedConversationsFull.args = {
|
|||
},
|
||||
};
|
||||
|
||||
export const ReactionFailed = Template.bind({});
|
||||
ReactionFailed.args = {
|
||||
toast: {
|
||||
toastType: ToastType.ReactionFailed,
|
||||
},
|
||||
};
|
||||
|
||||
export const ReportedSpamAndBlocked = Template.bind({});
|
||||
ReportedSpamAndBlocked.args = {
|
||||
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({});
|
||||
UnableToLoadAttachment.args = {
|
||||
toast: {
|
||||
|
|
|
@ -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) {
|
||||
return (
|
||||
<Toast onClose={hideToast}>
|
||||
|
@ -173,6 +189,10 @@ export function ToastManager({
|
|||
return <Toast onClose={hideToast}>{i18n('pinnedConversationsFull')}</Toast>;
|
||||
}
|
||||
|
||||
if (toastType === ToastType.ReactionFailed) {
|
||||
return <Toast onClose={hideToast}>{i18n('Reactions--error')}</Toast>;
|
||||
}
|
||||
|
||||
if (toastType === ToastType.StoryMuted) {
|
||||
return (
|
||||
<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) {
|
||||
return <Toast onClose={hideToast}>{i18n('unableToLoadAttachment')}</Toast>;
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
};
|
|
@ -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>;
|
||||
}
|
|
@ -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',
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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',
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -91,6 +91,7 @@ import type { AnyPaymentEvent } from '../../types/Payment';
|
|||
import { Emojify } from './Emojify';
|
||||
import { getPaymentEventDescription } from '../../messages/helpers';
|
||||
import { PanelType } from '../../types/Panels';
|
||||
import { openLinkInWebBrowser } from '../../util/openLinkInWebBrowser';
|
||||
|
||||
const GUESS_METADATA_WIDTH_TIMESTAMP_SIZE = 16;
|
||||
const GUESS_METADATA_WIDTH_EXPIRE_TIMER_SIZE = 18;
|
||||
|
@ -317,7 +318,6 @@ export type PropsActions = {
|
|||
attachment: AttachmentType;
|
||||
messageId: string;
|
||||
}) => void;
|
||||
markViewed(messageId: string): void;
|
||||
saveAttachment: SaveAttachmentActionCreatorType;
|
||||
showLightbox: (options: {
|
||||
attachment: AttachmentType;
|
||||
|
@ -325,7 +325,6 @@ export type PropsActions = {
|
|||
}) => void;
|
||||
showLightboxForViewOnceMedia: (messageId: string) => unknown;
|
||||
|
||||
openLink: (url: string) => void;
|
||||
scrollToQuotedMessage: (options: {
|
||||
authorId: string;
|
||||
sentAt: number;
|
||||
|
@ -1072,7 +1071,6 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
i18n,
|
||||
id,
|
||||
kickOffAttachmentDownload,
|
||||
openLink,
|
||||
previews,
|
||||
quote,
|
||||
shouldCollapseAbove,
|
||||
|
@ -1123,7 +1121,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
});
|
||||
return;
|
||||
}
|
||||
openLink(first.url);
|
||||
openLinkInWebBrowser(first.url);
|
||||
}
|
||||
: noop;
|
||||
const contents = (
|
||||
|
@ -1208,14 +1206,14 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
openLink(first.url);
|
||||
openLinkInWebBrowser(first.url);
|
||||
}
|
||||
}}
|
||||
onClick={(event: React.MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
openLink(first.url);
|
||||
openLinkInWebBrowser(first.url);
|
||||
}}
|
||||
>
|
||||
{contents}
|
||||
|
@ -2277,15 +2275,8 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
return;
|
||||
}
|
||||
|
||||
if (attachments && !isDownloaded(attachments[0])) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
kickOffAttachmentDownload({
|
||||
attachment: attachments[0],
|
||||
messageId: id,
|
||||
});
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (isTapToViewExpired) {
|
||||
const action =
|
||||
|
@ -2293,13 +2284,21 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
? showExpiredOutgoingTapToViewToast
|
||||
: showExpiredIncomingTapToViewToast;
|
||||
action();
|
||||
} else {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
showLightboxForViewOnceMedia(id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (attachments && !isDownloaded(attachments[0])) {
|
||||
kickOffAttachmentDownload({
|
||||
attachment: attachments[0],
|
||||
messageId: id,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
showLightboxForViewOnceMedia(id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -77,10 +77,8 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
doubleCheckMissingQuoteReference: action('doubleCheckMissingQuoteReference'),
|
||||
kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
|
||||
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
|
||||
markViewed: action('markViewed'),
|
||||
showConversation: action('showConversation'),
|
||||
openGiftBadge: action('openGiftBadge'),
|
||||
openLink: action('openLink'),
|
||||
renderAudioAttachment: () => <div>*AudioAttachment*</div>,
|
||||
saveAttachment: action('saveAttachment'),
|
||||
pushPanelForConversation: action('pushPanelForConversation'),
|
||||
|
|
|
@ -68,7 +68,6 @@ export type PropsData = {
|
|||
i18n: LocalizerType;
|
||||
theme: ThemeType;
|
||||
getPreferredBadge: PreferredBadgeSelectorType;
|
||||
markViewed: (messageId: string) => void;
|
||||
} & Pick<
|
||||
MessagePropsType,
|
||||
| 'getPreferredBadge'
|
||||
|
@ -79,14 +78,7 @@ export type PropsData = {
|
|||
|
||||
export type PropsBackboneActions = Pick<
|
||||
MessagePropsType,
|
||||
| 'kickOffAttachmentDownload'
|
||||
| 'markAttachmentAsCorrupted'
|
||||
| 'openGiftBadge'
|
||||
| 'openLink'
|
||||
| 'renderAudioAttachment'
|
||||
| 'showExpiredIncomingTapToViewToast'
|
||||
| 'showExpiredOutgoingTapToViewToast'
|
||||
| 'startConversation'
|
||||
'renderAudioAttachment' | 'startConversation'
|
||||
>;
|
||||
|
||||
export type PropsReduxActions = Pick<
|
||||
|
@ -94,10 +86,15 @@ export type PropsReduxActions = Pick<
|
|||
| 'checkForAccount'
|
||||
| 'clearSelectedMessage'
|
||||
| 'doubleCheckMissingQuoteReference'
|
||||
| 'kickOffAttachmentDownload'
|
||||
| 'markAttachmentAsCorrupted'
|
||||
| 'openGiftBadge'
|
||||
| 'pushPanelForConversation'
|
||||
| 'saveAttachment'
|
||||
| 'showContactModal'
|
||||
| 'showConversation'
|
||||
| 'showExpiredIncomingTapToViewToast'
|
||||
| 'showExpiredOutgoingTapToViewToast'
|
||||
| 'showLightbox'
|
||||
| 'showLightboxForViewOnceMedia'
|
||||
| 'viewStory'
|
||||
|
@ -289,9 +286,7 @@ export class MessageDetail extends React.Component<Props> {
|
|||
interactionMode,
|
||||
kickOffAttachmentDownload,
|
||||
markAttachmentAsCorrupted,
|
||||
markViewed,
|
||||
openGiftBadge,
|
||||
openLink,
|
||||
pushPanelForConversation,
|
||||
renderAudioAttachment,
|
||||
saveAttachment,
|
||||
|
@ -334,11 +329,9 @@ export class MessageDetail extends React.Component<Props> {
|
|||
interactionMode={interactionMode}
|
||||
kickOffAttachmentDownload={kickOffAttachmentDownload}
|
||||
markAttachmentAsCorrupted={markAttachmentAsCorrupted}
|
||||
markViewed={markViewed}
|
||||
messageExpanded={noop}
|
||||
showConversation={showConversation}
|
||||
openGiftBadge={openGiftBadge}
|
||||
openLink={openLink}
|
||||
pushPanelForConversation={pushPanelForConversation}
|
||||
renderAudioAttachment={renderAudioAttachment}
|
||||
saveAttachment={saveAttachment}
|
||||
|
|
|
@ -110,11 +110,9 @@ const defaultMessageProps: TimelineMessagesProps = {
|
|||
isMessageRequestAccepted: true,
|
||||
kickOffAttachmentDownload: action('default--kickOffAttachmentDownload'),
|
||||
markAttachmentAsCorrupted: action('default--markAttachmentAsCorrupted'),
|
||||
markViewed: action('default--markViewed'),
|
||||
messageExpanded: action('default--message-expanded'),
|
||||
showConversation: action('default--showConversation'),
|
||||
openGiftBadge: action('openGiftBadge'),
|
||||
openLink: action('default--openLink'),
|
||||
previews: [],
|
||||
reactToMessage: action('default--reactToMessage'),
|
||||
readStatus: ReadStatus.Read,
|
||||
|
@ -122,7 +120,7 @@ const defaultMessageProps: TimelineMessagesProps = {
|
|||
renderReactionPicker: () => <div />,
|
||||
renderAudioAttachment: () => <div>*AudioAttachment*</div>,
|
||||
setQuoteByMessageId: action('default--setQuoteByMessageId'),
|
||||
retrySend: action('default--retrySend'),
|
||||
retryMessageSend: action('default--retryMessageSend'),
|
||||
retryDeleteForEveryone: action('default--retryDeleteForEveryone'),
|
||||
saveAttachment: action('saveAttachment'),
|
||||
scrollToQuotedMessage: action('default--scrollToQuotedMessage'),
|
||||
|
|
|
@ -278,23 +278,22 @@ const actions = () => ({
|
|||
reactToMessage: action('reactToMessage'),
|
||||
setQuoteByMessageId: action('setQuoteByMessageId'),
|
||||
retryDeleteForEveryone: action('retryDeleteForEveryone'),
|
||||
retrySend: action('retrySend'),
|
||||
retryMessageSend: action('retryMessageSend'),
|
||||
deleteMessage: action('deleteMessage'),
|
||||
deleteMessageForEveryone: action('deleteMessageForEveryone'),
|
||||
showMessageDetail: action('showMessageDetail'),
|
||||
saveAttachment: action('saveAttachment'),
|
||||
pushPanelForConversation: action('pushPanelForConversation'),
|
||||
showContactDetail: action('showContactDetail'),
|
||||
showContactModal: action('showContactModal'),
|
||||
showConversation: action('showConversation'),
|
||||
kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
|
||||
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
|
||||
markViewed: action('markViewed'),
|
||||
messageExpanded: action('messageExpanded'),
|
||||
showLightbox: action('showLightbox'),
|
||||
showLightboxForViewOnceMedia: action('showLightboxForViewOnceMedia'),
|
||||
doubleCheckMissingQuoteReference: action('doubleCheckMissingQuoteReference'),
|
||||
|
||||
openLink: action('openLink'),
|
||||
openGiftBadge: action('openGiftBadge'),
|
||||
scrollToQuotedMessage: action('scrollToQuotedMessage'),
|
||||
showExpiredIncomingTapToViewToast: action(
|
||||
|
@ -307,8 +306,6 @@ const actions = () => ({
|
|||
|
||||
toggleSafetyNumberModal: action('toggleSafetyNumberModal'),
|
||||
|
||||
downloadNewVersion: action('downloadNewVersion'),
|
||||
|
||||
startCallingLobby: action('startCallingLobby'),
|
||||
startConversation: action('startConversation'),
|
||||
returnToActiveCall: action('returnToActiveCall'),
|
||||
|
|
|
@ -19,11 +19,8 @@ import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary';
|
|||
import { WidthBreakpoint } from '../_util';
|
||||
|
||||
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 GroupV2ChangeActionsType } from './GroupV2Change';
|
||||
import { ErrorBoundary } from './ErrorBoundary';
|
||||
import type { PropsActions as SafetyNumberActionsType } from './SafetyNumberNotification';
|
||||
import { Intl } from '../Intl';
|
||||
import { TimelineWarning } from './TimelineWarning';
|
||||
import { TimelineWarnings } from './TimelineWarnings';
|
||||
|
@ -47,6 +44,8 @@ import {
|
|||
} from '../../util/scrollUtil';
|
||||
import { LastSeenIndicator } from './LastSeenIndicator';
|
||||
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_DETECTOR_STYLE = { height: AT_BOTTOM_THRESHOLD };
|
||||
|
@ -125,7 +124,7 @@ type PropsHousekeepingType = {
|
|||
theme: ThemeType;
|
||||
|
||||
renderItem: (props: {
|
||||
actionProps: PropsActionsType;
|
||||
actionProps: PropsActionsFromBackboneForChildrenType;
|
||||
containerElementRef: RefObject<HTMLElement>;
|
||||
containerWidthBreakpoint: WidthBreakpoint;
|
||||
conversationId: string;
|
||||
|
@ -146,41 +145,47 @@ type PropsHousekeepingType = {
|
|||
) => JSX.Element;
|
||||
};
|
||||
|
||||
export type PropsActionsFromBackboneForChildrenType = Pick<
|
||||
MessageActionsType,
|
||||
'scrollToQuotedMessage' | 'showMessageDetail' | 'startConversation'
|
||||
> &
|
||||
ChatSessionRefreshedNotificationActionsType &
|
||||
DeliveryIssueNotificationActionsType &
|
||||
GroupV2ChangeActionsType;
|
||||
|
||||
export type PropsActionsType = {
|
||||
// From Backbone
|
||||
acknowledgeGroupMemberNameCollisions: (
|
||||
groupNameCollisions: Readonly<GroupNameCollisionsWithIdsByTitle>
|
||||
) => 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;
|
||||
clearSelectedMessage: () => unknown;
|
||||
closeContactSpoofingReview: () => void;
|
||||
deleteConversation: (conversationId: string) => unknown;
|
||||
setIsNearBottom: (conversationId: string, isNearBottom: boolean) => unknown;
|
||||
peekGroupCallForTheFirstTime: (conversationId: string) => unknown;
|
||||
peekGroupCallIfItHasMembers: (conversationId: string) => unknown;
|
||||
reviewGroupMemberNameCollision: (groupConversationId: string) => void;
|
||||
reviewMessageRequestNameCollision: (
|
||||
_: Readonly<{
|
||||
safeConversationId: string;
|
||||
}>
|
||||
) => 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;
|
||||
clearSelectedMessage: () => unknown;
|
||||
unblurAvatar: () => void;
|
||||
updateSharedGroups: () => unknown;
|
||||
} & MessageActionsType &
|
||||
SafetyNumberActionsType &
|
||||
UnsupportedMessageActionsType &
|
||||
GroupV2ChangeActionsType &
|
||||
ChatSessionRefreshedNotificationActionsType;
|
||||
showContactModal: (contactId: string, conversationId?: string) => void;
|
||||
} & PropsActionsFromBackboneForChildrenType;
|
||||
|
||||
export type PropsType = PropsDataType &
|
||||
PropsHousekeepingType &
|
||||
|
@ -209,69 +214,29 @@ const getActions = createSelector(
|
|||
// use `createSelector` to memoize them by the last seen `props` object.
|
||||
(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, [
|
||||
'acknowledgeGroupMemberNameCollisions',
|
||||
'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',
|
||||
// MessageActionsType
|
||||
'scrollToQuotedMessage',
|
||||
'showExpiredIncomingTapToViewToast',
|
||||
'showExpiredOutgoingTapToViewToast',
|
||||
'showMessageDetail',
|
||||
'startConversation',
|
||||
|
||||
'toggleSafetyNumberModal',
|
||||
|
||||
'downloadNewVersion',
|
||||
|
||||
// ChatSessionRefreshedNotificationActionsType
|
||||
'contactSupport',
|
||||
|
||||
'viewStory',
|
||||
// DeliveryIssueNotificationActionsType
|
||||
'learnMoreAboutDeliveryIssue',
|
||||
|
||||
// GroupV2ChangeActionsType
|
||||
'blockGroupLinkRequests',
|
||||
]);
|
||||
|
||||
const safe: AssertProps<PropsActionsType, typeof unsafe> = unsafe;
|
||||
const safe: AssertProps<
|
||||
PropsActionsFromBackboneForChildrenType,
|
||||
typeof unsafe
|
||||
> = unsafe;
|
||||
|
||||
return safe;
|
||||
}
|
||||
|
|
|
@ -68,14 +68,13 @@ const getDefaultProps = () => ({
|
|||
contactSupport: action('contactSupport'),
|
||||
setQuoteByMessageId: action('setQuoteByMessageId'),
|
||||
retryDeleteForEveryone: action('retryDeleteForEveryone'),
|
||||
retrySend: action('retrySend'),
|
||||
retryMessageSend: action('retryMessageSend'),
|
||||
blockGroupLinkRequests: action('blockGroupLinkRequests'),
|
||||
deleteMessage: action('deleteMessage'),
|
||||
deleteMessageForEveryone: action('deleteMessageForEveryone'),
|
||||
kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
|
||||
learnMoreAboutDeliveryIssue: action('learnMoreAboutDeliveryIssue'),
|
||||
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
|
||||
markViewed: action('markViewed'),
|
||||
messageExpanded: action('messageExpanded'),
|
||||
showMessageDetail: action('showMessageDetail'),
|
||||
showConversation: action('showConversation'),
|
||||
|
@ -93,9 +92,7 @@ const getDefaultProps = () => ({
|
|||
showExpiredOutgoingTapToViewToast: action(
|
||||
'showExpiredIncomingTapToViewToast'
|
||||
),
|
||||
openLink: action('openLink'),
|
||||
scrollToQuotedMessage: action('scrollToQuotedMessage'),
|
||||
downloadNewVersion: action('downloadNewVersion'),
|
||||
toggleSafetyNumberModal: action('toggleSafetyNumberModal'),
|
||||
startCallingLobby: action('startCallingLobby'),
|
||||
startConversation: action('startConversation'),
|
||||
|
|
|
@ -26,10 +26,7 @@ import type { PropsData as ChangeNumberNotificationProps } from './ChangeNumberN
|
|||
import { ChangeNumberNotification } from './ChangeNumberNotification';
|
||||
import type { CallingNotificationType } from '../../util/callingNotification';
|
||||
import { InlineNotificationWrapper } from './InlineNotificationWrapper';
|
||||
import type {
|
||||
PropsActions as UnsupportedMessageActionsType,
|
||||
PropsData as UnsupportedMessageProps,
|
||||
} from './UnsupportedMessage';
|
||||
import type { PropsData as UnsupportedMessageProps } from './UnsupportedMessage';
|
||||
import { UnsupportedMessage } from './UnsupportedMessage';
|
||||
import type { PropsData as TimerNotificationProps } from './TimerNotification';
|
||||
import { TimerNotification } from './TimerNotification';
|
||||
|
@ -177,7 +174,6 @@ type PropsActionsType = MessageActionsType &
|
|||
DeliveryIssueActionProps &
|
||||
GroupV2ChangeActionsType &
|
||||
PropsChatSessionRefreshedActionsType &
|
||||
UnsupportedMessageActionsType &
|
||||
SafetyNumberActionsType;
|
||||
|
||||
export type PropsType = PropsLocalType &
|
||||
|
|
|
@ -277,11 +277,9 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
isTapToViewExpired: overrideProps.isTapToViewExpired,
|
||||
kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
|
||||
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
|
||||
markViewed: action('markViewed'),
|
||||
messageExpanded: action('messageExpanded'),
|
||||
showConversation: action('showConversation'),
|
||||
openGiftBadge: action('openGiftBadge'),
|
||||
openLink: action('openLink'),
|
||||
previews: overrideProps.previews || [],
|
||||
quote: overrideProps.quote || undefined,
|
||||
reactions: overrideProps.reactions,
|
||||
|
@ -295,7 +293,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
renderAudioAttachment,
|
||||
saveAttachment: action('saveAttachment'),
|
||||
setQuoteByMessageId: action('setQuoteByMessageId'),
|
||||
retrySend: action('retrySend'),
|
||||
retryMessageSend: action('retryMessageSend'),
|
||||
retryDeleteForEveryone: action('retryDeleteForEveryone'),
|
||||
scrollToQuotedMessage: action('scrollToQuotedMessage'),
|
||||
selectMessage: action('selectMessage'),
|
||||
|
|
|
@ -49,7 +49,7 @@ export type PropsActions = {
|
|||
id: string,
|
||||
{ emoji, remove }: { emoji: string; remove: boolean }
|
||||
) => void;
|
||||
retrySend: (id: string) => void;
|
||||
retryMessageSend: (id: string) => void;
|
||||
retryDeleteForEveryone: (id: string) => void;
|
||||
setQuoteByMessageId: (conversationId: string, messageId: string) => void;
|
||||
} & MessagePropsActions;
|
||||
|
@ -99,7 +99,7 @@ export function TimelineMessage(props: Props): JSX.Element {
|
|||
setQuoteByMessageId,
|
||||
renderReactionPicker,
|
||||
renderEmojiPicker,
|
||||
retrySend,
|
||||
retryMessageSend,
|
||||
retryDeleteForEveryone,
|
||||
selectedReaction,
|
||||
toggleForwardMessageModal,
|
||||
|
@ -395,7 +395,7 @@ export function TimelineMessage(props: Props): JSX.Element {
|
|||
onDownload={handleDownload}
|
||||
onReplyToMessage={handleReplyToMessage}
|
||||
onReact={handleReact}
|
||||
onRetrySend={canRetry ? () => retrySend(id) : undefined}
|
||||
onRetryMessageSend={canRetry ? () => retryMessageSend(id) : undefined}
|
||||
onRetryDeleteForEveryone={
|
||||
canRetryDeleteForEveryone
|
||||
? () => retryDeleteForEveryone(id)
|
||||
|
@ -575,7 +575,7 @@ type MessageContextProps = {
|
|||
onDownload: (() => void) | undefined;
|
||||
onReplyToMessage: (() => void) | undefined;
|
||||
onReact: (() => void) | undefined;
|
||||
onRetrySend: (() => void) | undefined;
|
||||
onRetryMessageSend: (() => void) | undefined;
|
||||
onRetryDeleteForEveryone: (() => void) | undefined;
|
||||
onForward: (() => void) | undefined;
|
||||
onDeleteForMe: () => void;
|
||||
|
@ -591,7 +591,7 @@ const MessageContextMenu = ({
|
|||
onReplyToMessage,
|
||||
onReact,
|
||||
onMoreInfo,
|
||||
onRetrySend,
|
||||
onRetryMessageSend,
|
||||
onRetryDeleteForEveryone,
|
||||
onForward,
|
||||
onDeleteForMe,
|
||||
|
@ -660,7 +660,7 @@ const MessageContextMenu = ({
|
|||
>
|
||||
{i18n('moreInfo')}
|
||||
</MenuItem>
|
||||
{onRetrySend && (
|
||||
{onRetryMessageSend && (
|
||||
<MenuItem
|
||||
attributes={{
|
||||
className:
|
||||
|
@ -670,7 +670,7 @@ const MessageContextMenu = ({
|
|||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
onRetrySend();
|
||||
onRetryMessageSend();
|
||||
}}
|
||||
>
|
||||
{i18n('retrySend')}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import { boolean, text } from '@storybook/addon-knobs';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { setupI18n } from '../../util/setupI18n';
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
|
@ -26,7 +25,6 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
i18n,
|
||||
canProcessNow: boolean('canProcessNow', overrideProps.canProcessNow || false),
|
||||
contact: overrideProps.contact || ({} as ContactType),
|
||||
downloadNewVersion: action('downloadNewVersion'),
|
||||
});
|
||||
|
||||
export function FromSomeone(): JSX.Element {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { Button, ButtonSize, ButtonVariant } from '../Button';
|
|||
import { ContactName } from './ContactName';
|
||||
import { Intl } from '../Intl';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import { openLinkInWebBrowser } from '../../util/openLinkInWebBrowser';
|
||||
|
||||
export type ContactType = {
|
||||
id: string;
|
||||
|
@ -23,21 +24,16 @@ export type PropsData = {
|
|||
contact: ContactType;
|
||||
};
|
||||
|
||||
export type PropsActions = {
|
||||
downloadNewVersion: () => unknown;
|
||||
};
|
||||
|
||||
type PropsHousekeeping = {
|
||||
i18n: LocalizerType;
|
||||
};
|
||||
|
||||
export type Props = PropsData & PropsHousekeeping & PropsActions;
|
||||
export type Props = PropsData & PropsHousekeeping;
|
||||
|
||||
export function UnsupportedMessage({
|
||||
canProcessNow,
|
||||
contact,
|
||||
i18n,
|
||||
downloadNewVersion,
|
||||
}: Props): JSX.Element {
|
||||
const { isMe } = contact;
|
||||
|
||||
|
@ -75,7 +71,7 @@ export function UnsupportedMessage({
|
|||
<div className="SystemMessage__line">
|
||||
<Button
|
||||
onClick={() => {
|
||||
downloadNewVersion();
|
||||
openLinkInWebBrowser('https://signal.org/download');
|
||||
}}
|
||||
size={ButtonSize.Small}
|
||||
variant={ButtonVariant.SystemMessage}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue