fixes & fixes

This commit is contained in:
morethanwords 2021-09-17 20:50:29 +04:00
parent 99c14e01e8
commit cc257fc495
18 changed files with 664 additions and 449 deletions

View File

@ -149,7 +149,7 @@ export default class ChatBubbles {
private replyFollowHistory: number[] = [];
private isHeavyAnimationInProgress = false;
private scrollingToNewBubble: HTMLElement;
private scrollingToBubble: HTMLElement;
private isFirstLoad = true;
private needReflowScroll: boolean;
@ -225,7 +225,7 @@ export default class ChatBubbles {
this.setBubblePosition(bubble, message, false);
//this.log('history_update', this.bubbles[mid], mid, message);
if(this.scrollingToNewBubble) {
if(this.scrollingToBubble) {
this.scrollToBubbleEnd();
}
@ -1420,6 +1420,12 @@ export default class ChatBubbles {
return;
} */
if(!scrolledDown) {
if(this.scrollingToBubble && this.scrollingToBubble === this.getLastBubble()) {
scrolledDown = true;
}
}
const promise = this.performHistoryResult(mids, false, true);
if(scrolledDown) {
promise.then(() => {
@ -1437,6 +1443,13 @@ export default class ChatBubbles {
}
}
public getLastBubble() {
const lastDateGroup = this.getLastDateGroup();
if(lastDateGroup) {
return lastDateGroup.lastElementChild as HTMLElement;
}
}
public scrollToBubble(
element: HTMLElement,
position: ScrollLogicalPosition,
@ -1458,21 +1471,17 @@ export default class ChatBubbles {
return this.scrollable.scrollIntoViewNew(element, position, 4, undefined, forceDirection, forceDuration);
}
public scrollToBubbleEnd(bubble?: HTMLElement) {
if(!bubble) {
const lastDateGroup = this.getLastDateGroup();
if(lastDateGroup) {
bubble = lastDateGroup.lastElementChild as HTMLElement;
}
}
public scrollToBubbleEnd(bubble = this.getLastBubble()) {
/* if(DEBUG) {
this.log('scrollToNewLastBubble: will scroll into view:', bubble);
} */
if(bubble) {
this.scrollingToNewBubble = bubble;
this.scrollingToBubble = bubble;
const middleware = this.getMiddleware();
this.scrollToBubble(bubble, 'end').then(() => {
this.scrollingToNewBubble = null;
if(!middleware()) return;
this.scrollingToBubble = undefined;
});
}
}
@ -1517,7 +1526,7 @@ export default class ChatBubbles {
const date = new Date(message.date * 1000);
date.setHours(0, 0, 0);
const dateTimestamp = date.getTime();
if(!(dateTimestamp in this.dateMessages)) {
if(!this.dateMessages[dateTimestamp]) {
let dateElement: HTMLElement;
const today = new Date();
@ -1549,8 +1558,8 @@ export default class ChatBubbles {
}
}
const div = document.createElement('div');
div.className = 'bubble service is-date';
const bubble = document.createElement('div');
bubble.className = 'bubble service is-date';
const bubbleContent = document.createElement('div');
bubbleContent.classList.add('bubble-content');
const serviceMsg = document.createElement('div');
@ -1559,36 +1568,38 @@ export default class ChatBubbles {
serviceMsg.append(dateElement);
bubbleContent.append(serviceMsg);
div.append(bubbleContent);
bubble.append(bubbleContent);
////////this.log('need to render date message', dateTimestamp, str);
const container = document.createElement('div');
container.className = 'bubbles-date-group';
container.append(bubble);
const haveTimestamps = getObjectKeysAndSort(this.dateMessages, 'asc');
let i = 0;
for(; i < haveTimestamps.length; ++i) {
const t = haveTimestamps[i];
if(dateTimestamp < t) {
break;
}
}
this.dateMessages[dateTimestamp] = {
div,
div: bubble,
container,
firstTimestamp: date.getTime()
};
container.append(div);
const haveTimestamps = getObjectKeysAndSort(this.dateMessages, 'asc');
let i = 0, length = haveTimestamps.length, insertBefore: HTMLElement; // there can be 'first bubble' (e.g. bot description) so can't insert by index
for(; i < haveTimestamps.length; ++i) {
const t = haveTimestamps[i];
insertBefore = this.dateMessages[t].container;
if(dateTimestamp < t) {
break;
}
}
positionElementByIndex(container, this.chatInner, i);
if(i === length && insertBefore) {
insertBefore = insertBefore.nextElementSibling as HTMLElement;
}
/* if(reverse) {
this.chatInner.prepend(container);
} else {
if(!insertBefore) {
this.chatInner.append(container);
} */
} else {
this.chatInner.insertBefore(container, insertBefore);
}
if(this.stickyIntersector) {
this.stickyIntersector.observeStickyHeaderChanges(container);
@ -1676,6 +1687,8 @@ export default class ChatBubbles {
this.onAnimateLadder = undefined;
this.resolveLadderAnimation = undefined;
this.emptyPlaceholderMid = undefined;
this.scrollingToBubble = undefined;
////console.timeEnd('appImManager cleanup');
}

View File

@ -15,7 +15,7 @@ export default class AppArchivedTab extends SliderSuperTab {
this.container.id = 'chats-archived-container';
this.setTitle('ArchivedChats');
if(!appDialogsManager.chatLists[AppArchivedTab.filterId]) {
if(!appDialogsManager.sortedLists[AppArchivedTab.filterId]) {
const chatList = appDialogsManager.createChatList();
appDialogsManager.generateScrollable(chatList, AppArchivedTab.filterId).container.append(chatList);
appDialogsManager.setListClickListener(chatList, null, true);
@ -40,7 +40,7 @@ export default class AppArchivedTab extends SliderSuperTab {
// вообще, так делать нельзя, но нет времени чтобы переделать главный чатлист на слайд...
onOpenAfterTimeout() {
appDialogsManager.chatLists[this.wasFilterId].innerHTML = '';
appDialogsManager.sortedLists[this.wasFilterId].clear();
}
onClose() {
@ -49,7 +49,7 @@ export default class AppArchivedTab extends SliderSuperTab {
}
onCloseAfterTimeout() {
appDialogsManager.chatLists[AppArchivedTab.filterId].innerHTML = '';
appDialogsManager.sortedLists[AppArchivedTab.filterId].clear();
return super.onCloseAfterTimeout();
}
}

View File

@ -8,21 +8,19 @@ import type { LazyLoadQueueIntersector } from "./lazyLoadQueue";
import appDialogsManager, { DialogDom } from "../lib/appManagers/appDialogsManager";
import { getHeavyAnimationPromise } from "../hooks/useHeavyAnimationCheck";
import appUsersManager from "../lib/appManagers/appUsersManager";
import { insertInDescendSortedArray } from "../helpers/array";
import isInDOM from "../helpers/dom/isInDOM";
import positionElementByIndex from "../helpers/dom/positionElementByIndex";
import replaceContent from "../helpers/dom/replaceContent";
import { safeAssign } from "../helpers/object";
import { fastRaf } from "../helpers/schedulers";
import SortedList, { SortedElementBase } from "../helpers/sortedList";
type SortedUser = {
peerId: number,
status: number,
interface SortedUser extends SortedElementBase {
dom: DialogDom
};
export default class SortedUserList {
}
export default class SortedUserList extends SortedList<SortedUser> {
protected static SORT_INTERVAL = 30e3;
protected users: Map<number, SortedUser>;
protected sorted: Array<SortedUser>;
public list: HTMLUListElement;
protected lazyLoadQueue: LazyLoadQueueIntersector;
@ -35,17 +33,53 @@ export default class SortedUserList {
rippleEnabled: SortedUserList['rippleEnabled'],
new: boolean
}> = {}) {
super({
getIndex: (id) => appUsersManager.getUserStatusForSort(id),
onDelete: (element) => element.dom.listEl.remove(),
onUpdate: (element) => {
const status = appUsersManager.getUserStatusString(element.id);
replaceContent(element.dom.lastMessageSpan, status);
},
onSort: (element, idx) => positionElementByIndex(element.dom.listEl, this.list, idx),
onElementCreate: (base) => {
const {dom} = appDialogsManager.addDialogNew({
dialog: base.id,
container: false,
drawStatus: false,
avatarSize: this.avatarSize,
autonomous: true,
meAsSaved: false,
rippleEnabled: this.rippleEnabled,
lazyLoadQueue: this.lazyLoadQueue
});
(base as SortedUser).dom = dom;
return base as SortedUser;
},
updateElementWith: fastRaf,
updateListWith: async(callback) => {
if(!isInDOM(this.list)) {
return callback(false);
}
await getHeavyAnimationPromise();
if(!isInDOM(this.list)) {
return callback(false);
}
callback(true);
}
});
safeAssign(this, options);
this.list = appDialogsManager.createChatList({new: options.new});
this.users = new Map();
this.sorted = [];
let timeout: number;
const doTimeout = () => {
timeout = window.setTimeout(() => {
this.updateList().then((good) => {
this.updateList((good) => {
if(good) {
doTimeout();
}
@ -55,83 +89,4 @@ export default class SortedUserList {
doTimeout();
}
public async updateList() {
if(!isInDOM(this.list)) {
return false;
}
await getHeavyAnimationPromise();
if(!isInDOM(this.list)) {
return false;
}
this.users.forEach(user => {
this.update(user.peerId, true);
});
this.sorted.forEach((sortedUser, idx) => {
positionElementByIndex(sortedUser.dom.listEl, this.list, idx);
});
return true;
}
public has(peerId: number) {
return this.users.has(peerId);
}
public add(peerId: number) {
if(this.has(peerId)) {
return;
}
const {dom} = appDialogsManager.addDialogNew({
dialog: peerId,
container: false,
drawStatus: false,
avatarSize: this.avatarSize,
autonomous: true,
meAsSaved: false,
rippleEnabled: this.rippleEnabled,
lazyLoadQueue: this.lazyLoadQueue
});
const sortedUser: SortedUser = {
peerId,
status: appUsersManager.getUserStatusForSort(peerId),
dom
};
this.users.set(peerId, sortedUser);
this.update(peerId);
}
public delete(peerId: number) {
const user = this.users.get(peerId);
if(!user) {
return;
}
user.dom.listEl.remove();
this.users.delete(peerId);
const idx = this.sorted.indexOf(user);
if(idx !== -1) {
this.sorted.splice(idx, 1);
}
}
public update(peerId: number, batch = false) {
const sortedUser = this.users.get(peerId);
sortedUser.status = appUsersManager.getUserStatusForSort(peerId);
const status = appUsersManager.getUserStatusString(peerId);
replaceContent(sortedUser.dom.lastMessageSpan, status);
const idx = insertInDescendSortedArray(this.sorted, sortedUser, 'status');
if(!batch) {
positionElementByIndex(sortedUser.dom.listEl, this.list, idx);
}
}
}

View File

@ -8,8 +8,6 @@ import rootScope from "../lib/rootScope";
import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise";
import { dispatchHeavyAnimationEvent } from "../hooks/useHeavyAnimationCheck";
import whichChild from "../helpers/dom/whichChild";
import findUpClassName from "../helpers/dom/findUpClassName";
import { isSafari } from "../helpers/userAgent";
import { cancelEvent } from "../helpers/dom/cancelEvent";
function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
@ -33,13 +31,13 @@ function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, t
function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
// Jolly Cobra's // Workaround for scrollable content flickering during animation.
const scrollableContainer = findUpClassName(tabContent, 'scrollable-y');
if(scrollableContainer && scrollableContainer.style.overflowY !== 'hidden') {
// const scrollBarWidth = scrollableContainer.offsetWidth - scrollableContainer.clientWidth;
scrollableContainer.style.overflowY = 'hidden';
// scrollableContainer.style.paddingRight = `${scrollBarWidth}px`;
// this.container.classList.add('sliding');
}
// const scrollableContainer = findUpClassName(tabContent, 'scrollable-y');
// if(scrollableContainer && scrollableContainer.style.overflowY !== 'hidden') {
// // const scrollBarWidth = scrollableContainer.offsetWidth - scrollableContainer.clientWidth;
// scrollableContainer.style.overflowY = 'hidden';
// // scrollableContainer.style.paddingRight = `${scrollBarWidth}px`;
// // this.container.classList.add('sliding');
// }
//window.requestAnimationFrame(() => {
const width = prevTabContent.getBoundingClientRect().width;
@ -62,22 +60,22 @@ function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight
return () => {
prevTabContent.style.transform = '';
if(scrollableContainer) {
// Jolly Cobra's // Workaround for scrollable content flickering during animation.
if(isSafari) { // ! safari doesn't respect sticky header, so it flicks when overflow is changing
scrollableContainer.style.display = 'none';
}
// if(scrollableContainer) {
// // Jolly Cobra's // Workaround for scrollable content flickering during animation.
// if(isSafari) { // ! safari doesn't respect sticky header, so it flicks when overflow is changing
// scrollableContainer.style.display = 'none';
// }
scrollableContainer.style.overflowY = '';
// scrollableContainer.style.overflowY = '';
if(isSafari) {
void scrollableContainer.offsetLeft; // reflow
scrollableContainer.style.display = '';
}
// if(isSafari) {
// void scrollableContainer.offsetLeft; // reflow
// scrollableContainer.style.display = '';
// }
// scrollableContainer.style.paddingRight = '0';
// this.container.classList.remove('sliding');
}
// // scrollableContainer.style.paddingRight = '0';
// // this.container.classList.remove('sliding');
// }
};
}

View File

@ -1005,6 +1005,59 @@ export function renderImageWithFadeIn(container: HTMLElement,
});
}
// export function renderImageWithFadeIn(container: HTMLElement,
// image: HTMLImageElement,
// url: string,
// needFadeIn: boolean,
// aspecter = container,
// thumbImage?: HTMLImageElement
// ) {
// if(needFadeIn) {
// // image.classList.add('fade-in-new', 'not-yet');
// image.classList.add('fade-in');
// }
// return new Promise<void>((resolve) => {
// /* if(photo._ === 'document') {
// console.error('wrapPhoto: will render document', photo, size, cacheContext);
// return resolve();
// } */
// renderImageFromUrl(image, url, () => {
// sequentialDom.mutateElement(container, () => {
// aspecter.append(image);
// // (needFadeIn ? getHeavyAnimationPromise() : Promise.resolve()).then(() => {
// // fastRaf(() => {
// resolve();
// // });
// if(needFadeIn) {
// fastRaf(() => {
// /* if(!image.isConnected) {
// alert('aaaa');
// } */
// // fastRaf(() => {
// image.classList.remove('not-yet');
// // });
// });
// image.addEventListener('transitionend', () => {
// sequentialDom.mutate(() => {
// image.classList.remove('fade-in-new');
// if(thumbImage) {
// thumbImage.remove();
// }
// });
// }, {once: true});
// }
// // });
// });
// });
// });
// }
export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, onlyThumb, emoji, width, height, withThumb, loop, loadPromises, needFadeIn}: {
doc: MyDocument,
div: HTMLElement,

View File

@ -38,14 +38,22 @@ export function forEachReverse<T>(array: Array<T>, callback: (value: T, index?:
};
export function insertInDescendSortedArray<T extends {[smth in K]?: number}, K extends keyof T>(array: Array<T>, element: T, property: K, pos?: number) {
const sortProperty: number = element[property];
if(pos === undefined) {
pos = array.indexOf(element);
if(pos !== -1) {
const prev = array[pos - 1];
const next = array[pos + 1];
if((!prev || prev[property] >= sortProperty) && (!next || next[property] <= sortProperty)) {
// console.warn('same pos', pos, sortProperty, prev, next);
return pos;
}
array.splice(pos, 1);
}
}
const sortProperty: number = element[property];
const len = array.length;
if(!len || sortProperty <= array[len - 1][property]) {
return array.push(element) - 1;

View File

@ -17,7 +17,9 @@ export default function positionElementByIndex(element: HTMLElement, container:
pos += 1;
}
if(container.childElementCount > pos) {
if(!pos) {
container.prepend(element);
} else if(container.childElementCount > pos) {
container.insertBefore(element, container.children[pos]);
} else {
container.append(element);

View File

@ -63,6 +63,27 @@ export function fastRaf(callback: NoneToVoidFunction) {
}
}
let fastRafConventionalCallbacks: NoneToVoidFunction[] | undefined, processing = false;
export function fastRafConventional(callback: NoneToVoidFunction) {
if(!fastRafConventionalCallbacks) {
fastRafConventionalCallbacks = [callback];
requestAnimationFrame(() => {
processing = true;
for(let i = 0; i < fastRafConventionalCallbacks.length; ++i) {
fastRafConventionalCallbacks[i]();
}
fastRafConventionalCallbacks = undefined;
processing = false;
});
} else if(processing) {
callback();
} else {
fastRafConventionalCallbacks.push(callback);
}
}
let rafPromise: Promise<DOMHighResTimeStamp>;
export function fastRafPromise() {
if(rafPromise) return rafPromise;

163
src/helpers/sortedList.ts Normal file
View File

@ -0,0 +1,163 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { insertInDescendSortedArray } from "./array";
import { getMiddleware } from "./middleware";
import { safeAssign } from "./object";
export type SortedElementBase = {
id: number,
index: number
};
export default class SortedList<SortedElement extends SortedElementBase> {
protected elements: Map<number, SortedElement>;
protected sorted: Array<SortedElement>;
protected getIndex: (id: number) => number;
protected onDelete: (element: SortedElement) => void;
protected onUpdate: (element: SortedElement) => void;
protected onSort: (element: SortedElement, idx: number) => void;
protected onElementCreate: (base: SortedElementBase, batch: boolean) => SortedElement;
protected updateElementWith = (callback: () => void) => callback();
protected updateListWith = (callback: (canUpdate: boolean | undefined) => void) => callback(true);
protected middleware = getMiddleware();
constructor(options: {
getIndex: SortedList<SortedElement>['getIndex'],
onDelete?: SortedList<SortedElement>['onDelete'],
onUpdate?: SortedList<SortedElement>['onUpdate'],
onSort?: SortedList<SortedElement>['onSort'],
onElementCreate: SortedList<SortedElement>['onElementCreate'],
updateElementWith?: SortedList<SortedElement>['updateElementWith'],
updateListWith?: SortedList<SortedElement>['updateListWith']
}) {
safeAssign(this, options);
this.elements = new Map();
this.sorted = [];
}
public clear() {
this.middleware.clean();
this.elements.clear();
this.sorted.length = 0;
}
protected _updateList() {
this.elements.forEach(element => {
this.update(element.id, true);
});
if(this.onSort) {
this.sorted.forEach((element, idx) => {
this.onSort(element, idx);
});
}
}
public updateList(callback: (updated: boolean) => void) {
const middleware = this.middleware.get();
this.updateListWith((canUpdate) => {
if(!middleware() || (canUpdate !== undefined && !canUpdate)) {
return callback(false);
}
this._updateList();
callback(true);
});
}
public has(id: number) {
return this.elements.has(id);
}
public get(id: number) {
return this.elements.get(id);
}
public getAll() {
return this.elements;
}
public add(id: number, batch = false, updateElementWith?: SortedList<SortedElement>['updateElementWith'], updateBatch = batch) {
let element = this.get(id);
if(element) {
return element;
}
const base: SortedElementBase = {
id,
index: 0
};
element = this.onElementCreate(base, batch);
this.elements.set(id, element);
this.update(id, updateBatch, element, updateElementWith);
return element;
}
public delete(id: number, noScheduler?: boolean) {
const element = this.elements.get(id);
if(!element) {
return false;
}
this.elements.delete(id);
const idx = this.sorted.indexOf(element);
if(idx !== -1) {
this.sorted.splice(idx, 1);
}
if(this.onDelete) {
if(noScheduler) {
this.onDelete(element);
} else {
const middleware = this.middleware.get();
this.updateElementWith(() => {
if(!middleware()) {
return;
}
this.onDelete(element);
});
}
}
return true;
}
public update(id: number, batch = false, element = this.get(id), updateElementWith?: SortedList<SortedElement>['updateElementWith']) {
if(!element) {
return;
}
element.index = this.getIndex(id);
this.onUpdate && this.onUpdate(element);
const idx = insertInDescendSortedArray(this.sorted, element, 'index');
if(!batch && this.onSort) {
const middleware = this.middleware.get();
(updateElementWith || this.updateElementWith)(() => {
if(!middleware()) {
return;
}
// * в случае пересортировки этого же элемента во время ожидания вызовется вторая такая же. нужно соблюдать последовательность событий
this.onSort(element, idx);
/* if(this.get(id) === element) {
this.onSort(element, this.sorted.indexOf(element));
} */
});
}
}
}

View File

@ -43,7 +43,7 @@ import replaceContent from "../../helpers/dom/replaceContent";
import ConnectionStatusComponent from "../../components/connectionStatus";
import appChatsManager from "./appChatsManager";
import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl";
import { fastRafPromise } from "../../helpers/schedulers";
import { fastRaf, fastRafConventional, fastRafPromise } from "../../helpers/schedulers";
import SortedUserList from "../../components/sortedUserList";
import { isTouchSupported } from "../../helpers/touchSupport";
import handleTabSwipe from "../../helpers/dom/handleTabSwipe";
@ -52,6 +52,8 @@ import isInDOM from "../../helpers/dom/isInDOM";
import appPhotosManager, { MyPhoto } from "./appPhotosManager";
import { MyDocument } from "./appDocsManager";
import { setSendingStatus } from "../../components/sendingStatus";
import SortedList, { SortedElementBase } from "../../helpers/sortedList";
import debounce from "../../helpers/schedulers/debounce";
export type DialogDom = {
avatarEl: AvatarElement,
@ -68,14 +70,56 @@ export type DialogDom = {
subtitleEl: HTMLElement
};
interface SortedDialog extends SortedElementBase {
dom: DialogDom,
loadPromises?: Promise<any>[]
}
class SortedDialogList extends SortedList<SortedDialog> {
constructor(public list: HTMLUListElement, public indexKey: ReturnType<DialogsStorage['getDialogIndexKey']>) {
super({
getIndex: (id) => appMessagesManager.getDialogOnly(id)[this.indexKey],
onDelete: (element) => {
element.dom.listEl.remove();
appDialogsManager.onListLengthChange();
},
onSort: (element, idx) => {
const willChangeLength = element.dom.listEl.parentElement !== this.list;
positionElementByIndex(element.dom.listEl, this.list, idx);
if(willChangeLength) {
appDialogsManager.onListLengthChange();
}
},
onElementCreate: (base, batch) => {
const loadPromises: Promise<any>[] = batch ? [] : undefined;
const {dom} = appDialogsManager.addListDialog({dialog: base.id, loadPromises, isBatch: batch});
(base as SortedDialog).dom = dom;
if(loadPromises?.length) {
(base as SortedDialog).loadPromises = loadPromises;
Promise.all(loadPromises).finally(() => {
delete (base as SortedDialog).loadPromises;
});
}
return base as SortedDialog;
},
updateElementWith: fastRafConventional
});
}
public clear() {
this.list.innerHTML = '';
super.clear();
}
}
//const testScroll = false;
//let testTopSlice = 1;
export class AppDialogsManager {
private chatList: HTMLUListElement;
private doms: {[peerId: number]: DialogDom} = {};
private chatsContainer = document.getElementById('chatlist-container') as HTMLDivElement;
private chatsPreloader: HTMLElement;
@ -87,7 +131,8 @@ export class AppDialogsManager {
private contextMenu = new DialogsContextMenu();
public chatLists: {[filterId: number]: HTMLUListElement} = {};
public sortedList: SortedDialogList;
public sortedLists: {[filterId: number]: SortedDialogList} = {};
public scrollables: {[filterId: number]: Scrollable} = {};
public filterId: number;
private folders: {[k in 'menu' | 'container' | 'menuScrollContainer']: HTMLElement} = {
@ -111,7 +156,6 @@ export class AppDialogsManager {
//private topOffsetIndex = 0;
private sliceTimeout: number;
private reorderDialogsTimeout: number;
private lastActiveElements: Set<HTMLElement> = new Set();
@ -122,6 +166,8 @@ export class AppDialogsManager {
private indexKey: ReturnType<DialogsStorage['getDialogIndexKey']>;
public onListLengthChange: () => Promise<void>;
constructor() {
this.chatsPreloader = putPreloader(null, true);
@ -170,7 +216,7 @@ export class AppDialogsManager {
orderIndex: 0
});
this.chatList = this.chatLists[this.filterId];
this.sortedList = this.sortedLists[this.filterId];
this.scroll = this.scrollables[this.filterId];
/* if(testScroll) {
@ -212,7 +258,7 @@ export class AppDialogsManager {
rootScope.addEventListener('dialog_flush', ({peerId}) => {
const dialog = appMessagesManager.getDialogOnly(peerId);
if(dialog) {
this.setLastMessage(dialog);
this.setLastMessage(dialog, undefined, undefined, undefined, undefined, undefined, true);
this.validateDialogForFilter(dialog);
this.setFiltersUnreadCount();
}
@ -313,7 +359,7 @@ export class AppDialogsManager {
elements.container.remove();
elements.menu.remove();
delete this.chatLists[filter.id];
delete this.sortedLists[filter.id];
delete this.scrollables[filter.id];
delete this.filtersRendered[filter.id];
@ -328,6 +374,9 @@ export class AppDialogsManager {
const filter = appMessagesManager.filtersStorage.getFilter(filterId);
const renderedFilter = this.filtersRendered[filterId];
const sortedList = this.sortedLists[filterId];
sortedList.indexKey = appMessagesManager.dialogsStorage.getDialogIndexKey(filterId);
positionElementByIndex(renderedFilter.menu, containerToAppend, filter.orderIndex);
positionElementByIndex(renderedFilter.container, this.folders.container, filter.orderIndex);
});
@ -393,13 +442,13 @@ export class AppDialogsManager {
if(this.filterId === id) return;
this.chatLists[id].innerHTML = '';
this.sortedLists[id].clear();
this.setFilterId(id);
this.onTabChange();
}, () => {
for(const folderId in this.chatLists) {
for(const folderId in this.sortedLists) {
if(+folderId !== this.filterId) {
this.chatLists[folderId].innerHTML = '';
this.sortedLists[folderId].clear();
}
}
}, undefined, foldersScrollable);
@ -436,6 +485,12 @@ export class AppDialogsManager {
setTimeout(() => {
lottieLoader.loadLottieWorkers();
}, 200);
this.onListLengthChange = debounce(this._onListLengthChange, 100, false, true);
}
public get chatList() {
return this.sortedList.list;
}
public setFilterId(filterId: number) {
@ -484,7 +539,7 @@ export class AppDialogsManager {
}
private isDialogMustBeInViewport(dialog: Dialog) {
if(dialog.migratedTo !== undefined) return false;
if(dialog.migratedTo !== undefined || !this.testDialogForFilter(dialog)) return false;
//return true;
const topOffset = this.getOffsetIndex('top');
const bottomOffset = this.getOffsetIndex('bottom');
@ -498,69 +553,31 @@ export class AppDialogsManager {
}
private deleteDialog(peerId: number) {
const dom = this.getDialogDom(peerId);
if(dom) {
dom.listEl.remove();
delete this.doms[peerId];
this.onListLengthChange();
return true;
}
return false;
this.sortedList.delete(peerId);
}
private updateDialog(dialog: Dialog) {
if(!dialog) {
return;
}
if(this.isDialogMustBeInViewport(dialog)) {
if(!this.doms.hasOwnProperty(dialog.peerId)) {
const ret = this.addListDialog({dialog});
if(ret) {
const idx = appMessagesManager.getDialogByPeerId(dialog.peerId)[1];
positionElementByIndex(ret.dom.listEl, this.chatList, idx);
this.onListLengthChange();
} else {
return;
}
}
this.sortedList.add(dialog.peerId);
} else {
this.deleteDialog(dialog.peerId);
return;
}
/* const topOffset = this.getOffset('top');
if(topOffset.index && dialog.index > topOffset.index) {
const dom = this.getDialogDom(dialog.peerId);
if(dom) {
dom.listEl.remove();
delete this.doms[dialog.peerId];
}
return;
}
if(!this.doms.hasOwnProperty(dialog.peerId)) {
this.addDialogNew({dialog});
} */
if(this.getDialogDom(dialog.peerId)) {
this.setLastMessage(dialog);
this.reorderDialogs();
const dom = this.getDialogDom(dialog.peerId);
if(dom) {
this.setLastMessage(dialog, undefined, dom, undefined, undefined, undefined, true);
this.sortedList.update(dialog.peerId);
}
}
public onTabChange = () => {
this.doms = {};
this.scroll = this.scrollables[this.filterId];
this.scroll.loadedAll.top = true;
this.scroll.loadedAll.bottom = false;
this.offsets.top = this.offsets.bottom = 0;
this.loadDialogsPromise = undefined;
this.chatList = this.chatLists[this.filterId];
this.sortedList = this.sortedLists[this.filterId];
this.onChatsScroll();
};
@ -603,37 +620,37 @@ export class AppDialogsManager {
* Удалит неподходящие чаты из списка, но не добавит их(!)
*/
private validateListForFilter() {
// !WARNING, возможно это было зачем-то, но комментарий исправил архивирование
//if(this.filterId === 0) return;
const folder = appMessagesManager.dialogsStorage.getFolder(this.filterId);
for(const _peerId in this.doms) {
const peerId = +_peerId;
// если больше не подходит по фильтру, удаляем
if(folder.findIndex((dialog) => dialog.peerId === peerId) === -1) {
this.deleteDialog(peerId);
const filter = appMessagesManager.filtersStorage.getFilter(this.filterId);
this.sortedList.getAll().forEach((element) => {
const dialog = appMessagesManager.getDialogOnly(element.id);
if(!this.testDialogForFilter(dialog, filter || null)) {
this.deleteDialog(element.id);
}
}
});
}
/**
* Удалит неподходящие чат из списка, но не добавит его(!)
* Удалит неподходящий чат из списка, но не добавит его(!)
*/
private validateDialogForFilter(dialog: Dialog, filter?: MyDialogFilter) {
if(this.filterId <= 1 || !this.doms[dialog.peerId]) {
if(!this.getDialogDom(dialog.peerId)) {
return;
}
if(!filter) {
filter = appMessagesManager.filtersStorage.getFilter(this.filterId);
}
if(!appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) {
if(!this.testDialogForFilter(dialog, filter)) {
this.deleteDialog(dialog.peerId);
}
}
public testDialogForFilter(dialog: Dialog, filter = appMessagesManager.filtersStorage.getFilter(this.filterId)) {
if((filter && !appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) ||
(!filter && this.filterId !== dialog.folder_id)) {
return false;
}
return true;
}
public generateScrollable(list: HTMLUListElement, filterId: number) {
const scrollable = new Scrollable(null, 'CL', 500);
scrollable.container.addEventListener('scroll', this.onChatsRegularScroll);
@ -642,8 +659,13 @@ export class AppDialogsManager {
scrollable.onScrolledBottom = this.onChatsScroll;
scrollable.setVirtualContainer(list);
this.chatLists[filterId] = list;
const sortedDialogList = new SortedDialogList(list, appMessagesManager.dialogsStorage ? appMessagesManager.dialogsStorage.getDialogIndexKey(filterId) : 'index');
this.scrollables[filterId] = scrollable;
this.sortedLists[filterId] = sortedDialogList;
// list.classList.add('hide');
// scrollable.container.style.backgroundColor = '#' + (Math.random() * (16 ** 6 - 1) | 0).toString(16);
return scrollable;
}
@ -723,15 +745,12 @@ export class AppDialogsManager {
if(this.loadDialogsPromise/* || 1 === 1 */) return this.loadDialogsPromise;
const promise = new Promise<void>(async(resolve) => {
if(!this.chatList.childElementCount) {
const container = this.chatList.parentElement;
container.append(this.chatsPreloader);
}
const {chatList, filterId} = this;
//return;
const filterId = this.filterId;
let loadCount = 30/*this.chatsLoadCount */;
// let loadCount = 30/*this.chatsLoadCount */;
let loadCount = windowSize.windowH / 72 * 1.25 | 0;
let offsetIndex = 0;
const {index: currentOffsetIndex} = this.getOffsetIndex(side);
@ -753,8 +772,9 @@ export class AppDialogsManager {
//console.time('getDialogs time');
const getConversationsResult = appMessagesManager.getConversations('', offsetIndex, loadCount, filterId, true);
if(getConversationsResult.cached) {
this.chatsPreloader.remove();
if(!getConversationsResult.cached && !chatList.childElementCount) {
const container = chatList.parentElement;
container.append(this.chatsPreloader);
}
const result = await getConversationsResult.promise;
@ -766,43 +786,37 @@ export class AppDialogsManager {
//console.timeEnd('getDialogs time');
// * loaded all
//if(!result.dialogs.length || this.chatList.childElementCount === result.count) {
//if(!result.dialogs.length || chatList.childElementCount === result.count) {
// !result.dialogs.length не подходит, так как при супердревном диалоге getConversations его не выдаст.
//if(this.chatList.childElementCount === result.count) {
//if(chatList.childElementCount === result.count) {
if(side === 'bottom') {
if(result.isEnd) {
this.scroll.loadedAll[side] = true;
}
} else {
const storage = appMessagesManager.dialogsStorage.getFolder(filterId);
if(!result.dialogs.length || (storage.length && storage[0][this.indexKey] < offsetIndex)) {
this.scroll.loadedAll[side] = true;
}
} else if(result.isTopEnd) {
this.scroll.loadedAll[side] = true;
}
if(result.dialogs.length) {
const dialogs = side === 'top' ? result.dialogs.slice().reverse() : result.dialogs;
const container = document.createDocumentFragment();
const loadPromises: Promise<any>[] = [];
const append = side === 'bottom';
const callbacks: (() => void)[] = [];
const cccc = (callback: () => void) => {
callbacks.push(callback);
};
dialogs.forEach((dialog) => {
this.addListDialog({
dialog,
container,
append,
loadPromises,
isBatch: true
});
const element = this.sortedList.add(dialog.peerId, true, cccc, false);
if(element.loadPromises) {
loadPromises.push(...element.loadPromises);
}
});
if(container.childElementCount) {
if(loadPromises.length) {
await Promise.all(loadPromises).finally();
}
await Promise.all(loadPromises).finally();
this.scroll[append ? 'append' : 'prepend'](container);
}
callbacks.forEach(callback => callback());
}
const offsetDialog = result.dialogs[side === 'top' ? 0 : result.dialogs.length - 1];
@ -810,9 +824,7 @@ export class AppDialogsManager {
this.offsets[side] = offsetDialog[this.indexKey];
}
this.onListLengthChange();
this.log.debug('getDialogs ' + loadCount + ' dialogs by offset:', offsetIndex, result, this.chatList.childElementCount);
this.log.debug('getDialogs ' + loadCount + ' dialogs by offset:', offsetIndex, result, chatList.childElementCount);
setTimeout(() => {
this.scroll.onScroll();
@ -821,7 +833,10 @@ export class AppDialogsManager {
this.log.error(err);
}
this.chatsPreloader.remove();
if(this.chatsPreloader.parentElement) {
this.chatsPreloader.remove();
}
resolve();
}).finally(() => {
this.loadDialogsPromise = undefined;
@ -860,10 +875,11 @@ export class AppDialogsManager {
return;
}
const part = this.chatList.parentElement as HTMLElement;
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.scroll.loadedAll.bottom && !this.chatList.childElementCount/* || true */;
// this.chatList.style.display = 'none';
const needPlaceholder = this.scroll.loadedAll.bottom && !chatList.childElementCount/* || true */;
// chatList.style.display = 'none';
if(needPlaceholder && placeholderContainer) {
return;
@ -944,15 +960,16 @@ export class AppDialogsManager {
part.classList.add('with-placeholder');
}
private onListLengthChange = () => {
public _onListLengthChange = () => {
this.checkIfPlaceholderNeeded();
if(this.filterId > 0) return;
const count = this.chatList.childElementCount;
const chatList = this.chatList;
const count = chatList.childElementCount;
const parts = this.chatList.parentElement.parentElement;
const bottom = this.chatList.parentElement.nextElementSibling as HTMLElement;
const parts = chatList.parentElement.parentElement;
const bottom = chatList.parentElement.nextElementSibling as HTMLElement;
const hasContacts = !!bottom.childElementCount;
if(count >= 10) {
if(hasContacts) {
@ -1019,14 +1036,11 @@ export class AppDialogsManager {
};
public onChatsRegularScroll = () => {
// return;
if(this.sliceTimeout) clearTimeout(this.sliceTimeout);
this.sliceTimeout = window.setTimeout(() => {
this.sliceTimeout = undefined;
if(this.reorderDialogsTimeout) {
this.onChatsRegularScroll();
return;
}
if(!this.chatList.childElementCount || this.processContact) {
return;
@ -1040,6 +1054,10 @@ export class AppDialogsManager {
observer.observe(el);
}); */
fastRafConventional(() => {
const perf = performance.now();
const scrollTopWas = this.scroll.scrollTop;
const firstElementChild = this.chatList.firstElementChild;
@ -1115,16 +1133,20 @@ export class AppDialogsManager {
//alert('left length:' + children.length);
this.scroll.scrollTop = firstElement.offsetTop - elementOverflow;
this.log('slice time', performance.now() - perf);
/* const firstElementRect = firstElement.getBoundingClientRect();
const scrollTop = */
//this.scroll.scrollIntoView(firstElement, false);
});
}, 200);
};
private setOffsets() {
const firstDialog = this.getDialogFromElement(this.chatList.firstElementChild as HTMLElement);
const lastDialog = this.getDialogFromElement(this.chatList.lastElementChild as HTMLElement);
const chatList = this.chatList;
const firstDialog = this.getDialogFromElement(chatList.firstElementChild as HTMLElement);
const lastDialog = this.getDialogFromElement(chatList.lastElementChild as HTMLElement);
this.offsets.top = firstDialog[this.indexKey];
this.offsets.bottom = lastDialog[this.indexKey];
@ -1230,71 +1252,14 @@ export class AppDialogsManager {
return list;
}
private reorderDialogs() {
//const perf = performance.now();
if(this.reorderDialogsTimeout) {
return;
}
if(this.loadDialogsPromise) {
this.loadDialogsPromise.then(() => {
this.reorderDialogs();
});
return;
}
this.reorderDialogsTimeout = window.requestAnimationFrame(() => {
this.reorderDialogsTimeout = 0;
if(this.loadDialogsPromise) {
this.loadDialogsPromise.then(() => {
this.reorderDialogs();
});
return;
}
const dialogs = appMessagesManager.dialogsStorage.getFolder(this.filterId);
const currentOrder = (Array.from(this.chatList.children) as HTMLElement[]).map(el => +el.dataset.peerId);
const {index} = this.getOffsetIndex('top');
const pos = dialogs.findIndex(dialog => dialog[this.indexKey] <= index);
const offset = Math.max(0, pos);
dialogs.forEach((dialog, index) => {
const dom = this.getDialogDom(dialog.peerId);
if(!dom) {
return;
}
const needIndex = index - offset;
if(needIndex > currentOrder.length) {
this.deleteDialog(dialog.peerId);
return;
}
const peerIdByIndex = currentOrder[needIndex];
if(peerIdByIndex !== dialog.peerId) {
if(positionElementByIndex(dom.listEl, this.chatList, needIndex)) {
this.log.debug('setDialogPosition:', dialog, dom, peerIdByIndex, needIndex);
}
}
});
//this.log('Reorder time:', performance.now() - perf);
});
}
public setLastMessage(
dialog: Dialog,
lastMessage?: any,
dom?: DialogDom,
highlightWord?: string,
loadPromises?: Promise<any>[],
isBatch = false
isBatch = false,
setUnread = false
) {
///////console.log('setlastMessage:', lastMessage);
if(!dom) {
@ -1408,16 +1373,16 @@ export class AppDialogsManager {
replaceContent(dom.lastTimeSpan, formatDateAccordingToTodayNew(new Date(date * 1000)));
} else dom.lastTimeSpan.textContent = '';
if(this.doms[peerId] === dom) {
this.setUnreadMessages(dialog, isBatch);
} else { // means search
dom.listEl.dataset.mid = lastMessage.mid;
if(setUnread !== null) {
if(setUnread) {
this.setUnreadMessages(dialog, dom, isBatch);
} else { // means search
dom.listEl.dataset.mid = lastMessage.mid;
}
}
}
private setUnreadMessages(dialog: Dialog, isBatch = false) {
const dom = this.getDialogDom(dialog.peerId);
private setUnreadMessages(dialog: Dialog, dom = this.getDialogDom(dialog.peerId), isBatch = false) {
if(dialog.folder_id === 1) {
this.accumulateArchivedUnread();
}
@ -1523,7 +1488,9 @@ export class AppDialogsManager {
}
private getDialogDom(peerId: number) {
return this.doms[peerId];
// return this.doms[peerId];
const element = this.sortedList.get(peerId);
return element?.dom;
}
private getDialog(dialog: Dialog | number): Dialog {
@ -1543,32 +1510,20 @@ export class AppDialogsManager {
return dialog;
}
private addListDialog(options: Parameters<AppDialogsManager['addDialogNew']>[0] & {isBatch?: boolean}) {
public addListDialog(options: Parameters<AppDialogsManager['addDialogNew']>[0] & {isBatch?: boolean}) {
const dialog = this.getDialog(options.dialog);
const filter = appMessagesManager.filtersStorage.getFilter(this.filterId);
if((filter && !appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) ||
(!filter && this.filterId !== dialog.folder_id)) {
return;
}
if(!options.container) {
options.container = this.scroll;
}
options.autonomous = false;
const ret = this.addDialogNew(options);
if(ret) {
this.doms[dialog.peerId] = ret.dom;
const isMuted = appNotificationsManager.isPeerLocalMuted(dialog.peerId, true);
if(isMuted) {
ret.dom.listEl.classList.add('is-muted');
}
this.setLastMessage(dialog, undefined, undefined, undefined, options.loadPromises, options.isBatch);
this.setLastMessage(dialog, undefined, ret.dom, undefined, options.loadPromises, options.isBatch, true);
}
return ret;
@ -1745,7 +1700,7 @@ export class AppDialogsManager {
}
dom.lastMessageSpan.classList.remove('user-typing');
this.setLastMessage(dialog, null, dom);
this.setLastMessage(dialog, null, dom, undefined, undefined, undefined, null);
}
}

View File

@ -51,7 +51,7 @@ import PeerTitle from "../../components/peerTitle";
import { forEachReverse } from "../../helpers/array";
import htmlToDocumentFragment from "../../helpers/dom/htmlToDocumentFragment";
import htmlToSpan from "../../helpers/dom/htmlToSpan";
import { REPLIES_PEER_ID, SERVICE_PEER_ID } from "../mtproto/mtproto_config";
import { MUTE_UNTIL, REPLIES_PEER_ID, SERVICE_PEER_ID } from "../mtproto/mtproto_config";
import formatCallDuration from "../../helpers/formatCallDuration";
import appAvatarsManager from "./appAvatarsManager";
import telegramMeWebManager from "../mtproto/telegramMeWebManager";
@ -1379,8 +1379,8 @@ export class AppMessagesManager {
//if(!options.isGroupedItem) {
this.saveMessages([message], {storage, isOutgoing: true});
this.setDialogTopMessage(message);
setTimeout(() => {
this.setDialogTopMessage(message);
rootScope.dispatchEvent('history_append', {storage, peerId, mid: messageId});
}, 0);
}
@ -3096,7 +3096,7 @@ export class AppMessagesManager {
return true;
}
public canDeleteMessage(message: any) {
public canDeleteMessage(message: MyMessage) {
return message && (
message.peerId > 0
|| message.fromId === rootScope.myId
@ -3553,7 +3553,7 @@ export class AppMessagesManager {
}
}
handleNewMessages = () => {
private handleNewMessages = () => {
clearTimeout(this.newMessagesHandleTimeout);
this.newMessagesHandleTimeout = 0;
@ -3561,7 +3561,7 @@ export class AppMessagesManager {
this.newMessagesToHandle = {};
};
handleNewDialogs = () => {
private handleNewDialogs = () => {
let newMaxSeenId = 0;
const obj = this.newDialogsToHandle;
for(const peerId in obj) {
@ -3593,8 +3593,9 @@ export class AppMessagesManager {
}
if(this.newDialogsHandlePromise) return this.newDialogsHandlePromise;
return this.newDialogsHandlePromise = new Promise((resolve) => {
return this.newDialogsHandlePromise = new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
this.newDialogsHandlePromise = undefined;
this.handleNewDialogs();
}, 0);
@ -4113,7 +4114,7 @@ export class AppMessagesManager {
}
if((message as Message.message).fwd_from) {
notifyPeerToHandle.fwdCount++;
++notifyPeerToHandle.fwdCount;
}
notifyPeerToHandle.topMessage = message;
@ -4322,17 +4323,10 @@ export class AppMessagesManager {
};
private onUpdateChannelAvailableMessages = (update: Update.updateChannelAvailableMessages) => {
const channelId: number = update.channel_id;
const messages: number[] = [];
const peerId: number = -channelId;
const peerId: number = -update.channel_id;
const history = this.getHistoryStorage(peerId).history.slice;
if(history.length) {
history.forEach((msgId: number) => {
if(!update.available_min_id || msgId <= update.available_min_id) {
messages.push(msgId);
}
});
}
const availableMinId = appMessagesIdsManager.generateMessageId(update.available_min_id);
const messages = history.filter(mid => mid <= availableMinId);
(update as any as Update.updateDeleteChannelMessages).messages = messages;
this.onUpdateDeleteMessages(update as any as Update.updateDeleteChannelMessages);
@ -4368,17 +4362,14 @@ export class AppMessagesManager {
return this.getHistoryStorage(+splitted[0], +splitted[1]);
});
[this.getHistoryStorage(peerId)].concat(threadsStorages).forEach(historyStorage => {
for(const mid in historyUpdated.msgs) {
historyStorage.history.delete(+mid);
const historyStorage = this.getHistoryStorage(peerId);
[historyStorage].concat(threadsStorages).forEach(historyStorage => {
for(const mid of historyUpdated.msgs) {
historyStorage.history.delete(mid);
}
if(historyUpdated.count &&
historyStorage.count !== null &&
historyStorage.count > 0) {
historyStorage.count -= historyUpdated.count;
if(historyStorage.count < 0) {
historyStorage.count = 0;
}
if(historyUpdated.count && historyStorage.count) {
historyStorage.count = Math.max(0, historyStorage.count - historyUpdated.count);
}
});
@ -4392,12 +4383,21 @@ export class AppMessagesManager {
if(historyUpdated.unread) {
foundDialog.unread_count -= historyUpdated.unread;
}
if(historyUpdated.unreadMentions || historyUpdated.unread) {
rootScope.dispatchEvent('dialog_unread', {peerId});
}
if(historyUpdated.msgs.has(foundDialog.top_message)) {
this.reloadConversation(peerId);
const slice = historyStorage.history.first;
if(slice.isEnd(SliceEnd.Bottom) && slice.length) {
const mid = slice[0];
const message = this.getMessageByPeer(peerId, mid);
this.setDialogTopMessage(message, foundDialog);
} else {
this.reloadConversation(peerId);
}
}
}
};
@ -4420,7 +4420,7 @@ export class AppMessagesManager {
const dialog = this.getDialogOnly(peerId);
if(!!dialog !== needDialog) {
if(needDialog) {
this.reloadConversation(-channelId);
this.reloadConversation(peerId);
} else {
if(dialog) {
this.dialogsStorage.dropDialog(peerId);
@ -4431,13 +4431,12 @@ export class AppMessagesManager {
};
private onUpdateChannelReload = (update: Update.updateChannelReload) => {
const channelId = update.channel_id;
const peerId = -channelId;
const peerId = update.channel_id;
this.dialogsStorage.dropDialog(peerId);
delete this.historiesStorage[peerId];
this.reloadConversation(-channelId).then(() => {
this.reloadConversation(peerId).then(() => {
rootScope.dispatchEvent('history_reload', peerId);
});
};
@ -4447,7 +4446,7 @@ export class AppMessagesManager {
const peerId = -update.channel_id;
const mid = appMessagesIdsManager.generateMessageId(update.id);
const message: Message.message = this.getMessageByPeer(peerId, mid);
if(!message.deleted && message.views && message.views < views) {
if(!message.deleted && message.views !== undefined && message.views < views) {
message.views = views;
rootScope.dispatchEvent('message_views', {peerId, mid, views});
}
@ -4458,7 +4457,7 @@ export class AppMessagesManager {
const fromId = SERVICE_PEER_ID;
const peerId = fromId;
const messageId = this.generateTempMessageId(peerId);
const message: any = {
const message: Message.message = {
_: 'message',
id: messageId,
from_id: appPeersManager.getOutputPeer(fromId),
@ -4474,7 +4473,7 @@ export class AppMessagesManager {
_: 'user',
id: fromId,
pFlags: {verified: true},
access_hash: 0,
access_hash: '0',
first_name: 'Telegram',
phone: '42777'
}]);
@ -4595,7 +4594,7 @@ export class AppMessagesManager {
}
};
public setDialogToStateIfMessageIsTop(message: any) {
public setDialogToStateIfMessageIsTop(message: MyMessage) {
const dialog = this.getDialogOnly(message.peerId);
if(dialog && dialog.top_message === message.mid) {
this.dialogsStorage.setDialogToState(dialog);
@ -4642,9 +4641,9 @@ export class AppMessagesManager {
return promise;
}
private checkPendingMessage(message: any) {
private checkPendingMessage(message: MyMessage) {
const randomId = this.pendingByMessageId[message.mid];
let pendingMessage: any;
let pendingMessage: ReturnType<AppMessagesManager['finalizePendingMessage']>;
if(randomId) {
const pendingData = this.pendingByRandomId[randomId];
if(pendingMessage = this.finalizePendingMessage(randomId, message)) {
@ -4666,7 +4665,7 @@ export class AppMessagesManager {
mute = !appNotificationsManager.isPeerLocalMuted(peerId, false);
}
settings.mute_until = mute ? 0x7FFFFFFF : 0;
settings.mute_until = mute ? MUTE_UNTIL : 0;
return appNotificationsManager.updateNotifySettings({
_: 'inputNotifyPeer',
@ -4684,7 +4683,7 @@ export class AppMessagesManager {
}
}
public finalizePendingMessage(randomId: string, finalMessage: any) {
public finalizePendingMessage(randomId: string, finalMessage: MyMessage) {
const pendingData = this.pendingByRandomId[randomId];
// this.log('pdata', randomID, pendingData)
@ -4699,7 +4698,7 @@ export class AppMessagesManager {
// this.log('pending', randomID, historyStorage.pending)
const message = this.getMessageFromStorage(storage, tempId);
const message: Message.message = this.getMessageFromStorage(storage, tempId);
if(!message.deleted) {
delete message.pFlags.is_outgoing;
delete message.pending;
@ -5382,17 +5381,18 @@ export class AppMessagesManager {
message.deleted = true;
if(message._ !== 'messageService' && message.grouped_id) {
const groupedStorage = this.groupedMessagesStorage[message.grouped_id];
const groupedId = (message as Message.message).grouped_id;
if(groupedId) {
const groupedStorage = this.groupedMessagesStorage[groupedId];
if(groupedStorage) {
delete groupedStorage[mid];
if(!history.albums) history.albums = {};
(history.albums[message.grouped_id] || (history.albums[message.grouped_id] = new Set())).add(mid);
(history.albums[groupedId] || (history.albums[groupedId] = new Set())).add(mid);
if(!Object.keys(groupedStorage).length) {
delete history.albums;
delete this.groupedMessagesStorage[message.grouped_id];
delete this.groupedMessagesStorage[groupedId];
}
}
}

View File

@ -344,7 +344,7 @@ export class AppUsersManager {
});
}
public saveApiUsers(apiUsers: any[], override?: boolean) {
public saveApiUsers(apiUsers: MTUser[], override?: boolean) {
apiUsers.forEach((user) => this.saveApiUser(user, override));
}

View File

@ -11,3 +11,4 @@ export type UserAuth = {dcID: number | string, date: number, id: number};
export const REPLIES_PEER_ID = 1271266957;
export const SERVICE_PEER_ID = 777000;
export const MUTE_UNTIL = 0x7FFFFFFF;

View File

@ -43,6 +43,7 @@ export type BroadcastEvents = {
'dialog_migrate': {migrateFrom: number, migrateTo: number},
//'dialog_top': Dialog,
'dialog_notify_settings': Dialog,
// 'dialog_order': {dialog: Dialog, pos: number},
'dialogs_multiupdate': {[peerId: string]: Dialog},
'dialogs_archived_unread': {count: number},

View File

@ -27,6 +27,7 @@ import rootScope from "../rootScope";
import { safeReplaceObject } from "../../helpers/object";
import { AppStateManager } from "../appManagers/appStateManager";
import { SliceEnd } from "../../helpers/slicedArray";
import { MyDialogFilter } from "./filters";
export type FolderDialog = {
dialog: Dialog,
@ -84,6 +85,14 @@ export default class DialogsStorage {
}
});
// to set new indexes
rootScope.addEventListener('filter_order', () => {
// ! MUST BE REFACTORED !
for(let id in this.appMessagesManager.filtersStorage.filters) {
this.getFolder(+id, false);
}
});
rootScope.addMultipleEventsListeners({
updateFolderPeers: this.onUpdateFolderPeers,
@ -186,19 +195,7 @@ export default class DialogsStorage {
const indexStr = this.getDialogIndexKey(id);
for(const peerId in this.dialogs) {
const dialog = this.dialogs[peerId];
if(this.appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter) && (!skipMigrated || dialog.migratedTo === undefined)) {
let index: number;
const pinnedIndex = filter.pinned_peers.indexOf(dialog.peerId);
if(pinnedIndex !== -1) {
index = this.generateDialogIndex(this.generateDialogPinnedDateByIndex(filter.pinned_peers.length - 1 - pinnedIndex), true);
} else if(dialog.pFlags?.pinned) {
index = this.generateIndexForDialog(dialog, true);
} else {
index = dialog.index;
}
dialog[indexStr] = index;
if(this.setDialogIndexInFilter(dialog, indexStr, filter) && (!skipMigrated || dialog.migratedTo === undefined)) {
insertInDescendSortedArray(dialogs, dialog, indexStr, -1);
}
}
@ -209,6 +206,23 @@ export default class DialogsStorage {
// return dialogs.map(d => d.dialog);
}
private setDialogIndexInFilter(dialog: Dialog, indexKey: ReturnType<DialogsStorage['getDialogIndexKey']>, filter: MyDialogFilter) {
let index: number;
if(this.appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) {
const pinnedIndex = filter.pinned_peers.indexOf(dialog.peerId);
if(pinnedIndex !== -1) {
index = this.generateDialogIndex(this.generateDialogPinnedDateByIndex(filter.pinned_peers.length - 1 - pinnedIndex), true);
} else if(dialog.pFlags?.pinned) {
index = this.generateIndexForDialog(dialog, true);
} else {
index = dialog.index;
}
}
return dialog[indexKey] = index;
}
public getDialog(peerId: number, folderId?: number, skipMigrated = true): [Dialog, number] | [] {
const folders: Dialog[][] = [];
@ -286,8 +300,17 @@ export default class DialogsStorage {
}
const index = this.generateDialogIndex(topDate, isPinned);
if(justReturn) return index;
if(justReturn) {
return index;
}
dialog.index = index;
// ! MUST BE REFACTORED !
for(let id in this.appMessagesManager.filtersStorage.filters) {
const filter = this.appMessagesManager.filtersStorage.filters[id];
this.setDialogIndexInFilter(dialog, this.getDialogIndexKey(+id), filter);
}
}
public generateDialogPinnedDateByIndex(pinnedIndex: number) {
@ -327,11 +350,12 @@ export default class DialogsStorage {
public setDialogToState(dialog: Dialog) {
const historyStorage = this.appMessagesManager.getHistoryStorage(dialog.peerId);
const history = [].concat(historyStorage.history.slice);
const messagesStorage = this.appMessagesManager.getMessagesStorage(dialog.peerId);
const history = historyStorage.history.slice;
let incomingMessage: any;
for(let i = 0, length = history.length; i < length; ++i) {
const mid = history[i];
const message = this.appMessagesManager.getMessageByPeer(dialog.peerId, mid);
const message: MyMessage = this.appMessagesManager.getMessageFromStorage(messagesStorage, mid);
if(!message.pFlags.is_outgoing) {
incomingMessage = message;
@ -339,7 +363,7 @@ export default class DialogsStorage {
if(fromId !== dialog.peerId) {
this.appStateManager.requestPeer(fromId, 'topMessage_' + dialog.peerId, 1);
}
break;
}
}
@ -372,24 +396,29 @@ export default class DialogsStorage {
if(pos !== -1) {
dialogs.splice(pos, 1);
}
//if(!this.dialogs[dialog.peerId]) {
this.dialogs[dialog.peerId] = dialog;
this.setDialogToState(dialog);
//}
//}
// let pos: number;
if(offsetDate &&
!dialog.pFlags.pinned &&
(!this.dialogsOffsetDate[dialog.folder_id] || offsetDate < this.dialogsOffsetDate[dialog.folder_id])) {
if(pos !== -1) {
// So the dialog jumped to the last position
if(pos !== -1) { // So the dialog jumped to the last position
// dialogs.splice(pos, 1);
return false;
}
this.dialogsOffsetDate[dialog.folder_id] = offsetDate;
}
insertInDescendSortedArray(dialogs, dialog, 'index', pos);
/* const newPos = */insertInDescendSortedArray(dialogs, dialog, 'index', pos);
/* if(pos !== -1 && pos !== newPos) {
rootScope.dispatchEvent('dialog_order', {dialog, pos: newPos});
} */
}
public dropDialog(peerId: number): [Dialog, number] | [] {
@ -600,12 +629,13 @@ export default class DialogsStorage {
}
public getDialogs(query = '', offsetIndex?: number, limit = 20, folderId = 0, skipMigrated = false): {
cached: boolean;
cached: boolean,
promise: Promise<{
dialogs: Dialog[];
count: number;
isEnd: boolean;
}>;
dialogs: Dialog[],
count: number,
isTopEnd: boolean,
isEnd: boolean
}>
} {
if(folderId > 1) {
const fillContactsResult = this.appUsersManager.fillContacts();
@ -659,12 +689,14 @@ export default class DialogsStorage {
}
const loadedAll = this.isDialogsLoaded(realFolderId);
if(query || loadedAll || curDialogStorage.length >= offset + limit) {
if(query || loadedAll || curDialogStorage.length >= (offset + limit)) {
const dialogs = curDialogStorage.slice(offset, offset + limit);
return {
cached: true,
promise: Promise.resolve({
dialogs: curDialogStorage.slice(offset, offset + limit),
dialogs,
count: loadedAll ? curDialogStorage.length : null,
isTopEnd: curDialogStorage.length && ((dialogs[0] && dialogs[0] === curDialogStorage[0]) || curDialogStorage[0][indexStr] < offsetIndex),
isEnd: loadedAll && (offset + limit) >= curDialogStorage.length
})
};
@ -689,9 +721,11 @@ export default class DialogsStorage {
//this.log.warn(offset, offset + limit, curDialogStorage.dialogs.length, this.dialogs.length);
const dialogs = curDialogStorage.slice(offset, offset + limit);
return {
dialogs: curDialogStorage.slice(offset, offset + limit),
dialogs,
count: result.count === undefined ? curDialogStorage.length : result.count,
isTopEnd: curDialogStorage.length && ((dialogs[0] && dialogs[0] === curDialogStorage[0]) || curDialogStorage[0][indexStr] < offsetIndex),
// isEnd: this.isDialogsLoaded(realFolderId) && (offset + limit) >= curDialogStorage.length
isEnd: result.isEnd
};

View File

@ -632,6 +632,10 @@ $bubble-margin: .25rem;
> .thumbnail {
opacity: .8;
&.fade-in {
animation: thumbnail-fade-in-opacity .2s ease-in-out forwards;
}
}
}
}

View File

@ -12,6 +12,10 @@
.media-photo {
border-radius: inherit;
&.thumbnail {
left: 0;
}
}
&-ico {

View File

@ -931,6 +931,15 @@ img.emoji {
}
}
/* .fade-in-new {
opacity: 1;
transition: opacity .2s ease-in-out;
&.not-yet {
opacity: 0;
}
} */
.show-more {
padding-top: 13px;
padding-bottom: 13px;
@ -1169,12 +1178,6 @@ middle-ellipsis-element {
.media-sticker,
.media-round,
.media-poster {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
@include animation-level(2) {
&.fade-in {
animation: fade-in-opacity .2s ease-in-out forwards;
@ -1184,14 +1187,14 @@ middle-ellipsis-element {
animation: fade-out-opacity .2s ease-in-out forwards;
}
}
}
.media-photo.thumbnail {
@include animation-level(2) {
&.fade-in {
animation: thumbnail-fade-in-opacity .2s ease-in-out forwards;
}
}
// & ~ & {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
// }
}
.media-video {