34bdf75789
Unread chats counter on mobile screens Fix scrolling top peers on touch Fix wrapping draft emojis Fix avatar transition on swipe-to-reply
974 lines
34 KiB
TypeScript
974 lines
34 KiB
TypeScript
/*
|
||
* 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?.();
|
||
});
|
||
};
|
||
}
|