From 09d0030de4143d279e0b1648c6edcd5e0657aba1 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Fri, 16 Sep 2022 21:44:10 +0400 Subject: [PATCH] Fix bugged sticker viewer Fix replies layout with custom emoji Fix wrapping some custom emojis Fix opening restricted chat Fix wrapping encoded spoiler Fix playing custom emoji interactive animation Fix loading archived chatlist Fix reaction effect at top left corner --- src/components/appMediaViewerBase.ts | 4 ++- src/components/chat/bubbles.ts | 7 +++-- src/components/chat/reaction.ts | 26 ++++++++++++------- src/components/chat/selection.ts | 12 ++++----- src/components/chat/topbar.ts | 9 +++++-- .../sidebarLeft/tabs/archivedTab.ts | 4 +-- src/components/stickerViewer.ts | 3 ++- src/components/wrappers/sticker.ts | 4 +-- src/components/wrappers/stickerAnimation.ts | 12 ++++++++- src/helpers/eventListenerBase.ts | 4 +-- src/lib/appManagers/appDialogsManager.ts | 3 ++- src/lib/richTextProcessor/wrapRichText.ts | 10 ++++--- src/scss/partials/_chatBubble.scss | 6 ++++- 13 files changed, 69 insertions(+), 35 deletions(-) diff --git a/src/components/appMediaViewerBase.ts b/src/components/appMediaViewerBase.ts index 86cc6c142..2675f98b7 100644 --- a/src/components/appMediaViewerBase.ts +++ b/src/components/appMediaViewerBase.ts @@ -1168,7 +1168,9 @@ export default class AppMediaViewerBase< } } - renderImageFromUrl(el, url); + if((el as HTMLImageElement).src !== url) { + renderImageFromUrl(el, url); + } // ! костыль, но он тут даже и не нужен if(el.classList.contains('thumbnail') && el.parentElement.classList.contains('media-container-aspecter')) { diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index c4bc52f94..37b978f7f 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -2328,7 +2328,7 @@ export default class ChatBubbles { // * if it's a start, then scroll to start of the group if(bubble && position !== 'end') { const item = this.bubbleGroups.getItemByBubble(bubble); - if(item.group.firstItem === item && whichChild(item.group.container) === (this.stickyIntersector ? STICKY_OFFSET : 1)) { + if(item && item.group.firstItem === item && whichChild(item.group.container) === (this.stickyIntersector ? STICKY_OFFSET : 1)) { const dateGroup = item.group.container.parentElement; // if(whichChild(dateGroup) === 0) { fallbackToElementStartWhenCentering = dateGroup; @@ -3600,7 +3600,8 @@ export default class ChatBubbles { loadPromises, lazyLoadQueue: this.lazyLoadQueue, customEmojiSize, - middleware + middleware, + animationGroup: CHAT_ANIMATION_GROUP }); let canHaveTail = true; @@ -4521,6 +4522,8 @@ export default class ChatBubbles { if(isFooter) { canHaveTail = true; + } else { + bubble.classList.add('with-beside-replies'); } } diff --git a/src/components/chat/reaction.ts b/src/components/chat/reaction.ts index e47906a4b..2b3ebe177 100644 --- a/src/components/chat/reaction.ts +++ b/src/components/chat/reaction.ts @@ -16,6 +16,7 @@ import SetTransition from '../singleTransition'; import StackedAvatars from '../stackedAvatars'; import {wrapSticker, wrapStickerAnimation} from '../wrappers'; import {Awaited} from '../../types'; +import noop from '../../helpers/noop'; const CLASS_NAME = 'reaction'; const TAG_NAME = CLASS_NAME + '-element'; @@ -186,26 +187,33 @@ export default class ReactionElement extends HTMLElement { skipRatio: 1, play: false, managers: this.managers - }).stickerPromise + }).stickerPromise.catch(noop) ]).then(([iconPlayer, aroundPlayer]) => { const remove = () => { // if(!isInDOM(div)) return; - fastRaf(() => { - // if(!isInDOM(div)) return; - iconPlayer.remove(); - div.remove(); - this.stickerContainer.classList.remove('has-animation'); - }); + iconPlayer.remove(); + div.remove(); + this.stickerContainer.classList.remove('has-animation'); + }; + + if(!aroundPlayer) { + remove(); + return; + } + + const removeOnFrame = () => { + // if(!isInDOM(div)) return; + fastRaf(remove); }; iconPlayer.addEventListener('enterFrame', (frameNo) => { if(frameNo === iconPlayer.maxFrame) { if(this.wrapStickerPromise) { // wait for fade in animation this.wrapStickerPromise.then(() => { - setTimeout(remove, 1e3); + setTimeout(removeOnFrame, 1e3); }); } else { - remove(); + removeOnFrame(); } } }); diff --git a/src/components/chat/selection.ts b/src/components/chat/selection.ts index 5ea9c0b5f..17dd370ab 100644 --- a/src/components/chat/selection.ts +++ b/src/components/chat/selection.ts @@ -14,7 +14,6 @@ import ButtonIcon from '../buttonIcon'; import CheckboxField from '../checkboxField'; import PopupDeleteMessages from '../popups/deleteMessages'; import PopupForward from '../popups/forward'; -import {toast} from '../toast'; import SetTransition from '../singleTransition'; import ListenerSetter from '../../helpers/listenerSetter'; import PopupSendNow from '../popups/sendNow'; @@ -26,7 +25,6 @@ import blurActiveElement from '../../helpers/dom/blurActiveElement'; import cancelEvent from '../../helpers/dom/cancelEvent'; import cancelSelection from '../../helpers/dom/cancelSelection'; import getSelectedText from '../../helpers/dom/getSelectedText'; -import rootScope from '../../lib/rootScope'; import replaceContent from '../../helpers/dom/replaceContent'; import AppSearchSuper from '../appSearchSuper.'; import isInDOM from '../../helpers/dom/isInDOM'; @@ -58,7 +56,7 @@ class AppSelection extends EventListenerBase<{ protected isScheduled: boolean; protected listenElement: HTMLElement; - protected onToggleSelection: (forwards: boolean, animate: boolean) => void; + protected onToggleSelection: (forwards: boolean, animate: boolean) => void | Promise; protected onUpdateContainer: (cantForward: boolean, cantDelete: boolean, cantSend: boolean) => void; protected onCancelSelection: () => void; protected toggleByMid: (peerId: PeerId, mid: number) => void; @@ -361,7 +359,7 @@ class AppSelection extends EventListenerBase<{ if(cantForward && cantDelete) break; } - this.onUpdateContainer && this.onUpdateContainer(cantForward, cantDelete, cantSend); + this.onUpdateContainer?.(cantForward, cantDelete, cantSend); } public toggleSelection(toggleCheckboxes = true, forceSelection = false) { @@ -404,7 +402,7 @@ class AppSelection extends EventListenerBase<{ blurActiveElement(); const forwards = !!size || forceSelection; - this.onToggleSelection && this.onToggleSelection(forwards, !this.doNotAnimate); + const toggleResult = this.onToggleSelection?.(forwards, !this.doNotAnimate); if(!IS_MOBILE_SAFARI) { if(forwards) { @@ -420,7 +418,7 @@ class AppSelection extends EventListenerBase<{ } if(forceSelection) { - this.updateContainer(forceSelection); + (toggleResult || Promise.resolve()).then(() => this.updateContainer(forceSelection)); } return true; @@ -972,7 +970,7 @@ export default class ChatSelection extends AppSelection { replaceContent(this.selectionCountEl, i18n('messages', [this.length()])); this.selectionSendNowBtn && this.selectionSendNowBtn.toggleAttribute('disabled', cantSend); this.selectionForwardBtn && this.selectionForwardBtn.toggleAttribute('disabled', cantForward); - this.selectionDeleteBtn.toggleAttribute('disabled', cantDelete); + this.selectionDeleteBtn && this.selectionDeleteBtn.toggleAttribute('disabled', cantDelete); }; protected onCancelSelection = async() => { diff --git a/src/components/chat/topbar.ts b/src/components/chat/topbar.ts index afc719e33..2a56ad0a6 100644 --- a/src/components/chat/topbar.ts +++ b/src/components/chat/topbar.ts @@ -708,7 +708,7 @@ export default class ChatTopbar { return () => { this.btnMute && this.btnMute.classList.toggle('hide', !isBroadcast); if(this.btnJoin) { - if(isAnyChat) { + if(isAnyChat && !this.chat.isRestricted) { replaceContent(this.btnJoin, i18n(isBroadcast ? 'Chat.Subscribe' : 'ChannelJoin')); this.btnJoin.classList.toggle('hide', !chat?.pFlags?.left); } else { @@ -783,10 +783,15 @@ export default class ChatTopbar { } else if(this.chat.type === 'discussion') { if(count === undefined) { const result = await this.managers.acknowledged.appMessagesManager.getHistory(peerId, 0, 1, 0, this.chat.threadId); + if(!middleware()) return; if(result.cached) { const historyResult = await result.result; + if(!middleware()) return; count = historyResult.count; - } else result.result.then((historyResult) => this.setTitle(historyResult.count)); + } else result.result.then((historyResult) => { + if(!middleware()) return; + this.setTitle(historyResult.count); + }); } if(count === undefined) titleEl = i18n('Loading'); diff --git a/src/components/sidebarLeft/tabs/archivedTab.ts b/src/components/sidebarLeft/tabs/archivedTab.ts index 0546307e2..f0b84476f 100644 --- a/src/components/sidebarLeft/tabs/archivedTab.ts +++ b/src/components/sidebarLeft/tabs/archivedTab.ts @@ -32,8 +32,8 @@ export default class AppArchivedTab extends SliderSuperTab { const scrollable = appDialogsManager.scrollables[AppArchivedTab.filterId]; this.scrollable.container.replaceWith(scrollable.container); - this.scrollable = scrollable; - + // ! DO NOT UNCOMMENT NEXT LINE - chats will stop loading on scroll after closing the tab + // this.scrollable = scrollable; return appDialogsManager.setFilterIdAndChangeTab(AppArchivedTab.filterId).then(({cached, renderPromise}) => { if(cached) { return renderPromise; diff --git a/src/components/stickerViewer.ts b/src/components/stickerViewer.ts index 391d01d48..a7d428734 100644 --- a/src/components/stickerViewer.ts +++ b/src/components/stickerViewer.ts @@ -180,6 +180,7 @@ export default function attachStickerViewerListeners({listenTo, listenerSetter, player.addEventListener('enterFrame', c); }); + if(!middleware()) return; player.pause(); } else if(player instanceof HTMLVideoElement) { player.currentTime = (mediaContainer.querySelector('video') as HTMLVideoElement).currentTime; @@ -283,7 +284,6 @@ export default function attachStickerViewerListeners({listenTo, listenerSetter, const onMousePreMove = (e: MouseEvent) => { if(!findUpAsChild(e.target as HTMLElement, mediaContainer)) { - document.removeEventListener('mousemove', onMousePreMove); onMouseUp(); } }; @@ -303,6 +303,7 @@ export default function attachStickerViewerListeners({listenTo, listenerSetter, attachClickEvent(document.body, cancelEvent, {capture: true, once: true}); } + document.removeEventListener('mousemove', onMousePreMove); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp, {capture: true}); }; diff --git a/src/components/wrappers/sticker.ts b/src/components/wrappers/sticker.ts index e96df797d..f44e746f7 100644 --- a/src/components/wrappers/sticker.ts +++ b/src/components/wrappers/sticker.ts @@ -700,8 +700,8 @@ export async function onEmojiStickerClick({event, container, managers, peerId, m data.a.length = 0; }, 1000, false); - const animation = lottieLoader.getAnimation(container); - if(animation.paused) { + const animation = !container.classList.contains('custom-emoji') ? lottieLoader.getAnimation(container) : undefined; + if(animation?.paused) { const doc = await managers.appStickersManager.getAnimatedEmojiSoundDocument(emoji); if(doc) { const audio = document.createElement('audio'); diff --git a/src/components/wrappers/stickerAnimation.ts b/src/components/wrappers/stickerAnimation.ts index 9f6c3c275..a17b31a40 100644 --- a/src/components/wrappers/stickerAnimation.ts +++ b/src/components/wrappers/stickerAnimation.ts @@ -7,7 +7,8 @@ import IS_VIBRATE_SUPPORTED from '../../environment/vibrateSupport'; import assumeType from '../../helpers/assumeType'; import isInDOM from '../../helpers/dom/isInDOM'; -import {Middleware} from '../../helpers/middleware'; +import makeError from '../../helpers/makeError'; +import {getMiddleware, Middleware} from '../../helpers/middleware'; import throttleWithRaf from '../../helpers/schedulers/throttleWithRaf'; import windowSize from '../../helpers/windowSize'; import {PhotoSize, VideoSize} from '../../layer'; @@ -53,11 +54,15 @@ export default function wrapStickerAnimation({ let animation: RLottiePlayer; const unmountAnimation = () => { + middlewareHelper.clean(); animation?.remove(); animationDiv.remove(); appImManager.chat.bubbles.scrollable.container.removeEventListener('scroll', onScroll); }; + const middlewareHelper = middleware?.create() ?? getMiddleware(); + middleware = middlewareHelper.get(); + const stickerPromise = wrapSticker({ div: animationDiv, doc, @@ -74,6 +79,11 @@ export default function wrapStickerAnimation({ fullThumb }).then(({render}) => render).then((_animation) => { assumeType(_animation); + if(!middleware()) { + _animation.remove(); + throw makeError('MIDDLEWARE'); + } + animation = _animation; animation.addEventListener('enterFrame', (frameNo) => { if((!loopEffect && frameNo === animation.maxFrame) || !isInDOM(target)) { diff --git a/src/helpers/eventListenerBase.ts b/src/helpers/eventListenerBase.ts index 49dfcf606..79c63f73b 100644 --- a/src/helpers/eventListenerBase.ts +++ b/src/helpers/eventListenerBase.ts @@ -84,7 +84,7 @@ export default class EventListenerBase } public addEventListener(name: T, callback: Listeners[T], options?: boolean | AddEventListenerOptions) { - (this.listeners[name] ?? (this.listeners[name] = [])).push({callback, options}); // ! add before because if you don't, you won't be able to delete it from callback + (this.listeners[name] ??= []).push({callback, options}); // ! add before because if you don't, you won't be able to delete it from callback if(this.listenerResults.hasOwnProperty(name)) { callback(...this.listenerResults[name]); @@ -108,7 +108,7 @@ export default class EventListenerBase public removeEventListener(name: T, callback: Listeners[T], options?: boolean | AddEventListenerOptions) { if(this.listeners[name]) { - findAndSplice(this.listeners[name], l => l.callback === callback); + findAndSplice(this.listeners[name], (l) => l.callback === callback); } // e.remove(this, name, callback); } diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index 7d69f8679..2f5cb6af5 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -1003,6 +1003,7 @@ export class AppDialogsManager { let loadCount = windowSize.height / 72 * 1.25 | 0; let offsetIndex = 0; + const doNotRenderChatList = this.doNotRenderChatList; // cache before awaits const {index: currentOffsetIndex} = this.getOffsetIndex(side); if(currentOffsetIndex) { if(side === 'top') { @@ -1045,7 +1046,7 @@ export class AppDialogsManager { const a = await getConversationsResult; const result = await a.result; - if(this.loadDialogsRenderPromise !== renderPromise || this.doNotRenderChatList) { + if(this.loadDialogsRenderPromise !== renderPromise || doNotRenderChatList) { reject(); cachedInfoPromise.reject(); return; diff --git a/src/lib/richTextProcessor/wrapRichText.ts b/src/lib/richTextProcessor/wrapRichText.ts index fa740885e..da270ccc0 100644 --- a/src/lib/richTextProcessor/wrapRichText.ts +++ b/src/lib/richTextProcessor/wrapRichText.ts @@ -437,7 +437,6 @@ export default function wrapRichText(text: string, options: Partial<{ }, voodoo?: boolean, customEmojis?: {[docId: DocId]: CustomEmojiElement[]}, - wrappingSpoiler?: boolean, loadPromises?: Promise[], middleware?: Middleware, @@ -478,7 +477,8 @@ export default function wrapRichText(text: string, options: Partial<{ } } else if((entity.offset + entity.length) > textLength) { entity = copy(entity); - entity.length = entity.offset + entity.length - textLength; + // entity.length = entity.offset + entity.length - textLength; + entity.length = textLength - entity.offset; } if(entity.length) { @@ -621,7 +621,7 @@ export default function wrapRichText(text: string, options: Partial<{ break; } - if(nextEntity?._ === 'messageEntityEmoji') { + while(nextEntity?._ === 'messageEntityEmoji' && nextEntity.offset < endOffset) { ++nasty.i; nasty.lastEntity = nextEntity; nasty.usedLength += nextEntity.length; @@ -808,7 +808,9 @@ export default function wrapRichText(text: string, options: Partial<{ const encoded = encodeSpoiler(nasty.text, entity); nasty.text = encoded.text; partText = encoded.entityText; - nasty.usedLength += partText.length; + if(endPartOffset !== endOffset) { + nasty.usedLength += endOffset - endPartOffset; + } let n: MessageEntity; for(; n = entities[nasty.i + 1], n && n.offset < endOffset;) { // nasty.usedLength += n.length; diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index 041ee328e..a2d44c2b9 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -1753,7 +1753,7 @@ $bubble-beside-button-width: 38px; } } - &.with-replies:not(.sticker) .message { + &.with-replies:not(.sticker):not(.with-beside-replies) .message { bottom: 55px; } @@ -1786,6 +1786,10 @@ $bubble-beside-button-width: 38px; border-bottom-left-radius: 0; border-bottom-right-radius: 0; } + + &.with-beside-replies .bubble-content { + min-height: 5.5rem; + } .time { visibility: hidden; // * can't use color transparent here, because in name can be emoji