Composer: Add silent post button and hide them on text (#5749)

This commit is contained in:
zubiden 2025-03-21 14:02:27 +04:00 committed by Alexander Zinchuk
parent 440001b938
commit 7522a16188
10 changed files with 233 additions and 91 deletions

View File

@ -735,27 +735,26 @@ async function getFullChannelInfo(
}; };
} }
export function updateChatMutedState({ export function updateChatNotifySettings({
chat, isMuted, mutedUntil = 0, chat, settings,
}: { }: {
chat: ApiChat; isMuted?: boolean; mutedUntil?: number; chat: ApiChat; settings: Partial<ApiPeerNotifySettings>;
}) { }) {
if (isMuted && !mutedUntil) {
mutedUntil = MAX_INT_32;
}
invokeRequest(new GramJs.account.UpdateNotifySettings({ invokeRequest(new GramJs.account.UpdateNotifySettings({
peer: new GramJs.InputNotifyPeer({ peer: new GramJs.InputNotifyPeer({
peer: buildInputPeer(chat.id, chat.accessHash), peer: buildInputPeer(chat.id, chat.accessHash),
}), }),
settings: new GramJs.InputPeerNotifySettings({ muteUntil: mutedUntil }), settings: new GramJs.InputPeerNotifySettings({
muteUntil: settings.mutedUntil,
showPreviews: settings.shouldShowPreviews,
silent: settings.isSilentPosting,
}),
})); }));
sendApiUpdate({ sendApiUpdate({
'@type': 'updateChatNotifySettings', '@type': 'updateChatNotifySettings',
chatId: chat.id, chatId: chat.id,
settings: { settings,
mutedUntil,
},
}); });
void requestChatUpdate({ void requestChatUpdate({

View File

@ -1271,6 +1271,18 @@
"AriaOpenBotMenu" = "Open bot menu"; "AriaOpenBotMenu" = "Open bot menu";
"AriaOpenSymbolMenu" = "Choose emoji, sticker or GIF"; "AriaOpenSymbolMenu" = "Choose emoji, sticker or GIF";
"AriaComposerOpenScheduled" = "Open scheduled messages"; "AriaComposerOpenScheduled" = "Open scheduled messages";
"AriaComposerBotKeyboard" = "Show bot keyboard";
"AriaComposerSilentPostingEnable" = "Enable silent notifications.";
"AriaComposerSilentPostingDisable" = "Disable silent notifications.";
"ComposerSilentPostingEnabledTootlip" = "Subscribers will receive a silent notification.";
"ComposerSilentPostingDisabledTootlip" = "Subscribers will be notified when you post.";
"ComposerPlaceholder" = "Message";
"ComposerPlaceholderBroadcast" = "Broadcast";
"ComposerPlaceholderBroadcastSilent" = "Silent Broadcast";
"ComposerPlaceholderTopic" = "Message in {topic}";
"ComposerPlaceholderTopicGeneral" = "Message in General";
"ComposerStoryPlaceholderLocked" = "Replies restricted";
"ComposerPlaceholderNoText" = "Text not allowed";
"AriaComposerCancelVoice" = "Cancel voice recording"; "AriaComposerCancelVoice" = "Cancel voice recording";
"PreviewForwardedMessage_one" = "{count} forwarded message"; "PreviewForwardedMessage_one" = "{count} forwarded message";
"PreviewForwardedMessage_other" = "{count} forwarded messages"; "PreviewForwardedMessage_other" = "{count} forwarded messages";

View File

@ -292,6 +292,24 @@
color: #fff; color: #fff;
} }
} }
.composer-action-buttons-container {
width: auto;
position: relative;
+ .AttachMenu {
margin-left: var(--action-button-compact-fix);
}
}
.composer-action-buttons {
display: flex;
top: 0;
right: 0;
left: auto;
width: auto;
height: auto;
}
} }
.mobile-symbol-menu-button { .mobile-symbol-menu-button {
@ -384,25 +402,32 @@
} }
.message-input-wrapper { .message-input-wrapper {
--action-button-size: var(--base-height, 3.5rem);
--action-button-compact-fix: -1rem;
display: flex; display: flex;
@media (max-width: 600px) {
--action-button-size: 2.875rem;
--action-button-compact-fix: -0.6875rem;
}
.input-scroller { .input-scroller {
margin-right: 0.5rem; --_scroller-right-gap: calc((var(--action-button-size) + var(--action-button-compact-fix) - 0.125rem));
padding-right: 0.25rem; margin-right: calc(-1 * var(--_scroller-right-gap));
padding-right: var(--_scroller-right-gap);
} }
> .Spinner { > .Spinner {
align-self: center; align-self: center;
--spinner-size: 1.5rem; --spinner-size: 1.5rem;
margin-right: -0.5rem; margin-right: 0.5rem;
} }
> .AttachMenu > .Button, .composer-action-button {
> .Button {
flex-shrink: 0; flex-shrink: 0;
background: none !important; background: none !important;
width: var(--base-height, 3.5rem); width: var(--action-button-size);
height: var(--base-height, 3.5rem); height: var(--action-button-size);
margin: 0; margin: 0;
padding: 0; padding: 0;
align-self: flex-end; align-self: flex-end;
@ -411,17 +436,8 @@
color: var(--color-composer-button); color: var(--color-composer-button);
} }
+ .Button, + .AttachMenu { + .composer-action-button {
margin-left: -1rem; margin-left: var(--action-button-compact-fix);
}
@media (max-width: 600px) {
width: 2.875rem;
height: 2.875rem;
+ .Button, + .AttachMenu {
margin-left: -0.6875rem;
}
} }
&.bot-menu { &.bot-menu {

View File

@ -66,6 +66,7 @@ import {
isSystemBot, isSystemBot,
isUserId, isUserId,
} from '../../global/helpers'; } from '../../global/helpers';
import { getChatNotifySettings } from '../../global/helpers/notifications';
import { import {
selectBot, selectBot,
selectCanPlayAnimatedEmojis, selectCanPlayAnimatedEmojis,
@ -86,6 +87,8 @@ import {
selectIsReactionPickerOpen, selectIsReactionPickerOpen,
selectIsRightColumnShown, selectIsRightColumnShown,
selectNewestMessageWithBotKeyboardButtons, selectNewestMessageWithBotKeyboardButtons,
selectNotifyDefaults,
selectNotifyException,
selectNoWebPage, selectNoWebPage,
selectPeerStory, selectPeerStory,
selectPerformanceSettingsValue, selectPerformanceSettingsValue,
@ -126,6 +129,7 @@ import useDerivedState from '../../hooks/useDerivedState';
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps'; import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
import useFlag from '../../hooks/useFlag'; import useFlag from '../../hooks/useFlag';
import useGetSelectionRange from '../../hooks/useGetSelectionRange'; import useGetSelectionRange from '../../hooks/useGetSelectionRange';
import useLang from '../../hooks/useLang';
import useLastCallback from '../../hooks/useLastCallback'; import useLastCallback from '../../hooks/useLastCallback';
import useOldLang from '../../hooks/useOldLang'; import useOldLang from '../../hooks/useOldLang';
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated'; import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
@ -170,6 +174,7 @@ import ReactionSelector from '../middle/message/reactions/ReactionSelector';
import Button from '../ui/Button'; import Button from '../ui/Button';
import ResponsiveHoverButton from '../ui/ResponsiveHoverButton'; import ResponsiveHoverButton from '../ui/ResponsiveHoverButton';
import Spinner from '../ui/Spinner'; import Spinner from '../ui/Spinner';
import Transition from '../ui/Transition';
import Avatar from './Avatar'; import Avatar from './Avatar';
import Icon from './icons/Icon'; import Icon from './icons/Icon';
import ReactionAnimatedEmoji from './reactions/ReactionAnimatedEmoji'; import ReactionAnimatedEmoji from './reactions/ReactionAnimatedEmoji';
@ -272,6 +277,7 @@ type StateProps =
canPlayEffect?: boolean; canPlayEffect?: boolean;
shouldPlayEffect?: boolean; shouldPlayEffect?: boolean;
maxMessageLength: number; maxMessageLength: number;
isSilentPosting?: boolean;
}; };
enum MainButtonState { enum MainButtonState {
@ -304,9 +310,6 @@ const Composer: FC<OwnProps & StateProps> = ({
canScheduleUntilOnline, canScheduleUntilOnline,
isReady, isReady,
isMobile, isMobile,
onDropHide,
onFocus,
onBlur,
editingMessage, editingMessage,
chatId, chatId,
threadId, threadId,
@ -376,7 +379,6 @@ const Composer: FC<OwnProps & StateProps> = ({
quickReplyMessages, quickReplyMessages,
quickReplies, quickReplies,
canSendQuickReplies, canSendQuickReplies,
onForward,
webPagePreview, webPagePreview,
noWebPage, noWebPage,
isContactRequirePremium, isContactRequirePremium,
@ -386,6 +388,11 @@ const Composer: FC<OwnProps & StateProps> = ({
canPlayEffect, canPlayEffect,
shouldPlayEffect, shouldPlayEffect,
maxMessageLength, maxMessageLength,
isSilentPosting,
onDropHide,
onFocus,
onBlur,
onForward,
}) => { }) => {
const { const {
sendMessage, sendMessage,
@ -412,9 +419,11 @@ const Composer: FC<OwnProps & StateProps> = ({
saveEffectInDraft, saveEffectInDraft,
setReactionEffect, setReactionEffect,
hideEffectInComposer, hideEffectInComposer,
updateChatSilentPosting,
} = getActions(); } = getActions();
const lang = useOldLang(); const oldLang = useOldLang();
const lang = useLang();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const inputRef = useRef<HTMLDivElement>(null); const inputRef = useRef<HTMLDivElement>(null);
@ -785,21 +794,21 @@ const Composer: FC<OwnProps & StateProps> = ({
const notificationNumber = customEmojiNotificationNumber.current; const notificationNumber = customEmojiNotificationNumber.current;
if (!notificationNumber) { if (!notificationNumber) {
showNotification({ showNotification({
message: lang('UnlockPremiumEmojiHint'), message: oldLang('UnlockPremiumEmojiHint'),
action: { action: {
action: 'openPremiumModal', action: 'openPremiumModal',
payload: { initialSection: 'animated_emoji' }, payload: { initialSection: 'animated_emoji' },
}, },
actionText: lang('PremiumMore'), actionText: oldLang('PremiumMore'),
}); });
} else { } else {
showNotification({ showNotification({
message: lang('UnlockPremiumEmojiHint2'), message: oldLang('UnlockPremiumEmojiHint2'),
action: { action: {
action: 'openChat', action: 'openChat',
payload: { id: currentUserId, shouldReplaceHistory: true }, payload: { id: currentUserId, shouldReplaceHistory: true },
}, },
actionText: lang('Open'), actionText: oldLang('Open'),
}); });
} }
customEmojiNotificationNumber.current = Number(!notificationNumber); customEmojiNotificationNumber.current = Number(!notificationNumber);
@ -910,7 +919,7 @@ const Composer: FC<OwnProps & StateProps> = ({
: slowMode.seconds - secondsSinceLastMessage!; : slowMode.seconds - secondsSinceLastMessage!;
showDialog({ showDialog({
data: { data: {
message: lang('SlowModeHint', formatMediaDuration(secondsRemaining)), message: oldLang('SlowModeHint', formatMediaDuration(secondsRemaining)),
isSlowMode: true, isSlowMode: true,
hasErrorKey: false, hasErrorKey: false,
}, },
@ -942,6 +951,7 @@ const Composer: FC<OwnProps & StateProps> = ({
if (!currentMessageList && !storyId) { if (!currentMessageList && !storyId) {
return; return;
} }
isSilent = isSilent || isSilentPosting;
const { text, entities } = parseHtmlAsFormattedText(getHtml()); const { text, entities } = parseHtmlAsFormattedText(getHtml());
if (!text && !attachmentsToSend.length) { if (!text && !attachmentsToSend.length) {
@ -1018,6 +1028,8 @@ const Composer: FC<OwnProps & StateProps> = ({
return; return;
} }
isSilent = isSilent || isSilentPosting;
let currentAttachments = attachments; let currentAttachments = attachments;
if (activeVoiceRecording) { if (activeVoiceRecording) {
@ -1129,7 +1141,7 @@ const Composer: FC<OwnProps & StateProps> = ({
threadId, threadId,
queryId, queryId,
scheduledAt, scheduledAt,
isSilent, isSilent: isSilent || isSilentPosting,
}); });
return; return;
} }
@ -1197,6 +1209,8 @@ const Composer: FC<OwnProps & StateProps> = ({
return; return;
} }
isSilent = isSilent || isSilentPosting;
if (isInScheduledList || isScheduleRequested) { if (isInScheduledList || isScheduleRequested) {
forceShowSymbolMenu(); forceShowSymbolMenu();
requestCalendar((scheduledAt) => { requestCalendar((scheduledAt) => {
@ -1225,6 +1239,8 @@ const Composer: FC<OwnProps & StateProps> = ({
return; return;
} }
isSilent = isSilent || isSilentPosting;
sticker = { sticker = {
...sticker, ...sticker,
isPreloadedGlobally: true, isPreloadedGlobally: true,
@ -1261,6 +1277,8 @@ const Composer: FC<OwnProps & StateProps> = ({
return; return;
} }
isSilent = isSilent || isSilentPosting;
if (isInScheduledList || isScheduleRequested) { if (isInScheduledList || isScheduleRequested) {
requestCalendar((scheduledAt) => { requestCalendar((scheduledAt) => {
handleMessageSchedule({ handleMessageSchedule({
@ -1308,7 +1326,7 @@ const Composer: FC<OwnProps & StateProps> = ({
}); });
closePollModal(); closePollModal();
} else { } else {
sendMessage({ messageList: currentMessageList, poll }); sendMessage({ messageList: currentMessageList, poll, isSilent: isSilentPosting });
closePollModal(); closePollModal();
} }
}); });
@ -1378,6 +1396,17 @@ const Composer: FC<OwnProps & StateProps> = ({
}); });
}); });
const handleToggleSilentPosting = useLastCallback(() => {
const newValue = !isSilentPosting;
updateChatSilentPosting({ chatId, isEnabled: newValue });
showNotification({
localId: 'silentPosting',
icon: newValue ? 'mute' : 'unmute',
message: lang(`ComposerSilentPosting${newValue ? 'Enabled' : 'Disabled'}Tootlip`),
});
});
useEffect(() => { useEffect(() => {
if (isRightColumnShown && isMobile) { if (isRightColumnShown && isMobile) {
closeSymbolMenu(); closeSymbolMenu();
@ -1396,10 +1425,11 @@ const Composer: FC<OwnProps & StateProps> = ({
} }
}, [isSelectModeActive, enableHover, disableHover, isReady]); }, [isSelectModeActive, enableHover, disableHover, isReady]);
const withBotMenuButton = isChatWithBot && botMenuButton?.type === 'webApp' && !editingMessage; const hasText = useDerivedState(() => Boolean(getHtml()), [getHtml]);
const isBotMenuButtonOpen = useDerivedState(() => {
return withBotMenuButton && !getHtml() && !activeVoiceRecording; const withBotMenuButton = isChatWithBot && botMenuButton?.type === 'webApp' && !editingMessage
}, [withBotMenuButton, getHtml, activeVoiceRecording]); && messageListType === 'thread';
const isBotMenuButtonOpen = withBotMenuButton && !hasText && !activeVoiceRecording;
const [timedPlaceholderLangKey, timedPlaceholderDate] = useMemo(() => { const [timedPlaceholderLangKey, timedPlaceholderDate] = useMemo(() => {
if (slowMode?.nextSendDate) { if (slowMode?.nextSendDate) {
@ -1419,11 +1449,33 @@ const Composer: FC<OwnProps & StateProps> = ({
|| isCustomSendMenuOpen || Boolean(activeVoiceRecording) || attachments.length > 0 || isInputHasFocus; || isCustomSendMenuOpen || Boolean(activeVoiceRecording) || attachments.length > 0 || isInputHasFocus;
const isReactionSelectorOpen = isComposerHasFocus && !isReactionPickerOpen && isInStoryViewer && !isAttachMenuOpen const isReactionSelectorOpen = isComposerHasFocus && !isReactionPickerOpen && isInStoryViewer && !isAttachMenuOpen
&& !isSymbolMenuOpen; && !isSymbolMenuOpen;
const placeholderForForumAsMessages = chat?.isForum && chat?.isForumAsMessages && threadId === MAIN_THREAD_ID
? (replyToTopic const placeholder = useMemo(() => {
? lang('Chat.InputPlaceholderReplyInTopic', replyToTopic.title) if (activeVoiceRecording && windowWidth <= SCREEN_WIDTH_TO_HIDE_PLACEHOLDER) {
: lang('Message.Placeholder.MessageInGeneral')) return '';
: undefined; }
if (!isComposerBlocked) {
if (botKeyboardPlaceholder) return botKeyboardPlaceholder;
if (inputPlaceholder) return inputPlaceholder;
if (chat?.isForum && chat?.isForumAsMessages && threadId === MAIN_THREAD_ID) {
return replyToTopic
? lang('ComposerPlaceholderTopic', { topic: replyToTopic.title })
: lang('ComposerPlaceholderTopicGeneral');
}
if (isChannel) {
return lang(isSilentPosting ? 'ComposerPlaceholderBroadcastSilent' : 'ComposerPlaceholderBroadcast');
}
return lang('ComposerPlaceholder');
}
if (isInStoryViewer) return lang('ComposerStoryPlaceholderLocked');
return lang('ComposerPlaceholderNoText');
}, [
activeVoiceRecording, botKeyboardPlaceholder, chat, inputPlaceholder, isChannel, isComposerBlocked,
isInStoryViewer, isSilentPosting, lang, replyToTopic, threadId, windowWidth,
]);
useEffect(() => { useEffect(() => {
if (isComposerHasFocus) { if (isComposerHasFocus) {
@ -1452,7 +1504,7 @@ const Composer: FC<OwnProps & StateProps> = ({
if (areVoiceMessagesNotAllowed) { if (areVoiceMessagesNotAllowed) {
if (!canSendVoiceByPrivacy) { if (!canSendVoiceByPrivacy) {
showNotification({ showNotification({
message: lang('VoiceMessagesRestrictedByPrivacy', chat?.title), message: oldLang('VoiceMessagesRestrictedByPrivacy', chat?.title),
}); });
} else if (!canSendVoices) { } else if (!canSendVoices) {
showAllowedMessageTypesNotification({ chatId }); showAllowedMessageTypesNotification({ chatId });
@ -1788,9 +1840,10 @@ const Composer: FC<OwnProps & StateProps> = ({
round round
color="translucent" color="translucent"
onClick={isSendAsMenuOpen ? closeSendAsMenu : handleSendAsMenuOpen} onClick={isSendAsMenuOpen ? closeSendAsMenu : handleSendAsMenuOpen}
ariaLabel={lang('SendMessageAsTitle')} ariaLabel={oldLang('SendMessageAsTitle')}
className={buildClassName( className={buildClassName(
'send-as-button', 'send-as-button',
'composer-action-button',
shouldAnimateSendAsButtonRef.current && 'appear-animation', shouldAnimateSendAsButtonRef.current && 'appear-animation',
)} )}
> >
@ -1840,13 +1893,7 @@ const Composer: FC<OwnProps & StateProps> = ({
isReady={isReady} isReady={isReady}
isActive={!hasAttachments} isActive={!hasAttachments}
getHtml={getHtml} getHtml={getHtml}
placeholder={ placeholder={placeholder}
activeVoiceRecording && windowWidth <= SCREEN_WIDTH_TO_HIDE_PLACEHOLDER
? ''
: (!isComposerBlocked
? (botKeyboardPlaceholder || inputPlaceholder || lang(placeholderForForumAsMessages || 'Message'))
: isInStoryViewer ? lang('StoryRepliesLocked') : lang('Chat.PlaceholderTextNotAllowed'))
}
timedPlaceholderDate={timedPlaceholderDate} timedPlaceholderDate={timedPlaceholderDate}
timedPlaceholderLangKey={timedPlaceholderLangKey} timedPlaceholderLangKey={timedPlaceholderLangKey}
forcedPlaceholder={inlineBotHelp} forcedPlaceholder={inlineBotHelp}
@ -1866,29 +1913,55 @@ const Composer: FC<OwnProps & StateProps> = ({
{isInlineBotLoading && Boolean(inlineBotId) && ( {isInlineBotLoading && Boolean(inlineBotId) && (
<Spinner color="gray" /> <Spinner color="gray" />
)} )}
{withScheduledButton && ( <Transition
<Button className="composer-action-buttons-container"
round slideClassName="composer-action-buttons"
faded activeKey={Number(hasText)}
className="scheduled-button" direction="inverse"
color="translucent" name="slideFadeAndroid"
onClick={handleAllScheduledClick} >
ariaLabel="Open scheduled messages" {!hasText && (
> <>
<Icon name="schedule" /> {isChannel && (
</Button> <Button
)} round
{Boolean(botKeyboardMessageId) && !activeVoiceRecording && !editingMessage && ( faded
<ResponsiveHoverButton className="composer-action-button"
className={isBotKeyboardOpen ? 'activated' : ''} color="translucent"
round onClick={handleToggleSilentPosting}
color="translucent" ariaLabel={lang(
onActivate={openBotKeyboard} isSilentPosting ? 'AriaComposerSilentPostingDisable' : 'AriaComposerSilentPostingEnable',
ariaLabel="Open bot command keyboard" )}
> >
<Icon name="bot-command" /> <Icon name={isSilentPosting ? 'mute' : 'unmute'} />
</ResponsiveHoverButton> </Button>
)} )}
{withScheduledButton && (
<Button
round
faded
className="composer-action-button scheduled-button"
color="translucent"
onClick={handleAllScheduledClick}
ariaLabel={lang('AriaComposerOpenScheduled')}
>
<Icon name="schedule" />
</Button>
)}
{Boolean(botKeyboardMessageId) && !activeVoiceRecording && !editingMessage && (
<ResponsiveHoverButton
className={buildClassName('composer-action-button', isBotKeyboardOpen && 'activated')}
round
color="translucent"
onActivate={openBotKeyboard}
ariaLabel={lang('AriaComposerBotKeyboard')}
>
<Icon name="bot-command" />
</ResponsiveHoverButton>
)}
</>
)}
</Transition>
</> </>
)} )}
{activeVoiceRecording && Boolean(currentRecordTime) && ( {activeVoiceRecording && Boolean(currentRecordTime) && (
@ -1968,7 +2041,7 @@ const Composer: FC<OwnProps & StateProps> = ({
className={buildClassName('view-once', isViewOnceEnabled && 'active')} className={buildClassName('view-once', isViewOnceEnabled && 'active')}
round round
color="secondary" color="secondary"
ariaLabel={lang('Chat.PlayOnceVoiceMessageTooltip')} ariaLabel={oldLang('Chat.PlayOnceVoiceMessageTooltip')}
onClick={toogleViewOnceEnabled} onClick={toogleViewOnceEnabled}
> >
<Icon name="view-once" /> <Icon name="view-once" />
@ -1994,7 +2067,7 @@ const Composer: FC<OwnProps & StateProps> = ({
onClick={handleLikeStory} onClick={handleLikeStory}
onContextMenu={handleStoryPickerContextMenu} onContextMenu={handleStoryPickerContextMenu}
onMouseDown={handleBeforeStoryPickerContextMenu} onMouseDown={handleBeforeStoryPickerContextMenu}
ariaLabel={lang('AccDescrLike')} ariaLabel={oldLang('AccDescrLike')}
ref={storyReactionRef} ref={storyReactionRef}
> >
{sentStoryReaction && ( {sentStoryReaction && (
@ -2023,7 +2096,7 @@ const Composer: FC<OwnProps & StateProps> = ({
disabled={areVoiceMessagesNotAllowed} disabled={areVoiceMessagesNotAllowed}
allowDisabledClick allowDisabledClick
noFastClick noFastClick
ariaLabel={lang(sendButtonAriaLabel)} ariaLabel={oldLang(sendButtonAriaLabel)}
onClick={mainButtonHandler} onClick={mainButtonHandler}
onContextMenu={ onContextMenu={
mainButtonState === MainButtonState.Send && canShowCustomSendMenu ? handleContextMenu : undefined mainButtonState === MainButtonState.Send && canShowCustomSendMenu ? handleContextMenu : undefined
@ -2140,6 +2213,11 @@ export default memo(withGlobal<OwnProps>(
const canSendQuickReplies = isChatWithUser && !isChatWithBot && !isInScheduledList && !isChatWithSelf; const canSendQuickReplies = isChatWithUser && !isChatWithBot && !isInScheduledList && !isChatWithSelf;
const noWebPage = selectNoWebPage(global, chatId, threadId); const noWebPage = selectNoWebPage(global, chatId, threadId);
const isSilentPosting = chat && getChatNotifySettings(
chat,
selectNotifyDefaults(global),
selectNotifyException(global, chatId),
)?.isSilentPosting;
const areEffectsSupported = isChatWithUser && !isChatWithBot const areEffectsSupported = isChatWithUser && !isChatWithBot
&& !isInScheduledList && !isChatWithSelf && type !== 'story' && chatId !== SERVICE_NOTIFICATIONS_USER_ID; && !isInScheduledList && !isChatWithSelf && type !== 'story' && chatId !== SERVICE_NOTIFICATIONS_USER_ID;
@ -2227,6 +2305,7 @@ export default memo(withGlobal<OwnProps>(
canPlayEffect, canPlayEffect,
shouldPlayEffect, shouldPlayEffect,
maxMessageLength, maxMessageLength,
isSilentPosting,
}; };
}, },
)(Composer)); )(Composer));

View File

@ -20,6 +20,7 @@ import {
getMessageWebPagePhoto, getMessageWebPagePhoto,
getMessageWebPageVideo, getMessageWebPageVideo,
} from '../../../global/helpers'; } from '../../../global/helpers';
import buildClassName from '../../../util/buildClassName';
import { getDebugLogs } from '../../../util/debugConsole'; import { getDebugLogs } from '../../../util/debugConsole';
import { validateFiles } from '../../../util/files'; import { validateFiles } from '../../../util/files';
import { openSystemFilesDialog } from '../../../util/systemFilesDialog'; import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
@ -175,7 +176,7 @@ const AttachMenu: FC<OwnProps> = ({
editingMessage && canEditMedia ? ( editingMessage && canEditMedia ? (
<ResponsiveHoverButton <ResponsiveHoverButton
id="replace-menu-button" id="replace-menu-button"
className={isAttachMenuOpen ? 'AttachMenu--button activated' : 'AttachMenu--button'} className={buildClassName('AttachMenu--button composer-action-button', isAttachMenuOpen && 'activated')}
round round
color="translucent" color="translucent"
onActivate={handleToggleAttachMenu} onActivate={handleToggleAttachMenu}
@ -189,7 +190,7 @@ const AttachMenu: FC<OwnProps> = ({
<ResponsiveHoverButton <ResponsiveHoverButton
id="attach-menu-button" id="attach-menu-button"
disabled={Boolean(editingMessage)} disabled={Boolean(editingMessage)}
className={isAttachMenuOpen ? 'AttachMenu--button activated' : 'AttachMenu--button'} className={buildClassName('AttachMenu--button composer-action-button', isAttachMenuOpen && 'activated')}
round round
color="translucent" color="translucent"
onActivate={handleToggleAttachMenu} onActivate={handleToggleAttachMenu}

View File

@ -46,7 +46,7 @@ const BotMenuButton: FC<OwnProps> = ({
return ( return (
<Button <Button
className={buildClassName('bot-menu', isOpen && 'open')} className={buildClassName('composer-action-button bot-menu', isOpen && 'open')}
round round
color="translucent" color="translucent"
disabled={isDisabled} disabled={isDisabled}

View File

@ -92,7 +92,7 @@ const SymbolMenuButton: FC<OwnProps> = ({
const [contextMenuAnchor, setContextMenuAnchor] = useState<IAnchorPosition | undefined>(undefined); const [contextMenuAnchor, setContextMenuAnchor] = useState<IAnchorPosition | undefined>(undefined);
const symbolMenuButtonClassName = buildClassName( const symbolMenuButtonClassName = buildClassName(
'mobile-symbol-menu-button', 'composer-action-button mobile-symbol-menu-button',
!isReady && 'not-ready', !isReady && 'not-ready',
isSymbolMenuLoaded isSymbolMenuLoaded
? (isSymbolMenuOpen && 'menu-opened') ? (isSymbolMenuOpen && 'menu-opened')
@ -157,7 +157,7 @@ const SymbolMenuButton: FC<OwnProps> = ({
</Button> </Button>
) : ( ) : (
<ResponsiveHoverButton <ResponsiveHoverButton
className={buildClassName('symbol-menu-button', isSymbolMenuOpen && 'activated')} className={buildClassName('composer-action-button symbol-menu-button', isSymbolMenuOpen && 'activated')}
round round
color="translucent" color="translucent"
onActivate={handleActivateSymbolMenu} onActivate={handleActivateSymbolMenu}

View File

@ -23,6 +23,7 @@ import {
CHAT_LIST_LOAD_SLICE, CHAT_LIST_LOAD_SLICE,
DEBUG, DEBUG,
GLOBAL_SUGGESTED_CHANNELS_ID, GLOBAL_SUGGESTED_CHANNELS_ID,
MAX_INT_32,
RE_TG_LINK, RE_TG_LINK,
SAVED_FOLDER_ID, SAVED_FOLDER_ID,
SERVICE_NOTIFICATIONS_USER_ID, SERVICE_NOTIFICATIONS_USER_ID,
@ -625,13 +626,29 @@ addActionHandler('requestSavedDialogUpdate', async (global, actions, payload): P
}); });
addActionHandler('updateChatMutedState', (global, actions, payload): ActionReturnType => { addActionHandler('updateChatMutedState', (global, actions, payload): ActionReturnType => {
const { chatId, isMuted, mutedUntil } = payload; const { chatId, isMuted } = payload;
let { mutedUntil } = payload;
const chat = selectChat(global, chatId);
if (!chat) {
return;
}
if (isMuted && !mutedUntil) {
mutedUntil = MAX_INT_32;
}
void callApi('updateChatNotifySettings', { chat, settings: { mutedUntil } });
});
addActionHandler('updateChatSilentPosting', (global, actions, payload): ActionReturnType => {
const { chatId, isEnabled } = payload;
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
if (!chat) { if (!chat) {
return; return;
} }
void callApi('updateChatMutedState', { chat, isMuted, mutedUntil }); void callApi('updateChatNotifySettings', { chat, settings: { isSilentPosting: isEnabled } });
}); });
addActionHandler('updateTopicMutedState', (global, actions, payload): ActionReturnType => { addActionHandler('updateTopicMutedState', (global, actions, payload): ActionReturnType => {

View File

@ -1051,6 +1051,10 @@ export interface ActionPayloads {
isMuted?: boolean; isMuted?: boolean;
mutedUntil?: number; mutedUntil?: number;
}; };
updateChatSilentPosting: {
chatId: string;
isEnabled: boolean;
};
updateChat: { updateChat: {
chatId: string; chatId: string;

View File

@ -1087,6 +1087,17 @@ export interface LangPair {
'AriaOpenBotMenu': undefined; 'AriaOpenBotMenu': undefined;
'AriaOpenSymbolMenu': undefined; 'AriaOpenSymbolMenu': undefined;
'AriaComposerOpenScheduled': undefined; 'AriaComposerOpenScheduled': undefined;
'AriaComposerBotKeyboard': undefined;
'AriaComposerSilentPostingEnable': undefined;
'AriaComposerSilentPostingDisable': undefined;
'ComposerSilentPostingEnabledTootlip': undefined;
'ComposerSilentPostingDisabledTootlip': undefined;
'ComposerPlaceholder': undefined;
'ComposerPlaceholderBroadcast': undefined;
'ComposerPlaceholderBroadcastSilent': undefined;
'ComposerPlaceholderTopicGeneral': undefined;
'ComposerStoryPlaceholderLocked': undefined;
'ComposerPlaceholderNoText': undefined;
'AriaComposerCancelVoice': undefined; 'AriaComposerCancelVoice': undefined;
'PreviewEditMessage': undefined; 'PreviewEditMessage': undefined;
'FileDropZoneTitle': undefined; 'FileDropZoneTitle': undefined;
@ -1706,6 +1717,9 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
'MediaViewDownloading': { 'MediaViewDownloading': {
'count': V; 'count': V;
}; };
'ComposerPlaceholderTopic': {
'topic': V;
};
'ChannelManagementLinkDiscussion': { 'ChannelManagementLinkDiscussion': {
'group': V; 'group': V;
'channel': V; 'channel': V;