tweb/src/components/chat/topbar.ts
Eduard Kuzmenko 34bdf75789 Some notification fixes
Unread chats counter on mobile screens
Fix scrolling top peers on touch
Fix wrapping draft emojis
Fix avatar transition on swipe-to-reply
2023-01-31 18:38:29 +04:00

974 lines
34 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import type {Channel} from '../../lib/appManagers/appChatsManager';
import type {AppSidebarRight} from '../sidebarRight';
import type Chat from './chat';
import {RIGHT_COLUMN_ACTIVE_CLASSNAME} from '../sidebarRight';
import mediaSizes, {ScreenSize} from '../../helpers/mediaSizes';
import {IS_SAFARI} from '../../environment/userAgent';
import rootScope from '../../lib/rootScope';
import AvatarElement from '../avatar';
import Button from '../button';
import ButtonIcon from '../buttonIcon';
import ButtonMenuToggle from '../buttonMenuToggle';
import ChatAudio from './audio';
import ChatPinnedMessage from './pinnedMessage';
import ListenerSetter from '../../helpers/listenerSetter';
import PopupDeleteDialog from '../popups/deleteDialog';
import appNavigationController from '../appNavigationController';
import {LEFT_COLUMN_ACTIVE_CLASSNAME} from '../sidebarLeft';
import PeerTitle from '../peerTitle';
import {i18n} from '../../lib/langPack';
import findUpClassName from '../../helpers/dom/findUpClassName';
import blurActiveElement from '../../helpers/dom/blurActiveElement';
import cancelEvent from '../../helpers/dom/cancelEvent';
import {attachClickEvent} from '../../helpers/dom/clickEvent';
import findUpTag from '../../helpers/dom/findUpTag';
import {toast, toastNew} from '../toast';
import replaceContent from '../../helpers/dom/replaceContent';
import {ChatFull, Chat as MTChat, GroupCall} from '../../layer';
import PopupPickUser from '../popups/pickUser';
import PopupPeer from '../popups/peer';
import AppEditContactTab from '../sidebarRight/tabs/editContact';
import appMediaPlaybackController from '../appMediaPlaybackController';
import IS_GROUP_CALL_SUPPORTED from '../../environment/groupCallSupport';
import IS_CALL_SUPPORTED from '../../environment/callSupport';
import {CallType} from '../../lib/calls/types';
import PopupMute from '../popups/mute';
import {AppManagers} from '../../lib/appManagers/managers';
import hasRights from '../../lib/appManagers/utils/chats/hasRights';
import wrapPeerTitle from '../wrappers/peerTitle';
import groupCallsController from '../../lib/calls/groupCallsController';
import apiManagerProxy from '../../lib/mtproto/mtprotoworker';
import {makeMediaSize} from '../../helpers/mediaSize';
import {FOLDER_ID_ALL} from '../../lib/mtproto/mtproto_config';
import formatNumber from '../../helpers/number/formatNumber';
type ButtonToVerify = {element?: HTMLElement, verify: () => boolean | Promise<boolean>};
export default class ChatTopbar {
public container: HTMLDivElement;
private btnBack: HTMLButtonElement;
private btnBackBadge: HTMLElement;
private chatInfo: HTMLDivElement;
private avatarElement: AvatarElement;
private title: HTMLDivElement;
private subtitle: HTMLDivElement;
private chatUtils: HTMLDivElement;
private btnJoin: HTMLButtonElement;
private btnPinned: HTMLButtonElement;
private btnCall: HTMLButtonElement;
private btnGroupCall: HTMLButtonElement;
private btnMute: HTMLButtonElement;
private btnSearch: HTMLButtonElement;
private btnMore: HTMLElement;
private chatAudio: ChatAudio;
public pinnedMessage: ChatPinnedMessage;
private setUtilsRAF: number;
private setPeerStatusInterval: number;
public listenerSetter: ListenerSetter;
private menuButtons: Parameters<typeof ButtonMenuToggle>[0]['buttons'];
private buttonsToVerify: ButtonToVerify[];
private chatInfoContainer: HTMLDivElement;
private person: HTMLDivElement;
constructor(
private chat: Chat,
private appSidebarRight: AppSidebarRight,
private managers: AppManagers
) {
this.listenerSetter = new ListenerSetter();
this.menuButtons = [];
this.buttonsToVerify = [];
}
public construct() {
// this.chat.log.error('Topbar construction');
this.container = document.createElement('div');
this.container.classList.add('sidebar-header', 'topbar', 'hide');
this.container.dataset.floating = '0';
this.btnBack = ButtonIcon('left sidebar-close-button', {noRipple: true});
this.btnBackBadge = document.createElement('span');
this.btnBackBadge.classList.add('badge', 'badge-20', 'badge-primary', 'back-unread-badge');
this.btnBack.append(this.btnBackBadge);
// * chat info section
this.chatInfoContainer = document.createElement('div');
this.chatInfoContainer.classList.add('chat-info-container');
this.chatInfo = document.createElement('div');
this.chatInfo.classList.add('chat-info');
const person = this.person = document.createElement('div');
person.classList.add('person');
const content = document.createElement('div');
content.classList.add('content');
const top = document.createElement('div');
top.classList.add('top');
this.title = document.createElement('div');
this.title.classList.add('user-title');
top.append(this.title);
const bottom = document.createElement('div');
bottom.classList.add('bottom');
if(this.subtitle) {
bottom.append(this.subtitle);
}
content.append(top, bottom);
if(this.avatarElement) {
person.append(this.avatarElement);
}
person.append(content);
this.chatInfo.append(person);
// * chat utils section
this.chatUtils = document.createElement('div');
this.chatUtils.classList.add('chat-utils');
this.chatAudio = new ChatAudio(this, this.chat, this.managers);
if(this.menuButtons.length) {
this.btnMore = ButtonMenuToggle({
listenerSetter: this.listenerSetter,
direction: 'bottom-left',
buttons: this.menuButtons,
onOpen: async(e, element) => {
const deleteButton = this.menuButtons[this.menuButtons.length - 1];
if(deleteButton?.element) {
const deleteButtonText = await this.managers.appPeersManager.getDeleteButtonText(this.peerId);
deleteButton.element.lastChild.replaceWith(i18n(deleteButtonText));
}
}
});
}
this.chatUtils.append(...[
// this.chatAudio ? this.chatAudio.divAndCaption.container : null,
this.pinnedMessage ? this.pinnedMessage.pinnedMessageContainer.divAndCaption.container : null,
this.btnJoin,
this.btnPinned,
this.btnCall,
this.btnGroupCall,
this.btnMute,
this.btnSearch,
this.btnMore
].filter(Boolean));
this.pushButtonToVerify(this.btnCall, this.verifyCallButton.bind(this, 'voice'));
this.pushButtonToVerify(this.btnGroupCall, this.verifyVideoChatButton);
this.chatInfoContainer.append(this.btnBack, this.chatInfo, this.chatUtils);
this.container.append(this.chatInfoContainer);
if(this.chatAudio) {
// this.container.append(this.chatAudio.divAndCaption.container, this.chatUtils);
this.container.append(this.chatAudio.divAndCaption.container);
}
// * construction end
// * fix topbar overflow section
this.listenerSetter.add(window)('resize', this.onResize);
this.listenerSetter.add(mediaSizes)('changeScreen', this.onChangeScreen);
attachClickEvent(this.container, (e) => {
const container = findUpClassName(e.target, 'pinned-container');
blurActiveElement();
if(container) {
cancelEvent(e);
if(findUpClassName(e.target, 'progress-line')) {
return;
}
const mid = +container.dataset.mid;
if(container.classList.contains('pinned-message')) {
// if(!this.pinnedMessage.locked) {
this.pinnedMessage.followPinnedMessage(mid);
// }
} else {
const peerId = container.dataset.peerId.toPeerId();
const searchContext = appMediaPlaybackController.getSearchContext();
this.chat.appImManager.setInnerPeer({
peerId,
lastMsgId: mid,
type: searchContext.isScheduled ? 'scheduled' : (searchContext.threadId ? 'discussion' : undefined),
threadId: searchContext.threadId
});
}
} else {
if(mediaSizes.activeScreen === ScreenSize.medium && document.body.classList.contains(LEFT_COLUMN_ACTIVE_CLASSNAME)) {
onBtnBackClick();
} else if(findUpTag(e.target, 'AVATAR-ELEMENT')) {
this.appSidebarRight.toggleSidebar(!document.body.classList.contains(RIGHT_COLUMN_ACTIVE_CLASSNAME));
} else {
this.appSidebarRight.toggleSidebar(true);
}
}
}, {listenerSetter: this.listenerSetter});
const onBtnBackClick = (e?: Event) => {
if(e) {
cancelEvent(e);
}
// const item = appNavigationController.findItemByType('chat');
// * return manually to chat by arrow, since can't get back to
if(mediaSizes.activeScreen === ScreenSize.medium && document.body.classList.contains(LEFT_COLUMN_ACTIVE_CLASSNAME)) {
this.chat.appImManager.setPeer({peerId: this.peerId});
} else {
const isFirstChat = this.chat.appImManager.chats.indexOf(this.chat) === 0;
appNavigationController.back(isFirstChat ? 'im' : 'chat');
/* return;
if(mediaSizes.activeScreen === ScreenSize.medium && !appNavigationController.findItemByType('chat')) {
this.chat.appImManager.setPeer(0);
blurActiveElement();
} else {
appNavigationController.back('chat');
} */
}
};
attachClickEvent(this.btnBack, onBtnBackClick, {listenerSetter: this.listenerSetter});
}
private pushButtonToVerify(element: HTMLElement, verify: ButtonToVerify['verify']) {
if(!element) {
return;
}
this.buttonsToVerify.push({element, verify});
}
private verifyButtons = (e?: Event) => {
const isMenuOpen = !!e || !!(this.btnMore && this.btnMore.classList.contains('menu-open'));
e && cancelEvent(e);
const r = async() => {
const buttons = this.buttonsToVerify.concat(isMenuOpen ? this.menuButtons as any : []);
const results = await Promise.all(buttons.map(async(button) => {
return {
result: await button.verify(),
button
}
}));
results.forEach(({button, result}) => {
button.element.classList.toggle('hide', !result);
});
};
r();
};
private verifyVideoChatButton = async(type?: 'group' | 'broadcast') => {
if(!IS_GROUP_CALL_SUPPORTED || this.peerId.isUser() || this.chat.type !== 'chat' || this.chat.threadId) return false;
const currentGroupCall = groupCallsController.groupCall;
const chatId = this.peerId.toChatId();
if(currentGroupCall?.chatId === chatId) {
return false;
}
if(type) {
if(((await this.managers.appPeersManager.isBroadcast(this.peerId)) && type === 'group') ||
((await this.managers.appPeersManager.isAnyGroup(this.peerId)) && type === 'broadcast')) {
return false;
}
}
const chat = await this.managers.appChatsManager.getChat(chatId);
return (chat as MTChat.chat).pFlags?.call_active || hasRights(chat, 'manage_call');
};
private verifyCallButton = async(type?: CallType) => {
if(!IS_CALL_SUPPORTED || !this.peerId.isUser()) return false;
const userId = this.peerId.toUserId();
const userFull = await this.managers.appProfileManager.getCachedFullUser(userId);
return !!userFull && !!(type === 'voice' ? userFull.pFlags.phone_calls_available : userFull.pFlags.video_calls_available);
};
public constructUtils() {
this.menuButtons = [{
icon: 'search',
text: 'Search',
onClick: () => {
this.chat.initSearch();
},
verify: () => mediaSizes.isMobile
}, /* {
icon: 'pinlist',
text: 'Pinned Messages',
onClick: () => this.openPinned(false),
verify: () => mediaSizes.isMobile
}, */{
icon: 'mute',
text: 'ChatList.Context.Mute',
onClick: this.onMuteClick,
verify: async() => this.chat.type === 'chat' && rootScope.myId !== this.peerId && !(await this.managers.appNotificationsManager.isPeerLocalMuted({peerId: this.peerId, respectType: false, threadId: this.chat.threadId}))
}, {
icon: 'unmute',
text: 'ChatList.Context.Unmute',
onClick: () => {
this.managers.appMessagesManager.togglePeerMute({peerId: this.peerId, threadId: this.chat.threadId});
},
verify: async() => this.chat.type === 'chat' && rootScope.myId !== this.peerId && (await this.managers.appNotificationsManager.isPeerLocalMuted({peerId: this.peerId, respectType: false, threadId: this.chat.threadId}))
}, {
icon: 'comments',
text: 'ViewDiscussion',
onClick: () => {
const middleware = this.chat.bubbles.getMiddleware();
Promise.resolve(this.managers.appProfileManager.getChannelFull(this.peerId.toChatId())).then((channelFull) => {
if(middleware() && channelFull.linked_chat_id) {
this.chat.appImManager.setInnerPeer({
peerId: channelFull.linked_chat_id.toPeerId(true)
});
}
});
},
verify: async() => {
const chatFull = await this.managers.appProfileManager.getCachedFullChat(this.peerId.toChatId());
return this.chat.type === 'chat' && !!(chatFull as ChatFull.channelFull)?.linked_chat_id;
}
}, {
icon: 'phone',
text: 'Call',
onClick: this.onCallClick.bind(this, 'voice'),
verify: this.verifyCallButton.bind(this, 'voice')
}, {
icon: 'videocamera',
text: 'VideoCall',
onClick: this.onCallClick.bind(this, 'video'),
verify: this.verifyCallButton.bind(this, 'video')
}, {
icon: 'videochat',
text: 'PeerInfo.Action.LiveStream',
onClick: this.onJoinGroupCallClick,
verify: this.verifyVideoChatButton.bind(this, 'broadcast')
}, {
icon: 'videochat',
text: 'PeerInfo.Action.VoiceChat',
onClick: this.onJoinGroupCallClick,
verify: this.verifyVideoChatButton.bind(this, 'group')
}, {
icon: 'select',
text: 'Chat.Menu.SelectMessages',
onClick: () => {
const selection = this.chat.selection;
selection.toggleSelection(true, true);
apiManagerProxy.getState().then((state) => {
if(state.chatContextMenuHintWasShown) {
return;
}
const original = selection.toggleByElement.bind(selection);
selection.toggleByElement = async(bubble) => {
this.managers.appStateManager.pushToState('chatContextMenuHintWasShown', true);
toast(i18n('Chat.Menu.Hint'));
selection.toggleByElement = original;
selection.toggleByElement(bubble);
};
});
},
verify: () => !this.chat.selection.isSelecting && !!this.chat.bubbles.getRenderedLength()
}, {
icon: 'select',
text: 'Chat.Menu.ClearSelection',
onClick: () => {
this.chat.selection.cancelSelection();
},
verify: () => this.chat.selection.isSelecting
}, {
icon: 'adduser',
text: 'AddContact',
onClick: () => {
if(!this.appSidebarRight.isTabExists(AppEditContactTab)) {
const tab = this.appSidebarRight.createTab(AppEditContactTab);
tab.peerId = this.peerId;
tab.open();
this.appSidebarRight.toggleSidebar(true);
}
},
verify: async() => this.peerId.isUser() && !(await this.managers.appPeersManager.isContact(this.peerId))
}, {
icon: 'forward',
text: 'ShareContact',
onClick: () => {
const contactPeerId = this.peerId;
new PopupPickUser({
peerTypes: ['dialogs', 'contacts'],
onSelect: (peerId) => {
return new Promise((resolve, reject) => {
new PopupPeer('', {
titleLangKey: 'SendMessageTitle',
descriptionLangKey: 'SendContactToGroupText',
descriptionLangArgs: [new PeerTitle({peerId, dialog: true}).element],
buttons: [{
langKey: 'Send',
callback: () => {
resolve();
this.managers.appMessagesManager.sendContact(peerId, contactPeerId);
this.chat.appImManager.setInnerPeer({peerId});
}
}, {
langKey: 'Cancel',
callback: () => {
reject();
},
isCancel: true
}],
peerId,
overlayClosable: true
}).show();
});
},
placeholder: 'ShareModal.Search.Placeholder',
chatRightsAction: 'send_messages',
selfPresence: 'ChatYourSelf'
});
},
verify: async() => rootScope.myId !== this.peerId && this.peerId.isUser() && (await this.managers.appPeersManager.isContact(this.peerId)) && !!(await this.managers.appUsersManager.getUser(this.peerId.toUserId())).phone
}, {
icon: 'bots',
text: 'Settings',
onClick: () => {
this.managers.appMessagesManager.sendText(this.peerId, '/settings');
},
verify: async() => {
try {
const attachMenuBot = await this.managers.appAttachMenuBotsManager.getAttachMenuBot(this.peerId.toUserId());
return !!attachMenuBot?.pFlags?.has_settings;
} catch(err) {
return false;
}
}
}, {
icon: 'lock',
text: 'BlockUser',
onClick: () => {
new PopupPeer('', {
peerId: this.peerId,
titleLangKey: 'BlockUser',
descriptionLangKey: 'AreYouSureBlockContact2',
descriptionLangArgs: [new PeerTitle({peerId: this.peerId}).element],
buttons: [{
langKey: 'BlockUser',
isDanger: true,
callback: () => {
this.managers.appUsersManager.toggleBlock(this.peerId, true).then((value) => {
if(value) {
toastNew({langPackKey: 'UserBlocked'});
}
});
}
}]
}).show();
},
verify: async() => {
if(!this.peerId.isUser()) return false;
const userFull = await this.managers.appProfileManager.getCachedFullUser(this.peerId.toUserId());
return this.peerId !== rootScope.myId && userFull && !userFull.pFlags?.blocked;
}
}, {
icon: 'lockoff',
text: 'Unblock',
onClick: () => {
this.managers.appUsersManager.toggleBlock(this.peerId, false).then((value) => {
if(value) {
toastNew({langPackKey: 'UserUnblocked'});
}
});
},
verify: async() => {
const userFull = await this.managers.appProfileManager.getCachedFullUser(this.peerId.toUserId());
return !!userFull?.pFlags?.blocked;
}
}, {
icon: 'delete danger',
text: 'Delete',
onClick: () => {
new PopupDeleteDialog(this.peerId/* , 'leave' */);
},
verify: async() => this.chat.type === 'chat' && !!(await this.managers.appMessagesManager.getDialogOnly(this.peerId))
}];
this.btnSearch = ButtonIcon('search');
this.attachClickEvent(this.btnSearch, (e) => {
this.chat.initSearch();
}, true);
}
public attachClickEvent(el: HTMLElement, cb: (e: MouseEvent) => void, noBlur?: boolean) {
attachClickEvent(el, (e) => {
cancelEvent(e);
!noBlur && blurActiveElement();
cb(e);
}, {listenerSetter: this.listenerSetter});
}
private onCallClick(type: CallType) {
this.chat.appImManager.callUser(this.peerId.toUserId(), type);
}
private onJoinGroupCallClick = () => {
this.chat.appImManager.joinGroupCall(this.peerId);
};
private constructAvatar() {
const avatarElement = new AvatarElement();
avatarElement.isDialog = true;
avatarElement.classList.add('avatar-42', 'person-avatar');
return avatarElement;
}
private get peerId() {
return this.chat.peerId;
}
public constructPeerHelpers() {
this.avatarElement = this.constructAvatar();
this.subtitle = document.createElement('div');
this.subtitle.classList.add('info');
this.pinnedMessage = new ChatPinnedMessage(this, this.chat, this.managers);
this.btnJoin = Button('btn-primary btn-color-primary chat-join hide');
this.btnCall = ButtonIcon('phone');
this.btnGroupCall = ButtonIcon('videochat');
this.btnPinned = ButtonIcon('pinlist');
this.btnMute = ButtonIcon('mute');
this.attachClickEvent(this.btnCall, this.onCallClick.bind(this, 'voice'));
this.attachClickEvent(this.btnGroupCall, this.onJoinGroupCallClick);
this.attachClickEvent(this.btnPinned, () => {
this.openPinned(true);
});
this.attachClickEvent(this.btnMute, this.onMuteClick);
this.attachClickEvent(this.btnJoin, async() => {
const middleware = this.chat.bubbles.getMiddleware();
this.btnJoin.setAttribute('disabled', 'true');
const chatId = this.peerId.toChatId();
let promise: Promise<any>;
if(await this.managers.appChatsManager.isChannel(chatId)) {
promise = this.managers.appChatsManager.joinChannel(chatId);
} else {
promise = this.managers.appChatsManager.addChatUser(chatId, rootScope.myId);
}
promise.finally(() => {
if(!middleware()) {
return;
}
this.btnJoin.removeAttribute('disabled');
});
});
this.listenerSetter.add(rootScope)('folder_unread', (folder) => {
if(folder.id !== FOLDER_ID_ALL) {
return;
}
const size = folder.unreadUnmutedPeerIds.size;
this.btnBackBadge.textContent = size ? '' + formatNumber(size, 1) : '';
// this.btnBack.classList.remove('tgico-left', 'tgico-previous');
// this.btnBack.classList.add(size ? 'tgico-previous' : 'tgico-left');
});
this.listenerSetter.add(rootScope)('chat_update', async(chatId) => {
if(this.peerId === chatId.toPeerId(true)) {
const chat = await this.managers.appChatsManager.getChat(chatId) as Channel/* | Chat */;
this.btnJoin.classList.toggle('hide', !(chat as Channel)?.pFlags?.left);
this.setUtilsWidth();
this.verifyButtons();
}
});
this.listenerSetter.add(rootScope)('dialog_notify_settings', (dialog) => {
if(dialog.peerId === this.peerId) {
this.setMutedState();
}
});
this.listenerSetter.add(rootScope)('peer_typings', ({peerId}) => {
if(this.peerId === peerId) {
this.setPeerStatus();
}
});
this.listenerSetter.add(rootScope)('user_update', (userId) => {
if(this.peerId === userId.toPeerId()) {
this.setPeerStatus();
}
});
this.listenerSetter.add(rootScope)('peer_full_update', (peerId) => {
if(this.peerId === peerId) {
this.verifyButtons();
}
});
this.chat.addEventListener('setPeer', (mid, isTopMessage) => {
const middleware = this.chat.bubbles.getMiddleware();
apiManagerProxy.getState().then((state) => {
if(!middleware() || !this.pinnedMessage) return;
this.pinnedMessage.hidden = !!state.hiddenPinnedMessages[this.chat.peerId];
if(isTopMessage) {
this.pinnedMessage.unsetScrollDownListener();
this.pinnedMessage.testMid(mid, 0); // * because slider will not let get bubble by document.elementFromPoint
} else if(!this.pinnedMessage.locked) {
this.pinnedMessage.handleFollowingPinnedMessage();
this.pinnedMessage.testMid(mid);
}
});
});
this.listenerSetter.add(rootScope)('peer_pinned_messages', ({peerId, mids}) => {
if(this.chat.type !== 'pinned' || peerId !== this.peerId) {
return;
}
if(mids) {
this.setTitle();
}
});
this.setPeerStatusInterval = window.setInterval(this.setPeerStatus, 60e3);
return this;
}
public openPinned(byCurrent: boolean) {
this.chat.appImManager.setInnerPeer({
peerId: this.peerId,
lastMsgId: byCurrent ? +this.pinnedMessage.pinnedMessageContainer.divAndCaption.container.dataset.mid : 0,
type: 'pinned'
});
}
private onMuteClick = () => {
new PopupMute(this.peerId);
};
private onResize = () => {
this.setUtilsWidth(true);
this.setFloating();
};
private onChangeScreen = (from: ScreenSize, to: ScreenSize) => {
this.container.classList.toggle('is-pinned-floating', mediaSizes.isMobile);
// this.chatAudio && this.chatAudio.divAndCaption.container.classList.toggle('is-floating', to === ScreenSize.mobile);
this.pinnedMessage && this.pinnedMessage.pinnedMessageContainer.divAndCaption.container.classList.toggle('is-floating', to === ScreenSize.mobile);
this.onResize();
};
public destroy() {
// this.chat.log.error('Topbar destroying');
this.listenerSetter.removeAll();
window.clearInterval(this.setPeerStatusInterval);
this.pinnedMessage?.destroy(); // * возможно это можно не делать
this.chatAudio?.destroy();
delete this.chatAudio;
delete this.pinnedMessage;
}
public cleanup() {
if(!this.chat.peerId) {
this.container.classList.add('hide');
}
}
public async finishPeerChange(isTarget: boolean) {
const {peerId, threadId} = this.chat;
let newAvatar: AvatarElement;
if(this.chat.type === 'chat') {
if(this.avatarElement?.peerId !== this.peerId || this.avatarElement.threadId !== this.chat.threadId) {
newAvatar = this.constructAvatar();
} else {
newAvatar = this.avatarElement;
}
}
const [isBroadcast, isAnyChat, chat, _, setTitleCallback, setStatusCallback, state] = await Promise.all([
this.managers.appPeersManager.isBroadcast(peerId),
this.managers.appPeersManager.isAnyChat(peerId),
peerId.isAnyChat() ? this.managers.appChatsManager.getChat(peerId.toChatId()) : undefined,
newAvatar ? newAvatar.updateWithOptions({peerId, threadId, wrapOptions: {customEmojiSize: makeMediaSize(32, 32)}}) : undefined,
this.setTitleManual(),
this.setPeerStatusManual(true),
apiManagerProxy.getState()
]);
return () => {
const canHaveSomeButtons = !(this.chat.type === 'pinned' || this.chat.type === 'scheduled');
this.btnMute && this.btnMute.classList.toggle('hide', !isBroadcast || !canHaveSomeButtons);
if(this.btnJoin) {
if(isAnyChat && !this.chat.isRestricted && canHaveSomeButtons) {
replaceContent(this.btnJoin, i18n(isBroadcast ? 'Chat.Subscribe' : 'ChannelJoin'));
this.btnJoin.classList.toggle('hide', !(chat as MTChat.chat)?.pFlags?.left);
} else {
this.btnJoin.classList.add('hide');
}
}
if(this.btnSearch) {
this.btnSearch.classList.toggle('hide', !canHaveSomeButtons);
}
if(this.btnPinned) {
this.btnPinned.classList.toggle('hide', !canHaveSomeButtons);
}
if(this.avatarElement !== newAvatar) {
if(newAvatar) {
if(this.avatarElement) {
this.avatarElement.replaceWith(newAvatar);
} else {
this.person.prepend(newAvatar);
}
}
this.avatarElement?.remove();
this.avatarElement = newAvatar;
}
this.setUtilsWidth();
this.verifyButtons();
if(this.btnMore) {
this.btnMore.classList.toggle('hide', !canHaveSomeButtons);
}
const isPinnedMessagesNeeded = this.chat.isPinnedMessagesNeeded();
if(isPinnedMessagesNeeded || this.chat.type === 'discussion') {
if(this.chat.wasAlreadyUsed || !this.pinnedMessage) { // * change
const newPinnedMessage = new ChatPinnedMessage(this, this.chat, this.managers);
if(this.pinnedMessage) {
this.pinnedMessage.pinnedMessageContainer.divAndCaption.container.replaceWith(newPinnedMessage.pinnedMessageContainer.divAndCaption.container);
this.pinnedMessage.destroy();
// this.pinnedMessage.pinnedMessageContainer.toggle(true);
} else {
this.chatUtils.prepend(this.pinnedMessage.pinnedMessageContainer.divAndCaption.container);
}
this.pinnedMessage = newPinnedMessage;
}
if(isPinnedMessagesNeeded) {
this.pinnedMessage.hidden = !!state.hiddenPinnedMessages[peerId];
} else if(this.chat.type === 'discussion') {
this.pinnedMessage.pinnedMid = this.chat.threadId;
this.pinnedMessage.count = 1;
this.pinnedMessage.pinnedIndex = 0;
this.pinnedMessage._setPinnedMessage();
}
} else if(this.pinnedMessage) {
this.pinnedMessage.destroy();
this.pinnedMessage = undefined;
}
setTitleCallback();
setStatusCallback?.();
this.subtitle.classList.toggle('hide', !setStatusCallback);
this.setMutedState();
this.container.classList.remove('hide');
};
}
public async setTitleManual(count?: number) {
const {peerId, threadId} = this.chat;
const middleware = () => this.chat.bubbles.getMiddleware();
let titleEl: HTMLElement, icons: Element[];
if(this.chat.type === 'pinned') {
if(count === undefined) titleEl = i18n('Loading');
else titleEl = i18n('PinnedMessagesCount', [count]);
if(count === undefined) {
this.managers.appMessagesManager.getSearchCounters(peerId, [{_: 'inputMessagesFilterPinned'}], false).then((result) => {
if(!middleware()) return;
const count = result[0].count;
this.setTitle(count);
// ! костыль х2, это нужно делать в другом месте
if(!count) {
this.chat.appImManager.setPeer(); // * close tab
// ! костыль, это скроет закреплённые сообщения сразу, вместо того, чтобы ждать пока анимация перехода закончится
const originalChat = this.chat.appImManager.chat;
if(originalChat.topbar.pinnedMessage) {
originalChat.topbar.pinnedMessage.pinnedMessageContainer.toggle(true);
}
}
});
}
} else if(this.chat.type === 'scheduled') {
titleEl = i18n(peerId === rootScope.myId ? 'Reminders' : 'ScheduledMessages');
} else if(this.chat.type === 'discussion') {
if(count === undefined) {
const result = await this.managers.acknowledged.appMessagesManager.getHistory(peerId, 0, 1, 0, threadId);
if(!middleware()) return;
if(result.cached) {
const historyResult = await result.result;
if(!middleware()) return;
count = historyResult.count;
} else result.result.then((historyResult) => {
if(!middleware()) return;
this.setTitle(historyResult.count);
});
}
if(count === undefined) titleEl = i18n('Loading');
else titleEl = i18n('Chat.Title.Comments', [count]);
} else if(this.chat.type === 'chat') {
[titleEl/* , icons */] = await Promise.all([
wrapPeerTitle({
peerId,
dialog: true,
withIcons: !threadId,
threadId: threadId
})
// generateTitleIcons(peerId)
]);
if(!middleware()) {
return;
}
}
return () => {
replaceContent(this.title, titleEl);
// if(icons) {
// this.title.append(...icons);
// }
};
}
public setTitle(count?: number) {
this.setTitleManual(count).then((setTitleCallback) => setTitleCallback());
}
public async setMutedState() {
if(!this.btnMute) return;
const peerId = this.peerId;
const muted = await this.managers.appNotificationsManager.isPeerLocalMuted({peerId, respectType: false, threadId: this.chat.threadId});
if(await this.managers.appPeersManager.isBroadcast(peerId)) { // not human
this.btnMute.classList.remove('tgico-mute', 'tgico-unmute');
this.btnMute.classList.add(muted ? 'tgico-unmute' : 'tgico-mute');
this.btnMute.style.display = '';
} else {
this.btnMute.style.display = 'none';
}
}
// ! У МЕНЯ ПРОСТО СГОРЕЛО, САФАРИ КОНЧЕННЫЙ БРАУЗЕР - ЕСЛИ НЕ СКРЫВАТЬ БЛОК, ТО ПРИ ПЕРЕВОРОТЕ ЭКРАНА НА АЙФОНЕ БЛОК БУДЕТ НЕПРАВИЛЬНО ШИРИНЫ, ДАЖЕ БЕЗ ЭТОЙ ФУНКЦИИ!
public setUtilsWidth = (resize = false) => {
// return;
if(this.setUtilsRAF) window.cancelAnimationFrame(this.setUtilsRAF);
if(IS_SAFARI && resize) {
this.chatUtils.classList.add('hide');
}
// mutationObserver.disconnect();
this.setUtilsRAF = window.requestAnimationFrame(() => {
// mutationRAF = window.requestAnimationFrame(() => {
// setTimeout(() => {
if(IS_SAFARI && resize) {
this.chatUtils.classList.remove('hide');
}
/* this.chatInfo.style.removeProperty('--utils-width');
void this.chatInfo.offsetLeft; // reflow */
const width = /* chatUtils.scrollWidth */this.chatUtils.getBoundingClientRect().width;
this.chat.log('utils width:', width);
this.container.style.setProperty('--utils-width', width + 'px');
// this.chatInfo.classList.toggle('have-utils-width', !!width);
// }, 0);
this.setUtilsRAF = 0;
// mutationObserver.observe(chatUtils, observeOptions);
// });
});
};
public setFloating = () => {
const containers = [this.chatAudio, this.pinnedMessage?.pinnedMessageContainer].filter(Boolean);
const count = containers.reduce((acc, container) => {
const isFloating = container.isFloating();
this.container.classList.toggle(`is-pinned-${container.className}-floating`, isFloating);
if(!container.isVisible()) {
return acc;
}
return acc + +isFloating;
}, 0);
this.container.dataset.floating = '' + count;
};
public setPeerStatusManual = async(needClear = false) => {
if(!this.subtitle || this.chat.type !== 'chat') return;
if(this.chat.threadId) {
const title = await wrapPeerTitle({peerId: this.peerId, dialog: true});
const span = i18n('TopicProfileStatus', [title]);
return () => replaceContent(this.subtitle, span);
}
const peerId = this.peerId;
return this.chat.appImManager.setPeerStatus({
peerId,
element: this.subtitle,
needClear,
useWhitespace: false,
middleware: () => peerId === this.peerId
});
};
public setPeerStatus = (needClear?: boolean) => {
return this.setPeerStatusManual(needClear).then((callback) => {
callback?.();
});
};
}