Telegram Web K with changes to work inside I2P
https://web.telegram.i2p/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
927 lines
32 KiB
927 lines
32 KiB
/* |
|
* 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 { ButtonMenuItemOptions } from "../buttonMenu"; |
|
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 { fastRaf } from "../../helpers/schedulers"; |
|
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 generateTitleIcons from "../generateTitleIcons"; |
|
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"; |
|
|
|
type ButtonToVerify = {element?: HTMLElement, verify: () => boolean | Promise<boolean>}; |
|
|
|
export default class ChatTopbar { |
|
public container: HTMLDivElement; |
|
private btnBack: HTMLButtonElement; |
|
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: (ButtonMenuItemOptions & {verify: ButtonToVerify['verify']})[]; |
|
private buttonsToVerify: ButtonToVerify[]; |
|
private chatInfoContainer: 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}); |
|
|
|
// * 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 = 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}, 'bottom-left', this.menuButtons, this.verifyButtons); |
|
} |
|
|
|
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 deleteButtonText = await this.managers.appPeersManager.getDeleteButtonText(this.peerId); |
|
if(isMenuOpen) { |
|
// delete button |
|
this.menuButtons[this.menuButtons.length - 1].element.lastChild.replaceWith(i18n(deleteButtonText)); |
|
} |
|
|
|
const buttons = this.buttonsToVerify.concat(isMenuOpen ? this.menuButtons : []); |
|
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()) 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.getChatTyped(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(this.peerId, false)) |
|
}, { |
|
icon: 'unmute', |
|
text: 'ChatList.Context.Unmute', |
|
onClick: () => { |
|
this.managers.appMessagesManager.togglePeerMute(this.peerId); |
|
}, |
|
verify: async() => this.chat.type === 'chat' && rootScope.myId !== this.peerId && (await this.managers.appNotificationsManager.isPeerLocalMuted(this.peerId, false)) |
|
}, { |
|
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: '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)('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(); |
|
} |
|
}); |
|
|
|
if(this.pinnedMessage) { |
|
this.chat.addEventListener('setPeer', (mid, isTopMessage) => { |
|
const middleware = this.chat.bubbles.getMiddleware(); |
|
apiManagerProxy.getState().then((state) => { |
|
if(!middleware()) 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.setPeerStatusInterval = window.setInterval(this.setPeerStatus, 60e3); |
|
|
|
return this; |
|
} |
|
|
|
public constructPinnedHelpers() { |
|
this.listenerSetter.add(rootScope)('peer_pinned_messages', ({peerId, mids}) => { |
|
if(peerId !== this.peerId) return; |
|
|
|
if(mids) { |
|
this.setTitle(); |
|
} |
|
}); |
|
} |
|
|
|
public constructDiscussionHelpers() { |
|
this.pinnedMessage = new ChatPinnedMessage(this, this.chat, this.managers); |
|
} |
|
|
|
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); |
|
|
|
if(this.pinnedMessage) { |
|
this.pinnedMessage.destroy(); // * возможно это можно не делать |
|
} |
|
|
|
if(this.chatAudio) { |
|
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 = this.peerId; |
|
|
|
let newAvatar: AvatarElement; |
|
if(this.avatarElement) { |
|
newAvatar = this.constructAvatar(); |
|
} |
|
|
|
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}) : undefined, |
|
this.setTitleManual(), |
|
this.setPeerStatusManual(true), |
|
apiManagerProxy.getState() |
|
]); |
|
|
|
return () => { |
|
this.btnMute && this.btnMute.classList.toggle('hide', !isBroadcast); |
|
if(this.btnJoin) { |
|
if(isAnyChat) { |
|
replaceContent(this.btnJoin, i18n(isBroadcast ? 'Chat.Subscribe' : 'ChannelJoin')); |
|
this.btnJoin.classList.toggle('hide', !chat?.pFlags?.left); |
|
} else { |
|
this.btnJoin.classList.add('hide'); |
|
} |
|
} |
|
|
|
if(newAvatar) { |
|
this.avatarElement.replaceWith(newAvatar); |
|
this.avatarElement = newAvatar; |
|
} |
|
|
|
this.setUtilsWidth(); |
|
|
|
this.verifyButtons(); |
|
|
|
if(this.pinnedMessage) { // * replace with new one |
|
if(this.chat.type === 'chat') { |
|
if(this.chat.wasAlreadyUsed) { // * change |
|
const newPinnedMessage = new ChatPinnedMessage(this, this.chat, this.managers); |
|
this.pinnedMessage.pinnedMessageContainer.divAndCaption.container.replaceWith(newPinnedMessage.pinnedMessageContainer.divAndCaption.container); |
|
this.pinnedMessage.destroy(); |
|
//this.pinnedMessage.pinnedMessageContainer.toggle(true); |
|
this.pinnedMessage = newPinnedMessage; |
|
} |
|
|
|
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(); |
|
} |
|
} |
|
|
|
setTitleCallback(); |
|
setStatusCallback && setStatusCallback(); |
|
this.setMutedState(); |
|
|
|
this.container.classList.remove('hide'); |
|
}; |
|
} |
|
|
|
public async setTitleManual(count?: number) { |
|
const peerId = this.peerId; |
|
const middleware = () => this.peerId === peerId; |
|
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') { |
|
if(peerId === rootScope.myId) { |
|
//title = [count > 1 ? count : false, 'Reminders'].filter(Boolean).join(' '); |
|
titleEl = i18n('Reminders'); |
|
} else { |
|
titleEl = i18n('ScheduledMessages'); |
|
//title = [count > 1 ? count : false, 'Scheduled Messages'].filter(Boolean).join(' '); |
|
} |
|
|
|
if(count === undefined) { |
|
this.managers.appMessagesManager.getScheduledMessages(peerId).then((mids) => { |
|
if(!middleware()) return; |
|
this.setTitle(mids.length); |
|
}); |
|
} |
|
} else if(this.chat.type === 'discussion') { |
|
if(count === undefined) titleEl = i18n('Loading'); |
|
else titleEl = i18n('Chat.Title.Comments', [count]); |
|
|
|
if(count === undefined) { |
|
Promise.all([ |
|
this.managers.appMessagesManager.getHistory(peerId, 0, 1, 0, this.chat.threadId), |
|
Promise.resolve() |
|
]).then(([historyResult]) => { |
|
if(!middleware()) return; |
|
const count = historyResult?.count; |
|
if(typeof(count) !== 'number') { |
|
setTimeout(() => { |
|
if(!middleware()) return; |
|
this.setTitle(); |
|
}, 30); |
|
} else { |
|
this.setTitle(count); |
|
} |
|
}); |
|
} |
|
} else if(this.chat.type === 'chat') { |
|
[titleEl, icons] = await Promise.all([ |
|
wrapPeerTitle({ |
|
peerId, |
|
dialog: true |
|
}), |
|
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; |
|
let muted = await this.managers.appNotificationsManager.isPeerLocalMuted(peerId, false); |
|
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 && 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) return; |
|
|
|
const peerId = this.peerId; |
|
return this.chat.appImManager.setPeerStatus( |
|
peerId, |
|
this.subtitle, |
|
needClear, |
|
false, |
|
() => peerId === this.peerId |
|
); |
|
}; |
|
|
|
public setPeerStatus = (needClear?: boolean) => { |
|
return this.setPeerStatusManual(needClear).then((callback) => { |
|
if(callback) { |
|
callback(); |
|
} |
|
}); |
|
}; |
|
}
|
|
|