Message Reports: Implement reporting for messages and stories (#5052)
This commit is contained in:
parent
f7e553734b
commit
01dc204fa7
@ -16,6 +16,7 @@ import type {
|
|||||||
ApiMessageActionStarGift,
|
ApiMessageActionStarGift,
|
||||||
ApiMessageEntity,
|
ApiMessageEntity,
|
||||||
ApiMessageForwardInfo,
|
ApiMessageForwardInfo,
|
||||||
|
ApiMessageReportResult,
|
||||||
ApiNewPoll,
|
ApiNewPoll,
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiPhoto,
|
ApiPhoto,
|
||||||
@ -1246,3 +1247,33 @@ export function buildApiQuickReply(reply: GramJs.TypeQuickReply): ApiQuickReply
|
|||||||
topMessageId: topMessage,
|
topMessageId: topMessage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildApiReportResult(
|
||||||
|
result: GramJs.TypeReportResult,
|
||||||
|
): ApiMessageReportResult {
|
||||||
|
if (result instanceof GramJs.ReportResultReported) {
|
||||||
|
return {
|
||||||
|
type: 'reported',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result instanceof GramJs.ReportResultAddComment) {
|
||||||
|
return {
|
||||||
|
type: 'comment',
|
||||||
|
isOptional: result.optional,
|
||||||
|
option: serializeBytes(result.option),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = result.title;
|
||||||
|
const options = result.options.map((option) => ({
|
||||||
|
text: option.text,
|
||||||
|
option: serializeBytes(option.option),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'selectOption',
|
||||||
|
title,
|
||||||
|
options,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import type {
|
|||||||
ApiChat,
|
ApiChat,
|
||||||
ApiClickSponsoredMessage,
|
ApiClickSponsoredMessage,
|
||||||
ApiContact,
|
ApiContact,
|
||||||
|
ApiError,
|
||||||
ApiFormattedText,
|
ApiFormattedText,
|
||||||
ApiGlobalMessageSearchType,
|
ApiGlobalMessageSearchType,
|
||||||
ApiInputReplyInfo,
|
ApiInputReplyInfo,
|
||||||
@ -19,7 +20,6 @@ import type {
|
|||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiPoll,
|
ApiPoll,
|
||||||
ApiReaction,
|
ApiReaction,
|
||||||
ApiReportReason,
|
|
||||||
ApiSendMessageAction,
|
ApiSendMessageAction,
|
||||||
ApiSticker,
|
ApiSticker,
|
||||||
ApiStory,
|
ApiStory,
|
||||||
@ -36,6 +36,7 @@ import {
|
|||||||
GIF_MIME_TYPE,
|
GIF_MIME_TYPE,
|
||||||
MAX_INT_32,
|
MAX_INT_32,
|
||||||
MENTION_UNREAD_SLICE,
|
MENTION_UNREAD_SLICE,
|
||||||
|
MESSAGE_ID_REQUIRED_ERROR,
|
||||||
PINNED_MESSAGES_LIMIT,
|
PINNED_MESSAGES_LIMIT,
|
||||||
REACTION_UNREAD_SLICE,
|
REACTION_UNREAD_SLICE,
|
||||||
SUPPORTED_PHOTO_CONTENT_TYPES,
|
SUPPORTED_PHOTO_CONTENT_TYPES,
|
||||||
@ -47,13 +48,17 @@ import { compact, split } from '../../../util/iteratees';
|
|||||||
import { getMessageKey } from '../../../util/keys/messageKey';
|
import { getMessageKey } from '../../../util/keys/messageKey';
|
||||||
import { getServerTimeOffset } from '../../../util/serverTime';
|
import { getServerTimeOffset } from '../../../util/serverTime';
|
||||||
import { interpolateArray } from '../../../util/waveform';
|
import { interpolateArray } from '../../../util/waveform';
|
||||||
import { buildApiChatFromPreview, buildApiSendAsPeerId } from '../apiBuilders/chats';
|
import {
|
||||||
|
buildApiChatFromPreview,
|
||||||
|
buildApiSendAsPeerId,
|
||||||
|
} from '../apiBuilders/chats';
|
||||||
import { buildApiFormattedText } from '../apiBuilders/common';
|
import { buildApiFormattedText } from '../apiBuilders/common';
|
||||||
import { buildMessageMediaContent, buildMessageTextContent, buildWebPage } from '../apiBuilders/messageContent';
|
import { buildMessageMediaContent, buildMessageTextContent, buildWebPage } from '../apiBuilders/messageContent';
|
||||||
import {
|
import {
|
||||||
buildApiFactCheck,
|
buildApiFactCheck,
|
||||||
buildApiMessage,
|
buildApiMessage,
|
||||||
buildApiQuickReply,
|
buildApiQuickReply,
|
||||||
|
buildApiReportResult,
|
||||||
buildApiSponsoredMessage,
|
buildApiSponsoredMessage,
|
||||||
buildApiThreadInfo,
|
buildApiThreadInfo,
|
||||||
buildLocalForwardedMessage,
|
buildLocalForwardedMessage,
|
||||||
@ -901,18 +906,45 @@ export async function deleteSavedHistory({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function reportMessages({
|
export async function reportMessages({
|
||||||
peer, messageIds, description,
|
peer, messageIds, description, option,
|
||||||
}: {
|
}: {
|
||||||
peer: ApiPeer; messageIds: number[]; reason: ApiReportReason; description?: string;
|
peer: ApiPeer; messageIds: number[]; description: string; option: string;
|
||||||
}) {
|
}) {
|
||||||
const result = await invokeRequest(new GramJs.messages.Report({
|
try {
|
||||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
const result = await invokeRequest(new GramJs.messages.Report({
|
||||||
id: messageIds,
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
option: Buffer.alloc(0),
|
id: messageIds,
|
||||||
message: description,
|
option: deserializeBytes(option),
|
||||||
}));
|
message: description,
|
||||||
|
}), { shouldThrow: true });
|
||||||
|
|
||||||
return result;
|
if (!result) return undefined;
|
||||||
|
|
||||||
|
return { result: buildApiReportResult(result), error: undefined };
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage = (err as ApiError).message;
|
||||||
|
|
||||||
|
if (errorMessage === MESSAGE_ID_REQUIRED_ERROR) {
|
||||||
|
return {
|
||||||
|
result: undefined,
|
||||||
|
error: errorMessage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reportChannelSpam({
|
||||||
|
peer, chat, messageIds,
|
||||||
|
}: {
|
||||||
|
peer: ApiPeer; chat: ApiChat; messageIds: number[];
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.channels.ReportSpam({
|
||||||
|
participant: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
||||||
|
id: messageIds,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendMessageAction({
|
export async function sendMessageAction({
|
||||||
|
|||||||
@ -2,16 +2,17 @@ import { Api as GramJs } from '../../../lib/gramjs';
|
|||||||
|
|
||||||
import type { ApiInputPrivacyRules } from '../../../types';
|
import type { ApiInputPrivacyRules } from '../../../types';
|
||||||
import type {
|
import type {
|
||||||
|
ApiError,
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiPeerStories,
|
ApiPeerStories,
|
||||||
ApiReaction,
|
ApiReaction,
|
||||||
ApiReportReason,
|
|
||||||
ApiStealthMode,
|
ApiStealthMode,
|
||||||
ApiTypeStory,
|
ApiTypeStory,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { STORY_LIST_LIMIT } from '../../../config';
|
import { MESSAGE_ID_REQUIRED_ERROR, STORY_LIST_LIMIT } from '../../../config';
|
||||||
import { buildCollectionByCallback } from '../../../util/iteratees';
|
import { buildCollectionByCallback } from '../../../util/iteratees';
|
||||||
|
import { buildApiReportResult } from '../apiBuilders/messages';
|
||||||
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
||||||
import {
|
import {
|
||||||
buildApiPeerStories,
|
buildApiPeerStories,
|
||||||
@ -25,7 +26,7 @@ import {
|
|||||||
buildInputPrivacyRules,
|
buildInputPrivacyRules,
|
||||||
buildInputReaction,
|
buildInputReaction,
|
||||||
} from '../gramjsBuilders';
|
} from '../gramjsBuilders';
|
||||||
import { addStoryToLocalDb } from '../helpers';
|
import { addStoryToLocalDb, deserializeBytes } from '../helpers';
|
||||||
import { invokeRequest } from './client';
|
import { invokeRequest } from './client';
|
||||||
|
|
||||||
export async function fetchAllStories({
|
export async function fetchAllStories({
|
||||||
@ -328,19 +329,37 @@ export async function fetchStoryLink({ peer, storyId }: { peer: ApiPeer ; storyI
|
|||||||
return result.link;
|
return result.link;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reportStory({
|
export async function reportStory({
|
||||||
peer,
|
peer,
|
||||||
storyId,
|
storyId,
|
||||||
description,
|
description,
|
||||||
|
option,
|
||||||
}: {
|
}: {
|
||||||
peer: ApiPeer; storyId: number; reason: ApiReportReason; description?: string;
|
peer: ApiPeer; storyId: number; description: string; option: string;
|
||||||
}) {
|
}) {
|
||||||
return invokeRequest(new GramJs.stories.Report({
|
try {
|
||||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
const result = await invokeRequest(new GramJs.stories.Report({
|
||||||
id: [storyId],
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
option: Buffer.alloc(0),
|
id: [storyId],
|
||||||
message: description,
|
option: deserializeBytes(option),
|
||||||
}));
|
message: description,
|
||||||
|
}), { shouldThrow: true });
|
||||||
|
|
||||||
|
if (!result) return undefined;
|
||||||
|
|
||||||
|
return { result: buildApiReportResult(result), error: undefined };
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage = (err as ApiError).message;
|
||||||
|
|
||||||
|
if (errorMessage === MESSAGE_ID_REQUIRED_ERROR) {
|
||||||
|
return {
|
||||||
|
result: undefined,
|
||||||
|
error: errorMessage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function editStoryPrivacy({
|
export function editStoryPrivacy({
|
||||||
|
|||||||
@ -871,6 +871,21 @@ export interface ApiMessageThreadInfo extends ApiBaseThreadInfo {
|
|||||||
|
|
||||||
export type ApiThreadInfo = ApiCommentsInfo | ApiMessageThreadInfo;
|
export type ApiThreadInfo = ApiCommentsInfo | ApiMessageThreadInfo;
|
||||||
|
|
||||||
|
export type ApiMessageReportResult = {
|
||||||
|
type: 'reported';
|
||||||
|
} | {
|
||||||
|
type: 'comment';
|
||||||
|
isOptional?: boolean;
|
||||||
|
option: string;
|
||||||
|
} | {
|
||||||
|
type: 'selectOption';
|
||||||
|
title: string;
|
||||||
|
options: {
|
||||||
|
text: string;
|
||||||
|
option: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiMessageOutgoingStatus = 'read' | 'succeeded' | 'pending' | 'failed';
|
export type ApiMessageOutgoingStatus = 'read' | 'succeeded' | 'pending' | 'failed';
|
||||||
|
|
||||||
export type ApiSponsoredMessage = {
|
export type ApiSponsoredMessage = {
|
||||||
|
|||||||
BIN
src/assets/tgs/Report.tgs
Normal file
BIN
src/assets/tgs/Report.tgs
Normal file
Binary file not shown.
@ -29,6 +29,7 @@ export { default as AboutAdsModal } from '../components/common/AboutAdsModal';
|
|||||||
export { default as AboutMonetizationModal } from '../components/common/AboutMonetizationModal';
|
export { default as AboutMonetizationModal } from '../components/common/AboutMonetizationModal';
|
||||||
export { default as VerificationMonetizationModal } from '../components/common/VerificationMonetizationModal';
|
export { default as VerificationMonetizationModal } from '../components/common/VerificationMonetizationModal';
|
||||||
export { default as ReportAdModal } from '../components/modals/reportAd/ReportAdModal';
|
export { default as ReportAdModal } from '../components/modals/reportAd/ReportAdModal';
|
||||||
|
export { default as ReportModal } from '../components/modals/reportModal/ReportModal';
|
||||||
export { default as CalendarModal } from '../components/common/CalendarModal';
|
export { default as CalendarModal } from '../components/common/CalendarModal';
|
||||||
export { default as DeleteMessageModal } from '../components/common/DeleteMessageModal';
|
export { default as DeleteMessageModal } from '../components/common/DeleteMessageModal';
|
||||||
export { default as PinMessageModal } from '../components/common/PinMessageModal';
|
export { default as PinMessageModal } from '../components/common/PinMessageModal';
|
||||||
|
|||||||
@ -96,7 +96,7 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
|
|||||||
const {
|
const {
|
||||||
deleteMessages,
|
deleteMessages,
|
||||||
deleteScheduledMessages,
|
deleteScheduledMessages,
|
||||||
reportMessages,
|
reportChannelSpam,
|
||||||
deleteChatMember,
|
deleteChatMember,
|
||||||
updateChatMemberBannedRights,
|
updateChatMemberBannedRights,
|
||||||
closeDeleteMessageModal,
|
closeDeleteMessageModal,
|
||||||
@ -231,10 +231,10 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
|
|||||||
if (isSchedule) {
|
if (isSchedule) {
|
||||||
deleteScheduledMessages({ messageIds });
|
deleteScheduledMessages({ messageIds });
|
||||||
} else if (!isOwn && (chosenSpanOption || chosenDeleteOption || chosenBanOption) && (isGroup || isSuperGroup)) {
|
} else if (!isOwn && (chosenSpanOption || chosenDeleteOption || chosenBanOption) && (isGroup || isSuperGroup)) {
|
||||||
if (chosenSpanOption) {
|
if (chosenSpanOption && sender) {
|
||||||
const filteredMessageIdList = filterMessageIdByUserId(chosenSpanOption, messageIdList!);
|
const filteredMessageIdList = filterMessageIdByUserId(chosenSpanOption, messageIdList!);
|
||||||
if (filteredMessageIdList && filteredMessageIdList.length) {
|
if (filteredMessageIdList && filteredMessageIdList.length) {
|
||||||
reportMessages({ messageIds: filteredMessageIdList, reason: 'spam', description: '' });
|
reportChannelSpam({ participantId: sender.id, chatId: chat.id, messageIds: filteredMessageIdList });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,6 @@ import { getActions } from '../../global';
|
|||||||
|
|
||||||
import type { ApiPhoto, ApiReportReason } from '../../api/types';
|
import type { ApiPhoto, ApiReportReason } from '../../api/types';
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
|
||||||
|
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
|
|
||||||
@ -17,55 +15,28 @@ import RadioGroup from '../ui/RadioGroup';
|
|||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
subject?: 'peer' | 'messages' | 'media' | 'story';
|
|
||||||
peerId?: string;
|
peerId?: string;
|
||||||
photo?: ApiPhoto;
|
photo?: ApiPhoto;
|
||||||
messageIds?: number[];
|
|
||||||
storyId?: number;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onCloseAnimationEnd?: () => void;
|
onCloseAnimationEnd?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ReportModal: FC<OwnProps> = ({
|
const ReportAvatarModal: FC<OwnProps> = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
subject = 'messages',
|
|
||||||
peerId,
|
peerId,
|
||||||
photo,
|
photo,
|
||||||
messageIds,
|
|
||||||
storyId,
|
|
||||||
onClose,
|
onClose,
|
||||||
onCloseAnimationEnd,
|
onCloseAnimationEnd,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const { reportProfilePhoto } = getActions();
|
||||||
reportMessages,
|
|
||||||
reportPeer,
|
|
||||||
reportProfilePhoto,
|
|
||||||
reportStory,
|
|
||||||
exitMessageSelectMode,
|
|
||||||
} = getActions();
|
|
||||||
|
|
||||||
const [selectedReason, setSelectedReason] = useState<ApiReportReason>('spam');
|
const [selectedReason, setSelectedReason] = useState<ApiReportReason>('spam');
|
||||||
const [description, setDescription] = useState('');
|
const [description, setDescription] = useState('');
|
||||||
|
|
||||||
const handleReport = useLastCallback(() => {
|
const handleReport = useLastCallback(() => {
|
||||||
switch (subject) {
|
reportProfilePhoto({
|
||||||
case 'messages':
|
chatId: peerId, photo, reason: selectedReason, description,
|
||||||
reportMessages({ messageIds: messageIds!, reason: selectedReason, description });
|
});
|
||||||
exitMessageSelectMode();
|
|
||||||
break;
|
|
||||||
case 'peer':
|
|
||||||
reportPeer({ chatId: peerId, reason: selectedReason, description });
|
|
||||||
break;
|
|
||||||
case 'media':
|
|
||||||
reportProfilePhoto({
|
|
||||||
chatId: peerId, photo, reason: selectedReason, description,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'story':
|
|
||||||
reportStory({
|
|
||||||
peerId: peerId!, storyId: storyId!, reason: selectedReason, description,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
onClose();
|
onClose();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -90,18 +61,11 @@ const ReportModal: FC<OwnProps> = ({
|
|||||||
{ value: 'other', label: lang('lng_report_reason_other') },
|
{ value: 'other', label: lang('lng_report_reason_other') },
|
||||||
], [lang]);
|
], [lang]);
|
||||||
|
|
||||||
if (
|
if (!peerId || !photo) {
|
||||||
(subject === 'messages' && !messageIds)
|
|
||||||
|| (subject === 'peer' && !peerId)
|
|
||||||
|| (subject === 'media' && (!peerId || !photo))
|
|
||||||
|| (subject === 'story' && (!storyId || !peerId))
|
|
||||||
) {
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = subject === 'messages'
|
const title = lang('ReportPeer.Report');
|
||||||
? lang('lng_report_message_title')
|
|
||||||
: lang('ReportPeer.Report');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@ -109,7 +73,7 @@ const ReportModal: FC<OwnProps> = ({
|
|||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onEnter={isOpen ? handleReport : undefined}
|
onEnter={isOpen ? handleReport : undefined}
|
||||||
onCloseAnimationEnd={onCloseAnimationEnd}
|
onCloseAnimationEnd={onCloseAnimationEnd}
|
||||||
className={buildClassName('narrow', subject === 'story' && 'component-theme-dark')}
|
className="narrow"
|
||||||
title={title}
|
title={title}
|
||||||
>
|
>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
@ -133,4 +97,4 @@ const ReportModal: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(ReportModal);
|
export default memo(ReportAvatarModal);
|
||||||
@ -19,6 +19,7 @@ import MonkeyIdle from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyIdle.tgs
|
|||||||
import MonkeyPeek from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyPeek.tgs';
|
import MonkeyPeek from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyPeek.tgs';
|
||||||
import MonkeyTracking from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyTracking.tgs';
|
import MonkeyTracking from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyTracking.tgs';
|
||||||
import ReadTime from '../../../assets/tgs/ReadTime.tgs';
|
import ReadTime from '../../../assets/tgs/ReadTime.tgs';
|
||||||
|
import Report from '../../../assets/tgs/Report.tgs';
|
||||||
import Congratulations from '../../../assets/tgs/settings/Congratulations.tgs';
|
import Congratulations from '../../../assets/tgs/settings/Congratulations.tgs';
|
||||||
import DiscussionGroups from '../../../assets/tgs/settings/DiscussionGroupsDucks.tgs';
|
import DiscussionGroups from '../../../assets/tgs/settings/DiscussionGroupsDucks.tgs';
|
||||||
import Experimental from '../../../assets/tgs/settings/Experimental.tgs';
|
import Experimental from '../../../assets/tgs/settings/Experimental.tgs';
|
||||||
@ -62,4 +63,5 @@ export const LOCAL_TGS_URLS = {
|
|||||||
Fragment,
|
Fragment,
|
||||||
StarReactionEffect,
|
StarReactionEffect,
|
||||||
StarReaction,
|
StarReaction,
|
||||||
|
Report,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -69,7 +69,6 @@ import DeleteChatModal from '../../common/DeleteChatModal';
|
|||||||
import FullNameTitle from '../../common/FullNameTitle';
|
import FullNameTitle from '../../common/FullNameTitle';
|
||||||
import StarIcon from '../../common/icons/StarIcon';
|
import StarIcon from '../../common/icons/StarIcon';
|
||||||
import LastMessageMeta from '../../common/LastMessageMeta';
|
import LastMessageMeta from '../../common/LastMessageMeta';
|
||||||
import ReportModal from '../../common/ReportModal';
|
|
||||||
import ListItem from '../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
import ChatFolderModal from '../ChatFolderModal.async';
|
import ChatFolderModal from '../ChatFolderModal.async';
|
||||||
import MuteChatModal from '../MuteChatModal.async';
|
import MuteChatModal from '../MuteChatModal.async';
|
||||||
@ -170,17 +169,16 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
openForumPanel,
|
openForumPanel,
|
||||||
closeForumPanel,
|
closeForumPanel,
|
||||||
setShouldCloseRightColumn,
|
setShouldCloseRightColumn,
|
||||||
|
reportMessages,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
|
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
|
||||||
const [isMuteModalOpen, openMuteModal, closeMuteModal] = useFlag();
|
const [isMuteModalOpen, openMuteModal, closeMuteModal] = useFlag();
|
||||||
const [isChatFolderModalOpen, openChatFolderModal, closeChatFolderModal] = useFlag();
|
const [isChatFolderModalOpen, openChatFolderModal, closeChatFolderModal] = useFlag();
|
||||||
const [isReportModalOpen, openReportModal, closeReportModal] = useFlag();
|
|
||||||
const [shouldRenderDeleteModal, markRenderDeleteModal, unmarkRenderDeleteModal] = useFlag();
|
const [shouldRenderDeleteModal, markRenderDeleteModal, unmarkRenderDeleteModal] = useFlag();
|
||||||
const [shouldRenderMuteModal, markRenderMuteModal, unmarkRenderMuteModal] = useFlag();
|
const [shouldRenderMuteModal, markRenderMuteModal, unmarkRenderMuteModal] = useFlag();
|
||||||
const [shouldRenderChatFolderModal, markRenderChatFolderModal, unmarkRenderChatFolderModal] = useFlag();
|
const [shouldRenderChatFolderModal, markRenderChatFolderModal, unmarkRenderChatFolderModal] = useFlag();
|
||||||
const [shouldRenderReportModal, markRenderReportModal, unmarkRenderReportModal] = useFlag();
|
|
||||||
|
|
||||||
const { isForum, isForumAsMessages } = chat || {};
|
const { isForum, isForumAsMessages } = chat || {};
|
||||||
|
|
||||||
@ -275,8 +273,8 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleReport = useLastCallback(() => {
|
const handleReport = useLastCallback(() => {
|
||||||
markRenderReportModal();
|
if (!chat) return;
|
||||||
openReportModal();
|
reportMessages({ chatId: chat.id, messageIds: [] });
|
||||||
});
|
});
|
||||||
|
|
||||||
const contextActions = useChatContextActions({
|
const contextActions = useChatContextActions({
|
||||||
@ -432,15 +430,6 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{shouldRenderReportModal && (
|
|
||||||
<ReportModal
|
|
||||||
isOpen={isReportModalOpen}
|
|
||||||
onClose={closeReportModal}
|
|
||||||
onCloseAnimationEnd={unmarkRenderReportModal}
|
|
||||||
peerId={chatId}
|
|
||||||
subject="peer"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -53,7 +53,7 @@ import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
|||||||
import { dispatchPriorityPlaybackEvent } from '../../hooks/usePriorityPlaybackCheck';
|
import { dispatchPriorityPlaybackEvent } from '../../hooks/usePriorityPlaybackCheck';
|
||||||
import { useMediaProps } from './hooks/useMediaProps';
|
import { useMediaProps } from './hooks/useMediaProps';
|
||||||
|
|
||||||
import ReportModal from '../common/ReportModal';
|
import ReportAvatarModal from '../common/ReportAvatarModal';
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import ShowTransition from '../ui/ShowTransition';
|
import ShowTransition from '../ui/ShowTransition';
|
||||||
import Transition from '../ui/Transition';
|
import Transition from '../ui/Transition';
|
||||||
@ -446,10 +446,9 @@ const MediaViewer = ({
|
|||||||
onCloseMediaViewer={handleClose}
|
onCloseMediaViewer={handleClose}
|
||||||
onForward={handleForward}
|
onForward={handleForward}
|
||||||
/>
|
/>
|
||||||
<ReportModal
|
<ReportAvatarModal
|
||||||
isOpen={isReportAvatarModalOpen}
|
isOpen={isReportAvatarModalOpen}
|
||||||
onClose={closeReportAvatarModal}
|
onClose={closeReportAvatarModal}
|
||||||
subject="media"
|
|
||||||
photo={avatar}
|
photo={avatar}
|
||||||
peerId={avatarOwner?.id}
|
peerId={avatarOwner?.id}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -94,7 +94,7 @@ const DeleteSelectedMessageModal: FC<OwnProps & StateProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
deleteMessages,
|
deleteMessages,
|
||||||
reportMessages,
|
reportChannelSpam,
|
||||||
deleteChatMember,
|
deleteChatMember,
|
||||||
deleteScheduledMessages,
|
deleteScheduledMessages,
|
||||||
exitMessageSelectMode,
|
exitMessageSelectMode,
|
||||||
@ -227,6 +227,18 @@ const DeleteSelectedMessageModal: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleReportSpam = useLastCallback((userMessagesMap: Record<string, number[]>) => {
|
||||||
|
Object.entries(userMessagesMap).forEach(([userId, messageIdList]) => {
|
||||||
|
if (messageIdList.length) {
|
||||||
|
reportChannelSpam({
|
||||||
|
participantId: userId,
|
||||||
|
chatId: chat!.id,
|
||||||
|
messageIds: messageIdList,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const handleDeleteMessages = useLastCallback((filteredMessageIdList: number[]) => {
|
const handleDeleteMessages = useLastCallback((filteredMessageIdList: number[]) => {
|
||||||
if (filteredMessageIdList && filteredMessageIdList.length) {
|
if (filteredMessageIdList && filteredMessageIdList.length) {
|
||||||
deleteMessages({ messageIds: filteredMessageIdList, shouldDeleteForAll: true });
|
deleteMessages({ messageIds: filteredMessageIdList, shouldDeleteForAll: true });
|
||||||
@ -257,10 +269,18 @@ const DeleteSelectedMessageModal: FC<OwnProps & StateProps> = ({
|
|||||||
} else if (!isSenderOwner && shouldShowOptions) {
|
} else if (!isSenderOwner && shouldShowOptions) {
|
||||||
if (chosenSpanOption) {
|
if (chosenSpanOption) {
|
||||||
const userIdList = chosenSpanOption.filter((option) => !Number.isNaN(Number(option)));
|
const userIdList = chosenSpanOption.filter((option) => !Number.isNaN(Number(option)));
|
||||||
const filteredMessageIdList = filterMessageIdByUserId(userIdList, selectedMessageIds!);
|
const userMessagesMap = selectedMessageIds!.reduce<Record<string, number[]>>((acc, msgId) => {
|
||||||
if (filteredMessageIdList?.length) {
|
const sender = selectSenderFromMessage(getGlobal(), chat.id, msgId);
|
||||||
reportMessages({ messageIds: filteredMessageIdList, reason: 'spam', description: '' });
|
if (sender && userIdList.includes(sender.id)) {
|
||||||
}
|
if (!acc[sender.id]) {
|
||||||
|
acc[sender.id] = [];
|
||||||
|
}
|
||||||
|
acc[sender.id].push(Number(msgId));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
handleReportSpam(userMessagesMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chosenDeleteOption) {
|
if (chosenDeleteOption) {
|
||||||
|
|||||||
@ -49,7 +49,6 @@ import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
|
|||||||
import useShowTransitionDeprecated from '../../hooks/useShowTransitionDeprecated';
|
import useShowTransitionDeprecated from '../../hooks/useShowTransitionDeprecated';
|
||||||
|
|
||||||
import DeleteChatModal from '../common/DeleteChatModal';
|
import DeleteChatModal from '../common/DeleteChatModal';
|
||||||
import ReportModal from '../common/ReportModal';
|
|
||||||
import MuteChatModal from '../left/MuteChatModal.async';
|
import MuteChatModal from '../left/MuteChatModal.async';
|
||||||
import Menu from '../ui/Menu';
|
import Menu from '../ui/Menu';
|
||||||
import MenuItem from '../ui/MenuItem';
|
import MenuItem from '../ui/MenuItem';
|
||||||
@ -109,8 +108,8 @@ type StateProps = {
|
|||||||
isForum?: boolean;
|
isForum?: boolean;
|
||||||
isForumAsMessages?: true;
|
isForumAsMessages?: true;
|
||||||
canAddContact?: boolean;
|
canAddContact?: boolean;
|
||||||
canReportChat?: boolean;
|
|
||||||
canDeleteChat?: boolean;
|
canDeleteChat?: boolean;
|
||||||
|
canReportChat?: boolean;
|
||||||
canGift?: boolean;
|
canGift?: boolean;
|
||||||
canCreateTopic?: boolean;
|
canCreateTopic?: boolean;
|
||||||
canEditTopic?: boolean;
|
canEditTopic?: boolean;
|
||||||
@ -143,6 +142,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
isChatInfoShown,
|
isChatInfoShown,
|
||||||
canStartBot,
|
canStartBot,
|
||||||
canSubscribe,
|
canSubscribe,
|
||||||
|
canReportChat,
|
||||||
canSearch,
|
canSearch,
|
||||||
canCall,
|
canCall,
|
||||||
canMute,
|
canMute,
|
||||||
@ -156,7 +156,6 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
chat,
|
chat,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
isMuted,
|
isMuted,
|
||||||
canReportChat,
|
|
||||||
canDeleteChat,
|
canDeleteChat,
|
||||||
canGift,
|
canGift,
|
||||||
hasLinkedChat,
|
hasLinkedChat,
|
||||||
@ -203,13 +202,13 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
unblockUser,
|
unblockUser,
|
||||||
setViewForumAsMessages,
|
setViewForumAsMessages,
|
||||||
openBoostModal,
|
openBoostModal,
|
||||||
|
reportMessages,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(true);
|
const [isMenuOpen, setIsMenuOpen] = useState(true);
|
||||||
const [shouldCloseFast, setShouldCloseFast] = useState(false);
|
const [shouldCloseFast, setShouldCloseFast] = useState(false);
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
const [isReportModalOpen, setIsReportModalOpen] = useState(false);
|
|
||||||
const [isMuteModalOpen, setIsMuteModalOpen] = useState(false);
|
const [isMuteModalOpen, setIsMuteModalOpen] = useState(false);
|
||||||
const [shouldRenderMuteModal, markRenderMuteModal, unmarkRenderMuteModal] = useFlag();
|
const [shouldRenderMuteModal, markRenderMuteModal, unmarkRenderMuteModal] = useFlag();
|
||||||
const { x, y } = anchor;
|
const { x, y } = anchor;
|
||||||
@ -219,18 +218,14 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
(!isChatInfoShown && isForum) ? true : undefined, CLOSE_MENU_ANIMATION_DURATION,
|
(!isChatInfoShown && isForum) ? true : undefined, CLOSE_MENU_ANIMATION_DURATION,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleReport = useLastCallback(() => {
|
const closeMuteModal = useLastCallback(() => {
|
||||||
setIsMenuOpen(false);
|
setIsMuteModalOpen(false);
|
||||||
setIsReportModalOpen(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
const closeReportModal = useLastCallback(() => {
|
|
||||||
setIsReportModalOpen(false);
|
|
||||||
onClose();
|
onClose();
|
||||||
});
|
});
|
||||||
|
|
||||||
const closeMuteModal = useLastCallback(() => {
|
const handleReport = useLastCallback(() => {
|
||||||
setIsMuteModalOpen(false);
|
setIsMenuOpen(false);
|
||||||
|
reportMessages({ chatId, messageIds: [] });
|
||||||
onClose();
|
onClose();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -725,14 +720,6 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
chatId={chat.id}
|
chatId={chat.id}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{canReportChat && chat?.id && (
|
|
||||||
<ReportModal
|
|
||||||
isOpen={isReportModalOpen}
|
|
||||||
onClose={closeReportModal}
|
|
||||||
subject="peer"
|
|
||||||
peerId={chat.id}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
@ -749,8 +736,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const canAddContact = user && getCanAddContact(user);
|
const canAddContact = user && getCanAddContact(user);
|
||||||
const isMainThread = threadId === MAIN_THREAD_ID;
|
const isMainThread = threadId === MAIN_THREAD_ID;
|
||||||
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
||||||
const canReportChat = isMainThread && (isChatChannel(chat) || isChatGroup(chat) || (user && !user.isSelf));
|
|
||||||
const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global) || {};
|
const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global) || {};
|
||||||
|
const canReportChat = isMainThread && !user && (isChatChannel(chat) || isChatGroup(chat));
|
||||||
|
|
||||||
const chatBot = !isSystemBot(chatId) ? selectBot(global, chatId) : undefined;
|
const chatBot = !isSystemBot(chatId) ? selectBot(global, chatId) : undefined;
|
||||||
const userFullInfo = isPrivate ? selectUserFullInfo(global, chatId) : undefined;
|
const userFullInfo = isPrivate ? selectUserFullInfo(global, chatId) : undefined;
|
||||||
@ -778,8 +765,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isForum: chat?.isForum,
|
isForum: chat?.isForum,
|
||||||
isForumAsMessages: chat?.isForumAsMessages,
|
isForumAsMessages: chat?.isForumAsMessages,
|
||||||
canAddContact,
|
canAddContact,
|
||||||
canReportChat,
|
|
||||||
canDeleteChat: getCanDeleteChat(chat),
|
canDeleteChat: getCanDeleteChat(chat),
|
||||||
|
canReportChat,
|
||||||
canGift,
|
canGift,
|
||||||
hasLinkedChat: Boolean(chatFullInfo?.linkedChatId),
|
hasLinkedChat: Boolean(chatFullInfo?.linkedChatId),
|
||||||
botCommands: chatBot ? userFullInfo?.botInfo?.commands : undefined,
|
botCommands: chatBot ? userFullInfo?.botInfo?.commands : undefined,
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { FC } from '../../lib/teact/teact';
|
|||||||
import React, { memo, useEffect } from '../../lib/teact/teact';
|
import React, { memo, useEffect } from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
|
import type { ApiChat } from '../../api/types';
|
||||||
import type { MessageListType } from '../../global/types';
|
import type { MessageListType } from '../../global/types';
|
||||||
import type { IconName } from '../../types/icons';
|
import type { IconName } from '../../types/icons';
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ import {
|
|||||||
selectCanDeleteSelectedMessages,
|
selectCanDeleteSelectedMessages,
|
||||||
selectCanDownloadSelectedMessages,
|
selectCanDownloadSelectedMessages,
|
||||||
selectCanForwardMessages,
|
selectCanForwardMessages,
|
||||||
selectCanReportSelectedMessages,
|
selectCanReportSelectedMessages, selectCurrentChat,
|
||||||
selectCurrentMessageList, selectHasProtectedMessage,
|
selectCurrentMessageList, selectHasProtectedMessage,
|
||||||
selectSelectedMessagesCount,
|
selectSelectedMessagesCount,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
@ -23,7 +24,6 @@ import useOldLang from '../../hooks/useOldLang';
|
|||||||
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
||||||
import useCopySelectedMessages from './hooks/useCopySelectedMessages';
|
import useCopySelectedMessages from './hooks/useCopySelectedMessages';
|
||||||
|
|
||||||
import ReportModal from '../common/ReportModal';
|
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import DeleteSelectedMessageModal from './DeleteSelectedMessageModal';
|
import DeleteSelectedMessageModal from './DeleteSelectedMessageModal';
|
||||||
|
|
||||||
@ -36,6 +36,7 @@ export type OwnProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
|
chat?: ApiChat;
|
||||||
isSchedule: boolean;
|
isSchedule: boolean;
|
||||||
selectedMessagesCount?: number;
|
selectedMessagesCount?: number;
|
||||||
canDeleteMessages?: boolean;
|
canDeleteMessages?: boolean;
|
||||||
@ -48,6 +49,7 @@ type StateProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
||||||
|
chat,
|
||||||
canPost,
|
canPost,
|
||||||
isActive,
|
isActive,
|
||||||
messageListType,
|
messageListType,
|
||||||
@ -67,11 +69,11 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
|||||||
downloadSelectedMessages,
|
downloadSelectedMessages,
|
||||||
copySelectedMessages,
|
copySelectedMessages,
|
||||||
showNotification,
|
showNotification,
|
||||||
|
reportMessages,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
|
|
||||||
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
|
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
|
||||||
const [isReportModalOpen, openReportModal, closeReportModal] = useFlag();
|
|
||||||
|
|
||||||
useCopySelectedMessages(isActive);
|
useCopySelectedMessages(isActive);
|
||||||
|
|
||||||
@ -80,7 +82,7 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return isActive && !isDeleteModalOpen && !isReportModalOpen && !isAnyModalOpen
|
return isActive && !isDeleteModalOpen && !isAnyModalOpen
|
||||||
? captureKeyboardListeners({
|
? captureKeyboardListeners({
|
||||||
onBackspace: canDeleteMessages ? openDeleteModal : undefined,
|
onBackspace: canDeleteMessages ? openDeleteModal : undefined,
|
||||||
onDelete: canDeleteMessages ? openDeleteModal : undefined,
|
onDelete: canDeleteMessages ? openDeleteModal : undefined,
|
||||||
@ -88,7 +90,7 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
|||||||
})
|
})
|
||||||
: undefined;
|
: undefined;
|
||||||
}, [
|
}, [
|
||||||
isActive, isDeleteModalOpen, isReportModalOpen, openDeleteModal, handleExitMessageSelectMode, isAnyModalOpen,
|
isActive, isDeleteModalOpen, openDeleteModal, handleExitMessageSelectMode, isAnyModalOpen,
|
||||||
canDeleteMessages,
|
canDeleteMessages,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -110,6 +112,15 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const formattedMessagesCount = lang('VoiceOver.Chat.MessagesSelected', renderingSelectedMessagesCount, 'i');
|
const formattedMessagesCount = lang('VoiceOver.Chat.MessagesSelected', renderingSelectedMessagesCount, 'i');
|
||||||
|
|
||||||
|
const openMessageReport = useLastCallback(() => {
|
||||||
|
if (!selectedMessageIds || !chat) return;
|
||||||
|
reportMessages({
|
||||||
|
chatId: chat.id,
|
||||||
|
messageIds: selectedMessageIds,
|
||||||
|
});
|
||||||
|
exitMessageSelectMode();
|
||||||
|
});
|
||||||
|
|
||||||
const className = buildClassName(
|
const className = buildClassName(
|
||||||
'MessageSelectToolbar',
|
'MessageSelectToolbar',
|
||||||
canPost && 'with-composer',
|
canPost && 'with-composer',
|
||||||
@ -160,7 +171,7 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
{canReportMessages && (
|
{canReportMessages && (
|
||||||
renderButton('flag', lang('Conversation.ReportMessages'), openReportModal)
|
renderButton('flag', lang('Conversation.ReportMessages'), openMessageReport)
|
||||||
)}
|
)}
|
||||||
{canDownloadMessages && !hasProtectedMessage && (
|
{canDownloadMessages && !hasProtectedMessage && (
|
||||||
renderButton('download', lang('lng_media_download'), handleDownload)
|
renderButton('download', lang('lng_media_download'), handleDownload)
|
||||||
@ -181,11 +192,6 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
|||||||
onClose={closeDeleteModal}
|
onClose={closeDeleteModal}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ReportModal
|
|
||||||
isOpen={isReportModalOpen}
|
|
||||||
onClose={closeReportModal}
|
|
||||||
messageIds={selectedMessageIds}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -193,6 +199,7 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const tabState = selectTabState(global);
|
const tabState = selectTabState(global);
|
||||||
|
const chat = selectCurrentChat(global);
|
||||||
const { type: messageListType, chatId } = selectCurrentMessageList(global) || {};
|
const { type: messageListType, chatId } = selectCurrentMessageList(global) || {};
|
||||||
const isSchedule = messageListType === 'scheduled';
|
const isSchedule = messageListType === 'scheduled';
|
||||||
const { canDelete } = selectCanDeleteSelectedMessages(global);
|
const { canDelete } = selectCanDeleteSelectedMessages(global);
|
||||||
@ -203,9 +210,10 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const canForward = !isSchedule && chatId ? selectCanForwardMessages(global, chatId, selectedMessageIds) : false;
|
const canForward = !isSchedule && chatId ? selectCanForwardMessages(global, chatId, selectedMessageIds) : false;
|
||||||
const isShareMessageModalOpen = tabState.isShareMessageModalShown;
|
const isShareMessageModalOpen = tabState.isShareMessageModalShown;
|
||||||
const isAnyModalOpen = Boolean(isShareMessageModalOpen || tabState.requestedDraft
|
const isAnyModalOpen = Boolean(isShareMessageModalOpen || tabState.requestedDraft
|
||||||
|| tabState.requestedAttachBotInChat || tabState.requestedAttachBotInstall);
|
|| tabState.requestedAttachBotInChat || tabState.requestedAttachBotInstall || tabState.reportModal);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
chat,
|
||||||
isSchedule,
|
isSchedule,
|
||||||
selectedMessagesCount: selectSelectedMessagesCount(global),
|
selectedMessagesCount: selectSelectedMessagesCount(global),
|
||||||
canDeleteMessages: canDelete,
|
canDeleteMessages: canDelete,
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { getActions, getGlobal, withGlobal } from '../../../global';
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiAvailableReaction,
|
ApiAvailableReaction,
|
||||||
|
ApiChat,
|
||||||
ApiChatReactions,
|
ApiChatReactions,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiPoll,
|
ApiPoll,
|
||||||
@ -72,7 +73,6 @@ import useSchedule from '../../../hooks/useSchedule';
|
|||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
|
|
||||||
import PinMessageModal from '../../common/PinMessageModal.async';
|
import PinMessageModal from '../../common/PinMessageModal.async';
|
||||||
import ReportModal from '../../common/ReportModal';
|
|
||||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
import MessageContextMenu from './MessageContextMenu';
|
import MessageContextMenu from './MessageContextMenu';
|
||||||
|
|
||||||
@ -94,6 +94,7 @@ type StateProps = {
|
|||||||
threadId?: ThreadId;
|
threadId?: ThreadId;
|
||||||
poll?: ApiPoll;
|
poll?: ApiPoll;
|
||||||
story?: ApiTypeStory;
|
story?: ApiTypeStory;
|
||||||
|
chat?: ApiChat;
|
||||||
availableReactions?: ApiAvailableReaction[];
|
availableReactions?: ApiAvailableReaction[];
|
||||||
topReactions?: ApiReaction[];
|
topReactions?: ApiReaction[];
|
||||||
defaultTagReactions?: ApiReaction[];
|
defaultTagReactions?: ApiReaction[];
|
||||||
@ -169,8 +170,9 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
repliesThreadInfo,
|
repliesThreadInfo,
|
||||||
canUnpin,
|
canUnpin,
|
||||||
canDelete,
|
canDelete,
|
||||||
canReport,
|
|
||||||
canShowReactionsCount,
|
canShowReactionsCount,
|
||||||
|
chat,
|
||||||
|
canReport,
|
||||||
canShowReactionList,
|
canShowReactionList,
|
||||||
canEdit,
|
canEdit,
|
||||||
enabledReactions,
|
enabledReactions,
|
||||||
@ -241,6 +243,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
openDeleteMessageModal,
|
openDeleteMessageModal,
|
||||||
addLocalPaidReaction,
|
addLocalPaidReaction,
|
||||||
openPaidReactionModal,
|
openPaidReactionModal,
|
||||||
|
reportMessages,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
@ -250,7 +253,6 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
className: false,
|
className: false,
|
||||||
});
|
});
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(true);
|
const [isMenuOpen, setIsMenuOpen] = useState(true);
|
||||||
const [isReportModalOpen, setIsReportModalOpen] = useState(false);
|
|
||||||
const [isPinModalOpen, setIsPinModalOpen] = useState(false);
|
const [isPinModalOpen, setIsPinModalOpen] = useState(false);
|
||||||
const [isClosePollDialogOpen, openClosePollDialog, closeClosePollDialog] = useFlag();
|
const [isClosePollDialogOpen, openClosePollDialog, closeClosePollDialog] = useFlag();
|
||||||
const [canQuoteSelection, setCanQuoteSelection] = useState(false);
|
const [canQuoteSelection, setCanQuoteSelection] = useState(false);
|
||||||
@ -366,16 +368,6 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
openDeleteMessageModal({ isSchedule: messageListType === 'scheduled', album, message });
|
openDeleteMessageModal({ isSchedule: messageListType === 'scheduled', album, message });
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleReport = useLastCallback(() => {
|
|
||||||
setIsMenuOpen(false);
|
|
||||||
setIsReportModalOpen(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
const closeReportModal = useLastCallback(() => {
|
|
||||||
setIsReportModalOpen(false);
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
|
|
||||||
const closePinModal = useLastCallback(() => {
|
const closePinModal = useLastCallback(() => {
|
||||||
setIsPinModalOpen(false);
|
setIsPinModalOpen(false);
|
||||||
onClose();
|
onClose();
|
||||||
@ -589,6 +581,15 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const reportMessageIds = useMemo(() => (album ? album.messages : [message]).map(({ id }) => id), [album, message]);
|
const reportMessageIds = useMemo(() => (album ? album.messages : [message]).map(({ id }) => id), [album, message]);
|
||||||
|
|
||||||
|
const handleReport = useLastCallback(() => {
|
||||||
|
if (!chat) return;
|
||||||
|
setIsMenuOpen(false);
|
||||||
|
onClose();
|
||||||
|
reportMessages({
|
||||||
|
chatId: chat.id, messageIds: reportMessageIds,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (noOptions) {
|
if (noOptions) {
|
||||||
closeMenu();
|
closeMenu();
|
||||||
|
|
||||||
@ -622,8 +623,8 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
canReply={canReply}
|
canReply={canReply}
|
||||||
canQuote={canQuoteSelection}
|
canQuote={canQuoteSelection}
|
||||||
canDelete={canDelete}
|
canDelete={canDelete}
|
||||||
canReport={canReport}
|
|
||||||
canPin={canPin}
|
canPin={canPin}
|
||||||
|
canReport={canReport}
|
||||||
repliesThreadInfo={repliesThreadInfo}
|
repliesThreadInfo={repliesThreadInfo}
|
||||||
canUnpin={canUnpin}
|
canUnpin={canUnpin}
|
||||||
canEdit={canEdit}
|
canEdit={canEdit}
|
||||||
@ -683,11 +684,6 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
onShowOriginal={handleShowOriginal}
|
onShowOriginal={handleShowOriginal}
|
||||||
onSelectLanguage={handleSelectLanguage}
|
onSelectLanguage={handleSelectLanguage}
|
||||||
/>
|
/>
|
||||||
<ReportModal
|
|
||||||
isOpen={isReportModalOpen}
|
|
||||||
onClose={closeReportModal}
|
|
||||||
messageIds={reportMessageIds}
|
|
||||||
/>
|
|
||||||
<PinMessageModal
|
<PinMessageModal
|
||||||
isOpen={isPinModalOpen}
|
isOpen={isPinModalOpen}
|
||||||
messageId={message.id}
|
messageId={message.id}
|
||||||
@ -811,17 +807,18 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
threadId,
|
threadId,
|
||||||
|
chat,
|
||||||
availableReactions,
|
availableReactions,
|
||||||
topReactions,
|
topReactions,
|
||||||
defaultTagReactions: defaultTags,
|
defaultTagReactions: defaultTags,
|
||||||
noOptions,
|
noOptions,
|
||||||
|
canReport,
|
||||||
canSendNow: isScheduled,
|
canSendNow: isScheduled,
|
||||||
canReschedule: isScheduled,
|
canReschedule: isScheduled,
|
||||||
canReply: !isPinned && !isScheduled && canReplyGlobally,
|
canReply: !isPinned && !isScheduled && canReplyGlobally,
|
||||||
canPin: !isScheduled && canPin,
|
canPin: !isScheduled && canPin,
|
||||||
canUnpin: !isScheduled && canUnpin,
|
canUnpin: !isScheduled && canUnpin,
|
||||||
canDelete,
|
canDelete,
|
||||||
canReport,
|
|
||||||
canEdit: !isPinned && canEdit,
|
canEdit: !isPinned && canEdit,
|
||||||
canForward: !isScheduled && canForward,
|
canForward: !isScheduled && canForward,
|
||||||
canFaveSticker: !isScheduled && canFaveSticker,
|
canFaveSticker: !isScheduled && canFaveSticker,
|
||||||
|
|||||||
@ -101,8 +101,8 @@ type OwnProps = {
|
|||||||
onUnpin?: NoneToVoidFunction;
|
onUnpin?: NoneToVoidFunction;
|
||||||
onForward?: NoneToVoidFunction;
|
onForward?: NoneToVoidFunction;
|
||||||
onDelete?: NoneToVoidFunction;
|
onDelete?: NoneToVoidFunction;
|
||||||
onReport?: NoneToVoidFunction;
|
|
||||||
onFaveSticker?: NoneToVoidFunction;
|
onFaveSticker?: NoneToVoidFunction;
|
||||||
|
onReport?: NoneToVoidFunction;
|
||||||
onUnfaveSticker?: NoneToVoidFunction;
|
onUnfaveSticker?: NoneToVoidFunction;
|
||||||
onSelect?: NoneToVoidFunction;
|
onSelect?: NoneToVoidFunction;
|
||||||
onSend?: NoneToVoidFunction;
|
onSend?: NoneToVoidFunction;
|
||||||
@ -161,8 +161,8 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
canPin,
|
canPin,
|
||||||
canUnpin,
|
canUnpin,
|
||||||
canDelete,
|
canDelete,
|
||||||
canReport,
|
|
||||||
canForward,
|
canForward,
|
||||||
|
canReport,
|
||||||
canFaveSticker,
|
canFaveSticker,
|
||||||
canUnfaveSticker,
|
canUnfaveSticker,
|
||||||
canCopy,
|
canCopy,
|
||||||
@ -194,8 +194,8 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
onUnpin,
|
onUnpin,
|
||||||
onForward,
|
onForward,
|
||||||
onDelete,
|
onDelete,
|
||||||
onReport,
|
|
||||||
onFaveSticker,
|
onFaveSticker,
|
||||||
|
onReport,
|
||||||
onUnfaveSticker,
|
onUnfaveSticker,
|
||||||
onSelect,
|
onSelect,
|
||||||
onSend,
|
onSend,
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import MapModal from './map/MapModal.async';
|
|||||||
import OneTimeMediaModal from './oneTimeMedia/OneTimeMediaModal.async';
|
import OneTimeMediaModal from './oneTimeMedia/OneTimeMediaModal.async';
|
||||||
import PaidReactionModal from './paidReaction/PaidReactionModal.async';
|
import PaidReactionModal from './paidReaction/PaidReactionModal.async';
|
||||||
import ReportAdModal from './reportAd/ReportAdModal.async';
|
import ReportAdModal from './reportAd/ReportAdModal.async';
|
||||||
|
import ReportModal from './reportModal/ReportModal.async';
|
||||||
import StarsGiftModal from './stars/gift/StarsGiftModal.async';
|
import StarsGiftModal from './stars/gift/StarsGiftModal.async';
|
||||||
import StarsBalanceModal from './stars/StarsBalanceModal.async';
|
import StarsBalanceModal from './stars/StarsBalanceModal.async';
|
||||||
import StarsPaymentModal from './stars/StarsPaymentModal.async';
|
import StarsPaymentModal from './stars/StarsPaymentModal.async';
|
||||||
@ -40,6 +41,7 @@ type ModalKey = keyof Pick<TabState,
|
|||||||
'requestedAttachBotInstall' |
|
'requestedAttachBotInstall' |
|
||||||
'collectibleInfoModal' |
|
'collectibleInfoModal' |
|
||||||
'reportAdModal' |
|
'reportAdModal' |
|
||||||
|
'reportModal' |
|
||||||
'starsBalanceModal' |
|
'starsBalanceModal' |
|
||||||
'starsPayment' |
|
'starsPayment' |
|
||||||
'starsTransactionModal' |
|
'starsTransactionModal' |
|
||||||
@ -75,6 +77,7 @@ const MODALS: ModalRegistry = {
|
|||||||
inviteViaLinkModal: InviteViaLinkModal,
|
inviteViaLinkModal: InviteViaLinkModal,
|
||||||
requestedAttachBotInstall: AttachBotInstallModal,
|
requestedAttachBotInstall: AttachBotInstallModal,
|
||||||
reportAdModal: ReportAdModal,
|
reportAdModal: ReportAdModal,
|
||||||
|
reportModal: ReportModal,
|
||||||
webApps: WebAppModal,
|
webApps: WebAppModal,
|
||||||
collectibleInfoModal: CollectibleInfoModal,
|
collectibleInfoModal: CollectibleInfoModal,
|
||||||
mapModal: MapModal,
|
mapModal: MapModal,
|
||||||
|
|||||||
18
src/components/modals/reportModal/ReportModal.async.tsx
Normal file
18
src/components/modals/reportModal/ReportModal.async.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { OwnProps } from './ReportModal';
|
||||||
|
|
||||||
|
import { Bundles } from '../../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||||
|
|
||||||
|
const ReportModalAsync: FC<OwnProps> = (props) => {
|
||||||
|
const { modal } = props;
|
||||||
|
const ReportModal = useModuleLoader(Bundles.Extra, 'ReportModal', !modal);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return ReportModal ? <ReportModal {...props} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReportModalAsync;
|
||||||
92
src/components/modals/reportModal/ReportModal.module.scss
Normal file
92
src/components/modals/reportModal/ReportModal.module.scss
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
.slide { // Do not remove .slide, identifier used in JS
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root {
|
||||||
|
:global(.modal-dialog) {
|
||||||
|
max-width: 23rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalTitle {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionText {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionButton {
|
||||||
|
padding-block: 0 !important;
|
||||||
|
padding: 0 0 0 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionTitle {
|
||||||
|
color: var(--color-primary);
|
||||||
|
padding-inline: 1rem;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
padding-inline: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 1.1875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hasDepth {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleMultiline > .title {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition {
|
||||||
|
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
||||||
|
transition: height 0.25s ease-in-out;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionInfo {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reportIcon {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
232
src/components/modals/reportModal/ReportModal.tsx
Normal file
232
src/components/modals/reportModal/ReportModal.tsx
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
import type { ChangeEvent } from 'react';
|
||||||
|
import React, {
|
||||||
|
memo, useEffect, useMemo, useRef, useState,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
|
import type { TabState } from '../../../global/types';
|
||||||
|
|
||||||
|
import { requestMeasure, requestMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
|
||||||
|
import Icon from '../../common/icons/Icon';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
import ListItem from '../../ui/ListItem';
|
||||||
|
import Modal from '../../ui/Modal';
|
||||||
|
import TextArea from '../../ui/TextArea';
|
||||||
|
import Transition, { ACTIVE_SLIDE_CLASS_NAME, TO_SLIDE_CLASS_NAME } from '../../ui/Transition';
|
||||||
|
|
||||||
|
import styles from './ReportModal.module.scss';
|
||||||
|
|
||||||
|
const MAX_DESCRIPTION = 512;
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
modal: TabState['reportModal'];
|
||||||
|
};
|
||||||
|
|
||||||
|
const ReportModal = ({
|
||||||
|
modal,
|
||||||
|
}: OwnProps) => {
|
||||||
|
const {
|
||||||
|
reportMessages, reportStory, closeReportModal, openPreviousReportModal,
|
||||||
|
} = getActions();
|
||||||
|
const lang = useOldLang();
|
||||||
|
const isOpen = Boolean(modal);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const transitionRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const [text, setText] = useState('');
|
||||||
|
|
||||||
|
const handleOptionClick = useLastCallback((e, option: string) => {
|
||||||
|
const {
|
||||||
|
messageIds, subject, peerId, chatId,
|
||||||
|
} = modal!;
|
||||||
|
if (!messageIds) return;
|
||||||
|
switch (subject) {
|
||||||
|
case 'message':
|
||||||
|
reportMessages({ chatId: chatId!, messageIds, option });
|
||||||
|
break;
|
||||||
|
case 'story':
|
||||||
|
reportStory({
|
||||||
|
storyId: messageIds[0], peerId: peerId!, option,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [renderingSection, renderingDepth] = useMemo(() => {
|
||||||
|
if (!modal) return [undefined, 0];
|
||||||
|
const sectionDepth = modal.sections.length - 1;
|
||||||
|
return [modal?.sections[sectionDepth], sectionDepth];
|
||||||
|
}, [modal]);
|
||||||
|
|
||||||
|
const handleBackClick = useLastCallback(() => {
|
||||||
|
openPreviousReportModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCloseClick = useLastCallback(() => {
|
||||||
|
closeReportModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
const header = useMemo(() => {
|
||||||
|
if (!modal) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasSubtitle = Boolean(renderingSection?.subtitle);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="modal-header-condensed">
|
||||||
|
{renderingDepth ? (
|
||||||
|
<Button
|
||||||
|
round
|
||||||
|
color="translucent"
|
||||||
|
size="smaller"
|
||||||
|
ariaLabel={lang('Back')}
|
||||||
|
onClick={handleBackClick}
|
||||||
|
>
|
||||||
|
<Icon name="arrow-left" />
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
round
|
||||||
|
color="translucent"
|
||||||
|
size="smaller"
|
||||||
|
ariaLabel={lang('Close')}
|
||||||
|
onClick={handleCloseClick}
|
||||||
|
>
|
||||||
|
<Icon name="close" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<div className={buildClassName('modal-title', styles.modalTitle, hasSubtitle && styles.titleMultiline)}>
|
||||||
|
<h3 className={buildClassName(styles.title, renderingDepth && styles.hasDepth)}>
|
||||||
|
{renderingSection?.options
|
||||||
|
? lang(modal?.subject === 'story' ? 'ReportStory' : 'Report') : renderingSection?.title}
|
||||||
|
</h3>
|
||||||
|
{hasSubtitle && (
|
||||||
|
<span className={styles.subtitle}>{renderingSection.subtitle}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [lang, modal, renderingDepth, renderingSection?.options, renderingSection?.subtitle, renderingSection?.title]);
|
||||||
|
|
||||||
|
const handleTextChange = useLastCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
setText(e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!modal) return;
|
||||||
|
const slide = document.querySelector<HTMLElement>(`.${ACTIVE_SLIDE_CLASS_NAME} > .${styles.slide}`);
|
||||||
|
if (!slide) return;
|
||||||
|
|
||||||
|
const height = slide.scrollHeight;
|
||||||
|
requestMutation(() => {
|
||||||
|
transitionRef.current!.style.height = `${height}px`;
|
||||||
|
});
|
||||||
|
}, [modal]);
|
||||||
|
|
||||||
|
const handleAnimationStart = useLastCallback(() => {
|
||||||
|
const slide = document.querySelector<HTMLElement>(`.${TO_SLIDE_CLASS_NAME} > .${styles.slide}`)!;
|
||||||
|
|
||||||
|
requestMeasure(() => {
|
||||||
|
const height = slide.scrollHeight;
|
||||||
|
requestMutation(() => {
|
||||||
|
transitionRef.current!.style.height = `${height}px`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeReportMessageModalHandler = useLastCallback(() => {
|
||||||
|
setText('');
|
||||||
|
closeReportModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendMessageReportHandler = useLastCallback(() => {
|
||||||
|
const {
|
||||||
|
messageIds, subject, peerId, chatId,
|
||||||
|
} = modal!;
|
||||||
|
switch (subject) {
|
||||||
|
case 'message':
|
||||||
|
reportMessages({
|
||||||
|
chatId: chatId!, messageIds, option: renderingSection?.option, description: text,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'story':
|
||||||
|
reportStory({
|
||||||
|
storyId: messageIds?.[0], peerId: peerId!, option: renderingSection?.option, description: text,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
closeReportMessageModalHandler();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
header={header}
|
||||||
|
onClose={closeReportMessageModalHandler}
|
||||||
|
className={buildClassName(styles.root, modal?.subject === 'story' && 'component-theme-dark')}
|
||||||
|
>
|
||||||
|
<Transition
|
||||||
|
name="slide"
|
||||||
|
className={styles.transition}
|
||||||
|
ref={transitionRef}
|
||||||
|
activeKey={renderingDepth}
|
||||||
|
onStart={handleAnimationStart}
|
||||||
|
>
|
||||||
|
<div className={styles.slide}>
|
||||||
|
{renderingSection?.options
|
||||||
|
? <h3 className={styles.sectionTitle}>{renderingSection?.title}</h3> : undefined}
|
||||||
|
{renderingSection?.options?.map((option) => (
|
||||||
|
<ListItem
|
||||||
|
narrow
|
||||||
|
secondaryIcon="next"
|
||||||
|
className={styles.option}
|
||||||
|
buttonClassName={styles.optionButton}
|
||||||
|
clickArg={option.option}
|
||||||
|
onClick={handleOptionClick}
|
||||||
|
>
|
||||||
|
<div className={styles.optionText}>{option.text}</div>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
{renderingSection?.option ? (
|
||||||
|
<div className={styles.block}>
|
||||||
|
<AnimatedIconWithPreview
|
||||||
|
tgsUrl={LOCAL_TGS_URLS.Report}
|
||||||
|
size={150}
|
||||||
|
className={styles.reportIcon}
|
||||||
|
nonInteractive
|
||||||
|
forceAlways
|
||||||
|
/>
|
||||||
|
<TextArea
|
||||||
|
id="option"
|
||||||
|
className={styles.optionInfo}
|
||||||
|
label={renderingSection.isOptional ? lang('Report2CommentOptional') : lang('Report2Comment')}
|
||||||
|
onChange={handleTextChange}
|
||||||
|
value={text}
|
||||||
|
maxLength={MAX_DESCRIPTION}
|
||||||
|
maxLengthIndicator={(MAX_DESCRIPTION - text.length).toString()}
|
||||||
|
noReplaceNewlines
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="smaller"
|
||||||
|
onClick={sendMessageReportHandler}
|
||||||
|
disabled={!renderingSection.isOptional ? !text.length : undefined}
|
||||||
|
>{lang('ReportSend')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : undefined}
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ReportModal);
|
||||||
@ -20,13 +20,13 @@ import { disableDirectTextInput, enableDirectTextInput } from '../../util/direct
|
|||||||
import { animateClosing, animateOpening } from './helpers/ghostAnimation';
|
import { animateClosing, animateOpening } from './helpers/ghostAnimation';
|
||||||
|
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
||||||
import { dispatchPriorityPlaybackEvent } from '../../hooks/usePriorityPlaybackCheck';
|
import { dispatchPriorityPlaybackEvent } from '../../hooks/usePriorityPlaybackCheck';
|
||||||
import useSlideSizes from './hooks/useSlideSizes';
|
import useSlideSizes from './hooks/useSlideSizes';
|
||||||
import useStoryProps from './hooks/useStoryProps';
|
import useStoryProps from './hooks/useStoryProps';
|
||||||
|
|
||||||
import ReportModal from '../common/ReportModal';
|
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import ShowTransition from '../ui/ShowTransition';
|
import ShowTransition from '../ui/ShowTransition';
|
||||||
import StealthModeModal from './StealthModeModal';
|
import StealthModeModal from './StealthModeModal';
|
||||||
@ -48,6 +48,7 @@ interface StateProps {
|
|||||||
shouldSkipHistoryAnimations?: boolean;
|
shouldSkipHistoryAnimations?: boolean;
|
||||||
withAnimation?: boolean;
|
withAnimation?: boolean;
|
||||||
isPrivacyModalOpen?: boolean;
|
isPrivacyModalOpen?: boolean;
|
||||||
|
isReportModalOpen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function StoryViewer({
|
function StoryViewer({
|
||||||
@ -59,13 +60,13 @@ function StoryViewer({
|
|||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
withAnimation,
|
withAnimation,
|
||||||
isPrivacyModalOpen,
|
isPrivacyModalOpen,
|
||||||
|
isReportModalOpen,
|
||||||
}: StateProps) {
|
}: StateProps) {
|
||||||
const { closeStoryViewer, closeStoryPrivacyEditor } = getActions();
|
const { closeStoryViewer, closeStoryPrivacyEditor, reportStory } = getActions();
|
||||||
|
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
const [storyToDelete, setStoryToDelete] = useState<ApiTypeStory | undefined>(undefined);
|
const [storyToDelete, setStoryToDelete] = useState<ApiTypeStory | undefined>(undefined);
|
||||||
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag(false);
|
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag(false);
|
||||||
const [isReportModalOpen, openReportModal, closeReportModal] = useFlag(false);
|
|
||||||
|
|
||||||
const { bestImageData, thumbnail } = useStoryProps(story);
|
const { bestImageData, thumbnail } = useStoryProps(story);
|
||||||
const slideSizes = useSlideSizes();
|
const slideSizes = useSlideSizes();
|
||||||
@ -78,7 +79,6 @@ function StoryViewer({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
setStoryToDelete(undefined);
|
setStoryToDelete(undefined);
|
||||||
closeReportModal();
|
|
||||||
closeDeleteModal();
|
closeDeleteModal();
|
||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
@ -101,15 +101,20 @@ function StoryViewer({
|
|||||||
closeStoryViewer();
|
closeStoryViewer();
|
||||||
}, [closeStoryViewer]);
|
}, [closeStoryViewer]);
|
||||||
|
|
||||||
const handleOpenDeleteModal = useCallback((s: ApiTypeStory) => {
|
const handleOpenDeleteModal = useLastCallback((s: ApiTypeStory) => {
|
||||||
setStoryToDelete(s);
|
setStoryToDelete(s);
|
||||||
openDeleteModal();
|
openDeleteModal();
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleCloseDeleteModal = useCallback(() => {
|
const handleCloseDeleteModal = useLastCallback(() => {
|
||||||
closeDeleteModal();
|
closeDeleteModal();
|
||||||
setStoryToDelete(undefined);
|
setStoryToDelete(undefined);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
|
const openMessageReport = useLastCallback(() => {
|
||||||
|
if (!storyId) return;
|
||||||
|
reportStory({ storyId, peerId });
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => (isOpen ? captureEscKeyListener(() => {
|
useEffect(() => (isOpen ? captureEscKeyListener(() => {
|
||||||
handleClose();
|
handleClose();
|
||||||
@ -162,7 +167,7 @@ function StoryViewer({
|
|||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
isReportModalOpen={isReportModalOpen}
|
isReportModalOpen={isReportModalOpen}
|
||||||
isDeleteModalOpen={isDeleteModalOpen}
|
isDeleteModalOpen={isDeleteModalOpen}
|
||||||
onReport={openReportModal}
|
onReport={openMessageReport}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
onDelete={handleOpenDeleteModal}
|
onDelete={handleOpenDeleteModal}
|
||||||
/>
|
/>
|
||||||
@ -175,13 +180,6 @@ function StoryViewer({
|
|||||||
<StoryViewModal />
|
<StoryViewModal />
|
||||||
<StealthModeModal />
|
<StealthModeModal />
|
||||||
<StorySettings isOpen={isPrivacyModalOpen} onClose={closeStoryPrivacyEditor} />
|
<StorySettings isOpen={isPrivacyModalOpen} onClose={closeStoryPrivacyEditor} />
|
||||||
<ReportModal
|
|
||||||
isOpen={isReportModalOpen}
|
|
||||||
onClose={closeReportModal}
|
|
||||||
subject="story"
|
|
||||||
peerId={peerId!}
|
|
||||||
storyId={storyId}
|
|
||||||
/>
|
|
||||||
</ShowTransition>
|
</ShowTransition>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -190,14 +188,17 @@ export default memo(withGlobal((global): StateProps => {
|
|||||||
const {
|
const {
|
||||||
shouldSkipHistoryAnimations, storyViewer: {
|
shouldSkipHistoryAnimations, storyViewer: {
|
||||||
storyId, peerId, isPrivacyModalOpen, origin,
|
storyId, peerId, isPrivacyModalOpen, origin,
|
||||||
},
|
}, reportModal,
|
||||||
} = selectTabState(global);
|
} = selectTabState(global);
|
||||||
const story = peerId && storyId ? selectPeerStory(global, peerId, storyId) : undefined;
|
const story = peerId && storyId ? selectPeerStory(global, peerId, storyId) : undefined;
|
||||||
const withAnimation = selectPerformanceSettingsValue(global, 'mediaViewerAnimations');
|
const withAnimation = selectPerformanceSettingsValue(global, 'mediaViewerAnimations');
|
||||||
|
|
||||||
|
const isReportModalOpen = Boolean(reportModal);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isOpen: selectIsStoryViewerOpen(global),
|
isOpen: selectIsStoryViewerOpen(global),
|
||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
|
isReportModalOpen,
|
||||||
peerId: peerId!,
|
peerId: peerId!,
|
||||||
storyId,
|
storyId,
|
||||||
story,
|
story,
|
||||||
|
|||||||
@ -286,6 +286,7 @@ export const RE_TELEGRAM_LINK = /^(https?:\/\/)?telegram\.org\//i;
|
|||||||
export const TME_LINK_PREFIX = 'https://t.me/';
|
export const TME_LINK_PREFIX = 'https://t.me/';
|
||||||
export const BOT_FATHER_USERNAME = 'botfather';
|
export const BOT_FATHER_USERNAME = 'botfather';
|
||||||
export const USERNAME_PURCHASE_ERROR = 'USERNAME_PURCHASE_AVAILABLE';
|
export const USERNAME_PURCHASE_ERROR = 'USERNAME_PURCHASE_AVAILABLE';
|
||||||
|
export const MESSAGE_ID_REQUIRED_ERROR = 'MESSAGE_ID_REQUIRED';
|
||||||
export const PURCHASE_USERNAME = 'auction';
|
export const PURCHASE_USERNAME = 'auction';
|
||||||
export const ACCEPTABLE_USERNAME_ERRORS = new Set([USERNAME_PURCHASE_ERROR, 'USERNAME_INVALID']);
|
export const ACCEPTABLE_USERNAME_ERRORS = new Set([USERNAME_PURCHASE_ERROR, 'USERNAME_INVALID']);
|
||||||
export const TME_WEB_DOMAINS = new Set(['t.me', 'web.t.me', 'a.t.me', 'k.t.me', 'z.t.me']);
|
export const TME_WEB_DOMAINS = new Set(['t.me', 'web.t.me', 'a.t.me', 'k.t.me', 'z.t.me']);
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import { LoadMoreDirection, type ThreadId } from '../../../types';
|
|||||||
import {
|
import {
|
||||||
GIF_MIME_TYPE,
|
GIF_MIME_TYPE,
|
||||||
MAX_MEDIA_FILES_FOR_ALBUM,
|
MAX_MEDIA_FILES_FOR_ALBUM,
|
||||||
|
MESSAGE_ID_REQUIRED_ERROR,
|
||||||
MESSAGE_LIST_SLICE,
|
MESSAGE_LIST_SLICE,
|
||||||
RE_TELEGRAM_LINK,
|
RE_TELEGRAM_LINK,
|
||||||
SERVICE_NOTIFICATIONS_USER_ID,
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
@ -118,6 +119,7 @@ import {
|
|||||||
selectMessageReplyInfo,
|
selectMessageReplyInfo,
|
||||||
selectNoWebPage,
|
selectNoWebPage,
|
||||||
selectOutlyingListByMessageId,
|
selectOutlyingListByMessageId,
|
||||||
|
selectPeer,
|
||||||
selectPeerStory,
|
selectPeerStory,
|
||||||
selectPinnedIds,
|
selectPinnedIds,
|
||||||
selectPollFromMessage,
|
selectPollFromMessage,
|
||||||
@ -824,33 +826,81 @@ addActionHandler('deleteSavedHistory', async (global, actions, payload): Promise
|
|||||||
|
|
||||||
addActionHandler('reportMessages', async (global, actions, payload): Promise<void> => {
|
addActionHandler('reportMessages', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
messageIds, reason, description, tabId = getCurrentTabId(),
|
messageIds, description = '', option = '', chatId, tabId = getCurrentTabId(),
|
||||||
} = payload!;
|
} = payload!;
|
||||||
const currentMessageList = selectCurrentMessageList(global, tabId);
|
|
||||||
if (!currentMessageList) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove after implementing the new report system
|
|
||||||
if (messageIds) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.warn('UNSUPPORTED');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { chatId } = currentMessageList;
|
|
||||||
const chat = selectChat(global, chatId)!;
|
const chat = selectChat(global, chatId)!;
|
||||||
|
|
||||||
const result = await callApi('reportMessages', {
|
const response = await callApi('reportMessages', {
|
||||||
peer: chat, messageIds, reason, description,
|
peer: chat, messageIds, description, option,
|
||||||
});
|
});
|
||||||
|
|
||||||
actions.showNotification({
|
if (!response) return;
|
||||||
message: result
|
|
||||||
? oldTranslate('ReportPeer.AlertSuccess')
|
const { result, error } = response;
|
||||||
: 'An error occurred while submitting your report. Please, try again later.',
|
|
||||||
tabId,
|
if (error === MESSAGE_ID_REQUIRED_ERROR) {
|
||||||
});
|
actions.showNotification({
|
||||||
|
message: oldTranslate('lng_report_please_select_messages'),
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
actions.closeReportModal({ tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
if (result.type === 'reported') {
|
||||||
|
actions.showNotification({
|
||||||
|
message: result
|
||||||
|
? oldTranslate('ReportPeer.AlertSuccess')
|
||||||
|
: 'An error occurred while submitting your report. Please, try again later.',
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
actions.closeReportModal({ tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.type === 'selectOption') {
|
||||||
|
global = getGlobal();
|
||||||
|
const oldSections = selectTabState(global, tabId).reportModal?.sections;
|
||||||
|
const selectedOption = oldSections?.[oldSections.length - 1]?.options?.find((o) => o.option === option);
|
||||||
|
const newSection = {
|
||||||
|
title: result.title,
|
||||||
|
options: result.options,
|
||||||
|
subtitle: selectedOption?.text,
|
||||||
|
};
|
||||||
|
global = updateTabState(global, {
|
||||||
|
reportModal: {
|
||||||
|
chatId,
|
||||||
|
messageIds,
|
||||||
|
description,
|
||||||
|
subject: 'message',
|
||||||
|
sections: oldSections ? [...oldSections, newSection] : [newSection],
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.type === 'comment') {
|
||||||
|
global = getGlobal();
|
||||||
|
const oldSections = selectTabState(global, tabId).reportModal?.sections;
|
||||||
|
const selectedOption = oldSections?.[oldSections.length - 1]?.options?.find((o) => o.option === option);
|
||||||
|
const newSection = {
|
||||||
|
isOptional: result.isOptional,
|
||||||
|
option: result.option,
|
||||||
|
title: selectedOption?.text,
|
||||||
|
};
|
||||||
|
global = updateTabState(global, {
|
||||||
|
reportModal: {
|
||||||
|
chatId,
|
||||||
|
messageIds,
|
||||||
|
description,
|
||||||
|
subject: 'message',
|
||||||
|
sections: oldSections ? [...oldSections, newSection] : [newSection],
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('sendMessageAction', async (global, actions, payload): Promise<void> => {
|
addActionHandler('sendMessageAction', async (global, actions, payload): Promise<void> => {
|
||||||
@ -868,6 +918,17 @@ addActionHandler('sendMessageAction', async (global, actions, payload): Promise<
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('reportChannelSpam', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { participantId, chatId, messageIds } = payload;
|
||||||
|
const peer = selectPeer(global, participantId);
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
if (!peer || !chat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void callApi('reportChannelSpam', { peer, chat, messageIds });
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('markMessageListRead', (global, actions, payload): ActionReturnType => {
|
addActionHandler('markMessageListRead', (global, actions, payload): ActionReturnType => {
|
||||||
const { maxId, tabId = getCurrentTabId() } = payload!;
|
const { maxId, tabId = getCurrentTabId() } = payload!;
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { ActionReturnType } from '../../types';
|
import type { ActionReturnType } from '../../types';
|
||||||
|
|
||||||
import { DEBUG } from '../../../config';
|
import { DEBUG, MESSAGE_ID_REQUIRED_ERROR } from '../../../config';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
import { oldTranslate } from '../../../util/oldLangProvider';
|
import { oldTranslate } from '../../../util/oldLangProvider';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
@ -25,9 +25,10 @@ import {
|
|||||||
updateStoryViews,
|
updateStoryViews,
|
||||||
updateStoryViewsLoading,
|
updateStoryViewsLoading,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import {
|
import {
|
||||||
selectPeer, selectPeerStories, selectPeerStory,
|
selectPeer, selectPeerStories, selectPeerStory,
|
||||||
selectPinnedStories,
|
selectPinnedStories, selectTabState,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
|
|
||||||
const INFINITE_LOOP_MARKER = 100;
|
const INFINITE_LOOP_MARKER = 100;
|
||||||
@ -414,8 +415,8 @@ addActionHandler('reportStory', async (global, actions, payload): Promise<void>
|
|||||||
const {
|
const {
|
||||||
peerId,
|
peerId,
|
||||||
storyId,
|
storyId,
|
||||||
reason,
|
description = '',
|
||||||
description,
|
option = '',
|
||||||
tabId = getCurrentTabId(),
|
tabId = getCurrentTabId(),
|
||||||
} = payload;
|
} = payload;
|
||||||
const peer = selectPeer(global, peerId);
|
const peer = selectPeer(global, peerId);
|
||||||
@ -423,26 +424,80 @@ addActionHandler('reportStory', async (global, actions, payload): Promise<void>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove after implementing the new report system
|
const response = await callApi('reportStory', {
|
||||||
if (storyId) {
|
peer,
|
||||||
// eslint-disable-next-line no-console
|
storyId,
|
||||||
console.warn('UNSUPPORTED');
|
description,
|
||||||
|
option,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response) return;
|
||||||
|
|
||||||
|
const { result, error } = response;
|
||||||
|
|
||||||
|
if (error === MESSAGE_ID_REQUIRED_ERROR) {
|
||||||
|
actions.showNotification({
|
||||||
|
message: oldTranslate('lng_report_please_select_messages'),
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
actions.closeReportModal({ tabId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await callApi('reportStory', {
|
if (!result) return;
|
||||||
peer,
|
|
||||||
storyId,
|
|
||||||
reason,
|
|
||||||
description,
|
|
||||||
});
|
|
||||||
|
|
||||||
actions.showNotification({
|
if (result.type === 'reported') {
|
||||||
message: result
|
actions.showNotification({
|
||||||
? oldTranslate('ReportPeer.AlertSuccess')
|
message: result
|
||||||
: 'An error occurred while submitting your report. Please, try again later.',
|
? oldTranslate('ReportPeer.AlertSuccess')
|
||||||
tabId,
|
: 'An error occurred while submitting your report. Please, try again later.',
|
||||||
});
|
tabId,
|
||||||
|
});
|
||||||
|
actions.closeReportModal({ tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.type === 'selectOption') {
|
||||||
|
global = getGlobal();
|
||||||
|
const oldSections = selectTabState(global, tabId).reportModal?.sections;
|
||||||
|
const selectedOption = oldSections?.[oldSections.length - 1]?.options?.find((o) => o.option === option);
|
||||||
|
const newSection = {
|
||||||
|
title: result.title,
|
||||||
|
options: result.options,
|
||||||
|
subtitle: selectedOption?.text,
|
||||||
|
};
|
||||||
|
global = updateTabState(global, {
|
||||||
|
reportModal: {
|
||||||
|
messageIds: [storyId],
|
||||||
|
subject: 'story',
|
||||||
|
peerId,
|
||||||
|
description,
|
||||||
|
sections: oldSections ? [...oldSections, newSection] : [newSection],
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.type === 'comment') {
|
||||||
|
global = getGlobal();
|
||||||
|
const oldSections = selectTabState(global, tabId).reportModal?.sections;
|
||||||
|
const selectedOption = oldSections?.[oldSections.length - 1]?.options?.find((o) => o.option === option);
|
||||||
|
const newSection = {
|
||||||
|
isOptional: result.isOptional,
|
||||||
|
option: result.option,
|
||||||
|
title: selectedOption?.text,
|
||||||
|
};
|
||||||
|
global = updateTabState(global, {
|
||||||
|
reportModal: {
|
||||||
|
messageIds: [storyId],
|
||||||
|
description,
|
||||||
|
peerId,
|
||||||
|
subject: 'story',
|
||||||
|
sections: oldSections ? [...oldSections, newSection] : [newSection],
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('editStoryPrivacy', (global, actions, payload): ActionReturnType => {
|
addActionHandler('editStoryPrivacy', (global, actions, payload): ActionReturnType => {
|
||||||
|
|||||||
@ -929,6 +929,13 @@ addActionHandler('closeReportAdModal', (global, actions, payload): ActionReturnT
|
|||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeReportModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
return updateTabState(global, {
|
||||||
|
reportModal: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('openPreviousReportAdModal', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openPreviousReportAdModal', (global, actions, payload): ActionReturnType => {
|
||||||
const { tabId = getCurrentTabId() } = payload || {};
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
const reportAdModal = selectTabState(global, tabId).reportAdModal;
|
const reportAdModal = selectTabState(global, tabId).reportAdModal;
|
||||||
@ -949,6 +956,26 @@ addActionHandler('openPreviousReportAdModal', (global, actions, payload): Action
|
|||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openPreviousReportModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
const reportModal = selectTabState(global, tabId).reportModal;
|
||||||
|
if (!reportModal) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reportModal.sections.length === 1) {
|
||||||
|
actions.closeReportModal({ tabId });
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
reportModal: {
|
||||||
|
...reportModal,
|
||||||
|
sections: reportModal.sections.slice(0, -1),
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('openPaidReactionModal', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openPaidReactionModal', (global, actions, payload): ActionReturnType => {
|
||||||
const { chatId, messageId, tabId = getCurrentTabId() } = payload;
|
const { chatId, messageId, tabId = getCurrentTabId() } = payload;
|
||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
|
|||||||
@ -638,6 +638,24 @@ export type TabState = {
|
|||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
reportModal?: {
|
||||||
|
chatId?: string;
|
||||||
|
messageIds: number[];
|
||||||
|
description: string;
|
||||||
|
peerId?: string;
|
||||||
|
subject: 'story' | 'message';
|
||||||
|
sections: {
|
||||||
|
title?: string;
|
||||||
|
subtitle?: string;
|
||||||
|
options?: {
|
||||||
|
text: string;
|
||||||
|
option: string;
|
||||||
|
}[];
|
||||||
|
isOptional?: boolean;
|
||||||
|
option?: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
activeDownloads: ActiveDownloads;
|
activeDownloads: ActiveDownloads;
|
||||||
|
|
||||||
statistics: {
|
statistics: {
|
||||||
@ -1728,7 +1746,9 @@ export interface ActionPayloads {
|
|||||||
option?: string;
|
option?: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
openPreviousReportAdModal: WithTabId | undefined;
|
openPreviousReportAdModal: WithTabId | undefined;
|
||||||
|
openPreviousReportModal: WithTabId | undefined;
|
||||||
closeReportAdModal: WithTabId | undefined;
|
closeReportAdModal: WithTabId | undefined;
|
||||||
|
closeReportModal: WithTabId | undefined;
|
||||||
hideSponsoredMessages: WithTabId | undefined;
|
hideSponsoredMessages: WithTabId | undefined;
|
||||||
loadSendAs: {
|
loadSendAs: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
@ -1768,15 +1788,21 @@ export interface ActionPayloads {
|
|||||||
isReversed?: boolean;
|
isReversed?: boolean;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
reportMessages: {
|
reportMessages: {
|
||||||
|
chatId: string;
|
||||||
messageIds: number[];
|
messageIds: number[];
|
||||||
reason: ApiReportReason;
|
description?: string;
|
||||||
description: string;
|
option?: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
sendMessageAction: {
|
sendMessageAction: {
|
||||||
action: ApiSendMessageAction;
|
action: ApiSendMessageAction;
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: ThreadId;
|
threadId: ThreadId;
|
||||||
};
|
};
|
||||||
|
reportChannelSpam: {
|
||||||
|
chatId: string;
|
||||||
|
participantId: string;
|
||||||
|
messageIds: number[];
|
||||||
|
};
|
||||||
loadSeenBy: {
|
loadSeenBy: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
@ -2698,9 +2724,9 @@ export interface ActionPayloads {
|
|||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
reportStory: {
|
reportStory: {
|
||||||
peerId: string;
|
peerId: string;
|
||||||
|
option?: string;
|
||||||
storyId: number;
|
storyId: number;
|
||||||
reason: ApiReportReason;
|
description?: string;
|
||||||
description: string;
|
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
openStoryPrivacyEditor: WithTabId | undefined;
|
openStoryPrivacyEditor: WithTabId | undefined;
|
||||||
closeStoryPrivacyEditor: WithTabId | undefined;
|
closeStoryPrivacyEditor: WithTabId | undefined;
|
||||||
|
|||||||
@ -159,7 +159,7 @@ const useChatContextActions = ({
|
|||||||
? { title: lang('Unarchive'), icon: 'unarchive', handler: () => toggleChatArchived({ id: chat.id }) }
|
? { title: lang('Unarchive'), icon: 'unarchive', handler: () => toggleChatArchived({ id: chat.id }) }
|
||||||
: { title: lang('Archive'), icon: 'archive', handler: () => toggleChatArchived({ id: chat.id }) };
|
: { title: lang('Archive'), icon: 'archive', handler: () => toggleChatArchived({ id: chat.id }) };
|
||||||
|
|
||||||
const canReport = handleReport && (isChatChannel(chat) || isChatGroup(chat) || (user && !user.isSelf));
|
const canReport = handleReport && !user && (isChatChannel(chat) || isChatGroup(chat));
|
||||||
const actionReport = canReport
|
const actionReport = canReport
|
||||||
? { title: lang('ReportPeer.Report'), icon: 'flag', handler: handleReport }
|
? { title: lang('ReportPeer.Report'), icon: 'flag', handler: handleReport }
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|||||||
@ -1600,6 +1600,7 @@ help.getPeerColors#da80f42f hash:int = help.PeerColors;
|
|||||||
help.getTimezonesList#49b30240 hash:int = help.TimezonesList;
|
help.getTimezonesList#49b30240 hash:int = help.TimezonesList;
|
||||||
channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
|
channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
|
||||||
channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
|
channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
|
||||||
|
channels.reportSpam#f44a8315 channel:InputChannel participant:InputPeer id:Vector<int> = Bool;
|
||||||
channels.getMessages#ad8c9a23 channel:InputChannel id:Vector<InputMessage> = messages.Messages;
|
channels.getMessages#ad8c9a23 channel:InputChannel id:Vector<InputMessage> = messages.Messages;
|
||||||
channels.getParticipants#77ced9d0 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:long = channels.ChannelParticipants;
|
channels.getParticipants#77ced9d0 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:long = channels.ChannelParticipants;
|
||||||
channels.getParticipant#a0ab6cc6 channel:InputChannel participant:InputPeer = channels.ChannelParticipant;
|
channels.getParticipant#a0ab6cc6 channel:InputChannel participant:InputPeer = channels.ChannelParticipant;
|
||||||
|
|||||||
@ -228,6 +228,7 @@
|
|||||||
"channels.getChannelRecommendations",
|
"channels.getChannelRecommendations",
|
||||||
"channels.reportSponsoredMessage",
|
"channels.reportSponsoredMessage",
|
||||||
"channels.searchPosts",
|
"channels.searchPosts",
|
||||||
|
"channels.reportSpam",
|
||||||
"bots.canSendMessage",
|
"bots.canSendMessage",
|
||||||
"bots.allowSendMessage",
|
"bots.allowSendMessage",
|
||||||
"bots.invokeWebViewCustomMethod",
|
"bots.invokeWebViewCustomMethod",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user