import { findUpClassName, $rootScope, escapeRegExp, findUpTag, cancelEvent, positionElementByIndex } from "../utils"; import appImManager, { AppImManager } from "./appImManager"; import appPeersManager from './appPeersManager'; import appMessagesManager, { Dialog, MyDialogFilter as DialogFilter } from "./appMessagesManager"; import appUsersManager, { User } from "./appUsersManager"; import { RichTextProcessor } from "../richtextprocessor"; import { putPreloader, positionMenu, openBtnMenu, parseMenuButtonsTo, attachContextMenuListener } from "../../components/misc"; //import Scrollable from "../../components/scrollable"; import Scrollable, { ScrollableX } from "../../components/scrollable_new"; import { logger, LogLevels } from "../logger"; import appChatsManager from "./appChatsManager"; import AvatarElement from "../../components/avatar"; import { PopupButton, PopupPeer } from "../../components/popup"; import { SliderTab } from "../../components/slider"; import appStateManager from "./appStateManager"; import { touchSupport } from "../config"; import { horizontalMenu } from "../../components/horizontalMenu"; import { ripple } from "../../components/ripple"; import { isSafari } from "../../helpers/userAgent"; type DialogDom = { avatarEl: AvatarElement, captionDiv: HTMLDivElement, titleSpan: HTMLSpanElement, statusSpan: HTMLSpanElement, lastTimeSpan: HTMLSpanElement, unreadMessagesSpan: HTMLSpanElement, lastMessageSpan: HTMLSpanElement, containerEl: HTMLDivElement, listEl: HTMLLIElement }; const testScroll = false; //const USEPINNEDDELIMITER = false; class DialogsContextMenu { private element = document.getElementById('dialogs-contextmenu') as HTMLDivElement; private buttons: { archive: HTMLButtonElement, pin: HTMLButtonElement, mute: HTMLButtonElement, unread: HTMLButtonElement, delete: HTMLButtonElement, //clear: HTMLButtonElement, } = {} as any; private selectedID: number; private peerType: 'channel' | 'chat' | 'megagroup' | 'group' | 'saved'; private filterID: number; constructor() { parseMenuButtonsTo(this.buttons, this.element.children); this.buttons.archive.addEventListener('click', () => { let dialog = appMessagesManager.getDialogByPeerID(this.selectedID)[0]; if(dialog) { appMessagesManager.editPeerFolders([dialog.peerID], +!dialog.folder_id); } }); this.buttons.pin.addEventListener('click', () => { appMessagesManager.toggleDialogPin(this.selectedID, this.filterID); }); this.buttons.mute.addEventListener('click', () => { appImManager.mutePeer(this.selectedID); }); this.buttons.unread.addEventListener('click', () => { const dialog = appMessagesManager.getDialogByPeerID(this.selectedID)[0]; if(!dialog) return; if(dialog.unread_count) { appMessagesManager.readHistory(this.selectedID, dialog.top_message); appMessagesManager.markDialogUnread(this.selectedID, true); } else { appMessagesManager.markDialogUnread(this.selectedID); } }); this.buttons.delete.addEventListener('click', () => { let firstName = appPeersManager.getPeerTitle(this.selectedID, false, true); let callbackFlush = (justClear: boolean) => { appMessagesManager.flushHistory(this.selectedID, justClear); }; let callbackLeave = () => { appChatsManager.leaveChannel(-this.selectedID); }; let title: string, description: string, buttons: PopupButton[]; switch(this.peerType) { case 'channel': { title = 'Leave Channel?'; description = `Are you sure you want to leave this channel?`; buttons = [{ text: 'LEAVE ' + firstName, isDanger: true, callback: callbackLeave }]; break; } case 'megagroup': { title = 'Leave Group?'; description = `Are you sure you want to leave this group?`; buttons = [{ text: 'LEAVE ' + firstName, isDanger: true, callback: callbackLeave }]; break; } case 'chat': { title = 'Delete Chat?'; description = `Are you sure you want to delete chat with ${firstName}?`; buttons = [{ text: 'DELETE FOR ME AND ' + firstName, isDanger: true, callback: () => callbackFlush(false) }, { text: 'DELETE JUST FOR ME', isDanger: true, callback: () => callbackFlush(true) }]; break; } case 'saved': { title = 'Delete Saved Messages?'; description = `Are you sure you want to delete all your saved messages?`; buttons = [{ text: 'DELETE SAVED MESSAGES', isDanger: true, callback: () => callbackFlush(false) }]; break; } case 'group': { title = 'Delete and leave Group?'; description = `Are you sure you want to delete all message history and leave ${firstName}?`; buttons = [{ text: 'DELETE AND LEAVE ' + firstName, isDanger: true, callback: () => callbackFlush(true) }]; break; } } buttons.push({ text: 'CANCEL', isCancel: true }); let popup = new PopupPeer('popup-delete-chat', { peerID: this.selectedID, title: title, description: description, buttons: buttons }); popup.show(); }); } onContextMenu = (e: MouseEvent | Touch) => { let li: HTMLElement = null; try { li = findUpTag(e.target, 'LI'); } catch(e) {} if(!li) return; if(e instanceof MouseEvent) e.preventDefault(); if(this.element.classList.contains('active')) { return false; } if(e instanceof MouseEvent) e.cancelBubble = true; this.filterID = appDialogsManager.filterID; this.selectedID = +li.getAttribute('data-peerID'); const dialog = appMessagesManager.getDialogByPeerID(this.selectedID)[0]; const notOurDialog = dialog.peerID != $rootScope.myID; // archive button if(notOurDialog) { const button = this.buttons.archive; const condition = dialog.folder_id == 1; button.classList.toggle('flip-icon', condition); button.innerText = condition ? 'Unarchive' : 'Archive'; this.buttons.archive.style.display = ''; } else { this.buttons.archive.style.display = 'none'; } // pin button { const button = this.buttons.pin; //const condition = !!dialog.pFlags?.pinned; const condition = this.filterID > 1 ? appMessagesManager.filtersStorage.filters[this.filterID].pinned_peers.includes(dialog.peerID) : !!dialog.pFlags?.pinned; button.classList.toggle('flip-icon', condition); button.innerText = condition ? 'Unpin' : 'Pin'; } // mute button if(notOurDialog) { const button = this.buttons.mute; const condition = dialog.notify_settings && dialog.notify_settings.mute_until > (Date.now() / 1000 | 0); button.classList.toggle('flip-icon', condition); button.innerText = condition ? 'Unmute' : 'Mute'; this.buttons.mute.style.display = ''; } else { this.buttons.mute.style.display = 'none'; } // unread button { const button = this.buttons.unread; const condition = !!(dialog.pFlags?.unread_mark || dialog.unread_count); button.classList.toggle('flip-icon', condition); button.innerText = condition ? 'Mark as Read' : 'Mark as Unread'; } /* // clear history button if(appPeersManager.isChannel(this.selectedID)) { this.buttons.clear.style.display = 'none'; } else { this.buttons.clear.style.display = ''; } */ // delete button let deleteButtonText = ''; if(appPeersManager.isMegagroup(this.selectedID)) { deleteButtonText = 'Leave'; //deleteButtonText = 'Leave group'; this.peerType = 'megagroup'; } else if(appPeersManager.isChannel(this.selectedID)) { deleteButtonText = 'Leave'; //deleteButtonText = 'Leave channel'; this.peerType = 'channel'; } else if(this.selectedID < 0) { deleteButtonText = 'Delete'; //deleteButtonText = 'Delete and leave'; this.peerType = 'group'; } else { deleteButtonText = 'Delete'; //deleteButtonText = 'Delete chat'; this.peerType = this.selectedID == $rootScope.myID ? 'saved' : 'chat'; } this.buttons.delete.innerText = deleteButtonText; li.classList.add('menu-open'); positionMenu(e, this.element); openBtnMenu(this.element, () => { li.classList.remove('menu-open'); }); }; } export class AppArchivedTab implements SliderTab { public container = document.getElementById('chats-archived-container') as HTMLDivElement; public chatList = document.getElementById('dialogs-archived') as HTMLUListElement; public scroll: Scrollable = null; public loadedAll: boolean; public loadDialogsPromise: Promise; public wasFilterID: number; init() { this.scroll = new Scrollable(this.container, 'CLA', this.chatList, 500); this.scroll.setVirtualContainer(this.chatList); this.scroll.onScrolledBottom = appDialogsManager.onChatsScroll; ///this.scroll.attachSentinels(); appDialogsManager.setListClickListener(this.chatList, null, true); window.addEventListener('resize', () => { setTimeout(appDialogsManager.onChatsScroll, 0); }); } onOpen() { if(this.init) { this.init(); this.init = null; } this.wasFilterID = appDialogsManager.filterID; appDialogsManager.scroll = this.scroll; appDialogsManager.filterID = 1; appDialogsManager.onTabChange(); } // вообще, так делать нельзя, но нет времени чтобы переделать главный чатлист на слайд... onOpenAfterTimeout() { appDialogsManager.chatLists[this.wasFilterID].innerHTML = ''; } onClose() { appDialogsManager.scroll = appDialogsManager._scroll; appDialogsManager.filterID = this.wasFilterID; appDialogsManager.onTabChange(); } onCloseAfterTimeout() { this.chatList.innerHTML = ''; } } export const archivedTab = new AppArchivedTab(); export class AppDialogsManager { public _chatList = document.getElementById('dialogs') as HTMLUListElement; public chatList = this._chatList; //public pinnedDelimiter: HTMLDivElement; public doms: {[peerID: number]: DialogDom} = {}; public lastActiveListElement: HTMLElement = null; /* private rippleCallback: (value?: boolean | PromiseLike) => void = null; private lastClickID = 0; private lastGoodClickID = 0; */ public chatsContainer = document.getElementById('chats-container') as HTMLDivElement; private chatsPreloader: HTMLDivElement; public loadDialogsPromise: Promise; public loadedAll = false; public scroll: Scrollable = null; public _scroll: Scrollable = null; private log = logger('DIALOGS', LogLevels.log | LogLevels.error | LogLevels.warn | LogLevels.debug); public contextMenu = new DialogsContextMenu(); public chatLists: {[filterID: number]: HTMLUListElement} = { 0: this.chatList, 1: archivedTab.chatList }; public filterID = 0; private folders: {[k in 'menu' | 'container' | 'menuScrollContainer']: HTMLElement} = { menu: document.getElementById('folders-tabs'), menuScrollContainer: null, container: document.getElementById('folders-container') }; private filtersRendered: { [filterID: string]: { menu: HTMLElement, container: HTMLElement, unread: HTMLElement, title: HTMLElement } } = {}; private showFiltersTimeout: number; private allUnreadCount: HTMLElement; private accumulateArchivedTimeout: number; constructor() { this.chatsPreloader = putPreloader(null, true); this.allUnreadCount = this.folders.menu.querySelector('.unread-count'); /* if(USEPINNEDDELIMITER) { this.pinnedDelimiter = document.createElement('div'); this.pinnedDelimiter.classList.add('pinned-delimiter'); this.pinnedDelimiter.appendChild(document.createElement('span')); } */ this.folders.menuScrollContainer = this.folders.menu.parentElement; this.scroll = this._scroll = new Scrollable(this.chatsContainer, 'CL', this.chatList, 500); this.scroll.onScrolledBottom = this.onChatsScroll; this.scroll.setVirtualContainer(this.chatList); //this.scroll.attachSentinels(); if(touchSupport && isSafari) { let allowUp: boolean, allowDown: boolean, slideBeginY: number; const container = this.scroll.container; container.addEventListener('touchstart', (event) => { allowUp = container.scrollTop > 0; allowDown = (container.scrollTop < container.scrollHeight - container.clientHeight); // @ts-ignore slideBeginY = event.pageY; }); container.addEventListener('touchmove', (event: any) => { var up = (event.pageY > slideBeginY); var down = (event.pageY < slideBeginY); slideBeginY = event.pageY; if((up && allowUp) || (down && allowDown)) { event.stopPropagation(); } else if(up || down) { event.preventDefault(); } }); } this.setListClickListener(this.chatList, null, true); /* if(testScroll) { let i = 0; let add = () => { let li = document.createElement('li'); li.dataset.id = '' + i; li.id = '' + i; li.innerHTML = `

