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