diff --git a/package-lock.json b/package-lock.json index 9f0fa5f5..178d54c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4558,8 +4558,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -4577,13 +4576,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4596,18 +4593,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -4710,8 +4704,7 @@ }, "inherits": { "version": "2.0.4", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -4721,7 +4714,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4734,20 +4726,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "1.2.5", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.9.0", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -4764,7 +4753,6 @@ "mkdirp": { "version": "0.5.3", "bundled": true, - "optional": true, "requires": { "minimist": "^1.2.5" } @@ -4820,8 +4808,7 @@ }, "npm-normalize-package-bin": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "npm-packlist": { "version": "1.4.8", @@ -4846,8 +4833,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -4857,7 +4843,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -4926,8 +4911,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -4957,7 +4941,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4975,7 +4958,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5014,13 +4996,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.1.1", - "bundled": true, - "optional": true + "bundled": true } } }, diff --git a/src/components/lazyLoadQueue.ts b/src/components/lazyLoadQueue.ts index bc6fd069..a9ce6f26 100644 --- a/src/components/lazyLoadQueue.ts +++ b/src/components/lazyLoadQueue.ts @@ -17,7 +17,9 @@ export default class LazyLoadQueue { private observer: IntersectionObserver; - constructor(private parallelLimit = 5) { + constructor(private parallelLimit = 5, withObserver = true) { + if(!withObserver) return; + this.observer = new IntersectionObserver(entries => { if(this.lockPromise) return; @@ -41,7 +43,10 @@ export default class LazyLoadQueue { this.tempID--; this.lazyLoadMedia.length = 0; this.loadingMedia = 0; - this.observer.disconnect(); + + if(this.observer) { + this.observer.disconnect(); + } } public length() { @@ -103,15 +108,26 @@ export default class LazyLoadQueue { } } } - - public push(el: LazyLoadElement) { - this.lazyLoadMedia.push(el); + public addElement(el: LazyLoadElement) { if(el.wasSeen) { this.processQueue(el); } else { el.wasSeen = false; - this.observer.observe(el.div); + + if(this.observer) { + this.observer.observe(el.div); + } } } + + public push(el: LazyLoadElement) { + this.lazyLoadMedia.push(el); + this.addElement(el); + } + + public unshift(el: LazyLoadElement) { + this.lazyLoadMedia.unshift(el); + this.addElement(el); + } } diff --git a/src/components/misc.ts b/src/components/misc.ts index 87f7f25e..121e10b7 100644 --- a/src/components/misc.ts +++ b/src/components/misc.ts @@ -341,6 +341,13 @@ export function formatPhoneNumber(str: string) { return {formatted: str, country}; } +export function parseMenuButtonsTo(to: {[name: string]: HTMLButtonElement}, elements: HTMLCollection) { + Array.from(elements).forEach(el => { + let name = el.className.match(/ menu-(.+?) /)[1]; + to[name] = el as HTMLButtonElement; + }); +} + let onMouseMove = (e: MouseEvent) => { let rect = openedMenu.getBoundingClientRect(); let {clientX, clientY} = e; diff --git a/src/components/popup.ts b/src/components/popup.ts new file mode 100644 index 00000000..babae72c --- /dev/null +++ b/src/components/popup.ts @@ -0,0 +1,92 @@ +import AvatarElement from "./avatar"; +import { ripple } from "./misc"; + +export class PopupElement { + protected element = document.createElement('div'); + protected container = document.createElement('div'); + protected header = document.createElement('div'); + protected title = document.createElement('div'); + + constructor(className: string) { + this.element.classList.add('popup'); + this.element.className = 'popup' + (className ? ' ' + className : ''); + this.container.classList.add('popup-container', 'z-depth-1'); + + this.header.classList.add('popup-header'); + this.title.classList.add('popup-title'); + + this.header.append(this.title); + this.container.append(this.header); + this.element.append(this.container); + } + + public show() { + document.body.append(this.element); + void this.element.offsetWidth; // reflow + this.element.classList.add('active'); + } + + public destroy() { + this.element.classList.remove('active'); + setTimeout(() => { + this.element.remove(); + }, 1000); + } +} + +export type PopupPeerButton = { + text: string, + callback?: () => void, + isDanger?: true, + isCancel?: true +}; + +export class PopupPeer extends PopupElement { + constructor(private className: string, options: Partial<{ + peerID: number, + title: string, + description: string, + buttons: Array + }> = {}) { + super('popup-peer' + (className ? ' ' + className : '')); + + let avatarEl = new AvatarElement(); + avatarEl.setAttribute('dialog', '1'); + avatarEl.setAttribute('peer', '' + options.peerID); + avatarEl.classList.add('peer-avatar'); + + this.title.innerText = options.title || ''; + this.header.prepend(avatarEl); + + let p = document.createElement('p'); + p.classList.add('popup-description'); + p.innerHTML = options.description; + + let buttonsDiv = document.createElement('div'); + buttonsDiv.classList.add('popup-buttons'); + + let buttons = options.buttons.map(b => { + let button = document.createElement('button'); + ripple(button); + button.className = 'btn' + (b.isDanger ? ' danger' : ''); + button.innerHTML = b.text; + + if(b.callback) { + button.addEventListener('click', () => { + b.callback(); + this.destroy(); + }); + } else if(b.isCancel) { + button.addEventListener('click', () => { + this.destroy(); + }); + } + + return button; + }); + + buttonsDiv.append(...buttons); + + this.container.append(p, buttonsDiv); + } +} \ No newline at end of file diff --git a/src/components/scrollable_new.ts b/src/components/scrollable_new.ts index 0fa748ca..fd0c6871 100644 --- a/src/components/scrollable_new.ts +++ b/src/components/scrollable_new.ts @@ -364,7 +364,10 @@ export default class Scrollable { public scrollIntoView(element: HTMLElement, smooth = true) { if(element.parentElement && !this.scrollLocked) { let isFirstUnread = element.classList.contains('is-first-unread'); + let offsetTop = element.getBoundingClientRect().top - this.container.getBoundingClientRect().top; + offsetTop = this.container.scrollTop + offsetTop; + if(!smooth && isFirstUnread) { this.scrollTo(offsetTop, false); return; @@ -374,7 +377,7 @@ export default class Scrollable { let height = element.scrollHeight; let d = (clientHeight - height) / 2; - offsetTop = this.container.scrollTop + offsetTop - d; + offsetTop -= d; this.scrollTo(offsetTop, smooth); } diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index d902a47f..e802c57a 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -796,8 +796,6 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o //console.timeEnd('decompress sticker' + doc.id); - console.log('sticker json:', json); - let animation = await LottieLoader.loadAnimation({ container: div, loop: false, diff --git a/src/lib/appManagers/apiUpdatesManager.ts b/src/lib/appManagers/apiUpdatesManager.ts index b355c05e..3e43087e 100644 --- a/src/lib/appManagers/apiUpdatesManager.ts +++ b/src/lib/appManagers/apiUpdatesManager.ts @@ -176,8 +176,8 @@ export class ApiUpdatesManager { public getDifference() { // console.trace(dT(), 'Get full diff') - let updatesState = this.updatesState; - if (!updatesState.syncLoading) { + const updatesState = this.updatesState; + if(!updatesState.syncLoading) { updatesState.syncLoading = true; updatesState.pendingSeqUpdates = {}; updatesState.pendingPtsUpdates = []; @@ -188,7 +188,7 @@ export class ApiUpdatesManager { updatesState.syncPending = false; } - apiManager.invokeApi('updates.getDifference', { + return apiManager.invokeApi('updates.getDifference', { pts: updatesState.pts, date: updatesState.date, qts: -1 @@ -232,7 +232,7 @@ export class ApiUpdatesManager { }); }); - var nextState = differenceResult.intermediate_state || differenceResult.state; + const nextState = differenceResult.intermediate_state || differenceResult.state; updatesState.seq = nextState.seq; updatesState.pts = nextState.pts; updatesState.date = nextState.date; @@ -515,7 +515,6 @@ export class ApiUpdatesManager { }); } else { Object.assign(this.updatesState, state); - this.updatesState.syncLoading = false; this.getDifference(); } } diff --git a/src/lib/appManagers/appChatsManager.ts b/src/lib/appManagers/appChatsManager.ts index c8f9028f..194cd2dc 100644 --- a/src/lib/appManagers/appChatsManager.ts +++ b/src/lib/appManagers/appChatsManager.ts @@ -128,12 +128,10 @@ export class AppChatsManager { return this.chats[id] || {id: id, deleted: true, access_hash: this.channelAccess[id]}; } - public hasRights(id: number, action: 'send' | 'edit_title' | 'edit_photo' | 'invite') { - if(!(id in this.chats)) { - return false; - } + public hasRights(id: number, action: 'send' | 'edit_title' | 'edit_photo' | 'invite' | 'pin' | 'deleteRevoke') { + const chat = this.getChat(id); + if(!chat) return false; - let chat = this.getChat(id); if(chat._ == 'chatForbidden' || chat._ == 'channelForbidden' || chat.pFlags.kicked || @@ -145,24 +143,50 @@ export class AppChatsManager { return true; } + let myFlags = (chat.admin_rights || chat.banned_rights || chat.default_banned_rights)?.pFlags ?? {}; + switch(action) { + // good case 'send': { if(chat._ == 'channel' && - !chat.pFlags.megagroup && - !chat.pFlags.editor) { + !chat.pFlags.megagroup && + !myFlags.post_messages) { + return false; + } + + break; + } + + // good + case 'deleteRevoke': { + if(chat._ == 'channel') { + return !!myFlags.delete_messages; + } else if(!chat.pFlags.admin) { return false; } + + break; + } + + // good + case 'pin': { + if(chat._ == 'channel') { + return chat.admin_rights ? !!myFlags.pin_messages || !!myFlags.post_messages : !myFlags.pin_messages; + } else { + if(myFlags.pin_messages && !chat.pFlags.admin) { + return false; + } + } + break; } - case 'edit_title': case 'edit_photo': case 'invite': { if(chat._ == 'channel') { if(chat.pFlags.megagroup) { - if(!chat.pFlags.editor && - !(action == 'invite' && chat.pFlags.democracy)) { + if(!(action == 'invite' && chat.pFlags.democracy)) { return false; } } else { @@ -174,6 +198,7 @@ export class AppChatsManager { return false; } } + break; } } @@ -306,7 +331,7 @@ export class AppChatsManager { let chat = this.getChat(id); let myID = appUsersManager.getSelf().id; if(this.isChannel(id)) { - let isAdmin = chat.pFlags.creator || chat.pFlags.editor || chat.pFlags.moderator; + let isAdmin = chat.pFlags.creator; participants.forEach((participant) => { participant.canLeave = myID == participant.user_id; participant.canKick = isAdmin && participant._ == 'channelParticipant'; diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index 93c5282e..1c239385 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -4,12 +4,13 @@ import appPeersManager from './appPeersManager'; import appMessagesManager, { AppMessagesManager, Dialog } from "./appMessagesManager"; import appUsersManager, { User } from "./appUsersManager"; import { RichTextProcessor } from "../richtextprocessor"; -import { ripple, putPreloader, positionMenu, openBtnMenu } from "../../components/misc"; +import { ripple, putPreloader, positionMenu, openBtnMenu, parseMenuButtonsTo } from "../../components/misc"; //import Scrollable from "../../components/scrollable"; import Scrollable from "../../components/scrollable_new"; import { logger } from "../polyfill"; import appChatsManager from "./appChatsManager"; import AvatarElement from "../../components/avatar"; +import { PopupPeerButton, PopupPeer } from "../../components/popup"; type DialogDom = { avatarEl: AvatarElement, @@ -25,95 +26,7 @@ type DialogDom = { let testScroll = false; -class PopupElement { - protected element = document.createElement('div'); - protected container = document.createElement('div'); - protected header = document.createElement('div'); - protected title = document.createElement('div'); - constructor(className: string) { - this.element.classList.add('popup'); - this.element.className = 'popup' + (className ? ' ' + className : ''); - this.container.classList.add('popup-container', 'z-depth-1'); - - this.header.classList.add('popup-header'); - this.title.classList.add('popup-title'); - - this.header.append(this.title); - this.container.append(this.header); - this.element.append(this.container); - } - - public show() { - document.body.append(this.element); - void this.element.offsetWidth; // reflow - this.element.classList.add('active'); - } - - public destroy() { - this.element.classList.remove('active'); - setTimeout(() => { - this.element.remove(); - }, 1000); - } -} - -type PopupPeerButton = { - text: string, - callback?: () => void, - isDanger?: true, - isCancel?: true -}; - -class PopupPeer extends PopupElement { - constructor(private className: string, options: Partial<{ - peerID: number, - title: string, - description: string, - buttons: Array - }> = {}) { - super('popup-peer' + (className ? ' ' + className : '')); - - let avatarEl = new AvatarElement(); - avatarEl.setAttribute('dialog', '1'); - avatarEl.setAttribute('peer', '' + options.peerID); - avatarEl.classList.add('peer-avatar'); - - this.title.innerText = options.title || ''; - this.header.prepend(avatarEl); - - let p = document.createElement('p'); - p.classList.add('popup-description'); - p.innerHTML = options.description; - - let buttonsDiv = document.createElement('div'); - buttonsDiv.classList.add('popup-buttons'); - - let buttons = options.buttons.map(b => { - let button = document.createElement('button'); - ripple(button); - button.className = 'btn' + (b.isDanger ? ' danger' : ''); - button.innerHTML = b.text; - - if(b.callback) { - button.addEventListener('click', () => { - b.callback(); - this.destroy(); - }); - } else if(b.isCancel) { - button.addEventListener('click', () => { - this.destroy(); - }); - } - - return button; - }); - - buttonsDiv.append(...buttons); - - this.container.append(p, buttonsDiv); - } -} class DialogsContextMenu { private element = document.getElementById('dialogs-contextmenu') as HTMLDivElement; @@ -129,11 +42,7 @@ class DialogsContextMenu { private peerType: 'channel' | 'chat' | 'megagroup' | 'group' | 'saved'; constructor(private attachTo: HTMLElement[]) { - (Array.from(this.element.querySelectorAll('.btn-menu-item')) as HTMLElement[]).forEach(el => { - let name = el.className.match(/ menu-(.+?) /)[1]; - // @ts-ignore - this.buttons[name] = el; - }); + parseMenuButtonsTo(this.buttons, this.element.children); const onContextMenu = (e: MouseEvent) => { let li: HTMLDivElement = null; @@ -145,13 +54,9 @@ class DialogsContextMenu { if(!li) return; e.preventDefault(); - if(this.element.classList.contains('active')) { - /* this.element.classList.remove('active'); - this.element.parentElement.classList.remove('menu-open'); */ return false; } - e.cancelBubble = true; this.selectedID = +li.getAttribute('data-peerID'); diff --git a/src/lib/appManagers/appDocsManager.ts b/src/lib/appManagers/appDocsManager.ts index 625a8275..2d5ceb04 100644 --- a/src/lib/appManagers/appDocsManager.ts +++ b/src/lib/appManagers/appDocsManager.ts @@ -85,10 +85,6 @@ class AppDocsManager { if(/* apiDoc.thumbs && */apiDoc.mime_type == 'image/webp') { apiDoc.type = 'sticker'; apiDoc.sticker = 1; - } else if(apiDoc.mime_type == 'application/x-tgsticker') { - apiDoc.type = 'sticker'; - apiDoc.animated = true; - apiDoc.sticker = 2; } break; @@ -134,6 +130,12 @@ class AppDocsManager { if(!apiDoc.file_name) { apiDoc.file_name = ''; } + + if(apiDoc.mime_type == 'application/x-tgsticker' && apiDoc.file_name == "AnimatedSticker.tgs") { + apiDoc.type = 'sticker'; + apiDoc.animated = true; + apiDoc.sticker = 2; + } if(apiDoc._ == 'documentEmpty') { apiDoc.size = 0; diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 4c79fa6e..0c6c7087 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -15,11 +15,10 @@ import lottieLoader from "../lottieLoader"; import appMediaViewer from "./appMediaViewer"; import appSidebarLeft from "./appSidebarLeft"; import appChatsManager from "./appChatsManager"; -import appMessagesIDsManager from "./appMessagesIDsManager"; import apiUpdatesManager from './apiUpdatesManager'; import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, wrapAlbum, wrapPoll } from '../../components/wrappers'; import ProgressivePreloader from '../../components/preloader'; -import { openBtnMenu, formatPhoneNumber, positionMenu, ripple } from '../../components/misc'; +import { openBtnMenu, formatPhoneNumber, positionMenu, ripple, parseMenuButtonsTo } from '../../components/misc'; import { ChatInput } from '../../components/chatInput'; //import Scrollable from '../../components/scrollable'; import Scrollable from '../../components/scrollable_new'; @@ -31,6 +30,7 @@ import appStickersManager from './appStickersManager'; import AvatarElement from '../../components/avatar'; import appInlineBotsManager from './AppInlineBotsManager'; import StickyIntersector from '../../components/stickyIntersector'; +import { PopupPeerButton, PopupPeer } from '../../components/popup'; console.log('appImManager included!'); @@ -40,6 +40,172 @@ let testScroll = false; const IGNOREACTIONS = ['messageActionChannelMigrateFrom']; +class ChatContextMenu { + private element = document.getElementById('bubble-contextmenu') as HTMLDivElement; + private buttons: { + reply: HTMLButtonElement, + edit: HTMLButtonElement, + copy: HTMLButtonElement, + pin: HTMLButtonElement, + forward: HTMLButtonElement, + delete: HTMLButtonElement + } = {} as any; + public msgID: number; + + constructor(private attachTo: HTMLElement) { + parseMenuButtonsTo(this.buttons, this.element.children); + + attachTo.addEventListener('contextmenu', e => { + let bubble: HTMLDivElement = null; + + try { + bubble = findUpClassName(e.target, 'bubble__container'); + } catch(e) {} + + if(!bubble) return; + + e.preventDefault(); + if(this.element.classList.contains('active')) { + return false; + } + e.cancelBubble = true; + + bubble = bubble.parentElement as HTMLDivElement; // bc container + + let msgID = +bubble.dataset.mid; + if(!msgID) return; + + let peerID = $rootScope.selectedPeerID; + this.msgID = msgID; + + const message = appMessagesManager.getMessage(msgID); + + this.buttons.copy.style.display = message.message ? '' : 'none'; + + if($rootScope.myID == peerID || (peerID < 0 && appChatsManager.hasRights(-peerID, 'pin'))) { + this.buttons.pin.style.display = ''; + } else { + this.buttons.pin.style.display = 'none'; + } + + this.buttons.edit.style.display = appMessagesManager.canEditMessage(msgID) ? '' : 'none'; + + let side: 'left' | 'right' = bubble.classList.contains('is-in') ? 'left' : 'right'; + positionMenu(e, this.element, side); + openBtnMenu(this.element); + + /////this.log('contextmenu', e, bubble, msgID, side); + }); + + this.buttons.copy.addEventListener('click', () => { + let message = appMessagesManager.getMessage(this.msgID); + + let str = message ? message.message : ''; + + var textArea = document.createElement("textarea"); + textArea.value = str; + textArea.style.position = "fixed"; //avoid scrolling to bottom + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + document.execCommand('copy'); + } catch (err) { + console.error('Oops, unable to copy', err); + } + + document.body.removeChild(textArea); + }); + + this.buttons.delete.addEventListener('click', () => { + let peerID = $rootScope.selectedPeerID; + let firstName = appPeersManager.getPeerTitle(peerID, false, true); + + let callback = (revoke: boolean) => { + appMessagesManager.deleteMessages([this.msgID], revoke); + }; + + let title: string, description: string, buttons: PopupPeerButton[]; + title = 'Delete Message?'; + description = `Are you sure you want to delete this message?`; + + if(peerID == $rootScope.myID) { + buttons = [{ + text: 'DELETE', + isDanger: true, + callback: () => callback(false) + }]; + } else { + buttons = [{ + text: 'DELETE JUST FOR ME', + isDanger: true, + callback: () => callback(false) + }]; + + if(peerID > 0) { + buttons.push({ + text: 'DELETE FOR ME AND ' + firstName, + isDanger: true, + callback: () => callback(true) + }); + } else if(appChatsManager.hasRights(-peerID, 'deleteRevoke')) { + buttons.push({ + text: 'DELETE FOR ALL', + isDanger: true, + callback: () => callback(true) + }); + } + } + + buttons.push({ + text: 'CANCEL', + isCancel: true + }); + + let popup = new PopupPeer('popup-delete-chat', { + peerID: peerID, + title: title, + description: description, + buttons: buttons + }); + + popup.show(); + }); + + this.buttons.reply.addEventListener('click', () => { + const message = appMessagesManager.getMessage(this.msgID); + const chatInputC = appImManager.chatInputC; + chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message); + chatInputC.replyToMsgID = this.msgID; + chatInputC.editMsgID = 0; + }); + + this.buttons.forward.addEventListener('click', () => { + appForward.init([this.msgID]); + }); + + this.buttons.edit.addEventListener('click', () => { + const message = appMessagesManager.getMessage(this.msgID); + const chatInputC = appImManager.chatInputC; + chatInputC.setTopInfo('Editing', message.message, message.message, message); + chatInputC.replyToMsgID = 0; + chatInputC.editMsgID = this.msgID; + }); + + this.buttons.pin.addEventListener('click', () => { + apiManager.invokeApi('messages.updatePinnedMessage', { + flags: 0, + peer: appPeersManager.getInputPeerByID($rootScope.selectedPeerID), + id: this.msgID + }).then(updates => { + /////this.log('pinned updates:', updates); + apiUpdatesManager.processUpdateMessage(updates); + }); + }); + } +} + export class AppImManager { public pageEl = document.getElementById('page-chats') as HTMLDivElement; public btnMute = this.pageEl.querySelector('.tool-mute') as HTMLButtonElement; @@ -58,9 +224,8 @@ export class AppImManager { public myID = 0; public peerID = 0; - public muted = false; - - public bubbles: {[mid: number]: HTMLDivElement} = {}; + + public bubbles: {[mid: string]: HTMLDivElement} = {}; public dateMessages: {[timestamp: number]: { div: HTMLDivElement, firstTimestamp: number, @@ -94,11 +259,8 @@ export class AppImManager { private scrolledAll: boolean; private scrolledAllDown: boolean; - public contextMenu = document.getElementById('bubble-contextmenu') as HTMLDivElement; - private contextMenuPin = this.contextMenu.querySelector('.menu-pin') as HTMLDivElement; - private contextMenuEdit = this.contextMenu.querySelector('.menu-edit') as HTMLDivElement; - private contextMenuMsgID: number; - + public contextMenu = new ChatContextMenu(this.bubblesContainer); + private popupDeleteMessage: { popupEl?: HTMLDivElement, deleteBothBtn?: HTMLButtonElement, @@ -510,123 +672,14 @@ export class AppImManager { }; document.body.addEventListener('keydown', onKeyDown); - - this.bubblesContainer.addEventListener('contextmenu', e => { - let bubble: HTMLDivElement = null; - - try { - bubble = findUpClassName(e.target, 'bubble__container'); - } catch(e) {} - - if(bubble) { - bubble = bubble.parentElement as HTMLDivElement; // bc container - e.preventDefault(); - e.cancelBubble = true; - - let msgID = 0; - for(let id in this.bubbles) { - if(this.bubbles[id] === bubble) { - msgID = +id; - break; - } - } - - if(!msgID) return; - - if(this.myID == this.peerID || (this.peerID < 0 && !appPeersManager.isChannel(this.peerID) && !appPeersManager.isMegagroup(this.peerID))) { - this.contextMenuPin.style.display = ''; - } else this.contextMenuPin.style.display = 'none'; - - this.contextMenuMsgID = msgID; - - let side = bubble.classList.contains('is-in') ? 'left' : 'right'; - - this.contextMenuEdit.style.display = side == 'right' ? '' : 'none'; - - positionMenu(e, this.contextMenu, side as any); - openBtnMenu(this.contextMenu); - - /////this.log('contextmenu', e, bubble, msgID, side); - } - }); - - this.contextMenu.querySelector('.menu-copy').addEventListener('click', () => { - let message = appMessagesManager.getMessage(this.contextMenuMsgID); - - let str = message ? message.message : ''; - - var textArea = document.createElement("textarea"); - textArea.value = str; - textArea.style.position = "fixed"; //avoid scrolling to bottom - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - - try { - document.execCommand('copy'); - } catch (err) { - console.error('Oops, unable to copy', err); - } - - document.body.removeChild(textArea); - }); - - this.contextMenu.querySelector('.menu-delete').addEventListener('click', () => { - if(this.peerID == this.myID) { - this.popupDeleteMessage.deleteBothBtn.style.display = 'none'; - this.popupDeleteMessage.deleteMeBtn.innerText = 'DELETE'; - } else { - this.popupDeleteMessage.deleteBothBtn.style.display = ''; - this.popupDeleteMessage.deleteMeBtn.innerText = 'DELETE JUST FOR ME'; - - if(this.peerID > 0) { - let title = appPeersManager.getPeerTitle(this.peerID); - this.popupDeleteMessage.deleteBothBtn.innerHTML = 'DELETE FOR ME AND ' + title; - } else { - this.popupDeleteMessage.deleteBothBtn.innerText = 'DELETE FOR ALL'; - } - } - - this.popupDeleteMessage.popupEl.classList.add('active'); - }); - - this.contextMenu.querySelector('.menu-reply').addEventListener('click', () => { - let message = appMessagesManager.getMessage(this.contextMenuMsgID); - this.chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message); - this.chatInputC.replyToMsgID = this.contextMenuMsgID; - this.chatInputC.editMsgID = 0; - }); - - this.contextMenu.querySelector('.menu-forward').addEventListener('click', () => { - appForward.init([this.contextMenuMsgID]); - }); - - this.contextMenuEdit.addEventListener('click', () => { - let message = appMessagesManager.getMessage(this.contextMenuMsgID); - this.chatInputC.setTopInfo('Editing', message.message, message.message, message); - this.chatInputC.replyToMsgID = 0; - this.chatInputC.editMsgID = this.contextMenuMsgID; - }); - - this.contextMenuPin.addEventListener('click', () => { - apiManager.invokeApi('messages.updatePinnedMessage', { - flags: 0, - peer: appPeersManager.getInputPeerByID(this.peerID), - id: this.contextMenuMsgID - }).then(updates => { - /////this.log('pinned updates:', updates); - apiUpdatesManager.processUpdateMessage(updates); - }); - }); - this.popupDeleteMessage.deleteBothBtn.addEventListener('click', () => { - this.deleteMessages(true); + appMessagesManager.deleteMessages([this.contextMenu.msgID], true); this.popupDeleteMessage.cancelBtn.click(); }); this.popupDeleteMessage.deleteMeBtn.addEventListener('click', () => { - this.deleteMessages(false); + appMessagesManager.deleteMessages([this.contextMenu.msgID], false); this.popupDeleteMessage.cancelBtn.click(); }); @@ -690,7 +743,7 @@ export class AppImManager { } */ //appMessagesManager.readMessages(readed); - /* false && */appMessagesManager.readHistory(this.peerID, max, length).catch((err: any) => { + /* false && */ appMessagesManager.readHistory(this.peerID, max, length).catch((err: any) => { this.log.error('readHistory err:', err); appMessagesManager.readHistory(this.peerID, max, length); }); @@ -698,36 +751,6 @@ export class AppImManager { }); } - public deleteMessages(revoke = false) { - let flags = revoke ? 1 : 0; - let ids = [this.contextMenuMsgID]; - - apiManager.invokeApi('messages.deleteMessages', { - flags: flags, - revoke: revoke, - id: ids - }).then((affectedMessages: any) => { - /////this.log('deleted messages:', affectedMessages); - - apiUpdatesManager.processUpdateMessage({ - _: 'updateShort', - update: { - _: 'updatePts', - pts: affectedMessages.pts, - pts_count: affectedMessages.pts_count - } - }); - - apiUpdatesManager.processUpdateMessage({ - _: 'updateShort', - update: { - _: 'updateDeleteMessages', - messages: ids - } - }); - }); - } - public updateStatus() { if(!this.myID) return Promise.resolve(); @@ -897,8 +920,7 @@ export class AppImManager { ////console.time('appImManager cleanup'); this.scrolledAll = false; this.scrolledAllDown = false; - this.muted = false; - + this.bubbles = {}; this.dateMessages = {}; this.bubbleGroups.cleanup(); @@ -951,15 +973,12 @@ export class AppImManager { const samePeer = this.peerID == peerID; if(this.setPeerPromise && samePeer) return this.setPeerPromise; - - /* if(lastMsgID) { - appMessagesManager.readHistory(peerID, lastMsgID); // lol - } */ const dialog = appMessagesManager.getDialogByPeerID(peerID)[0] || null; const topMessage = lastMsgID <= 0 ? lastMsgID : dialog?.top_message ?? 0; - if(lastMsgID === undefined && dialog) { - if(dialog.unread_count) { + const isTarget = lastMsgID !== undefined; + if(!isTarget && dialog) { + if(dialog.unread_count && !samePeer) { lastMsgID = dialog.read_inbox_max_id; } else { lastMsgID = dialog.top_message; @@ -971,7 +990,7 @@ export class AppImManager { if(dialog && lastMsgID == topMessage) { this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight); this.scroll.scrollTop = this.scroll.scrollHeight; - } else { + } else if(isTarget) { this.scrollable.scrollIntoView(this.bubbles[lastMsgID]); this.highlightBubble(this.bubbles[lastMsgID]); } @@ -1053,11 +1072,11 @@ export class AppImManager { } const fromUp = maxBubbleID > 0 && (maxBubbleID < lastMsgID || lastMsgID < 0); - if(!fromUp && samePeer) { + const forwardingUnread = dialog.read_inbox_max_id == lastMsgID; + if(!fromUp && (samePeer || forwardingUnread)) { this.scrollable.scrollTop = this.scrollable.scrollHeight; } - const forwardingUnread = dialog.read_inbox_max_id == lastMsgID; const bubble = forwardingUnread ? (this.firstUnreadBubble || this.bubbles[lastMsgID]) : this.bubbles[lastMsgID]; this.scrollable.scrollIntoView(bubble, samePeer/* , fromUp */); @@ -1075,6 +1094,12 @@ export class AppImManager { this.log('scrolledAllDown:', this.scrolledAllDown); + if(!this.unreaded.length && dialog) { // lol + appMessagesManager.readHistory(peerID, dialog.top_message); + } + + this.chatInner.classList.remove('disable-hover', 'is-scrolling'); // warning, performance! + //console.timeEnd('appImManager setPeer'); return true; @@ -1126,6 +1151,8 @@ export class AppImManager { this.pinnedMessageContainer.style.display = 'none'; + this.btnMute.style.display = appPeersManager.isBroadcast(peerID) ? '' : 'none'; + window.requestAnimationFrame(() => { let title = ''; if(this.peerID == this.myID) title = 'Saved Messages'; @@ -1642,6 +1669,8 @@ export class AppImManager { } } } + + const isOut = our && (!message.fwd_from || this.peerID != this.myID); // media if(messageMedia/* && messageMedia._ == 'messageMediaPhoto' */) { @@ -1696,7 +1725,7 @@ export class AppImManager { boxWidth: 380, boxHeight: 380, withTail: doc.type != 'round', - isOut: our, + isOut: isOut, lazyLoadQueue: this.lazyLoadQueue, middleware: null }); @@ -1744,7 +1773,7 @@ export class AppImManager { lazyLoadQueue: this.lazyLoadQueue }); } else { - wrapPhoto(photo.id, message, attachmentDiv, undefined, undefined, true, our, this.lazyLoadQueue, () => { + wrapPhoto(photo.id, message, attachmentDiv, undefined, undefined, true, isOut, this.lazyLoadQueue, () => { return this.peerID == peerID; }); } @@ -1800,7 +1829,8 @@ export class AppImManager { lazyLoadQueue: this.lazyLoadQueue, middleware: () => { return this.peerID == peerID; - } + }, + isOut }); //} } else { @@ -1907,7 +1937,7 @@ export class AppImManager { boxWidth: 380, boxHeight: 380, withTail: doc.type != 'round', - isOut: our, + isOut: isOut, lazyLoadQueue: this.lazyLoadQueue, middleware: () => { return this.peerID == peerID; @@ -2097,7 +2127,7 @@ export class AppImManager { bubble.classList.add('hide-name'); } - bubble.classList.add(our && (!message.fwd_from || this.peerID != this.myID) ? 'is-out' : 'is-in'); + bubble.classList.add(isOut ? 'is-out' : 'is-in'); if(updatePosition) { this.bubbleGroups.addBubble(bubble, message, reverse); @@ -2151,10 +2181,16 @@ export class AppImManager { let realLength = this.scrollable.length; let previousScrollHeightMinusTop: number; - if(realLength > 0 && reverse) { + if(realLength > 0 && reverse) { // for safari need set when scrolling bottom too this.messagesQueueOnRender = () => { let scrollTop = this.scrollable.scrollTop; + previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop; + /* if(reverse) { + previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop; + } else { + previousScrollHeightMinusTop = scrollTop; + } */ this.log('performHistoryResult: messagesQueueOnRender, scrollTop:', scrollTop, previousScrollHeightMinusTop); this.messagesQueueOnRender = undefined; @@ -2168,8 +2204,9 @@ export class AppImManager { (this.messagesQueuePromise || Promise.resolve()).then(() => { if(previousScrollHeightMinusTop !== undefined) { - this.log('performHistoryResult: will set scrollTop', this.scrollable.scrollHeight, previousScrollHeightMinusTop); - this.scrollable.scrollTop = this.scrollable.scrollHeight - previousScrollHeightMinusTop; + const newScrollTop = reverse ? this.scrollable.scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop; + this.log('performHistoryResult: will set scrollTop', this.scrollable.scrollHeight, newScrollTop, this.scrollable.container.clientHeight); + this.scrollable.scrollTop = newScrollTop; } resolve(true); @@ -2186,7 +2223,7 @@ export class AppImManager { let peerID = this.peerID; //console.time('appImManager call getHistory'); - let pageCount = this.bubblesContainer.clientHeight / 38/* * 1.25 */ | 0; + let pageCount = appPhotosManager.windowH / 38/* * 1.25 */ | 0; //let loadCount = Object.keys(this.bubbles).length > 0 ? 50 : pageCount; let realLoadCount = Object.keys(this.bubbles).length > 0 ? Math.max(40, pageCount) : pageCount;//let realLoadCount = 50; let loadCount = realLoadCount; @@ -2215,7 +2252,7 @@ export class AppImManager { if(result instanceof Promise) { cached = false; promise = result.then((result) => { - this.log('getHistory result by maxID:', maxID, reverse, isBackLimit, result); + this.log('getHistory not cached result by maxID:', maxID, reverse, isBackLimit, result, peerID); //console.timeEnd('appImManager call getHistory'); @@ -2234,8 +2271,8 @@ export class AppImManager { return false; }); } else { - this.log('getHistory result by maxID:', maxID, reverse, isBackLimit, result); cached = true; + this.log('getHistory cached result by maxID:', maxID, reverse, isBackLimit, result, peerID); promise = this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID); //return (reverse ? this.getHistoryTopPromise = promise : this.getHistoryBottomPromise = promise); //return this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true); @@ -2292,7 +2329,7 @@ export class AppImManager { if(!dialog?.unread_count) return; let maxID = dialog.read_inbox_max_id; - maxID = Object.keys(this.bubbles).map(i => +i).sort((a, b) => a - b).find(i => i > maxID); + maxID = Object.keys(this.bubbles).filter(mid => !this.bubbles[mid].classList.contains('is-out')).map(i => +i).sort((a, b) => a - b).find(i => i > maxID); if(maxID && this.bubbles[maxID]) { let bubble = this.bubbles[maxID]; @@ -2313,7 +2350,7 @@ export class AppImManager { for(let i in this.dateMessages) { let dateMessage = this.dateMessages[i]; - if(dateMessage.container.childElementCount == 1) { // only date div + if(dateMessage.container.childElementCount == 2) { // only date div + sentinel div dateMessage.container.remove(); this.stickyIntersector.unobserve(dateMessage.container, dateMessage.div); delete this.dateMessages[i]; @@ -2324,19 +2361,11 @@ export class AppImManager { public setMutedState(muted = false) { appSidebarRight.profileElements.notificationsCheckbox.checked = !muted; appSidebarRight.profileElements.notificationsStatus.innerText = muted ? 'Disabled' : 'Enabled'; - - let peerID = this.peerID; - - this.muted = muted; - if(peerID < 0) { // not human - let isChannel = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID); - if(isChannel) { - 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'; - } + + if(appPeersManager.isBroadcast(this.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'; } diff --git a/src/lib/appManagers/appMediaViewer.ts b/src/lib/appManagers/appMediaViewer.ts index 9cc9087c..1705b50e 100644 --- a/src/lib/appManagers/appMediaViewer.ts +++ b/src/lib/appManagers/appMediaViewer.ts @@ -9,6 +9,8 @@ import appDocsManager from "./appDocsManager"; import VideoPlayer from "../mediaPlayer"; import { renderImageFromUrl } from "../../components/misc"; import AvatarElement from "../../components/avatar"; +import LazyLoadQueue from "../../components/lazyLoadQueue"; +import appForward from "../../components/appForward"; export class AppMediaViewer { private overlaysDiv = document.querySelector('.overlays') as HTMLDivElement; @@ -59,10 +61,15 @@ export class AppMediaViewer { private needLoadMore = true; private pageEl = document.getElementById('page-chats') as HTMLDivElement; + + private setMoverPromise: Promise; + + private lazyLoadQueue: LazyLoadQueue; constructor() { this.log = logger('AMV'); this.preloader = new ProgressivePreloader(); + this.lazyLoadQueue = new LazyLoadQueue(5, false); this.onKeyDownBinded = this.onKeyDown.bind(this); @@ -75,6 +82,7 @@ export class AppMediaViewer { this.peerID = 0; this.currentMessageID = 0; + this.lazyLoadQueue.clear(); this.setMoverToTarget(this.lastTarget, true); @@ -88,6 +96,8 @@ export class AppMediaViewer { }); this.buttons.prev.addEventListener('click', () => { + if(this.setMoverPromise) return; + let target = this.prevTargets.pop(); if(target) { this.nextTargets.unshift({element: this.lastTarget, mid: this.currentMessageID}); @@ -98,6 +108,8 @@ export class AppMediaViewer { }); this.buttons.next.addEventListener('click', () => { + if(this.setMoverPromise) return; + let target = this.nextTargets.shift(); if(target) { this.prevTargets.push({element: this.lastTarget, mid: this.currentMessageID}); @@ -124,6 +136,10 @@ export class AppMediaViewer { } }); + this.buttons.forward.addEventListener('click', () => { + appForward.init([this.currentMessageID]); + }); + this.onClickBinded = (e: MouseEvent) => { let target = e.target as HTMLElement; @@ -155,7 +171,7 @@ export class AppMediaViewer { } } - public setMoverToTarget(target: HTMLElement, closing = false, fromRight = 0) { + public async setMoverToTarget(target: HTMLElement, closing = false, fromRight = 0) { let mover = this.content.mover; if(!closing) { @@ -222,7 +238,7 @@ export class AppMediaViewer { } } else { aspecter = document.createElement('div'); - aspecter.classList.add('media-viewer-aspecter'); + aspecter.classList.add('media-viewer-aspecter', 'disable-hover'); mover.prepend(aspecter); } @@ -257,19 +273,25 @@ export class AppMediaViewer { let isOut = target.classList.contains('is-out'); if(!closing) { - let img: HTMLImageElement; - let video: HTMLVideoElement; - - if(target.tagName == 'DIV') { // means backgrounded with cover - img = new Image(); - img.src = target.style.backgroundImage.slice(5, -2); + let mediaElement: HTMLImageElement | HTMLVideoElement; + let src: string; + + if(target.tagName == 'DIV') { // useContainerAsTarget + if(target.firstElementChild) { + mediaElement = new Image(); + src = (target.firstElementChild as HTMLImageElement).src; + mover.append(mediaElement); + } + /* mediaElement = new Image(); + src = target.style.backgroundImage.slice(5, -2); */ + } else if(target instanceof HTMLImageElement) { - img = new Image(); - img.src = target.src; + mediaElement = new Image(); + src = target.src; } else if(target instanceof HTMLVideoElement) { - video = document.createElement('video'); + let video = mediaElement = document.createElement('video'); let source = document.createElement('source'); - source.src = target.querySelector('source')?.src; + src = target.querySelector('source')?.src; video.append(source); } else if(target instanceof SVGSVGElement) { let clipID = target.dataset.clipID; @@ -314,23 +336,42 @@ export class AppMediaViewer { path.setAttributeNS(null, 'd', d); } - let mediaEl = newSvg.lastElementChild; - mediaEl.setAttributeNS(null, 'width', '' + containerRect.width); - mediaEl.setAttributeNS(null, 'height', '' + containerRect.height); + let foreignObject = newSvg.lastElementChild; + foreignObject.setAttributeNS(null, 'width', '' + containerRect.width); + foreignObject.setAttributeNS(null, 'height', '' + containerRect.height); mover.prepend(newSvg); } if(aspecter) { aspecter.style.borderRadius = borderRadius; - aspecter.append(img || video); + aspecter.append(mediaElement); + } + + mediaElement = mover.querySelector('video, img'); + if(mediaElement instanceof HTMLImageElement) { + await new Promise((resolve, reject) => { + mediaElement.addEventListener('load', resolve); + + if(src) { + mediaElement.src = src; + } + }); + } else if(mediaElement instanceof HTMLVideoElement && mediaElement.firstElementChild && (mediaElement.firstElementChild as HTMLSourceElement).src) { + await new Promise((resolve, reject) => { + mediaElement.addEventListener('loadeddata', resolve); + + if(src) { + (mediaElement.firstElementChild as HTMLSourceElement).src = src; + } + }); } mover.style.display = ''; - setTimeout(() => { + window.requestAnimationFrame(() => { mover.classList.add(wasActive ? 'moving' : 'active'); - }, 0); + }); } else { if(target instanceof SVGSVGElement) { path = mover.querySelector('path'); @@ -340,6 +381,10 @@ export class AppMediaViewer { } } + if(target.classList.contains('media-viewer-media')) { + mover.classList.add('hiding'); + } + setTimeout(() => { this.overlaysDiv.classList.remove('active'); }, 0); @@ -354,46 +399,48 @@ export class AppMediaViewer { setTimeout(() => { mover.innerHTML = ''; - mover.classList.remove('moving', 'active'); + mover.classList.remove('moving', 'active', 'hiding'); mover.style.display = 'none'; }, delay); - } - return () => { - mover.style.transform = `translate(${containerRect.left}px,${containerRect.top}px) scale(1,1)`; + return; + } - if(aspecter) { - this.setFullAspect(aspecter, containerRect, rect); - aspecter.classList.add('disable-hover'); - } + //await new Promise((resolve) => setTimeout(resolve, 0)); + await new Promise((resolve) => window.requestAnimationFrame(resolve)); - setTimeout(() => { - mover.style.borderRadius = ''; + mover.style.transform = `translate(${containerRect.left}px,${containerRect.top}px) scale(1,1)`; - if(mover.firstElementChild) { - (mover.firstElementChild as HTMLElement).style.borderRadius = ''; - } - }, delay / 2); + if(aspecter) { + this.setFullAspect(aspecter, containerRect, rect); + } - mover.dataset.timeout = '' + setTimeout(() => { - mover.classList.remove('moving'); + setTimeout(() => { + mover.style.borderRadius = ''; - if(aspecter) { // всё из-за видео, элементы управления скейлятся, так бы можно было этого не делать - mover.classList.remove('active'); - aspecter.style.cssText = ''; - void mover.offsetLeft; // reflow + if(mover.firstElementChild) { + (mover.firstElementChild as HTMLElement).style.borderRadius = ''; + } + }, delay / 2); - aspecter.classList.remove('disable-hover'); - } + mover.dataset.timeout = '' + setTimeout(() => { + mover.classList.remove('moving'); - mover.classList.add('active'); - delete mover.dataset.timeout; - }, delay); + if(aspecter) { // всё из-за видео, элементы управления скейлятся, так бы можно было этого не делать + mover.classList.remove('active'); + //aspecter.style.cssText = ''; + void mover.offsetLeft; // reflow - if(path) { - this.sizeTailPath(path, containerRect, scaleX, delay, true, isOut, borderRadius); + aspecter.classList.remove('disable-hover'); } - }; + + mover.classList.add('active'); + delete mover.dataset.timeout; + }, delay); + + if(path) { + this.sizeTailPath(path, containerRect, scaleX, delay, true, isOut, borderRadius); + } } private setFullAspect(aspecter: HTMLDivElement, containerRect: DOMRect, rect: DOMRect) { @@ -415,6 +462,8 @@ export class AppMediaViewer { height = width * proportion; } + //this.log('will set style aspecter:', `width: ${width}px; height: ${height}px; transform: scale(${containerRect.width / width}, ${containerRect.height / height});`); + aspecter.style.cssText = `width: ${width}px; height: ${height}px; transform: scale(${containerRect.width / width}, ${containerRect.height / height});`; } } @@ -581,7 +630,8 @@ export class AppMediaViewer { public openMedia(message: any, target?: HTMLElement, reverse = false, targetContainer?: HTMLElement, prevTargets: AppMediaViewer['prevTargets'] = [], nextTargets: AppMediaViewer['prevTargets'] = [], needLoadMore = true) { - ////////this.log('openMedia doc:', message, prevTarget, nextTarget); + if(this.setMoverPromise) return this.setMoverPromise; + this.log('openMedia doc:', message); const media = message.media.photo || message.media.document || message.media.webpage.document || message.media.webpage.photo; const isVideo = media.mime_type == 'video/mp4'; @@ -652,7 +702,11 @@ export class AppMediaViewer { this.content.caption.innerHTML = ''; } + let oldAvatar = this.author.avatarEl; + // @ts-ignore + this.author.avatarEl = this.author.avatarEl.cloneNode(); this.author.avatarEl.setAttribute('peer', '' + message.fromID); + oldAvatar.parentElement.replaceChild(this.author.avatarEl, oldAvatar); // ok set @@ -675,18 +729,17 @@ export class AppMediaViewer { const size = appPhotosManager.setAttachmentSize(isVideo ? media : media.id, container, maxWidth, maxHeight); // need after setAttachmentSize - if(useContainerAsTarget) { + /* if(useContainerAsTarget) { target = target.querySelector('img, video') || target; - } + } */ + let setMoverPromise: Promise; if(isVideo) { ////////this.log('will wrap video', media, size); - let afterTimeout = this.setMoverToTarget(target, false, fromRight); + setMoverPromise = this.setMoverToTarget(target, false, fromRight).then(() => { //return; // set and don't move //if(wasActive) return; - setTimeout(() => { - afterTimeout(); //return; let video = mover.querySelector('video') || document.createElement('video'); @@ -694,6 +747,7 @@ export class AppMediaViewer { if(media.type == 'gif') { video.autoplay = true; + video.loop = true; } let createPlayer = () => { @@ -704,88 +758,112 @@ export class AppMediaViewer { let player = new VideoPlayer(video, true); /* player.wrapper.parentElement.append(video); mover.append(player.wrapper); */ + } else { + video.play(); } }; if(!source || !source.src) { - let promise = appDocsManager.downloadDoc(media); - this.preloader.attach(mover, true, promise); - - promise.then(() => { - if(this.currentMessageID != message.mid) { - this.log.warn('media viewer changed video'); - return; - } - - let url = media.url; - if(target instanceof SVGSVGElement) { - this.updateMediaSource(mover, url, 'source'); - this.updateMediaSource(target, url, 'source'); - } else { - let div = mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover; - let image = div.firstElementChild as HTMLImageElement; - if(image instanceof HTMLImageElement) { - image.remove(); + let load = () => { + let promise = appDocsManager.downloadDoc(media); + this.preloader.attach(mover, true, promise); + + promise.then(() => { + if(this.currentMessageID != message.mid) { + this.log.warn('media viewer changed video'); + return; } - - renderImageFromUrl(source, url); - source.type = media.mime_type; - - if(!source.parentElement) { - video.append(source); + + let url = media.url; + if(target instanceof SVGSVGElement) { + this.updateMediaSource(mover, url, 'source'); + this.updateMediaSource(target, url, 'source'); + } else { + let div = mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover; + let image = div.firstElementChild as HTMLImageElement; + if(image instanceof HTMLImageElement) { + image.remove(); + } + + renderImageFromUrl(source, url); + source.type = media.mime_type; + + if(!source.parentElement) { + video.append(source); + } + + if(!video.parentElement) { + div.prepend(video); + } } + + createPlayer(); + }); - if(!video.parentElement) { - div.prepend(video); - } - } + return promise; + }; - createPlayer(); + this.lazyLoadQueue.unshift({ + div: null, + load, + wasSeen: true }); } else createPlayer(); - }, 0); + }); } else { - let afterTimeout = this.setMoverToTarget(target, false, fromRight); + setMoverPromise = this.setMoverToTarget(target, false, fromRight).then(() => { //return; // set and don't move //if(wasActive) return; - setTimeout(() => { - afterTimeout(); //return; - this.preloader.attach(mover); - - let cancellablePromise = appPhotosManager.preloadPhoto(media.id, size); - cancellablePromise.then(() => { - if(this.currentMessageID != message.mid) { - this.log.warn('media viewer changed photo'); - return; - } - - ///////this.log('indochina', blob); - - let url = media.url; - if(target instanceof SVGSVGElement) { - this.updateMediaSource(target, url, 'img'); - this.updateMediaSource(mover, url, 'img'); - } else { - let div = mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover; - let image = div.firstElementChild as HTMLImageElement; - if(!image || image.tagName != 'IMG') { - image = new Image(); + + let load = () => { + let cancellablePromise = appPhotosManager.preloadPhoto(media.id, size); + this.preloader.attach(mover, true, cancellablePromise); + cancellablePromise.then(() => { + if(this.currentMessageID != message.mid) { + this.log.warn('media viewer changed photo'); + return; } + + ///////this.log('indochina', blob); + + let url = media.url; + if(target instanceof SVGSVGElement) { + this.updateMediaSource(target, url, 'img'); + this.updateMediaSource(mover, url, 'img'); + } else { + let div = mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover; + let image = div.firstElementChild as HTMLImageElement; + if(!image || image.tagName != 'IMG') { + image = new Image(); + } + + //this.log('will renderImageFromUrl:', image, div, target); + + renderImageFromUrl(image, url).then(() => { + div.append(image); + }); + } + + this.preloader.detach(); + }).catch(err => { + this.log.error(err); + }); - //this.log('will renderImageFromUrl:', image, div, target); - - renderImageFromUrl(image, url).then(() => { - div.append(image); - }); - } + return cancellablePromise; + }; - this.preloader.detach(); - }).catch(err => { - this.log.error(err); + this.lazyLoadQueue.unshift({ + div: null, + load, + wasSeen: true }); - }, 0); + }); } + + return this.setMoverPromise = setMoverPromise.then(() => { + this.setMoverPromise = null; + }); } } diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index de3b7885..5dffb0de 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -1844,42 +1844,21 @@ export class AppMessagesManager { case 'messageMediaDocument': let document = message.media.document; - let found = false; - for(let attribute of document.attributes) { - if(found) break; - - switch(attribute._) { - case 'documentAttributeSticker': - messageText += RichTextProcessor.wrapRichText(attribute.alt) + 'Sticker'; - found = true; - break; - case 'documentAttributeFilename': - messageText += '' + attribute.file_name + ''; - found = true; - break; - /* default: - console.warn('Got unknown document type!', message); - break; */ - } - } - if(document.type == 'video') { messageText = 'Video' + (message.message ? ', ' : '') + ''; - found = true; } else if(document.type == 'voice') { messageText = 'Voice message'; - found = true; } else if(document.type == 'gif') { messageText = 'GIF' + (message.message ? ', ' : '') + ''; - found = true; } else if(document.type == 'round') { messageText = 'Video message' + (message.message ? ', ' : '') + ''; - found = true; + } else if(document.type == 'sticker') { + messageText = (document.stickerEmoji || '') + 'Sticker'; + } else { + messageText = '' + document.file_name + ''; } - if(found) { - break; - } + break; default: ///////console.warn('Got unknown message.media type!', message); @@ -2537,6 +2516,77 @@ export class AppMessagesManager { } } + public deleteMessages(messageIDs: number[], revoke: boolean) { + const splitted = appMessagesIDsManager.splitMessageIDsByChannels(messageIDs); + const promises: Promise[] = []; + for(const channelIDStr in splitted.msgIDs) { + const channelID = +channelIDStr; + let msgIDs = splitted.msgIDs[channelID]; + + let promise: Promise; + if(channelID > 0) { + const channel = appChatsManager.getChat(channelID); + if(!channel.pFlags.creator && !(channel.pFlags.editor && channel.pFlags.megagroup)) { + const goodMsgIDs: number[] = []; + if (channel.pFlags.editor || channel.pFlags.megagroup) { + msgIDs.forEach((msgID, i) => { + const message = this.getMessage(splitted.mids[channelID][i]); + if(message.pFlags.out) { + goodMsgIDs.push(msgID); + } + }); + } + + if(!goodMsgIDs.length) { + return; + } + + msgIDs = goodMsgIDs; + } + + promise = apiManager.invokeApi('channels.deleteMessages', { + channel: appChatsManager.getChannelInput(channelID), + id: msgIDs + }).then((affectedMessages) => { + apiUpdatesManager.processUpdateMessage({ + _: 'updateShort', + update: { + _: 'updateDeleteChannelMessages', + channel_id: channelID, + messages: msgIDs, + pts: affectedMessages.pts, + pts_count: affectedMessages.pts_count + } + }); + }); + } else { + let flags = 0; + if(revoke) { + flags |= 1; + } + + promise = apiManager.invokeApi('messages.deleteMessages', { + flags: flags, + id: msgIDs + }).then((affectedMessages) => { + apiUpdatesManager.processUpdateMessage({ + _: 'updateShort', + update: { + _: 'updateDeleteMessages', + messages: msgIDs, + pts: affectedMessages.pts, + pts_count: affectedMessages.pts_count + } + }); + }); + } + + promises.push(promise); + } + + return Promise.all(promises); + } + public readHistory(peerID: number, maxID = 0, readLength = 0): Promise { // console.trace('start read') const isChannel = appPeersManager.isChannel(peerID); @@ -2590,7 +2640,7 @@ export class AppMessagesManager { index = historyStorage.history.indexOf(maxID); } - let readedLength = 0; + let readedLength = 1; if(historyStorage.history.length && maxID) { for(let i = index == -1 ? 0 : index, length = historyStorage.history.length; i < length; i++) { diff --git a/src/lib/appManagers/appSidebarLeft.ts b/src/lib/appManagers/appSidebarLeft.ts index d96fc690..2d8e4695 100644 --- a/src/lib/appManagers/appSidebarLeft.ts +++ b/src/lib/appManagers/appSidebarLeft.ts @@ -5,7 +5,7 @@ import appImManager from "./appImManager"; //import apiManager from '../mtproto/apiManager'; import apiManager from '../mtproto/mtprotoworker'; import AppSearch, { SearchGroup } from "../../components/appSearch"; -import { horizontalMenu, putPreloader } from "../../components/misc"; +import { horizontalMenu, putPreloader, parseMenuButtonsTo } from "../../components/misc"; import appUsersManager from "./appUsersManager"; import Scrollable from "../../components/scrollable_new"; import appPhotosManager from "./appPhotosManager"; @@ -273,10 +273,14 @@ class AppContactsTab implements SliderTab { public onCloseAfterTimeout() { this.list.innerHTML = ''; + this.input.value = ''; } public openContacts(query?: string) { - appSidebarLeft.selectTab(SLIDERITEMSIDS.contacts); + if(appSidebarLeft.historyTabIDs.indexOf(SLIDERITEMSIDS.contacts) === -1) { + appSidebarLeft.selectTab(SLIDERITEMSIDS.contacts); + } + if(this.promise) return this.promise; this.scrollable.onScrolledBottom = null; @@ -288,6 +292,9 @@ class AppContactsTab implements SliderTab { return; } + contacts = contacts.slice(); + contacts.findAndSplice(u => u == $rootScope.myID); + let sorted = contacts .map(userID => { let user = appUsersManager.getUser(userID); @@ -340,11 +347,7 @@ class AppSettingsTab implements SliderTab { } = {} as any; constructor() { - (Array.from(this.container.querySelector('.profile-buttons').children) as HTMLButtonElement[]).forEach(el => { - let name = el.className.match(/ menu-(.+?) /)[1]; - // @ts-ignore - this.buttons[name] = el; - }); + parseMenuButtonsTo(this.buttons, this.container.querySelector('.profile-buttons').children); $rootScope.$on('user_auth', (e: CustomEvent) => { this.fillElements(); @@ -569,19 +572,22 @@ class AppSidebarLeft { private searchInput = document.getElementById('global-search') as HTMLInputElement; private menuEl = this.toolsBtn.querySelector('.btn-menu'); - private newGroupBtn = this.menuEl.querySelector('.menu-new-group'); - private contactsBtn = this.menuEl.querySelector('.menu-contacts'); - private archivedBtn = this.menuEl.querySelector('.menu-archive'); - private savedBtn = this.menuEl.querySelector('.menu-saved'); - private settingsBtn = this.menuEl.querySelector('.menu-settings'); - public archivedCount = this.archivedBtn.querySelector('.archived-count') as HTMLSpanElement; + private buttons: { + newGroup: HTMLButtonElement, + contacts: HTMLButtonElement, + archived: HTMLButtonElement, + saved: HTMLButtonElement, + settings: HTMLButtonElement, + help: HTMLButtonElement + } = {} as any; + public archivedCount: HTMLSpanElement; private newBtnMenu = this.sidebarEl.querySelector('#new-menu'); - private newButtons = { - channel: this.newBtnMenu.querySelector('.menu-channel'), - group: this.newBtnMenu.querySelector('.menu-group'), - privateChat: this.newBtnMenu.querySelector('.menu-private-chat'), - }; + private newButtons: { + channel: HTMLButtonElement, + group: HTMLButtonElement, + privateChat: HTMLButtonElement, + } = {} as any; public newChannelTab = new AppNewChannelTab(); public addMembersTab = new AppAddMembersTab(); @@ -620,7 +626,12 @@ class AppSidebarLeft { this.searchGroups.people.container.append(peopleContainer); let peopleScrollable = new Scrollable(peopleContainer, 'x'); - this.savedBtn.addEventListener('click', (e) => { + parseMenuButtonsTo(this.buttons, this.menuEl.children); + parseMenuButtonsTo(this.newButtons, this.newBtnMenu.firstElementChild.children); + + this.archivedCount = this.buttons.archived.querySelector('.archived-count') as HTMLSpanElement; + + this.buttons.saved.addEventListener('click', (e) => { ///////this.log('savedbtn click'); setTimeout(() => { // menu doesn't close if no timeout (lol) let dom = appDialogsManager.getDialogDom(appImManager.myID); @@ -628,15 +639,15 @@ class AppSidebarLeft { }, 0); }); - this.archivedBtn.addEventListener('click', (e) => { + this.buttons.archived.addEventListener('click', (e) => { this.selectTab(SLIDERITEMSIDS.archived); }); - this.contactsBtn.addEventListener('click', (e) => { + this.buttons.contacts.addEventListener('click', (e) => { this.contactsTab.openContacts(); }); - this.settingsBtn.addEventListener('click', () => { + this.buttons.settings.addEventListener('click', () => { this.settingsTab.fillElements(); this.selectTab(SLIDERITEMSIDS.settings); }); @@ -676,7 +687,7 @@ class AppSidebarLeft { this.selectTab(SLIDERITEMSIDS.newChannel); }); - [this.newButtons.group, this.newGroupBtn].forEach(btn => { + [this.newButtons.group, this.buttons.newGroup].forEach(btn => { btn.addEventListener('click', (e) => { this.addMembersTab.init(0, 'chat', false, (peerIDs) => { this.newGroupTab.init(peerIDs); diff --git a/src/lib/appManagers/appSidebarRight.ts b/src/lib/appManagers/appSidebarRight.ts index 8091e4ae..8b7b51a5 100644 --- a/src/lib/appManagers/appSidebarRight.ts +++ b/src/lib/appManagers/appSidebarRight.ts @@ -175,7 +175,7 @@ class AppSidebarRight { appImManager.mutePeer(this.peerID); }); - if(testScroll) { + if(testScroll && false) { let div = document.createElement('div'); for(let i = 0; i < 500; ++i) { //div.insertAdjacentHTML('beforeend', `
`); @@ -713,9 +713,8 @@ class AppSidebarRight { setText(appPeersManager.getPeerUsername(peerID), this.profileElements.username); } - let dialog: any = appMessagesManager.getDialogByPeerID(peerID); - if(dialog.length) { - dialog = dialog[0]; + let dialog = appMessagesManager.getDialogByPeerID(peerID)[0]; + if(dialog) { let muted = false; if(dialog.notify_settings && dialog.notify_settings.mute_until) { muted = new Date(dialog.notify_settings.mute_until * 1000) > new Date(); diff --git a/src/lib/appManagers/appStickersManager.ts b/src/lib/appManagers/appStickersManager.ts index 9051d5e0..315dbd64 100644 --- a/src/lib/appManagers/appStickersManager.ts +++ b/src/lib/appManagers/appStickersManager.ts @@ -68,7 +68,7 @@ class AppStickersManager { } //if(!this.stickerSets['emoji']) { - this.getStickerSet({id: 'emoji', access_hash: ''}); + this.getStickerSet({id: 'emoji', access_hash: ''}, {overwrite: true}); //} }); } @@ -95,8 +95,10 @@ class AppStickersManager { public async getStickerSet(set: { id: string, access_hash: string - }) { - if(this.stickerSets[set.id]) return this.stickerSets[set.id]; + }, params: Partial<{ + overwrite: boolean + }> = {}) { + if(this.stickerSets[set.id] && !params.overwrite) return this.stickerSets[set.id]; let promise = apiManager.invokeApi('messages.getStickerSet', { stickerset: set.id == 'emoji' ? { diff --git a/src/lib/lottieLoader.ts b/src/lib/lottieLoader.ts index 51d4b63b..97b48f66 100644 --- a/src/lib/lottieLoader.ts +++ b/src/lib/lottieLoader.ts @@ -1,4 +1,4 @@ -import { isInDOM } from "./utils"; +//import { isInDOM } from "./utils"; import LottiePlayer, { AnimationConfigWithPath, AnimationConfigWithData, AnimationItem } from "lottie-web/build/player/lottie.d"; let convert = (value: number) => { @@ -85,7 +85,7 @@ class LottieLoader { for(let i = length - 1; i >= 0; --i) { let {animation, container, paused, autoplay, canvas} = animations[i]; - if(destroy && !isInDOM(container)) { + if(destroy && !container.parentElement/* !isInDOM(container) */) { this.debug && console.log('destroy animation'); animation.destroy(); animations.splice(i, 1); @@ -140,7 +140,7 @@ class LottieLoader { k[2] = (foundReplacement[1] & 255) / 255; } - console.log('foundReplacement!', foundReplacement, color.toString(16), k); + //console.log('foundReplacement!', foundReplacement, color.toString(16), k); break; } diff --git a/src/pages/pageSignIn.ts b/src/pages/pageSignIn.ts index 24c10649..c8431956 100644 --- a/src/pages/pageSignIn.ts +++ b/src/pages/pageSignIn.ts @@ -206,7 +206,7 @@ let onFirstMount = () => { putPreloader(this); //this.innerHTML = 'PLEASE WAIT...'; - return; + //return; let phone_number = telEl.value; apiManager.invokeApi('auth.sendCode', { diff --git a/src/scss/partials/_mediaViewer.scss b/src/scss/partials/_mediaViewer.scss index 0737b854..0a00b5ee 100644 --- a/src/scss/partials/_mediaViewer.scss +++ b/src/scss/partials/_mediaViewer.scss @@ -74,49 +74,39 @@ $move-duration: .35s; max-height: 100%; max-width: 100%; overflow: hidden; + } - .media-viewer-stub { - flex: 1; - } - - .media-viewer-container { - align-self: center; - position: relative; - max-width: 100%; - max-height: 100%; - overflow: hidden; - flex: 1 1 auto; - display: flex; - align-items: center; - } + &-stub { + flex: 1; + } - .media-viewer-media { - display: flex; - align-items: center; - justify-content: center; - visibility: hidden; - } + &-container { + align-self: center; + position: relative; + max-width: 100%; + max-height: 100%; + overflow: hidden; + flex: 1 1 auto; + display: flex; + align-items: center; + } - img, video { - max-height: calc(100vh - 100px); - max-width: calc(100vw - 16px); - /* max-height: 720px; - max-width: 1280px; */ - } + &-media { + visibility: hidden; + } - .media-viewer-caption { - flex: 1; - text-align: center; - color: $color-gray; - transition: $open-duration; - max-width: 50vw; - word-break: break-word; - overflow: hidden; - text-overflow: ellipsis; + &-caption { + flex: 1; + text-align: center; + color: $color-gray; + transition: $open-duration; + max-width: 50vw; + word-break: break-word; + overflow: hidden; + text-overflow: ellipsis; - &:hover { - color: #fff; - } + &:hover { + color: #fff; } } @@ -188,6 +178,7 @@ $move-duration: .35s; height: 100%; user-select: none; object-fit: cover; + opacity: 1; } &.active { @@ -197,6 +188,13 @@ $move-duration: .35s; &.moving { transition: $move-duration transform ease; } + + &.hiding { + img, video { + transition: $open-duration opacity; + opacity: 0; + } + } } // возможно тут это вообще не нужно @@ -205,6 +203,7 @@ $move-duration: .35s; height: 100%; transform: scale(1); overflow: hidden; + position: absolute; } &-mover.active &-aspecter { diff --git a/src/scss/partials/_rightSIdebar.scss b/src/scss/partials/_rightSIdebar.scss index b813c67e..b4fb4291 100644 --- a/src/scss/partials/_rightSIdebar.scss +++ b/src/scss/partials/_rightSIdebar.scss @@ -30,6 +30,10 @@ } } + #forward-container { + z-index: 5; + } + .sidebar-search { display: none; @@ -41,7 +45,7 @@ .profile { &-content { - flex: 1 1 auto; + /* flex: 1 1 auto; */ display: flex; flex-direction: column; /* height: 100%; */ @@ -54,7 +58,7 @@ } &-wrapper { - flex: 0 0 auto; + flex: 1 1 auto; display: flex; flex-direction: column; margin-bottom: 36px; @@ -64,16 +68,18 @@ width: 100%; max-width: 100%; //overflow: hidden; - flex: 1 1 auto; - position: relative; - //height: 1%; // fix safari + position: absolute; + top: 100%; + min-height: calc(100vh - 100% - 60px); + display: flex; + flex-direction: column; + } + } - /* &.loaded { // warning - .profile-tabs-content { - position: relative; - min-height: auto; - } - } */ + &-container { + > .scrollable { + display: flex; + flex-direction: column; } } @@ -159,12 +165,13 @@ position: -webkit-sticky !important; position: sticky !important; top: 0; - z-index: 3; + z-index: 2; background-color: #fff; &-content { //min-height: 100%; min-height: calc(100% - 49px); + flex: 1 1 auto; //position: absolute; // FIX THE SAFARI! //position: relative; /* width: 500%; diff --git a/src/scss/partials/_scrollable.scss b/src/scss/partials/_scrollable.scss index 1c46a7e6..83d9d469 100644 --- a/src/scss/partials/_scrollable.scss +++ b/src/scss/partials/_scrollable.scss @@ -26,8 +26,8 @@ div.scrollable::-webkit-scrollbar-thumb { //position: relative; //will-change: transform; - transform: translateZ(0); - -webkit-transform: translateZ(0); + /* transform: translateZ(0); + -webkit-transform: translateZ(0); */ position: absolute; top: 0px; diff --git a/src/scss/partials/popups/_peer.scss b/src/scss/partials/popups/_peer.scss index 392d611f..8d922c72 100644 --- a/src/scss/partials/popups/_peer.scss +++ b/src/scss/partials/popups/_peer.scss @@ -18,6 +18,7 @@ font-size: 1.25rem; font-weight: 500; margin-bottom: 0.125rem; + text-transform: capitalize; } &-description { diff --git a/src/scss/style.scss b/src/scss/style.scss index b74fabc5..9f338fa1 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -534,7 +534,7 @@ avatar-element { } &-download { - z-index: 2; + z-index: 1; align-items: center; font-size: 24px; cursor: pointer; diff --git a/webpack.common.js b/webpack.common.js index 46432a68..0bf3a72c 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -1,7 +1,7 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); -let allowedIPs = ['195.66.140.39', '192.168.31.144', '127.0.0.1', '192.168.31.1', '192.168.31.192', '192.168.0.111']; +let allowedIPs = ['195.66.140.39', '192.168.31.144', '127.0.0.1', '192.168.31.1', '192.168.31.192', '192.168.0.111', '192.168.0.105']; const opts = { MTPROTO_WORKER: true,