${i}18:33

-_-_-_-: qweasd

`; i++; this.scroll.append(li); }; for(let i = 0; i < 100; ++i) { add(); } (window as any).addElement = add; } */ $rootScope.$on('user_update', (e) => { let userID = e.detail; let user = appUsersManager.getUser(userID); let dialog = appMessagesManager.getDialogByPeerID(user.id)[0]; //console.log('updating user:', user, dialog); if(dialog && !appUsersManager.isBot(dialog.peerID) && dialog.peerID != $rootScope.myID) { let online = user.status && user.status._ == 'userStatusOnline'; let dom = this.getDialogDom(dialog.peerID); if(dom) { if(online) { dom.avatarEl.classList.add('is-online'); } else { dom.avatarEl.classList.remove('is-online'); } } } if($rootScope.selectedPeerID == user.id) { appImManager.setPeerStatus(); } }); $rootScope.$on('dialog_top', (e) => { let dialog: any = e.detail; this.setLastMessage(dialog); this.setDialogPosition(dialog); //this.setPinnedDelimiter(); this.setFiltersUnreadCount(); }); $rootScope.$on('dialog_flush', (e) => { let peerID: number = e.detail.peerID; let dialog = appMessagesManager.getDialogByPeerID(peerID)[0]; if(dialog) { this.setLastMessage(dialog); this.validateForFilter(); this.setFiltersUnreadCount(); } }); $rootScope.$on('dialogs_multiupdate', (e) => { const dialogs = e.detail; for(const id in dialogs) { const dialog = dialogs[id]; this.updateDialog(dialog); } //this.setPinnedDelimiter(); this.validateForFilter(); this.setFiltersUnreadCount(); }); $rootScope.$on('dialog_drop', (e) => { let {peerID, dialog} = e.detail; let dom = this.getDialogDom(peerID); if(dom) { dom.listEl.remove(); delete this.doms[peerID]; this.scroll.reorder(); } this.setFiltersUnreadCount(); }); $rootScope.$on('dialog_unread', (e) => { let info = e.detail; let dialog = appMessagesManager.getDialogByPeerID(info.peerID)[0]; if(dialog) { this.setUnreadMessages(dialog); if(dialog.peerID == $rootScope.selectedPeerID) { appImManager.updateUnreadByDialog(dialog); } this.validateForFilter(); this.setFiltersUnreadCount(); } }); $rootScope.$on('dialog_notify_settings', e => { const dialog = appMessagesManager.getDialogByPeerID(e.detail)[0]; if(dialog) { this.setUnreadMessages(dialog); // возможно это не нужно, но нужно менять is-muted } }); $rootScope.$on('peer_changed', (e) => { let peerID = e.detail; let lastPeerID = this.lastActiveListElement && +this.lastActiveListElement.getAttribute('data-peerID'); if(this.lastActiveListElement && lastPeerID != peerID) { this.lastActiveListElement.classList.remove('active'); this.lastActiveListElement = null; } if(lastPeerID != peerID) { let dom = this.getDialogDom(peerID); if(dom) { this.lastActiveListElement = dom.listEl; dom.listEl.classList.add('active'); } } }); $rootScope.$on('filter_update', (e) => { const filter: DialogFilter = e.detail; if(!this.filtersRendered[filter.id]) { this.addFilter(filter); return; } else if(filter.id == this.filterID) { // это нет тут смысла вызывать, так как будет dialogs_multiupdate //this.validateForFilter(); const folder = appMessagesManager.dialogsStorage.getFolder(filter.id); this.validateForFilter(); for(let i = 0, length = folder.length; i < length; ++i) { const dialog = folder[i]; this.updateDialog(dialog); } this.setFiltersUnreadCount(); } const elements = this.filtersRendered[filter.id]; elements.title.innerHTML = RichTextProcessor.wrapEmojiText(filter.title); }); $rootScope.$on('filter_delete', (e) => { const filter: DialogFilter = e.detail; const elements = this.filtersRendered[filter.id]; if(!elements) return; // set tab //(this.folders.menu.firstElementChild.children[Math.max(0, filter.id - 2)] as HTMLElement).click(); (this.folders.menu.firstElementChild.children[0] as HTMLElement).click(); elements.container.remove(); elements.menu.remove(); delete this.chatLists[filter.id]; delete this.filtersRendered[filter.id]; if(!Object.keys(this.filtersRendered).length) { this.folders.menuScrollContainer.classList.add('hide'); } }); /* $rootScope.$on('filter_pinned_order', (e) => { const {order, id} = e.detail as {order: number[], id: number}; if(this.prevTabID != id) { return; } for(const peerID of order) { this.updateDialog(appMessagesManager.getDialogByPeerID(peerID)[0]); } }); */ const foldersScrollable = new ScrollableX(this.folders.menuScrollContainer); this.chatsContainer.prepend(this.folders.menuScrollContainer); const selectTab = horizontalMenu(this.folders.menu, this.folders.container, (id, tabContent) => { /* if(id != 0) { id += 1; } */ foldersScrollable.scrollIntoView(this.folders.menu.firstElementChild.children[id] as HTMLElement, true, 250); id = +tabContent.dataset.filterID || 0; if(this.filterID == id) return; this.chatLists[id].innerHTML = ''; this.scroll.setVirtualContainer(this.chatLists[id]); this.filterID = id; this.onTabChange(); }, () => { for(const folderID in this.chatLists) { if(+folderID != this.filterID) { this.chatLists[folderID].innerHTML = ''; } } }); //selectTab(0); (this.folders.menu.firstElementChild.firstElementChild as HTMLElement).click(); /* false && */appStateManager.loadSavedState().then(() => { return appMessagesManager.filtersStorage.getDialogFilters(); }).then(filters => { for(const filterID in filters) { this.addFilter(filters[filterID]); } return this.loadDialogs(this.filterID); }).then(result => { //this.setPinnedDelimiter(); //appSidebarLeft.onChatsScroll(); this.loadDialogs(1); }); } private updateDialog(dialog: Dialog) { if(!dialog) { return; } if(!this.doms.hasOwnProperty(dialog.peerID)) { this.addDialog(dialog); } if(this.getDialogDom(dialog.peerID)) { this.setLastMessage(dialog); this.setDialogPosition(dialog); } } onTabChange = () => { this.doms = {}; this.loadedAll = false; this.lastActiveListElement = null; this.chatList = this.chatLists[this.filterID]; this.loadDialogs(this.filterID); }; public setFilterUnreadCount(filterID: number, folder?: Dialog[]) { const unreadSpan = filterID == 0 ? this.allUnreadCount : this.filtersRendered[filterID]?.unread; if(!unreadSpan) { return; } folder = folder || appMessagesManager.dialogsStorage.getFolder(filterID); const sum = folder.reduce((acc, dialog) => acc + +!!dialog.unread_count, 0); unreadSpan.innerText = sum ? '' + sum : ''; } public setFiltersUnreadCount() { for(const filterID in this.filtersRendered) { this.setFilterUnreadCount(+filterID); } this.setFilterUnreadCount(0); } /** * Удалит неподходящие чаты из списка, но не добавит их(!) */ public validateForFilter() { // !WARNING, возможно это было зачем-то, но комментарий исправил архивирование //if(this.filterID == 0) return; const folder = appMessagesManager.dialogsStorage.getFolder(this.filterID); let affected = false; for(const _peerID in this.doms) { const peerID = +_peerID; // если больше не подходит по фильтру, удаляем if(folder.findIndex((dialog) => dialog.peerID == peerID) === -1) { const listEl = this.doms[peerID].listEl; listEl.remove(); affected = true; if(this.lastActiveListElement == listEl) { this.lastActiveListElement = null; } } } if(affected) { this.scroll.reorder(); } } public addFilter(filter: DialogFilter) { if(this.filtersRendered[filter.id]) return; const li = document.createElement('li'); const span = document.createElement('span'); const titleSpan = document.createElement('span'); titleSpan.innerHTML = RichTextProcessor.wrapEmojiText(filter.title); const unreadSpan = document.createElement('span'); unreadSpan.classList.add('unread-count'); const i = document.createElement('i'); span.append(titleSpan, unreadSpan, i); li.append(span); ripple(li); const containerToAppend = this.folders.menu.firstElementChild as HTMLUListElement; positionElementByIndex(li, containerToAppend, filter.orderIndex + 1); // because 0 is All //containerToAppend.append(li); const ul = document.createElement('ul'); const div = document.createElement('div'); div.append(ul); div.dataset.filterID = '' + filter.id; //this.folders.container.append(div); positionElementByIndex(div, this.folders.container, filter.orderIndex + 1); // because 0 is All this.chatLists[filter.id] = ul; this.setListClickListener(ul, null, true); if(!this.showFiltersTimeout) { this.showFiltersTimeout = window.setTimeout(() => { this.showFiltersTimeout = 0; this.folders.menuScrollContainer.classList.remove('hide'); this.setFiltersUnreadCount(); }, 0); } this.filtersRendered[filter.id] = { menu: li, container: div, unread: unreadSpan, title: titleSpan }; } public async loadDialogs(folderID: number) { if(testScroll) { return; } if(this.loadDialogsPromise/* || 1 == 1 */) return this.loadDialogsPromise; if(!this.chatList.childElementCount) { const container = this.chatList.parentElement; container.append(this.chatsPreloader); } //return; const storage = appMessagesManager.dialogsStorage.getFolder(folderID); let offsetIndex = 0; for(let i = storage.length - 1; i >= 0; --i) { const dialog = storage[i]; if(this.getDialogDom(dialog.peerID)) { offsetIndex = dialog.index; break; } } //let offset = storage[storage.length - 1]?.index || 0; try { //console.time('getDialogs time'); const loadCount = 50/*this.chatsLoadCount */; const getConversationPromise = (this.filterID > 1 ? appUsersManager.getContacts() as Promise : Promise.resolve()).then(() => { return appMessagesManager.getConversations('', offsetIndex, loadCount, folderID); }); this.loadDialogsPromise = getConversationPromise; const result = await getConversationPromise; //console.timeEnd('getDialogs time'); if(result && result.dialogs && result.dialogs.length) { result.dialogs.forEach((dialog: any) => { this.addDialog(dialog); }); } if(!result.dialogs.length || this.chatList.childElementCount == result.count) { // loaded all this.loadedAll = true; } this.log.debug('getDialogs ' + loadCount + ' dialogs by offset:', offsetIndex, result, this.chatList.childElementCount); this.scroll.onScroll(); } catch(err) { this.log.error(err); } this.chatsPreloader.remove(); this.loadDialogsPromise = undefined; } onChatsScroll = () => { if(this.loadedAll || this.loadDialogsPromise) return; this.log('onChatsScroll'); this.loadDialogs(this.filterID); } public setListClickListener(list: HTMLUListElement, onFound?: () => void, withContext = false) { list.addEventListener('click', (e: Event) => { cancelEvent(e); this.log('dialogs click list'); let target = e.target as HTMLElement; let elem = target.classList.contains('rp') ? target : findUpClassName(target, 'rp'); if(!elem) { return; } elem = elem.parentElement; let samePeer = this.lastActiveListElement == elem; if(this.lastActiveListElement && !samePeer) { this.lastActiveListElement.classList.remove('active'); } let result: ReturnType; //console.log('appDialogsManager: lock lazyLoadQueue'); if(elem) { if(onFound) onFound(); let peerID = +elem.getAttribute('data-peerID'); let lastMsgID = +elem.dataset.mid || undefined; if(!samePeer) { elem.classList.add('active'); this.lastActiveListElement = elem; } result = appImManager.setPeer(peerID, lastMsgID); /* if(result instanceof Promise) { this.lastGoodClickID = this.lastClickID; appImManager.lazyLoadQueue.lock(); } */ } else { result = appImManager.setPeer(0); } }, {capture: true}); if(withContext) { attachContextMenuListener(list, this.contextMenu.onContextMenu); } } public setDialogPosition(dialog: Dialog, pos?: number) { const dom = this.getDialogDom(dialog.peerID); if(!dom) { return; } if(pos === undefined) { pos = appMessagesManager.dialogsStorage.getDialog(dialog.peerID, this.filterID)[1]; } if(positionElementByIndex(dom.listEl, this.chatList, pos)) { this.scroll.reorder(); this.log.debug('setDialogPosition:', dialog, dom, pos); } } /* public setPinnedDelimiter() { if(!USEPINNEDDELIMITER) return; let index = -1; let dialogs = appMessagesManager.dialogsStorage.getFolder(0); for(let dialog of dialogs) { if(dialog.pFlags?.pinned) { index++; } } let currentIndex = (this.pinnedDelimiter.parentElement && whichChild(this.pinnedDelimiter.parentElement)) ?? -1; if(index == currentIndex) return; let children = this.chatList.children; let modifying: HTMLElement[] = []; if(currentIndex != -1 && children.length > currentIndex) { let li = children[currentIndex] as HTMLElement; modifying.push(li); } if(index != -1 && children.length > index) { let li = children[index] as HTMLElement; modifying.push(li); li.append(this.pinnedDelimiter); } else { this.pinnedDelimiter.remove(); } modifying.forEach(elem => { this.scroll.updateElement(elem); }); } */ public setLastMessage(dialog: any, lastMessage?: any, dom?: DialogDom, highlightWord?: string) { if(!lastMessage) { lastMessage = appMessagesManager.getMessage(dialog.top_message); } ///////console.log('setlastMessage:', lastMessage); if(!dom) { dom = this.getDialogDom(dialog.peerID); if(!dom) { //this.log.error('no dom for dialog:', dialog, lastMessage, dom, highlightWord); return; } } if(lastMessage._ == 'messageEmpty' || (lastMessage._ == 'messageService' && !lastMessage.rReply)) { dom.lastMessageSpan.innerHTML = ''; dom.lastTimeSpan.innerHTML = ''; delete dom.listEl.dataset.mid; return; } let peer = dialog.peer; let peerID = dialog.peerID; //let peerID = appMessagesManager.getMessagePeer(lastMessage); //console.log('setting last message:', lastMessage); /* if(!dom.lastMessageSpan.classList.contains('user-typing')) */ { if(highlightWord && lastMessage.message) { let lastMessageText = appMessagesManager.getRichReplyText(lastMessage, ''); let messageText = lastMessage.message; let entities = RichTextProcessor.parseEntities(messageText.replace(/\n/g, ' '), {noLinebreakers: true}); let regExp = new RegExp(escapeRegExp(highlightWord), 'gi'); let match: any; if(!entities) entities = []; let found = false; while((match = regExp.exec(messageText)) !== null) { entities.push({_: 'messageEntityHighlight', length: highlightWord.length, offset: match.index}); found = true; } if(found) { entities.sort((a: any, b: any) => a.offset - b.offset); } let messageWrapped = RichTextProcessor.wrapRichText(messageText, { noLinebreakers: true, entities: entities, noTextFormat: true }); dom.lastMessageSpan.innerHTML = lastMessageText + messageWrapped; } else if(!lastMessage.deleted) { dom.lastMessageSpan.innerHTML = lastMessage.rReply; } else { dom.lastMessageSpan.innerHTML = ''; } /* if(lastMessage.from_id == auth.id) { // You: */ if(peer._ != 'peerUser' && peerID != -lastMessage.from_id) { let sender = appUsersManager.getUser(lastMessage.from_id); if(sender && sender.id) { let senderBold = document.createElement('b'); let str = ''; if(sender.id == $rootScope.myID) { str = 'You'; } else { str = sender.first_name || sender.last_name || sender.username; } //senderBold.innerText = str + ': '; senderBold.innerHTML = RichTextProcessor.wrapRichText(str, {noLinebreakers: true}) + ': '; //console.log(sender, senderBold.innerText); dom.lastMessageSpan.prepend(senderBold); } //////// else console.log('no sender', lastMessage, peerID); } } if(!lastMessage.deleted) { let timeStr = ''; let timestamp = lastMessage.date; let now = Date.now() / 1000; let time = new Date(lastMessage.date * 1000); if((now - timestamp) < 86400) { // if < 1 day timeStr = ('0' + time.getHours()).slice(-2) + ':' + ('0' + time.getMinutes()).slice(-2); } else if((now - timestamp) < (86400 * 7)) { // week let date = new Date(timestamp * 1000); timeStr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][date.getDay()]; } else { let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; timeStr = months[time.getMonth()] + ' ' + ('0' + time.getDate()).slice(-2); } dom.lastTimeSpan.innerHTML = timeStr; } else dom.lastTimeSpan.innerHTML = ''; if(this.doms[peerID] == dom) { this.setUnreadMessages(dialog); } else { // means search dom.listEl.dataset.mid = lastMessage.mid; } } public setUnreadMessages(dialog: Dialog) { const dom = this.getDialogDom(dialog.peerID); if(dialog.folder_id == 1) { this.accumulateArchivedUnread(); } if(!dom) { //this.log.error('setUnreadMessages no dom!', dialog); return; } const isMuted = (dialog.notify_settings?.mute_until * 1000) > Date.now(); dom.listEl.classList.toggle('is-muted', isMuted); const lastMessage = appMessagesManager.getMessage(dialog.top_message); if(lastMessage._ != 'messageEmpty' && !lastMessage.deleted && lastMessage.from_id == $rootScope.myID && lastMessage.peerID != $rootScope.myID && dialog.read_outbox_max_id) { // maybe comment, 06.20.2020 const outgoing = (lastMessage.pFlags && lastMessage.pFlags.unread) /* && dialog.read_outbox_max_id != 0 */; // maybe uncomment, 31.01.2020 //console.log('outgoing', outgoing, lastMessage); if(outgoing) { dom.statusSpan.classList.remove('tgico-checks'); dom.statusSpan.classList.add('tgico-check'); } else { dom.statusSpan.classList.remove('tgico-check'); dom.statusSpan.classList.add('tgico-checks'); } } else dom.statusSpan.classList.remove('tgico-check', 'tgico-checks'); dom.unreadMessagesSpan.innerText = ''; dom.unreadMessagesSpan.classList.remove('tgico-pinnedchat'); const filter = appMessagesManager.filtersStorage.filters[this.filterID]; let isPinned: boolean; if(filter) { isPinned = filter.pinned_peers.findIndex(peerID => peerID == dialog.peerID) !== -1; } else { isPinned = !!dialog.pFlags.pinned; } if(dialog.unread_count || dialog.pFlags.unread_mark) { //dom.unreadMessagesSpan.innerText = '' + (dialog.unread_count ? formatNumber(dialog.unread_count, 1) : ' '); dom.unreadMessagesSpan.innerText = '' + (dialog.unread_count || ' '); dom.unreadMessagesSpan.classList.add(isMuted ? 'unread-muted' : 'unread'); } else if(isPinned) { dom.unreadMessagesSpan.classList.remove('unread', 'unread-muted'); dom.unreadMessagesSpan.classList.add('tgico-pinnedchat'); } } public accumulateArchivedUnread() { if(this.accumulateArchivedTimeout) return; this.accumulateArchivedTimeout = window.setTimeout(() => { this.accumulateArchivedTimeout = 0; const dialogs = appMessagesManager.dialogsStorage.getFolder(1); const sum = dialogs.reduce((acc, dialog) => acc + dialog.unread_count, 0); $rootScope.$broadcast('dialogs_archived_unread', {count: sum}); }, 0); } public getDialogDom(peerID: number) { return this.doms[peerID]; } public addDialog(_dialog: Dialog | number, container?: HTMLUListElement | Scrollable, drawStatus = true, rippleEnabled = true, onlyFirstName = false, meAsSaved = true) { let dialog: Dialog; if(typeof(_dialog) === 'number') { let originalDialog = appMessagesManager.getDialogByPeerID(_dialog)[0]; if(!originalDialog) { originalDialog = { peerID: _dialog, pFlags: {} } as any; } dialog = originalDialog; } else { dialog = _dialog; } let peerID: number = dialog.peerID; if(!container) { if(this.doms[peerID]) return; const filter = appMessagesManager.filtersStorage.filters[this.filterID]; if((filter && !appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) || (!filter && this.filterID != dialog.folder_id)) { return; } } let title = appPeersManager.getPeerTitle(peerID, false, onlyFirstName); let avatarEl = new AvatarElement(); avatarEl.setAttribute('dialog', meAsSaved ? '1' : '0'); avatarEl.setAttribute('peer', '' + peerID); avatarEl.classList.add('dialog-avatar'); if(drawStatus && peerID != $rootScope.myID && dialog.peer) { let peer = dialog.peer; switch(peer._) { case 'peerUser': let user = appUsersManager.getUser(peerID); //console.log('found user', user); if(user.status && user.status._ == 'userStatusOnline') { avatarEl.classList.add('is-online'); } break; default: break; } } let captionDiv = document.createElement('div'); captionDiv.classList.add('user-caption'); let titleSpan = document.createElement('span'); titleSpan.classList.add('user-title'); if(peerID == $rootScope.myID && meAsSaved) { title = onlyFirstName ? 'Saved' : 'Saved Messages'; } titleSpan.innerHTML = title; //p.classList.add('') // в других случаях иконка верификации не нужна (а первый - это главные чатлисты) if(!container) { let peer: any; // for muted icon titleSpan.classList.add('tgico'); if(peerID < 0) { peer = appChatsManager.getChat(-peerID); } else { peer = appUsersManager.getUser(peerID); } if(peer?.pFlags?.verified) { titleSpan.classList.add('is-verified'); const i = document.createElement('i'); i.classList.add('verified-icon'); titleSpan.append(i); } } let span = document.createElement('span'); span.classList.add('user-last-message'); //captionDiv.append(titleSpan); //captionDiv.append(span); let paddingDiv = document.createElement('div'); paddingDiv.classList.add('rp'); paddingDiv.append(avatarEl, captionDiv); if(rippleEnabled) { ripple(paddingDiv); /* ripple(paddingDiv, (id) => { this.log('dialogs click element'); this.lastClickID = id; return new Promise((resolve, reject) => { this.rippleCallback = resolve; //setTimeout(() => resolve(), 100); //window.requestAnimationFrame(() => window.requestAnimationFrame(() => resolve())); }); }, (id) => { //console.log('appDialogsManager: ripple onEnd called!'); if(id == this.lastGoodClickID) { appImManager.lazyLoadQueue.unlock(); } }); */ } let li = document.createElement('li'); li.append(paddingDiv); li.setAttribute('data-peerID', '' + peerID); let statusSpan = document.createElement('span'); statusSpan.classList.add('message-status'); let lastTimeSpan = document.createElement('span'); lastTimeSpan.classList.add('message-time'); let unreadMessagesSpan = document.createElement('span'); let titleP = document.createElement('p'); let rightSpan = document.createElement('span'); rightSpan.append(statusSpan, lastTimeSpan); titleP.append(titleSpan, rightSpan); let messageP = document.createElement('p'); messageP.append(span, unreadMessagesSpan); captionDiv.append(titleP, messageP); let dom: DialogDom = { avatarEl, captionDiv, titleSpan, statusSpan, lastTimeSpan, unreadMessagesSpan, lastMessageSpan: span, containerEl: paddingDiv, listEl: li }; /* let good = false; for(const folderID in this.chatLists) { if(this.chatLists[folderID] == container) { good = true; } } */ if(!container/* || good */) { this.scroll.append(li); this.doms[dialog.peerID] = dom; if($rootScope.selectedPeerID == peerID) { li.classList.add('active'); this.lastActiveListElement = li; } /* if(container) { container.append(li); } */ this.setLastMessage(dialog); } else { container.append(li); } return {dom, dialog}; } public setTyping(dialog: Dialog, user: User) { const dom = this.getDialogDom(dialog.peerID); if(!dom) { return; } let str = ''; if(dialog.peerID < 0) { let s = user.rFirstName || user.username; if(!s) return; str = s + ' '; } const senderBold = document.createElement('i'); str += 'typing...'; senderBold.innerHTML = str; dom.lastMessageSpan.innerHTML = ''; dom.lastMessageSpan.append(senderBold); dom.lastMessageSpan.classList.add('user-typing'); } public unsetTyping(dialog: Dialog) { const dom = this.getDialogDom(dialog.peerID); if(!dom) { return; } dom.lastMessageSpan.classList.remove('user-typing'); this.setLastMessage(dialog, null, dom); } } const appDialogsManager = new AppDialogsManager(); export default appDialogsManager;