Introduce Granular Media Permissions (#2576)
This commit is contained in:
parent
06a7bf1162
commit
3cc27156cb
@ -1238,6 +1238,14 @@ export function deleteChatMember(chat: ApiChat, user: ApiUser) {
|
||||
changeInfo: true,
|
||||
inviteUsers: true,
|
||||
pinMessages: true,
|
||||
manageTopics: true,
|
||||
sendPhotos: true,
|
||||
sendVideos: true,
|
||||
sendRoundvideos: true,
|
||||
sendAudios: true,
|
||||
sendVoices: true,
|
||||
sendDocs: true,
|
||||
sendPlain: true,
|
||||
},
|
||||
untilDate: MAX_INT_32,
|
||||
});
|
||||
|
||||
@ -263,7 +263,7 @@ export function sendMessage(
|
||||
|
||||
// This is expected to arrive after `updateMessageSendSucceeded` which replaces the local ID,
|
||||
// so in most cases this will be simply ignored
|
||||
setTimeout(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
onUpdate({
|
||||
'@type': localMessage.isScheduled ? 'updateScheduledMessage' : 'updateMessage',
|
||||
id: localMessage.id,
|
||||
@ -326,21 +326,31 @@ export function sendMessage(
|
||||
|
||||
const RequestClass = media ? GramJs.messages.SendMedia : GramJs.messages.SendMessage;
|
||||
|
||||
await invokeRequest(new RequestClass({
|
||||
clearDraft: true,
|
||||
message: text || '',
|
||||
entities: entities ? entities.map(buildMtpMessageEntity) : undefined,
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
randomId,
|
||||
...(isSilent && { silent: isSilent }),
|
||||
...(scheduledAt && { scheduleDate: scheduledAt }),
|
||||
...(replyingTo && { replyToMsgId: replyingTo }),
|
||||
...(replyingToTopId && { topMsgId: replyingToTopId }),
|
||||
...(media && { media }),
|
||||
...(noWebPage && { noWebpage: noWebPage }),
|
||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||
...(shouldUpdateStickerSetsOrder && { updateStickersetsOrder: shouldUpdateStickerSetsOrder }),
|
||||
}), true);
|
||||
try {
|
||||
await invokeRequest(new RequestClass({
|
||||
clearDraft: true,
|
||||
message: text || '',
|
||||
entities: entities ? entities.map(buildMtpMessageEntity) : undefined,
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
randomId,
|
||||
...(isSilent && { silent: isSilent }),
|
||||
...(scheduledAt && { scheduleDate: scheduledAt }),
|
||||
...(replyingTo && { replyToMsgId: replyingTo }),
|
||||
...(replyingToTopId && { topMsgId: replyingToTopId }),
|
||||
...(media && { media }),
|
||||
...(noWebPage && { noWebpage: noWebPage }),
|
||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||
...(shouldUpdateStickerSetsOrder && { updateStickersetsOrder: shouldUpdateStickerSetsOrder }),
|
||||
}), true, true);
|
||||
} catch (error: any) {
|
||||
onUpdate({
|
||||
'@type': 'updateMessageSendFailed',
|
||||
chatId: chat.id,
|
||||
localId: localMessage.id,
|
||||
error: error.message,
|
||||
});
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
})();
|
||||
|
||||
return queue;
|
||||
|
||||
@ -157,8 +157,15 @@ export interface ApiChatBannedRights {
|
||||
changeInfo?: true;
|
||||
inviteUsers?: true;
|
||||
pinMessages?: true;
|
||||
untilDate?: number;
|
||||
manageTopics?: true;
|
||||
sendPhotos?: true;
|
||||
sendVideos?: true;
|
||||
sendRoundvideos?: true;
|
||||
sendAudios?: true;
|
||||
sendVoices?: true;
|
||||
sendDocs?: true;
|
||||
sendPlain?: true;
|
||||
untilDate?: number;
|
||||
}
|
||||
|
||||
export interface ApiRestrictionReason {
|
||||
|
||||
@ -243,9 +243,7 @@ export type ApiUpdateMessageSendFailed = {
|
||||
'@type': 'updateMessageSendFailed';
|
||||
chatId: string;
|
||||
localId: number;
|
||||
sendingState: {
|
||||
'@type': 'messageSendingStateFailed';
|
||||
};
|
||||
error: string;
|
||||
};
|
||||
|
||||
export type ApiUpdateCommonBoxMessages = {
|
||||
|
||||
@ -18,4 +18,20 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.MessageOutgoingStatus--failed::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
inset: 0.25rem;
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.icon-message-failed {
|
||||
background: none;
|
||||
color: var(--color-error);
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,11 @@ const MessageOutgoingStatus: FC<OwnProps> = ({ status }) => {
|
||||
return (
|
||||
<div className="MessageOutgoingStatus">
|
||||
<Transition name="reveal" activeKey={Keys[status]}>
|
||||
<i className={`icon-message-${status}`} />
|
||||
{status === 'failed' ? (
|
||||
<div className="MessageOutgoingStatus--failed">
|
||||
<i className="icon-message-failed" />
|
||||
</div>
|
||||
) : <i className={`icon-message-${status}`} />}
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -7,7 +7,11 @@ import type { GlobalState } from '../../../global/types';
|
||||
import type { ApiAttachMenuPeerType } from '../../../api/types';
|
||||
import type { ISettings } from '../../../types';
|
||||
|
||||
import { CONTENT_TYPES_WITH_PREVIEW } from '../../../config';
|
||||
import {
|
||||
CONTENT_TYPES_WITH_PREVIEW, SUPPORTED_AUDIO_CONTENT_TYPES,
|
||||
SUPPORTED_IMAGE_CONTENT_TYPES,
|
||||
SUPPORTED_VIDEO_CONTENT_TYPES,
|
||||
} from '../../../config';
|
||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
||||
import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
|
||||
import { validateFiles } from '../../../util/files';
|
||||
@ -29,6 +33,10 @@ export type OwnProps = {
|
||||
isButtonVisible: boolean;
|
||||
canAttachMedia: boolean;
|
||||
canAttachPolls: boolean;
|
||||
canSendPhotos: boolean;
|
||||
canSendVideos: boolean;
|
||||
canSendDocuments: boolean;
|
||||
canSendAudios: boolean;
|
||||
isScheduled?: boolean;
|
||||
attachBots: GlobalState['attachMenu']['bots'];
|
||||
peerType?: ApiAttachMenuPeerType;
|
||||
@ -43,6 +51,10 @@ const AttachMenu: FC<OwnProps> = ({
|
||||
isButtonVisible,
|
||||
canAttachMedia,
|
||||
canAttachPolls,
|
||||
canSendPhotos,
|
||||
canSendVideos,
|
||||
canSendDocuments,
|
||||
canSendAudios,
|
||||
attachBots,
|
||||
peerType,
|
||||
isScheduled,
|
||||
@ -53,6 +65,9 @@ const AttachMenu: FC<OwnProps> = ({
|
||||
const [isAttachMenuOpen, openAttachMenu, closeAttachMenu] = useFlag();
|
||||
const [handleMouseEnter, handleMouseLeave, markMouseInside] = useMouseInside(isAttachMenuOpen, closeAttachMenu);
|
||||
|
||||
const canSendVideoAndPhoto = canSendPhotos && canSendVideos;
|
||||
const canSendVideoOrPhoto = canSendPhotos || canSendVideos;
|
||||
|
||||
const [isAttachmentBotMenuOpen, markAttachmentBotMenuOpen, unmarkAttachmentBotMenuOpen] = useFlag();
|
||||
useEffect(() => {
|
||||
if (isAttachMenuOpen) {
|
||||
@ -79,14 +94,19 @@ const AttachMenu: FC<OwnProps> = ({
|
||||
|
||||
const handleQuickSelect = useCallback(() => {
|
||||
openSystemFilesDialog(
|
||||
Array.from(CONTENT_TYPES_WITH_PREVIEW).join(','),
|
||||
Array.from(canSendVideoAndPhoto ? CONTENT_TYPES_WITH_PREVIEW : (
|
||||
canSendPhotos ? SUPPORTED_IMAGE_CONTENT_TYPES : SUPPORTED_VIDEO_CONTENT_TYPES
|
||||
)).join(','),
|
||||
(e) => handleFileSelect(e, true),
|
||||
);
|
||||
}, [handleFileSelect]);
|
||||
}, [canSendPhotos, canSendVideoAndPhoto, handleFileSelect]);
|
||||
|
||||
const handleDocumentSelect = useCallback(() => {
|
||||
openSystemFilesDialog('*', (e) => handleFileSelect(e, false));
|
||||
}, [handleFileSelect]);
|
||||
openSystemFilesDialog(!canSendDocuments && canSendAudios
|
||||
? Array.from(SUPPORTED_AUDIO_CONTENT_TYPES).join(',') : (
|
||||
'*'
|
||||
), (e) => handleFileSelect(e, false));
|
||||
}, [canSendAudios, canSendDocuments, handleFileSelect]);
|
||||
|
||||
const bots = useMemo(() => {
|
||||
return Object.values(attachBots).filter((bot) => {
|
||||
@ -141,8 +161,18 @@ const AttachMenu: FC<OwnProps> = ({
|
||||
)}
|
||||
{canAttachMedia && (
|
||||
<>
|
||||
<MenuItem icon="photo" onClick={handleQuickSelect}>{lang('AttachmentMenu.PhotoOrVideo')}</MenuItem>
|
||||
<MenuItem icon="document" onClick={handleDocumentSelect}>{lang('AttachDocument')}</MenuItem>
|
||||
{canSendVideoOrPhoto && (
|
||||
<MenuItem icon="photo" onClick={handleQuickSelect}>
|
||||
{lang(canSendVideoAndPhoto ? 'AttachmentMenu.PhotoOrVideo'
|
||||
: (canSendPhotos ? 'InputAttach.Popover.Photo' : 'InputAttach.Popover.Video'))}
|
||||
</MenuItem>
|
||||
)}
|
||||
{(canSendDocuments || canSendAudios)
|
||||
&& (
|
||||
<MenuItem icon="document" onClick={handleDocumentSelect}>
|
||||
{lang(!canSendDocuments && canSendAudios ? 'InputAttach.Popover.Music' : 'AttachDocument')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{canAttachPolls && (
|
||||
|
||||
@ -61,6 +61,8 @@ export type OwnProps = {
|
||||
isReady?: boolean;
|
||||
shouldSchedule?: boolean;
|
||||
shouldSuggestCompression?: boolean;
|
||||
shouldForceCompression?: boolean;
|
||||
shouldForceAsFile?: boolean;
|
||||
isForCurrentMessageList?: boolean;
|
||||
onCaptionUpdate: (html: string) => void;
|
||||
onSend: (sendCompressed: boolean, sendGrouped: boolean) => void;
|
||||
@ -109,6 +111,8 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
||||
customEmojiForEmoji,
|
||||
attachmentSettings,
|
||||
shouldSuggestCompression,
|
||||
shouldForceCompression,
|
||||
shouldForceAsFile,
|
||||
isForCurrentMessageList,
|
||||
onAttachmentsUpdate,
|
||||
onCaptionUpdate,
|
||||
@ -140,6 +144,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
||||
const [shouldSendCompressed, setShouldSendCompressed] = useState(
|
||||
shouldSuggestCompression ?? attachmentSettings.shouldCompress,
|
||||
);
|
||||
const isSendingCompressed = Boolean((shouldSendCompressed || shouldForceCompression) && !shouldForceAsFile);
|
||||
const [shouldSendGrouped, setShouldSendGrouped] = useState(attachmentSettings.shouldSendGrouped);
|
||||
|
||||
const {
|
||||
@ -241,15 +246,15 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
||||
if (isOpen) {
|
||||
const send = (shouldSchedule || shouldSendScheduled) ? onSendScheduled
|
||||
: isSilent ? onSendSilent : onSend;
|
||||
send(shouldSendCompressed, shouldSendGrouped);
|
||||
send(isSendingCompressed, shouldSendGrouped);
|
||||
updateAttachmentSettings({
|
||||
shouldCompress: shouldSendCompressed,
|
||||
shouldCompress: isSendingCompressed,
|
||||
shouldSendGrouped,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
isOpen, shouldSchedule, onSendScheduled, onSend, updateAttachmentSettings, shouldSendCompressed, shouldSendGrouped,
|
||||
onSendSilent,
|
||||
isOpen, shouldSchedule, onSendScheduled, onSendSilent, onSend, isSendingCompressed, shouldSendGrouped,
|
||||
updateAttachmentSettings,
|
||||
]);
|
||||
|
||||
const handleSendSilent = useCallback(() => {
|
||||
@ -366,7 +371,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
||||
return leftCharsBeforeLimit <= MAX_LEFT_CHARS_TO_SHOW ? leftCharsBeforeLimit : undefined;
|
||||
}, [captionLimit, getHtml, renderingIsOpen]);
|
||||
|
||||
const isQuickGallery = shouldSendCompressed && hasOnlyMedia;
|
||||
const isQuickGallery = isSendingCompressed && hasOnlyMedia;
|
||||
|
||||
const [areAllPhotos, areAllVideos, areAllAudios] = useMemo(() => {
|
||||
if (!isQuickGallery || !renderingAttachments) return [false, false, false];
|
||||
@ -413,7 +418,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
||||
{hasMedia && (
|
||||
<>
|
||||
{
|
||||
shouldSendCompressed ? (
|
||||
!shouldForceAsFile && !shouldForceCompression && (isSendingCompressed ? (
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
<MenuItem icon="document" onClick={() => setShouldSendCompressed(false)}>
|
||||
{lang(isMultiple ? 'Attachment.SendAsFiles' : 'Attachment.SendAsFile')}
|
||||
@ -423,9 +428,9 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
||||
<MenuItem icon="photo" onClick={() => setShouldSendCompressed(true)}>
|
||||
{isMultiple ? 'Send All as Media' : 'Send as Media'}
|
||||
</MenuItem>
|
||||
)
|
||||
))
|
||||
}
|
||||
{shouldSendCompressed && (
|
||||
{isSendingCompressed && (
|
||||
hasSpoiler ? (
|
||||
<MenuItem icon="spoiler-disable" onClick={handleDisableSpoilers}>
|
||||
{lang('Attachment.DisableSpoiler')}
|
||||
@ -496,7 +501,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
||||
{renderingAttachments.map((attachment, i) => (
|
||||
<AttachmentModalItem
|
||||
attachment={attachment}
|
||||
shouldDisplayCompressed={shouldSendCompressed}
|
||||
shouldDisplayCompressed={isSendingCompressed}
|
||||
shouldDisplayGrouped={shouldSendGrouped}
|
||||
isSingle={renderingAttachments.length === 1}
|
||||
index={i}
|
||||
@ -548,6 +553,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
||||
onRemoveSymbol={onRemoveSymbol}
|
||||
onEmojiSelect={onEmojiSelect}
|
||||
isAttachmentModal
|
||||
canSendPlainText
|
||||
className="attachment-modal-symbol-menu with-menu-transitions"
|
||||
/>
|
||||
<MessageInput
|
||||
|
||||
@ -508,6 +508,15 @@
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
|
||||
&.with-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
margin-inline-end: 0.25rem;
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
left: 0.75rem;
|
||||
}
|
||||
|
||||
@ -294,6 +294,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
callAttachBot,
|
||||
addRecentCustomEmoji,
|
||||
showNotification,
|
||||
showAllowedMessageTypesNotification,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
@ -355,8 +356,15 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
const [attachments, setAttachments] = useState<ApiAttachment[]>([]);
|
||||
const hasAttachments = Boolean(attachments.length);
|
||||
|
||||
const {
|
||||
canSendStickers, canSendGifs, canAttachMedia, canAttachPolls, canAttachEmbedLinks,
|
||||
canSendVoices, canSendPlainText, canSendAudios, canSendVideos, canSendPhotos, canSendDocuments,
|
||||
} = useMemo(() => getAllowedAttachmentOptions(chat, isChatWithBot), [chat, isChatWithBot]);
|
||||
|
||||
const {
|
||||
shouldSuggestCompression,
|
||||
shouldForceCompression,
|
||||
shouldForceAsFile,
|
||||
handleAppendFiles,
|
||||
handleFileSelect,
|
||||
onCaptionUpdate,
|
||||
@ -367,6 +375,11 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
setHtml,
|
||||
setAttachments,
|
||||
fileSizeLimit,
|
||||
chatId,
|
||||
canSendAudios,
|
||||
canSendVideos,
|
||||
canSendPhotos,
|
||||
canSendDocuments,
|
||||
});
|
||||
|
||||
const [isBotKeyboardOpen, openBotKeyboard, closeBotKeyboard] = useFlag();
|
||||
@ -403,10 +416,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
}, [getHtml, isEditingRef, sendMessageAction]);
|
||||
|
||||
const {
|
||||
canSendStickers, canSendGifs, canAttachMedia, canAttachPolls, canAttachEmbedLinks,
|
||||
} = useMemo(() => getAllowedAttachmentOptions(chat, isChatWithBot), [chat, isChatWithBot]);
|
||||
|
||||
const isAdmin = chat && isChatAdmin(chat);
|
||||
const slowMode = getChatSlowModeOptions(chat);
|
||||
|
||||
@ -492,6 +501,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
|
||||
const insertHtmlAndUpdateCursor = useCallback((newHtml: string, inputId: string = EDITABLE_INPUT_ID) => {
|
||||
if (inputId === EDITABLE_INPUT_ID && !canSendPlainText) return;
|
||||
const selection = window.getSelection()!;
|
||||
let messageInput: HTMLDivElement;
|
||||
if (inputId === EDITABLE_INPUT_ID) {
|
||||
@ -515,7 +525,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
requestAnimationFrame(() => {
|
||||
focusEditableElement(messageInput);
|
||||
});
|
||||
}, [getHtml, setHtml]);
|
||||
}, [canSendPlainText, getHtml, setHtml]);
|
||||
|
||||
const insertFormattedTextAndUpdateCursor = useCallback((
|
||||
text: ApiFormattedText, inputId: string = EDITABLE_INPUT_ID,
|
||||
@ -1060,6 +1070,12 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
insertHtmlAndUpdateCursor(newHtml, inputId);
|
||||
}, [insertHtmlAndUpdateCursor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (canSendPlainText) return;
|
||||
|
||||
setHtml('');
|
||||
}, [canSendPlainText, setHtml, attachments]);
|
||||
|
||||
const insertTextAndUpdateCursorAttachmentModal = useCallback((text: string) => {
|
||||
insertTextAndUpdateCursor(text, EDITABLE_INPUT_MODAL_ID);
|
||||
}, [insertTextAndUpdateCursor]);
|
||||
@ -1107,7 +1123,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
}, [isSelectModeActive, enableHover, disableHover, isReady]);
|
||||
|
||||
const areVoiceMessagesNotAllowed = mainButtonState === MainButtonState.Record
|
||||
&& (!canAttachMedia || !canSendVoiceByPrivacy);
|
||||
&& (!canAttachMedia || !canSendVoiceByPrivacy || !canSendVoices);
|
||||
|
||||
const mainButtonHandler = useCallback(() => {
|
||||
switch (mainButtonState) {
|
||||
@ -1120,6 +1136,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
showNotification({
|
||||
message: lang('VoiceMessagesRestrictedByPrivacy', chat?.title),
|
||||
});
|
||||
} else if (!canSendVoices) {
|
||||
showAllowedMessageTypesNotification({ chatId });
|
||||
}
|
||||
} else {
|
||||
startRecordingVoice();
|
||||
@ -1143,7 +1161,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
}, [
|
||||
mainButtonState, handleSend, handleEditComplete, activeVoiceRecording, requestCalendar, areVoiceMessagesNotAllowed,
|
||||
canSendVoiceByPrivacy, showNotification, lang, chat?.title, startRecordingVoice, pauseRecordingVoice,
|
||||
handleMessageSchedule,
|
||||
handleMessageSchedule, chatId, showAllowedMessageTypesNotification, canSendVoices,
|
||||
]);
|
||||
|
||||
const prevEditedMessage = usePrevious(editingMessage, true);
|
||||
@ -1224,6 +1242,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
getHtml={getHtml}
|
||||
isReady={isReady}
|
||||
shouldSuggestCompression={shouldSuggestCompression}
|
||||
shouldForceCompression={shouldForceCompression}
|
||||
shouldForceAsFile={shouldForceAsFile}
|
||||
isForCurrentMessageList={isForCurrentMessageList}
|
||||
onCaptionUpdate={onCaptionUpdate}
|
||||
onSendSilent={handleSendSilentAttachments}
|
||||
@ -1335,37 +1355,43 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
<SymbolMenuButton
|
||||
chatId={chatId}
|
||||
threadId={threadId}
|
||||
isMobile={isMobile}
|
||||
isReady={isReady}
|
||||
isSymbolMenuOpen={isSymbolMenuOpen}
|
||||
openSymbolMenu={openSymbolMenu}
|
||||
closeSymbolMenu={closeSymbolMenu}
|
||||
canSendStickers={canSendStickers}
|
||||
canSendGifs={canSendGifs}
|
||||
onGifSelect={handleGifSelect}
|
||||
onStickerSelect={handleStickerSelect}
|
||||
onCustomEmojiSelect={handleCustomEmojiSelect}
|
||||
onRemoveSymbol={removeSymbol}
|
||||
onEmojiSelect={insertTextAndUpdateCursor}
|
||||
closeBotCommandMenu={closeBotCommandMenu}
|
||||
closeSendAsMenu={closeSendAsMenu}
|
||||
isSymbolMenuForced={isSymbolMenuForced}
|
||||
/>
|
||||
{(canSendPlainText || canSendGifs || canSendStickers) && (
|
||||
<SymbolMenuButton
|
||||
chatId={chatId}
|
||||
threadId={threadId}
|
||||
isMobile={isMobile}
|
||||
isReady={isReady}
|
||||
isSymbolMenuOpen={isSymbolMenuOpen}
|
||||
openSymbolMenu={openSymbolMenu}
|
||||
closeSymbolMenu={closeSymbolMenu}
|
||||
canSendStickers={canSendStickers}
|
||||
canSendGifs={canSendGifs}
|
||||
onGifSelect={handleGifSelect}
|
||||
onStickerSelect={handleStickerSelect}
|
||||
onCustomEmojiSelect={handleCustomEmojiSelect}
|
||||
onRemoveSymbol={removeSymbol}
|
||||
onEmojiSelect={insertTextAndUpdateCursor}
|
||||
closeBotCommandMenu={closeBotCommandMenu}
|
||||
closeSendAsMenu={closeSendAsMenu}
|
||||
isSymbolMenuForced={isSymbolMenuForced}
|
||||
canSendPlainText={canSendPlainText}
|
||||
/>
|
||||
)}
|
||||
<MessageInput
|
||||
ref={inputRef}
|
||||
id="message-input-text"
|
||||
editableInputId={EDITABLE_INPUT_ID}
|
||||
chatId={chatId}
|
||||
canSendPlainText={canSendPlainText}
|
||||
threadId={threadId}
|
||||
isActive={!hasAttachments}
|
||||
getHtml={getHtml}
|
||||
placeholder={
|
||||
activeVoiceRecording && windowWidth <= SCREEN_WIDTH_TO_HIDE_PLACEHOLDER
|
||||
? ''
|
||||
: botKeyboardPlaceholder || lang('Message')
|
||||
: (canSendPlainText
|
||||
? (botKeyboardPlaceholder || lang('Message'))
|
||||
: lang('Chat.PlaceholderTextNotAllowed'))
|
||||
}
|
||||
forcedPlaceholder={inlineBotHelp}
|
||||
canAutoFocus={isReady && isForCurrentMessageList && !hasAttachments}
|
||||
@ -1413,6 +1439,10 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
isButtonVisible={!activeVoiceRecording && !editingMessage}
|
||||
canAttachMedia={canAttachMedia}
|
||||
canAttachPolls={canAttachPolls}
|
||||
canSendPhotos={canSendPhotos}
|
||||
canSendVideos={canSendVideos}
|
||||
canSendDocuments={canSendDocuments}
|
||||
canSendAudios={canSendAudios}
|
||||
onFileSelect={handleFileSelect}
|
||||
onPollCreate={openPollModal}
|
||||
isScheduled={shouldSchedule}
|
||||
|
||||
@ -22,8 +22,8 @@ import './GifPicker.scss';
|
||||
type OwnProps = {
|
||||
className: string;
|
||||
loadAndPlay: boolean;
|
||||
canSendGifs: boolean;
|
||||
onGifSelect: (gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => void;
|
||||
canSendGifs?: boolean;
|
||||
onGifSelect?: (gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
|
||||
@ -54,6 +54,7 @@ type OwnProps = {
|
||||
canAutoFocus: boolean;
|
||||
shouldSuppressFocus?: boolean;
|
||||
shouldSuppressTextFormatter?: boolean;
|
||||
canSendPlainText?: boolean;
|
||||
onUpdate: (html: string) => void;
|
||||
onSuppressedFocus?: () => void;
|
||||
onSend: () => void;
|
||||
@ -102,6 +103,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
||||
getHtml,
|
||||
placeholder,
|
||||
forcedPlaceholder,
|
||||
canSendPlainText,
|
||||
canAutoFocus,
|
||||
noFocusInterception,
|
||||
shouldSuppressFocus,
|
||||
@ -117,6 +119,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
||||
const {
|
||||
editLastMessage,
|
||||
replyToNextMessage,
|
||||
showAllowedMessageTypesNotification,
|
||||
} = getActions();
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
@ -405,6 +408,11 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
function handleClick() {
|
||||
if (isAttachmentModalInput || canSendPlainText) return;
|
||||
showAllowedMessageTypesNotification({ chatId });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (IS_TOUCH_ENV) {
|
||||
return;
|
||||
@ -506,13 +514,17 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
||||
|
||||
return (
|
||||
<div id={id} onClick={shouldSuppressFocus ? onSuppressedFocus : undefined} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<div className={buildClassName('custom-scroll', SCROLLER_CLASS)} onScroll={onScroll}>
|
||||
<div
|
||||
className={buildClassName('custom-scroll', SCROLLER_CLASS)}
|
||||
onScroll={onScroll}
|
||||
onClick={!isAttachmentModalInput && !canSendPlainText ? handleClick : undefined}
|
||||
>
|
||||
<div className="input-scroller-content">
|
||||
<div
|
||||
ref={inputRef}
|
||||
id={editableInputId || EDITABLE_INPUT_ID}
|
||||
className={className}
|
||||
contentEditable
|
||||
contentEditable={isAttachmentModalInput || canSendPlainText}
|
||||
role="textbox"
|
||||
dir="auto"
|
||||
tabIndex={0}
|
||||
@ -524,7 +536,18 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
||||
onTouchCancel={IS_ANDROID ? processSelectionWithTimeout : undefined}
|
||||
aria-label={placeholder}
|
||||
/>
|
||||
{!forcedPlaceholder && <span className="placeholder-text" dir="auto">{placeholder}</span>}
|
||||
{!forcedPlaceholder && (
|
||||
<span
|
||||
className={buildClassName(
|
||||
'placeholder-text',
|
||||
!isAttachmentModalInput && !canSendPlainText && 'with-icon',
|
||||
)}
|
||||
dir="auto"
|
||||
>
|
||||
{!isAttachmentModalInput && !canSendPlainText && <i className="icon-lock-badge placeholder-icon" />}
|
||||
{placeholder}
|
||||
</span>
|
||||
)}
|
||||
<canvas ref={sharedCanvasRef} className="shared-canvas" />
|
||||
<canvas ref={sharedCanvasHqRef} className="shared-canvas" />
|
||||
<div ref={absoluteContainerRef} className="absolute-video-container" />
|
||||
|
||||
@ -44,7 +44,7 @@ type OwnProps = {
|
||||
threadId?: number;
|
||||
className: string;
|
||||
loadAndPlay: boolean;
|
||||
canSendStickers: boolean;
|
||||
canSendStickers?: boolean;
|
||||
onStickerSelect: (
|
||||
sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean, shouldUpdateStickerSetsOrder?: boolean,
|
||||
) => void;
|
||||
|
||||
@ -30,6 +30,7 @@ import Portal from '../../ui/Portal';
|
||||
import './SymbolMenu.scss';
|
||||
|
||||
const ANIMATION_DURATION = 350;
|
||||
const STICKERS_TAB_INDEX = 2;
|
||||
|
||||
export type OwnProps = {
|
||||
chatId: string;
|
||||
@ -55,6 +56,7 @@ export type OwnProps = {
|
||||
addRecentCustomEmoji: GlobalActions['addRecentCustomEmoji'];
|
||||
className?: string;
|
||||
isAttachmentModal?: boolean;
|
||||
canSendPlainText?: boolean;
|
||||
positionX?: 'left' | 'right';
|
||||
positionY?: 'top' | 'bottom';
|
||||
transformOriginX?: number;
|
||||
@ -83,6 +85,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
||||
onClose,
|
||||
onEmojiSelect,
|
||||
isAttachmentModal,
|
||||
canSendPlainText,
|
||||
onCustomEmojiSelect,
|
||||
onStickerSelect,
|
||||
className,
|
||||
@ -114,6 +117,12 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
||||
onLoad();
|
||||
}, [onLoad]);
|
||||
|
||||
// If we can't send plain text, we should always show the stickers tab
|
||||
useEffect(() => {
|
||||
if (canSendPlainText) return;
|
||||
setActiveTab(STICKERS_TAB_INDEX);
|
||||
}, [canSendPlainText]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!lastSyncTime) return;
|
||||
if (isCurrentUserPremium) {
|
||||
@ -218,8 +227,6 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
);
|
||||
case SymbolMenuTabs.Stickers:
|
||||
if (!canSendStickers) return undefined;
|
||||
|
||||
return (
|
||||
<StickerPicker
|
||||
className="picker-tab"
|
||||
@ -231,8 +238,6 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
);
|
||||
case SymbolMenuTabs.GIFs:
|
||||
if (!canSendGifs || !onGifSelect) return undefined;
|
||||
|
||||
return (
|
||||
<GifPicker
|
||||
className="picker-tab"
|
||||
@ -278,6 +283,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
||||
onRemoveSymbol={onRemoveSymbol}
|
||||
onSearchOpen={handleSearch}
|
||||
isAttachmentModal={isAttachmentModal}
|
||||
canSendPlainText={canSendPlainText}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -44,6 +44,7 @@ type OwnProps = {
|
||||
closeSendAsMenu?: VoidFunction;
|
||||
isSymbolMenuForced?: boolean;
|
||||
isAttachmentModal?: boolean;
|
||||
canSendPlainText?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
@ -61,6 +62,7 @@ const SymbolMenuButton: FC<OwnProps> = ({
|
||||
onStickerSelect,
|
||||
onGifSelect,
|
||||
isAttachmentModal,
|
||||
canSendPlainText,
|
||||
onRemoveSymbol,
|
||||
onEmojiSelect,
|
||||
closeBotCommandMenu,
|
||||
@ -196,6 +198,7 @@ const SymbolMenuButton: FC<OwnProps> = ({
|
||||
addRecentEmoji={addRecentEmoji}
|
||||
addRecentCustomEmoji={addRecentCustomEmoji}
|
||||
isAttachmentModal={isAttachmentModal}
|
||||
canSendPlainText={canSendPlainText}
|
||||
className={className}
|
||||
positionX={isAttachmentModal ? positionX : undefined}
|
||||
positionY={isAttachmentModal ? positionY : undefined}
|
||||
|
||||
@ -11,6 +11,7 @@ type OwnProps = {
|
||||
onRemoveSymbol: () => void;
|
||||
onSearchOpen: (type: 'stickers' | 'gifs') => void;
|
||||
isAttachmentModal?: boolean;
|
||||
canSendPlainText?: boolean;
|
||||
};
|
||||
|
||||
export enum SymbolMenuTabs {
|
||||
@ -36,6 +37,7 @@ const SYMBOL_MENU_TAB_ICONS = {
|
||||
|
||||
const SymbolMenuFooter: FC<OwnProps> = ({
|
||||
activeTab, onSwitchTab, onRemoveSymbol, onSearchOpen, isAttachmentModal,
|
||||
canSendPlainText,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
@ -78,8 +80,8 @@ const SymbolMenuFooter: FC<OwnProps> = ({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{renderTabButton(SymbolMenuTabs.Emoji)}
|
||||
{renderTabButton(SymbolMenuTabs.CustomEmoji)}
|
||||
{canSendPlainText && renderTabButton(SymbolMenuTabs.Emoji)}
|
||||
{canSendPlainText && renderTabButton(SymbolMenuTabs.CustomEmoji)}
|
||||
{!isAttachmentModal && renderTabButton(SymbolMenuTabs.Stickers)}
|
||||
{!isAttachmentModal && renderTabButton(SymbolMenuTabs.GIFs)}
|
||||
|
||||
|
||||
@ -5,19 +5,36 @@ import type { ApiAttachment } from '../../../../api/types';
|
||||
|
||||
import buildAttachment from '../helpers/buildAttachment';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../../util/memo';
|
||||
import {
|
||||
SUPPORTED_AUDIO_CONTENT_TYPES,
|
||||
SUPPORTED_IMAGE_CONTENT_TYPES,
|
||||
SUPPORTED_VIDEO_CONTENT_TYPES,
|
||||
} from '../../../../config';
|
||||
|
||||
export default function useAttachmentModal({
|
||||
attachments,
|
||||
fileSizeLimit,
|
||||
setHtml,
|
||||
setAttachments,
|
||||
chatId,
|
||||
canSendAudios,
|
||||
canSendVideos,
|
||||
canSendPhotos,
|
||||
canSendDocuments,
|
||||
}: {
|
||||
attachments: ApiAttachment[];
|
||||
fileSizeLimit: number;
|
||||
setHtml: (html: string) => void;
|
||||
setAttachments: (attachments: ApiAttachment[]) => void;
|
||||
chatId: string;
|
||||
canSendAudios?: boolean;
|
||||
canSendVideos?: boolean;
|
||||
canSendPhotos?: boolean;
|
||||
canSendDocuments?: boolean;
|
||||
}) {
|
||||
const { openLimitReachedModal } = getActions();
|
||||
const { openLimitReachedModal, showAllowedMessageTypesNotification } = getActions();
|
||||
const [shouldForceAsFile, setShouldForceAsFile] = useState<boolean>(false);
|
||||
const [shouldForceCompression, setShouldForceCompression] = useState<boolean>(false);
|
||||
const [shouldSuggestCompression, setShouldSuggestCompression] = useState<boolean | undefined>(undefined);
|
||||
|
||||
const handleClearAttachments = useCallback(() => {
|
||||
@ -28,18 +45,40 @@ export default function useAttachmentModal({
|
||||
(newValue: ApiAttachment[] | ((current: ApiAttachment[]) => ApiAttachment[])) => {
|
||||
const newAttachments = typeof newValue === 'function' ? newValue(attachments) : newValue;
|
||||
if (!newAttachments.length) {
|
||||
setAttachments(MEMO_EMPTY_ARRAY);
|
||||
handleClearAttachments();
|
||||
return;
|
||||
}
|
||||
|
||||
if (newAttachments.some(({ size }) => size > fileSizeLimit)) {
|
||||
if (newAttachments.some((attachment) => {
|
||||
const type = getAttachmentType(attachment);
|
||||
|
||||
return (type === 'audio' && !canSendAudios && !canSendDocuments)
|
||||
|| (type === 'video' && !canSendVideos && !canSendDocuments)
|
||||
|| (type === 'image' && !canSendPhotos && !canSendDocuments)
|
||||
|| (type === 'file' && !canSendDocuments);
|
||||
})) {
|
||||
showAllowedMessageTypesNotification({ chatId });
|
||||
} else if (newAttachments.some(({ size }) => size > fileSizeLimit)) {
|
||||
openLimitReachedModal({
|
||||
limit: 'uploadMaxFileparts',
|
||||
});
|
||||
} else {
|
||||
setAttachments(newAttachments);
|
||||
const shouldForce = newAttachments.some((attachment) => {
|
||||
const type = getAttachmentType(attachment);
|
||||
|
||||
return (type === 'audio' && !canSendAudios)
|
||||
|| (type === 'video' && !canSendVideos)
|
||||
|| (type === 'image' && !canSendPhotos);
|
||||
});
|
||||
|
||||
setShouldForceAsFile(Boolean(shouldForce && canSendDocuments));
|
||||
setShouldForceCompression(!canSendDocuments);
|
||||
}
|
||||
}, [attachments, fileSizeLimit, openLimitReachedModal, setAttachments],
|
||||
}, [
|
||||
attachments, canSendAudios, canSendDocuments, canSendPhotos, canSendVideos, chatId, fileSizeLimit,
|
||||
handleClearAttachments, openLimitReachedModal, setAttachments, showAllowedMessageTypesNotification,
|
||||
],
|
||||
);
|
||||
|
||||
const handleAppendFiles = useCallback(async (files: File[], isSpoiler?: boolean) => {
|
||||
@ -63,5 +102,23 @@ export default function useAttachmentModal({
|
||||
onCaptionUpdate: setHtml,
|
||||
handleClearAttachments,
|
||||
handleSetAttachments,
|
||||
shouldForceCompression,
|
||||
shouldForceAsFile,
|
||||
};
|
||||
}
|
||||
|
||||
function getAttachmentType(attachment: ApiAttachment) {
|
||||
if (SUPPORTED_IMAGE_CONTENT_TYPES.has(attachment.mimeType)) {
|
||||
return 'image';
|
||||
}
|
||||
|
||||
if (SUPPORTED_VIDEO_CONTENT_TYPES.has(attachment.mimeType)) {
|
||||
return 'video';
|
||||
}
|
||||
|
||||
if (SUPPORTED_AUDIO_CONTENT_TYPES.has(attachment.mimeType)) {
|
||||
return 'audio';
|
||||
}
|
||||
|
||||
return 'file';
|
||||
}
|
||||
|
||||
119
src/components/right/hooks/useManagePermissions.ts
Normal file
119
src/components/right/hooks/useManagePermissions.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import type React from '../../../lib/teact/teact';
|
||||
import { useCallback, useEffect, useState } from '../../../lib/teact/teact';
|
||||
|
||||
import type { ApiChatBannedRights } from '../../../api/types';
|
||||
|
||||
const FLOATING_BUTTON_ANIMATION_TIMEOUT_MS = 250;
|
||||
const MEDIA_PERMISSIONS: Array<keyof ApiChatBannedRights> = [
|
||||
'embedLinks',
|
||||
'sendPolls',
|
||||
'sendPhotos',
|
||||
'sendVideos',
|
||||
'sendRoundvideos',
|
||||
'sendVoices',
|
||||
'sendAudios',
|
||||
'sendDocs',
|
||||
'sendStickers',
|
||||
'sendGifs',
|
||||
];
|
||||
const MESSAGE_PERMISSIONS: typeof MEDIA_PERMISSIONS = [...MEDIA_PERMISSIONS, 'sendPlain'];
|
||||
|
||||
export default function useManagePermissions(defaultPermissions: ApiChatBannedRights | undefined) {
|
||||
const [permissions, setPermissions] = useState<ApiChatBannedRights>({});
|
||||
const [havePermissionChanged, setHavePermissionChanged] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setPermissions(defaultPermissions || {});
|
||||
setHavePermissionChanged(false);
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, FLOATING_BUTTON_ANIMATION_TIMEOUT_MS);
|
||||
}, [defaultPermissions]);
|
||||
|
||||
const handlePermissionChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name: targetName } = e.target;
|
||||
|
||||
const name = targetName as Exclude<keyof ApiChatBannedRights, 'untilDate'>;
|
||||
|
||||
function getUpdatedPermissionValue(value: true | undefined) {
|
||||
return value ? undefined : true;
|
||||
}
|
||||
|
||||
const oldPermissions = permissions;
|
||||
|
||||
let newPermissions: ApiChatBannedRights = {
|
||||
...oldPermissions,
|
||||
[name]: getUpdatedPermissionValue(oldPermissions[name]),
|
||||
...(name === 'sendStickers' && {
|
||||
sendGifs: getUpdatedPermissionValue(oldPermissions[name]),
|
||||
}),
|
||||
};
|
||||
|
||||
const checkMedia = () => {
|
||||
const mediaPermissions = MEDIA_PERMISSIONS.map((key) => newPermissions[key]);
|
||||
if (mediaPermissions.some((v) => !v)) {
|
||||
newPermissions = {
|
||||
...newPermissions,
|
||||
sendMedia: undefined,
|
||||
};
|
||||
} else if (mediaPermissions.every(Boolean)) {
|
||||
newPermissions = {
|
||||
...newPermissions,
|
||||
sendMedia: true,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
if (name !== 'sendMedia') {
|
||||
checkMedia();
|
||||
} else {
|
||||
newPermissions = {
|
||||
...newPermissions,
|
||||
...(MEDIA_PERMISSIONS.reduce((acc, key) => (
|
||||
Object.assign(acc, { [key]: newPermissions.sendMedia })
|
||||
), {})),
|
||||
};
|
||||
}
|
||||
|
||||
// Embed links can't be enabled if plain text is banned
|
||||
if (name !== 'embedLinks' && !newPermissions.embedLinks && newPermissions.sendPlain) {
|
||||
newPermissions = {
|
||||
...newPermissions,
|
||||
embedLinks: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (name !== 'sendPlain' && !newPermissions.embedLinks && newPermissions.sendPlain) {
|
||||
newPermissions = {
|
||||
...newPermissions,
|
||||
sendPlain: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (name !== 'sendMedia') {
|
||||
checkMedia();
|
||||
}
|
||||
|
||||
const sendMessages = MESSAGE_PERMISSIONS.every((key) => newPermissions[key]);
|
||||
newPermissions = {
|
||||
...newPermissions,
|
||||
sendMessages: sendMessages ? true : undefined,
|
||||
};
|
||||
|
||||
setPermissions(newPermissions);
|
||||
|
||||
setHavePermissionChanged(!defaultPermissions || Object.keys(newPermissions).some((k) => {
|
||||
const key = k as Exclude<keyof ApiChatBannedRights, 'untilDate'>;
|
||||
return Boolean(defaultPermissions[key]) !== Boolean(newPermissions[key]);
|
||||
}));
|
||||
}, [defaultPermissions, permissions]);
|
||||
|
||||
return {
|
||||
permissions,
|
||||
isLoading,
|
||||
havePermissionChanged,
|
||||
handlePermissionChange,
|
||||
setIsLoading,
|
||||
};
|
||||
}
|
||||
@ -100,6 +100,7 @@ const ManageChatRemovedUsers: FC<OwnProps & StateProps> = ({
|
||||
<PrivateChatInfo
|
||||
userId={member.userId}
|
||||
status={getRemovedBy(member)}
|
||||
forceShowSelf
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
|
||||
@ -63,9 +63,24 @@ type StateProps = {
|
||||
const GROUP_TITLE_EMPTY = 'Group title can\'t be empty';
|
||||
const GROUP_MAX_DESCRIPTION = 255;
|
||||
|
||||
const ALL_PERMISSIONS: Array<keyof ApiChatBannedRights> = [
|
||||
'sendMessages',
|
||||
'embedLinks',
|
||||
'sendPolls',
|
||||
'changeInfo',
|
||||
'inviteUsers',
|
||||
'pinMessages',
|
||||
'manageTopics',
|
||||
'sendPhotos',
|
||||
'sendVideos',
|
||||
'sendRoundvideos',
|
||||
'sendVoices',
|
||||
'sendAudios',
|
||||
'sendDocs',
|
||||
];
|
||||
// Some checkboxes control multiple rights, and some rights are not controlled from Permissions screen,
|
||||
// so we need to define the amount manually
|
||||
const TOTAL_PERMISSIONS_COUNT = 9;
|
||||
const TOTAL_PERMISSIONS_COUNT = ALL_PERMISSIONS.length + 1;
|
||||
|
||||
const ManageGroup: FC<OwnProps & StateProps> = ({
|
||||
chatId,
|
||||
@ -245,16 +260,7 @@ const ManageGroup: FC<OwnProps & StateProps> = ({
|
||||
return 0;
|
||||
}
|
||||
|
||||
let totalCount = [
|
||||
'sendMessages',
|
||||
'sendMedia',
|
||||
'embedLinks',
|
||||
'sendPolls',
|
||||
'changeInfo',
|
||||
'inviteUsers',
|
||||
'pinMessages',
|
||||
'manageTopics',
|
||||
].filter(
|
||||
let totalCount = ALL_PERMISSIONS.filter(
|
||||
(key) => !chat.defaultBannedRights![key as keyof ApiChatBannedRights],
|
||||
).length;
|
||||
|
||||
@ -347,7 +353,7 @@ const ManageGroup: FC<OwnProps & StateProps> = ({
|
||||
>
|
||||
<span className="title">{lang('ChannelPermissions')}</span>
|
||||
<span className="subtitle" dir="auto">
|
||||
{enabledPermissionsCount}/{TOTAL_PERMISSIONS_COUNT}
|
||||
{enabledPermissionsCount}/{TOTAL_PERMISSIONS_COUNT - (chat.isForum ? 0 : 1)}
|
||||
</span>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
|
||||
@ -1,15 +1,18 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useCallback, useEffect, useMemo, useState,
|
||||
memo, useCallback, useMemo, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import { ManagementScreens } from '../../../types';
|
||||
import type { ApiChat, ApiChatBannedRights, ApiChatMember } from '../../../api/types';
|
||||
|
||||
import stopEvent from '../../../util/stopEvent';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import { selectChat } from '../../../global/selectors';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import useManagePermissions from '../hooks/useManagePermissions';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Checkbox from '../../ui/Checkbox';
|
||||
@ -30,9 +33,11 @@ type StateProps = {
|
||||
currentUserId?: string;
|
||||
};
|
||||
|
||||
const FLOATING_BUTTON_ANIMATION_TIMEOUT_MS = 250;
|
||||
const ITEM_HEIGHT = 24 + 32;
|
||||
const BEFORE_ITEMS_COUNT = 2;
|
||||
const ITEMS_COUNT = 9;
|
||||
|
||||
function getLangKeyForBannedRightKey(key: string) {
|
||||
function getLangKeyForBannedRightKey(key: keyof ApiChatBannedRights) {
|
||||
switch (key) {
|
||||
case 'sendMessages':
|
||||
return 'UserRestrictionsNoSend';
|
||||
@ -52,6 +57,20 @@ function getLangKeyForBannedRightKey(key: string) {
|
||||
return 'UserRestrictionsPinMessages';
|
||||
case 'manageTopics':
|
||||
return 'GroupPermission.NoManageTopics';
|
||||
case 'sendPlain':
|
||||
return 'UserRestrictionsNoSendText';
|
||||
case 'sendDocs':
|
||||
return 'UserRestrictionsNoSendDocs';
|
||||
case 'sendRoundvideos':
|
||||
return 'UserRestrictionsNoSendRound';
|
||||
case 'sendVoices':
|
||||
return 'UserRestrictionsNoSendVoice';
|
||||
case 'sendAudios':
|
||||
return 'UserRestrictionsNoSendMusic';
|
||||
case 'sendVideos':
|
||||
return 'UserRestrictionsNoSendVideos';
|
||||
case 'sendPhotos':
|
||||
return 'UserRestrictionsNoSendPhotos';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
@ -67,11 +86,10 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
|
||||
}) => {
|
||||
const { updateChatDefaultBannedRights } = getActions();
|
||||
|
||||
const [permissions, setPermissions] = useState<ApiChatBannedRights>({});
|
||||
const [havePermissionChanged, setHavePermissionChanged] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const {
|
||||
permissions, havePermissionChanged, isLoading, handlePermissionChange, setIsLoading,
|
||||
} = useManagePermissions(chat?.defaultBannedRights);
|
||||
const lang = useLang();
|
||||
|
||||
const { isForum } = chat || {};
|
||||
|
||||
useHistoryBack({
|
||||
@ -92,30 +110,11 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
|
||||
onScreenSelect(ManagementScreens.GroupUserPermissions);
|
||||
}, [currentUserId, onChatMemberSelect, onScreenSelect]);
|
||||
|
||||
useEffect(() => {
|
||||
setPermissions((chat?.defaultBannedRights) || {});
|
||||
setHavePermissionChanged(false);
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, FLOATING_BUTTON_ANIMATION_TIMEOUT_MS);
|
||||
}, [chat]);
|
||||
|
||||
const handlePermissionChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name } = e.target;
|
||||
|
||||
function getUpdatedPermissionValue(value: true | undefined) {
|
||||
return value ? undefined : true;
|
||||
}
|
||||
|
||||
setPermissions((p) => ({
|
||||
...p,
|
||||
[name]: getUpdatedPermissionValue(p[name as Exclude<keyof ApiChatBannedRights, 'untilDate'>]),
|
||||
...(name === 'sendStickers' && {
|
||||
sendGifs: getUpdatedPermissionValue(p[name]),
|
||||
}),
|
||||
}));
|
||||
setHavePermissionChanged(true);
|
||||
}, []);
|
||||
const [isMediaDropdownOpen, setIsMediaDropdownOpen] = useState(false);
|
||||
const handleOpenMediaDropdown = useCallback((e: React.MouseEvent) => {
|
||||
stopEvent(e);
|
||||
setIsMediaDropdownOpen(!isMediaDropdownOpen);
|
||||
}, [isMediaDropdownOpen]);
|
||||
|
||||
const handleSavePermissions = useCallback(() => {
|
||||
if (!chat) {
|
||||
@ -124,7 +123,7 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
|
||||
|
||||
setIsLoading(true);
|
||||
updateChatDefaultBannedRights({ chatId: chat.id, bannedRights: permissions });
|
||||
}, [chat, permissions, updateChatDefaultBannedRights]);
|
||||
}, [chat, permissions, setIsLoading, updateChatDefaultBannedRights]);
|
||||
|
||||
const removedUsersCount = useMemo(() => {
|
||||
if (!chat || !chat.fullInfo || !chat.fullInfo.kickedMembers) {
|
||||
@ -150,10 +149,11 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const { defaultBannedRights } = chat;
|
||||
|
||||
return Object.keys(bannedRights).reduce((result, key) => {
|
||||
return Object.keys(bannedRights).reduce((result, k) => {
|
||||
const key = k as keyof ApiChatBannedRights;
|
||||
if (
|
||||
!bannedRights[key as keyof ApiChatBannedRights]
|
||||
|| (defaultBannedRights?.[key as keyof ApiChatBannedRights])
|
||||
!bannedRights[key]
|
||||
|| (defaultBannedRights?.[key])
|
||||
|| key === 'sendInline' || key === 'viewMessages' || key === 'sendGames'
|
||||
) {
|
||||
return result;
|
||||
@ -172,15 +172,19 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
|
||||
}, [chat, lang]);
|
||||
|
||||
return (
|
||||
<div className="Management">
|
||||
<div
|
||||
className="Management with-shifted-dropdown"
|
||||
style={`--shift-height: ${ITEMS_COUNT * ITEM_HEIGHT}px;`
|
||||
+ `--before-shift-height: ${BEFORE_ITEMS_COUNT * ITEM_HEIGHT}px;`}
|
||||
>
|
||||
<div className="custom-scroll">
|
||||
<div className="section">
|
||||
<div className="section without-bottom-shadow">
|
||||
<h3 className="section-heading" dir="auto">{lang('ChannelPermissionsHeader')}</h3>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendMessages"
|
||||
checked={!permissions.sendMessages}
|
||||
name="sendPlain"
|
||||
checked={!permissions.sendPlain}
|
||||
label={lang('UserRestrictionsSend')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
@ -192,77 +196,158 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
|
||||
checked={!permissions.sendMedia}
|
||||
label={lang('UserRestrictionsSendMedia')}
|
||||
blocking
|
||||
rightIcon={isMediaDropdownOpen ? 'up' : 'down'}
|
||||
onChange={handlePermissionChange}
|
||||
onClickLabel={handleOpenMediaDropdown}
|
||||
/>
|
||||
</div>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendStickers"
|
||||
checked={!permissions.sendStickers && !permissions.sendGifs}
|
||||
label={lang('UserRestrictionsSendStickers')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
<div className="DropdownListTrap">
|
||||
<div
|
||||
className={buildClassName(
|
||||
'DropdownList',
|
||||
isMediaDropdownOpen && 'DropdownList--open',
|
||||
)}
|
||||
>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendPhotos"
|
||||
checked={!permissions.sendPhotos}
|
||||
label={lang('UserRestrictionsSendPhotos')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendVideos"
|
||||
checked={!permissions.sendVideos}
|
||||
label={lang('UserRestrictionsSendVideos')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendStickers"
|
||||
checked={!permissions.sendStickers && !permissions.sendGifs}
|
||||
label={lang('UserRestrictionsSendStickers')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendAudios"
|
||||
checked={!permissions.sendAudios}
|
||||
label={lang('UserRestrictionsSendMusic')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendDocs"
|
||||
checked={!permissions.sendDocs}
|
||||
label={lang('UserRestrictionsSendFiles')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendVoices"
|
||||
checked={!permissions.sendVoices}
|
||||
label={lang('UserRestrictionsSendVoices')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendRoundvideos"
|
||||
checked={!permissions.sendRoundvideos}
|
||||
label={lang('UserRestrictionsSendRound')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="embedLinks"
|
||||
checked={!permissions.embedLinks}
|
||||
label={lang('UserRestrictionsEmbedLinks')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendPolls"
|
||||
checked={!permissions.sendPolls}
|
||||
label={lang('UserRestrictionsSendPolls')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendPolls"
|
||||
checked={!permissions.sendPolls}
|
||||
label={lang('UserRestrictionsSendPolls')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="embedLinks"
|
||||
checked={!permissions.embedLinks}
|
||||
label={lang('UserRestrictionsEmbedLinks')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="inviteUsers"
|
||||
checked={!permissions.inviteUsers}
|
||||
label={lang('UserRestrictionsInviteUsers')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="pinMessages"
|
||||
checked={!permissions.pinMessages}
|
||||
label={lang('UserRestrictionsPinMessages')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="changeInfo"
|
||||
checked={!permissions.changeInfo}
|
||||
label={lang('UserRestrictionsChangeInfo')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
{isForum && (
|
||||
|
||||
<div className={buildClassName('part', isMediaDropdownOpen && 'shifted')}>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="manageTopics"
|
||||
checked={!permissions.manageTopics}
|
||||
label={lang('CreateTopicsPermission')}
|
||||
name="inviteUsers"
|
||||
checked={!permissions.inviteUsers}
|
||||
label={lang('UserRestrictionsInviteUsers')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="pinMessages"
|
||||
checked={!permissions.pinMessages}
|
||||
label={lang('UserRestrictionsPinMessages')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="changeInfo"
|
||||
checked={!permissions.changeInfo}
|
||||
label={lang('UserRestrictionsChangeInfo')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
{isForum && (
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="manageTopics"
|
||||
checked={!permissions.manageTopics}
|
||||
label={lang('CreateTopicsPermission')}
|
||||
blocking
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="section">
|
||||
<div
|
||||
className={buildClassName(
|
||||
'section',
|
||||
isMediaDropdownOpen && 'shifted',
|
||||
)}
|
||||
>
|
||||
<ListItem
|
||||
icon="delete-user"
|
||||
multiline
|
||||
@ -274,7 +359,12 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
|
||||
</ListItem>
|
||||
</div>
|
||||
|
||||
<div className="section">
|
||||
<div
|
||||
className={buildClassName(
|
||||
'section',
|
||||
isMediaDropdownOpen && 'shifted',
|
||||
)}
|
||||
>
|
||||
<h3 className="section-heading" dir="auto">{lang('PrivacyExceptions')}</h3>
|
||||
|
||||
<ListItem
|
||||
@ -294,6 +384,7 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
|
||||
<PrivateChatInfo
|
||||
userId={member.userId}
|
||||
status={getMemberExceptions(member)}
|
||||
forceShowSelf
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
|
||||
@ -8,6 +8,9 @@ import type { ApiChat, ApiChatBannedRights } from '../../../api/types';
|
||||
import { ManagementScreens } from '../../../types';
|
||||
|
||||
import { selectChat } from '../../../global/selectors';
|
||||
import stopEvent from '../../../util/stopEvent';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import useManagePermissions from '../hooks/useManagePermissions';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
@ -33,6 +36,12 @@ type StateProps = {
|
||||
isFormFullyDisabled?: boolean;
|
||||
};
|
||||
|
||||
const ITEM_HEIGHT = 24 + 32;
|
||||
const SHIFT_HEIGHT_MINUS = 1;
|
||||
const BEFORE_ITEMS_COUNT = 2;
|
||||
const BEFORE_USER_INFO_HEIGHT = 96;
|
||||
const ITEMS_COUNT = 9;
|
||||
|
||||
const ManageGroupUserPermissions: FC<OwnProps & StateProps> = ({
|
||||
chat,
|
||||
selectedChatMemberId,
|
||||
@ -43,9 +52,17 @@ const ManageGroupUserPermissions: FC<OwnProps & StateProps> = ({
|
||||
}) => {
|
||||
const { updateChatMemberBannedRights } = getActions();
|
||||
|
||||
const [permissions, setPermissions] = useState<ApiChatBannedRights>({});
|
||||
const [havePermissionChanged, setHavePermissionChanged] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const selectedChatMember = useMemo(() => {
|
||||
if (!chat || !chat.fullInfo || !chat.fullInfo.members) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return chat.fullInfo.members.find(({ userId }) => userId === selectedChatMemberId);
|
||||
}, [chat, selectedChatMemberId]);
|
||||
|
||||
const {
|
||||
permissions, havePermissionChanged, isLoading, handlePermissionChange, setIsLoading,
|
||||
} = useManagePermissions(selectedChatMember?.bannedRights || chat?.defaultBannedRights);
|
||||
const [isBanConfirmationDialogOpen, openBanConfirmationDialog, closeBanConfirmationDialog] = useFlag();
|
||||
const lang = useLang();
|
||||
|
||||
@ -56,43 +73,12 @@ const ManageGroupUserPermissions: FC<OwnProps & StateProps> = ({
|
||||
onBack: onClose,
|
||||
});
|
||||
|
||||
const selectedChatMember = useMemo(() => {
|
||||
if (!chat || !chat.fullInfo || !chat.fullInfo.members) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return chat.fullInfo.members.find(({ userId }) => userId === selectedChatMemberId);
|
||||
}, [chat, selectedChatMemberId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (chat?.fullInfo && selectedChatMemberId && !selectedChatMember) {
|
||||
onScreenSelect(ManagementScreens.GroupPermissions);
|
||||
}
|
||||
}, [chat, onScreenSelect, selectedChatMember, selectedChatMemberId]);
|
||||
|
||||
useEffect(() => {
|
||||
setPermissions((selectedChatMember?.bannedRights) || (chat?.defaultBannedRights) || {});
|
||||
setHavePermissionChanged(false);
|
||||
setIsLoading(false);
|
||||
}, [chat, selectedChatMember]);
|
||||
|
||||
const handlePermissionChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name } = e.target;
|
||||
|
||||
function getUpdatedPermissionValue(value: true | undefined) {
|
||||
return value ? undefined : true;
|
||||
}
|
||||
|
||||
setPermissions((p) => ({
|
||||
...p,
|
||||
[name]: getUpdatedPermissionValue(p[name as Exclude<keyof ApiChatBannedRights, 'untilDate'>]),
|
||||
...(name === 'sendStickers' && {
|
||||
sendGifs: getUpdatedPermissionValue(p[name]),
|
||||
}),
|
||||
}));
|
||||
setHavePermissionChanged(true);
|
||||
}, []);
|
||||
|
||||
const handleSavePermissions = useCallback(() => {
|
||||
if (!chat || !selectedChatMemberId) {
|
||||
return;
|
||||
@ -104,7 +90,7 @@ const ManageGroupUserPermissions: FC<OwnProps & StateProps> = ({
|
||||
userId: selectedChatMemberId,
|
||||
bannedRights: permissions,
|
||||
});
|
||||
}, [chat, selectedChatMemberId, permissions, updateChatMemberBannedRights]);
|
||||
}, [chat, selectedChatMemberId, setIsLoading, updateChatMemberBannedRights, permissions]);
|
||||
|
||||
const handleBanFromGroup = useCallback(() => {
|
||||
if (!chat || !selectedChatMemberId) {
|
||||
@ -132,116 +118,216 @@ const ManageGroupUserPermissions: FC<OwnProps & StateProps> = ({
|
||||
return chat.defaultBannedRights[key];
|
||||
}, [chat, isFormFullyDisabled]);
|
||||
|
||||
const [isMediaDropdownOpen, setIsMediaDropdownOpen] = useState(false);
|
||||
const handleOpenMediaDropdown = useCallback((e: React.MouseEvent) => {
|
||||
stopEvent(e);
|
||||
setIsMediaDropdownOpen(!isMediaDropdownOpen);
|
||||
}, [isMediaDropdownOpen]);
|
||||
|
||||
if (!selectedChatMember) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="Management">
|
||||
<div
|
||||
className="Management with-shifted-dropdown"
|
||||
style={`--shift-height: ${ITEMS_COUNT * ITEM_HEIGHT - SHIFT_HEIGHT_MINUS}px;`
|
||||
+ `--before-shift-height: ${BEFORE_ITEMS_COUNT * ITEM_HEIGHT + BEFORE_USER_INFO_HEIGHT}px;`}
|
||||
>
|
||||
<div className="custom-scroll">
|
||||
<div className="section">
|
||||
<div className="section without-bottom-shadow">
|
||||
<ListItem inactive className="chat-item-clickable">
|
||||
<PrivateChatInfo userId={selectedChatMember.userId} />
|
||||
<PrivateChatInfo userId={selectedChatMember.userId} forceShowSelf />
|
||||
</ListItem>
|
||||
|
||||
<h3 className="section-heading mt-4" dir="auto">{lang('UserRestrictionsCanDo')}</h3>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendMessages"
|
||||
checked={!permissions.sendMessages}
|
||||
name="sendPlain"
|
||||
checked={!permissions.sendPlain}
|
||||
label={lang('UserRestrictionsSend')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('sendMessages')}
|
||||
disabled={getControlIsDisabled('sendPlain')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendMedia"
|
||||
checked={!permissions.sendMedia}
|
||||
label={lang('UserRestrictionsSendMedia')}
|
||||
blocking
|
||||
rightIcon={isMediaDropdownOpen ? 'up' : 'down'}
|
||||
disabled={getControlIsDisabled('sendMedia')}
|
||||
onChange={handlePermissionChange}
|
||||
onClickLabel={handleOpenMediaDropdown}
|
||||
/>
|
||||
</div>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendStickers"
|
||||
checked={!permissions.sendStickers && !permissions.sendGifs}
|
||||
label={lang('UserRestrictionsSendStickers')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('sendStickers')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
|
||||
<div className="DropdownListTrap">
|
||||
<div
|
||||
className={buildClassName(
|
||||
'DropdownList',
|
||||
isMediaDropdownOpen && 'DropdownList--open',
|
||||
)}
|
||||
>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendPhotos"
|
||||
checked={!permissions.sendPhotos}
|
||||
label={lang('UserRestrictionsSendPhotos')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('sendPhotos')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendVideos"
|
||||
checked={!permissions.sendVideos}
|
||||
label={lang('UserRestrictionsSendVideos')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('sendVideos')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendStickers"
|
||||
checked={!permissions.sendStickers && !permissions.sendGifs}
|
||||
label={lang('UserRestrictionsSendStickers')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('sendStickers')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendAudios"
|
||||
checked={!permissions.sendAudios}
|
||||
label={lang('UserRestrictionsSendMusic')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('sendAudios')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendDocs"
|
||||
checked={!permissions.sendDocs}
|
||||
label={lang('UserRestrictionsSendFiles')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('sendDocs')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendVoices"
|
||||
checked={!permissions.sendVoices}
|
||||
label={lang('UserRestrictionsSendVoices')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('sendVoices')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendRoundvideos"
|
||||
checked={!permissions.sendRoundvideos}
|
||||
label={lang('UserRestrictionsSendRound')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('sendRoundvideos')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="embedLinks"
|
||||
checked={!permissions.embedLinks}
|
||||
label={lang('UserRestrictionsEmbedLinks')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('embedLinks')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendPolls"
|
||||
checked={!permissions.sendPolls}
|
||||
label={lang('UserRestrictionsSendPolls')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('sendPolls')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="sendPolls"
|
||||
checked={!permissions.sendPolls}
|
||||
label={lang('UserRestrictionsSendPolls')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('sendPolls')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="embedLinks"
|
||||
checked={!permissions.embedLinks}
|
||||
label={lang('UserRestrictionsEmbedLinks')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('embedLinks')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="inviteUsers"
|
||||
checked={!permissions.inviteUsers}
|
||||
label={lang('UserRestrictionsInviteUsers')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('inviteUsers')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="pinMessages"
|
||||
checked={!permissions.pinMessages}
|
||||
label={lang('UserRestrictionsPinMessages')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('pinMessages')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="changeInfo"
|
||||
checked={!permissions.changeInfo}
|
||||
label={lang('UserRestrictionsChangeInfo')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('changeInfo')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
{isForum && (
|
||||
|
||||
<div className={buildClassName('part', isMediaDropdownOpen && 'shifted')}>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="manageTopics"
|
||||
checked={!permissions.manageTopics}
|
||||
label={lang('CreateTopicsPermission')}
|
||||
name="inviteUsers"
|
||||
checked={!permissions.inviteUsers}
|
||||
label={lang('UserRestrictionsInviteUsers')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('manageTopics')}
|
||||
disabled={getControlIsDisabled('inviteUsers')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="pinMessages"
|
||||
checked={!permissions.pinMessages}
|
||||
label={lang('UserRestrictionsPinMessages')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('pinMessages')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="changeInfo"
|
||||
checked={!permissions.changeInfo}
|
||||
label={lang('UserRestrictionsChangeInfo')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('changeInfo')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
{isForum && (
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
name="manageTopics"
|
||||
checked={!permissions.manageTopics}
|
||||
label={lang('CreateTopicsPermission')}
|
||||
blocking
|
||||
disabled={getControlIsDisabled('manageTopics')}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!isFormFullyDisabled && (
|
||||
<div className="section">
|
||||
<div
|
||||
className={buildClassName(
|
||||
'section',
|
||||
isMediaDropdownOpen && 'shifted',
|
||||
)}
|
||||
>
|
||||
<ListItem icon="delete-user" ripple destructive onClick={openBanConfirmationDialog}>
|
||||
{lang('UserRestrictionsBlock')}
|
||||
</ListItem>
|
||||
|
||||
@ -305,3 +305,61 @@
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.DropdownList {
|
||||
transition: 0.25s ease-in-out transform;
|
||||
transform: translateY(calc(-100%));
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
padding: 0 1.5rem 0 2.5rem;
|
||||
background: var(--color-background);
|
||||
|
||||
&--open {
|
||||
transform: translateY(-2rem);
|
||||
}
|
||||
}
|
||||
|
||||
.DropdownListTrap {
|
||||
height: 0;
|
||||
width: 100%;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: calc(var(--before-shift-height) + 2.5rem);
|
||||
background: var(--color-background);
|
||||
content: "";
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.with-shifted-dropdown {
|
||||
.ListItem, .section-heading {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.without-bottom-shadow {
|
||||
box-shadow: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.part {
|
||||
background-color: var(--color-background);
|
||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
||||
margin: 0 -1.5rem;
|
||||
padding: 0 1.5rem 1rem;
|
||||
}
|
||||
|
||||
.section, .part {
|
||||
position: relative;
|
||||
transition: 0.25s ease-in-out transform;
|
||||
|
||||
&.shifted {
|
||||
transform: translateY(var(--shift-height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,6 +70,7 @@
|
||||
.Checkbox-main {
|
||||
&::before,
|
||||
&::after {
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
@ -96,12 +97,19 @@
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: initial;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 0.25rem;
|
||||
}
|
||||
|
||||
.right-icon {
|
||||
margin-left: auto;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.subLabel {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { ChangeEvent } from 'react';
|
||||
import type { FC, TeactNode } from '../../lib/teact/teact';
|
||||
import React, { memo, useCallback } from '../../lib/teact/teact';
|
||||
import React, { memo, useCallback, useRef } from '../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import useLang from '../../hooks/useLang';
|
||||
@ -17,6 +17,7 @@ type OwnProps = {
|
||||
label: TeactNode;
|
||||
subLabel?: string;
|
||||
checked: boolean;
|
||||
rightIcon?: string;
|
||||
disabled?: boolean;
|
||||
tabIndex?: number;
|
||||
round?: boolean;
|
||||
@ -26,6 +27,7 @@ type OwnProps = {
|
||||
className?: string;
|
||||
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
onCheck?: (isChecked: boolean) => void;
|
||||
onClickLabel?: (e: React.MouseEvent) => void;
|
||||
};
|
||||
|
||||
const Checkbox: FC<OwnProps> = ({
|
||||
@ -41,10 +43,16 @@ const Checkbox: FC<OwnProps> = ({
|
||||
blocking,
|
||||
isLoading,
|
||||
className,
|
||||
rightIcon,
|
||||
onChange,
|
||||
onCheck,
|
||||
onClickLabel,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const labelRef = useRef<HTMLLabelElement>(null);
|
||||
|
||||
const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (onChange) {
|
||||
onChange(event);
|
||||
@ -55,6 +63,16 @@ const Checkbox: FC<OwnProps> = ({
|
||||
}
|
||||
}, [onChange, onCheck]);
|
||||
|
||||
function handleClick(event: React.MouseEvent) {
|
||||
if (event.target !== labelRef.current) {
|
||||
onClickLabel?.(event);
|
||||
}
|
||||
}
|
||||
|
||||
function handleInputClick(event: React.MouseEvent) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
const labelClassName = buildClassName(
|
||||
'Checkbox',
|
||||
disabled && 'disabled',
|
||||
@ -65,7 +83,13 @@ const Checkbox: FC<OwnProps> = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<label className={labelClassName} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
||||
<label
|
||||
className={labelClassName}
|
||||
dir={lang.isRtl ? 'rtl' : undefined}
|
||||
onClick={onClickLabel ? handleClick : undefined}
|
||||
ref={labelRef}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={id}
|
||||
@ -75,9 +99,13 @@ const Checkbox: FC<OwnProps> = ({
|
||||
disabled={disabled}
|
||||
tabIndex={tabIndex}
|
||||
onChange={handleChange}
|
||||
onClick={onClickLabel ? handleInputClick : undefined}
|
||||
/>
|
||||
<div className="Checkbox-main">
|
||||
<span className="label" dir="auto">{typeof label === 'string' ? renderText(label) : label}</span>
|
||||
<span className="label" dir="auto">
|
||||
{typeof label === 'string' ? renderText(label) : label}
|
||||
{rightIcon && <i className={`icon-${rightIcon} right-icon`} />}
|
||||
</span>
|
||||
{subLabel && <span className="subLabel" dir="auto">{renderText(subLabel)}</span>}
|
||||
</div>
|
||||
{isLoading && <Spinner />}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
bottom: 1rem;
|
||||
transform: translateY(calc(5rem - var(--call-header-height, 0rem)));
|
||||
transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
z-index: 1;
|
||||
z-index: 2;
|
||||
|
||||
body.animation-level-0 & {
|
||||
transition: none !important;
|
||||
|
||||
@ -80,7 +80,7 @@ import {
|
||||
selectIsCurrentUserPremium,
|
||||
selectForwardsContainVoiceMessages,
|
||||
selectTabState,
|
||||
selectThreadIdFromMessage,
|
||||
selectThreadIdFromMessage, selectForwardsCanBeSentToChat,
|
||||
} from '../../selectors';
|
||||
import {
|
||||
debounce, onTickEnd, rafPromise,
|
||||
@ -1377,6 +1377,11 @@ addActionHandler('setForwardChatOrTopic', async (global, actions, payload): Prom
|
||||
}
|
||||
}
|
||||
|
||||
if (!selectForwardsCanBeSentToChat(global, chatId, tabId)) {
|
||||
actions.showAllowedMessageTypesNotification({ chatId, tabId });
|
||||
return;
|
||||
}
|
||||
|
||||
global = updateTabState(global, {
|
||||
forwardMessages: {
|
||||
...selectTabState(global, tabId).forwardMessages,
|
||||
|
||||
@ -627,6 +627,19 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
setGlobal(global);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'updateMessageSendFailed': {
|
||||
const { chatId, localId, error } = update;
|
||||
|
||||
if (error.match(/CHAT_SEND_.+?FORBIDDEN/)) {
|
||||
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
||||
actions.showAllowedMessageTypesNotification({ chatId, tabId });
|
||||
});
|
||||
}
|
||||
|
||||
global = updateChatMessage(global, chatId, localId, { sendingState: 'messageSendingStateFailed' });
|
||||
setGlobal(global);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -15,13 +15,15 @@ import {
|
||||
selectChatMessage, selectCurrentChat, selectCurrentMessageList, selectTabState, selectIsTrustedBot, selectChat,
|
||||
} from '../../selectors';
|
||||
import generateIdFor from '../../../util/generateIdFor';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
import { compact, unique } from '../../../util/iteratees';
|
||||
import { getAllMultitabTokens, getCurrentTabId, reestablishMasterToSelf } from '../../../util/establishMultitabRole';
|
||||
import { getAllNotificationsCount } from '../../../util/folderManager';
|
||||
import updateIcon from '../../../util/updateIcon';
|
||||
import setPageTitle from '../../../util/updatePageTitle';
|
||||
import { updateTabState } from '../../reducers/tabs';
|
||||
import { getIsMobile, getIsTablet } from '../../../hooks/useAppLayout';
|
||||
import * as langProvider from '../../../util/langProvider';
|
||||
import { getAllowedAttachmentOptions } from '../../helpers';
|
||||
|
||||
export const APP_VERSION_URL = 'version.txt';
|
||||
const MAX_STORED_EMOJIS = 8 * 4; // Represents four rows of recent emojis
|
||||
@ -264,7 +266,7 @@ addActionHandler('reorderStickerSets', (global, actions, payload): ActionReturnT
|
||||
});
|
||||
|
||||
addActionHandler('showNotification', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId(), ...notification } = payload!;
|
||||
const { tabId = getCurrentTabId(), ...notification } = payload;
|
||||
notification.localId = generateIdFor({});
|
||||
|
||||
const newNotifications = [...selectTabState(global, tabId).notifications];
|
||||
@ -280,6 +282,44 @@ addActionHandler('showNotification', (global, actions, payload): ActionReturnTyp
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('showAllowedMessageTypesNotification', (global, actions, payload): ActionReturnType => {
|
||||
const { chatId, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) return;
|
||||
|
||||
const {
|
||||
canSendPlainText, canSendPhotos, canSendVideos, canSendDocuments, canSendAudios,
|
||||
canSendStickers, canSendRoundVideos, canSendVoices,
|
||||
} = getAllowedAttachmentOptions(chat);
|
||||
const allowedContent = compact([
|
||||
canSendPlainText ? 'Chat.SendAllowedContentTypeText' : undefined,
|
||||
canSendPhotos ? 'Chat.SendAllowedContentTypePhoto' : undefined,
|
||||
canSendVideos ? 'Chat.SendAllowedContentTypeVideo' : undefined,
|
||||
canSendVoices ? 'Chat.SendAllowedContentTypeVoiceMessage' : undefined,
|
||||
canSendRoundVideos ? 'Chat.SendAllowedContentTypeVideoMessage' : undefined,
|
||||
canSendDocuments ? 'Chat.SendAllowedContentTypeFile' : undefined,
|
||||
canSendAudios ? 'Chat.SendAllowedContentTypeMusic' : undefined,
|
||||
canSendStickers ? 'Chat.SendAllowedContentTypeSticker' : undefined,
|
||||
]).map((l) => langProvider.translate(l));
|
||||
|
||||
if (!allowedContent.length) {
|
||||
actions.showNotification({
|
||||
message: langProvider.translate('Chat.SendNotAllowedText'),
|
||||
tabId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const lastDelimiter = langProvider.translate('AutoDownloadSettings.LastDelimeter');
|
||||
const allowedContentString = allowedContent.join(', ').replace(/,([^,]*)$/, `${lastDelimiter}$1`);
|
||||
|
||||
actions.showNotification({
|
||||
message: langProvider.translate('Chat.SendAllowedContentText', allowedContentString),
|
||||
tabId,
|
||||
});
|
||||
});
|
||||
|
||||
addActionHandler('dismissNotification', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload;
|
||||
const newNotifications = selectTabState(global, tabId)
|
||||
@ -636,6 +676,7 @@ addActionHandler('updatePageTitle', (global, actions, payload): ActionReturnType
|
||||
|
||||
addCallback((global: GlobalState) => {
|
||||
if (global.notificationIndex === undefined || global.allNotificationsCount === undefined) return;
|
||||
// eslint-disable-next-line eslint-multitab-tt/no-getactions-in-actions
|
||||
const { updatePageTitle } = getActions();
|
||||
|
||||
const index = global.notificationIndex;
|
||||
|
||||
@ -217,6 +217,13 @@ export interface IAllowedAttachmentOptions {
|
||||
canSendStickers: boolean;
|
||||
canSendGifs: boolean;
|
||||
canAttachEmbedLinks: boolean;
|
||||
canSendPhotos: boolean;
|
||||
canSendVideos: boolean;
|
||||
canSendRoundVideos: boolean;
|
||||
canSendAudios: boolean;
|
||||
canSendVoices: boolean;
|
||||
canSendPlainText: boolean;
|
||||
canSendDocuments: boolean;
|
||||
}
|
||||
|
||||
export function getAllowedAttachmentOptions(chat?: ApiChat, isChatWithBot = false): IAllowedAttachmentOptions {
|
||||
@ -227,6 +234,13 @@ export function getAllowedAttachmentOptions(chat?: ApiChat, isChatWithBot = fals
|
||||
canSendStickers: false,
|
||||
canSendGifs: false,
|
||||
canAttachEmbedLinks: false,
|
||||
canSendPhotos: false,
|
||||
canSendVideos: false,
|
||||
canSendRoundVideos: false,
|
||||
canSendAudios: false,
|
||||
canSendVoices: false,
|
||||
canSendPlainText: false,
|
||||
canSendDocuments: false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -238,6 +252,13 @@ export function getAllowedAttachmentOptions(chat?: ApiChat, isChatWithBot = fals
|
||||
canSendStickers: isAdmin || !isUserRightBanned(chat, 'sendStickers'),
|
||||
canSendGifs: isAdmin || !isUserRightBanned(chat, 'sendGifs'),
|
||||
canAttachEmbedLinks: isAdmin || !isUserRightBanned(chat, 'embedLinks'),
|
||||
canSendPhotos: isAdmin || !isUserRightBanned(chat, 'sendPhotos'),
|
||||
canSendVideos: isAdmin || !isUserRightBanned(chat, 'sendVideos'),
|
||||
canSendRoundVideos: isAdmin || !isUserRightBanned(chat, 'sendRoundvideos'),
|
||||
canSendAudios: isAdmin || !isUserRightBanned(chat, 'sendAudios'),
|
||||
canSendVoices: isAdmin || !isUserRightBanned(chat, 'sendVoices'),
|
||||
canSendPlainText: isAdmin || !isUserRightBanned(chat, 'sendPlain'),
|
||||
canSendDocuments: isAdmin || !isUserRightBanned(chat, 'sendDocs'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -44,7 +44,7 @@ import {
|
||||
isServiceNotificationMessage,
|
||||
isUserId,
|
||||
isUserRightBanned,
|
||||
canSendReaction,
|
||||
canSendReaction, getAllowedAttachmentOptions,
|
||||
} from '../helpers';
|
||||
import { findLast } from '../../util/iteratees';
|
||||
import { selectIsStickerFavorite } from './symbols';
|
||||
@ -1279,3 +1279,42 @@ export function selectForwardsContainVoiceMessages<T extends GlobalState>(
|
||||
return Boolean(message.content.voice) || message.content.video?.isRound;
|
||||
});
|
||||
}
|
||||
|
||||
export function selectForwardsCanBeSentToChat<T extends GlobalState>(
|
||||
global: T,
|
||||
toChatId: string,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
) {
|
||||
const { messageIds, fromChatId } = selectTabState(global, tabId).forwardMessages;
|
||||
const chat = selectChat(global, toChatId);
|
||||
if (!messageIds || !chat) return false;
|
||||
|
||||
const chatMessages = selectChatMessages(global, fromChatId!);
|
||||
const {
|
||||
canSendVoices, canSendRoundVideos, canSendStickers, canSendDocuments, canSendAudios, canSendVideos,
|
||||
canSendPhotos, canSendGifs, canSendPlainText,
|
||||
} = getAllowedAttachmentOptions(chat);
|
||||
return !messageIds.some((messageId) => {
|
||||
const message = chatMessages[messageId];
|
||||
const isVoice = message.content.voice;
|
||||
const isRoundVideo = message.content.video?.isRound;
|
||||
const isPhoto = message.content.photo;
|
||||
const isGif = message.content.video?.isGif;
|
||||
const isVideo = message.content.video && !isRoundVideo && !isGif;
|
||||
const isAudio = message.content.audio;
|
||||
const isDocument = message.content.document;
|
||||
const isSticker = message.content.sticker;
|
||||
const isPlainText = message.content.text
|
||||
&& !isVoice && !isRoundVideo && !isSticker && !isDocument && !isAudio && !isVideo && !isPhoto && !isGif;
|
||||
|
||||
return (isVoice && !canSendVoices)
|
||||
|| (isRoundVideo && !canSendRoundVideos)
|
||||
|| (isSticker && !canSendStickers)
|
||||
|| (isDocument && !canSendDocuments)
|
||||
|| (isAudio && !canSendAudios)
|
||||
|| (isVideo && !canSendVideos)
|
||||
|| (isPhoto && !canSendPhotos)
|
||||
|| (isGif && !canSendGifs)
|
||||
|| (isPlainText && !canSendPlainText);
|
||||
});
|
||||
}
|
||||
|
||||
@ -2104,6 +2104,9 @@ export interface ActionPayloads {
|
||||
actionText?: string;
|
||||
action?: CallbackAction;
|
||||
} & WithTabId;
|
||||
showAllowedMessageTypesNotification: {
|
||||
chatId: string;
|
||||
} & WithTabId;
|
||||
dismissNotification: { localId: string } & WithTabId;
|
||||
|
||||
updatePageTitle: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user