Browse Source

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
master
Eduard Kuzmenko 2 years ago committed by r4sas
parent
commit
0abf74f57f
  1. 4
      src/components/appMediaViewerBase.ts
  2. 7
      src/components/chat/bubbles.ts
  3. 26
      src/components/chat/reaction.ts
  4. 12
      src/components/chat/selection.ts
  5. 9
      src/components/chat/topbar.ts
  6. 4
      src/components/sidebarLeft/tabs/archivedTab.ts
  7. 3
      src/components/stickerViewer.ts
  8. 4
      src/components/wrappers/sticker.ts
  9. 12
      src/components/wrappers/stickerAnimation.ts
  10. 4
      src/helpers/eventListenerBase.ts
  11. 3
      src/lib/appManagers/appDialogsManager.ts
  12. 10
      src/lib/richTextProcessor/wrapRichText.ts
  13. 6
      src/scss/partials/_chatBubble.scss

4
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')) { if(el.classList.contains('thumbnail') && el.parentElement.classList.contains('media-container-aspecter')) {

7
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 it's a start, then scroll to start of the group
if(bubble && position !== 'end') { if(bubble && position !== 'end') {
const item = this.bubbleGroups.getItemByBubble(bubble); 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; const dateGroup = item.group.container.parentElement;
// if(whichChild(dateGroup) === 0) { // if(whichChild(dateGroup) === 0) {
fallbackToElementStartWhenCentering = dateGroup; fallbackToElementStartWhenCentering = dateGroup;
@ -3600,7 +3600,8 @@ export default class ChatBubbles {
loadPromises, loadPromises,
lazyLoadQueue: this.lazyLoadQueue, lazyLoadQueue: this.lazyLoadQueue,
customEmojiSize, customEmojiSize,
middleware middleware,
animationGroup: CHAT_ANIMATION_GROUP
}); });
let canHaveTail = true; let canHaveTail = true;
@ -4521,6 +4522,8 @@ export default class ChatBubbles {
if(isFooter) { if(isFooter) {
canHaveTail = true; canHaveTail = true;
} else {
bubble.classList.add('with-beside-replies');
} }
} }

26
src/components/chat/reaction.ts

