Moves blockAndReportSpam to redux
This commit is contained in:
parent
92a512a16d
commit
105162dc66
26 changed files with 282 additions and 324 deletions
|
@ -96,11 +96,10 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
clearShowPickerHint: action('clearShowPickerHint'),
|
clearShowPickerHint: action('clearShowPickerHint'),
|
||||||
// Message Requests
|
// Message Requests
|
||||||
conversationType: 'direct',
|
conversationType: 'direct',
|
||||||
onAccept: action('onAccept'),
|
acceptConversation: action('acceptConversation'),
|
||||||
onBlock: action('onBlock'),
|
blockConversation: action('blockConversation'),
|
||||||
onBlockAndReportSpam: action('onBlockAndReportSpam'),
|
blockAndReportSpam: action('blockAndReportSpam'),
|
||||||
onDelete: action('onDelete'),
|
deleteConversation: action('deleteConversation'),
|
||||||
onUnblock: action('onUnblock'),
|
|
||||||
messageRequestsEnabled: boolean(
|
messageRequestsEnabled: boolean(
|
||||||
'messageRequestsEnabled',
|
'messageRequestsEnabled',
|
||||||
overrideProps.messageRequestsEnabled || false
|
overrideProps.messageRequestsEnabled || false
|
||||||
|
|
|
@ -240,11 +240,10 @@ export function CompositionArea({
|
||||||
isMissingMandatoryProfileSharing,
|
isMissingMandatoryProfileSharing,
|
||||||
left,
|
left,
|
||||||
messageRequestsEnabled,
|
messageRequestsEnabled,
|
||||||
onAccept,
|
acceptConversation,
|
||||||
onBlock,
|
blockConversation,
|
||||||
onBlockAndReportSpam,
|
blockAndReportSpam,
|
||||||
onDelete,
|
deleteConversation,
|
||||||
onUnblock,
|
|
||||||
title,
|
title,
|
||||||
// GroupV1 Disabled Actions
|
// GroupV1 Disabled Actions
|
||||||
isGroupV1AndDisabled,
|
isGroupV1AndDisabled,
|
||||||
|
@ -497,14 +496,14 @@ export function CompositionArea({
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<MessageRequestActions
|
<MessageRequestActions
|
||||||
i18n={i18n}
|
acceptConversation={acceptConversation}
|
||||||
|
blockAndReportSpam={blockAndReportSpam}
|
||||||
|
blockConversation={blockConversation}
|
||||||
|
conversationId={conversationId}
|
||||||
conversationType={conversationType}
|
conversationType={conversationType}
|
||||||
|
deleteConversation={deleteConversation}
|
||||||
|
i18n={i18n}
|
||||||
isBlocked={isBlocked}
|
isBlocked={isBlocked}
|
||||||
onBlock={onBlock}
|
|
||||||
onBlockAndReportSpam={onBlockAndReportSpam}
|
|
||||||
onUnblock={onUnblock}
|
|
||||||
onDelete={onDelete}
|
|
||||||
onAccept={onAccept}
|
|
||||||
title={title}
|
title={title}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -549,12 +548,13 @@ export function CompositionArea({
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<MandatoryProfileSharingActions
|
<MandatoryProfileSharingActions
|
||||||
i18n={i18n}
|
acceptConversation={acceptConversation}
|
||||||
|
blockAndReportSpam={blockAndReportSpam}
|
||||||
|
blockConversation={blockConversation}
|
||||||
|
conversationId={conversationId}
|
||||||
conversationType={conversationType}
|
conversationType={conversationType}
|
||||||
onBlock={onBlock}
|
deleteConversation={deleteConversation}
|
||||||
onBlockAndReportSpam={onBlockAndReportSpam}
|
i18n={i18n}
|
||||||
onDelete={onDelete}
|
|
||||||
onAccept={onAccept}
|
|
||||||
title={title}
|
title={title}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -80,3 +80,10 @@ StoryVideoError.args = {
|
||||||
toastType: ToastType.StoryVideoError,
|
toastType: ToastType.StoryVideoError,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ReportedSpamAndBlocked = Template.bind({});
|
||||||
|
ReportedSpamAndBlocked.args = {
|
||||||
|
toast: {
|
||||||
|
toastType: ToastType.ReportedSpamAndBlocked,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -97,6 +97,14 @@ export function ToastManager({
|
||||||
return <ToastMessageBodyTooLong i18n={i18n} onClose={hideToast} />;
|
return <ToastMessageBodyTooLong i18n={i18n} onClose={hideToast} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (toastType === ToastType.ReportedSpamAndBlocked) {
|
||||||
|
return (
|
||||||
|
<Toast onClose={hideToast}>
|
||||||
|
{i18n('MessageRequests--block-and-report-spam-success-toast')}
|
||||||
|
</Toast>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (toastType === ToastType.StoryMuted) {
|
if (toastType === ToastType.StoryMuted) {
|
||||||
return (
|
return (
|
||||||
<Toast onClose={hideToast} timeout={SHORT_TIMEOUT}>
|
<Toast onClose={hideToast} timeout={SHORT_TIMEOUT}>
|
||||||
|
|
|
@ -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 { ToastReportedSpamAndBlocked } from './ToastReportedSpamAndBlocked';
|
|
||||||
|
|
||||||
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/ToastReportedSpamAndBlocked',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const _ToastReportedSpamAndBlocked = (): JSX.Element => (
|
|
||||||
<ToastReportedSpamAndBlocked {...defaultProps} />
|
|
||||||
);
|
|
||||||
|
|
||||||
_ToastReportedSpamAndBlocked.story = {
|
|
||||||
name: 'ToastReportedSpamAndBlocked',
|
|
||||||
};
|
|
|
@ -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 ToastReportedSpamAndBlocked({
|
|
||||||
i18n,
|
|
||||||
onClose,
|
|
||||||
}: PropsType): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Toast onClose={onClose}>
|
|
||||||
{i18n('MessageRequests--block-and-report-spam-success-toast')}
|
|
||||||
</Toast>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -19,15 +19,15 @@ export default {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCommonProps = () => ({
|
const getCommonProps = () => ({
|
||||||
|
acceptConversation: action('acceptConversation'),
|
||||||
|
blockAndReportSpam: action('blockAndReportSpam'),
|
||||||
|
blockConversation: action('blockConversation'),
|
||||||
|
deleteConversation: action('deleteConversation'),
|
||||||
getPreferredBadge: () => undefined,
|
getPreferredBadge: () => undefined,
|
||||||
i18n,
|
|
||||||
groupConversationId: 'convo-id',
|
groupConversationId: 'convo-id',
|
||||||
onBlock: action('onBlock'),
|
i18n,
|
||||||
onBlockAndReportSpam: action('onBlockAndReportSpam'),
|
|
||||||
onClose: action('onClose'),
|
onClose: action('onClose'),
|
||||||
onDelete: action('onDelete'),
|
|
||||||
onShowContactModal: action('onShowContactModal'),
|
onShowContactModal: action('onShowContactModal'),
|
||||||
onUnblock: action('onUnblock'),
|
|
||||||
removeMember: action('removeMember'),
|
removeMember: action('removeMember'),
|
||||||
theme: ThemeType.light,
|
theme: ThemeType.light,
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,14 +25,14 @@ import { missingCaseError } from '../../util/missingCaseError';
|
||||||
import { isInSystemContacts } from '../../util/isInSystemContacts';
|
import { isInSystemContacts } from '../../util/isInSystemContacts';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
|
acceptConversation: (conversationId: string) => unknown;
|
||||||
|
blockAndReportSpam: (conversationId: string) => unknown;
|
||||||
|
blockConversation: (conversationId: string) => unknown;
|
||||||
|
deleteConversation: (conversationId: string) => unknown;
|
||||||
getPreferredBadge: PreferredBadgeSelectorType;
|
getPreferredBadge: PreferredBadgeSelectorType;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
onBlock: (conversationId: string) => unknown;
|
|
||||||
onBlockAndReportSpam: (conversationId: string) => unknown;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onDelete: (conversationId: string) => unknown;
|
|
||||||
onShowContactModal: (contactId: string, conversationId?: string) => unknown;
|
onShowContactModal: (contactId: string, conversationId?: string) => unknown;
|
||||||
onUnblock: (conversationId: string) => unknown;
|
|
||||||
removeMember: (conversationId: string) => unknown;
|
removeMember: (conversationId: string) => unknown;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
} & (
|
} & (
|
||||||
|
@ -62,14 +62,14 @@ enum ConfirmationStateType {
|
||||||
|
|
||||||
export function ContactSpoofingReviewDialog(props: PropsType): JSX.Element {
|
export function ContactSpoofingReviewDialog(props: PropsType): JSX.Element {
|
||||||
const {
|
const {
|
||||||
|
acceptConversation,
|
||||||
|
blockAndReportSpam,
|
||||||
|
blockConversation,
|
||||||
|
deleteConversation,
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
i18n,
|
i18n,
|
||||||
onBlock,
|
|
||||||
onBlockAndReportSpam,
|
|
||||||
onClose,
|
onClose,
|
||||||
onDelete,
|
|
||||||
onShowContactModal,
|
onShowContactModal,
|
||||||
onUnblock,
|
|
||||||
removeMember,
|
removeMember,
|
||||||
theme,
|
theme,
|
||||||
} = props;
|
} = props;
|
||||||
|
@ -96,21 +96,14 @@ export function ContactSpoofingReviewDialog(props: PropsType): JSX.Element {
|
||||||
case ConfirmationStateType.ConfirmingBlock:
|
case ConfirmationStateType.ConfirmingBlock:
|
||||||
return (
|
return (
|
||||||
<MessageRequestActionsConfirmation
|
<MessageRequestActionsConfirmation
|
||||||
i18n={i18n}
|
acceptConversation={acceptConversation}
|
||||||
onBlock={() => {
|
blockAndReportSpam={blockAndReportSpam}
|
||||||
onBlock(affectedConversation.id);
|
blockConversation={blockConversation}
|
||||||
}}
|
conversationId={affectedConversation.id}
|
||||||
onBlockAndReportSpam={() => {
|
|
||||||
onBlockAndReportSpam(affectedConversation.id);
|
|
||||||
}}
|
|
||||||
onUnblock={() => {
|
|
||||||
onUnblock(affectedConversation.id);
|
|
||||||
}}
|
|
||||||
onDelete={() => {
|
|
||||||
onDelete(affectedConversation.id);
|
|
||||||
}}
|
|
||||||
title={affectedConversation.title}
|
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
deleteConversation={deleteConversation}
|
||||||
|
i18n={i18n}
|
||||||
|
title={affectedConversation.title}
|
||||||
state={
|
state={
|
||||||
type === ConfirmationStateType.ConfirmingDelete
|
type === ConfirmationStateType.ConfirmingDelete
|
||||||
? MessageRequestState.deleting
|
? MessageRequestState.deleting
|
||||||
|
@ -279,7 +272,7 @@ export function ContactSpoofingReviewDialog(props: PropsType): JSX.Element {
|
||||||
<Button
|
<Button
|
||||||
variant={ButtonVariant.SecondaryAffirmative}
|
variant={ButtonVariant.SecondaryAffirmative}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onUnblock(conversationInfo.conversation.id);
|
acceptConversation(conversationInfo.conversation.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18n('MessageRequests--unblock')}
|
{i18n('MessageRequests--unblock')}
|
||||||
|
|
|
@ -15,16 +15,17 @@ const i18n = setupI18n('en', enMessages);
|
||||||
const getBaseProps = (
|
const getBaseProps = (
|
||||||
isGroup = false
|
isGroup = false
|
||||||
): MandatoryProfileSharingActionsProps => ({
|
): MandatoryProfileSharingActionsProps => ({
|
||||||
|
conversationId: '123',
|
||||||
i18n,
|
i18n,
|
||||||
conversationType: isGroup ? 'group' : 'direct',
|
conversationType: isGroup ? 'group' : 'direct',
|
||||||
firstName: text('firstName', 'Cayce'),
|
firstName: text('firstName', 'Cayce'),
|
||||||
title: isGroup
|
title: isGroup
|
||||||
? text('title', 'NYC Rock Climbers')
|
? text('title', 'NYC Rock Climbers')
|
||||||
: text('title', 'Cayce Bollard'),
|
: text('title', 'Cayce Bollard'),
|
||||||
onBlock: action('block'),
|
acceptConversation: action('acceptConversation'),
|
||||||
onBlockAndReportSpam: action('onBlockAndReportSpam'),
|
blockAndReportSpam: action('blockAndReportSpam'),
|
||||||
onDelete: action('delete'),
|
blockConversation: action('blockConversation'),
|
||||||
onAccept: action('accept'),
|
deleteConversation: action('deleteConversation'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -16,21 +16,26 @@ import type { LocalizerType } from '../../types/Util';
|
||||||
export type Props = {
|
export type Props = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
firstName?: string;
|
firstName?: string;
|
||||||
onAccept(): unknown;
|
|
||||||
} & Omit<ContactNameProps, 'module'> &
|
} & Omit<ContactNameProps, 'module'> &
|
||||||
Pick<
|
Pick<
|
||||||
MessageRequestActionsConfirmationProps,
|
MessageRequestActionsConfirmationProps,
|
||||||
'conversationType' | 'onBlock' | 'onBlockAndReportSpam' | 'onDelete'
|
| 'acceptConversation'
|
||||||
|
| 'blockAndReportSpam'
|
||||||
|
| 'blockConversation'
|
||||||
|
| 'conversationId'
|
||||||
|
| 'conversationType'
|
||||||
|
| 'deleteConversation'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function MandatoryProfileSharingActions({
|
export function MandatoryProfileSharingActions({
|
||||||
|
acceptConversation,
|
||||||
|
blockAndReportSpam,
|
||||||
|
blockConversation,
|
||||||
|
conversationId,
|
||||||
conversationType,
|
conversationType,
|
||||||
|
deleteConversation,
|
||||||
firstName,
|
firstName,
|
||||||
i18n,
|
i18n,
|
||||||
onAccept,
|
|
||||||
onBlock,
|
|
||||||
onBlockAndReportSpam,
|
|
||||||
onDelete,
|
|
||||||
title,
|
title,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const [mrState, setMrState] = React.useState(MessageRequestState.default);
|
const [mrState, setMrState] = React.useState(MessageRequestState.default);
|
||||||
|
@ -39,15 +44,16 @@ export function MandatoryProfileSharingActions({
|
||||||
<>
|
<>
|
||||||
{mrState !== MessageRequestState.default ? (
|
{mrState !== MessageRequestState.default ? (
|
||||||
<MessageRequestActionsConfirmation
|
<MessageRequestActionsConfirmation
|
||||||
i18n={i18n}
|
acceptConversation={() => {
|
||||||
onBlock={onBlock}
|
|
||||||
onBlockAndReportSpam={onBlockAndReportSpam}
|
|
||||||
onUnblock={() => {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Should not be able to unblock from MandatoryProfileSharingActions'
|
'Should not be able to unblock from MandatoryProfileSharingActions'
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
onDelete={onDelete}
|
blockConversation={blockConversation}
|
||||||
|
conversationId={conversationId}
|
||||||
|
deleteConversation={deleteConversation}
|
||||||
|
i18n={i18n}
|
||||||
|
blockAndReportSpam={blockAndReportSpam}
|
||||||
title={title}
|
title={title}
|
||||||
conversationType={conversationType}
|
conversationType={conversationType}
|
||||||
state={mrState}
|
state={mrState}
|
||||||
|
@ -103,7 +109,7 @@ export function MandatoryProfileSharingActions({
|
||||||
{i18n('MessageRequests--delete')}
|
{i18n('MessageRequests--delete')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={onAccept}
|
onClick={() => acceptConversation(conversationId)}
|
||||||
variant={ButtonVariant.SecondaryAffirmative}
|
variant={ButtonVariant.SecondaryAffirmative}
|
||||||
>
|
>
|
||||||
{i18n('MessageRequests--continue')}
|
{i18n('MessageRequests--continue')}
|
||||||
|
|
|
@ -13,17 +13,17 @@ import enMessages from '../../../_locales/en/messages.json';
|
||||||
const i18n = setupI18n('en', enMessages);
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
const getBaseProps = (isGroup = false): MessageRequestActionsProps => ({
|
const getBaseProps = (isGroup = false): MessageRequestActionsProps => ({
|
||||||
|
conversationId: '123',
|
||||||
i18n,
|
i18n,
|
||||||
conversationType: isGroup ? 'group' : 'direct',
|
conversationType: isGroup ? 'group' : 'direct',
|
||||||
firstName: text('firstName', 'Cayce'),
|
firstName: text('firstName', 'Cayce'),
|
||||||
title: isGroup
|
title: isGroup
|
||||||
? text('title', 'NYC Rock Climbers')
|
? text('title', 'NYC Rock Climbers')
|
||||||
: text('title', 'Cayce Bollard'),
|
: text('title', 'Cayce Bollard'),
|
||||||
onBlock: action('block'),
|
acceptConversation: action('acceptConversation'),
|
||||||
onDelete: action('delete'),
|
blockAndReportSpam: action('blockAndReportSpam'),
|
||||||
onBlockAndReportSpam: action('blockAndReportSpam'),
|
blockConversation: action('blockConversation'),
|
||||||
onUnblock: action('unblock'),
|
deleteConversation: action('deleteConversation'),
|
||||||
onAccept: action('accept'),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -15,7 +15,6 @@ import type { LocalizerType } from '../../types/Util';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
onAccept(): unknown;
|
|
||||||
} & Omit<ContactNameProps, 'module'> &
|
} & Omit<ContactNameProps, 'module'> &
|
||||||
Omit<
|
Omit<
|
||||||
MessageRequestActionsConfirmationProps,
|
MessageRequestActionsConfirmationProps,
|
||||||
|
@ -23,15 +22,15 @@ export type Props = {
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function MessageRequestActions({
|
export function MessageRequestActions({
|
||||||
|
acceptConversation,
|
||||||
|
blockAndReportSpam,
|
||||||
|
blockConversation,
|
||||||
|
conversationId,
|
||||||
conversationType,
|
conversationType,
|
||||||
|
deleteConversation,
|
||||||
firstName,
|
firstName,
|
||||||
i18n,
|
i18n,
|
||||||
isBlocked,
|
isBlocked,
|
||||||
onAccept,
|
|
||||||
onBlock,
|
|
||||||
onBlockAndReportSpam,
|
|
||||||
onDelete,
|
|
||||||
onUnblock,
|
|
||||||
title,
|
title,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const [mrState, setMrState] = React.useState(MessageRequestState.default);
|
const [mrState, setMrState] = React.useState(MessageRequestState.default);
|
||||||
|
@ -40,15 +39,16 @@ export function MessageRequestActions({
|
||||||
<>
|
<>
|
||||||
{mrState !== MessageRequestState.default ? (
|
{mrState !== MessageRequestState.default ? (
|
||||||
<MessageRequestActionsConfirmation
|
<MessageRequestActionsConfirmation
|
||||||
i18n={i18n}
|
acceptConversation={acceptConversation}
|
||||||
onBlock={onBlock}
|
blockAndReportSpam={blockAndReportSpam}
|
||||||
onBlockAndReportSpam={onBlockAndReportSpam}
|
blockConversation={blockConversation}
|
||||||
onUnblock={onUnblock}
|
conversationId={conversationId}
|
||||||
onDelete={onDelete}
|
|
||||||
title={title}
|
|
||||||
conversationType={conversationType}
|
conversationType={conversationType}
|
||||||
state={mrState}
|
deleteConversation={deleteConversation}
|
||||||
|
i18n={i18n}
|
||||||
onChangeState={setMrState}
|
onChangeState={setMrState}
|
||||||
|
state={mrState}
|
||||||
|
title={title}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="module-message-request-actions">
|
<div className="module-message-request-actions">
|
||||||
|
@ -102,7 +102,7 @@ export function MessageRequestActions({
|
||||||
)}
|
)}
|
||||||
{!isBlocked ? (
|
{!isBlocked ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={onAccept}
|
onClick={() => acceptConversation(conversationId)}
|
||||||
variant={ButtonVariant.SecondaryAffirmative}
|
variant={ButtonVariant.SecondaryAffirmative}
|
||||||
>
|
>
|
||||||
{i18n('MessageRequests--accept')}
|
{i18n('MessageRequests--accept')}
|
||||||
|
|
|
@ -16,25 +16,27 @@ export enum MessageRequestState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
i18n: LocalizerType;
|
acceptConversation(conversationId: string): unknown;
|
||||||
|
blockAndReportSpam(conversationId: string): unknown;
|
||||||
|
blockConversation(conversationId: string): unknown;
|
||||||
|
conversationId: string;
|
||||||
conversationType: 'group' | 'direct';
|
conversationType: 'group' | 'direct';
|
||||||
|
deleteConversation(conversationId: string): unknown;
|
||||||
|
i18n: LocalizerType;
|
||||||
isBlocked?: boolean;
|
isBlocked?: boolean;
|
||||||
onBlock(): unknown;
|
|
||||||
onBlockAndReportSpam(): unknown;
|
|
||||||
onUnblock(): unknown;
|
|
||||||
onDelete(): unknown;
|
|
||||||
state: MessageRequestState;
|
|
||||||
onChangeState(state: MessageRequestState): unknown;
|
onChangeState(state: MessageRequestState): unknown;
|
||||||
|
state: MessageRequestState;
|
||||||
} & Omit<ContactNameProps, 'module'>;
|
} & Omit<ContactNameProps, 'module'>;
|
||||||
|
|
||||||
export function MessageRequestActionsConfirmation({
|
export function MessageRequestActionsConfirmation({
|
||||||
|
acceptConversation,
|
||||||
|
blockAndReportSpam,
|
||||||
|
blockConversation,
|
||||||
|
conversationId,
|
||||||
conversationType,
|
conversationType,
|
||||||
|
deleteConversation,
|
||||||
i18n,
|
i18n,
|
||||||
onBlock,
|
|
||||||
onBlockAndReportSpam,
|
|
||||||
onChangeState,
|
onChangeState,
|
||||||
onDelete,
|
|
||||||
onUnblock,
|
|
||||||
state,
|
state,
|
||||||
title,
|
title,
|
||||||
}: Props): JSX.Element | null {
|
}: Props): JSX.Element | null {
|
||||||
|
@ -58,14 +60,14 @@ export function MessageRequestActionsConfirmation({
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
text: i18n('MessageRequests--block-and-report-spam'),
|
text: i18n('MessageRequests--block-and-report-spam'),
|
||||||
action: onBlockAndReportSpam,
|
action: () => blockAndReportSpam(conversationId),
|
||||||
style: 'negative' as const,
|
style: 'negative' as const,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
{
|
{
|
||||||
text: i18n('MessageRequests--block'),
|
text: i18n('MessageRequests--block'),
|
||||||
action: onBlock,
|
action: () => blockConversation(conversationId),
|
||||||
style: 'negative',
|
style: 'negative',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
@ -95,7 +97,7 @@ export function MessageRequestActionsConfirmation({
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
text: i18n('MessageRequests--unblock'),
|
text: i18n('MessageRequests--unblock'),
|
||||||
action: onUnblock,
|
action: () => acceptConversation(conversationId),
|
||||||
style: 'affirmative',
|
style: 'affirmative',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
@ -123,7 +125,7 @@ export function MessageRequestActionsConfirmation({
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
text: i18n(`MessageRequests--delete-${conversationType}`),
|
text: i18n(`MessageRequests--delete-${conversationType}`),
|
||||||
action: onDelete,
|
action: () => deleteConversation(conversationId),
|
||||||
style: 'negative',
|
style: 'negative',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|
|
@ -321,10 +321,10 @@ const actions = () => ({
|
||||||
'reviewMessageRequestNameCollision'
|
'reviewMessageRequestNameCollision'
|
||||||
),
|
),
|
||||||
|
|
||||||
onBlock: action('onBlock'),
|
acceptConversation: action('acceptConversation'),
|
||||||
onBlockAndReportSpam: action('onBlockAndReportSpam'),
|
blockAndReportSpam: action('blockAndReportSpam'),
|
||||||
onDelete: action('onDelete'),
|
blockConversation: action('blockConversation'),
|
||||||
onUnblock: action('onUnblock'),
|
deleteConversation: action('deleteConversation'),
|
||||||
removeMember: action('removeMember'),
|
removeMember: action('removeMember'),
|
||||||
|
|
||||||
unblurAvatar: action('unblurAvatar'),
|
unblurAvatar: action('unblurAvatar'),
|
||||||
|
|
|
@ -165,10 +165,10 @@ export type PropsActionsType = {
|
||||||
loadNewerMessages: (messageId: string) => unknown;
|
loadNewerMessages: (messageId: string) => unknown;
|
||||||
loadNewestMessages: (messageId: string, setFocus?: boolean) => unknown;
|
loadNewestMessages: (messageId: string, setFocus?: boolean) => unknown;
|
||||||
markMessageRead: (messageId: string) => unknown;
|
markMessageRead: (messageId: string) => unknown;
|
||||||
onBlock: (conversationId: string) => unknown;
|
blockConversation: (conversationId: string) => unknown;
|
||||||
onBlockAndReportSpam: (conversationId: string) => unknown;
|
blockAndReportSpam: (conversationId: string) => unknown;
|
||||||
onDelete: (conversationId: string) => unknown;
|
deleteConversation: (conversationId: string) => unknown;
|
||||||
onUnblock: (conversationId: string) => unknown;
|
acceptConversation: (conversationId: string) => unknown;
|
||||||
peekGroupCallForTheFirstTime: (conversationId: string) => unknown;
|
peekGroupCallForTheFirstTime: (conversationId: string) => unknown;
|
||||||
peekGroupCallIfItHasMembers: (conversationId: string) => unknown;
|
peekGroupCallIfItHasMembers: (conversationId: string) => unknown;
|
||||||
removeMember: (conversationId: string) => unknown;
|
removeMember: (conversationId: string) => unknown;
|
||||||
|
@ -224,10 +224,10 @@ const getActions = createSelector(
|
||||||
'loadNewestMessages',
|
'loadNewestMessages',
|
||||||
'markMessageRead',
|
'markMessageRead',
|
||||||
'markViewed',
|
'markViewed',
|
||||||
'onBlock',
|
'acceptConversation',
|
||||||
'onBlockAndReportSpam',
|
'blockAndReportSpam',
|
||||||
'onDelete',
|
'blockConversation',
|
||||||
'onUnblock',
|
'deleteConversation',
|
||||||
'peekGroupCallForTheFirstTime',
|
'peekGroupCallForTheFirstTime',
|
||||||
'peekGroupCallIfItHasMembers',
|
'peekGroupCallIfItHasMembers',
|
||||||
'removeMember',
|
'removeMember',
|
||||||
|
@ -827,10 +827,14 @@ export class Timeline extends React.Component<
|
||||||
|
|
||||||
public override render(): JSX.Element | null {
|
public override render(): JSX.Element | null {
|
||||||
const {
|
const {
|
||||||
|
acceptConversation,
|
||||||
acknowledgeGroupMemberNameCollisions,
|
acknowledgeGroupMemberNameCollisions,
|
||||||
|
blockAndReportSpam,
|
||||||
|
blockConversation,
|
||||||
clearInvitedUuidsForNewlyCreatedGroup,
|
clearInvitedUuidsForNewlyCreatedGroup,
|
||||||
closeContactSpoofingReview,
|
closeContactSpoofingReview,
|
||||||
contactSpoofingReview,
|
contactSpoofingReview,
|
||||||
|
deleteConversation,
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
getTimestampForMessage,
|
getTimestampForMessage,
|
||||||
haveNewest,
|
haveNewest,
|
||||||
|
@ -844,15 +848,11 @@ export class Timeline extends React.Component<
|
||||||
items,
|
items,
|
||||||
messageLoadingState,
|
messageLoadingState,
|
||||||
oldestUnseenIndex,
|
oldestUnseenIndex,
|
||||||
onBlock,
|
|
||||||
onBlockAndReportSpam,
|
|
||||||
onDelete,
|
|
||||||
onUnblock,
|
|
||||||
removeMember,
|
removeMember,
|
||||||
|
renderContactSpoofingReviewDialog,
|
||||||
renderHeroRow,
|
renderHeroRow,
|
||||||
renderItem,
|
renderItem,
|
||||||
renderTypingBubble,
|
renderTypingBubble,
|
||||||
renderContactSpoofingReviewDialog,
|
|
||||||
reviewGroupMemberNameCollision,
|
reviewGroupMemberNameCollision,
|
||||||
reviewMessageRequestNameCollision,
|
reviewMessageRequestNameCollision,
|
||||||
showContactModal,
|
showContactModal,
|
||||||
|
@ -1082,14 +1082,14 @@ export class Timeline extends React.Component<
|
||||||
let contactSpoofingReviewDialog: ReactNode;
|
let contactSpoofingReviewDialog: ReactNode;
|
||||||
if (contactSpoofingReview) {
|
if (contactSpoofingReview) {
|
||||||
const commonProps = {
|
const commonProps = {
|
||||||
|
acceptConversation,
|
||||||
|
blockAndReportSpam,
|
||||||
|
blockConversation,
|
||||||
|
deleteConversation,
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
i18n,
|
i18n,
|
||||||
onBlock,
|
|
||||||
onBlockAndReportSpam,
|
|
||||||
onClose: closeContactSpoofingReview,
|
onClose: closeContactSpoofingReview,
|
||||||
onDelete,
|
|
||||||
onShowContactModal: showContactModal,
|
onShowContactModal: showContactModal,
|
||||||
onUnblock,
|
|
||||||
removeMember,
|
removeMember,
|
||||||
theme,
|
theme,
|
||||||
};
|
};
|
||||||
|
|
|
@ -40,10 +40,12 @@ const createProps = (
|
||||||
hasGroupLink = false,
|
hasGroupLink = false,
|
||||||
expireTimer?: DurationInSeconds
|
expireTimer?: DurationInSeconds
|
||||||
): Props => ({
|
): Props => ({
|
||||||
|
acceptConversation: action('acceptConversation'),
|
||||||
addMembers: async () => {
|
addMembers: async () => {
|
||||||
action('addMembers');
|
action('addMembers');
|
||||||
},
|
},
|
||||||
areWeASubscriber: false,
|
areWeASubscriber: false,
|
||||||
|
blockConversation: action('blockConversation'),
|
||||||
canEditGroupInfo: false,
|
canEditGroupInfo: false,
|
||||||
canAddNewMembers: false,
|
canAddNewMembers: false,
|
||||||
conversation: expireTimer
|
conversation: expireTimer
|
||||||
|
@ -90,9 +92,7 @@ const createProps = (
|
||||||
updateGroupAttributes: async () => {
|
updateGroupAttributes: async () => {
|
||||||
action('updateGroupAttributes')();
|
action('updateGroupAttributes')();
|
||||||
},
|
},
|
||||||
onBlock: action('onBlock'),
|
|
||||||
onLeave: action('onLeave'),
|
onLeave: action('onLeave'),
|
||||||
onUnblock: action('onUnblock'),
|
|
||||||
deleteAvatarFromDisk: action('deleteAvatarFromDisk'),
|
deleteAvatarFromDisk: action('deleteAvatarFromDisk'),
|
||||||
replaceAvatar: action('replaceAvatar'),
|
replaceAvatar: action('replaceAvatar'),
|
||||||
saveAvatarToDisk: action('saveAvatarToDisk'),
|
saveAvatarToDisk: action('saveAvatarToDisk'),
|
||||||
|
|
|
@ -96,9 +96,7 @@ export type StateProps = {
|
||||||
title?: string;
|
title?: string;
|
||||||
}>
|
}>
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
onBlock: () => void;
|
|
||||||
onLeave: () => void;
|
onLeave: () => void;
|
||||||
onUnblock: () => void;
|
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
userAvatarData: Array<AvatarDataType>;
|
userAvatarData: Array<AvatarDataType>;
|
||||||
renderChooseGroupMembersModal: (
|
renderChooseGroupMembersModal: (
|
||||||
|
@ -110,6 +108,8 @@ export type StateProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
type ActionProps = {
|
type ActionProps = {
|
||||||
|
acceptConversation: (id: string) => void;
|
||||||
|
blockConversation: (id: string) => void;
|
||||||
deleteAvatarFromDisk: DeleteAvatarFromDiskActionType;
|
deleteAvatarFromDisk: DeleteAvatarFromDiskActionType;
|
||||||
loadRecentMediaItems: (id: string, limit: number) => void;
|
loadRecentMediaItems: (id: string, limit: number) => void;
|
||||||
onOutgoingAudioCallInConversation: (conversationId: string) => unknown;
|
onOutgoingAudioCallInConversation: (conversationId: string) => unknown;
|
||||||
|
@ -128,9 +128,11 @@ type ActionProps = {
|
||||||
export type Props = StateProps & ActionProps;
|
export type Props = StateProps & ActionProps;
|
||||||
|
|
||||||
export function ConversationDetails({
|
export function ConversationDetails({
|
||||||
|
acceptConversation,
|
||||||
addMembers,
|
addMembers,
|
||||||
areWeASubscriber,
|
areWeASubscriber,
|
||||||
badges,
|
badges,
|
||||||
|
blockConversation,
|
||||||
canEditGroupInfo,
|
canEditGroupInfo,
|
||||||
canAddNewMembers,
|
canAddNewMembers,
|
||||||
conversation,
|
conversation,
|
||||||
|
@ -146,11 +148,9 @@ export function ConversationDetails({
|
||||||
memberships,
|
memberships,
|
||||||
maxGroupSize,
|
maxGroupSize,
|
||||||
maxRecommendedGroupSize,
|
maxRecommendedGroupSize,
|
||||||
onBlock,
|
|
||||||
onLeave,
|
onLeave,
|
||||||
onOutgoingAudioCallInConversation,
|
onOutgoingAudioCallInConversation,
|
||||||
onOutgoingVideoCallInConversation,
|
onOutgoingVideoCallInConversation,
|
||||||
onUnblock,
|
|
||||||
pendingApprovalMemberships,
|
pendingApprovalMemberships,
|
||||||
pendingMemberships,
|
pendingMemberships,
|
||||||
renderChooseGroupMembersModal,
|
renderChooseGroupMembersModal,
|
||||||
|
@ -551,15 +551,16 @@ export function ConversationDetails({
|
||||||
|
|
||||||
{!conversation.isMe && (
|
{!conversation.isMe && (
|
||||||
<ConversationDetailsActions
|
<ConversationDetailsActions
|
||||||
|
acceptConversation={acceptConversation}
|
||||||
|
blockConversation={blockConversation}
|
||||||
cannotLeaveBecauseYouAreLastAdmin={cannotLeaveBecauseYouAreLastAdmin}
|
cannotLeaveBecauseYouAreLastAdmin={cannotLeaveBecauseYouAreLastAdmin}
|
||||||
|
conversationId={conversation.id}
|
||||||
conversationTitle={conversation.title}
|
conversationTitle={conversation.title}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isBlocked={Boolean(conversation.isBlocked)}
|
isBlocked={Boolean(conversation.isBlocked)}
|
||||||
isGroup={isGroup}
|
isGroup={isGroup}
|
||||||
left={Boolean(conversation.left)}
|
left={Boolean(conversation.left)}
|
||||||
onBlock={onBlock}
|
|
||||||
onLeave={onLeave}
|
onLeave={onLeave}
|
||||||
onUnblock={onUnblock}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -19,19 +19,20 @@ export default {
|
||||||
};
|
};
|
||||||
|
|
||||||
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
|
acceptConversation: action('acceptConversation'),
|
||||||
|
blockConversation: action('blockConversation'),
|
||||||
cannotLeaveBecauseYouAreLastAdmin: isBoolean(
|
cannotLeaveBecauseYouAreLastAdmin: isBoolean(
|
||||||
overrideProps.cannotLeaveBecauseYouAreLastAdmin
|
overrideProps.cannotLeaveBecauseYouAreLastAdmin
|
||||||
)
|
)
|
||||||
? overrideProps.cannotLeaveBecauseYouAreLastAdmin
|
? overrideProps.cannotLeaveBecauseYouAreLastAdmin
|
||||||
: false,
|
: false,
|
||||||
|
conversationId: '123',
|
||||||
conversationTitle: overrideProps.conversationTitle || '',
|
conversationTitle: overrideProps.conversationTitle || '',
|
||||||
left: isBoolean(overrideProps.left) ? overrideProps.left : false,
|
|
||||||
onBlock: action('onBlock'),
|
|
||||||
onLeave: action('onLeave'),
|
|
||||||
onUnblock: action('onUnblock'),
|
|
||||||
i18n,
|
i18n,
|
||||||
isBlocked: isBoolean(overrideProps.isBlocked),
|
isBlocked: isBoolean(overrideProps.isBlocked),
|
||||||
isGroup: true,
|
isGroup: true,
|
||||||
|
left: isBoolean(overrideProps.left) ? overrideProps.left : false,
|
||||||
|
onLeave: action('onLeave'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function Basic(): JSX.Element {
|
export function Basic(): JSX.Element {
|
||||||
|
|
|
@ -14,27 +14,29 @@ import { PanelSection } from './PanelSection';
|
||||||
import { ConversationDetailsIcon, IconType } from './ConversationDetailsIcon';
|
import { ConversationDetailsIcon, IconType } from './ConversationDetailsIcon';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
|
acceptConversation: (id: string) => void;
|
||||||
|
blockConversation: (id: string) => void;
|
||||||
cannotLeaveBecauseYouAreLastAdmin: boolean;
|
cannotLeaveBecauseYouAreLastAdmin: boolean;
|
||||||
|
conversationId: string;
|
||||||
conversationTitle: string;
|
conversationTitle: string;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isBlocked: boolean;
|
isBlocked: boolean;
|
||||||
isGroup: boolean;
|
isGroup: boolean;
|
||||||
left: boolean;
|
left: boolean;
|
||||||
onBlock: () => void;
|
|
||||||
onLeave: () => void;
|
onLeave: () => void;
|
||||||
onUnblock: () => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ConversationDetailsActions({
|
export function ConversationDetailsActions({
|
||||||
|
acceptConversation,
|
||||||
|
blockConversation,
|
||||||
cannotLeaveBecauseYouAreLastAdmin,
|
cannotLeaveBecauseYouAreLastAdmin,
|
||||||
|
conversationId,
|
||||||
conversationTitle,
|
conversationTitle,
|
||||||
i18n,
|
i18n,
|
||||||
isBlocked,
|
isBlocked,
|
||||||
isGroup,
|
isGroup,
|
||||||
left,
|
left,
|
||||||
onBlock,
|
|
||||||
onLeave,
|
onLeave,
|
||||||
onUnblock,
|
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const [confirmLeave, gLeave] = useState<boolean>(false);
|
const [confirmLeave, gLeave] = useState<boolean>(false);
|
||||||
const [confirmGroupBlock, gGroupBlock] = useState<boolean>(false);
|
const [confirmGroupBlock, gGroupBlock] = useState<boolean>(false);
|
||||||
|
@ -193,7 +195,7 @@ export function ConversationDetailsActions({
|
||||||
text: i18n(
|
text: i18n(
|
||||||
'ConversationDetailsActions--block-group-modal-confirm'
|
'ConversationDetailsActions--block-group-modal-confirm'
|
||||||
),
|
),
|
||||||
action: onBlock,
|
action: () => blockConversation(conversationId),
|
||||||
style: 'affirmative',
|
style: 'affirmative',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
@ -214,7 +216,7 @@ export function ConversationDetailsActions({
|
||||||
text: i18n(
|
text: i18n(
|
||||||
'ConversationDetailsActions--unblock-group-modal-confirm'
|
'ConversationDetailsActions--unblock-group-modal-confirm'
|
||||||
),
|
),
|
||||||
action: onUnblock,
|
action: () => acceptConversation(conversationId),
|
||||||
style: 'affirmative',
|
style: 'affirmative',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
@ -234,7 +236,7 @@ export function ConversationDetailsActions({
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
text: i18n('MessageRequests--block'),
|
text: i18n('MessageRequests--block'),
|
||||||
action: onBlock,
|
action: () => blockConversation(conversationId),
|
||||||
style: 'affirmative',
|
style: 'affirmative',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
@ -253,7 +255,7 @@ export function ConversationDetailsActions({
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
text: i18n('MessageRequests--unblock'),
|
text: i18n('MessageRequests--unblock'),
|
||||||
action: onUnblock,
|
action: () => acceptConversation(conversationId),
|
||||||
style: 'affirmative',
|
style: 'affirmative',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|
|
@ -104,6 +104,9 @@ import { SHOW_TOAST, ToastType } from './toast';
|
||||||
import { isMemberRequestingToJoin } from '../../util/isMemberRequestingToJoin';
|
import { isMemberRequestingToJoin } from '../../util/isMemberRequestingToJoin';
|
||||||
import { removePendingMember } from '../../util/removePendingMember';
|
import { removePendingMember } from '../../util/removePendingMember';
|
||||||
import { denyPendingApprovalRequest } from '../../util/denyPendingApprovalRequest';
|
import { denyPendingApprovalRequest } from '../../util/denyPendingApprovalRequest';
|
||||||
|
import { SignalService as Proto } from '../../protobuf';
|
||||||
|
import { addReportSpamJob } from '../../jobs/helpers/addReportSpamJob';
|
||||||
|
import { reportSpamJobQueue } from '../../jobs/reportSpamJobQueue';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
|
@ -840,8 +843,11 @@ export type ConversationActionType =
|
||||||
// Action Creators
|
// Action Creators
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
|
acceptConversation,
|
||||||
addMemberToGroup,
|
addMemberToGroup,
|
||||||
approvePendingMembershipFromGroupV2,
|
approvePendingMembershipFromGroupV2,
|
||||||
|
blockAndReportSpam,
|
||||||
|
blockConversation,
|
||||||
cancelConversationVerification,
|
cancelConversationVerification,
|
||||||
changeHasGroupLink,
|
changeHasGroupLink,
|
||||||
clearCancelledConversationVerification,
|
clearCancelledConversationVerification,
|
||||||
|
@ -863,6 +869,7 @@ export const actions = {
|
||||||
conversationUnloaded,
|
conversationUnloaded,
|
||||||
createGroup,
|
createGroup,
|
||||||
deleteAvatarFromDisk,
|
deleteAvatarFromDisk,
|
||||||
|
deleteConversation,
|
||||||
deleteMessageForEveryone,
|
deleteMessageForEveryone,
|
||||||
destroyMessages,
|
destroyMessages,
|
||||||
discardMessages,
|
discardMessages,
|
||||||
|
@ -2205,6 +2212,121 @@ function revokePendingMembershipsFromGroupV2(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function blockAndReportSpam(
|
||||||
|
conversationId: string
|
||||||
|
): ThunkAction<void, RootStateType, unknown, ShowToastActionType> {
|
||||||
|
return async dispatch => {
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
log.error(
|
||||||
|
`blockAndReportSpam: Expected a conversation to be found for ${conversationId}. Doing nothing.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
||||||
|
const idForLogging = conversation.idForLogging();
|
||||||
|
|
||||||
|
longRunningTaskWrapper({
|
||||||
|
name: 'blockAndReportSpam',
|
||||||
|
idForLogging,
|
||||||
|
task: async () => {
|
||||||
|
await Promise.all([
|
||||||
|
conversation.syncMessageRequestResponse(messageRequestEnum.BLOCK),
|
||||||
|
addReportSpamJob({
|
||||||
|
conversation: conversation.format(),
|
||||||
|
getMessageServerGuidsForSpam:
|
||||||
|
window.Signal.Data.getMessageServerGuidsForSpam,
|
||||||
|
jobQueue: reportSpamJobQueue,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: SHOW_TOAST,
|
||||||
|
payload: {
|
||||||
|
toastType: ToastType.ReportedSpamAndBlocked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function acceptConversation(conversationId: string): NoopActionType {
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
throw new Error(
|
||||||
|
'acceptConversation: Expected a conversation to be found. Doing nothing'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
||||||
|
|
||||||
|
longRunningTaskWrapper({
|
||||||
|
name: 'acceptConversation',
|
||||||
|
idForLogging: conversation.idForLogging(),
|
||||||
|
task: conversation.syncMessageRequestResponse.bind(
|
||||||
|
conversation,
|
||||||
|
messageRequestEnum.ACCEPT
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'NOOP',
|
||||||
|
payload: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function blockConversation(conversationId: string): NoopActionType {
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
throw new Error(
|
||||||
|
'blockConversation: Expected a conversation to be found. Doing nothing'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
||||||
|
|
||||||
|
longRunningTaskWrapper({
|
||||||
|
name: 'blockConversation',
|
||||||
|
idForLogging: conversation.idForLogging(),
|
||||||
|
task: conversation.syncMessageRequestResponse.bind(
|
||||||
|
conversation,
|
||||||
|
messageRequestEnum.BLOCK
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'NOOP',
|
||||||
|
payload: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteConversation(conversationId: string): NoopActionType {
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
throw new Error(
|
||||||
|
'deleteConversation: Expected a conversation to be found. Doing nothing'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
||||||
|
|
||||||
|
longRunningTaskWrapper({
|
||||||
|
name: 'deleteConversation',
|
||||||
|
idForLogging: conversation.idForLogging(),
|
||||||
|
task: conversation.syncMessageRequestResponse.bind(
|
||||||
|
conversation,
|
||||||
|
messageRequestEnum.DELETE
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'NOOP',
|
||||||
|
payload: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function loadRecentMediaItems(
|
function loadRecentMediaItems(
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
limit: number
|
limit: number
|
||||||
|
|
|
@ -13,6 +13,7 @@ export enum ToastType {
|
||||||
Error = 'Error',
|
Error = 'Error',
|
||||||
FailedToDeleteUsername = 'FailedToDeleteUsername',
|
FailedToDeleteUsername = 'FailedToDeleteUsername',
|
||||||
MessageBodyTooLong = 'MessageBodyTooLong',
|
MessageBodyTooLong = 'MessageBodyTooLong',
|
||||||
|
ReportedSpamAndBlocked = 'ReportedSpamAndBlocked',
|
||||||
StoryMuted = 'StoryMuted',
|
StoryMuted = 'StoryMuted',
|
||||||
StoryReact = 'StoryReact',
|
StoryReact = 'StoryReact',
|
||||||
StoryReply = 'StoryReply',
|
StoryReply = 'StoryReply',
|
||||||
|
|
|
@ -54,9 +54,7 @@ export type SmartConversationDetailsProps = {
|
||||||
title?: string;
|
title?: string;
|
||||||
}>
|
}>
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
onBlock: () => void;
|
|
||||||
onLeave: () => void;
|
onLeave: () => void;
|
||||||
onUnblock: () => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ACCESS_ENUM = Proto.AccessControl.AccessRequired;
|
const ACCESS_ENUM = Proto.AccessControl.AccessRequired;
|
||||||
|
|
|
@ -21,21 +21,16 @@ export type PropsType = {
|
||||||
| 'getQuotedMessage'
|
| 'getQuotedMessage'
|
||||||
| 'handleClickQuotedMessage'
|
| 'handleClickQuotedMessage'
|
||||||
| 'id'
|
| 'id'
|
||||||
| 'onAccept'
|
|
||||||
| 'onBlock'
|
|
||||||
| 'onBlockAndReportSpam'
|
|
||||||
| 'onCancelJoinRequest'
|
| 'onCancelJoinRequest'
|
||||||
| 'onClearAttachments'
|
| 'onClearAttachments'
|
||||||
| 'onClickAddPack'
|
| 'onClickAddPack'
|
||||||
| 'onCloseLinkPreview'
|
| 'onCloseLinkPreview'
|
||||||
| 'onDelete'
|
|
||||||
| 'onEditorStateChange'
|
| 'onEditorStateChange'
|
||||||
| 'onPickSticker'
|
| 'onPickSticker'
|
||||||
| 'onSelectMediaQuality'
|
| 'onSelectMediaQuality'
|
||||||
| 'onSendMessage'
|
| 'onSendMessage'
|
||||||
| 'onStartGroupMigration'
|
| 'onStartGroupMigration'
|
||||||
| 'onTextTooLong'
|
| 'onTextTooLong'
|
||||||
| 'onUnblock'
|
|
||||||
| 'openConversation'
|
| 'openConversation'
|
||||||
>;
|
>;
|
||||||
conversationHeaderProps: ConversationHeaderPropsType;
|
conversationHeaderProps: ConversationHeaderPropsType;
|
||||||
|
|
|
@ -76,10 +76,6 @@ export type TimelinePropsType = ExternalProps &
|
||||||
| 'loadOlderMessages'
|
| 'loadOlderMessages'
|
||||||
| 'markAttachmentAsCorrupted'
|
| 'markAttachmentAsCorrupted'
|
||||||
| 'markMessageRead'
|
| 'markMessageRead'
|
||||||
| 'onBlock'
|
|
||||||
| 'onBlockAndReportSpam'
|
|
||||||
| 'onDelete'
|
|
||||||
| 'onUnblock'
|
|
||||||
| 'openConversation'
|
| 'openConversation'
|
||||||
| 'openGiftBadge'
|
| 'openGiftBadge'
|
||||||
| 'openLink'
|
| 'openLink'
|
||||||
|
|
|
@ -46,7 +46,6 @@ import type { ToastMessageBodyTooLong } from '../components/ToastMessageBodyTooL
|
||||||
import type { ToastOriginalMessageNotFound } from '../components/ToastOriginalMessageNotFound';
|
import type { ToastOriginalMessageNotFound } from '../components/ToastOriginalMessageNotFound';
|
||||||
import type { ToastPinnedConversationsFull } from '../components/ToastPinnedConversationsFull';
|
import type { ToastPinnedConversationsFull } from '../components/ToastPinnedConversationsFull';
|
||||||
import type { ToastReactionFailed } from '../components/ToastReactionFailed';
|
import type { ToastReactionFailed } from '../components/ToastReactionFailed';
|
||||||
import type { ToastReportedSpamAndBlocked } from '../components/ToastReportedSpamAndBlocked';
|
|
||||||
import type { ToastStickerPackInstallFailed } from '../components/ToastStickerPackInstallFailed';
|
import type { ToastStickerPackInstallFailed } from '../components/ToastStickerPackInstallFailed';
|
||||||
import type { ToastTapToViewExpiredIncoming } from '../components/ToastTapToViewExpiredIncoming';
|
import type { ToastTapToViewExpiredIncoming } from '../components/ToastTapToViewExpiredIncoming';
|
||||||
import type { ToastTapToViewExpiredOutgoing } from '../components/ToastTapToViewExpiredOutgoing';
|
import type { ToastTapToViewExpiredOutgoing } from '../components/ToastTapToViewExpiredOutgoing';
|
||||||
|
@ -96,7 +95,6 @@ export function showToast(Toast: typeof ToastUnsupportedMultiAttachment): void;
|
||||||
export function showToast(Toast: typeof ToastOriginalMessageNotFound): void;
|
export function showToast(Toast: typeof ToastOriginalMessageNotFound): void;
|
||||||
export function showToast(Toast: typeof ToastPinnedConversationsFull): void;
|
export function showToast(Toast: typeof ToastPinnedConversationsFull): void;
|
||||||
export function showToast(Toast: typeof ToastReactionFailed): void;
|
export function showToast(Toast: typeof ToastReactionFailed): void;
|
||||||
export function showToast(Toast: typeof ToastReportedSpamAndBlocked): 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 ToastTapToViewExpiredIncoming): void;
|
||||||
export function showToast(Toast: typeof ToastTapToViewExpiredOutgoing): void;
|
export function showToast(Toast: typeof ToastTapToViewExpiredOutgoing): void;
|
||||||
|
|
|
@ -27,8 +27,6 @@ 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 { enqueueReactionForSend } from '../reactions/enqueueReactionForSend';
|
||||||
import { addReportSpamJob } from '../jobs/helpers/addReportSpamJob';
|
|
||||||
import { reportSpamJobQueue } from '../jobs/reportSpamJobQueue';
|
|
||||||
import type { GroupNameCollisionsWithIdsByTitle } from '../util/groupMemberNameCollisions';
|
import type { GroupNameCollisionsWithIdsByTitle } from '../util/groupMemberNameCollisions';
|
||||||
import {
|
import {
|
||||||
isDirectConversation,
|
isDirectConversation,
|
||||||
|
@ -57,7 +55,6 @@ import type { EmbeddedContactType } from '../types/EmbeddedContact';
|
||||||
import { createConversationView } from '../state/roots/createConversationView';
|
import { createConversationView } from '../state/roots/createConversationView';
|
||||||
import { AttachmentToastType } from '../types/AttachmentToastType';
|
import { AttachmentToastType } from '../types/AttachmentToastType';
|
||||||
import type { CompositionAPIType } from '../components/CompositionArea';
|
import type { CompositionAPIType } from '../components/CompositionArea';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
|
||||||
import { ToastBlocked } from '../components/ToastBlocked';
|
import { ToastBlocked } from '../components/ToastBlocked';
|
||||||
import { ToastBlockedGroup } from '../components/ToastBlockedGroup';
|
import { ToastBlockedGroup } from '../components/ToastBlockedGroup';
|
||||||
import { ToastCannotMixMultiAndNonMultiAttachments } from '../components/ToastCannotMixMultiAndNonMultiAttachments';
|
import { ToastCannotMixMultiAndNonMultiAttachments } from '../components/ToastCannotMixMultiAndNonMultiAttachments';
|
||||||
|
@ -75,7 +72,6 @@ import { ToastUnsupportedMultiAttachment } from '../components/ToastUnsupportedM
|
||||||
import { ToastOriginalMessageNotFound } from '../components/ToastOriginalMessageNotFound';
|
import { ToastOriginalMessageNotFound } from '../components/ToastOriginalMessageNotFound';
|
||||||
import { ToastPinnedConversationsFull } from '../components/ToastPinnedConversationsFull';
|
import { ToastPinnedConversationsFull } from '../components/ToastPinnedConversationsFull';
|
||||||
import { ToastReactionFailed } from '../components/ToastReactionFailed';
|
import { ToastReactionFailed } from '../components/ToastReactionFailed';
|
||||||
import { ToastReportedSpamAndBlocked } from '../components/ToastReportedSpamAndBlocked';
|
|
||||||
import { ToastTapToViewExpiredIncoming } from '../components/ToastTapToViewExpiredIncoming';
|
import { ToastTapToViewExpiredIncoming } from '../components/ToastTapToViewExpiredIncoming';
|
||||||
import { ToastTapToViewExpiredOutgoing } from '../components/ToastTapToViewExpiredOutgoing';
|
import { ToastTapToViewExpiredOutgoing } from '../components/ToastTapToViewExpiredOutgoing';
|
||||||
import { ToastUnableToLoadAttachment } from '../components/ToastUnableToLoadAttachment';
|
import { ToastUnableToLoadAttachment } from '../components/ToastUnableToLoadAttachment';
|
||||||
|
@ -368,7 +364,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
window.reduxActions.conversations.setSelectedConversationHeaderTitle();
|
window.reduxActions.conversations.setSelectedConversationHeaderTitle();
|
||||||
|
|
||||||
// setupTimeline
|
// setupTimeline
|
||||||
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
|
||||||
|
|
||||||
const contactSupport = () => {
|
const contactSupport = () => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
|
@ -433,19 +428,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createMessageRequestResponseHandler =
|
|
||||||
(name: string, enumValue: number): ((conversationId: string) => void) =>
|
|
||||||
conversationId => {
|
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
|
||||||
if (!conversation) {
|
|
||||||
log.error(
|
|
||||||
`createMessageRequestResponseHandler: Expected a conversation to be found in ${name}. Doing nothing`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.syncMessageRequestResponse(name, conversation, enumValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
const timelineProps = {
|
const timelineProps = {
|
||||||
id: this.model.id,
|
id: this.model.id,
|
||||||
|
|
||||||
|
@ -465,28 +447,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
loadNewestMessages: this.model.loadNewestMessages.bind(this.model),
|
loadNewestMessages: this.model.loadNewestMessages.bind(this.model),
|
||||||
loadOlderMessages: this.model.loadOlderMessages.bind(this.model),
|
loadOlderMessages: this.model.loadOlderMessages.bind(this.model),
|
||||||
markMessageRead,
|
markMessageRead,
|
||||||
onBlock: createMessageRequestResponseHandler(
|
|
||||||
'onBlock',
|
|
||||||
messageRequestEnum.BLOCK
|
|
||||||
),
|
|
||||||
onBlockAndReportSpam: (conversationId: string) => {
|
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
|
||||||
if (!conversation) {
|
|
||||||
log.error(
|
|
||||||
`onBlockAndReportSpam: Expected a conversation to be found for ${conversationId}. Doing nothing.`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.blockAndReportSpam(conversation);
|
|
||||||
},
|
|
||||||
onDelete: createMessageRequestResponseHandler(
|
|
||||||
'onDelete',
|
|
||||||
messageRequestEnum.DELETE
|
|
||||||
),
|
|
||||||
onUnblock: createMessageRequestResponseHandler(
|
|
||||||
'onUnblock',
|
|
||||||
messageRequestEnum.ACCEPT
|
|
||||||
),
|
|
||||||
removeMember: (conversationId: string) => {
|
removeMember: (conversationId: string) => {
|
||||||
longRunningTaskWrapper({
|
longRunningTaskWrapper({
|
||||||
idForLogging: this.model.idForLogging(),
|
idForLogging: this.model.idForLogging(),
|
||||||
|
@ -518,37 +478,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
onTextTooLong: () => showToast(ToastMessageBodyTooLong),
|
onTextTooLong: () => showToast(ToastMessageBodyTooLong),
|
||||||
getQuotedMessage: () => this.model.get('quotedMessageId'),
|
getQuotedMessage: () => this.model.get('quotedMessageId'),
|
||||||
clearQuotedMessage: () => this.setQuoteMessage(null),
|
clearQuotedMessage: () => this.setQuoteMessage(null),
|
||||||
onAccept: () => {
|
|
||||||
this.syncMessageRequestResponse(
|
|
||||||
'onAccept',
|
|
||||||
this.model,
|
|
||||||
messageRequestEnum.ACCEPT
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onBlock: () => {
|
|
||||||
this.syncMessageRequestResponse(
|
|
||||||
'onBlock',
|
|
||||||
this.model,
|
|
||||||
messageRequestEnum.BLOCK
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onUnblock: () => {
|
|
||||||
this.syncMessageRequestResponse(
|
|
||||||
'onUnblock',
|
|
||||||
this.model,
|
|
||||||
messageRequestEnum.ACCEPT
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onDelete: () => {
|
|
||||||
this.syncMessageRequestResponse(
|
|
||||||
'onDelete',
|
|
||||||
this.model,
|
|
||||||
messageRequestEnum.DELETE
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onBlockAndReportSpam: () => {
|
|
||||||
this.blockAndReportSpam(this.model);
|
|
||||||
},
|
|
||||||
onStartGroupMigration: () => this.startMigrationToGV2(),
|
onStartGroupMigration: () => this.startMigrationToGV2(),
|
||||||
onCancelJoinRequest: async () => {
|
onCancelJoinRequest: async () => {
|
||||||
await window.showConfirmationDialog({
|
await window.showConfirmationDialog({
|
||||||
|
@ -1008,39 +937,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
this.processAttachments(files);
|
this.processAttachments(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
syncMessageRequestResponse(
|
|
||||||
name: string,
|
|
||||||
model: ConversationModel,
|
|
||||||
messageRequestType: number
|
|
||||||
): Promise<void> {
|
|
||||||
return longRunningTaskWrapper({
|
|
||||||
idForLogging: this.model.idForLogging(),
|
|
||||||
name,
|
|
||||||
task: model.syncMessageRequestResponse.bind(model, messageRequestType),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
blockAndReportSpam(model: ConversationModel): Promise<void> {
|
|
||||||
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
|
||||||
|
|
||||||
return longRunningTaskWrapper({
|
|
||||||
idForLogging: this.model.idForLogging(),
|
|
||||||
name: 'blockAndReportSpam',
|
|
||||||
task: async () => {
|
|
||||||
await Promise.all([
|
|
||||||
model.syncMessageRequestResponse(messageRequestEnum.BLOCK),
|
|
||||||
addReportSpamJob({
|
|
||||||
conversation: model.format(),
|
|
||||||
getMessageServerGuidsForSpam:
|
|
||||||
window.Signal.Data.getMessageServerGuidsForSpam,
|
|
||||||
jobQueue: reportSpamJobQueue,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
showToast(ToastReportedSpamAndBlocked);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveModel(): Promise<void> {
|
async saveModel(): Promise<void> {
|
||||||
window.Signal.Data.updateConversation(this.model.attributes);
|
window.Signal.Data.updateConversation(this.model.attributes);
|
||||||
}
|
}
|
||||||
|
@ -1803,8 +1699,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
this.model.throttledGetProfiles();
|
this.model.throttledGetProfiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
|
||||||
|
|
||||||
// these methods are used in more than one place and should probably be
|
// these methods are used in more than one place and should probably be
|
||||||
// dried up and hoisted to methods on ConversationView
|
// dried up and hoisted to methods on ConversationView
|
||||||
|
|
||||||
|
@ -1816,14 +1710,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBlock = () => {
|
|
||||||
this.syncMessageRequestResponse(
|
|
||||||
'onBlock',
|
|
||||||
this.model,
|
|
||||||
messageRequestEnum.BLOCK
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
addMembers: this.model.addMembersV2.bind(this.model),
|
addMembers: this.model.addMembersV2.bind(this.model),
|
||||||
conversationId: this.model.get('id'),
|
conversationId: this.model.get('id'),
|
||||||
|
@ -1840,14 +1726,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
this.model
|
this.model
|
||||||
),
|
),
|
||||||
onLeave,
|
onLeave,
|
||||||
onBlock,
|
|
||||||
onUnblock: () => {
|
|
||||||
this.syncMessageRequestResponse(
|
|
||||||
'onUnblock',
|
|
||||||
this.model,
|
|
||||||
messageRequestEnum.ACCEPT
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const view = new ReactWrapperView({
|
const view = new ReactWrapperView({
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue