import { findUpClassName, 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 { horizontalMenu } from "../../components/horizontalMenu";
import { ripple } from "../../components/ripple";
import { isSafari } from "../../helpers/userAgent";
import { formatDateAccordingToToday } from "../../helpers/date";
import $rootScope from "../rootScope";
import { isTouchSupported } from "../../helpers/touchSupport";
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.firstElementChild as HTMLElement).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.firstElementChild as HTMLElement).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.firstElementChild as HTMLElement).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.firstElementChild as HTMLElement).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.firstElementChild as HTMLElement).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(isTouchSupported && 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++;
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.getState().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.fromID) {
let sender = appUsersManager.getUser(lastMessage.fromID);
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) {
dom.lastTimeSpan.innerHTML = formatDateAccordingToToday(new Date(lastMessage.date * 1000));
} 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.fromID == $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;