@ -16,6 +16,7 @@ import SetTransition from '../singleTransition';
import StackedAvatars from '../stackedAvatars'; import StackedAvatars from '../stackedAvatars';
import {wrapSticker, wrapStickerAnimation} from '../wrappers'; import {wrapSticker, wrapStickerAnimation} from '../wrappers';
import {Awaited} from '../../types'; import {Awaited} from '../../types';
import noop from '../../helpers/noop';
const CLASS_NAME = 'reaction'; const CLASS_NAME = 'reaction';
const TAG_NAME = CLASS_NAME + '-element'; const TAG_NAME = CLASS_NAME + '-element';
@ -186,26 +187,33 @@ export default class ReactionElement extends HTMLElement {
skipRatio: 1, skipRatio: 1,
play: false, play: false,
managers: this.managers managers: this.managers
}).stickerPromise }).stickerPromise.catch(noop)
]).then(([iconPlayer, aroundPlayer]) => { ]).then(([iconPlayer, aroundPlayer]) => {
const remove = () => { const remove = () => {
// if(!isInDOM(div)) return; // if(!isInDOM(div)) return;
fastRaf(() => { iconPlayer.remove();
// if(!isInDOM(div)) return; div.remove();
iconPlayer.remove(); this.stickerContainer.classList.remove('has-animation');
div.remove(); };
this.stickerContainer.classList.remove('has-animation');
}); if(!aroundPlayer) {
remove();
return;
}
const removeOnFrame = () => {
// if(!isInDOM(div)) return;
fastRaf(remove);
}; };
iconPlayer.addEventListener('enterFrame', (frameNo) => { iconPlayer.addEventListener('enterFrame', (frameNo) => {
if(frameNo === iconPlayer.maxFrame) { if(frameNo === iconPlayer.maxFrame) {
if(this.wrapStickerPromise) { // wait for fade in animation if(this.wrapStickerPromise) { // wait for fade in animation
this.wrapStickerPromise.then(() => { this.wrapStickerPromise.then(() => {
setTimeout(remove, 1e3); setTimeout(removeOnFrame, 1e3);
}); });
} else { } else {
remove(); removeOnFrame();
} }
} }
}); });

12
src/components/chat/selection.ts

@ -14,7 +14,6 @@ import ButtonIcon from '../buttonIcon';
import CheckboxField from '../checkboxField'; import CheckboxField from '../checkboxField';
import PopupDeleteMessages from '../popups/deleteMessages'; import PopupDeleteMessages from '../popups/deleteMessages';
import PopupForward from '../popups/forward'; import PopupForward from '../popups/forward';
import {toast} from '../toast';
import SetTransition from '../singleTransition'; import SetTransition from '../singleTransition';
import ListenerSetter from '../../helpers/listenerSetter'; import ListenerSetter from '../../helpers/listenerSetter';
import PopupSendNow from '../popups/sendNow'; import PopupSendNow from '../popups/sendNow';
@ -26,7 +25,6 @@ import blurActiveElement from '../../helpers/dom/blurActiveElement';
import cancelEvent from '../../helpers/dom/cancelEvent'; import cancelEvent from '../../helpers/dom/cancelEvent';
import cancelSelection from '../../helpers/dom/cancelSelection'; import cancelSelection from '../../helpers/dom/cancelSelection';
import getSelectedText from '../../helpers/dom/getSelectedText'; import getSelectedText from '../../helpers/dom/getSelectedText';
import rootScope from '../../lib/rootScope';
import replaceContent from '../../helpers/dom/replaceContent'; import replaceContent from '../../helpers/dom/replaceContent';
import AppSearchSuper from '../appSearchSuper.'; import AppSearchSuper from '../appSearchSuper.';
import isInDOM from '../../helpers/dom/isInDOM'; import isInDOM from '../../helpers/dom/isInDOM';
@ -58,7 +56,7 @@ class AppSelection extends EventListenerBase<{
protected isScheduled: boolean; protected isScheduled: boolean;
protected listenElement: HTMLElement; protected listenElement: HTMLElement;
protected onToggleSelection: (forwards: boolean, animate: boolean) => void; protected onToggleSelection: (forwards: boolean, animate: boolean) => void | Promise<void>;
protected onUpdateContainer: (cantForward: boolean, cantDelete: boolean, cantSend: boolean) => void; protected onUpdateContainer: (cantForward: boolean, cantDelete: boolean, cantSend: boolean) => void;
protected onCancelSelection: () => void; protected onCancelSelection: () => void;
protected toggleByMid: (peerId: PeerId, mid: number) => void; protected toggleByMid: (peerId: PeerId, mid: number) => void;
@ -361,7 +359,7 @@ class AppSelection extends EventListenerBase<{
if(cantForward && cantDelete) break; if(cantForward && cantDelete) break;
} }
this.onUpdateContainer && this.onUpdateContainer(cantForward, cantDelete, cantSend); this.onUpdateContainer?.(cantForward, cantDelete, cantSend);
} }
public toggleSelection(toggleCheckboxes = true, forceSelection = false) { public toggleSelection(toggleCheckboxes = true, forceSelection = false) {
@ -404,7 +402,7 @@ class AppSelection extends EventListenerBase<{
blurActiveElement(); blurActiveElement();
const forwards = !!size || forceSelection; const forwards = !!size || forceSelection;
this.onToggleSelection && this.onToggleSelection(forwards, !this.doNotAnimate); const toggleResult = this.onToggleSelection?.(forwards, !this.doNotAnimate);
if(!IS_MOBILE_SAFARI) { if(!IS_MOBILE_SAFARI) {
if(forwards) { if(forwards) {
@ -420,7 +418,7 @@ class AppSelection extends EventListenerBase<{
} }
if(forceSelection) { if(forceSelection) {
this.updateContainer(forceSelection); (toggleResult || Promise.resolve()).then(() => this.updateContainer(forceSelection));
} }
return true; return true;
@ -972,7 +970,7 @@ export default class ChatSelection extends AppSelection {
replaceContent(this.selectionCountEl, i18n('messages', [this.length()])); replaceContent(this.selectionCountEl, i18n('messages', [this.length()]));
this.selectionSendNowBtn && this.selectionSendNowBtn.toggleAttribute('disabled', cantSend); this.selectionSendNowBtn && this.selectionSendNowBtn.toggleAttribute('disabled', cantSend);
this.selectionForwardBtn && this.selectionForwardBtn.toggleAttribute('disabled', cantForward); this.selectionForwardBtn && this.selectionForwardBtn.toggleAttribute('disabled', cantForward);
this.selectionDeleteBtn.toggleAttribute('disabled', cantDelete); this.selectionDeleteBtn && this.selectionDeleteBtn.toggleAttribute('disabled', cantDelete);
}; };
protected onCancelSelection = async() => { protected onCancelSelection = async() => {

9
src/components/chat/topbar.ts

@ -708,7 +708,7 @@ export default class ChatTopbar {
return () => { return () => {
this.btnMute && this.btnMute.classList.toggle('hide', !isBroadcast); this.btnMute && this.btnMute.classList.toggle('hide', !isBroadcast);
if(this.btnJoin) { if(this.btnJoin) {
if(isAnyChat) { if(isAnyChat && !this.chat.isRestricted) {
replaceContent(this.btnJoin, i18n(isBroadcast ? 'Chat.Subscribe' : 'ChannelJoin')); replaceContent(this.btnJoin, i18n(isBroadcast ? 'Chat.Subscribe' : 'ChannelJoin'));
this.btnJoin.classList.toggle('hide', !chat?.pFlags?.left); this.btnJoin.classList.toggle('hide', !chat?.pFlags?.left);
} else { } else {
@ -783,10 +783,15 @@ export default class ChatTopbar {
} else if(this.chat.type === 'discussion') { } else if(this.chat.type === 'discussion') {
if(count === undefined) { if(count === undefined) {
const result = await this.managers.acknowledged.appMessagesManager.getHistory(peerId, 0, 1, 0, this.chat.threadId); const result = await this.managers.acknowledged.appMessagesManager.getHistory(peerId, 0, 1, 0, this.chat.threadId);
if(!middleware()) return;
if(result.cached) { if(result.cached) {
const historyResult = await result.result; const historyResult = await result.result;
if(!middleware()) return;
count = historyResult.count; 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'); if(count === undefined) titleEl = i18n('Loading');

4
src/components/sidebarLeft/tabs/archivedTab.ts

@ -32,8 +32,8 @@ export default class AppArchivedTab extends SliderSuperTab {
const scrollable = appDialogsManager.scrollables[AppArchivedTab.filterId]; const scrollable = appDialogsManager.scrollables[AppArchivedTab.filterId];
this.scrollable.container.replaceWith(scrollable.container); 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}) => { return appDialogsManager.setFilterIdAndChangeTab(AppArchivedTab.filterId).then(({cached, renderPromise}) => {
if(cached) { if(cached) {
return renderPromise; return renderPromise;

3
src/components/stickerViewer.ts

@ -180,6 +180,7 @@ export default function attachStickerViewerListeners({listenTo, listenerSetter,
player.addEventListener('enterFrame', c); player.addEventListener('enterFrame', c);
}); });
if(!middleware()) return;
player.pause(); player.pause();
} else if(player instanceof HTMLVideoElement) { } else if(player instanceof HTMLVideoElement) {
player.currentTime = (mediaContainer.querySelector('video') as HTMLVideoElement).currentTime; player.currentTime = (mediaContainer.querySelector('video') as HTMLVideoElement).currentTime;
@ -283,7 +284,6 @@ export default function attachStickerViewerListeners({listenTo, listenerSetter,
const onMousePreMove = (e: MouseEvent) => { const onMousePreMove = (e: MouseEvent) => {
if(!findUpAsChild(e.target as HTMLElement, mediaContainer)) { if(!findUpAsChild(e.target as HTMLElement, mediaContainer)) {
document.removeEventListener('mousemove', onMousePreMove);
onMouseUp(); onMouseUp();
} }
}; };
@ -303,6 +303,7 @@ export default function attachStickerViewerListeners({listenTo, listenerSetter,
attachClickEvent(document.body, cancelEvent, {capture: true, once: true}); attachClickEvent(document.body, cancelEvent, {capture: true, once: true});
} }
document.removeEventListener('mousemove', onMousePreMove);
document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp, {capture: true}); document.removeEventListener('mouseup', onMouseUp, {capture: true});
}; };

4
src/components/wrappers/sticker.ts

@ -700,8 +700,8 @@ export async function onEmojiStickerClick({event, container, managers, peerId, m
data.a.length = 0; data.a.length = 0;
}, 1000, false); }, 1000, false);
const animation = lottieLoader.getAnimation(container); const animation = !container.classList.contains('custom-emoji') ? lottieLoader.getAnimation(container) : undefined;
if(animation.paused) { if(animation?.paused) {
const doc = await managers.appStickersManager.getAnimatedEmojiSoundDocument(emoji); const doc = await managers.appStickersManager.getAnimatedEmojiSoundDocument(emoji);
if(doc) { if(doc) {
const audio = document.createElement('audio'); const audio = document.createElement('audio');

12
src/components/wrappers/stickerAnimation.ts

@ -7,7 +7,8 @@
import IS_VIBRATE_SUPPORTED from '../../environment/vibrateSupport'; import IS_VIBRATE_SUPPORTED from '../../environment/vibrateSupport';
import assumeType from '../../helpers/assumeType'; import assumeType from '../../helpers/assumeType';
import isInDOM from '../../helpers/dom/isInDOM'; 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 throttleWithRaf from '../../helpers/schedulers/throttleWithRaf';
import windowSize from '../../helpers/windowSize'; import windowSize from '../../helpers/windowSize';
import {PhotoSize, VideoSize} from '../../layer'; import {PhotoSize, VideoSize} from '../../layer';
@ -53,11 +54,15 @@ export default function wrapStickerAnimation({
let animation: RLottiePlayer; let animation: RLottiePlayer;
const unmountAnimation = () => { const unmountAnimation = () => {
middlewareHelper.clean();
animation?.remove(); animation?.remove();
animationDiv.remove(); animationDiv.remove();
appImManager.chat.bubbles.scrollable.container.removeEventListener('scroll', onScroll); appImManager.chat.bubbles.scrollable.container.removeEventListener('scroll', onScroll);
}; };
const middlewareHelper = middleware?.create() ?? getMiddleware();
middleware = middlewareHelper.get();
const stickerPromise = wrapSticker({ const stickerPromise = wrapSticker({
div: animationDiv, div: animationDiv,
doc, doc,
@ -74,6 +79,11 @@ export default function wrapStickerAnimation({
fullThumb fullThumb
}).then(({render}) => render).then((_animation) => { }).then(({render}) => render).then((_animation) => {
assumeType<RLottiePlayer>(_animation); assumeType<RLottiePlayer>(_animation);
if(!middleware()) {
_animation.remove();
throw makeError('MIDDLEWARE');
}
animation = _animation; animation = _animation;
animation.addEventListener('enterFrame', (frameNo) => { animation.addEventListener('enterFrame', (frameNo) => {
if((!loopEffect && frameNo === animation.maxFrame) || !isInDOM(target)) { if((!loopEffect && frameNo === animation.maxFrame) || !isInDOM(target)) {

4
src/helpers/eventListenerBase.ts

@ -84,7 +84,7 @@ export default class EventListenerBase<Listeners extends EventListenerListeners>
} }
public addEventListener<T extends keyof Listeners>(name: T, callback: Listeners[T], options?: boolean | AddEventListenerOptions) { public addEventListener<T extends keyof Listeners>(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)) { if(this.listenerResults.hasOwnProperty(name)) {
callback(...this.listenerResults[name]); callback(...this.listenerResults[name]);
@ -108,7 +108,7 @@ export default class EventListenerBase<Listeners extends EventListenerListeners>
public removeEventListener<T extends keyof Listeners>(name: T, callback: Listeners[T], options?: boolean | AddEventListenerOptions) { public removeEventListener<T extends keyof Listeners>(name: T, callback: Listeners[T], options?: boolean | AddEventListenerOptions) {
if(this.listeners[name]) { 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); // e.remove(this, name, callback);
} }

3
src/lib/appManagers/appDialogsManager.ts

@ -1003,6 +1003,7 @@ export class AppDialogsManager {
let loadCount = windowSize.height / 72 * 1.25 | 0; let loadCount = windowSize.height / 72 * 1.25 | 0;
let offsetIndex = 0; let offsetIndex = 0;
const doNotRenderChatList = this.doNotRenderChatList; // cache before awaits
const {index: currentOffsetIndex} = this.getOffsetIndex(side); const {index: currentOffsetIndex} = this.getOffsetIndex(side);
if(currentOffsetIndex) { if(currentOffsetIndex) {
if(side === 'top') { if(side === 'top') {
@ -1045,7 +1046,7 @@ export class AppDialogsManager {
const a = await getConversationsResult; const a = await getConversationsResult;
const result = await a.result; const result = await a.result;
if(this.loadDialogsRenderPromise !== renderPromise || this.doNotRenderChatList) { if(this.loadDialogsRenderPromise !== renderPromise || doNotRenderChatList) {
reject(); reject();
cachedInfoPromise.reject(); cachedInfoPromise.reject();
return; return;

10
src/lib/richTextProcessor/wrapRichText.ts

@ -437,7 +437,6 @@ export default function wrapRichText(text: string, options: Partial<{
}, },
voodoo?: boolean, voodoo?: boolean,
customEmojis?: {[docId: DocId]: CustomEmojiElement[]}, customEmojis?: {[docId: DocId]: CustomEmojiElement[]},
wrappingSpoiler?: boolean,
loadPromises?: Promise<any>[], loadPromises?: Promise<any>[],
middleware?: Middleware, middleware?: Middleware,
@ -478,7 +477,8 @@ export default function wrapRichText(text: string, options: Partial<{
} }
} else if((entity.offset + entity.length) > textLength) { } else if((entity.offset + entity.length) > textLength) {
entity = copy(entity); 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) { if(entity.length) {
@ -621,7 +621,7 @@ export default function wrapRichText(text: string, options: Partial<{
break; break;
} }
if(nextEntity?._ === 'messageEntityEmoji') { while(nextEntity?._ === 'messageEntityEmoji' && nextEntity.offset < endOffset) {
++nasty.i; ++nasty.i;
nasty.lastEntity = nextEntity; nasty.lastEntity = nextEntity;
nasty.usedLength += nextEntity.length; nasty.usedLength += nextEntity.length;
@ -808,7 +808,9 @@ export default function wrapRichText(text: string, options: Partial<{
const encoded = encodeSpoiler(nasty.text, entity); const encoded = encodeSpoiler(nasty.text, entity);
nasty.text = encoded.text; nasty.text = encoded.text;
partText = encoded.entityText; partText = encoded.entityText;
nasty.usedLength += partText.length; if(endPartOffset !== endOffset) {
nasty.usedLength += endOffset - endPartOffset;
}
let n: MessageEntity; let n: MessageEntity;
for(; n = entities[nasty.i + 1], n && n.offset < endOffset;) { for(; n = entities[nasty.i + 1], n && n.offset < endOffset;) {
// nasty.usedLength += n.length; // nasty.usedLength += n.length;

6
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; bottom: 55px;
} }
@ -1787,6 +1787,10 @@ $bubble-beside-button-width: 38px;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
&.with-beside-replies .bubble-content {
min-height: 5.5rem;
}
.time { .time {
visibility: hidden; // * can't use color transparent here, because in name can be emoji visibility: hidden; // * can't use color transparent here, because in name can be emoji
font-size: 12px; font-size: 12px;

Loading…
Cancel
Save