@ -68,7 +68,7 @@ import whichChild from "../../helpers/dom/whichChild";
import { cancelAnimationByKey } from "../../helpers/animation" ;
import { cancelAnimationByKey } from "../../helpers/animation" ;
import assumeType from "../../helpers/assumeType" ;
import assumeType from "../../helpers/assumeType" ;
import { EmoticonsDropdown } from "../emoticonsDropdown" ;
import { EmoticonsDropdown } from "../emoticonsDropdown" ;
import debounce from "../../helpers/schedulers/debounce" ;
import debounce , { DebounceReturnType } from "../../helpers/schedulers/debounce" ;
import { SEND_WHEN_ONLINE_TIMESTAMP } from "../../lib/mtproto/constants" ;
import { SEND_WHEN_ONLINE_TIMESTAMP } from "../../lib/mtproto/constants" ;
import windowSize from "../../helpers/windowSize" ;
import windowSize from "../../helpers/windowSize" ;
import { formatPhoneNumber } from "../../helpers/formatPhoneNumber" ;
import { formatPhoneNumber } from "../../helpers/formatPhoneNumber" ;
@ -118,6 +118,7 @@ let queueId = 0;
type GenerateLocalMessageType < IsService > = IsService extends true ? Message.messageService : Message.message ;
type GenerateLocalMessageType < IsService > = IsService extends true ? Message.messageService : Message.message ;
const SPONSORED_MESSAGE_ID_OFFSET = 1 ;
const SPONSORED_MESSAGE_ID_OFFSET = 1 ;
const STICKY_OFFSET = 3 ;
export default class ChatBubbles {
export default class ChatBubbles {
public bubblesContainer : HTMLDivElement ;
public bubblesContainer : HTMLDivElement ;
@ -205,7 +206,8 @@ export default class ChatBubbles {
private hoverBubble : HTMLElement ;
private hoverBubble : HTMLElement ;
private hoverReaction : HTMLElement ;
private hoverReaction : HTMLElement ;
private sliceViewportDebounced : ( ) = > Promise < void > ;
private sliceViewportDebounced : DebounceReturnType < ChatBubbles [ ' sliceViewport ' ] > ;
resizeObserver : ResizeObserver ;
// private reactions: Map<number, ReactionsElement>;
// private reactions: Map<number, ReactionsElement>;
@ -649,18 +651,37 @@ export default class ChatBubbles {
} ) ;
} ) ;
}
}
if ( false ) this . stickyIntersector = new StickyIntersector ( this . scrollable . container , ( stuck , target ) = > {
/* if(false) */ this . stickyIntersector = new StickyIntersector ( this . scrollable . container , ( stuck , target ) = > {
for ( const timestamp in this . dateMessages ) {
for ( const timestamp in this . dateMessages ) {
const dateMessage = this . dateMessages [ timestamp ] ;
const dateMessage = this . dateMessages [ timestamp ] ;
if ( dateMessage . container === target ) {
if ( dateMessage . container === target ) {
dateMessage . div . classList . toggle ( 'is-sticky' , stuck ) ;
const dateBubble = dateMessage . div ;
// dateMessage.container.classList.add('has-sticky-dates');
// SetTransition(dateBubble, 'kek', stuck, this.previousStickyDate ? 300 : 0);
// if(this.previousStickyDate) {
// dateBubble.classList.add('kek');
// }
dateBubble . classList . toggle ( 'is-sticky' , stuck ) ;
if ( stuck ) {
this . previousStickyDate = dateBubble ;
}
break ;
break ;
}
}
}
}
if ( this . previousStickyDate ) {
// fastRaf(() => {
// this.bubblesContainer.classList.add('has-sticky-dates');
// });
}
} ) ;
} ) ;
if ( ! IS_SAFARI ) {
if ( ! IS_SAFARI ) {
// this.sliceViewportDebounced = debounce(this.sliceViewport.bind(this), 100, false, true);
this . sliceViewportDebounced = debounce ( this . sliceViewport . bind ( this ) , 100 , false , true ) ;
}
}
let middleware : ReturnType < ChatBubbles [ ' getMiddleware ' ] > ;
let middleware : ReturnType < ChatBubbles [ ' getMiddleware ' ] > ;
@ -668,12 +689,20 @@ export default class ChatBubbles {
this . isHeavyAnimationInProgress = true ;
this . isHeavyAnimationInProgress = true ;
this . lazyLoadQueue . lock ( ) ;
this . lazyLoadQueue . lock ( ) ;
middleware = this . getMiddleware ( ) ;
middleware = this . getMiddleware ( ) ;
// if(this.sliceViewportDebounced) {
// this.sliceViewportDebounced.clearTimeout();
// }
} , ( ) = > {
} , ( ) = > {
this . isHeavyAnimationInProgress = false ;
this . isHeavyAnimationInProgress = false ;
if ( middleware && middleware ( ) ) {
if ( middleware && middleware ( ) ) {
this . lazyLoadQueue . unlock ( ) ;
this . lazyLoadQueue . unlock ( ) ;
this . lazyLoadQueue . refresh ( ) ;
this . lazyLoadQueue . refresh ( ) ;
// if(this.sliceViewportDebounced) {
// this.sliceViewportDebounced();
// }
}
}
middleware = null ;
middleware = null ;
@ -760,7 +789,7 @@ export default class ChatBubbles {
} ) ;
} ) ;
if ( isScrolledDown ) {
if ( isScrolledDown ) {
this . scrollable . scrollTop = 99999 ;
this . scrollable . setScrollTopSilently ( 99999 ) ;
} else {
} else {
this . performHistoryResult ( [ ] , true , false , undefined ) ;
this . performHistoryResult ( [ ] , true , false , undefined ) ;
}
}
@ -839,104 +868,124 @@ export default class ChatBubbles {
this . appMessagesManager . incrementMessageViews ( this . peerId , mids ) ;
this . appMessagesManager . incrementMessageViews ( this . peerId , mids ) ;
} , 1000 , false , true ) ;
} , 1000 , false , true ) ;
}
if ( 'ResizeObserver' in window ) {
private createResizeObserver() {
let wasHeight = this . scrollable . container . offsetHeight ;
if ( ! ( 'ResizeObserver' in window ) || this . resizeObserver ) {
let resizing = false ;
return ;
let skip = false ;
}
let scrolled = 0 ;
let part = 0 ;
let rAF = 0 ;
const onResizeEnd = ( ) = > {
const height = this . scrollable . container . offsetHeight ;
const isScrolledDown = this . scrollable . isScrolledDown ;
if ( height !== wasHeight && ( ! skip || ! isScrolledDown ) ) { // * fix opening keyboard while ESG is active, offsetHeight will change right between 'start' and this first frame
part += wasHeight - height ;
}
/ * i f ( D E B U G ) {
const container = this . scrollable . container ;
this . log ( 'resize end' , scrolled , part , this . scrollable . scrollTop , height , wasHeight , this . scrollable . isScrolledDown ) ;
let wasHeight = container . offsetHeight ;
} * /
let resizing = false ;
let skip = false ;
let scrolled = 0 ;
let part = 0 ;
let rAF = 0 ;
let skipNext = true ;
if ( part ) {
const onResizeEnd = ( ) = > {
this . scrollable . scrollTop += Math . round ( part ) ;
const height = container . offsetHeight ;
}
const isScrolledDown = this . scrollable . isScrolledDown ;
if ( height !== wasHeight && ( ! skip || ! isScrolledDown ) ) { // * fix opening keyboard while ESG is active, offsetHeight will change right between 'start' and this first frame
part += wasHeight - height ;
}
wasHeight = height ;
/ * i f ( D E B U G ) {
scrolled = 0 ;
this . log ( 'resize end' , scrolled , part , this . scrollable . scrollTop , height , wasHeight , this . scrollable . isScrolledDown ) ;
rAF = 0 ;
} * /
part = 0 ;
resizing = false ;
skip = false ;
} ;
const setEndRAF = ( single : boolean ) = > {
if ( part ) {
if ( rAF ) window . cancelAnimationFrame ( rAF ) ;
this . scrollable . scrollTop += Math . round ( part ) ;
rAF = window . requestAnimationFrame ( single ? onResizeEnd : ( ) = > {
}
rAF = window . requestAnimationFrame ( onResizeEnd ) ;
//this.log('resize after RAF', part);
} ) ;
} ;
const processEntries = ( entries : any ) = > {
wasHeight = height ;
if ( skip ) {
scrolled = 0 ;
setEndRAF ( false ) ;
rAF = 0 ;
return ;
part = 0 ;
}
resizing = false ;
skip = false ;
} ;
const entry = entries [ 0 ] ;
const setEndRAF = ( single : boolean ) = > {
const height = entry . contentRect . height ; /* Math.ceil(entry.contentRect.height); */
if ( rAF ) window . cancelAnimationFrame ( rAF ) ;
rAF = window . requestAnimationFrame ( single ? onResizeEnd : ( ) = > {
if ( ! wasHeight ) {
rAF = window . requestAnimationFrame ( onResizeEnd ) ;
wasHeight = height ;
//this.log('resize after RAF', part);
return ;
} ) ;
}
} ;
const realDiff = wasHeight - height ;
const processEntries : ResizeObserverCallback = ( entries ) = > {
let diff = realDiff + part ;
if ( skipNext ) {
const _part = diff % 1 ;
skipNext = false ;
diff -= _part ;
return ;
}
if ( ! resizing ) {
resizing = true ;
/ * i f ( D E B U G ) {
if ( skip ) {
this . log ( 'resize start' , realDiff , this . scrollable . scrollTop , this . scrollable . container . offsetHeight , this . scrollable . isScrolledDown ) ;
setEndRAF ( false ) ;
} * /
return ;
}
if ( realDiff < 0 && this . scrollable . isScrolledDown ) {
const entry = entries [ 0 ] ;
//if(isSafari) { // * fix opening keyboard while ESG is active
const height = entry . contentRect . height ; /* Math.ceil(entry.contentRect.height); */
part = - realDiff ;
//}
if ( ! wasHeight ) {
wasHeight = height ;
return ;
}
skip = true ;
const realDiff = wasHeight - height ;
setEndRAF ( false ) ;
let diff = realDiff + part ;
return ;
const _part = diff % 1 ;
}
diff -= _part ;
}
scrolled += diff ;
if ( ! resizing ) {
resizing = true ;
/ * i f ( D E B U G ) {
/ * i f ( D E B U G ) {
this . log ( 'resize' , wasHeight - height , diff , this . scrollable . container . offsetHeight , this . scrollable . isScrolledDown , height , wasHeight ) ;
this . log ( 'resize start ' , realDiff , this . scrollable . scrollTop , this . scrollable . container . offsetHeight , this . scrollable . isScrolledDown ) ;
} * /
} * /
if ( diff ) {
if ( realDiff < 0 && this . scrollable . isScrolledDown ) {
const needScrollTop = this . scrollable . scrollTop + diff ;
//if(isSafari) { // * fix opening keyboard while ESG is active
this . scrollable . scrollTop = needScrollTop ;
part = - realDiff ;
//}
skip = true ;
setEndRAF ( false ) ;
return ;
}
}
}
setEndRAF ( false ) ;
part = _part ;
scrolled += diff ;
wasHeight = height ;
} ;
// @ts-ignore
/ * i f ( D E B U G ) {
const resizeObserver = new ResizeObserver ( processEntries ) ;
this . log ( 'resize' , wasHeight - height , diff , this . scrollable . container . offsetHeight , this . scrollable . isScrolledDown , height , wasHeight ) ;
resizeObserver . observe ( this . bubblesContainer ) ;
} * /
if ( diff ) {
const needScrollTop = this . scrollable . scrollTop + diff ;
this . scrollable . scrollTop = needScrollTop ;
}
setEndRAF ( false ) ;
part = _part ;
wasHeight = height ;
} ;
const resizeObserver = this . resizeObserver = new ResizeObserver ( processEntries ) ;
resizeObserver . observe ( container ) ;
}
private destroyResizeObserver() {
const resizeObserver = this . resizeObserver ;
if ( ! resizeObserver ) {
return ;
}
}
resizeObserver . disconnect ( ) ;
this . resizeObserver = undefined ;
}
}
private onBubblesMouseMove = ( e : MouseEvent ) = > {
private onBubblesMouseMove = ( e : MouseEvent ) = > {
@ -1047,7 +1096,7 @@ export default class ChatBubbles {
} ;
} ;
public setStickyDateManually() {
public setStickyDateManually() {
// return;
return ;
const timestamps = Object . keys ( this . dateMessages ) . map ( k = > + k ) . sort ( ( a , b ) = > b - a ) ;
const timestamps = Object . keys ( this . dateMessages ) . map ( k = > + k ) . sort ( ( a , b ) = > b - a ) ;
let lastVisible : HTMLElement ;
let lastVisible : HTMLElement ;
@ -1713,7 +1762,13 @@ export default class ChatBubbles {
//return;
//return;
// * В таком случае, кнопка не будет моргать если чат в самом низу, и правильно отработает случай написания нового сообщения и проскролла вниз
// * В таком случае, кнопка не будет моргать если чат в самом низу, и правильно отработает случай написания нового сообщения и проскролла вниз
if ( this . isHeavyAnimationInProgress && this . scrolledDown ) return ;
if ( this . isHeavyAnimationInProgress && this . scrolledDown ) {
if ( this . sliceViewportDebounced ) {
this . sliceViewportDebounced . clearTimeout ( ) ;
}
return ;
}
//lottieLoader.checkAnimations(false, 'chat');
//lottieLoader.checkAnimations(false, 'chat');
const distanceToEnd = this . scrollable . getDistanceToEnd ( ) ;
const distanceToEnd = this . scrollable . getDistanceToEnd ( ) ;
@ -1821,7 +1876,7 @@ export default class ChatBubbles {
}
}
}
}
public deleteMessagesByIds ( mids : number [ ] , permanent = true ) {
public deleteMessagesByIds ( mids : number [ ] , permanent = true , ignoreOnScroll? : boolean ) {
let deleted = false ;
let deleted = false ;
mids . forEach ( mid = > {
mids . forEach ( mid = > {
if ( ! ( mid in this . bubbles ) ) return ;
if ( ! ( mid in this . bubbles ) ) return ;
@ -1868,7 +1923,10 @@ export default class ChatBubbles {
animationIntersector . checkAnimations ( false , CHAT_ANIMATION_GROUP ) ;
animationIntersector . checkAnimations ( false , CHAT_ANIMATION_GROUP ) ;
this . deleteEmptyDateGroups ( ) ;
this . deleteEmptyDateGroups ( ) ;
this . onScroll ( ) ;
if ( ! ignoreOnScroll ) {
this . onScroll ( ) ;
}
}
}
public renderNewMessagesByIds ( mids : number [ ] , scrolledDown? : boolean ) {
public renderNewMessagesByIds ( mids : number [ ] , scrolledDown? : boolean ) {
@ -1928,7 +1986,7 @@ export default class ChatBubbles {
this . scrollable . scrollTop += add ; * /
this . scrollable . scrollTop += add ; * /
setPaddingTo = this . chatInner ;
setPaddingTo = this . chatInner ;
setPaddingTo . style . paddingTop = clientHeight + 'px' ;
setPaddingTo . style . paddingTop = clientHeight + 'px' ;
this . scrollable . scrollTop = scrollHeight ;
this . scrollable . setScrollTopSilently ( scrollHeight ) ;
this . isTopPaddingSet = true ;
this . isTopPaddingSet = true ;
}
}
}
}
@ -1982,7 +2040,7 @@ export default class ChatBubbles {
let fallbackToElementStartWhenCentering : HTMLElement ;
let fallbackToElementStartWhenCentering : HTMLElement ;
// * 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' && whichChild ( bubble ) === ( this . stickyIntersector ? 2 : 1 ) /* && this.chat.setPeerPromise */ ) {
if ( bubble && position !== 'end' && whichChild ( bubble ) === ( this . stickyIntersector ? STICKY_OFFSET : 1 ) /* && this.chat.setPeerPromise */ ) {
const dateGroup = bubble . parentElement ;
const dateGroup = bubble . parentElement ;
// if(whichChild(dateGroup) === 0) {
// if(whichChild(dateGroup) === 0) {
fallbackToElementStartWhenCentering = dateGroup ;
fallbackToElementStartWhenCentering = dateGroup ;
@ -2002,7 +2060,7 @@ export default class ChatBubbles {
} * /
} * /
const isChangingHeight = ( this . chat . input . messageInput && this . chat . input . messageInput . classList . contains ( 'is-changing-height' ) ) || this . chat . container . classList . contains ( 'is-toggling-helper' ) ;
const isChangingHeight = ( this . chat . input . messageInput && this . chat . input . messageInput . classList . contains ( 'is-changing-height' ) ) || this . chat . container . classList . contains ( 'is-toggling-helper' ) ;
return this . scrollable . scrollIntoViewNew ( {
const promise = this . scrollable . scrollIntoViewNew ( {
element ,
element ,
position ,
position ,
margin ,
margin ,
@ -2024,6 +2082,13 @@ export default class ChatBubbles {
} : undefined ,
} : undefined ,
fallbackToElementStartWhenCentering
fallbackToElementStartWhenCentering
} ) ;
} ) ;
// fix flickering date when opening unread chat and focusing message
if ( forceDirection === FocusDirection . Static ) {
this . scrollable . lastScrollPosition = this . scrollable . scrollTop ;
}
return promise ;
}
}
public scrollToEnd() {
public scrollToEnd() {
@ -2080,58 +2145,66 @@ export default class ChatBubbles {
} , 2000 ) ;
} , 2000 ) ;
}
}
public getDateContainerByMessage ( message : any , reverse : boolean ) {
private createDateBubble ( timestamp : number , date : Date = new Date ( timestamp * 1000 ) ) {
const date = new Date ( message . date * 1000 ) ;
let dateElement : HTMLElement ;
date . setHours ( 0 , 0 , 0 ) ;
const dateTimestamp = date . getTime ( ) ;
if ( ! this . dateMessages [ dateTimestamp ] ) {
let dateElement : HTMLElement ;
const today = new Date ( ) ;
const today = new Date ( ) ;
today . setHours ( 0 , 0 , 0 , 0 ) ;
today . setHours ( 0 , 0 , 0 , 0 ) ;
const isScheduled = this . chat . type === 'scheduled' ;
const isScheduled = this . chat . type === 'scheduled' ;
if ( today . getTime ( ) === date . getTime ( ) ) {
if ( today . getTime ( ) === date . getTime ( ) ) {
dateElement = i18n ( isScheduled ? 'Chat.Date.ScheduledForToday' : 'Date.Today' ) ;
dateElement = i18n ( isScheduled ? 'Chat.Date.ScheduledForToday' : 'Date.Today' ) ;
} else if ( isScheduled && message . date === SEND_WHEN_ONLINE_TIMESTAMP ) {
} else if ( isScheduled && timestamp === SEND_WHEN_ONLINE_TIMESTAMP ) {
dateElement = i18n ( 'MessageScheduledUntilOnline' ) ;
dateElement = i18n ( 'MessageScheduledUntilOnline' ) ;
} else {
} else {
const options : Intl.DateTimeFormatOptions = {
const options : Intl.DateTimeFormatOptions = {
day : 'numeric' ,
day : 'numeric' ,
month : 'long'
month : 'long'
} ;
} ;
if ( date . getFullYear ( ) !== today . getFullYear ( ) ) {
if ( date . getFullYear ( ) !== today . getFullYear ( ) ) {
options . year = 'numeric' ;
options . year = 'numeric' ;
}
}
dateElement = new I18n . IntlDateElement ( {
dateElement = new I18n . IntlDateElement ( {
date ,
date ,
options
options
} ) . element ;
} ) . element ;
if ( isScheduled ) {
if ( isScheduled ) {
dateElement = i18n ( 'Chat.Date.ScheduledFor' , [ dateElement ] ) ;
dateElement = i18n ( 'Chat.Date.ScheduledFor' , [ dateElement ] ) ;
}
}
}
}
const bubble = document . createElement ( 'div' ) ;
bubble . className = 'bubble service is-date' ;
const bubble = document . createElement ( 'div' ) ;
const bubbleContent = document . createElement ( 'div' ) ;
bubble . className = 'bubble service is-date' ;
bubbleContent . classList . add ( 'bubble-content' ) ;
const bubbleContent = document . createElement ( 'div' ) ;
const serviceMsg = document . createElement ( 'div' ) ;
bubbleContent . classList . add ( 'bubble-content' ) ;
serviceMsg . classList . add ( 'service-msg' ) ;
const serviceMsg = document . createElement ( 'div' ) ;
serviceMsg . classList . add ( 'service-msg' ) ;
serviceMsg . append ( dateElement ) ;
bubbleContent . append ( serviceMsg ) ;
bubble . append ( bubbleContent ) ;
serviceMsg . append ( dateElement ) ;
return bubble ;
}
bubbleContent . append ( serviceMsg ) ;
public getDateContainerByMessage ( message : any , reverse : boolean ) {
bubble . append ( bubbleContent ) ;
const date = new Date ( message . date * 1000 ) ;
////////this.log('need to render date message', dateTimestamp, str);
date . setHours ( 0 , 0 , 0 ) ;
const dateTimestamp = date . getTime ( ) ;
if ( ! this . dateMessages [ dateTimestamp ] ) {
const bubble = this . createDateBubble ( message . date , date ) ;
// bubble.classList.add('is-sticky');
const fakeBubble = this . createDateBubble ( message . date , date ) ;
fakeBubble . classList . add ( 'is-fake' ) ;
const container = document . createElement ( 'section' ) ;
const container = document . createElement ( 'section' ) ;
container . className = 'bubbles-date-group' ;
container . className = 'bubbles-date-group' ;
container . append ( bubble ) ;
container . append ( bubble , fakeBubble ) ;
this . dateMessages [ dateTimestamp ] = {
this . dateMessages [ dateTimestamp ] = {
div : bubble ,
div : bubble ,
@ -2238,6 +2311,8 @@ export default class ChatBubbles {
this . viewsObserver . disconnect ( ) ;
this . viewsObserver . disconnect ( ) ;
this . viewsMids . clear ( ) ;
this . viewsMids . clear ( ) ;
}
}
this . destroyResizeObserver ( ) ;
this . middleware . clean ( ) ;
this . middleware . clean ( ) ;
@ -2258,6 +2333,9 @@ export default class ChatBubbles {
clearTimeout ( this . isScrollingTimeout ) ;
clearTimeout ( this . isScrollingTimeout ) ;
this . isScrollingTimeout = 0 ;
this . isScrollingTimeout = 0 ;
}
}
this . bubblesContainer . classList . remove ( 'has-sticky-dates' ) ;
this . scrollable . cancelMeasure ( ) ;
}
}
public setPeer ( peerId : PeerId , lastMsgId? : number , startParam? : string ) : { cached? : boolean , promise : Chat [ 'setPeerPromise' ] } {
public setPeer ( peerId : PeerId , lastMsgId? : number , startParam? : string ) : { cached? : boolean , promise : Chat [ 'setPeerPromise' ] } {
@ -2331,7 +2409,7 @@ export default class ChatBubbles {
this . chat . dispatchEvent ( 'setPeer' , lastMsgId , false ) ;
this . chat . dispatchEvent ( 'setPeer' , lastMsgId , false ) ;
} else if ( topMessage && ! isJump ) {
} else if ( topMessage && ! isJump ) {
//this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight);
//this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight);
scrollable . scrollTop = scrollable . scrollHeight ;
scrollable . setScrollTopSilently ( scrollable . scrollHeight ) ;
this . chat . dispatchEvent ( 'setPeer' , lastMsgId , true ) ;
this . chat . dispatchEvent ( 'setPeer' , lastMsgId , true ) ;
}
}
@ -2422,6 +2500,8 @@ export default class ChatBubbles {
//console.timeEnd('appImManager setPeer pre promise');
//console.timeEnd('appImManager setPeer pre promise');
/ * t h i s . l a d d e r D e f e r r e d & & t h i s . l a d d e r D e f e r r e d . r e s o l v e ( ) ;
/ * t h i s . l a d d e r D e f e r r e d & & t h i s . l a d d e r D e f e r r e d . r e s o l v e ( ) ;
this . ladderDeferred = deferredPromise < void > ( ) ; * /
this . ladderDeferred = deferredPromise < void > ( ) ; * /
const middleware = this . getMiddleware ( ) ;
animationIntersector . lockGroup ( CHAT_ANIMATION_GROUP ) ;
animationIntersector . lockGroup ( CHAT_ANIMATION_GROUP ) ;
const setPeerPromise = promise . then ( ( ) = > {
const setPeerPromise = promise . then ( ( ) = > {
@ -2431,10 +2511,10 @@ export default class ChatBubbles {
if ( ! samePeer ) {
if ( ! samePeer ) {
this . chat . finishPeerChange ( isTarget , isJump , lastMsgId , startParam ) ; // * костыль
this . chat . finishPeerChange ( isTarget , isJump , lastMsgId , startParam ) ; // * костыль
}
}
} else {
this . preloader . detach ( ) ;
}
}
this . preloader . detach ( ) ;
if ( this . resolveLadderAnimation ) {
if ( this . resolveLadderAnimation ) {
this . resolveLadderAnimation ( ) ;
this . resolveLadderAnimation ( ) ;
this . resolveLadderAnimation = undefined ;
this . resolveLadderAnimation = undefined ;
@ -2455,7 +2535,7 @@ export default class ChatBubbles {
//if(dialog && lastMsgID && lastMsgID !== topMessage && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) {
//if(dialog && lastMsgID && lastMsgID !== topMessage && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) {
if ( savedPosition ) {
if ( savedPosition ) {
scrollable . scrollTop = scrollable . lastScrollPosition = savedPosition . top ;
scrollable . setScrollTopSilently ( savedPosition . top ) ;
/ * c o n s t m o u n t e d B y L a s t M s g I d = t h i s . g e t M o u n t e d B u b b l e ( l a s t M s g I d ) ;
/ * c o n s t m o u n t e d B y L a s t M s g I d = t h i s . g e t M o u n t e d B u b b l e ( l a s t M s g I d ) ;
let bubble : HTMLElement = mountedByLastMsgId ? . bubble ;
let bubble : HTMLElement = mountedByLastMsgId ? . bubble ;
if ( ! bubble ? . parentElement ) {
if ( ! bubble ? . parentElement ) {
@ -2470,9 +2550,9 @@ export default class ChatBubbles {
} else if ( ( topMessage && isJump ) || isTarget ) {
} else if ( ( topMessage && isJump ) || isTarget ) {
const fromUp = maxBubbleId > 0 && ( maxBubbleId < lastMsgId || lastMsgId < 0 ) ;
const fromUp = maxBubbleId > 0 && ( maxBubbleId < lastMsgId || lastMsgId < 0 ) ;
if ( ! fromUp && samePeer ) {
if ( ! fromUp && samePeer ) {
scrollable . scrollTop = scrollable . lastScrollPosition = 99999 ;
scrollable . setScrollTopSilently ( 99999 ) ;
} else if ( fromUp /* && (samePeer || forwardingUnread) */ ) {
} else if ( fromUp /* && (samePeer || forwardingUnread) */ ) {
scrollable . scrollTop = scrollable . lastScrollPosition = 0 ;
scrollable . setScrollTopSilently ( 0 ) ;
}
}
const mountedByLastMsgId = this . getMountedBubble ( lastMsgId ) ;
const mountedByLastMsgId = this . getMountedBubble ( lastMsgId ) ;
@ -2489,7 +2569,7 @@ export default class ChatBubbles {
}
}
}
}
} else {
} else {
scrollable . scrollTop = scrollable . lastScrollPosition = 99999 ;
scrollable . setScrollTopSilently ( 99999 ) ;
}
}
this . onScroll ( ) ;
this . onScroll ( ) ;
@ -2605,7 +2685,10 @@ export default class ChatBubbles {
//console.timeEnd('appImManager setPeer');
//console.timeEnd('appImManager setPeer');
} ) . catch ( err = > {
} ) . catch ( err = > {
this . log . error ( 'getHistory promise error:' , err ) ;
this . log . error ( 'getHistory promise error:' , err ) ;
this . preloader . detach ( ) ;
if ( ! middleware ( ) ) {
this . preloader . detach ( ) ;
}
throw err ;
throw err ;
} ) ;
} ) ;
@ -2628,6 +2711,8 @@ export default class ChatBubbles {
this . chatInner . classList . toggle ( 'is-chat' , this . chat . isAnyGroup ( ) ) ;
this . chatInner . classList . toggle ( 'is-chat' , this . chat . isAnyGroup ( ) ) ;
this . chatInner . classList . toggle ( 'is-channel' , isChannel ) ;
this . chatInner . classList . toggle ( 'is-channel' , isChannel ) ;
this . createResizeObserver ( ) ;
}
}
public renderMessagesQueue ( message : any , bubble : HTMLElement , reverse : boolean , promises : Promise < any > [ ] ) {
public renderMessagesQueue ( message : any , bubble : HTMLElement , reverse : boolean , promises : Promise < any > [ ] ) {
@ -2706,7 +2791,7 @@ export default class ChatBubbles {
const dateMessage = this . getDateContainerByMessage ( message , reverse ) ;
const dateMessage = this . getDateContainerByMessage ( message , reverse ) ;
if ( this . chat . type === 'scheduled' || this . chat . type === 'pinned' /* || true */ ) { // ! TEMP COMMENTED
if ( this . chat . type === 'scheduled' || this . chat . type === 'pinned' /* || true */ ) { // ! TEMP COMMENTED
const offset = this . stickyIntersector ? 2 : 1 ;
const offset = this . stickyIntersector ? STICKY_OFFSET : 1 ;
let children = Array . from ( dateMessage . container . children ) . slice ( offset ) as HTMLElement [ ] ;
let children = Array . from ( dateMessage . container . children ) . slice ( offset ) as HTMLElement [ ] ;
let i = 0 , foundMidOnSameTimestamp = 0 ;
let i = 0 , foundMidOnSameTimestamp = 0 ;
for ( ; i < children . length ; ++ i ) {
for ( ; i < children . length ; ++ i ) {
@ -2735,7 +2820,7 @@ export default class ChatBubbles {
positionElementByIndex ( bubble , dateMessage . container , index ) ;
positionElementByIndex ( bubble , dateMessage . container , index ) ;
} else {
} else {
if ( reverse ) {
if ( reverse ) {
dateMessage . container . insertBefore ( bubble , dateMessage . container . children [ this . stickyIntersector ? 1 : 0 ] . nextSibling ) ;
dateMessage . container . insertBefore ( bubble , dateMessage . container . children [ this . stickyIntersector ? STICKY_OFFSET - 1 : 0 ] . nextSibling ) ;
} else {
} else {
dateMessage . container . append ( bubble ) ;
dateMessage . container . append ( bubble ) ;
}
}
@ -3880,14 +3965,18 @@ export default class ChatBubbles {
this . log ( 'performHistoryResult: will render some messages:' , history . length , this . isHeavyAnimationInProgress , this . messagesQueuePromise ) ;
this . log ( 'performHistoryResult: will render some messages:' , history . length , this . isHeavyAnimationInProgress , this . messagesQueuePromise ) ;
} * /
} * /
let scrollSaver : ScrollSaver /* , viewportSlice: ReturnType<ChatBubbles['getViewportSlice']> */ ;
let scrollSaver : ScrollSaver , hadScroll : boolean /* , viewportSlice: ReturnType<ChatBubbles['getViewportSlice']> */ ;
this . messagesQueueOnRender = ( ) = > {
this . messagesQueueOnRender = ( ) = > {
scrollSaver = new ScrollSaver ( this . scrollable , reverse ) ;
scrollSaver = new ScrollSaver ( this . scrollable , reverse ) ;
const viewportSlice = this . getViewportSlice ( ) ;
if ( this . getRenderedLength ( ) && ! this . chat . setPeerPromise ) {
this . deleteViewportSlice ( viewportSlice ) ;
const viewportSlice = this . getViewportSlice ( ) ;
this . deleteViewportSlice ( viewportSlice ) ;
}
scrollSaver . save ( ) ;
scrollSaver . save ( ) ;
const saved = scrollSaver . getSaved ( ) ;
hadScroll = saved . scrollHeight !== saved . clientHeight ;
} ;
} ;
if ( this . needReflowScroll ) {
if ( this . needReflowScroll ) {
@ -3936,6 +4025,20 @@ export default class ChatBubbles {
if ( scrollSaver ) {
if ( scrollSaver ) {
scrollSaver . restore ( history . length === 1 && ! reverse ? false : true ) ;
scrollSaver . restore ( history . length === 1 && ! reverse ? false : true ) ;
const state = scrollSaver . getSaved ( ) ;
if ( state . scrollHeight !== state . clientHeight ) {
/ * f o r ( c o n s t t i m e s t a m p i n t h i s . d a t e M e s s a g e s ) {
const dateMessage = this . dateMessages [ timestamp ] ;
dateMessage . div . classList . add ( 'is-sticky' ) ;
} * /
const middleware = this . getMiddleware ( ) ;
setTimeout ( ( ) = > {
if ( ! middleware ( ) ) return ;
this . bubblesContainer . classList . add ( 'has-sticky-dates' ) ;
} , 600 ) ;
}
}
}
return true ;
return true ;
@ -4378,6 +4481,7 @@ export default class ChatBubbles {
}
}
public getViewportSlice() {
public getViewportSlice() {
// this.log.trace('viewport slice');
return getViewportSlice ( {
return getViewportSlice ( {
overflowElement : this.scrollable.container ,
overflowElement : this.scrollable.container ,
selector : '.bubbles-date-group .bubble:not(.is-date)' ,
selector : '.bubbles-date-group .bubble:not(.is-date)' ,
@ -4393,11 +4497,12 @@ export default class ChatBubbles {
if ( invisibleBottom . length ) this . setLoaded ( 'bottom' , false ) ;
if ( invisibleBottom . length ) this . setLoaded ( 'bottom' , false ) ;
const mids = invisible . map ( ( { element } ) = > + element . dataset . mid ) ;
const mids = invisible . map ( ( { element } ) = > + element . dataset . mid ) ;
this . deleteMessagesByIds ( mids , false ) ;
this . deleteMessagesByIds ( mids , false , true ) ;
}
}
public sliceViewport() {
public sliceViewport ( ignoreHeavyAnimation? : boolean ) {
if ( IS_SAFARI ) {
// Safari cannot reset the scroll.
if ( IS_SAFARI || ( this . isHeavyAnimationInProgress && ! ignoreHeavyAnimation ) /* || true */ ) {
return ;
return ;
}
}
@ -4765,7 +4870,7 @@ export default class ChatBubbles {
}
}
public deleteEmptyDateGroups() {
public deleteEmptyDateGroups() {
const mustBeCount = 1 + + ! ! this . stickyIntersector ;
const mustBeCount = this . stickyIntersector ? STICKY_OFFSET : 1 ;
let deleted = false ;
let deleted = false ;
for ( const i in this . dateMessages ) {
for ( const i in this . dateMessages ) {
const dateMessage = this . dateMessages [ i ] ;
const dateMessage = this . dateMessages [ i ] ;