3123 lines
97 KiB
TypeScript
3123 lines
97 KiB
TypeScript
/*
|
||
* https://github.com/morethanwords/tweb
|
||
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||
*/
|
||
|
||
import type {MyDialogFilter as DialogFilter, MyDialogFilter} from '../storages/filters';
|
||
import type LazyLoadQueue from '../../components/lazyLoadQueue';
|
||
import type {Dialog, ForumTopic, MyMessage} from './appMessagesManager';
|
||
import type {MyPhoto} from './appPhotosManager';
|
||
import type {MyDocument} from './appDocsManager';
|
||
import type {State} from '../../config/state';
|
||
import AvatarElement from '../../components/avatar';
|
||
import DialogsContextMenu from '../../components/dialogsContextMenu';
|
||
import {horizontalMenu} from '../../components/horizontalMenu';
|
||
import ripple from '../../components/ripple';
|
||
import Scrollable, {ScrollableX, SliceSides} from '../../components/scrollable';
|
||
import {formatDateAccordingToTodayNew} from '../../helpers/date';
|
||
import {IS_MOBILE_SAFARI, IS_SAFARI} from '../../environment/userAgent';
|
||
import {logger, LogTypes} from '../logger';
|
||
import rootScope from '../rootScope';
|
||
import appImManager, {AppImManager} from './appImManager';
|
||
import Button from '../../components/button';
|
||
import SetTransition from '../../components/singleTransition';
|
||
import {MyDraftMessage} from './appDraftsManager';
|
||
import DEBUG, {MOUNT_CLASS_TO} from '../../config/debug';
|
||
import PeerTitle from '../../components/peerTitle';
|
||
import I18n, {FormatterArguments, i18n, LangPackKey, _i18n} from '../langPack';
|
||
import findUpTag from '../../helpers/dom/findUpTag';
|
||
import lottieLoader from '../rlottie/lottieLoader';
|
||
import wrapPhoto from '../../components/wrappers/photo';
|
||
import AppEditFolderTab from '../../components/sidebarLeft/tabs/editFolder';
|
||
import appSidebarLeft from '../../components/sidebarLeft';
|
||
import {attachClickEvent} from '../../helpers/dom/clickEvent';
|
||
import positionElementByIndex from '../../helpers/dom/positionElementByIndex';
|
||
import replaceContent from '../../helpers/dom/replaceContent';
|
||
import ConnectionStatusComponent from '../../components/connectionStatus';
|
||
import {renderImageFromUrlPromise} from '../../helpers/dom/renderImageFromUrl';
|
||
import {fastRafConventional, fastRafPromise} from '../../helpers/schedulers';
|
||
import SortedUserList from '../../components/sortedUserList';
|
||
import IS_TOUCH_SUPPORTED from '../../environment/touchSupport';
|
||
import handleTabSwipe from '../../helpers/dom/handleTabSwipe';
|
||
import windowSize from '../../helpers/windowSize';
|
||
import isInDOM from '../../helpers/dom/isInDOM';
|
||
import {setSendingStatus} from '../../components/sendingStatus';
|
||
import SortedList, {SortedElementBase} from '../../helpers/sortedList';
|
||
import debounce from '../../helpers/schedulers/debounce';
|
||
import {FOLDER_ID_ALL, FOLDER_ID_ARCHIVE, NULL_PEER_ID, REAL_FOLDERS, REAL_FOLDER_ID} from '../mtproto/mtproto_config';
|
||
import groupCallActiveIcon from '../../components/groupCallActiveIcon';
|
||
import {Chat, Message, NotifyPeer} from '../../layer';
|
||
import IS_GROUP_CALL_SUPPORTED from '../../environment/groupCallSupport';
|
||
import mediaSizes from '../../helpers/mediaSizes';
|
||
import appNavigationController, {NavigationItem} from '../../components/appNavigationController';
|
||
import appMediaPlaybackController from '../../components/appMediaPlaybackController';
|
||
import setInnerHTML from '../../helpers/dom/setInnerHTML';
|
||
import {AppManagers} from './managers';
|
||
import appSidebarRight from '../../components/sidebarRight';
|
||
import PopupElement from '../../components/popups';
|
||
import choosePhotoSize from './utils/photos/choosePhotoSize';
|
||
import wrapEmojiText from '../richTextProcessor/wrapEmojiText';
|
||
import wrapMessageForReply from '../../components/wrappers/messageForReply';
|
||
import isMessageRestricted from './utils/messages/isMessageRestricted';
|
||
import getMediaFromMessage from './utils/messages/getMediaFromMessage';
|
||
import getMessageSenderPeerIdOrName from './utils/messages/getMessageSenderPeerIdOrName';
|
||
import wrapStickerEmoji from '../../components/wrappers/stickerEmoji';
|
||
import getDialogIndexKey from './utils/dialogs/getDialogIndexKey';
|
||
import getProxiedManagers from './getProxiedManagers';
|
||
import getDialogIndex from './utils/dialogs/getDialogIndex';
|
||
import {attachContextMenuListener} from '../../helpers/dom/attachContextMenuListener';
|
||
import deferredPromise, {CancellablePromise} from '../../helpers/cancellablePromise';
|
||
import wrapPeerTitle from '../../components/wrappers/peerTitle';
|
||
import middlewarePromise from '../../helpers/middlewarePromise';
|
||
import appDownloadManager from './appDownloadManager';
|
||
import groupCallsController from '../calls/groupCallsController';
|
||
import callsController from '../calls/callsController';
|
||
import cancelEvent from '../../helpers/dom/cancelEvent';
|
||
import noop from '../../helpers/noop';
|
||
import DialogsPlaceholder from '../../helpers/dialogsPlaceholder';
|
||
import pause from '../../helpers/schedulers/pause';
|
||
import apiManagerProxy from '../mtproto/mtprotoworker';
|
||
import filterAsync from '../../helpers/array/filterAsync';
|
||
import indexOfAndSplice from '../../helpers/array/indexOfAndSplice';
|
||
import whichChild from '../../helpers/dom/whichChild';
|
||
import {getMiddleware, MiddlewareHelper} from '../../helpers/middleware';
|
||
import makeError from '../../helpers/makeError';
|
||
import getUnsafeRandomInt from '../../helpers/number/getUnsafeRandomInt';
|
||
import Row, {RowMediaSizeType} from '../../components/row'
|
||
import SettingSection from '../../components/settingSection';
|
||
import {SliderSuperTabEventable} from '../../components/sliderTab';
|
||
import safeAssign from '../../helpers/object/safeAssign';
|
||
import ListenerSetter from '../../helpers/listenerSetter';
|
||
import {AckedResult} from '../mtproto/superMessagePort';
|
||
import ButtonMenuToggle from '../../components/buttonMenuToggle';
|
||
import getMessageThreadId from './utils/messages/getMessageThreadId';
|
||
import findUpClassName from '../../helpers/dom/findUpClassName';
|
||
import formatNumber from '../../helpers/number/formatNumber';
|
||
import AppSharedMediaTab from '../../components/sidebarRight/tabs/sharedMedia';
|
||
import {dispatchHeavyAnimationEvent} from '../../hooks/useHeavyAnimationCheck';
|
||
import AppArchivedTab from '../../components/sidebarLeft/tabs/archivedTab';
|
||
import shake from '../../helpers/dom/shake';
|
||
import AppEditTopicTab from '../../components/sidebarRight/tabs/editTopic';
|
||
import getServerMessageId from './utils/messageId/getServerMessageId';
|
||
|
||
export const DIALOG_LIST_ELEMENT_TAG = 'A';
|
||
|
||
export type DialogDom = {
|
||
avatarEl: AvatarElement,
|
||
captionDiv: HTMLElement,
|
||
titleSpan: HTMLSpanElement,
|
||
titleSpanContainer: HTMLSpanElement,
|
||
statusSpan: HTMLSpanElement,
|
||
lastTimeSpan: HTMLSpanElement,
|
||
unreadBadge?: HTMLElement,
|
||
unreadAvatarBadge?: HTMLElement,
|
||
callIcon?: ReturnType<typeof groupCallActiveIcon>,
|
||
mentionsBadge?: HTMLElement,
|
||
lastMessageSpan: HTMLSpanElement,
|
||
containerEl: HTMLElement,
|
||
listEl: HTMLElement,
|
||
subtitleEl: HTMLElement,
|
||
|
||
setLastMessagePromise?: CancellablePromise<void>,
|
||
setUnreadMessagePromise?: CancellablePromise<void>
|
||
};
|
||
|
||
interface SortedDialog extends SortedElementBase<PeerId> {
|
||
dom: DialogDom,
|
||
dialogElement: DialogElement
|
||
}
|
||
|
||
function setPromiseMiddleware<T extends {[smth in K as K]?: CancellablePromise<void>}, K extends keyof T>(obj: T, key: K) {
|
||
const oldPromise: CancellablePromise<void> = obj[key] as any;
|
||
oldPromise?.reject();
|
||
|
||
// @ts-ignore
|
||
const deferred = obj[key] = deferredPromise<void>();
|
||
deferred.catch(() => {}).finally(() => {
|
||
if((obj[key] as any) === deferred) {
|
||
delete obj[key];
|
||
}
|
||
});
|
||
|
||
const middleware = middlewarePromise(() => (obj[key] as any) === deferred);
|
||
return {deferred, middleware};
|
||
}
|
||
|
||
const BADGE_SIZE = 22;
|
||
const BADGE_TRANSITION_TIME = 250;
|
||
|
||
class SortedDialogList extends SortedList<SortedDialog> {
|
||
public managers: AppManagers;
|
||
public log: ReturnType<typeof logger>;
|
||
public list: HTMLElement;
|
||
public indexKey: ReturnType<typeof getDialogIndexKey>;
|
||
public onListLengthChange: () => void;
|
||
public forumPeerId: PeerId;
|
||
|
||
constructor(options: {
|
||
managers: SortedDialogList['managers'],
|
||
log?: SortedDialogList['log'],
|
||
list: SortedDialogList['list'],
|
||
indexKey: SortedDialogList['indexKey'],
|
||
onListLengthChange?: SortedDialogList['onListLengthChange'],
|
||
forumPeerId?: SortedDialogList['forumPeerId']
|
||
}) {
|
||
super({
|
||
getIndex: (element) => this.managers.dialogsStorage.getDialogIndex(this.forumPeerId ?? element.id, this.indexKey, this.forumPeerId ? element.id : undefined),
|
||
onDelete: (element) => {
|
||
element.dom.listEl.remove();
|
||
this.onListLengthChange?.();
|
||
},
|
||
onSort: (element, idx) => {
|
||
const willChangeLength = element.dom.listEl.parentElement !== this.list;
|
||
positionElementByIndex(element.dom.listEl, this.list, idx);
|
||
|
||
if(willChangeLength) {
|
||
this.onListLengthChange?.();
|
||
}
|
||
},
|
||
onElementCreate: async(base) => {
|
||
const loadPromises: Promise<any>[] = [];
|
||
|
||
const dialogElement = appDialogsManager.addListDialog({
|
||
peerId: this.forumPeerId ?? base.id,
|
||
loadPromises,
|
||
isBatch: true,
|
||
threadId: this.forumPeerId ? base.id : undefined,
|
||
isMainList: this.indexKey === 'index_0'
|
||
});
|
||
(base as SortedDialog).dom = dialogElement.dom;
|
||
(base as SortedDialog).dialogElement = dialogElement;
|
||
|
||
await Promise.all(loadPromises);
|
||
return base as SortedDialog;
|
||
},
|
||
updateElementWith: fastRafConventional,
|
||
log: options.log
|
||
});
|
||
|
||
safeAssign(this, options);
|
||
}
|
||
|
||
public clear() {
|
||
this.list.replaceChildren();
|
||
super.clear();
|
||
}
|
||
}
|
||
|
||
export type DialogElementSize = RowMediaSizeType;
|
||
type DialogElementOptions = {
|
||
peerId: PeerId,
|
||
rippleEnabled?: boolean,
|
||
onlyFirstName?: boolean,
|
||
meAsSaved?: boolean,
|
||
avatarSize?: RowMediaSizeType,
|
||
autonomous?: boolean,
|
||
loadPromises?: Promise<any>[],
|
||
fromName?: string,
|
||
noIcons?: boolean,
|
||
threadId?: number,
|
||
wrapOptions?: WrapSomethingOptions,
|
||
isMainList?: boolean
|
||
};
|
||
class DialogElement extends Row {
|
||
public dom: DialogDom;
|
||
|
||
constructor({
|
||
peerId,
|
||
rippleEnabled = true,
|
||
onlyFirstName = false,
|
||
meAsSaved = true,
|
||
avatarSize = 'bigger',
|
||
autonomous,
|
||
loadPromises,
|
||
fromName,
|
||
noIcons,
|
||
threadId,
|
||
wrapOptions = {},
|
||
isMainList
|
||
}: DialogElementOptions) {
|
||
super({
|
||
clickable: true,
|
||
noRipple: !rippleEnabled,
|
||
havePadding: !threadId,
|
||
title: true,
|
||
titleRightSecondary: true,
|
||
subtitle: true,
|
||
subtitleRight: true,
|
||
noWrap: true,
|
||
asLink: true
|
||
});
|
||
|
||
this.subtitleRight.remove();
|
||
|
||
const avatarEl = threadId ? undefined : new AvatarElement();
|
||
if(avatarEl) {
|
||
const avatarSizeMap: {[k in DialogElementSize]?: number} = {
|
||
bigger: 54,
|
||
abitbigger: 42,
|
||
small: 32
|
||
};
|
||
const s = avatarSizeMap[avatarSize];
|
||
avatarEl.classList.add('dialog-avatar', 'avatar-' + s);
|
||
avatarEl.updateWithOptions({
|
||
loadPromises,
|
||
lazyLoadQueue: wrapOptions.lazyLoadQueue,
|
||
isDialog: !!meAsSaved,
|
||
peerId,
|
||
peerTitle: fromName
|
||
});
|
||
|
||
this.applyMediaElement(avatarEl, avatarSize);
|
||
}
|
||
|
||
const captionDiv = this.container;
|
||
|
||
const titleSpanContainer = this.title;
|
||
titleSpanContainer.classList.add('user-title');
|
||
|
||
this.titleRow.classList.add('dialog-title');
|
||
|
||
const peerTitle = new PeerTitle();
|
||
const peerTitlePromise = peerTitle.update({
|
||
peerId,
|
||
fromName,
|
||
dialog: meAsSaved,
|
||
onlyFirstName,
|
||
withIcons: !noIcons,
|
||
threadId: threadId
|
||
});
|
||
|
||
loadPromises?.push(peerTitlePromise);
|
||
titleSpanContainer.append(peerTitle.element);
|
||
|
||
// for muted icon
|
||
titleSpanContainer.classList.add('tgico'); // * эта строка будет актуальна только для !container, но ладно
|
||
|
||
// const titleIconsPromise = generateTitleIcons(peerId).then((elements) => {
|
||
// titleSpanContainer.append(...elements);
|
||
// });
|
||
|
||
// if(loadPromises) {
|
||
// loadPromises.push(titleIconsPromise);
|
||
// }
|
||
// }
|
||
|
||
const span = this.subtitle;
|
||
// span.classList.add('user-last-message');
|
||
|
||
const li = this.container;
|
||
li.classList.add('chatlist-chat', 'chatlist-chat-' + avatarSize);
|
||
if(!autonomous) {
|
||
(li as HTMLAnchorElement).href = '#' + peerId;
|
||
}
|
||
// if(rippleEnabled) {
|
||
// ripple(li);
|
||
// }
|
||
|
||
if(avatarSize === 'bigger') {
|
||
this.container.classList.add('row-big');
|
||
} else if(avatarSize === 'small') {
|
||
this.container.classList.add('row-small');
|
||
}
|
||
|
||
li.dataset.peerId = '' + peerId;
|
||
if(threadId) {
|
||
li.dataset.threadId = '' + threadId;
|
||
}
|
||
|
||
const statusSpan = document.createElement('span');
|
||
statusSpan.classList.add('message-status', 'sending-status'/* , 'transition', 'reveal' */);
|
||
|
||
const lastTimeSpan = document.createElement('span');
|
||
lastTimeSpan.classList.add('message-time');
|
||
|
||
const rightSpan = this.titleRight;
|
||
rightSpan.classList.add('dialog-title-details');
|
||
rightSpan.append(statusSpan, lastTimeSpan);
|
||
|
||
this.subtitleRow.classList.add('dialog-subtitle');
|
||
|
||
const dom: DialogDom = this.dom = {
|
||
avatarEl,
|
||
captionDiv,
|
||
titleSpan: peerTitle.element,
|
||
titleSpanContainer,
|
||
statusSpan,
|
||
lastTimeSpan,
|
||
lastMessageSpan: span,
|
||
containerEl: li,
|
||
listEl: li,
|
||
subtitleEl: this.subtitleRow
|
||
};
|
||
|
||
if(!autonomous) {
|
||
(li as any).dialogDom = dom;
|
||
|
||
const chat = appImManager.chat;
|
||
if(chat && appImManager.isSamePeer(chat, {peerId, threadId: threadId, type: 'chat'})) {
|
||
appDialogsManager.setDialogActive(li, true);
|
||
}
|
||
|
||
if(isMainList && appDialogsManager.forumTab?.peerId === peerId && !threadId) {
|
||
li.classList.add('is-forum-open');
|
||
}
|
||
}
|
||
}
|
||
|
||
public createUnreadBadge() {
|
||
if(this.dom.unreadBadge) return;
|
||
const badge = this.dom.unreadBadge = document.createElement('div');
|
||
badge.className = `dialog-subtitle-badge badge badge-${BADGE_SIZE}`;
|
||
this.dom.subtitleEl.append(badge);
|
||
}
|
||
|
||
public createUnreadAvatarBadge() {
|
||
if(this.dom.unreadAvatarBadge) return;
|
||
const badge = this.dom.unreadAvatarBadge = document.createElement('div');
|
||
badge.className = `dialog-subtitle-badge badge badge-${BADGE_SIZE} avatar-badge`;
|
||
this.dom.listEl.append(badge);
|
||
}
|
||
|
||
public createMentionsBadge() {
|
||
if(this.dom.mentionsBadge) return;
|
||
const badge = this.dom.mentionsBadge = document.createElement('div');
|
||
badge.className = `dialog-subtitle-badge badge badge-${BADGE_SIZE} mention mention-badge`;
|
||
badge.innerText = '@';
|
||
this.dom.lastMessageSpan.after(badge);
|
||
}
|
||
|
||
public toggleBadgeByKey(
|
||
key: Extract<keyof DialogDom, 'unreadBadge' | 'unreadAvatarBadge' | 'mentionsBadge'>,
|
||
hasBadge: boolean,
|
||
justCreated: boolean,
|
||
batch?: boolean
|
||
) {
|
||
SetTransition({
|
||
element: this.dom[key],
|
||
className: 'is-visible',
|
||
forwards: hasBadge,
|
||
duration: batch ? 0 : BADGE_TRANSITION_TIME,
|
||
onTransitionEnd: hasBadge ? undefined : () => {
|
||
this.dom[key].remove();
|
||
delete this.dom[key];
|
||
},
|
||
useRafs: !justCreated || !isInDOM(this.dom[key]) ? 2 : 0
|
||
});
|
||
}
|
||
}
|
||
|
||
class ForumTab extends SliderSuperTabEventable {
|
||
private rows: HTMLElement;
|
||
private subtitle: HTMLElement;
|
||
|
||
public peerId: PeerId;
|
||
private firstTime: boolean;
|
||
|
||
private log: ReturnType<typeof logger>;
|
||
|
||
private xd: Some3;
|
||
|
||
public async toggle(value: boolean) {
|
||
if(this.init2) {
|
||
await this.init2();
|
||
}
|
||
|
||
SetTransition({
|
||
element: this.container,
|
||
className: 'is-visible',
|
||
forwards: value,
|
||
duration: 300,
|
||
onTransitionEnd: !value ? () => {
|
||
this.onCloseAfterTimeout();
|
||
} : undefined,
|
||
useRafs: this.firstTime ? (this.firstTime = undefined, 2) : undefined
|
||
});
|
||
}
|
||
|
||
public init(options: {
|
||
peerId: PeerId,
|
||
managers: AppManagers
|
||
}) {
|
||
safeAssign(this, options);
|
||
|
||
this.log = logger('FORUM');
|
||
this.firstTime = true;
|
||
this.container.classList.add('topics-container');
|
||
|
||
const isFloating = !this.slider;
|
||
if(isFloating) {
|
||
this.closeBtn.classList.replace('tgico-left', 'tgico-close');
|
||
this.container.classList.add('active', 'is-floating');
|
||
|
||
attachClickEvent(this.closeBtn, () => {
|
||
appDialogsManager.toggleForumTab(undefined, this);
|
||
}, {listenerSetter: this.listenerSetter});
|
||
}
|
||
|
||
this.rows = document.createElement('div');
|
||
this.rows.classList.add('sidebar-header__rows');
|
||
|
||
this.subtitle = document.createElement('div');
|
||
this.subtitle.classList.add('sidebar-header__subtitle');
|
||
|
||
this.title.replaceWith(this.rows);
|
||
this.rows.append(this.title, this.subtitle);
|
||
|
||
const list = appDialogsManager.createChatList();
|
||
appDialogsManager.setListClickListener(list, null, true);
|
||
this.scrollable.append(list);
|
||
|
||
this.xd = new Some3(this.peerId, isFloating ? 80 : 0);
|
||
this.xd.scrollable = this.scrollable;
|
||
this.xd.sortedList = new SortedDialogList({
|
||
managers: this.managers,
|
||
log: this.log,
|
||
list,
|
||
indexKey: 'index_0',
|
||
forumPeerId: this.peerId
|
||
});
|
||
this.xd.bindScrollable();
|
||
|
||
const getOptionsForMessages = (): Parameters<AppImManager['isSamePeer']>[0] => {
|
||
return {
|
||
peerId: this.peerId,
|
||
type: 'chat'
|
||
};
|
||
};
|
||
|
||
const btnMenu = ButtonMenuToggle({
|
||
listenerSetter: this.listenerSetter,
|
||
direction: 'bottom-left',
|
||
buttons: [{
|
||
icon: 'info',
|
||
text: 'ForumTopic.Context.Info',
|
||
onClick: async() => {
|
||
const tab = appSidebarLeft.createTab(AppSharedMediaTab, true);
|
||
tab.isFirst = true;
|
||
tab.setPeer(this.peerId);
|
||
(await tab.fillProfileElements())();
|
||
tab.loadSidebarMedia(true);
|
||
tab.open();
|
||
}
|
||
}, {
|
||
icon: 'message',
|
||
text: 'ForumTopic.Context.ShowAsMessages',
|
||
onClick: () => {
|
||
const chat = appImManager.chat;
|
||
appImManager[chat?.peerId === this.peerId ? 'setPeer' : 'setInnerPeer'](getOptionsForMessages());
|
||
},
|
||
verify: () => {
|
||
const chat = appImManager.chat;
|
||
return !chat || !appImManager.isSamePeer(chat, getOptionsForMessages());
|
||
}
|
||
}, {
|
||
icon: 'adduser',
|
||
text: 'ForumTopic.Context.AddMember',
|
||
onClick: () => {},
|
||
verify: () => false && this.managers.appChatsManager.hasRights(this.peerId.toChatId(), 'invite_users')
|
||
}, {
|
||
icon: 'add',
|
||
text: 'ForumTopic.Context.New',
|
||
onClick: () => {
|
||
appSidebarLeft.createTab(AppEditTopicTab).open(this.peerId);
|
||
},
|
||
separator: true,
|
||
verify: () => this.managers.appChatsManager.hasRights(this.peerId.toChatId(), 'manage_topics')
|
||
}]
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('history_reload', (peerId) => {
|
||
if(this.peerId !== peerId) {
|
||
return;
|
||
}
|
||
|
||
this.xd.fullReset();
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('chat_update', async(chatId) => {
|
||
if(this.peerId !== chatId.toPeerId(true)) {
|
||
return;
|
||
}
|
||
|
||
const chat = await this.managers.appChatsManager.getChat(chatId);
|
||
if(!(chat as Chat.channel).pFlags.forum) {
|
||
appDialogsManager.toggleForumTab(undefined, this);
|
||
}
|
||
});
|
||
|
||
this.header.append(btnMenu);
|
||
|
||
if(!isFloating) {
|
||
return this.init2();
|
||
}
|
||
}
|
||
|
||
public async init2() {
|
||
this.init2 = undefined;
|
||
|
||
const middleware = this.middlewareHelper.get();
|
||
const peerId = this.peerId;
|
||
|
||
this.managers.apiUpdatesManager.subscribeToChannelUpdates(this.peerId.toChatId());
|
||
middleware.onDestroy(() => {
|
||
this.managers.apiUpdatesManager.unsubscribeFromChannelUpdates(this.peerId.toChatId());
|
||
});
|
||
|
||
const peerTitlePromise = wrapPeerTitle({
|
||
peerId,
|
||
dialog: true,
|
||
wrapOptions: {middleware}
|
||
});
|
||
|
||
const setStatusPromise = appImManager.setPeerStatus({
|
||
peerId,
|
||
element: this.subtitle,
|
||
needClear: true,
|
||
useWhitespace: false,
|
||
middleware,
|
||
noTyping: true
|
||
});
|
||
|
||
// this.managers.dialogsStorage.getForumTopics(this.peerId).then((messagesForumTopics) => {
|
||
// console.log(messagesForumTopics);
|
||
|
||
// const promises = messagesForumTopics.topics.map((forumTopic) => {
|
||
// return this.sortedDialogList.add(forumTopic.id);
|
||
// });
|
||
|
||
// return Promise.all(promises);
|
||
// }).then(() => {
|
||
// this.dialogsPlaceholder.detach(this.sortedDialogList.getAll().size);
|
||
// });
|
||
|
||
return Promise.all([
|
||
peerTitlePromise,
|
||
setStatusPromise,
|
||
this.xd.onChatsScroll().then((loadResult) => {
|
||
return loadResult.cached ? loadResult.renderPromise : undefined
|
||
})
|
||
]).then(([
|
||
peerTitle,
|
||
setStatus,
|
||
_
|
||
]) => {
|
||
if(!middleware()) {
|
||
return;
|
||
}
|
||
|
||
this.title.append(peerTitle);
|
||
setStatus?.();
|
||
});
|
||
}
|
||
|
||
public onCloseAfterTimeout() {
|
||
super.onCloseAfterTimeout();
|
||
this.xd.destroy();
|
||
}
|
||
}
|
||
|
||
const NOT_IMPLEMENTED_ERROR = new Error('not implemented');
|
||
|
||
type DialogKey = Parameters<Some['sortedList']['delete']>[0];
|
||
class Some<T extends Dialog | ForumTopic = Dialog | ForumTopic> {
|
||
public sortedList: SortedDialogList;
|
||
public scrollable: Scrollable;
|
||
public loadedDialogsAtLeastOnce: boolean;
|
||
public needPlaceholderAtFirstTime: boolean;
|
||
protected offsets: {top: number, bottom: number};
|
||
protected indexKey: ReturnType<typeof getDialogIndexKey>;
|
||
protected sliceTimeout: number;
|
||
protected managers: AppManagers;
|
||
protected listenerSetter: ListenerSetter;
|
||
protected loadDialogsPromise: Promise<{cached: boolean, renderPromise: Some2['loadDialogsRenderPromise']}>;
|
||
protected loadDialogsRenderPromise: Promise<void>;
|
||
protected placeholder: DialogsPlaceholder;
|
||
protected log: ReturnType<typeof logger>;
|
||
protected placeholderOptions: ConstructorParameters<typeof DialogsPlaceholder>[0];
|
||
|
||
constructor() {
|
||
this.log = logger('CL');
|
||
this.offsets = {top: 0, bottom: 0};
|
||
this.managers = rootScope.managers;
|
||
this.listenerSetter = new ListenerSetter();
|
||
}
|
||
|
||
public getOffsetIndex(side: 'top' | 'bottom') {
|
||
return {index: this.scrollable.loadedAll[side] ? 0 : this.offsets[side]};
|
||
}
|
||
|
||
protected isDialogMustBeInViewport(dialog: T) {
|
||
// return true;
|
||
const topOffset = this.getOffsetIndex('top');
|
||
const bottomOffset = this.getOffsetIndex('bottom');
|
||
|
||
if(!topOffset.index && !bottomOffset.index) {
|
||
return true;
|
||
}
|
||
|
||
const index = getDialogIndex(dialog, this.indexKey);
|
||
return (!topOffset.index || index <= topOffset.index) &&
|
||
(!bottomOffset.index || index >= bottomOffset.index);
|
||
}
|
||
|
||
public setIndexKey(indexKey: Some['indexKey']) {
|
||
this.indexKey = indexKey;
|
||
this.sortedList.indexKey = indexKey;
|
||
}
|
||
|
||
protected deleteDialogByKey(key: DialogKey) {
|
||
this.sortedList.delete(key);
|
||
}
|
||
|
||
public deleteDialog(dialog: T) {
|
||
return this.deleteDialogByKey(this.getDialogKey(dialog));
|
||
}
|
||
|
||
public updateDialog(dialog: T) {
|
||
const key = this.getDialogKey(dialog);
|
||
if(this.isDialogMustBeInViewport(dialog)) {
|
||
if(!this.sortedList.has(key) && this.loadedDialogsAtLeastOnce) {
|
||
this.sortedList.add(key);
|
||
return;
|
||
}
|
||
} else {
|
||
this.deleteDialog(dialog);
|
||
return;
|
||
}
|
||
|
||
const dialogElement = this.getDialogElement(key);
|
||
if(dialogElement) {
|
||
appDialogsManager.setLastMessageN({
|
||
dialog,
|
||
dialogElement,
|
||
setUnread: true
|
||
});
|
||
this.sortedList.update(key);
|
||
}
|
||
}
|
||
|
||
public onChatsRegularScroll = () => {
|
||
// return;
|
||
|
||
if(this.sliceTimeout) clearTimeout(this.sliceTimeout);
|
||
this.sliceTimeout = window.setTimeout(() => {
|
||
this.sliceTimeout = undefined;
|
||
|
||
if(!this.sortedList.list.childElementCount || appDialogsManager.processContact) {
|
||
return;
|
||
}
|
||
|
||
/* const observer = new IntersectionObserver((entries) => {
|
||
const
|
||
});
|
||
|
||
Array.from(this.chatList.children).forEach((el) => {
|
||
observer.observe(el);
|
||
}); */
|
||
|
||
fastRafConventional(() => {
|
||
const perf = performance.now();
|
||
|
||
const scrollTopWas = this.scrollable.scrollTop;
|
||
|
||
const firstElementChild = this.sortedList.list.firstElementChild;
|
||
const rectContainer = this.scrollable.container.getBoundingClientRect();
|
||
const rectTarget = firstElementChild.getBoundingClientRect();
|
||
const children = Array.from(this.scrollable.splitUp.children) as HTMLElement[];
|
||
|
||
// const padding = 8;
|
||
// const offsetTop = this.folders.container.offsetTop;
|
||
let offsetTop = this.scrollable.splitUp.offsetTop;
|
||
if(offsetTop && scrollTopWas < offsetTop) offsetTop -= scrollTopWas;
|
||
// const offsetTop = scrollTopWas < padding ? padding - scrollTopWas : 0;
|
||
const firstY = rectContainer.y + offsetTop;
|
||
const lastY = rectContainer.y/* - 8 */; // 8px - .chatlist padding-bottom
|
||
|
||
const firstElement = findUpTag(document.elementFromPoint(Math.ceil(rectTarget.x), Math.ceil(firstY + 1)), firstElementChild.tagName) as HTMLElement;
|
||
const lastElement = findUpTag(document.elementFromPoint(Math.ceil(rectTarget.x), Math.floor(lastY + rectContainer.height - 1)), firstElementChild.tagName) as HTMLElement;
|
||
|
||
// alert('got element:' + rect.y);
|
||
|
||
if(!firstElement || !lastElement) {
|
||
return;
|
||
}
|
||
|
||
// alert('got element:' + !!firstElement);
|
||
|
||
const firstElementRect = firstElement.getBoundingClientRect();
|
||
const elementOverflow = firstElementRect.y - firstY;
|
||
|
||
const sliced: HTMLElement[] = [];
|
||
const firstIndex = children.indexOf(firstElement);
|
||
const lastIndex = children.indexOf(lastElement);
|
||
|
||
const saveLength = 10;
|
||
|
||
const sliceFromStart = IS_SAFARI ? [] : children.slice(0, Math.max(0, firstIndex - saveLength));
|
||
const sliceFromEnd = children.slice(lastIndex + saveLength);
|
||
|
||
/* if(sliceFromStart.length !== sliceFromEnd.length) {
|
||
console.log('not equal', sliceFromStart.length, sliceFromEnd.length);
|
||
}
|
||
|
||
if(sliceFromStart.length > sliceFromEnd.length) {
|
||
const diff = sliceFromStart.length - sliceFromEnd.length;
|
||
sliceFromStart.splice(0, diff);
|
||
} else if(sliceFromEnd.length > sliceFromStart.length) {
|
||
const diff = sliceFromEnd.length - sliceFromStart.length;
|
||
sliceFromEnd.splice(sliceFromEnd.length - diff, diff);
|
||
} */
|
||
|
||
if(sliceFromStart.length) {
|
||
this.scrollable.loadedAll.top = false;
|
||
}
|
||
|
||
if(sliceFromEnd.length) {
|
||
this.scrollable.loadedAll.bottom = false;
|
||
}
|
||
|
||
sliced.push(...sliceFromStart);
|
||
sliced.push(...sliceFromEnd);
|
||
|
||
sliced.forEach((el) => {
|
||
this.deleteDialogByKey(this.getDialogKeyFromElement(el));
|
||
});
|
||
|
||
this.setOffsets();
|
||
|
||
// this.log('[slicer] elements', firstElement, lastElement, rect, sliced, sliceFromStart.length, sliceFromEnd.length);
|
||
|
||
// this.log('[slicer] reset scrollTop', this.scroll.scrollTop, firstElement.offsetTop, firstElementRect.y, rect.y, elementOverflow);
|
||
|
||
// alert('left length:' + children.length);
|
||
|
||
this.scrollable.scrollTop = firstElement.offsetTop - elementOverflow;
|
||
|
||
this.log('slice time', performance.now() - perf);
|
||
/* const firstElementRect = firstElement.getBoundingClientRect();
|
||
const scrollTop = */
|
||
|
||
// this.scroll.scrollIntoView(firstElement, false);
|
||
});
|
||
}, 200);
|
||
};
|
||
|
||
public onChatsScrollTop() {
|
||
return this.onChatsScroll('top');
|
||
};
|
||
|
||
public onChatsScroll(side: SliceSides = 'bottom') {
|
||
return this.loadDialogs(side);
|
||
};
|
||
|
||
public createPlaceholder(): DialogsPlaceholder {
|
||
const placeholder = this.placeholder = new DialogsPlaceholder(this.placeholderOptions);
|
||
const getRectFrom = this.getRectFromForPlaceholder();
|
||
placeholder.attach({
|
||
container: this.sortedList.list.parentElement,
|
||
getRectFrom,
|
||
onRemove: () => {
|
||
if(this.placeholder === placeholder) {
|
||
this.placeholder = undefined;
|
||
}
|
||
},
|
||
blockScrollable: this.scrollable
|
||
});
|
||
|
||
return placeholder;
|
||
}
|
||
|
||
public loadDialogs(side: SliceSides) {
|
||
/* if(testScroll) {
|
||
return;
|
||
} */
|
||
|
||
const log = this.log.bindPrefix('load-' + getUnsafeRandomInt(1000, 9999));
|
||
log('try', side);
|
||
|
||
if(this.loadDialogsPromise || this.loadDialogsRenderPromise/* || 1 === 1 */) return this.loadDialogsPromise;
|
||
else if(this.scrollable.loadedAll[side]) {
|
||
return Promise.resolve({
|
||
cached: true,
|
||
renderPromise: Promise.resolve()
|
||
});
|
||
}
|
||
|
||
log.warn('start', side);
|
||
const middlewareError = makeError('MIDDLEWARE');
|
||
|
||
const cachedInfoPromise = deferredPromise<boolean>();
|
||
const renderPromise = new Promise<void>(async(resolve, reject) => {
|
||
const chatList = this.sortedList.list;
|
||
|
||
let placeholder = this.placeholder;
|
||
try {
|
||
const getConversationsResult = this.loadDialogsInner(side);
|
||
const a = await getConversationsResult;
|
||
|
||
if(
|
||
!chatList.childElementCount &&
|
||
!placeholder &&
|
||
(
|
||
(!this.loadedDialogsAtLeastOnce && this.needPlaceholderAtFirstTime) ||
|
||
!a.cached
|
||
)
|
||
) {
|
||
if(this.loadDialogsRenderPromise !== renderPromise) {
|
||
throw middlewareError;
|
||
}
|
||
|
||
placeholder = this.createPlaceholder();
|
||
|
||
cachedInfoPromise.resolve(false);
|
||
}
|
||
|
||
const result = await a.result;
|
||
// await pause(5000);
|
||
if(this.loadDialogsRenderPromise !== renderPromise) {
|
||
throw middlewareError;
|
||
}
|
||
|
||
cachedInfoPromise.resolve(a.cached);
|
||
|
||
// console.timeEnd('getDialogs time');
|
||
|
||
// * loaded all
|
||
// if(!result.dialogs.length || chatList.childElementCount === result.count) {
|
||
// !result.dialogs.length не подходит, так как при супердревном диалоге getConversations его не выдаст.
|
||
// if(chatList.childElementCount === result.count) {
|
||
if(side === 'bottom') {
|
||
if(result.isEnd) {
|
||
this.scrollable.loadedAll[side] = true;
|
||
}
|
||
} else if(result.isTopEnd) {
|
||
this.scrollable.loadedAll[side] = true;
|
||
}
|
||
|
||
const length = result.dialogs.length;
|
||
log(`will render ${length} dialogs`);
|
||
if(length) {
|
||
const dialogs = side === 'top' ? result.dialogs.slice().reverse() : result.dialogs;
|
||
|
||
const loadPromises = dialogs.map((dialog) => {
|
||
return this.sortedList.add(this.getDialogKey(dialog as T));
|
||
});
|
||
|
||
await Promise.all(loadPromises).catch();
|
||
if(this.loadDialogsRenderPromise !== renderPromise) {
|
||
throw middlewareError;
|
||
}
|
||
}
|
||
|
||
const offsetDialog = result.dialogs[side === 'top' ? 0 : length - 1];
|
||
if(offsetDialog) {
|
||
this.offsets[side] = getDialogIndex(offsetDialog, this.indexKey);
|
||
}
|
||
|
||
// don't set it before - no need to fire length change with every dialog
|
||
this.loadedDialogsAtLeastOnce = true;
|
||
appDialogsManager.onListLengthChange();
|
||
|
||
log('getDialogs', result, chatList.childElementCount);
|
||
|
||
setTimeout(() => {
|
||
this.scrollable.onScroll();
|
||
}, 0);
|
||
|
||
if(placeholder) {
|
||
// await pause(500);
|
||
placeholder.detach(chatList.childElementCount);
|
||
}
|
||
} catch(err) {
|
||
if((err as ApiError)?.type !== 'MIDDLEWARE') {
|
||
log.error(err);
|
||
}
|
||
|
||
reject(err);
|
||
cachedInfoPromise.reject(err);
|
||
return;
|
||
}
|
||
|
||
resolve();
|
||
}).finally(() => {
|
||
if(this.loadDialogsRenderPromise === renderPromise) {
|
||
log('end');
|
||
this.loadDialogsRenderPromise = undefined;
|
||
} else {
|
||
log('has been cleared');
|
||
}
|
||
});
|
||
|
||
this.loadDialogsRenderPromise = renderPromise;
|
||
const loadDialogsPromise = this.loadDialogsPromise = cachedInfoPromise.then((cached) => {
|
||
return {
|
||
cached,
|
||
renderPromise
|
||
};
|
||
}).finally(() => {
|
||
if(this.loadDialogsPromise === loadDialogsPromise) {
|
||
this.loadDialogsPromise = undefined;
|
||
}
|
||
});
|
||
|
||
return loadDialogsPromise;
|
||
}
|
||
|
||
public async setOffsets() {
|
||
const chatList = this.sortedList.list;
|
||
const [firstDialog, lastDialog] = await Promise.all([
|
||
this.getDialogFromElement(chatList.firstElementChild as HTMLElement),
|
||
this.getDialogFromElement(chatList.lastElementChild as HTMLElement)
|
||
]);
|
||
|
||
const {indexKey} = this;
|
||
this.offsets.top = getDialogIndex(firstDialog, indexKey);
|
||
this.offsets.bottom = getDialogIndex(lastDialog, indexKey);
|
||
}
|
||
|
||
public getDialogKey(dialog: T): DialogKey {
|
||
throw NOT_IMPLEMENTED_ERROR;
|
||
}
|
||
|
||
public getDialogKeyFromElement(element: HTMLElement): DialogKey {
|
||
throw NOT_IMPLEMENTED_ERROR;
|
||
}
|
||
|
||
public getRectFromForPlaceholder(): Parameters<DialogsPlaceholder['attach']>[0]['getRectFrom'] {
|
||
throw NOT_IMPLEMENTED_ERROR;
|
||
}
|
||
|
||
public getDialogFromElement(element: HTMLElement): Promise<T> {
|
||
throw NOT_IMPLEMENTED_ERROR;
|
||
}
|
||
|
||
public loadDialogsInner(side: SliceSides): ReturnType<AppManagers['acknowledged']['dialogsStorage']['getDialogs']> {
|
||
throw NOT_IMPLEMENTED_ERROR;
|
||
}
|
||
|
||
public async setTyping(dialog: T) {
|
||
const key = this.getDialogKey(dialog);
|
||
const dom = this.getDialogDom(key);
|
||
if(!dom) {
|
||
return;
|
||
}
|
||
|
||
const oldTypingElement = dom.lastMessageSpan.querySelector('.peer-typing-container') as HTMLElement;
|
||
const newTypingElement = await appImManager.getPeerTyping(
|
||
dialog.peerId,
|
||
oldTypingElement,
|
||
dialog._ === 'forumTopic' ? dialog.id : undefined
|
||
);
|
||
if(!oldTypingElement && newTypingElement) {
|
||
replaceContent(dom.lastMessageSpan, newTypingElement);
|
||
dom.lastMessageSpan.classList.add('user-typing');
|
||
}
|
||
}
|
||
|
||
public unsetTyping(dialog: T) {
|
||
const key = this.getDialogKey(dialog);
|
||
const dialogElement = this.getDialogElement(key);
|
||
if(!dialogElement) {
|
||
return;
|
||
}
|
||
|
||
dialogElement.dom.lastMessageSpan.classList.remove('user-typing');
|
||
appDialogsManager.setLastMessageN({
|
||
dialog,
|
||
lastMessage: null,
|
||
dialogElement,
|
||
setUnread: null
|
||
});
|
||
}
|
||
|
||
public getDialogDom(key: DialogKey) {
|
||
// return this.doms[peerId];
|
||
const element = this.sortedList.get(key);
|
||
return element?.dom;
|
||
}
|
||
|
||
public getDialogElement(key: DialogKey) {
|
||
const element = this.sortedList.get(key);
|
||
return element?.dialogElement;
|
||
}
|
||
|
||
public bindScrollable() {
|
||
this.scrollable.container.addEventListener('scroll', this.onChatsRegularScroll);
|
||
this.scrollable.onScrolledTop = this.onChatsScrollTop.bind(this);
|
||
this.scrollable.onScrolledBottom = this.onChatsScroll.bind(this);
|
||
this.scrollable.setVirtualContainer(this.sortedList.list);
|
||
}
|
||
|
||
public clear() {
|
||
this.sortedList.clear();
|
||
this.placeholder?.remove();
|
||
}
|
||
|
||
public reset() {
|
||
this.scrollable.loadedAll.top = true;
|
||
this.scrollable.loadedAll.bottom = false;
|
||
this.offsets.top = this.offsets.bottom = 0;
|
||
this.loadDialogsRenderPromise = undefined;
|
||
this.loadDialogsPromise = undefined;
|
||
}
|
||
|
||
public fullReset() {
|
||
this.reset();
|
||
this.clear();
|
||
return this.onChatsScroll();
|
||
}
|
||
|
||
public destroy() {
|
||
this.clear();
|
||
this.scrollable.destroy();
|
||
this.listenerSetter.removeAll();
|
||
}
|
||
}
|
||
|
||
class Some3 extends Some<ForumTopic> {
|
||
constructor(public peerId: PeerId, public paddingX: number) {
|
||
super();
|
||
|
||
this.placeholderOptions = {
|
||
avatarSize: 0,
|
||
marginVertical: 5,
|
||
totalHeight: 64
|
||
};
|
||
|
||
this.listenerSetter.add(rootScope)('peer_typings', async({peerId, threadId, typings}) => {
|
||
if(!threadId || this.peerId !== peerId) {
|
||
return;
|
||
}
|
||
|
||
const dialog = await this.managers.dialogsStorage.getForumTopic(peerId, threadId);
|
||
|
||
if(!dialog) return;
|
||
|
||
if(typings.length) {
|
||
this.setTyping(dialog);
|
||
} else {
|
||
this.unsetTyping(dialog);
|
||
}
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('dialogs_multiupdate', (dialogs) => {
|
||
for(const [peerId, {dialog, topics}] of dialogs) {
|
||
if(peerId !== this.peerId || !topics?.size) {
|
||
continue;
|
||
}
|
||
|
||
topics.forEach((forumTopic) => {
|
||
this.updateDialog(forumTopic);
|
||
});
|
||
}
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('dialog_unread', ({dialog}) => {
|
||
if(dialog?._ !== 'forumTopic' || dialog.peerId !== this.peerId) {
|
||
return;
|
||
}
|
||
|
||
appDialogsManager.setUnreadMessagesN({dialog, dialogElement: this.getDialogElement(this.getDialogKey(dialog))});
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('dialog_notify_settings', async(dialog) => {
|
||
if(dialog.peerId !== this.peerId) {
|
||
return;
|
||
}
|
||
|
||
if(dialog._ === 'dialog') {
|
||
const all = this.sortedList.getAll();
|
||
const entries = [...all.entries()];
|
||
const promises = entries.map(([id]) => this.managers.dialogsStorage.getForumTopic(this.peerId, id));
|
||
const topics = await Promise.all(promises);
|
||
entries.forEach(([id, element], idx) => {
|
||
appDialogsManager.setUnreadMessagesN({dialog: topics[idx], dialogElement: element.dialogElement}); // возможно это не нужно, но нужно менять is-muted
|
||
});
|
||
|
||
return;
|
||
}
|
||
|
||
appDialogsManager.setUnreadMessagesN({dialog, dialogElement: this.getDialogElement(this.getDialogKey(dialog))}); // возможно это не нужно, но нужно менять is-muted
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('dialog_drop', (dialog) => {
|
||
if(dialog._ !== 'forumTopic' || dialog.peerId !== this.peerId) {
|
||
return;
|
||
}
|
||
|
||
this.deleteDialogByKey(this.getDialogKey(dialog));
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('dialog_draft', ({dialog, drop, peerId}) => {
|
||
if(dialog._ !== 'forumTopic' || dialog.peerId !== this.peerId) {
|
||
return;
|
||
}
|
||
|
||
if(drop) {
|
||
this.deleteDialog(dialog);
|
||
} else {
|
||
this.updateDialog(dialog);
|
||
}
|
||
});
|
||
}
|
||
|
||
protected isDialogMustBeInViewport(dialog: ForumTopic) {
|
||
if(dialog.pFlags.hidden) return false;
|
||
return super.isDialogMustBeInViewport(dialog);
|
||
}
|
||
|
||
public getDialogKey(dialog: ForumTopic) {
|
||
return dialog.id;
|
||
}
|
||
|
||
public getDialogKeyFromElement(element: HTMLElement) {
|
||
return +element.dataset.threadId;
|
||
}
|
||
|
||
public getRectFromForPlaceholder() {
|
||
return (): DOMRectEditable => {
|
||
const sidebarRect = appSidebarLeft.rect;
|
||
const paddingY = 56;
|
||
return {
|
||
top: paddingY,
|
||
right: sidebarRect.right,
|
||
bottom: 0,
|
||
left: this.paddingX,
|
||
width: sidebarRect.width - this.paddingX,
|
||
height: sidebarRect.height - paddingY
|
||
};
|
||
};
|
||
}
|
||
|
||
public getDialogFromElement(element: HTMLElement) {
|
||
return this.managers.dialogsStorage.getForumTopic(+element.dataset.peerId, +element.dataset.threadId);
|
||
}
|
||
|
||
public async loadDialogsInner(side: SliceSides) {
|
||
const {indexKey} = this;
|
||
let loadCount = windowSize.height / 64 * 1.25 | 0;
|
||
let offsetIndex = 0;
|
||
|
||
const filterId = this.peerId;
|
||
const {index: currentOffsetIndex} = this.getOffsetIndex(side);
|
||
offsetIndex = currentOffsetIndex;
|
||
if(currentOffsetIndex) {
|
||
if(side === 'top') {
|
||
const storage = await this.managers.dialogsStorage.getFolderDialogs(filterId, true);
|
||
const index = storage.findIndex((dialog) => getDialogIndex(dialog, indexKey) <= currentOffsetIndex);
|
||
const needIndex = Math.max(0, index - loadCount);
|
||
loadCount = index - needIndex;
|
||
offsetIndex = getDialogIndex(storage[needIndex], indexKey) + 1;
|
||
} else {
|
||
offsetIndex = currentOffsetIndex;
|
||
}
|
||
}
|
||
|
||
return this.managers.acknowledged.dialogsStorage.getDialogs({
|
||
offsetIndex,
|
||
limit: loadCount,
|
||
filterId,
|
||
skipMigrated: true
|
||
});
|
||
}
|
||
}
|
||
|
||
class Some2 extends Some<Dialog> {
|
||
constructor(protected filterId: number) {
|
||
super();
|
||
|
||
this.needPlaceholderAtFirstTime = true;
|
||
|
||
this.listenerSetter.add(rootScope)('peer_typings', async({peerId, typings}) => {
|
||
const [dialog, isForum] = await Promise.all([
|
||
this.managers.appMessagesManager.getDialogOnly(peerId),
|
||
this.managers.appPeersManager.isForum(peerId)
|
||
]);
|
||
|
||
if(!dialog || isForum) return;
|
||
|
||
if(typings.length) {
|
||
this.setTyping(dialog);
|
||
} else {
|
||
this.unsetTyping(dialog);
|
||
}
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('user_update', async(userId) => {
|
||
if(!this.isActive) {
|
||
return;
|
||
}
|
||
|
||
const peerId = userId.toPeerId();
|
||
const dom = this.getDialogDom(peerId);
|
||
if(!dom) {
|
||
return;
|
||
}
|
||
|
||
const status = await this.managers.appUsersManager.getUserStatus(userId);
|
||
const online = status?._ === 'userStatusOnline';
|
||
this.setOnlineStatus(dom.avatarEl, online);
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('chat_update', async(chatId) => {
|
||
const peerId = chatId.toPeerId(true);
|
||
this.processDialogForCallStatus(peerId);
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('dialog_flush', ({dialog}) => {
|
||
if(!this.isActive || !dialog) {
|
||
return;
|
||
}
|
||
|
||
appDialogsManager.setLastMessageN({
|
||
dialog,
|
||
setUnread: true
|
||
});
|
||
this.validateDialogForFilter(dialog);
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('dialogs_multiupdate', (dialogs) => {
|
||
if(!this.isActive) {
|
||
return;
|
||
}
|
||
|
||
for(const [peerId, {dialog, topics}] of dialogs) {
|
||
if(dialog?._ !== 'dialog') {
|
||
continue;
|
||
}
|
||
|
||
this.updateDialog(dialog);
|
||
|
||
appDialogsManager.processContact?.(peerId.toPeerId());
|
||
|
||
this.validateDialogForFilter(dialog);
|
||
}
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('dialog_drop', (dialog) => {
|
||
if(!this.isActive || dialog._ !== 'dialog') {
|
||
return;
|
||
}
|
||
|
||
this.deleteDialogByKey(dialog.peerId);
|
||
appDialogsManager.processContact?.(dialog.peerId);
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('dialog_unread', ({dialog}) => {
|
||
if(!this.isActive || dialog?._ !== 'dialog') {
|
||
return;
|
||
}
|
||
|
||
appDialogsManager.setUnreadMessagesN({dialog, dialogElement: this.getDialogElement(this.getDialogKey(dialog))});
|
||
this.validateDialogForFilter(dialog);
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('dialog_notify_settings', (dialog) => {
|
||
if(!this.isActive || dialog._ === 'forumTopic') {
|
||
return;
|
||
}
|
||
|
||
this.validateDialogForFilter(dialog);
|
||
appDialogsManager.setUnreadMessagesN({dialog, dialogElement: this.getDialogElement(this.getDialogKey(dialog))}); // возможно это не нужно, но нужно менять is-muted
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('dialog_draft', ({dialog, drop, peerId}) => {
|
||
if(!this.isActive || dialog._ === 'forumTopic') {
|
||
return;
|
||
}
|
||
|
||
if(drop) {
|
||
this.deleteDialog(dialog);
|
||
} else {
|
||
this.updateDialog(dialog);
|
||
}
|
||
|
||
appDialogsManager.processContact?.(peerId);
|
||
});
|
||
|
||
this.listenerSetter.add(rootScope)('filter_update', async(filter) => {
|
||
if(this.isActive && filter.id === this.filterId && !REAL_FOLDERS.has(filter.id)) {
|
||
const dialogs = await this.managers.dialogsStorage.getCachedDialogs(true);
|
||
await this.validateListForFilter();
|
||
for(let i = 0, length = dialogs.length; i < length; ++i) {
|
||
const dialog = dialogs[i];
|
||
this.updateDialog(dialog);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
private get isActive() {
|
||
return appDialogsManager.xd === this;
|
||
}
|
||
|
||
public getRectFromForPlaceholder() {
|
||
return this.filterId === FOLDER_ID_ARCHIVE ? appDialogsManager.chatsContainer : appDialogsManager.folders.container;
|
||
}
|
||
|
||
public async loadDialogsInner(side: SliceSides) {
|
||
const {filterId, indexKey} = this;
|
||
let loadCount = windowSize.height / 72 * 1.25 | 0;
|
||
let offsetIndex = 0;
|
||
|
||
const doNotRenderChatList = appDialogsManager.doNotRenderChatList; // cache before awaits
|
||
|
||
const {index: currentOffsetIndex} = this.getOffsetIndex(side);
|
||
if(currentOffsetIndex) {
|
||
if(side === 'top') {
|
||
const storage = await this.managers.dialogsStorage.getFolderDialogs(filterId, true);
|
||
const index = storage.findIndex((dialog) => getDialogIndex(dialog, indexKey) <= currentOffsetIndex);
|
||
const needIndex = Math.max(0, index - loadCount);
|
||
loadCount = index - needIndex;
|
||
offsetIndex = getDialogIndex(storage[needIndex], indexKey) + 1;
|
||
} else {
|
||
offsetIndex = currentOffsetIndex;
|
||
}
|
||
}
|
||
|
||
const promise = this.managers.acknowledged.dialogsStorage.getDialogs({
|
||
offsetIndex,
|
||
limit: loadCount,
|
||
filterId,
|
||
skipMigrated: true
|
||
});
|
||
|
||
const a = await promise;
|
||
if(doNotRenderChatList) {
|
||
a.result = Promise.reject(makeError('MIDDLEWARE'));
|
||
}
|
||
|
||
return a;
|
||
}
|
||
|
||
public setOnlineStatus(element: HTMLElement, online: boolean) {
|
||
const className = 'is-online';
|
||
const hasClassName = element.classList.contains(className);
|
||
!hasClassName && online && element.classList.add(className);
|
||
SetTransition({
|
||
element: element,
|
||
className: 'is-visible',
|
||
forwards: online,
|
||
duration: 250,
|
||
onTransitionEnd: online ? undefined : () => {
|
||
element.classList.remove(className);
|
||
},
|
||
useRafs: online && !hasClassName ? 2 : 0
|
||
});
|
||
}
|
||
|
||
public generateScrollable(list: HTMLUListElement, filter: Parameters<AppDialogsManager['addFilter']>[0]) {
|
||
const filterId = filter.id;
|
||
const scrollable = new Scrollable(null, 'CL', 500);
|
||
scrollable.container.dataset.filterId = '' + filterId;
|
||
|
||
const indexKey = getDialogIndexKey(filter.localId);
|
||
const sortedDialogList = new SortedDialogList({
|
||
managers: rootScope.managers,
|
||
log: this.log,
|
||
list: list,
|
||
indexKey,
|
||
onListLengthChange: appDialogsManager.onListLengthChange
|
||
});
|
||
|
||
this.scrollable = scrollable;
|
||
this.sortedList = sortedDialogList;
|
||
this.setIndexKey(indexKey);
|
||
this.bindScrollable();
|
||
|
||
// list.classList.add('hide');
|
||
// scrollable.container.style.backgroundColor = '#' + (Math.random() * (16 ** 6 - 1) | 0).toString(16);
|
||
|
||
return scrollable;
|
||
}
|
||
|
||
public testDialogForFilter(dialog: Dialog) {
|
||
if(!REAL_FOLDERS.has(this.filterId) ? getDialogIndex(dialog, this.indexKey) === undefined : this.filterId !== dialog.folder_id) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
protected isDialogMustBeInViewport(dialog: Dialog) {
|
||
if(dialog.migratedTo !== undefined || !this.testDialogForFilter(dialog)) return false;
|
||
return super.isDialogMustBeInViewport(dialog);
|
||
}
|
||
|
||
/**
|
||
* Удалит неподходящие чаты из списка, но не добавит их(!)
|
||
*/
|
||
public async validateListForFilter() {
|
||
this.sortedList.getAll().forEach(async(element) => {
|
||
const dialog = await rootScope.managers.appMessagesManager.getDialogOnly(element.id);
|
||
if(!this.testDialogForFilter(dialog)) {
|
||
this.deleteDialog(dialog);
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Удалит неподходящий чат из списка, но не добавит его(!)
|
||
*/
|
||
public validateDialogForFilter(dialog: Dialog) {
|
||
if(!this.getDialogElement(dialog.peerId)) {
|
||
return;
|
||
}
|
||
|
||
if(!this.testDialogForFilter(dialog)) {
|
||
this.deleteDialog(dialog);
|
||
}
|
||
}
|
||
|
||
public setCallStatus(dom: DialogDom, visible: boolean) {
|
||
let {callIcon, listEl} = dom;
|
||
if(!callIcon && visible) {
|
||
const {canvas, startAnimation} = dom.callIcon = callIcon = groupCallActiveIcon(listEl.classList.contains('active'));
|
||
canvas.classList.add('dialog-group-call-icon');
|
||
listEl.append(canvas);
|
||
startAnimation();
|
||
}
|
||
|
||
if(!callIcon) {
|
||
return;
|
||
}
|
||
|
||
SetTransition({
|
||
element: dom.callIcon.canvas,
|
||
className: 'is-visible',
|
||
forwards: visible,
|
||
duration: BADGE_TRANSITION_TIME,
|
||
onTransitionEnd: visible ? undefined : () => {
|
||
dom.callIcon.canvas.remove();
|
||
dom.callIcon = undefined;
|
||
},
|
||
useRafs: visible ? 2 : 0
|
||
});
|
||
}
|
||
|
||
public async processDialogForCallStatus(peerId: PeerId, dom?: DialogDom) {
|
||
if(!IS_GROUP_CALL_SUPPORTED) {
|
||
return;
|
||
}
|
||
|
||
if(!dom) dom = this.getDialogDom(peerId);
|
||
if(!dom) return;
|
||
|
||
const chat = await rootScope.managers.appChatsManager.getChat(peerId.toChatId()) as Chat.chat | Chat.channel;
|
||
this.setCallStatus(dom, !!(chat.pFlags.call_active && chat.pFlags.call_not_empty));
|
||
}
|
||
|
||
public onChatsScroll(side: SliceSides = 'bottom') {
|
||
if(this.scrollable.loadedAll[side]) {
|
||
appDialogsManager.loadContacts?.();
|
||
}
|
||
|
||
this.log('onChatsScroll', side);
|
||
return super.onChatsScroll(side);
|
||
}
|
||
|
||
public toggleAvatarUnreadBadges(value: boolean, useRafs: number) {
|
||
if(!value) {
|
||
this.sortedList.getAll().forEach((sortedDialog) => {
|
||
const {dom, dialogElement} = sortedDialog;
|
||
if(!dom.unreadAvatarBadge) {
|
||
return;
|
||
}
|
||
|
||
dialogElement.toggleBadgeByKey('unreadAvatarBadge', false, false, false);
|
||
});
|
||
|
||
return;
|
||
}
|
||
|
||
const reuseClassNames = ['unread', 'mention'];
|
||
this.sortedList.getAll().forEach((sortedDialog) => {
|
||
const {dom, dialogElement} = sortedDialog;
|
||
const unreadContent = dom.unreadBadge?.textContent;
|
||
if(!unreadContent || dom.unreadBadge.classList.contains('backwards')) {
|
||
return;
|
||
}
|
||
|
||
const isUnreadAvatarBadgeMounted = !!dom.unreadAvatarBadge;
|
||
dialogElement.createUnreadAvatarBadge();
|
||
dialogElement.toggleBadgeByKey('unreadAvatarBadge', true, isUnreadAvatarBadgeMounted);
|
||
dom.unreadAvatarBadge.textContent = unreadContent;
|
||
const unreadAvatarBadgeClassList = dom.unreadAvatarBadge.classList;
|
||
const unreadBadgeClassList = dom.unreadBadge.classList;
|
||
reuseClassNames.forEach((className) => {
|
||
unreadAvatarBadgeClassList.toggle(className, unreadBadgeClassList.contains(className));
|
||
});
|
||
});
|
||
}
|
||
|
||
public getDialogKey(dialog: Dialog) {
|
||
return dialog.peerId;
|
||
}
|
||
|
||
public getDialogKeyFromElement(element: HTMLElement) {
|
||
return +element.dataset.peerId;
|
||
}
|
||
|
||
public getDialogFromElement(element: HTMLElement) {
|
||
return rootScope.managers.appMessagesManager.getDialogOnly(element.dataset.peerId.toPeerId());
|
||
}
|
||
}
|
||
|
||
// const testScroll = false;
|
||
// let testTopSlice = 1;
|
||
|
||
export class AppDialogsManager {
|
||
public chatsContainer = document.getElementById('chatlist-container') as HTMLDivElement;
|
||
|
||
private log = logger('DIALOGS', LogTypes.Log | LogTypes.Error | LogTypes.Warn | LogTypes.Debug);
|
||
|
||
private contextMenu: DialogsContextMenu;
|
||
|
||
public filterId: number;
|
||
public 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 showFiltersPromise: Promise<void>;
|
||
|
||
private lastActiveElements: Set<HTMLElement> = new Set();
|
||
|
||
public loadContacts: () => void;
|
||
public processContact: (peerId: PeerId) => void;
|
||
|
||
private initedListeners = false;
|
||
|
||
public onListLengthChange: () => Promise<void>;
|
||
private allChatsIntlElement: I18n.IntlElement;
|
||
|
||
private emptyDialogsPlaceholderSubtitle: I18n.IntlElement;
|
||
private updateContactsLengthPromise: Promise<number>;
|
||
|
||
private filtersNavigationItem: NavigationItem;
|
||
|
||
private managers: AppManagers;
|
||
private selectTab: ReturnType<typeof horizontalMenu>;
|
||
|
||
public doNotRenderChatList: boolean;
|
||
|
||
private stateMiddlewareHelper: MiddlewareHelper;
|
||
|
||
private forumsTabs: Map<PeerId, ForumTab>;
|
||
private forumsSlider: HTMLElement;
|
||
public forumTab: ForumTab;
|
||
private forumNavigationItem: NavigationItem;
|
||
|
||
public xd: Some2;
|
||
public xds: {[filterId: number]: Some2} = {};
|
||
|
||
public start() {
|
||
const managers = this.managers = getProxiedManagers();
|
||
|
||
this.contextMenu = new DialogsContextMenu(managers);
|
||
this.stateMiddlewareHelper = getMiddleware();
|
||
|
||
this.folders.menuScrollContainer = this.folders.menu.parentElement;
|
||
|
||
this.onListLengthChange = debounce(this._onListLengthChange, 100, false, true);
|
||
|
||
const bottomPart = document.createElement('div');
|
||
bottomPart.classList.add('connection-status-bottom');
|
||
bottomPart.append(this.folders.container);
|
||
|
||
this.forumsTabs = new Map();
|
||
this.forumsSlider = document.createElement('div');
|
||
this.forumsSlider.classList.add('topics-slider');
|
||
this.chatsContainer.parentElement.parentElement.append(this.forumsSlider);
|
||
// findUpClassName(this.chatsContainer, 'chatlist-container').append(this.forumsSlider);
|
||
|
||
// appSidebarLeft.onOpenTab = () => {
|
||
// return this.toggleForumTab();
|
||
// };
|
||
|
||
/* 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();
|
||
}
|
||
});
|
||
} */
|
||
|
||
if(IS_TOUCH_SUPPORTED) {
|
||
handleTabSwipe({
|
||
element: this.folders.container,
|
||
onSwipe: (xDiff) => {
|
||
const prevId = selectTab.prevId();
|
||
selectTab(xDiff > 0 ? prevId + 1 : prevId - 1);
|
||
}
|
||
});
|
||
}
|
||
|
||
this.allChatsIntlElement = new I18n.IntlElement({
|
||
key: 'FilterAllChatsShort'
|
||
});
|
||
|
||
rootScope.addEventListener('premium_toggle', async(isPremium) => {
|
||
if(isPremium) {
|
||
return;
|
||
}
|
||
|
||
const isFolderAvailable = await this.managers.filtersStorage.isFilterIdAvailable(this.filterId);
|
||
if(!isFolderAvailable) {
|
||
selectTab(whichChild(this.filtersRendered[FOLDER_ID_ALL].menu), false);
|
||
}
|
||
});
|
||
|
||
rootScope.addEventListener('state_cleared', () => {
|
||
const clearCurrent = REAL_FOLDERS.has(this.filterId);
|
||
// setTimeout(() =>
|
||
apiManagerProxy.getState().then(async(state) => {
|
||
this.xd.loadedDialogsAtLeastOnce = false;
|
||
this.showFiltersPromise = undefined;
|
||
|
||
/* const clearPromises: Promise<any>[] = [];
|
||
for(const name in this.managers.appStateManager.storagesResults) {
|
||
const results = this.managers.appStateManager.storagesResults[name as keyof AppStateManager['storages']];
|
||
const storage = this.managers.appStateManager.storages[name as keyof AppStateManager['storages']];
|
||
results.length = 0;
|
||
clearPromises.push(storage.clear());
|
||
} */
|
||
|
||
if(clearCurrent) {
|
||
this.xd.clear();
|
||
this.onTabChange();
|
||
}
|
||
|
||
this.onStateLoaded(state);
|
||
})// , 5000);
|
||
});
|
||
|
||
this.setFilterId(FOLDER_ID_ALL, FOLDER_ID_ALL);
|
||
this.addFilter({
|
||
id: FOLDER_ID_ALL,
|
||
title: '',
|
||
localId: FOLDER_ID_ALL
|
||
});
|
||
|
||
const foldersScrollable = new ScrollableX(this.folders.menuScrollContainer);
|
||
bottomPart.prepend(this.folders.menuScrollContainer);
|
||
const selectTab = this.selectTab = horizontalMenu(this.folders.menu, this.folders.container, async(id, tabContent) => {
|
||
/* if(id !== 0) {
|
||
id += 1;
|
||
} */
|
||
|
||
const _id = id;
|
||
id = +tabContent.dataset.filterId || FOLDER_ID_ALL;
|
||
|
||
const isFilterAvailable = this.filterId === -1 || REAL_FOLDERS.has(id) || await this.managers.filtersStorage.isFilterIdAvailable(id);
|
||
if(!isFilterAvailable) {
|
||
return false;
|
||
}
|
||
|
||
const wasFilterId = this.filterId;
|
||
if(!IS_MOBILE_SAFARI) {
|
||
if(_id) {
|
||
if(!this.filtersNavigationItem) {
|
||
this.filtersNavigationItem = {
|
||
type: 'filters',
|
||
onPop: () => {
|
||
selectTab(0);
|
||
this.filtersNavigationItem = undefined;
|
||
}
|
||
};
|
||
|
||
appNavigationController.spliceItems(1, 0, this.filtersNavigationItem);
|
||
}
|
||
} else if(this.filtersNavigationItem) {
|
||
appNavigationController.removeItem(this.filtersNavigationItem);
|
||
this.filtersNavigationItem = undefined;
|
||
}
|
||
}
|
||
|
||
if(wasFilterId === id) return;
|
||
|
||
this.xds[id].clear();
|
||
const promise = this.setFilterIdAndChangeTab(id).then(({cached, renderPromise}) => {
|
||
if(cached) {
|
||
return renderPromise;
|
||
}
|
||
});
|
||
|
||
if(wasFilterId !== -1) {
|
||
return promise;
|
||
}
|
||
}, () => {
|
||
for(const folderId in this.xds) {
|
||
if(+folderId !== this.filterId) {
|
||
this.xds[folderId].clear();
|
||
}
|
||
}
|
||
}, undefined, foldersScrollable);
|
||
|
||
apiManagerProxy.getState().then((state) => {
|
||
// * it should've had a better place :(
|
||
appMediaPlaybackController.setPlaybackParams(state.playbackParams);
|
||
appMediaPlaybackController.addEventListener('playbackParams', (params) => {
|
||
this.managers.appStateManager.pushToState('playbackParams', params);
|
||
});
|
||
|
||
return this.onStateLoaded(state);
|
||
})/* .then(() => {
|
||
const isLoadedMain = this.managers.appMessagesManager.dialogsStorage.isDialogsLoaded(0);
|
||
const isLoadedArchive = this.managers.appMessagesManager.dialogsStorage.isDialogsLoaded(1);
|
||
const wasLoaded = isLoadedMain || isLoadedArchive;
|
||
const a: Promise<any> = isLoadedMain ? Promise.resolve() : this.managers.appMessagesManager.getConversationsAll('', 0);
|
||
const b: Promise<any> = isLoadedArchive ? Promise.resolve() : this.managers.appMessagesManager.getConversationsAll('', 1);
|
||
a.finally(() => {
|
||
b.then(() => {
|
||
if(wasLoaded) {
|
||
(apiUpdatesManager.updatesState.syncLoading || Promise.resolve()).then(() => {
|
||
this.managers.appMessagesManager.refreshConversations();
|
||
});
|
||
}
|
||
});
|
||
});
|
||
}) */;
|
||
|
||
mediaSizes.addEventListener('resize', () => {
|
||
this.changeFiltersAllChatsKey();
|
||
});
|
||
|
||
new ConnectionStatusComponent(this.managers, this.chatsContainer);
|
||
this.chatsContainer.append(bottomPart);
|
||
|
||
setTimeout(() => {
|
||
lottieLoader.loadLottieWorkers();
|
||
}, 200);
|
||
|
||
PopupElement.MANAGERS = rootScope.managers = managers;
|
||
appDownloadManager.construct(managers);
|
||
appSidebarLeft.construct(managers);
|
||
appSidebarRight.construct(managers);
|
||
groupCallsController.construct(managers);
|
||
callsController.construct(managers);
|
||
appImManager.construct(managers);
|
||
|
||
// start
|
||
|
||
this.xd = this.xds[this.filterId];
|
||
|
||
// selectTab(0, false);
|
||
}
|
||
|
||
public get chatList() {
|
||
return this.xd.sortedList.list;
|
||
}
|
||
|
||
public setFilterId(filterId: number, localId: MyDialogFilter['localId']) {
|
||
this.filterId = filterId;
|
||
}
|
||
|
||
public async setFilterIdAndChangeTab(filterId: number) {
|
||
this.filterId = filterId;
|
||
return this.onTabChange();
|
||
}
|
||
|
||
private initListeners() {
|
||
rootScope.addEventListener('dialog_flush', ({dialog}) => {
|
||
if(!dialog) {
|
||
return;
|
||
}
|
||
|
||
this.setFiltersUnreadCount();
|
||
});
|
||
|
||
rootScope.addEventListener('folder_unread', async(folder) => {
|
||
if(folder.id < 0) {
|
||
const dialogElement = this.xd.getDialogElement(folder.id);
|
||
if(!dialogElement) {
|
||
return;
|
||
}
|
||
|
||
this.setUnreadMessagesN({
|
||
dialog: await this.managers.dialogsStorage.getDialogOnly(folder.id),
|
||
dialogElement
|
||
});
|
||
} else {
|
||
this.setFilterUnreadCount(folder.id);
|
||
}
|
||
});
|
||
|
||
rootScope.addEventListener('contacts_update', (userId) => {
|
||
this.processContact?.(userId.toPeerId());
|
||
});
|
||
|
||
appImManager.addEventListener('peer_changed', ({peerId, threadId, isForum}) => {
|
||
const options: Parameters<AppImManager['isSamePeer']>[0] = {peerId, threadId: isForum ? threadId : undefined};
|
||
// const perf = performance.now();
|
||
for(const element of this.lastActiveElements) {
|
||
const elementThreadId = +element.dataset.threadId || undefined;
|
||
const elementPeerId = element.dataset.peerId.toPeerId();
|
||
if(!appImManager.isSamePeer({peerId: elementPeerId, threadId: elementThreadId}, options)) {
|
||
this.setDialogActive(element, false);
|
||
}
|
||
}
|
||
|
||
const elements = Array.from(document.querySelectorAll(`[data-autonomous="0"] .chatlist-chat[data-peer-id="${peerId}"]`)) as HTMLElement[];
|
||
elements.forEach((element) => {
|
||
const elementThreadId = +element.dataset.threadId || undefined;
|
||
if(appImManager.isSamePeer({peerId, threadId: elementThreadId}, options)) {
|
||
this.setDialogActive(element, true);
|
||
}
|
||
});
|
||
// this.log('peer_changed total time:', performance.now() - perf);
|
||
});
|
||
|
||
rootScope.addEventListener('filter_update', async(filter) => {
|
||
if(REAL_FOLDERS.has(filter.id)) {
|
||
return;
|
||
}
|
||
|
||
if(!this.filtersRendered[filter.id]) {
|
||
this.addFilter(filter);
|
||
return;
|
||
}
|
||
|
||
const elements = this.filtersRendered[filter.id];
|
||
setInnerHTML(elements.title, wrapEmojiText(filter.title));
|
||
});
|
||
|
||
rootScope.addEventListener('filter_delete', (filter) => {
|
||
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();
|
||
elements.container.remove();
|
||
elements.menu.remove();
|
||
|
||
this.xds[filter.id].destroy();
|
||
delete this.xds[filter.id];
|
||
delete this.filtersRendered[filter.id];
|
||
|
||
this.onFiltersLengthChange();
|
||
|
||
if(this.filterId === filter.id) {
|
||
this.selectTab(0, false);
|
||
}
|
||
});
|
||
|
||
rootScope.addEventListener('filter_order', async(order) => {
|
||
order = order.slice();
|
||
indexOfAndSplice(order, FOLDER_ID_ARCHIVE);
|
||
|
||
const containerToAppend = this.folders.menu as HTMLElement;
|
||
const r = await Promise.all(order.map(async(filterId) => {
|
||
const [indexKey, filter] = await Promise.all([
|
||
this.managers.dialogsStorage.getDialogIndexKeyByFilterId(filterId),
|
||
this.managers.filtersStorage.getFilter(filterId)
|
||
]);
|
||
|
||
return {indexKey, filter};
|
||
}));
|
||
|
||
order.forEach((filterId, idx) => {
|
||
const {indexKey, filter} = r[idx];
|
||
const renderedFilter = this.filtersRendered[filterId];
|
||
|
||
this.xds[filterId].setIndexKey(indexKey);
|
||
|
||
positionElementByIndex(renderedFilter.menu, containerToAppend, filter.localId);
|
||
positionElementByIndex(renderedFilter.container, this.folders.container, filter.localId);
|
||
});
|
||
|
||
/* if(this.filterId) {
|
||
const tabIndex = order.indexOf(this.filterId) + 1;
|
||
selectTab.prevId = tabIndex;
|
||
} */
|
||
});
|
||
}
|
||
|
||
public setDialogActive(listEl: HTMLElement, active: boolean) {
|
||
const dom = (listEl as any).dialogDom as DialogDom;
|
||
listEl.classList.toggle('active', active);
|
||
listEl.classList.toggle('is-forum-open', this.forumTab?.peerId === listEl.dataset.peerId.toPeerId() && !listEl.dataset.threadId);
|
||
if(active) {
|
||
this.lastActiveElements.add(listEl);
|
||
} else {
|
||
this.lastActiveElements.delete(listEl);
|
||
}
|
||
|
||
if(dom?.callIcon) {
|
||
dom.callIcon.setActive(active);
|
||
}
|
||
}
|
||
|
||
private async onStateLoaded(state: State) {
|
||
this.stateMiddlewareHelper.clean();
|
||
const middleware = this.stateMiddlewareHelper.get();
|
||
const filtersArr = state.filtersArr;
|
||
const haveFilters = filtersArr.length > REAL_FOLDERS.size;
|
||
// const filter = filtersArr.find((filter) => filter.id !== FOLDER_ID_ARCHIVE);
|
||
|
||
const addFilters = (filters: MyDialogFilter[]) => {
|
||
for(const filter of filters) {
|
||
this.addFilter(filter);
|
||
}
|
||
};
|
||
|
||
let addFiltersPromise: Promise<any>;
|
||
if(haveFilters) {
|
||
addFilters(filtersArr);
|
||
} else {
|
||
addFiltersPromise = this.managers.filtersStorage.getDialogFilters().then(addFilters);
|
||
}
|
||
|
||
this.doNotRenderChatList = true;
|
||
const loadDialogsPromise = this.xd.onChatsScroll();
|
||
const m = middlewarePromise(middleware);
|
||
try {
|
||
await m(loadDialogsPromise);
|
||
} catch(err) {
|
||
|
||
}
|
||
|
||
// show the placeholder before the filters, and then will reset to the default tab again
|
||
if(!haveFilters) {
|
||
this.selectTab(0, false);
|
||
}
|
||
|
||
addFiltersPromise && await m(addFiltersPromise);
|
||
// this.folders.menu.children[0].classList.add('active');
|
||
|
||
this.doNotRenderChatList = undefined;
|
||
|
||
this.filterId = -1;
|
||
this.selectTab(0, false);
|
||
|
||
if(!this.initedListeners) {
|
||
this.initListeners();
|
||
this.initedListeners = true;
|
||
}
|
||
|
||
haveFilters && this.showFiltersPromise && await m(this.showFiltersPromise);
|
||
|
||
this.managers.appNotificationsManager.getNotifyPeerTypeSettings();
|
||
|
||
await (await m(loadDialogsPromise)).renderPromise.catch(noop);
|
||
this.managers.appMessagesManager.fillConversations();
|
||
}
|
||
|
||
/* private getOffset(side: 'top' | 'bottom'): {index: number, pos: number} {
|
||
if(!this.scroll.loadedAll[side]) {
|
||
const element = (side === 'top' ? this.chatList.firstElementChild : this.chatList.lastElementChild) as HTMLElement;
|
||
if(element) {
|
||
const peerId = element.dataset.peerId;
|
||
const dialog = this.managers.appMessagesManager.getDialogByPeerId(peerId);
|
||
return {index: dialog[0].index, pos: dialog[1]};
|
||
}
|
||
}
|
||
|
||
return {index: 0, pos: -1};
|
||
} */
|
||
|
||
public onTabChange = () => {
|
||
this.xd = this.xds[this.filterId];
|
||
this.xd.reset();
|
||
return this.xd.onChatsScroll();
|
||
};
|
||
|
||
private async setFilterUnreadCount(filterId: number) {
|
||
// if(filterId === FOLDER_ID_ALL) {
|
||
// return;
|
||
// }
|
||
|
||
const unreadSpan = this.filtersRendered[filterId]?.unread;
|
||
if(!unreadSpan) {
|
||
return;
|
||
}
|
||
|
||
const {unreadUnmutedCount, unreadCount} = await this.managers.dialogsStorage.getFolderUnreadCount(filterId);
|
||
|
||
unreadSpan.classList.toggle('badge-gray', !unreadUnmutedCount);
|
||
const count = filterId === FOLDER_ID_ALL ? unreadUnmutedCount : unreadCount;
|
||
unreadSpan.innerText = count ? '' + count : '';
|
||
}
|
||
|
||
private setFiltersUnreadCount() {
|
||
for(const filterId in this.filtersRendered) {
|
||
this.setFilterUnreadCount(+filterId);
|
||
}
|
||
}
|
||
|
||
public l(filter: Parameters<AppDialogsManager['addFilter']>[0]) {
|
||
const ul = this.createChatList();
|
||
const xd = this.xds[filter.id] = new Some2(filter.id);
|
||
const scrollable = xd.generateScrollable(ul, filter);
|
||
this.setListClickListener(ul, null, true);
|
||
|
||
return {ul, xd, scrollable};
|
||
}
|
||
|
||
private addFilter(filter: Pick<DialogFilter, 'title' | 'id' | 'localId'>) {
|
||
if(filter.id === FOLDER_ID_ARCHIVE) {
|
||
return;
|
||
}
|
||
|
||
const containerToAppend = this.folders.menu as HTMLElement;
|
||
const renderedFilter = this.filtersRendered[filter.id];
|
||
if(renderedFilter) {
|
||
positionElementByIndex(renderedFilter.menu, containerToAppend, filter.localId);
|
||
positionElementByIndex(renderedFilter.container, this.folders.container, filter.localId);
|
||
return;
|
||
}
|
||
|
||
const menuTab = document.createElement('div');
|
||
menuTab.classList.add('menu-horizontal-div-item');
|
||
const span = document.createElement('span');
|
||
const titleSpan = document.createElement('span');
|
||
titleSpan.classList.add('text-super');
|
||
if(filter.id === FOLDER_ID_ALL) titleSpan.append(this.allChatsIntlElement.element);
|
||
else setInnerHTML(titleSpan, wrapEmojiText(filter.title));
|
||
const unreadSpan = document.createElement('div');
|
||
unreadSpan.classList.add('badge', 'badge-20', 'badge-primary');
|
||
const i = document.createElement('i');
|
||
span.append(titleSpan, unreadSpan, i);
|
||
ripple(menuTab);
|
||
menuTab.append(span);
|
||
|
||
menuTab.dataset.filterId = '' + filter.id;
|
||
|
||
positionElementByIndex(menuTab, containerToAppend, filter.localId);
|
||
// containerToAppend.append(li);
|
||
|
||
const {ul, scrollable} = this.l(filter);
|
||
scrollable.container.classList.add('tabs-tab', 'chatlist-parts');
|
||
|
||
/* const parts = document.createElement('div');
|
||
parts.classList.add('chatlist-parts'); */
|
||
|
||
const top = document.createElement('div');
|
||
top.classList.add('chatlist-top');
|
||
|
||
const bottom = document.createElement('div');
|
||
bottom.classList.add('chatlist-bottom');
|
||
|
||
top.append(ul);
|
||
scrollable.container.append(top, bottom);
|
||
/* parts.append(top, bottom);
|
||
scrollable.container.append(parts); */
|
||
|
||
const div = scrollable.container;
|
||
// this.folders.container.append(div);
|
||
positionElementByIndex(scrollable.container, this.folders.container, filter.localId);
|
||
|
||
this.filtersRendered[filter.id] = {
|
||
menu: menuTab,
|
||
container: div,
|
||
unread: unreadSpan,
|
||
title: titleSpan
|
||
};
|
||
|
||
this.onFiltersLengthChange();
|
||
}
|
||
|
||
private changeFiltersAllChatsKey() {
|
||
const scrollable = this.folders.menuScrollContainer.firstElementChild;
|
||
const key: LangPackKey = scrollable.scrollWidth > scrollable.clientWidth ? 'FilterAllChatsShort' : 'FilterAllChats';
|
||
this.allChatsIntlElement.compareAndUpdate({key});
|
||
}
|
||
|
||
private onFiltersLengthChange() {
|
||
let promise = this.showFiltersPromise;
|
||
return promise ??= this.showFiltersPromise = pause(0).then(() => {
|
||
if(this.showFiltersPromise !== promise) {
|
||
return;
|
||
}
|
||
|
||
const length = Object.keys(this.filtersRendered).length;
|
||
const show = length > 1;
|
||
const wasShowing = !this.folders.menuScrollContainer.classList.contains('hide');
|
||
|
||
if(show !== wasShowing) {
|
||
this.folders.menuScrollContainer.classList.toggle('hide', !show);
|
||
if(show && !wasShowing) {
|
||
this.setFiltersUnreadCount();
|
||
}
|
||
|
||
this.chatsContainer.classList.toggle('has-filters', show);
|
||
}
|
||
|
||
this.changeFiltersAllChatsKey();
|
||
|
||
this.showFiltersPromise = undefined;
|
||
});
|
||
}
|
||
|
||
private generateEmptyPlaceholder(options: {
|
||
title: LangPackKey,
|
||
subtitle?: LangPackKey,
|
||
subtitleArgs?: FormatterArguments,
|
||
classNameType: string
|
||
}) {
|
||
const BASE_CLASS = 'empty-placeholder';
|
||
const container = document.createElement('div');
|
||
container.classList.add(BASE_CLASS, BASE_CLASS + '-' + options.classNameType);
|
||
|
||
const header = document.createElement('div');
|
||
header.classList.add(BASE_CLASS + '-header');
|
||
_i18n(header, options.title);
|
||
|
||
const subtitle = document.createElement('div');
|
||
subtitle.classList.add(BASE_CLASS + '-subtitle');
|
||
if(options.subtitle) {
|
||
_i18n(subtitle, options.subtitle, options.subtitleArgs);
|
||
}
|
||
|
||
container.append(header, subtitle);
|
||
|
||
return {container, header, subtitle};
|
||
}
|
||
|
||
private checkIfPlaceholderNeeded() {
|
||
if(this.filterId === FOLDER_ID_ARCHIVE) {
|
||
return;
|
||
}
|
||
|
||
const chatList = this.chatList;
|
||
const part = chatList.parentElement as HTMLElement;
|
||
let placeholderContainer = (Array.from(part.children) as HTMLElement[]).find((el) => el.matches('.empty-placeholder'));
|
||
const needPlaceholder = this.xd.scrollable.loadedAll.bottom && !chatList.childElementCount/* || true */;
|
||
// chatList.style.display = 'none';
|
||
|
||
if(needPlaceholder && placeholderContainer) {
|
||
return;
|
||
} else if(!needPlaceholder) {
|
||
if(placeholderContainer) {
|
||
part.classList.remove('with-placeholder');
|
||
placeholderContainer.remove();
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
let placeholder: ReturnType<AppDialogsManager['generateEmptyPlaceholder']>, type: 'dialogs' | 'folder';
|
||
if(!this.filterId) {
|
||
placeholder = this.generateEmptyPlaceholder({
|
||
title: 'ChatList.Main.EmptyPlaceholder.Title',
|
||
classNameType: type = 'dialogs'
|
||
});
|
||
|
||
placeholderContainer = placeholder.container;
|
||
|
||
const img = document.createElement('img');
|
||
img.classList.add('empty-placeholder-dialogs-icon');
|
||
|
||
this.emptyDialogsPlaceholderSubtitle = new I18n.IntlElement({
|
||
element: placeholder.subtitle
|
||
});
|
||
|
||
Promise.all([
|
||
this.updateContactsLength(false),
|
||
renderImageFromUrlPromise(img, 'assets/img/EmptyChats.svg'),
|
||
fastRafPromise()
|
||
]).then(([usersLength]) => {
|
||
placeholderContainer.classList.add('visible');
|
||
part.classList.toggle('has-contacts', !!usersLength);
|
||
});
|
||
|
||
placeholderContainer.prepend(img);
|
||
} else {
|
||
placeholder = this.generateEmptyPlaceholder({
|
||
title: 'FilterNoChatsToDisplay',
|
||
subtitle: 'FilterNoChatsToDisplayInfo',
|
||
classNameType: type = 'folder'
|
||
});
|
||
|
||
placeholderContainer = placeholder.container;
|
||
|
||
const div = document.createElement('div');
|
||
const emoji = '📂';
|
||
const size = 128;
|
||
wrapStickerEmoji({
|
||
div,
|
||
emoji: emoji,
|
||
width: size,
|
||
height: size
|
||
});
|
||
|
||
placeholderContainer.prepend(div);
|
||
|
||
const button = Button('btn-primary btn-color-primary btn-control tgico', {
|
||
text: 'FilterHeaderEdit',
|
||
icon: 'settings'
|
||
});
|
||
|
||
attachClickEvent(button, async() => {
|
||
appSidebarLeft.createTab(AppEditFolderTab).open(await this.managers.filtersStorage.getFilter(this.filterId));
|
||
});
|
||
|
||
placeholderContainer.append(button);
|
||
}
|
||
|
||
part.append(placeholderContainer);
|
||
part.classList.add('with-placeholder');
|
||
part.dataset.placeholderType = type;
|
||
}
|
||
|
||
private updateContactsLength(updatePartClassName: boolean) {
|
||
return this.updateContactsLengthPromise ??= this.managers.appUsersManager.getContacts().then((users) => {
|
||
const subtitle = this.emptyDialogsPlaceholderSubtitle;
|
||
if(subtitle) {
|
||
let key: LangPackKey, args: FormatterArguments;
|
||
|
||
if(users.length/* && false */) {
|
||
key = 'ChatList.Main.EmptyPlaceholder.Subtitle';
|
||
args = [i18n('Contacts.Count', [users.length])];
|
||
} else {
|
||
key = 'ChatList.Main.EmptyPlaceholder.SubtitleNoContacts';
|
||
args = [];
|
||
}
|
||
|
||
subtitle.compareAndUpdate({
|
||
key,
|
||
args
|
||
});
|
||
}
|
||
|
||
if(updatePartClassName) {
|
||
const chatList = this.chatList;
|
||
const part = chatList.parentElement as HTMLElement;
|
||
part.classList.toggle('has-contacts', !!users.length);
|
||
}
|
||
|
||
this.updateContactsLengthPromise = undefined;
|
||
|
||
return users.length;
|
||
});
|
||
}
|
||
|
||
private removeContactsPlaceholder() {
|
||
const chatList = this.chatList;
|
||
const parts = chatList.parentElement.parentElement;
|
||
const bottom = chatList.parentElement.nextElementSibling as HTMLElement;
|
||
parts.classList.remove('with-contacts');
|
||
bottom.replaceChildren();
|
||
this.loadContacts = undefined;
|
||
this.processContact = undefined;
|
||
}
|
||
|
||
private _onListLengthChange = () => {
|
||
if(!this.xd.loadedDialogsAtLeastOnce) {
|
||
return;
|
||
}
|
||
|
||
this.checkIfPlaceholderNeeded();
|
||
|
||
if(this.filterId !== FOLDER_ID_ALL) return;
|
||
|
||
const chatList = this.chatList;
|
||
const count = chatList.childElementCount;
|
||
|
||
const parts = chatList.parentElement.parentElement;
|
||
const bottom = chatList.parentElement.nextElementSibling as HTMLElement;
|
||
const hasContacts = !!bottom.childElementCount;
|
||
if(count >= 10) {
|
||
if(hasContacts) {
|
||
this.removeContactsPlaceholder();
|
||
}
|
||
|
||
return;
|
||
} else if(hasContacts) return;
|
||
|
||
parts.classList.add('with-contacts');
|
||
|
||
const section = new SettingSection({
|
||
name: 'Contacts',
|
||
noDelimiter: true,
|
||
fakeGradientDelimiter: true
|
||
});
|
||
|
||
section.container.classList.add('hide');
|
||
|
||
this.managers.appUsersManager.getContactsPeerIds(undefined, undefined, 'online').then((contacts) => {
|
||
let ready = false;
|
||
const onListLengthChange = () => {
|
||
if(ready) {
|
||
section.container.classList.toggle('hide', !sortedUserList.list.childElementCount);
|
||
}
|
||
|
||
this.updateContactsLength(true);
|
||
};
|
||
|
||
const sortedUserList = new SortedUserList({
|
||
avatarSize: 'abitbigger',
|
||
createChatListOptions: {
|
||
dialogSize: 48,
|
||
new: true
|
||
},
|
||
autonomous: false,
|
||
onListLengthChange,
|
||
managers: this.managers
|
||
});
|
||
|
||
this.loadContacts = () => {
|
||
const pageCount = windowSize.height / 60 | 0;
|
||
const promise = filterAsync(contacts.splice(0, pageCount), this.verifyPeerIdForContacts);
|
||
|
||
promise.then((arr) => {
|
||
arr.forEach((peerId) => {
|
||
sortedUserList.add(peerId);
|
||
});
|
||
});
|
||
|
||
if(!contacts.length) {
|
||
this.loadContacts = undefined;
|
||
}
|
||
};
|
||
|
||
this.loadContacts();
|
||
|
||
this.processContact = async(peerId) => {
|
||
if(peerId.isAnyChat()) {
|
||
return;
|
||
}
|
||
|
||
const good = await this.verifyPeerIdForContacts(peerId);
|
||
const added = sortedUserList.has(peerId);
|
||
if(!added && good) sortedUserList.add(peerId);
|
||
else if(added && !good) sortedUserList.delete(peerId);
|
||
};
|
||
|
||
const list = sortedUserList.list;
|
||
list.classList.add('chatlist-new');
|
||
this.setListClickListener(list);
|
||
section.content.append(list);
|
||
|
||
ready = true;
|
||
onListLengthChange();
|
||
});
|
||
|
||
bottom.append(section.container);
|
||
};
|
||
|
||
private verifyPeerIdForContacts = async(peerId: PeerId) => {
|
||
const [isContact, dialog] = await Promise.all([
|
||
this.managers.appPeersManager.isContact(peerId),
|
||
this.managers.appMessagesManager.getDialogOnly(peerId)
|
||
]);
|
||
|
||
return isContact && !dialog;
|
||
};
|
||
|
||
public async toggleForumTab(newTab?: ForumTab, hideTab = this.forumTab) {
|
||
if(!hideTab && !newTab) {
|
||
return;
|
||
}
|
||
|
||
if(hideTab) {
|
||
const dialogElement = this.xd.getDialogElement(hideTab.peerId);
|
||
if(dialogElement) {
|
||
dialogElement.dom.listEl.classList.remove('is-forum-open');
|
||
}
|
||
}
|
||
|
||
if(hideTab === newTab) {
|
||
newTab = undefined;
|
||
}
|
||
|
||
hideTab?.toggle(false);
|
||
const promise = newTab?.toggle(true);
|
||
if(hideTab === this.forumTab) {
|
||
this.forumTab = newTab;
|
||
}
|
||
|
||
if(newTab) {
|
||
const dialogElement = this.xd.getDialogElement(newTab.peerId);
|
||
if(dialogElement) {
|
||
dialogElement.dom.listEl.classList.add('is-forum-open');
|
||
}
|
||
}
|
||
|
||
if(promise) {
|
||
await promise;
|
||
}
|
||
|
||
if(newTab && !this.forumNavigationItem) {
|
||
this.forumNavigationItem = {
|
||
type: 'forum',
|
||
onPop: () => {
|
||
this.forumNavigationItem = undefined;
|
||
this.toggleForumTab();
|
||
}
|
||
};
|
||
|
||
appNavigationController.pushItem(this.forumNavigationItem);
|
||
} else if(!newTab && this.forumNavigationItem) {
|
||
appNavigationController.removeItem(this.forumNavigationItem);
|
||
this.forumNavigationItem = undefined;
|
||
}
|
||
|
||
const forwards = !!newTab;
|
||
const useRafs = promise ? 2 : undefined;
|
||
this.xd.toggleAvatarUnreadBadges(forwards, useRafs);
|
||
|
||
const deferred = deferredPromise<void>();
|
||
const duration = 300;
|
||
SetTransition({
|
||
element: this.forumsSlider.parentElement,
|
||
className: 'is-forum-visible',
|
||
duration,
|
||
forwards,
|
||
useRafs,
|
||
onTransitionEnd: () => {
|
||
deferred.resolve();
|
||
}
|
||
});
|
||
|
||
dispatchHeavyAnimationEvent(deferred, duration).then(() => deferred.resolve());
|
||
}
|
||
|
||
public toggleForumTabByPeerId(peerId: PeerId, show?: boolean) {
|
||
const {managers} = this;
|
||
const history = appSidebarLeft.getHistory();
|
||
const lastTab = history[history.length - 1];
|
||
let forumTab: ForumTab;
|
||
if(lastTab/* && !(lastTab instanceof AppArchivedTab) */) {
|
||
if(lastTab instanceof ForumTab && lastTab.peerId === peerId && show) {
|
||
shake(lastTab.container);
|
||
return;
|
||
}
|
||
|
||
forumTab = appSidebarLeft.createTab(ForumTab);
|
||
forumTab.open({peerId, managers});
|
||
return;
|
||
}
|
||
|
||
forumTab = this.forumsTabs.get(peerId);
|
||
const isSameTab = this.forumTab && this.forumTab === forumTab;
|
||
show ??= !isSameTab;
|
||
if(show === isSameTab) {
|
||
if(show) {
|
||
shake(forumTab.container);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
if(show && !forumTab) {
|
||
forumTab = new ForumTab(undefined);
|
||
forumTab.init({peerId, managers});
|
||
|
||
this.forumsTabs.set(peerId, forumTab);
|
||
this.forumsSlider.append(forumTab.container);
|
||
|
||
forumTab.managers = this.managers;
|
||
forumTab.eventListener.addEventListener('destroy', () => {
|
||
this.forumsTabs.delete(peerId);
|
||
});
|
||
}
|
||
|
||
return this.toggleForumTab(forumTab);
|
||
}
|
||
|
||
public setListClickListener(
|
||
list: HTMLUListElement,
|
||
onFound?: () => void,
|
||
withContext = false,
|
||
autonomous = false,
|
||
openInner = false
|
||
) {
|
||
let lastActiveListElement: HTMLElement;
|
||
|
||
const setPeerFunc = (openInner ? appImManager.setInnerPeer : appImManager.setPeer).bind(appImManager);
|
||
|
||
list.dataset.autonomous = '' + +autonomous;
|
||
list.addEventListener('mousedown', (e) => {
|
||
if(e.button !== 0) return;
|
||
|
||
this.log('dialogs click list');
|
||
const target = e.target as HTMLElement;
|
||
const elem = findUpTag(target, DIALOG_LIST_ELEMENT_TAG);
|
||
|
||
if(!elem) {
|
||
return;
|
||
}
|
||
|
||
const peerId = elem.dataset.peerId.toPeerId();
|
||
const lastMsgId = +elem.dataset.mid || undefined;
|
||
const threadId = +elem.dataset.threadId || undefined;
|
||
|
||
onFound?.();
|
||
|
||
const isForum = !!elem.querySelector('.is-forum');
|
||
if(isForum && !e.shiftKey && !lastMsgId) {
|
||
this.toggleForumTabByPeerId(peerId);
|
||
return;
|
||
}
|
||
|
||
if(e.ctrlKey || e.metaKey) {
|
||
window.open((elem as HTMLAnchorElement).href || ('#' + peerId), '_blank');
|
||
cancelEvent(e);
|
||
return;
|
||
}
|
||
|
||
if(autonomous) {
|
||
const sameElement = lastActiveListElement === elem;
|
||
if(lastActiveListElement && !sameElement) {
|
||
lastActiveListElement.classList.remove('active');
|
||
}
|
||
|
||
if(elem) {
|
||
elem.classList.add('active');
|
||
lastActiveListElement = elem;
|
||
this.lastActiveElements.add(elem);
|
||
}
|
||
}
|
||
|
||
if(
|
||
(!threadId || lastMsgId) &&
|
||
this.xd.sortedList.list === list &&
|
||
this.xd !== this.xds[FOLDER_ID_ARCHIVE]
|
||
) {
|
||
this.toggleForumTab();
|
||
}
|
||
|
||
setPeerFunc({
|
||
peerId,
|
||
lastMsgId,
|
||
threadId: threadId
|
||
});
|
||
}, {capture: true});
|
||
|
||
// cancel link click
|
||
// ! do not change it to attachClickEvent
|
||
list.addEventListener('click', (e) => {
|
||
if(e.button === 0) {
|
||
cancelEvent(e);
|
||
}
|
||
}, {capture: true});
|
||
|
||
if(withContext) {
|
||
attachContextMenuListener(list, this.contextMenu.onContextMenu);
|
||
}
|
||
}
|
||
|
||
public createChatList(options: {
|
||
// avatarSize?: number,
|
||
// handheldsSize?: number,
|
||
// size?: number,
|
||
new?: boolean,
|
||
dialogSize?: number,
|
||
ignoreClick?: boolean
|
||
} = {}) {
|
||
const list = document.createElement('ul');
|
||
list.classList.add('chatlist'/* ,
|
||
'chatlist-avatar-' + (options.avatarSize || 54) *//* , 'chatlist-' + (options.size || 72) */);
|
||
|
||
if(options.new) {
|
||
list.classList.add('chatlist-new');
|
||
}
|
||
|
||
if(options.dialogSize) {
|
||
list.classList.add('chatlist-' + options.dialogSize);
|
||
}
|
||
|
||
// if(options.ignoreClick) {
|
||
// list.classList.add('disable-hover');
|
||
// }
|
||
|
||
/* if(options.handheldsSize) {
|
||
list.classList.add('chatlist-handhelds-' + options.handheldsSize);
|
||
} */
|
||
|
||
return list;
|
||
}
|
||
|
||
public setLastMessageN(options: Parameters<AppDialogsManager['setLastMessage']>[0]) {
|
||
const promise = this.setLastMessage(options);
|
||
return promise.catch((err: ApiError) => {
|
||
if(err?.type !== 'MIDDLEWARE') {
|
||
this.log.error('set last message error', err);
|
||
}
|
||
});
|
||
}
|
||
|
||
private async setLastMessage({
|
||
dialog,
|
||
lastMessage,
|
||
dialogElement,
|
||
highlightWord,
|
||
isBatch = false,
|
||
setUnread = false
|
||
}: {
|
||
dialog: Dialog | ForumTopic,
|
||
lastMessage?: Message.message | Message.messageService,
|
||
dialogElement?: DialogElement,
|
||
highlightWord?: string,
|
||
isBatch?: boolean,
|
||
setUnread?: boolean
|
||
}) {
|
||
if(!dialogElement) {
|
||
dialogElement = this.xd.getDialogElement(dialog.peerId);
|
||
|
||
if(!dialogElement) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
const {dom} = dialogElement;
|
||
const {peerId} = dialog;
|
||
|
||
const {deferred: promise, middleware} = setPromiseMiddleware(dom, 'setLastMessagePromise');
|
||
|
||
let draftMessage: MyDraftMessage;
|
||
if(!lastMessage) {
|
||
if(
|
||
dialog.draft?._ === 'draftMessage' && (
|
||
!peerId.isAnyChat() ||
|
||
dialog._ === 'forumTopic' ||
|
||
!(await this.managers.appPeersManager.isForum(peerId))
|
||
)
|
||
) {
|
||
draftMessage = dialog.draft;
|
||
}
|
||
|
||
lastMessage = (dialog as Dialog).topMessage;
|
||
if(lastMessage?.mid !== dialog.top_message) {
|
||
const promise = this.managers.appMessagesManager.getMessageByPeer(peerId, dialog.top_message);
|
||
lastMessage = await middleware(promise);
|
||
}
|
||
}
|
||
|
||
// * do not uncomment next line - unsetTyping right after this call will interrupt setting unread badges
|
||
// if(setUnread) {
|
||
this.setUnreadMessagesN({dialog, dialogElement, isBatch, setLastMessagePromise: promise});
|
||
// }
|
||
|
||
if(!lastMessage/* || (lastMessage._ === 'messageService' && !lastMessage.rReply) */) {
|
||
dom.lastMessageSpan.replaceChildren();
|
||
dom.lastTimeSpan.replaceChildren();
|
||
delete dom.listEl.dataset.mid;
|
||
|
||
promise.resolve();
|
||
return;
|
||
}
|
||
|
||
const isRestricted = lastMessage && isMessageRestricted(lastMessage as Message.message);
|
||
|
||
/* if(!dom.lastMessageSpan.classList.contains('user-typing')) */ {
|
||
let mediaContainer: HTMLElement;
|
||
const willPrepend: (Promise<any> | HTMLElement)[] = [];
|
||
if(lastMessage && !draftMessage && !isRestricted) {
|
||
const media: MyDocument | MyPhoto = getMediaFromMessage(lastMessage);
|
||
const videoTypes: Set<MyDocument['type']> = new Set(['video', 'gif', 'round']);
|
||
if(media && (media._ === 'photo' || videoTypes.has(media.type))) {
|
||
const size = choosePhotoSize(media, 20, 20);
|
||
|
||
if(size._ !== 'photoSizeEmpty') {
|
||
mediaContainer = document.createElement('div');
|
||
mediaContainer.classList.add('dialog-subtitle-media');
|
||
|
||
if((media as MyDocument).type === 'round') {
|
||
mediaContainer.classList.add('is-round');
|
||
}
|
||
|
||
willPrepend.push(wrapPhoto({
|
||
photo: media,
|
||
message: lastMessage,
|
||
container: mediaContainer,
|
||
withoutPreloader: true,
|
||
size
|
||
}).then(() => mediaContainer));
|
||
|
||
if(videoTypes.has((media as MyDocument).type)) {
|
||
const playIcon = document.createElement('span');
|
||
playIcon.classList.add('tgico-play');
|
||
|
||
mediaContainer.append(playIcon);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* if(lastMessage.from_id === auth.id) { // You: */
|
||
if(draftMessage) {
|
||
const span = document.createElement('span');
|
||
span.classList.add('danger');
|
||
span.append(i18n('Draft'), ': ');
|
||
willPrepend.unshift(span);
|
||
} else if(peerId.isAnyChat() && peerId !== lastMessage.fromId && !(lastMessage as Message.messageService).action) {
|
||
const span = document.createElement('span');
|
||
span.classList.add('primary-text');
|
||
|
||
if(lastMessage.fromId === rootScope.myId) {
|
||
span.append(i18n('FromYou'));
|
||
willPrepend.unshift(span);
|
||
} else {
|
||
// str = sender.first_name || sender.last_name || sender.username;
|
||
const p = middleware(wrapPeerTitle({
|
||
peerId: lastMessage.fromId,
|
||
onlyFirstName: true
|
||
})).then((element) => {
|
||
span.prepend(element);
|
||
return span;
|
||
}, noop);
|
||
|
||
willPrepend.unshift(p);
|
||
}
|
||
|
||
span.append(': ');
|
||
// console.log(sender, senderBold.innerText);
|
||
}
|
||
|
||
const withoutMediaType = !!mediaContainer && !!(lastMessage as Message.message)?.message;
|
||
|
||
let fragment: DocumentFragment;
|
||
if(highlightWord && (lastMessage as Message.message).message) {
|
||
fragment = await middleware(wrapMessageForReply({message: lastMessage, highlightWord, withoutMediaType}));
|
||
} else if(draftMessage) {
|
||
fragment = await middleware(wrapMessageForReply({message: draftMessage}));
|
||
} else if(lastMessage) {
|
||
fragment = await middleware(wrapMessageForReply({message: lastMessage, withoutMediaType}));
|
||
} else { // rare case
|
||
fragment = document.createDocumentFragment();
|
||
}
|
||
|
||
if(willPrepend.length) {
|
||
const elements = await middleware(Promise.all(willPrepend));
|
||
fragment.prepend(...elements);
|
||
}
|
||
|
||
replaceContent(dom.lastMessageSpan, fragment);
|
||
}
|
||
|
||
if(lastMessage || draftMessage/* && lastMessage._ !== 'draftMessage' */) {
|
||
const date = draftMessage ? Math.max(draftMessage.date, lastMessage.date || 0) : lastMessage.date;
|
||
replaceContent(dom.lastTimeSpan, formatDateAccordingToTodayNew(new Date(date * 1000)));
|
||
} else dom.lastTimeSpan.textContent = '';
|
||
|
||
if(setUnread !== null && !setUnread) { // means search
|
||
dom.listEl.dataset.mid = '' + lastMessage.mid;
|
||
|
||
const replyTo = lastMessage.reply_to;
|
||
if(replyTo?.pFlags?.forum_topic) {
|
||
dom.listEl.dataset.threadId = '' + getMessageThreadId(lastMessage);
|
||
}
|
||
}
|
||
|
||
promise.resolve();
|
||
}
|
||
|
||
public setUnreadMessagesN(options: Parameters<AppDialogsManager['setUnreadMessages']>[0]) {
|
||
return this.setUnreadMessages(options).catch(() => {});
|
||
}
|
||
|
||
private async setUnreadMessages({
|
||
dialog,
|
||
dialogElement,
|
||
isBatch = false,
|
||
setLastMessagePromise
|
||
}: {
|
||
dialog: Dialog | ForumTopic,
|
||
dialogElement: DialogElement,
|
||
isBatch?: boolean,
|
||
setLastMessagePromise?: Promise<void>
|
||
}) {
|
||
const {dom} = dialogElement;
|
||
if(!dom) {
|
||
// this.log.error('setUnreadMessages no dom!', dialog);
|
||
return;
|
||
}
|
||
|
||
const isTopic = dialog._ === 'forumTopic';
|
||
|
||
const {deferred, middleware} = setPromiseMiddleware(dom, 'setUnreadMessagePromise');
|
||
|
||
const {peerId} = dialog;
|
||
const promises = Promise.all([
|
||
this.managers.appNotificationsManager.isPeerLocalMuted({peerId: peerId, respectType: true, threadId: isTopic ? dialog.id : undefined}),
|
||
dialog.draft?._ !== 'draftMessage' ? this.managers.appMessagesManager.getMessageByPeer(peerId, dialog.top_message) : undefined,
|
||
isTopic ? !!dialog.pFlags.pinned : this.managers.dialogsStorage.isDialogPinned(peerId, this.filterId),
|
||
this.managers.appMessagesManager.isDialogUnread(dialog),
|
||
peerId.isAnyChat() && !isTopic ? this.managers.acknowledged.dialogsStorage.getForumUnreadCount(peerId).then((result) => {
|
||
if(result.cached) {
|
||
return result.result;
|
||
} else {
|
||
result.result.then(() => {
|
||
this.setUnreadMessagesN({dialog, dialogElement});
|
||
});
|
||
|
||
return {count: 0, hasUnmuted: false};
|
||
}
|
||
}).catch(() => undefined as {count: number, hasUnmuted: boolean}) : undefined
|
||
]);
|
||
|
||
let [isMuted, lastMessage, isPinned, isDialogUnread, forumUnreadCount] = await middleware(promises);
|
||
const wasMuted = dom.listEl.classList.contains('is-muted');
|
||
|
||
const {count: unreadTopicsCount, hasUnmuted: hasUnmutedTopic} = forumUnreadCount || {};
|
||
|
||
let setStatusMessage: MyMessage;
|
||
if(lastMessage && lastMessage.pFlags.out && lastMessage.peerId !== rootScope.myId) {
|
||
setStatusMessage = lastMessage;
|
||
}
|
||
|
||
const unreadCount = unreadTopicsCount ?? dialog.unread_count;
|
||
if(unreadTopicsCount !== undefined) {
|
||
isDialogUnread = !!unreadCount;
|
||
}
|
||
|
||
if(isTopic && !isDialogUnread) {
|
||
isDialogUnread = !getServerMessageId(dialog.read_inbox_max_id);
|
||
}
|
||
|
||
const hasUnreadBadge = isPinned || isDialogUnread;
|
||
const hasUnreadAvatarBadge = this.xd !== this.xds[FOLDER_ID_ARCHIVE] && !isTopic && !!this.forumTab && this.xd.getDialogElement(peerId) === dialogElement && isDialogUnread;
|
||
// dom.messageEl.classList.toggle('has-badge', hasBadge);
|
||
|
||
// * have to await all promises before modifying something
|
||
|
||
if(setLastMessagePromise) {
|
||
try {
|
||
await middleware(setLastMessagePromise);
|
||
} catch(err) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
const transitionDuration = isBatch ? 0 : BADGE_TRANSITION_TIME;
|
||
|
||
dom.listEl.classList.toggle('no-unmuted-topic', !isMuted && hasUnmutedTopic !== undefined && !hasUnmutedTopic);
|
||
|
||
if(isMuted !== wasMuted) {
|
||
SetTransition({
|
||
element: dom.listEl,
|
||
className: 'is-muted',
|
||
forwards: isMuted,
|
||
duration: transitionDuration
|
||
});
|
||
}
|
||
|
||
setSendingStatus(dom.statusSpan, isTopic && dialog.pFlags.closed ? 'premium_lock' : setStatusMessage, true);
|
||
|
||
// if(isTopic) {
|
||
// dom.statusSpan.parentElement.classList.toggle('is-closed', !!dialog.pFlags.closed);
|
||
// }
|
||
|
||
const isUnreadBadgeMounted = !!dom.unreadBadge;
|
||
if(hasUnreadBadge) {
|
||
dialogElement.createUnreadBadge();
|
||
}
|
||
|
||
const isUnreadAvatarBadgeMounted = !!dom.unreadAvatarBadge;
|
||
if(hasUnreadAvatarBadge) {
|
||
dialogElement.createUnreadAvatarBadge();
|
||
}
|
||
|
||
const hasMentionsBadge = dialog.unread_mentions_count && (dialog.unread_mentions_count > 1 || dialog.unread_count > 1);
|
||
const isMentionsBadgeMounted = !!dom.mentionsBadge;
|
||
if(hasMentionsBadge) {
|
||
dialogElement.createMentionsBadge();
|
||
}
|
||
|
||
const a: [Parameters<DialogElement['toggleBadgeByKey']>[0], boolean, boolean][] = [
|
||
['unreadBadge', hasUnreadBadge, isUnreadBadgeMounted],
|
||
['unreadAvatarBadge', hasUnreadAvatarBadge, isUnreadAvatarBadgeMounted],
|
||
['mentionsBadge', hasMentionsBadge, isMentionsBadgeMounted]
|
||
];
|
||
|
||
a.forEach(([key, hasBadge, isBadgeMounted]) => {
|
||
const badge = dom[key];
|
||
if(!badge) {
|
||
return;
|
||
}
|
||
|
||
dialogElement.toggleBadgeByKey(key, hasBadge, isBadgeMounted, isBatch);
|
||
});
|
||
|
||
if(!hasUnreadBadge) {
|
||
deferred.resolve();
|
||
return;
|
||
}
|
||
|
||
if(isPinned) {
|
||
dom.unreadBadge.classList.add('tgico-chatspinned', 'tgico');
|
||
} else if(dom.unreadBadge) {
|
||
dom.unreadBadge.classList.remove('tgico-chatspinned', 'tgico');
|
||
}
|
||
|
||
let isUnread = true, isMention = false, unreadBadgeText: string;
|
||
if(dialog.unread_mentions_count && unreadCount === 1) {
|
||
unreadBadgeText = '@';
|
||
isMention = true;
|
||
// dom.unreadBadge.classList.add('tgico-mention', 'tgico');
|
||
} else if(isDialogUnread) {
|
||
// dom.unreadMessagesSpan.innerText = '' + (unreadCount ? formatNumber(unreadCount, 1) : ' ');
|
||
unreadBadgeText = '' + (unreadCount ? formatNumber(unreadCount, 1) : ' ');
|
||
} else {
|
||
unreadBadgeText = '';
|
||
isUnread = false;
|
||
}
|
||
|
||
if(isTopic) {
|
||
const notVisited = isDialogUnread && unreadBadgeText === ' ';
|
||
dom.unreadBadge.classList.toggle('not-visited', notVisited);
|
||
}
|
||
|
||
const b: Array<[HTMLElement, string]> = [
|
||
[dom.unreadBadge, unreadBadgeText],
|
||
[dom.unreadAvatarBadge, unreadBadgeText || undefined]
|
||
];
|
||
|
||
b.filter(Boolean).forEach(([badge, text]) => {
|
||
if(text !== undefined) {
|
||
badge.innerText = unreadBadgeText;
|
||
}
|
||
|
||
badge.classList.toggle('unread', isUnread);
|
||
badge.classList.toggle('mention', isMention);
|
||
});
|
||
|
||
deferred.resolve();
|
||
}
|
||
|
||
private async getDialog(dialog: Dialog | ForumTopic | PeerId, threadId?: number) {
|
||
if(typeof(dialog) !== 'object') {
|
||
let originalDialog: Dialog | ForumTopic;
|
||
if(threadId) {
|
||
originalDialog = await this.managers.dialogsStorage.getForumTopic(dialog, threadId);
|
||
if(!originalDialog) {
|
||
const peerId = dialog || NULL_PEER_ID;
|
||
return {
|
||
peerId,
|
||
pFlags: {}
|
||
} as any as ForumTopic;
|
||
}
|
||
} else {
|
||
originalDialog = await this.managers.appMessagesManager.getDialogOnly(dialog);
|
||
if(!originalDialog) {
|
||
const peerId = dialog || NULL_PEER_ID;
|
||
return {
|
||
peerId,
|
||
peer: await this.managers.appPeersManager.getOutputPeer(peerId),
|
||
pFlags: {}
|
||
} as any as Dialog;
|
||
}
|
||
}
|
||
|
||
return originalDialog;
|
||
}
|
||
|
||
return dialog as Dialog | ForumTopic;
|
||
}
|
||
|
||
public addListDialog(options: Parameters<AppDialogsManager['addDialogNew']>[0] & {isBatch?: boolean}) {
|
||
options.autonomous = false;
|
||
|
||
const ret = this.addDialogNew(options);
|
||
|
||
if(ret) {
|
||
const {peerId} = options;
|
||
const getDialogPromise = this.getDialog(peerId, options.threadId);
|
||
const promise = getDialogPromise.then((dialog) => {
|
||
const promises: Promise<any>[] = [];
|
||
const isUser = peerId.isUser();
|
||
if(!isUser && dialog._ === 'dialog') {
|
||
promises.push(this.xd.processDialogForCallStatus(peerId, ret.dom));
|
||
}
|
||
|
||
if(peerId !== rootScope.myId && isUser) {
|
||
promises.push(this.managers.appUsersManager.getUserStatus(peerId.toUserId()).then((status) => {
|
||
if(status?._ === 'userStatusOnline') {
|
||
this.xd.setOnlineStatus(ret.dom.avatarEl, true);
|
||
}
|
||
}));
|
||
}
|
||
|
||
promises.push(this.setLastMessageN({
|
||
dialog,
|
||
dialogElement: ret,
|
||
isBatch: options.isBatch,
|
||
setUnread: true
|
||
}));
|
||
|
||
return Promise.all(promises);
|
||
});
|
||
|
||
options.loadPromises?.push(promise);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* use for rendering search result
|
||
*/
|
||
public addDialogAndSetLastMessage(options: Omit<Parameters<AppDialogsManager['addDialogNew']>[0], 'dialog'> & {
|
||
message: MyMessage,
|
||
peerId: PeerId,
|
||
query?: string
|
||
}) {
|
||
const {peerId, message, query} = options;
|
||
const ret = this.addDialogNew({
|
||
...options,
|
||
...getMessageSenderPeerIdOrName(message),
|
||
peerId
|
||
});
|
||
|
||
this.setLastMessageN({
|
||
dialog: {_: 'dialog', peerId} as any,
|
||
lastMessage: message,
|
||
dialogElement: ret,
|
||
highlightWord: query
|
||
});
|
||
|
||
if(message.peerId !== peerId) {
|
||
ret.dom.listEl.dataset.peerId = '' + message.peerId;
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
public addDialogNew(options: DialogElementOptions & {container?: HTMLElement | Scrollable | false, append?: boolean}) {
|
||
const d = new DialogElement({
|
||
autonomous: !!options.container,
|
||
avatarSize: 'bigger',
|
||
...options
|
||
// avatarSize: !options.avatarSize || options.avatarSize >= 54 ? 'bigger' : 'abitbigger',
|
||
});
|
||
|
||
if(options.container) {
|
||
const method = !options.append ? 'append' : 'prepend';
|
||
options.container[method](d.container);
|
||
}
|
||
|
||
return d;
|
||
// return this.addDialog(options.peerId, options.container, options.rippleEnabled, options.onlyFirstName, options.meAsSaved, options.append, options.avatarSize, options.autonomous, options.lazyLoadQueue, options.loadPromises, options.fromName, options.noIcons);
|
||
}
|
||
}
|
||
|
||
const appDialogsManager = new AppDialogsManager();
|
||
MOUNT_CLASS_TO.appDialogsManager = appDialogsManager;
|
||
export default appDialogsManager;
|