From f17e88cccbabcbb766d86f6de445dc0b5772fc88 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Sun, 26 Jun 2022 18:10:42 +0200 Subject: [PATCH] Fix scheduled messages --- src/components/chat/bubbleGroups.ts | 61 +++++---- src/components/chat/bubbles.ts | 193 +++++++++++++++------------- src/components/chat/contextMenu.ts | 4 +- src/components/chat/input.ts | 6 +- 4 files changed, 144 insertions(+), 120 deletions(-) diff --git a/src/components/chat/bubbleGroups.ts b/src/components/chat/bubbleGroups.ts index ad699026..69ddee1e 100644 --- a/src/components/chat/bubbleGroups.ts +++ b/src/components/chat/bubbleGroups.ts @@ -94,6 +94,10 @@ class BubbleGroup { return this.items[this.items.length - 1]; } + get lastTimestamp() { + return this.lastItem.timestamp; + } + get lastMid() { return this.lastItem.mid; } @@ -143,33 +147,11 @@ class BubbleGroup { insertItem(item: GroupItem) { const {items} = this; - const {timestamp, mid} = item; - if(this.chat.type === 'scheduled') { - let foundMidOnSameTimestamp = 0; - let i = 0, length = items.length; - for(; i < length; ++i) { - const {timestamp: _timestamp, mid: _mid} = items[i]; - - if(timestamp < _timestamp) { - break; - } else if(timestamp === _timestamp) { - foundMidOnSameTimestamp = _mid; - } - - if(foundMidOnSameTimestamp && mid < foundMidOnSameTimestamp) { - break; - } - } - - items.splice(i, 0, item); - } else { - // insertInDescendSortedArray(items, item, 'mid'); - insertInDescendSortedArray(items, item, 'groupMid'); - } + insertInDescendSortedArray(items, item, this.groups.sortGroupItemsKey); item.group = this; if(items.length === 1) { - insertInDescendSortedArray(this.groups.groups, this, 'lastMid'); + this.groups.insertGroup(this); } } @@ -231,7 +213,7 @@ class BubbleGroup { return; } - const dateContainer = this.chat.bubbles.getDateContainerByTimestamp(this.firstTimestamp); + const dateContainer = this.chat.bubbles.getDateContainerByTimestamp(this.dateTimestamp / 1000); // const idx = this.groups.indexOf(group); const dateGroups = this.groups.groups.filter((_group) => _group.dateTimestamp === this.dateTimestamp); const dateGroupsLength = dateGroups.length; @@ -276,9 +258,14 @@ export default class BubbleGroups { private itemsMap: Map = new Map(); public groups: Array = []; // descend sorted private newGroupDiff = 121; // * 121 in scheduled messages + private sortItemsKey: Extract; + private sortGroupsKey: Extract; + public sortGroupItemsKey: Extract; constructor(private chat: Chat) { - + this.sortItemsKey = chat.type === 'scheduled' ? 'timestamp' : 'mid'; + this.sortGroupsKey = chat.type === 'scheduled' ? 'lastTimestamp' : 'lastMid'; + this.sortGroupItemsKey = /* chat.type === 'scheduled' ? 'timestamp' : */'groupMid'; } removeItem(item: GroupItem) { @@ -369,7 +356,7 @@ export default class BubbleGroups { // item.group.insertItem(item); indexOfAndSplice(this.itemsArr, item); - insertInDescendSortedArray(this.itemsArr, item, 'mid'); + this.insertItemToArray(item, this.itemsArr); } changeItemBubble(item: GroupItem, bubble: HTMLElement) { @@ -405,7 +392,7 @@ export default class BubbleGroups { findGroupSiblingByItem(item: GroupItem, items: GroupItem[]) { items = items.slice(); - const idx = insertInDescendSortedArray(items, item, 'mid'); + const idx = this.insertItemToArray(item, items); // return this.findGroupSiblingInSiblings(item, this.getSiblingsAtIndex(idx, items)); return this.findGroupSiblingInItems(item, items, idx); } @@ -436,8 +423,16 @@ export default class BubbleGroups { this.addItemToCache(item); } + insertItemToArray(item: GroupItem, array: GroupItem[]) { + return insertInDescendSortedArray(array, item, this.sortItemsKey); + } + + insertGroup(group: BubbleGroup) { + return insertInDescendSortedArray(this.groups, group, this.sortGroupsKey); + } + addItemToCache(item: GroupItem) { - insertInDescendSortedArray(this.itemsArr, item, 'mid'); + this.insertItemToArray(item, this.itemsArr); this.itemsMap.set(item.bubble, item); } @@ -463,10 +458,11 @@ export default class BubbleGroups { const {dateTimestamp} = this.chat.bubbles.getDateForDateContainer(timestamp); const item: GroupItem = { mid, - groupMid: mid, + groupMid: this.chat.type === 'scheduled' ? +`${(timestamp * 1000 - dateTimestamp) / 1000}.${mid}` : mid, fromId: this.getMessageFromId(message), bubble, - timestamp, + // timestamp: this.chat.type === 'scheduled' ? +`${(timestamp * 1000 - dateTimestamp) / 1000}.${mid}` : timestamp, + timestamp, dateTimestamp, mounted: false, single, @@ -505,7 +501,8 @@ export default class BubbleGroups { } prepareForGrouping(bubble: HTMLElement, message: MyMessage) { - if(this.getItemByBubble(bubble)) { + const foundItem = this.getItemByBubble(bubble); + if(foundItem) { // should happen only on edit debugger; return; } diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index f14f4fba..83cf9b03 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -284,7 +284,7 @@ export default class ChatBubbles { // will call when sent for update pos this.listenerSetter.add(rootScope)('history_update', async({storageKey, mid, message}) => { - if(this.chat.messagesStorageKey !== storageKey) { + if(this.chat.messagesStorageKey !== storageKey || this.chat.type === 'scheduled') { return; } @@ -741,95 +741,95 @@ export default class ChatBubbles { }); } - if(!IS_MOBILE && this.chat.type !== 'pinned') { - this.listenerSetter.add(container)('dblclick', async(e) => { - if(this.chat.selection.isSelecting || - !(await this.chat.canSend())) { - return; - } - - const target = e.target as HTMLElement; - const bubble = target.classList.contains('bubble') ? - target : - (target.classList.contains('document-selection') ? target.parentElement : null); - if(bubble && !bubble.classList.contains('bubble-first')) { - const mid = +bubble.dataset.mid; - const message = await this.chat.getMessage(mid); - if(message.pFlags.is_outgoing) { + if(this.chat.type !== 'pinned' && this.chat.type !== 'scheduled') { + if(!IS_MOBILE) { + this.listenerSetter.add(container)('dblclick', async(e) => { + if(this.chat.selection.isSelecting || + !(await this.chat.canSend())) { return; } - this.chat.input.initMessageReply(mid); - } - }); - } - - if(IS_TOUCH_SUPPORTED) { - const className = 'is-gesturing-reply'; - const MAX = 64; - const replyAfter = MAX * .75; - let shouldReply = false; - let target: HTMLElement; - let icon: HTMLElement; - handleHorizontalSwipe({ - element: container, - verifyTouchTarget: async(e) => { - if(this.chat.selection.isSelecting || !(await this.chat.canSend())) { - return false; - } - - // cancelEvent(e); - target = findUpClassName(e.target, 'bubble'); - if(target) { - SetTransition(target, className, true, 250); - void target.offsetLeft; // reflow - - if(!icon) { - icon = document.createElement('span'); - icon.classList.add('tgico-reply_filled', 'bubble-gesture-reply-icon'); - } else { - icon.classList.remove('is-visible'); - icon.style.opacity = ''; + const target = e.target as HTMLElement; + const bubble = target.classList.contains('bubble') ? + target : + (target.classList.contains('document-selection') ? target.parentElement : null); + if(bubble && !bubble.classList.contains('bubble-first')) { + const mid = +bubble.dataset.mid; + const message = await this.chat.getMessage(mid); + if(message.pFlags.is_outgoing) { + return; } - - target/* .querySelector('.bubble-content') */.append(icon); - } - - return !!target; - }, - onSwipe: (xDiff, yDiff) => { - shouldReply = xDiff >= replyAfter; - - if(shouldReply && !icon.classList.contains('is-visible')) { - icon.classList.add('is-visible'); + + this.chat.input.initMessageReply(mid); } - icon.style.opacity = '' + Math.min(1, xDiff / replyAfter); - - const x = -Math.max(0, Math.min(MAX, xDiff)); - target.style.transform = `translateX(${x}px)`; - cancelContextMenuOpening(); - }, - onReset: () => { - const _target = target; - SetTransition(_target, className, false, 250, () => { - if(icon.parentElement === _target) { - icon.classList.remove('is-visible'); - icon.remove(); + }); + } else if(IS_TOUCH_SUPPORTED) { + const className = 'is-gesturing-reply'; + const MAX = 64; + const replyAfter = MAX * .75; + let shouldReply = false; + let target: HTMLElement; + let icon: HTMLElement; + handleHorizontalSwipe({ + element: container, + verifyTouchTarget: async(e) => { + if(this.chat.selection.isSelecting || !(await this.chat.canSend())) { + return false; } - }); - - fastRaf(() => { - _target.style.transform = ``; - - if(shouldReply) { - const {mid} = _target.dataset; - this.chat.input.initMessageReply(+mid); - shouldReply = false; + + // cancelEvent(e); + target = findUpClassName(e.target, 'bubble'); + if(target) { + SetTransition(target, className, true, 250); + void target.offsetLeft; // reflow + + if(!icon) { + icon = document.createElement('span'); + icon.classList.add('tgico-reply_filled', 'bubble-gesture-reply-icon'); + } else { + icon.classList.remove('is-visible'); + icon.style.opacity = ''; + } + + target/* .querySelector('.bubble-content') */.append(icon); } - }); - }, - listenerOptions: {capture: true} - }); + + return !!target; + }, + onSwipe: (xDiff, yDiff) => { + shouldReply = xDiff >= replyAfter; + + if(shouldReply && !icon.classList.contains('is-visible')) { + icon.classList.add('is-visible'); + } + icon.style.opacity = '' + Math.min(1, xDiff / replyAfter); + + const x = -Math.max(0, Math.min(MAX, xDiff)); + target.style.transform = `translateX(${x}px)`; + cancelContextMenuOpening(); + }, + onReset: () => { + const _target = target; + SetTransition(_target, className, false, 250, () => { + if(icon.parentElement === _target) { + icon.classList.remove('is-visible'); + icon.remove(); + } + }); + + fastRaf(() => { + _target.style.transform = ``; + + if(shouldReply) { + const {mid} = _target.dataset; + this.chat.input.initMessageReply(+mid); + shouldReply = false; + } + }); + }, + listenerOptions: {capture: true} + }); + } } } @@ -2121,14 +2121,15 @@ export default class ChatBubbles { this.deleteEmptyDateGroups(); if(!ignoreOnScroll) { - this.onScroll(); + this.scrollable.onScroll(); + // this.onScroll(); } } private setTopPadding(middleware = this.getMiddleware()) { let isPaddingNeeded = false; let setPaddingTo: HTMLElement; - if(!this.isTopPaddingSet) { + if(!this.isTopPaddingSet && this.chat.type !== 'scheduled') { const {clientHeight, scrollHeight} = this.scrollable.container; isPaddingNeeded = clientHeight === scrollHeight; /* const firstEl = this.chatInner.firstElementChild as HTMLElement; @@ -3236,6 +3237,20 @@ export default class ChatBubbles { }/* & { unmountIfFound?: boolean } */>) { + let modifiedGroups: typeof groups; + + if(this.chat.type === 'scheduled') { + modifiedGroups = new Set(); + items.forEach(({bubble, message}) => { + const item = this.bubbleGroups.getItemByBubble(bubble); + const group = item?.group; + if(group && item.message.date !== message.date) { + this.bubbleGroups.removeItem(item); + modifiedGroups.add(group); + } + }); + } + items.forEach(({bubble, message}) => { this.bubbleGroups.prepareForGrouping(bubble, message); }); @@ -3245,11 +3260,17 @@ export default class ChatBubbles { const avatarPromises = Array.from(groups).map((group) => { if(group.avatar) return; const firstItem = group.firstItem; - if(this.chat.isAvatarNeeded(firstItem.message)) { + if(firstItem && this.chat.isAvatarNeeded(firstItem.message)) { return group.createAvatar(firstItem.message); } }).filter(Boolean); + if(modifiedGroups) { + for(const group of modifiedGroups) { + groups.add(group); + } + } + return { groups: [...groups], avatarPromises diff --git a/src/components/chat/contextMenu.ts b/src/components/chat/contextMenu.ts index f676ab25..96e889f0 100644 --- a/src/components/chat/contextMenu.ts +++ b/src/components/chat/contextMenu.ts @@ -39,6 +39,7 @@ import { attachContextMenuListener } from "../../helpers/dom/attachContextMenuLi import filterAsync from "../../helpers/array/filterAsync"; import appDownloadManager from "../../lib/appManagers/appDownloadManager"; import { SERVICE_PEER_ID } from "../../lib/mtproto/mtproto_config"; +import { MessagesStorageKey } from "../../lib/appManagers/appMessagesManager"; export default class ChatContextMenu { private buttons: (ButtonMenuItemOptions & {verify: () => boolean | Promise, notDirect?: () => boolean, withSelection?: true, isSponsored?: true})[]; @@ -340,8 +341,9 @@ export default class ChatContextMenu { } for(const [peerId, mids] of this.chat.selection.selectedMids) { + const storageKey: MessagesStorageKey = `${peerId}_${this.chat.type === 'scheduled' ? 'scheduled' : 'history'}`; for(const mid of mids) { - const message = (await this.managers.appMessagesManager.getMessageByPeer(peerId, mid)) as Message.message; + const message = (await this.managers.appMessagesManager.getMessageFromStorage(storageKey, mid)) as Message.message; if(!!message.message) { return true; } diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index 65678a36..5f8c0b07 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -2384,7 +2384,11 @@ export default class ChatInput { clearDraft: true }); - this.onMessageSent(false, false); + if(this.chat.type === 'scheduled') { + this.onMessageSent(true); + } else { + this.onMessageSent(false, false); + } // this.onMessageSent(); }