Browse Source

chat switch fix & lazyload intersection & scroll intersection & sidebar open intersection & open chat speed improvements

master
morethanwords 4 years ago
parent
commit
52c563d6a3
  1. 1
      src/components/appForward.ts
  2. 10
      src/components/avatar.ts
  3. 11
      src/components/bubbleGroups.ts
  4. 9
      src/components/chatInput.ts
  5. 50
      src/components/emoticonsDropdown.ts
  6. 79
      src/components/lazyLoadQueue.ts
  7. 420
      src/components/scrollable copy.ts
  8. 139
      src/components/scrollable_new.ts
  9. 270
      src/components/scrollable_spliceCount.ts
  10. 11
      src/components/wrappers.ts
  11. 2
      src/countries.json
  12. 2
      src/emoji.json
  13. 59
      src/format_jsons.js
  14. 2
      src/lib/appManagers/apiUpdatesManager.ts
  15. 74
      src/lib/appManagers/appDialogsManager.ts
  16. 152
      src/lib/appManagers/appImManager.ts
  17. 9
      src/lib/appManagers/appMediaViewer.ts
  18. 58
      src/lib/appManagers/appMessagesManager.ts
  19. 41
      src/lib/appManagers/appSidebarLeft.ts
  20. 130
      src/lib/appManagers/appSidebarRight.ts
  21. 3
      src/lib/appManagers/appStickersManager.ts
  22. 42
      src/lib/config.ts
  23. 4
      src/lib/lottie.ts
  24. 15
      src/lib/lottieLoader.ts
  25. 9
      src/lib/mtproto/apiFileManager.ts
  26. 123
      src/lib/richtextprocessor.js
  27. 117
      src/lib/utils.js
  28. 26
      src/scss/partials/_chatBubble.scss
  29. 4
      src/scss/partials/_rightSIdebar.scss
  30. 8
      src/scss/partials/_scrollable.scss

1
src/components/appForward.ts

@ -15,7 +15,6 @@ class AppForward {
this.closeBtn.addEventListener('click', () => { this.closeBtn.addEventListener('click', () => {
this.cleanup(); this.cleanup();
this.container.classList.remove('active'); this.container.classList.remove('active');
appSidebarRight.onSidebarScroll();
}); });
this.sendBtn.addEventListener('click', () => { this.sendBtn.addEventListener('click', () => {

10
src/components/avatar.ts

@ -14,6 +14,7 @@ $rootScope.$on('avatar_update', (e: CustomEvent) => {
export default class AvatarElement extends HTMLElement { export default class AvatarElement extends HTMLElement {
private peerID: number; private peerID: number;
private isDialog = false; private isDialog = false;
public peerTitle: string;
constructor() { constructor() {
super(); super();
@ -33,22 +34,23 @@ export default class AvatarElement extends HTMLElement {
} }
static get observedAttributes(): string[] { static get observedAttributes(): string[] {
return ['peer', 'dialog'/* массив имён атрибутов для отслеживания их изменений */]; return ['peer', 'dialog', 'peer-title'/* массив имён атрибутов для отслеживания их изменений */];
} }
attributeChangedCallback(name: string, oldValue: string, newValue: string) { attributeChangedCallback(name: string, oldValue: string, newValue: string) {
// вызывается при изменении одного из перечисленных выше атрибутов // вызывается при изменении одного из перечисленных выше атрибутов
if(name == 'peer') { if(name == 'peer') {
this.peerID = +newValue; this.peerID = +newValue;
this.update();
} else if(name == 'peer-title') {
this.peerTitle = newValue;
} else if(name == 'dialog') { } else if(name == 'dialog') {
this.isDialog = !!newValue; this.isDialog = !!newValue;
} }
this.update();
} }
public update() { public update() {
appProfileManager.putPhoto(this, this.peerID, this.isDialog); appProfileManager.putPhoto(this, this.peerID, this.isDialog, this.peerTitle);
} }
adoptedCallback() { adoptedCallback() {

11
src/components/bubbleGroups.ts

@ -1,4 +1,4 @@
import { generatePathData } from "../lib/utils"; import { generatePathData, $rootScope } from "../lib/utils";
export default class BubbleGroups { export default class BubbleGroups {
bubblesByGroups: Array<{timestamp: number, fromID: number, mid: number, group: HTMLDivElement[]}> = []; // map to group bubblesByGroups: Array<{timestamp: number, fromID: number, mid: number, group: HTMLDivElement[]}> = []; // map to group
@ -22,6 +22,11 @@ export default class BubbleGroups {
let timestamp = message.date; let timestamp = message.date;
let fromID = message.fromID; let fromID = message.fromID;
let group: HTMLDivElement[]; let group: HTMLDivElement[];
// fix for saved messages forward to self
if(fromID == $rootScope.myID && $rootScope.selectedPeerID == $rootScope.myID && message.fwdFromID == fromID) {
fromID = -fromID;
}
// try to find added // try to find added
//this.removeBubble(message.mid); //this.removeBubble(message.mid);
@ -48,7 +53,7 @@ export default class BubbleGroups {
this.groups.push(group = [bubble]); this.groups.push(group = [bubble]);
} }
//console.log('addBubble', bubble, message.mid, fromID, reverse, group); //console.log('[BUBBLE]: addBubble', bubble, message.mid, fromID, reverse, group);
this.bubblesByGroups[reverse ? 'unshift' : 'push']({timestamp, fromID, mid: message.mid, group}); this.bubblesByGroups[reverse ? 'unshift' : 'push']({timestamp, fromID, mid: message.mid, group});
this.updateGroup(group); this.updateGroup(group);
@ -117,7 +122,7 @@ export default class BubbleGroups {
let first = group[0]; let first = group[0];
//console.log('updateGroup', group, first); //console.log('[BUBBLE]: updateGroup', group, first);
if(group.length == 1) { if(group.length == 1) {
first.classList.add('is-group-first', 'is-group-last'); first.classList.add('is-group-first', 'is-group-last');

9
src/components/chatInput.ts

@ -471,7 +471,6 @@ export class ChatInput {
}; };
} else { } else {
this.emoticonsDropdown.classList.add('active'); this.emoticonsDropdown.classList.add('active');
this.emoticonsLazyLoadQueue.check();
} }
this.toggleEmoticons.classList.add('active'); this.toggleEmoticons.classList.add('active');
@ -513,11 +512,7 @@ export class ChatInput {
}, ''); }, '');
}; };
public onMessageSent(scrollDown = true, clearInput = true) { public onMessageSent(clearInput = true) {
if(scrollDown) {
appImManager.scroll.scrollTop = appImManager.scroll.scrollHeight;
}
let dialog = appMessagesManager.getDialogByPeerID(appImManager.peerID)[0]; let dialog = appMessagesManager.getDialogByPeerID(appImManager.peerID)[0];
if(dialog && dialog.top_message) { if(dialog && dialog.top_message) {
appMessagesManager.readHistory(appImManager.peerID, dialog.top_message); // lol appMessagesManager.readHistory(appImManager.peerID, dialog.top_message); // lol
@ -556,7 +551,7 @@ export class ChatInput {
}); });
} }
this.onMessageSent(!this.editMsgID); this.onMessageSent();
}; };
public setTopInfo(title: string, subtitle: string, input?: string, message?: any) { public setTopInfo(title: string, subtitle: string, input?: string, message?: any) {

50
src/components/emoticonsDropdown.ts

@ -39,7 +39,6 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
} }
}, () => { }, () => {
lottieLoader.checkAnimations(false, EMOTICONSSTICKERGROUP); lottieLoader.checkAnimations(false, EMOTICONSSTICKERGROUP);
lazyLoadQueue.check(); // for stickers or gifs
}); });
(tabs.firstElementChild.children[0] as HTMLLIElement).click(); // set emoji tab (tabs.firstElementChild.children[0] as HTMLLIElement).click(); // set emoji tab
@ -66,13 +65,11 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
setTimeout(() => { setTimeout(() => {
lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP); lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP);
lazyLoadQueue.check();
}, 100); }, 100);
/* window.requestAnimationFrame(() => { /* window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP); lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP);
lazyLoadQueue.check();
}); });
}); */ }); */
}); });
@ -106,32 +103,28 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
}; };
{ {
let categories = ["Smileys & Emotion", "Animals & Nature", "Food & Drink", "Travel & Places", "Activities", "Objects", /* "Symbols", */"Flags", "Skin Tones"]; const categories = ["Smileys & Emotion", "Animals & Nature", "Food & Drink", "Travel & Places", "Activities", "Objects", /* "Symbols", */"Flags", "Skin Tones"];
let divs: { let divs: {
[category: string]: HTMLDivElement [category: string]: HTMLDivElement
} = {}; } = {};
let keyCategory = Config.Emoji.keyCategory;
let sorted: { let sorted: {
[category: string]: any[] [category: string]: string[]
} = {}; } = {};
for(let unified in Config.Emoji.emoji) { for(let emoji in Config.Emoji) {
// @ts-ignore let details = Config.Emoji[emoji];
let details = Config.Emoji.emoji[unified]; let i = '' + details;
let category = details[keyCategory]; let category = categories[+i[0] - 1];
if(!category) continue; // maybe it's skin tones
if(category == 'Symbols') category = 'Objects';
details.unified = unified;
if(!sorted[category]) sorted[category] = []; if(!sorted[category]) sorted[category] = [];
sorted[category][details.sort_order] = details; sorted[category][+i.slice(1) || 0] = emoji;
} }
//console.log('emoticons sorted:', sorted); console.log('emoticons sorted:', sorted);
Object.keys(sorted).forEach(c => sorted[c].sort((a, b) => a - b)); //Object.keys(sorted).forEach(c => sorted[c].sort((a, b) => a - b));
categories.pop(); categories.pop();
delete sorted["Skin Tones"]; delete sorted["Skin Tones"];
@ -151,8 +144,8 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
div.append(titleDiv, itemsDiv); div.append(titleDiv, itemsDiv);
let emojis = sorted[category]; let emojis = sorted[category];
emojis.forEach(details => { emojis.forEach(emoji => {
let emoji = details.unified; //let emoji = details.unified;
//let emoji = (details.unified as string).split('-') //let emoji = (details.unified as string).split('-')
//.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), ''); //.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
@ -160,7 +153,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
let kek = RichTextProcessor.wrapRichText(emoji); let kek = RichTextProcessor.wrapRichText(emoji);
if(!kek.includes('emoji')) { if(!kek.includes('emoji')) {
console.log(details, emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji)); console.log(emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji));
return; return;
} }
@ -182,7 +175,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
let prevCategoryIndex = 1; let prevCategoryIndex = 1;
let menu = contentEmojiDiv.nextElementSibling.firstElementChild as HTMLUListElement; let menu = contentEmojiDiv.nextElementSibling.firstElementChild as HTMLUListElement;
let emojiScroll = new Scrollable(contentEmojiDiv, 'y', 500, 'EMOJI', null); let emojiScroll = new Scrollable(contentEmojiDiv, 'y', 'EMOJI', null);
emojiScroll.container.addEventListener('scroll', (e) => { emojiScroll.container.addEventListener('scroll', (e) => {
prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, emojiScroll.container); prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, emojiScroll.container);
}); });
@ -238,7 +231,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
let document = appDocsManager.getDoc(fileID); let document = appDocsManager.getDoc(fileID);
if(document._ != 'documentEmpty') { if(document._ != 'documentEmpty') {
appMessagesManager.sendFile(appImManager.peerID, document, {isMedia: true}); appMessagesManager.sendFile(appImManager.peerID, document, {isMedia: true});
appImManager.chatInputC.onMessageSent(true, false); appImManager.chatInputC.onMessageSent(false);
dropdown.classList.remove('active'); dropdown.classList.remove('active');
toggleEl.classList.remove('active'); toggleEl.classList.remove('active');
} else { } else {
@ -303,8 +296,6 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
if(prepend) stickersScroll.prepend(categoryDiv); if(prepend) stickersScroll.prepend(categoryDiv);
else stickersScroll.append(categoryDiv); else stickersScroll.append(categoryDiv);
setTimeout(() => lazyLoadQueue.check(), 0);
/* let scrollHeight = categoryDiv.scrollHeight; /* let scrollHeight = categoryDiv.scrollHeight;
let prevHeight = heights[heights.length - 1] || 0; let prevHeight = heights[heights.length - 1] || 0;
//console.log('scrollHeight', scrollHeight, categoryDiv, stickersDiv.childElementCount); //console.log('scrollHeight', scrollHeight, categoryDiv, stickersDiv.childElementCount);
@ -325,7 +316,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
concated.forEach((el, i) => { concated.forEach((el, i) => {
heights[i] = (heights[i - 1] || 0) + el.height + (i == 0 ? paddingTop : 0); heights[i] = (heights[i - 1] || 0) + el.height + (i == 0 ? paddingTop : 0);
}); */ }); */
let concated = Array.from(stickersScroll.splitUp.children); let concated = Array.from(stickersScroll.splitUp.children) as HTMLElement[];
concated.forEach((el, i) => { concated.forEach((el, i) => {
heights[i] = (heights[i - 1] || 0) + el.scrollHeight + (i == 0 ? paddingTop : 0); heights[i] = (heights[i - 1] || 0) + el.scrollHeight + (i == 0 ? paddingTop : 0);
}); });
@ -343,9 +334,8 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
}; };
let prevCategoryIndex = 0; let prevCategoryIndex = 0;
let stickersScroll = new Scrollable(contentStickersDiv, 'y', 500, 'STICKERS', undefined, undefined, 2); let stickersScroll = new Scrollable(contentStickersDiv, 'y', 'STICKERS', undefined, undefined, 2);
stickersScroll.container.addEventListener('scroll', (e) => { stickersScroll.container.addEventListener('scroll', (e) => {
lazyLoadQueue.check();
lottieLoader.checkAnimations(); lottieLoader.checkAnimations();
prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, stickersScroll.container, menuScroll); prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, stickersScroll.container, menuScroll);
@ -440,11 +430,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
masonry.addEventListener('click', onMediaClick); masonry.addEventListener('click', onMediaClick);
let scroll = new Scrollable(contentDiv, 'y', 500, 'GIFS', null); let scroll = new Scrollable(contentDiv, 'y', 'GIFS', null);
scroll.container.addEventListener('scroll', (e) => {
lazyLoadQueue.check();
});
let width = 400; let width = 400;
let maxSingleWidth = width - 100; let maxSingleWidth = width - 100;

79
src/components/lazyLoadQueue.ts

@ -1,5 +1,3 @@
import { isElementInViewport } from "../lib/utils";
type LazyLoadElement = { type LazyLoadElement = {
div: HTMLDivElement, div: HTMLDivElement,
load: () => Promise<any>, load: () => Promise<any>,
@ -17,14 +15,31 @@ export default class LazyLoadQueue {
private log = console.log.bind(console, '[LL]:'); private log = console.log.bind(console, '[LL]:');
private debug = false; private debug = false;
constructor(private parallelLimit = 5) { private observer: IntersectionObserver;
constructor(private parallelLimit = 5) {
this.observer = new IntersectionObserver(entries => {
for(let entry of entries) {
if(entry.isIntersecting) {
let target = entry.target as HTMLElement;
for(let item of this.lazyLoadMedia) {
if(item.div == target) {
item.wasSeen = true;
this.processQueue(item);
break;
}
}
}
}
});
} }
public clear() { public clear() {
this.tempID--; this.tempID--;
this.lazyLoadMedia.length = 0; this.lazyLoadMedia.length = 0;
this.loadingMedia = 0; this.loadingMedia = 0;
this.observer.disconnect();
} }
public length() { public length() {
@ -45,24 +60,13 @@ export default class LazyLoadQueue {
this.unlockResolve = null; this.unlockResolve = null;
} }
public async processQueue(id?: number) { public async processQueue(item?: LazyLoadElement) {
if(this.parallelLimit > 0 && this.loadingMedia >= this.parallelLimit) return; if(this.parallelLimit > 0 && this.loadingMedia >= this.parallelLimit) return;
let item: LazyLoadElement; if(item) {
let index: number; this.lazyLoadMedia.findAndSplice(i => i == item);
} else {
if(id !== undefined) item = this.lazyLoadMedia.splice(id, 1)[0]; item = this.lazyLoadMedia.findAndSplice(i => i.wasSeen);
else {
item = this.lazyLoadMedia.findAndSplice(i => isElementInViewport(i.div));
if(!item) {
let length = this.lazyLoadMedia.length;
for(index = length - 1; index >= 0; --index) {
if(this.lazyLoadMedia[index].wasSeen) {
item = this.lazyLoadMedia.splice(index, 1)[0];
break;
}
}
}
} }
if(item) { if(item) {
@ -73,17 +77,17 @@ export default class LazyLoadQueue {
this.debug && this.log('will load media', this.lockPromise, item); this.debug && this.log('will load media', this.lockPromise, item);
try { try {
if(this.lockPromise && false) { if(this.lockPromise/* && false */) {
let perf = performance.now(); let perf = performance.now();
await this.lockPromise; await this.lockPromise;
this.debug && this.log('waited lock:', performance.now() - perf); this.debug && this.log('waited lock:', performance.now() - perf);
} }
await new Promise((resolve, reject) => window.requestAnimationFrame(() => window.requestAnimationFrame(resolve))); //await new Promise((resolve, reject) => window.requestAnimationFrame(() => window.requestAnimationFrame(resolve)));
await item.load(); await item.load();
} catch(err) { } catch(err) {
console.error('loadMediaQueue error:', err, item, id, index); console.error('loadMediaQueue error:', err, item);
} }
if(tempID == this.tempID) { if(tempID == this.tempID) {
@ -98,39 +102,14 @@ export default class LazyLoadQueue {
} }
} }
public check(id?: number) {
if(id !== undefined) {
let {div, wasSeen} = this.lazyLoadMedia[id];
if(!wasSeen && isElementInViewport(div)) {
//console.log('will load div by id:', div, div.getBoundingClientRect());
this.lazyLoadMedia[id].wasSeen = true;
this.processQueue(id);
}
return;
}
let length = this.lazyLoadMedia.length;
for(let i = length - 1; i >= 0; --i) {
let {div, wasSeen} = this.lazyLoadMedia[i];
if(!wasSeen && isElementInViewport(div)) {
//console.log('will load div:', div);
this.lazyLoadMedia[i].wasSeen = true;
this.processQueue(i);
//this.lazyLoadMedia.splice(i, 1);
}
}
}
public push(el: LazyLoadElement) { public push(el: LazyLoadElement) {
let id = this.lazyLoadMedia.push(el) - 1; this.lazyLoadMedia.push(el);
if(el.wasSeen) { if(el.wasSeen) {
this.processQueue(id); this.processQueue(el);
} else { } else {
el.wasSeen = false; el.wasSeen = false;
this.check(id); this.observer.observe(el.div);
} }
} }
} }

420
src/components/scrollable copy.ts

@ -1,420 +0,0 @@
import { isElementInViewport, isScrolledIntoView, cancelEvent } from "../lib/utils";
export default class Scrollable {
public container: HTMLDivElement;
public thumb: HTMLDivElement;
public type: string;
public side: string;
public scrollType: string;
public scrollSide: string;
public clientAxis: string;
public scrollSize = -1;
public size = 0;
public thumbSize = 0;
public hiddenElements: {
up: {element: Element, height: number}[],
down: {element: Element, height: number}[]
} = {
up: [],
down: []
};
public paddings = {up: 0, down: 0};
public paddingTopDiv: HTMLDivElement;
public paddingBottomDiv: HTMLDivElement;
public splitUp: HTMLElement;
public splitOffset = 0;
public onAddedBottom: () => void = null;
public topObserver: IntersectionObserver;
public isTopIntersecting: boolean;
public bottomObserver: IntersectionObserver;
public isBottomIntersecting: boolean;
public splitObserver: IntersectionObserver;
constructor(public el: HTMLDivElement, x = false, y = true) {
this.container = document.createElement('div');
this.container.classList.add('scrollable');
let arr = [];
for(let i = 0.001; i < 1; i += 0.001) arr.push(i);
this.topObserver = new IntersectionObserver(entries => {
let entry = entries[0];
console.log('top intersection:', entries, this.isTopIntersecting, entry.isIntersecting, entry.intersectionRatio > 0);
if(this.isTopIntersecting = entry.isIntersecting) {
this.onTopIntersection(entry);
}
console.log('top intersection end');
}, {threshold: arr});
this.bottomObserver = new IntersectionObserver(entries => {
let entry = entries[0];
console.log('bottom intersection:', entries, this.isBottomIntersecting, entry.isIntersecting, entry.intersectionRatio > 0);
if(this.isBottomIntersecting = entry.isIntersecting) {
this.onBottomIntersection(entry);
if(this.onScrolledBottom) this.onScrolledBottom();
}
}, {threshold: arr});
this.splitObserver = new IntersectionObserver(entries => {
//console.log('splitObserver', entries);
for(let entry of entries) {
if(!entry.isIntersecting/* && entry.target.parentElement */) {
let child = entry.target;
console.log('onscroll entry', entry.boundingClientRect, child, entry);
let isTop = (entry.boundingClientRect.top + this.splitOffset) <= 0;
let isBottom = entry.rootBounds.height <= entry.boundingClientRect.top;
let height = child.scrollHeight;
let toPush = {element: child, height};
if(isTop) {
this.paddings.up += height;
this.hiddenElements.up.push(toPush);
child.parentElement.removeChild(child);
console.log('onscroll sliced up', child);
this.paddingTopDiv.style.height = this.paddings.up + 'px';
} else if(isBottom) {
this.paddings.down += height;
this.hiddenElements.down.unshift(toPush);
child.parentElement.removeChild(child);
console.log('onscroll sliced down', child);
this.paddingBottomDiv.style.height = this.paddings.down + 'px';
}
//console.log('splitObserver', entry, entry.target, isTop);
}
}
});
if(x) {
this.container.classList.add('scrollable-x');
this.type = 'width';
this.side = 'left';
this.scrollType = 'scrollWidth';
this.scrollSide = 'scrollLeft';
this.clientAxis = 'clientX';
let scrollHorizontally = (e: any) => {
e = window.event || e;
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
this.container.scrollLeft -= (delta * 20);
e.preventDefault();
};
if(this.container.addEventListener) {
// IE9, Chrome, Safari, Opera
this.container.addEventListener("mousewheel", scrollHorizontally, false);
// Firefox
this.container.addEventListener("DOMMouseScroll", scrollHorizontally, false);
} else {
// IE 6/7/8
// @ts-ignore
this.container.attachEvent("onmousewheel", scrollHorizontally);
}
} else if(y) {
this.container.classList.add('scrollable-y');
this.type = 'height';
this.side = 'top';
this.scrollType = 'scrollHeight';
this.scrollSide = 'scrollTop';
this.clientAxis = 'clientY';
} else {
throw new Error('no side for scroll');
}
this.thumb = document.createElement('div');
this.thumb.className = 'scrollbar-thumb';
// @ts-ignore
this.thumb.style[this.type] = '30px';
let onMouseMove = (e: MouseEvent) => {
let rect = this.thumb.getBoundingClientRect();
let diff: number;
// @ts-ignore
diff = e[this.clientAxis] - rect[this.side];
// @ts-ignore
this.container[this.scrollSide] += diff * 0.5;
console.log('onMouseMove', e, diff);
cancelEvent(e);
};
this.thumb.addEventListener('mousedown', () => {
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', () => {
window.removeEventListener('mousemove', onMouseMove);
}, {once: true});
});
//this.container.addEventListener('mouseover', this.resize.bind(this)); // omg
window.addEventListener('resize', this.resize.bind(this));
this.paddingTopDiv = document.createElement('div');
this.paddingTopDiv.classList.add('scroll-padding');
this.paddingBottomDiv = document.createElement('div');
this.paddingBottomDiv.classList.add('scroll-padding');
this.topObserver.observe(this.paddingTopDiv);
this.bottomObserver.observe(this.paddingBottomDiv);
this.container.addEventListener('scroll', this.onScroll.bind(this));
//this.container.append(this.paddingTopDiv);
Array.from(el.children).forEach(c => this.container.append(c));
//this.container.append(this.paddingBottomDiv);
el.append(this.container);//container.append(el);
this.container.parentElement.append(this.thumb);
this.resize();
}
public resize() {
console.time('scroll resize');
// @ts-ignore
this.scrollSize = this.container[this.scrollType];
let rect = this.container.getBoundingClientRect();
// @ts-ignore
this.size = rect[this.type];
if(!this.size || this.size == this.scrollSize) {
this.thumbSize = 0;
// @ts-ignore
this.thumb.style[this.type] = this.thumbSize + 'px';
console.timeEnd('scroll resize');
return;
}
//if(!height) return;
let divider = this.scrollSize / this.size / 0.5;
this.thumbSize = this.size / divider;
if(this.thumbSize < 20) this.thumbSize = 20;
// @ts-ignore
this.thumb.style[this.type] = this.thumbSize + 'px';
console.timeEnd('scroll resize');
// @ts-ignore
//console.log('onresize', thumb.style[type], thumbHeight, height);
}
public setVirtualContainer(el?: HTMLElement) {
this.splitUp = el;
this.hiddenElements.up.length = this.hiddenElements.down.length = 0;
this.paddings.up = this.paddings.down = 0;
if(this.paddingTopDiv.parentElement) {
this.paddingTopDiv.style.height = '';
this.paddingBottomDiv.style.height = '';
}
/* this.topObserver.unobserve(this.paddingTopDiv);
this.bottomObserver.unobserve(this.paddingBottomDiv);
this.topObserver.observe(this.paddingTopDiv);
this.bottomObserver.observe(this.paddingBottomDiv); */
if(el) {
el.parentElement.insertBefore(this.paddingTopDiv, el);
el.parentNode.insertBefore(this.paddingBottomDiv, el.nextSibling);
}
}
public onScroll() {
// @ts-ignore
//let st = container[scrollSide];
//console.time('scroll onScroll');
// @ts-ignore
if(this.container[this.scrollType] != this.scrollSize || this.thumbSize == 0) {
this.resize();
}
// @ts-ignore
let value = this.container[this.scrollSide] / (this.scrollSize - this.size) * 100;
let maxValue = 100 - (this.thumbSize / this.size * 100);
//console.log('onscroll', container.scrollHeight, thumbHeight, height, value, maxValue);
// @ts-ignore
this.thumb.style[this.side] = (value >= maxValue ? maxValue : value) + '%';
//console.timeEnd('scroll onScroll');
}
public async onTopIntersection2(entry: IntersectionObserverEntry) {
console.log('onTopIntersection');
if(this.hiddenElements.up.length && this.paddings.up) {
//while(this.isTopIntersecting && this.paddings.up) {
let child = this.hiddenElements.up.pop();
console.log('top returning from hidden', child);
if(!child) {
this.paddings.up = 0;
this.paddingTopDiv.style.height = '0px';
return;
}
/* await new Promise((resolve, reject) => {
window.requestAnimationFrame(resolve);
}); */
this.splitUp.prepend(child.element);
this.paddings.up -= child.height;
this.paddingTopDiv.style.height = this.paddings.up + 'px';
//}
} else {
this.paddingTopDiv.style.height = '0px';
}
}
public async onTopIntersection(entry: IntersectionObserverEntry) {
console.log('onTopIntersection');
if(this.hiddenElements.up.length && this.paddings.up) {
let needHeight = entry.intersectionRect.height + this.splitOffset;
while(needHeight > 0 && this.paddings.up) {
let child = this.hiddenElements.up.pop();
console.log('top returning from hidden', child);
if(!child) {
this.paddings.up = 0;
this.paddingTopDiv.style.height = '0px';
break;
}
/* await new Promise((resolve, reject) => {
window.requestAnimationFrame(resolve);
}); */
this.splitUp.prepend(child.element);
let height = child.height || child.element.scrollHeight;
needHeight -= height;
this.paddings.up -= height;
this.paddingTopDiv.style.height = this.paddings.up + 'px';
}
} else {
this.paddingTopDiv.style.height = '0px';
}
}
public onBottomIntersection2() {
console.log('onBottomIntersection');
if(this.hiddenElements.down.length && this.paddings.down) {
//while(this.isBottomIntersecting && this.paddings.down) {
let child = this.hiddenElements.down.shift();
if(!child) {
this.paddings.down = 0;
this.paddingBottomDiv.style.height = '0px';
return;//break;
}
this.splitUp.append(child.element);
this.paddings.down -= child.height;
this.paddingBottomDiv.style.height = this.paddings.down + 'px';
//}
if(this.onAddedBottom) this.onAddedBottom();
} else {
this.paddingBottomDiv.style.height = '0px';
}
}
public onBottomIntersection(entry: IntersectionObserverEntry) {
console.log('onBottomIntersection');
if(this.hiddenElements.down.length && this.paddings.down) {
let needHeight = entry.intersectionRect.height + this.splitOffset;
while(needHeight > 0 && this.paddings.down) {
let child = this.hiddenElements.down.shift();
if(!child) {
this.paddings.down = 0;
this.paddingBottomDiv.style.height = '0px';
break;
}
this.splitUp.append(child.element);
let height = child.height || child.element.scrollHeight;
needHeight -= height;
this.paddings.down -= height;
this.paddingBottomDiv.style.height = this.paddings.down + 'px';
}
if(this.onAddedBottom) this.onAddedBottom();
} else {
this.paddingBottomDiv.style.height = '0px';
}
}
public onScrolledBottom() {
}
public splitAppend(...smth: (string | Node)[]) {
this.splitUp.append(...smth);
for(let node of smth) {
if(typeof(node) !== 'string') {
this.splitObserver.observe(node as Element);
}
}
}
set scrollTop(y: number) {
this.container.scrollTop = y;
}
get scrollTop() {
return this.container.scrollTop;
}
get scrollHeight() {
return this.container.scrollHeight;
}
get parentElement() {
return this.container.parentElement;
}
get offsetHeight() {
return this.container.offsetHeight;
}
}

139
src/components/scrollable_new.ts

@ -1,4 +1,4 @@
import { logger, deferredPromise, CancellablePromise } from "../lib/polyfill"; import { logger } from "../lib/polyfill";
import smoothscroll from '../lib/smoothscroll'; import smoothscroll from '../lib/smoothscroll';
(window as any).__forceSmoothScrollPolyfill__ = true; (window as any).__forceSmoothScrollPolyfill__ = true;
smoothscroll.polyfill(); smoothscroll.polyfill();
@ -30,35 +30,23 @@ Array.from($0.querySelectorAll('.bubble__container')).forEach(_el => {
export default class Scrollable { export default class Scrollable {
public container: HTMLDivElement; public container: HTMLDivElement;
public type: string;
public side: string;
public translate: string;
public scrollType: string;
public scrollSide: string;
public clientAxis: string;
public clientSize: string;
public scrollSize = -1; // it will be scrollHeight
public size = 0; // it will be outerHeight of container (not scrollHeight)
public splitUp: HTMLElement; public splitUp: HTMLElement;
public onScrolledTop: () => void = null; public onScrolledTop: () => void = null;
public onScrolledBottom: () => void = null; public onScrolledBottom: () => void = null;
public onScrolledTopFired = false;
public onScrolledBottomFired = false;
public onScrollMeasure: number = null; public onScrollMeasure: number = null;
public lastScrollTop: number = 0; public lastScrollTop: number = 0;
public scrollTopOffset: number = 0;
private disableHoverTimeout: number = 0; private disableHoverTimeout: number = 0;
private log: ReturnType<typeof logger>; private log: ReturnType<typeof logger>;
private debug = false; private debug = false;
private measureMutex: CancellablePromise<void>; private sentinelsObserver: IntersectionObserver;
private topSentinel: HTMLDivElement;
private bottomSentinel: HTMLDivElement;
private observer: IntersectionObserver; private observer: IntersectionObserver;
private visible: Set<HTMLElement>; private visible: Set<HTMLElement>;
@ -86,7 +74,7 @@ export default class Scrollable {
this.visible.delete(element); this.visible.delete(element);
} }
constructor(public el: HTMLElement, axis: 'y' | 'x' = 'y', public splitOffset = 300, logPrefix = '', public appendTo = el, public onScrollOffset = splitOffset, public splitCount = 15) { constructor(public el: HTMLElement, axis: 'y' | 'x' = 'y', logPrefix = '', public appendTo = el, public onScrollOffset = 300, public splitCount = 15) {
this.container = document.createElement('div'); this.container = document.createElement('div');
this.container.classList.add('scrollable'); this.container.classList.add('scrollable');
@ -161,20 +149,10 @@ export default class Scrollable {
} }
this.log = logger('SCROLL' + (logPrefix ? '-' + logPrefix : '')); this.log = logger('SCROLL' + (logPrefix ? '-' + logPrefix : ''));
this.measureMutex = deferredPromise<void>();
this.measureMutex.resolve();
if(axis == 'x') { if(axis == 'x') {
this.container.classList.add('scrollable-x'); this.container.classList.add('scrollable-x');
this.type = 'width';
this.side = 'left';
this.translate = 'translateX';
this.scrollType = 'scrollWidth';
this.scrollSide = 'scrollLeft';
this.clientAxis = 'clientX';
this.clientSize = 'clientWidth';
let scrollHorizontally = (e: any) => { let scrollHorizontally = (e: any) => {
e = window.event || e; e = window.event || e;
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail))); var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
@ -193,24 +171,11 @@ export default class Scrollable {
} }
} else if(axis == 'y') { } else if(axis == 'y') {
this.container.classList.add('scrollable-y'); this.container.classList.add('scrollable-y');
this.type = 'height';
this.side = 'top';
this.translate = 'translateY';
this.scrollType = 'scrollHeight';
this.scrollSide = 'scrollTop';
this.clientAxis = 'clientY';
this.clientSize = 'clientHeight';
} else { } else {
throw new Error('no side for scroll'); throw new Error('no side for scroll');
} }
//this.container.addEventListener('mouseover', this.resize.bind(this)); // omg window.addEventListener('resize', () => this.onScroll());
window.addEventListener('resize', () => {
window.requestAnimationFrame(() => {
this.onScroll();
});
});
this.container.addEventListener('scroll', () => this.onScroll(), {passive: true, capture: true}); this.container.addEventListener('scroll', () => this.onScroll(), {passive: true, capture: true});
Array.from(el.children).forEach(c => this.container.append(c)); Array.from(el.children).forEach(c => this.container.append(c));
@ -219,12 +184,41 @@ export default class Scrollable {
//this.onScroll(); //this.onScroll();
} }
public attachSentinels(container = this.container, offset = this.onScrollOffset) {
if(!this.sentinelsObserver) {
this.topSentinel = document.createElement('div');
this.topSentinel.classList.add('scrollable-sentinel');
this.topSentinel.style.top = offset + 'px';
this.bottomSentinel = document.createElement('div');
this.bottomSentinel.classList.add('scrollable-sentinel');
this.bottomSentinel.style.bottom = offset + 'px';
this.container.append(this.topSentinel, this.bottomSentinel);
this.sentinelsObserver = new IntersectionObserver(entries => {
for(let entry of entries) {
if(entry.isIntersecting) {
let top = entry.target == this.topSentinel;
if(top) {
this.onScrolledTop && this.onScrolledTop();
} else {
this.onScrolledBottom && this.onScrolledBottom();
}
}
}
});
this.sentinelsObserver.observe(this.topSentinel);
this.sentinelsObserver.observe(this.bottomSentinel);
}
container.prepend(this.topSentinel);
container.append(this.bottomSentinel);
}
public setVirtualContainer(el?: HTMLElement) { public setVirtualContainer(el?: HTMLElement) {
this.splitUp = el; this.splitUp = el;
this.onScrolledBottomFired = this.onScrolledTopFired = false;
this.lastScrollTop = 0; this.lastScrollTop = 0;
this.log('setVirtualContainer:', el, this); this.log('setVirtualContainer:', el, this);
} }
@ -250,52 +244,11 @@ export default class Scrollable {
this.disableHoverTimeout = setTimeout(() => { this.disableHoverTimeout = setTimeout(() => {
appendTo.classList.remove('disable-hover'); appendTo.classList.remove('disable-hover');
this.lastScrollDirection = 0; this.lastScrollDirection = 0;
if(!this.measureMutex.isFulfilled) {
this.measureMutex.resolve();
}
}, 100); }, 100);
if(this.onScrollMeasure) return; //window.cancelAnimationFrame(this.onScrollMeasure);
this.onScrollMeasure = window.requestAnimationFrame(() => {
// @ts-ignore
let scrollPos = this.container[this.scrollSide];
//if(this.measureMutex.isFulfilled) { if(!this.splitUp || this.onScrollMeasure) return;
// @ts-ignore quick brown fix this.onScrollMeasure = window.requestAnimationFrame(() => {
this.size = this.container[this.clientSize]; let scrollTop = this.container.scrollTop;
// @ts-ignore
let scrollSize = this.container[this.scrollType];
this.scrollSize = scrollSize;
//this.measureMutex = deferredPromise<void>();
//}
let scrollTop = scrollPos - this.scrollTopOffset;
let maxScrollTop = this.scrollSize - this.scrollTopOffset - this.size;
if(this.onScrolledBottom && !this.scrollLocked) {
if((maxScrollTop - scrollTop) <= this.onScrollOffset) {
//if(!this.onScrolledBottomFired) {
this.onScrolledBottomFired = true;
this.onScrolledBottom();
//}
} else {
this.onScrolledBottomFired = false;
}
}
if(this.onScrolledTop && !this.scrollLocked) {
//this.log('onScrolledTop:', scrollTop, this.onScrollOffset);
if(scrollTop <= this.onScrollOffset) {
this.onScrolledTopFired = true;
this.onScrolledTop();
} else {
this.onScrolledTopFired = false;
}
}
if(this.lastScrollTop != scrollTop) { if(this.lastScrollTop != scrollTop) {
this.lastScrollDirection = this.lastScrollTop < scrollTop ? 1 : -1; this.lastScrollDirection = this.lastScrollTop < scrollTop ? 1 : -1;
this.lastScrollTop = scrollTop; this.lastScrollTop = scrollTop;
@ -385,8 +338,8 @@ export default class Scrollable {
} }
} }
public scrollTo(top: number, smooth = true) { public scrollTo(top: number, smooth = true, important = false) {
if(this.scrollLocked) return; if(this.scrollLocked && !important) return;
let scrollTop = this.scrollTop; let scrollTop = this.scrollTop;
if(scrollTop == Math.floor(top)) { if(scrollTop == Math.floor(top)) {

270
src/components/scrollable_spliceCount.ts

@ -1,270 +0,0 @@
import { isElementInViewport } from "../lib/utils";
export default class Scrollable {
public container: HTMLDivElement;
public thumb: HTMLDivElement;
public type: string;
public side: string;
public scrollType: string;
public scrollSide: string;
public scrollSize = -1;
public size = 0;
public thumbSize = 0;
public hiddenElements: {
up: Element[],
down: Element[]
} = {
up: [],
down: []
};
public paddings = {up: 0, down: 0};
public paddingTopDiv: HTMLDivElement;
public paddingBottomDiv: HTMLDivElement;
public splitUp: HTMLElement;
public spliceCount = 1;
public useStylePadding = false;
public useOneHeight = false;
constructor(public el: HTMLDivElement, x = false, y = true) {
this.container = document.createElement('div');
this.container.classList.add('scrollable');
if(x) {
this.container.classList.add('scrollable-x');
this.type = 'width';
this.side = 'left';
this.scrollType = 'scrollWidth';
this.scrollSide = 'scrollLeft';
} else if(y) {
this.container.classList.add('scrollable-y');
this.type = 'height';
this.side = 'top';
this.scrollType = 'scrollHeight';
this.scrollSide = 'scrollTop';
} else {
throw new Error('no side for scroll');
}
this.thumb = document.createElement('div');
this.thumb.className = 'scrollbar-thumb';
// @ts-ignore
this.thumb.style[this.type] = '30px';
this.container.addEventListener('mouseover', this.resize.bind(this));
window.addEventListener('resize', this.resize.bind(this));
this.paddingTopDiv = document.createElement('div');
this.paddingTopDiv.classList.add('scroll-padding');
this.paddingBottomDiv = document.createElement('div');
this.paddingBottomDiv.classList.add('scroll-padding');
this.container.addEventListener('scroll', this.onScroll.bind(this));
this.container.append(this.paddingTopDiv);
Array.from(el.children).forEach(c => this.container.append(c));
this.container.append(this.paddingBottomDiv);
el.append(this.container);//container.append(el);
this.container.parentElement.append(this.thumb);
this.resize();
}
public resize() {
// @ts-ignore
this.scrollSize = this.container[this.scrollType];
let rect = this.container.getBoundingClientRect();
// @ts-ignore
this.size = rect[this.type];
if(!this.size || this.size == this.scrollSize) {
this.thumbSize = 0;
// @ts-ignore
this.thumb.style[this.type] = this.thumbSize + 'px';
return;
}
//if(!height) return;
let divider = this.scrollSize / this.size / 0.5;
this.thumbSize = this.size / divider;
if(this.thumbSize < 20) this.thumbSize = 20;
// @ts-ignore
this.thumb.style[this.type] = this.thumbSize + 'px';
// @ts-ignore
//console.log('onresize', thumb.style[type], thumbHeight, height);
}
public setVirtualContainer(el: HTMLElement, spliceCount = 1, useStylePadding = false, useOneHeight = false) {
this.splitUp = el;
this.hiddenElements = {
up: [],
down: []
};
this.paddings = {
up: 0,
down: 0
};
this.spliceCount = spliceCount;
this.useStylePadding = useStylePadding;
this.useOneHeight = useOneHeight;
if(this.paddingTopDiv.parentElement) {
this.paddingTopDiv.style.height = '';
this.paddingBottomDiv.style.height = '';
}
/* if(useStylePadding) {
this.paddingTopDiv.parentElement.removeChild(this.paddingTopDiv);
this.paddingBottomDiv.parentElement.removeChild(this.paddingBottomDiv);
} else { */
el.parentElement.insertBefore(this.paddingTopDiv, el);
el.parentNode.insertBefore(this.paddingBottomDiv, el.nextSibling);
//}
if(useStylePadding) {
this.paddingTopDiv.style.height = '10px';
this.paddingBottomDiv.style.height = '10px';
}
}
public onScroll() {
// @ts-ignore
//let st = container[scrollSide];
if(this.container[this.scrollType] != this.scrollSize || this.thumbSize == 0) {
this.resize();
}
// @ts-ignore
let value = this.container[this.scrollSide] / (this.scrollSize - this.size) * 100;
let maxValue = 100 - (this.thumbSize / this.size * 100);
//console.log('onscroll', container.scrollHeight, thumbHeight, height, value, maxValue);
// @ts-ignore
this.thumb.style[this.side] = (value >= maxValue ? maxValue : value) + '%';
if(!this.splitUp) {
return;
}
let splitUp = this.splitUp;
let children = Array.from(splitUp.children) as HTMLElement[];
let firstVisible = -1, lastVisible = -1;
let length = children.length;
for(let i = 0; i < length; ++i) {
let child = children[i];
if(isElementInViewport(child)) {
if(firstVisible < 0) firstVisible = i;
lastVisible = i;
}
}
console.log('onscroll', firstVisible, lastVisible);
if(firstVisible > 0) {
let sliced = children.slice(0, firstVisible);
let height = 0, singleHeight = sliced[0].scrollHeight;
for(let child of sliced) {
height += child.scrollHeight;
this.hiddenElements.up.push(child);
child.parentElement.removeChild(child);
}
this.paddings.up += this.useOneHeight ? singleHeight : height;
//console.log('sliced up', sliced.length);
//sliced.forEach(child => child.style.display = 'none');
if(this.useStylePadding) splitUp.style.paddingTop = this.paddings.up + 'px';
else this.paddingTopDiv.style.height = this.paddings.up + 'px';
//console.log('onscroll need to add padding: ', paddings.up);
} else if(this.hiddenElements.up.length) {
console.log('onscroll up', isElementInViewport(this.paddingTopDiv), this.paddings.up);
while(isElementInViewport(this.paddingTopDiv) && this.paddings.up) {
//let child = this.hiddenElements.up.pop();
/*
splitUp.prepend(...childs);
this.paddings.up -= child.scrollHeight;
this.paddingTopDiv.style.height = this.paddings.up + 'px';*/
let childs = this.hiddenElements.up.splice(-this.spliceCount).reverse();
let height = 0;
for(let child of childs) {
splitUp.prepend(child);
height += child.scrollHeight;
}
this.paddings.up -= this.useOneHeight ? childs[0].scrollHeight : height;
if(this.useStylePadding) splitUp.style.paddingTop = this.paddings.up + 'px';
else this.paddingTopDiv.style.height = this.paddings.up + 'px';
}
}
if(lastVisible < (length - 1)) {
let sliced = children.slice(lastVisible + 1, this.useOneHeight ? lastVisible + 1 + this.spliceCount : undefined).reverse();
let height = 0, singleHeight = sliced[0].scrollHeight;
for(let child of sliced) {
height += child.scrollHeight;
this.hiddenElements.down.unshift(child);
child.parentElement.removeChild(child);
}
this.paddings.down += this.useOneHeight ? singleHeight : height;
console.log('onscroll sliced down', splitUp, sliced.length, this.paddings.down + 'px');
//sliced.forEach(child => child.style.display = 'none');
/* if(this.useStylePadding) splitUp.style.paddingBottom = this.paddings.down + 'px';
else */ this.paddingBottomDiv.style.height = this.paddings.down + 'px';
//console.log('onscroll need to add padding: ', paddings.up);
} else if(this.hiddenElements.down.length) {
console.log('onscroll down', isElementInViewport(this.paddingBottomDiv), this.paddings.down, this.hiddenElements);
while(isElementInViewport(this.paddingBottomDiv) && this.paddings.down) {
/* let child = this.hiddenElements.down.shift();
splitUp.append(child);
this.paddings.down -= child.scrollHeight;
this.paddingBottomDiv.style.height = this.paddings.down + 'px'; */
let childs = this.hiddenElements.down.splice(0, this.spliceCount);
let height = 0;
for(let child of childs) {
splitUp.append(child);
height += child.scrollHeight;
}
this.paddings.down -= this.useOneHeight ? childs[0].scrollHeight : height;
/* if(this.useStylePadding) splitUp.style.paddingBottom = this.paddings.down + 'px';
else */ this.paddingBottomDiv.style.height = this.paddings.down + 'px';
}
}
//console.log('onscroll', container, firstVisible, lastVisible, hiddenElements);
//lastScrollPos = st;
}
}

11
src/components/wrappers.ts

@ -165,6 +165,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
if(doc.type == 'gif') { if(doc.type == 'gif') {
video.autoplay = true; video.autoplay = true;
video.loop = true; video.loop = true;
video.play();
} else if(doc.type == 'round') { } else if(doc.type == 'round') {
//video.dataset.ckin = doc.type == 'round' ? 'circle' : 'default'; //video.dataset.ckin = doc.type == 'round' ? 'circle' : 'default';
video.dataset.ckin = 'circle'; video.dataset.ckin = 'circle';
@ -430,8 +431,9 @@ export function wrapVoiceMessage(doc: MTDocument, withTime = false): HTMLDivElem
let index = 0; let index = 0;
let skipped = 0; let skipped = 0;
let h = '';
for(let uint8 of wave) { for(let uint8 of wave) {
if (index > 0 && index % 4 == 0) { if(index > 0 && index % 4 == 0) {
++index; ++index;
++skipped; ++skipped;
continue; continue;
@ -443,12 +445,13 @@ export function wrapVoiceMessage(doc: MTDocument, withTime = false): HTMLDivElem
height = 2; height = 2;
} }
svg.insertAdjacentHTML('beforeend', ` h += `
<rect x="${(index - skipped) * 4}" y="${23 - height}" width="2" height="${height}" rx="1" ry="1"></rect> <rect x="${(index - skipped) * 4}" y="${23 - height}" width="2" height="${height}" rx="1" ry="1"></rect>
`); `;
++index; ++index;
} }
svg.insertAdjacentHTML('beforeend', h);
let progress = div.querySelector('.audio-waveform') as HTMLDivElement; let progress = div.querySelector('.audio-waveform') as HTMLDivElement;
@ -772,7 +775,7 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: (
console.timeEnd('render sticker' + doc.id); console.timeEnd('render sticker' + doc.id);
if(div.firstElementChild && div.firstElementChild.tagName != 'CANVAS') { if(div.firstElementChild && div.firstElementChild.tagName == 'IMG') {
div.firstElementChild.remove(); div.firstElementChild.remove();
} }

2
src/countries.json

File diff suppressed because one or more lines are too long

2
src/emoji.json

File diff suppressed because one or more lines are too long

59
src/format_jsons.js

@ -2,7 +2,7 @@ let emoji = require('./emoji_pretty.json');
//let countries = require('./countries_pretty.json'); //let countries = require('./countries_pretty.json');
let countries = require('fs').readFileSync('./countries.dat').toString(); let countries = require('fs').readFileSync('./countries.dat').toString();
console.log(countries); //console.log(countries);
//console.log(emoji, countries); //console.log(emoji, countries);
@ -23,7 +23,7 @@ let formatted = emoji.filter(e => e.has_img_apple);
require('fs').writeFileSync('./emoji.json', JSON.stringify(formatted)); */ require('fs').writeFileSync('./emoji.json', JSON.stringify(formatted)); */
{ if(false) {
let obj = {}; let obj = {};
formatted.forEach(e => { formatted.forEach(e => {
let {unified, name, short_names, category, sheet_x, sheet_y, sort_order} = e; let {unified, name, short_names, category, sheet_x, sheet_y, sort_order} = e;
@ -45,6 +45,59 @@ require('fs').writeFileSync('./emoji.json', JSON.stringify(formatted)); */
require('fs').writeFileSync('./emoji.json', JSON.stringify(obj)); require('fs').writeFileSync('./emoji.json', JSON.stringify(obj));
} }
{
let categories = {
"Smileys & Emotion": 1
, "Animals & Nature": 2
, "Food & Drink": 3
, "Travel & Places": 4
, "Activities": 5
, "Objects": 6
, "Symbols": 6
, "Flags": 7
, "Skin Tones": 8
};
let maxObjectsIndex = -1;
formatted.forEach(e => {
if(e.category == 'Objects') {
if(e.sort_order > maxObjectsIndex) {
maxObjectsIndex = e.sort_order;
}
}
});
formatted.forEach(e => {
if(e.category == 'Symbols') {
e.sort_order += maxObjectsIndex;
}
});
formatted.forEach(e => {
if(e.skin_variations) {
for(let i in e.skin_variations) {
formatted.push(e.skin_variations[i]);
}
}
});
let obj = {};
formatted.forEach(e => {
let {unified, name, short_names, category, sheet_x, sheet_y, sort_order} = e;
let emoji = unified.replace(/-FE0F/gi, '').split('-')
.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
let c = categories[category] === undefined ? 9 : categories[category];
//obj[emoji] = '' + c + sort_order;
//obj[emoji] = +('' + (c * 1000 + sort_order)).replace(/0+/g, '0').replace(/^(\d)0(\d)/g, '$1$2');
obj[emoji] = e.sort_order !== undefined ? +('' + c + sort_order) : 0;
});
console.log(obj);
require('fs').writeFileSync('./emoji.json', JSON.stringify(obj));
}
/* { /* {
let obj = {}; let obj = {};
formatted.forEach(e => { formatted.forEach(e => {
@ -89,7 +142,7 @@ require('fs').writeFileSync('./emoji.json', JSON.stringify(formatted)); */
}; };
arr.push(item); arr.push(item);
console.log(item); //console.log(item);
}); });
require('fs').writeFileSync('./countries.json', JSON.stringify(arr)); require('fs').writeFileSync('./countries.json', JSON.stringify(arr));

2
src/lib/appManagers/apiUpdatesManager.ts

@ -499,7 +499,7 @@ export class ApiUpdatesManager {
this.attached = true; this.attached = true;
apiManager.setUpdatesProcessor(this.processUpdateMessage.bind(this)); apiManager.setUpdatesProcessor(this.processUpdateMessage.bind(this));
if(!state) { if(!state || !state.pts || !state.date || !state.seq) {
apiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then((stateResult: any) => { apiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then((stateResult: any) => {
this.updatesState.seq = stateResult.seq; this.updatesState.seq = stateResult.seq;
this.updatesState.pts = stateResult.pts; this.updatesState.pts = stateResult.pts;

74
src/lib/appManagers/appDialogsManager.ts

@ -1,4 +1,4 @@
import { findUpClassName, $rootScope, escapeRegExp, whichChild, findUpTag } from "../utils"; import { findUpClassName, $rootScope, escapeRegExp, whichChild, findUpTag, cancelEvent } from "../utils";
import appImManager, { AppImManager } from "./appImManager"; import appImManager, { AppImManager } from "./appImManager";
import appPeersManager from './appPeersManager'; import appPeersManager from './appPeersManager';
import appMessagesManager, { AppMessagesManager, Dialog } from "./appMessagesManager"; import appMessagesManager, { AppMessagesManager, Dialog } from "./appMessagesManager";
@ -401,30 +401,20 @@ export class AppDialogsManager {
constructor() { constructor() {
this.chatsPreloader = putPreloader(null, true); this.chatsPreloader = putPreloader(null, true);
//this.chatsContainer.append(this.chatsPreloader);
this.pinnedDelimiter = document.createElement('div'); this.pinnedDelimiter = document.createElement('div');
this.pinnedDelimiter.classList.add('pinned-delimiter'); this.pinnedDelimiter.classList.add('pinned-delimiter');
this.pinnedDelimiter.appendChild(document.createElement('span')); this.pinnedDelimiter.appendChild(document.createElement('span'));
//this.chatsLoadCount = Math.round(document.body.scrollHeight / 70 * 1.5); this.scroll = new Scrollable(this.chatsContainer, 'y', 'CL', this.chatList, 500);
let splitOffset = 1110;
this.scroll = new Scrollable(this.chatsContainer, 'y', splitOffset, 'CL', this.chatList, 500);
this.scroll.setVirtualContainer(this.chatList); this.scroll.setVirtualContainer(this.chatList);
this.scroll.onScrolledBottom = this.onChatsScroll.bind(this); this.scroll.onScrolledBottom = this.onChatsScroll.bind(this);
/* this.chatsHidden = this.scroll.hiddenElements; this.scroll.attachSentinels();
this.chatsVisible = this.scroll.visibleElements; */
this.scrollArchived = new Scrollable(this.chatsArchivedContainer, 'y', splitOffset, 'CLA', this.chatListArchived, 500); this.scrollArchived = new Scrollable(this.chatsArchivedContainer, 'y', 'CLA', this.chatListArchived, 500);
this.scrollArchived.setVirtualContainer(this.chatListArchived); this.scrollArchived.setVirtualContainer(this.chatListArchived);
this.scrollArchived.onScrolledBottom = this.onChatsArchivedScroll.bind(this); this.scrollArchived.onScrolledBottom = this.onChatsArchivedScroll.bind(this);
/* this.chatsArchivedHidden = this.scrollArchived.hiddenElements; this.scroll.attachSentinels();
this.chatsArchivedVisible = this.scrollArchived.visibleElements; */
//this.scrollArchived.container.addEventListener('scroll', this.onChatsArchivedScroll.bind(this));
//let chatClosedDiv = document.getElementById('chat-closed');
this.setListClickListener(this.chatList); this.setListClickListener(this.chatList);
this.setListClickListener(this.chatListArchived); this.setListClickListener(this.chatListArchived);
@ -611,11 +601,6 @@ export class AppDialogsManager {
else this.loadedAll = true; else this.loadedAll = true;
} }
/* if(archived) {
let count = result.count;
this.archivedCount.innerText = '' + count;
} */
this.log('getDialogs ' + loadCount + ' dialogs by offset:', offsetIndex, result, this.scroll.length, archived); this.log('getDialogs ' + loadCount + ' dialogs by offset:', offsetIndex, result, this.scroll.length, archived);
this.scroll.onScroll(); this.scroll.onScroll();
} catch(err) { } catch(err) {
@ -627,20 +612,21 @@ export class AppDialogsManager {
} }
public onChatsScroll() { public onChatsScroll() {
if(this.loadedAll /* || this.scroll.hiddenElements.down.length > 0 */ || this.loadDialogsPromise/* || 1 == 1 */) return; if(this.loadedAll || this.loadDialogsPromise) return;
this.loadDialogs(); this.loadDialogs();
} }
public onChatsArchivedScroll() { public onChatsArchivedScroll() {
if(this.loadedArchivedAll /* || this.scrollArchived.hiddenElements.down.length > 0 */ || this.loadDialogsPromise/* || 1 == 1 */) return; if(this.loadedArchivedAll || this.loadDialogsPromise) return;
this.loadDialogs(true); this.loadDialogs(true);
} }
public setListClickListener(list: HTMLUListElement, onFound?: () => void) { public setListClickListener(list: HTMLUListElement, onFound?: () => void) {
list.addEventListener('click', (e: Event) => { list.addEventListener('click', (e: Event) => {
//return; cancelEvent(e);
console.log('dialogs click list'); console.log('dialogs click list');
let target = e.target as HTMLElement; let target = e.target as HTMLElement;
let elem = target.classList.contains('rp') ? target : findUpClassName(target, 'rp'); let elem = target.classList.contains('rp') ? target : findUpClassName(target, 'rp');
@ -657,14 +643,9 @@ export class AppDialogsManager {
this.lastActiveListElement.classList.remove('active'); this.lastActiveListElement.classList.remove('active');
} }
let startTime = Date.now();
let result: ReturnType<AppImManager['setPeer']>; let result: ReturnType<AppImManager['setPeer']>;
//console.log('appDialogsManager: lock lazyLoadQueue'); //console.log('appDialogsManager: lock lazyLoadQueue');
if(elem) { if(elem) {
/* if(chatClosedDiv) {
chatClosedDiv.style.display = 'none';
} */
if(onFound) onFound(); if(onFound) onFound();
let peerID = +elem.getAttribute('data-peerID'); let peerID = +elem.getAttribute('data-peerID');
@ -677,38 +658,14 @@ export class AppDialogsManager {
result = appImManager.setPeer(peerID, lastMsgID); result = appImManager.setPeer(peerID, lastMsgID);
if(result instanceof Promise) { /* if(result instanceof Promise) {
this.lastGoodClickID = this.lastClickID; this.lastGoodClickID = this.lastClickID;
appImManager.lazyLoadQueue.lock(); appImManager.lazyLoadQueue.lock();
} } */
} else /* if(chatClosedDiv) */ { } else {
result = appImManager.setPeer(0); result = appImManager.setPeer(0);
//chatClosedDiv.style.display = '';
} }
}, {capture: true});
/* if(!(result instanceof Promise)) { // if click on same dialog
this.rippleCallback();
this.rippleCallback = null;
} */
/* promise.then(() => {
appImManager.lazyLoadQueue.unlock();
}); */
/* promise.then(() => {
let length = appImManager.lazyLoadQueue.length();
console.log('pre ripple callback', length);
if(length) {
setTimeout(() => {
this.rippleCallback();
}, length * 25);
} else {
let elapsedTime = Date.now() - startTime;
this.rippleCallback(elapsedTime > 200);
}
}); */
});
} }
public setDialogPosition(dialog: Dialog) { public setDialogPosition(dialog: Dialog) {
@ -1012,7 +969,8 @@ export class AppDialogsManager {
paddingDiv.append(avatarEl, captionDiv); paddingDiv.append(avatarEl, captionDiv);
if(rippleEnabled) { if(rippleEnabled) {
ripple(paddingDiv, (id) => { ripple(paddingDiv);
/* ripple(paddingDiv, (id) => {
this.log('dialogs click element'); this.log('dialogs click element');
this.lastClickID = id; this.lastClickID = id;
@ -1026,7 +984,7 @@ export class AppDialogsManager {
if(id == this.lastGoodClickID) { if(id == this.lastGoodClickID) {
appImManager.lazyLoadQueue.unlock(); appImManager.lazyLoadQueue.unlock();
} }
}); }); */
} }

152
src/lib/appManagers/appImManager.ts

@ -154,8 +154,15 @@ export class AppImManager {
// will call when message is sent (only 1) // will call when message is sent (only 1)
$rootScope.$on('history_append', (e: CustomEvent) => { $rootScope.$on('history_append', (e: CustomEvent) => {
let details = e.detail; let details = e.detail;
this.renderNewMessagesByIDs([details.messageID]); if(!this.scrolledAllDown) {
let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0];
if(dialog) {
this.setPeer(this.peerID, dialog.top_message);
}
} else {
this.renderNewMessagesByIDs([details.messageID], true);
}
}); });
// will call when sent for update pos // will call when sent for update pos
@ -411,7 +418,7 @@ export class AppImManager {
if(['IMG', 'DIV'].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV'); if(['IMG', 'DIV'].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV');
if(target.tagName == 'DIV' || target.tagName == "AVATAR-ELEMENT") { if(target.tagName == 'DIV' || target.tagName == "AVATAR-ELEMENT") {
if(target.classList.contains('forward')) { if(target.classList.contains('goto-original')) {
let savedFrom = bubble.dataset.savedFrom; let savedFrom = bubble.dataset.savedFrom;
let splitted = savedFrom.split('_'); let splitted = savedFrom.split('_');
let peerID = +splitted[0]; let peerID = +splitted[0];
@ -685,7 +692,7 @@ export class AppImManager {
} }
//appMessagesManager.readMessages(readed); //appMessagesManager.readMessages(readed);
appMessagesManager.readHistory(this.peerID, max, min).catch((err: any) => { false && appMessagesManager.readHistory(this.peerID, max, min).catch((err: any) => {
this.log.error('readHistory err:', err); this.log.error('readHistory err:', err);
appMessagesManager.readHistory(this.peerID, max, min); appMessagesManager.readHistory(this.peerID, max, min);
}); });
@ -793,7 +800,7 @@ export class AppImManager {
if(this.isScrollingTimeout) { if(this.isScrollingTimeout) {
clearTimeout(this.isScrollingTimeout); clearTimeout(this.isScrollingTimeout);
} else { } else if(this.chatInner.classList.contains('is-scrolling')) {
this.chatInner.classList.add('is-scrolling'); this.chatInner.classList.add('is-scrolling');
} }
@ -815,14 +822,14 @@ export class AppImManager {
} }
public setScroll() { public setScroll() {
this.scrollable = new Scrollable(this.bubblesContainer, 'y', 750, 'IM', this.chatInner/* 1500 */, 300); this.scrollable = new Scrollable(this.bubblesContainer, 'y', 'IM', this.chatInner);
this.scroll = this.scrollable.container; this.scroll = this.scrollable.container;
this.bubblesContainer.append(this.goDownBtn); this.bubblesContainer.append(this.goDownBtn);
//this.scrollable.setVirtualContainer(this.chatInner);
this.scrollable.onScrolledTop = () => this.loadMoreHistory(true); this.scrollable.onScrolledTop = () => this.loadMoreHistory(true);
this.scrollable.onScrolledBottom = () => this.loadMoreHistory(false); this.scrollable.onScrolledBottom = () => this.loadMoreHistory(false);
this.scrollable.attachSentinels(undefined, 300);
this.scroll.addEventListener('scroll', this.onScroll.bind(this)); this.scroll.addEventListener('scroll', this.onScroll.bind(this));
this.scroll.parentElement.classList.add('scrolled-down'); this.scroll.parentElement.classList.add('scrolled-down');
@ -943,8 +950,8 @@ export class AppImManager {
this.peerChanged = false; this.peerChanged = false;
this.firstUnreadBubble = null; this.firstUnreadBubble = null;
/* this.messagesQueue.length = 0; this.messagesQueue.length = 0;
this.messagesQueuePromise = null; */ this.messagesQueuePromise = null;
lottieLoader.checkAnimations(false, 'chat', true); lottieLoader.checkAnimations(false, 'chat', true);
@ -1028,10 +1035,14 @@ export class AppImManager {
const maxBubbleID = samePeer && Math.max(...Object.keys(this.bubbles).map(mid => +mid)); const maxBubbleID = samePeer && Math.max(...Object.keys(this.bubbles).map(mid => +mid));
//let oldChatInner = this.chatInner;
this.cleanup(); this.cleanup();
this.chatInner = document.createElement('div'); this.chatInner = document.createElement('div');
this.chatInner.id = 'bubbles-inner'; this.chatInner.id = 'bubbles-inner';
this.scrollable.appendTo = this.chatInner; this.scrollable.appendTo = this.chatInner;
this.chatInner.classList.add('disable-hover', 'is-scrolling');
this.lazyLoadQueue.lock();
let {promise, cached} = this.getHistory(lastMsgID, true, isJump, additionMsgID); let {promise, cached} = this.getHistory(lastMsgID, true, isJump, additionMsgID);
@ -1040,6 +1051,7 @@ export class AppImManager {
// clear // clear
if(!cached) { if(!cached) {
this.scrollable.container.innerHTML = ''; this.scrollable.container.innerHTML = '';
//oldChatInner.remove();
this.finishPeerChange(); this.finishPeerChange();
this.preloader.attach(this.bubblesContainer); this.preloader.attach(this.bubblesContainer);
} }
@ -1052,12 +1064,17 @@ export class AppImManager {
if(cached) { if(cached) {
this.scrollable.container.innerHTML = ''; this.scrollable.container.innerHTML = '';
//oldChatInner.remove();
this.finishPeerChange(); this.finishPeerChange();
} else { } else {
this.preloader.detach(); this.preloader.detach();
} }
this.scrollable.container.append(this.chatInner); this.scrollable.container.append(this.chatInner);
this.scrollable.attachSentinels();
//this.scrollable.container.insertBefore(this.chatInner, this.scrollable.container.lastElementChild);
this.lazyLoadQueue.unlock();
if(dialog && lastMsgID && lastMsgID != dialog.top_message && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) { if(dialog && lastMsgID && lastMsgID != dialog.top_message && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) {
if(this.scrollable.scrollLocked) { if(this.scrollable.scrollLocked) {
@ -1086,35 +1103,7 @@ export class AppImManager {
this.scrolledAllDown = true; this.scrolledAllDown = true;
} }
//this.chatInner.style.visibility = ''; this.log('scrolledAllDown:', this.scrolledAllDown);
/* let promises: Promise<any>[] = [];
for(let i in this.bubbles) {
(Array.from(this.bubbles[i].querySelectorAll('img, image, video')) as HTMLImageElement[]).forEach(el => {
promises.push(new Promise((resolve, reject) => {
if(el.tagName == 'VIDEO') {
el.onloadeddata = () => {
console.log('onloadeddata');
resolve();
};
} else {
el.onload = resolve;
}
}));
});
}
Promise.all(promises).then(() => {
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
//this.chatInner.style.visibility = '';
let parent = oldChatInner.parentElement;
oldChatInner.remove();
parent.append(this.chatInner);
this.scrollable.scrollTop = this.scrollable.scrollHeight;
});
});
}); */
console.timeEnd('appImManager setPeer'); console.timeEnd('appImManager setPeer');
@ -1126,6 +1115,7 @@ export class AppImManager {
]).catch(err => { ]).catch(err => {
this.log.error('setPeer promises error:', err); this.log.error('setPeer promises error:', err);
this.preloader.detach(); this.preloader.detach();
//oldChatInner.remove();
return false; return false;
}).then(res => { }).then(res => {
if(this.peerID == peerID) { if(this.peerID == peerID) {
@ -1162,7 +1152,7 @@ export class AppImManager {
this.topbar.style.display = ''; this.topbar.style.display = '';
if(appPeersManager.isAnyGroup(peerID)) this.chatInner.classList.add('is-chat'); if(appPeersManager.isAnyGroup(peerID) || peerID == this.myID) this.chatInner.classList.add('is-chat');
else this.chatInner.classList.remove('is-chat'); else this.chatInner.classList.remove('is-chat');
if(isChannel) this.chatInner.classList.add('is-channel'); if(isChannel) this.chatInner.classList.add('is-channel');
else this.chatInner.classList.remove('is-channel'); else this.chatInner.classList.remove('is-channel');
@ -1238,13 +1228,12 @@ export class AppImManager {
this.deleteEmptyDateGroups(); this.deleteEmptyDateGroups();
} }
public renderNewMessagesByIDs(msgIDs: number[]) { public renderNewMessagesByIDs(msgIDs: number[], scrolledDown = this.scrolledDown) {
if(!this.scrolledAllDown) { // seems search active or sliced if(!this.scrolledAllDown) { // seems search active or sliced
//////this.log('seems search is active, skipping render:', msgIDs); this.log('seems search is active, skipping render:', msgIDs);
return; return;
} }
let scrolledDown = this.scrolledDown;
msgIDs.forEach((msgID: number) => { msgIDs.forEach((msgID: number) => {
let message = appMessagesManager.getMessage(msgID); let message = appMessagesManager.getMessage(msgID);
@ -1256,8 +1245,14 @@ export class AppImManager {
//if(scrolledDown) this.scrollable.scrollTop = this.scrollable.scrollHeight; //if(scrolledDown) this.scrollable.scrollTop = this.scrollable.scrollHeight;
if(this.messagesQueuePromise && scrolledDown) { if(this.messagesQueuePromise && scrolledDown) {
this.scrollable.scrollTo(this.scrollable.scrollHeight - 1, false, true);
this.messagesQueuePromise.then(() => { this.messagesQueuePromise.then(() => {
this.scrollable.scrollTo(this.scrollable.scrollHeight, false); this.log('messagesQueuePromise after:', this.chatInner.childElementCount, this.scrollable.scrollHeight);
this.scrollable.scrollTo(this.scrollable.scrollHeight, true, true);
setTimeout(() => {
this.log('messagesQueuePromise afterafter:', this.chatInner.childElementCount, this.scrollable.scrollHeight);
}, 10);
}); });
} }
} }
@ -1376,7 +1371,7 @@ export class AppImManager {
this.messagesQueue.length = 0; this.messagesQueue.length = 0;
let promises = queue.reduce((acc, {promises}) => acc.concat(promises), []); let promises = queue.reduce((acc, {promises}) => acc.concat(promises), []);
console.log('promises to call', promises, queue); //console.log('promises to call', promises, queue);
Promise.all(promises).then(() => { Promise.all(promises).then(() => {
if(this.chatInner != chatInner) { if(this.chatInner != chatInner) {
this.log.warn('chatInner changed!', this.chatInner, chatInner); this.log.warn('chatInner changed!', this.chatInner, chatInner);
@ -2010,35 +2005,44 @@ export class AppImManager {
let isHidden = message.fwd_from && !message.fwd_from.from_id && !message.fwd_from.channel_id; let isHidden = message.fwd_from && !message.fwd_from.from_id && !message.fwd_from.channel_id;
if(isHidden) { if(isHidden) {
///////this.log('message to render hidden', message); ///////this.log('message to render hidden', message);
title = message.fwd_from.from_name; title = RichTextProcessor.wrapEmojiText(message.fwd_from.from_name);
//title = message.fwd_from.from_name;
bubble.classList.add('hidden-profile'); bubble.classList.add('hidden-profile');
} }
//this.log(title); //this.log(title);
if(message.fwdFromID || message.fwd_from) { if((message.fwdFromID || message.fwd_from)) {
bubble.classList.add('forwarded'); if(this.peerID != this.myID) {
bubble.classList.add('forwarded');
}
if(message.savedFrom) { if(message.savedFrom) {
let fwd = document.createElement('div'); let goto = document.createElement('div');
fwd.classList.add('forward'/* , 'tgico-forward' */); goto.classList.add('goto-original', 'tgico-next');
fwd.innerHTML = ` /* fwd.innerHTML = `
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24">
<defs> <defs>
<path d="M13.55 3.24L13.64 3.25L13.73 3.27L13.81 3.29L13.9 3.32L13.98 3.35L14.06 3.39L14.14 3.43L14.22 3.48L14.29 3.53L14.36 3.59L14.43 3.64L22.23 10.85L22.36 10.99L22.48 11.15L22.57 11.31L22.64 11.48L22.69 11.66L22.72 11.85L22.73 12.04L22.71 12.22L22.67 12.41L22.61 12.59L22.53 12.76L22.42 12.93L22.29 13.09L22.23 13.15L14.43 20.36L14.28 20.48L14.12 20.58L13.95 20.66L13.77 20.72L13.58 20.76L13.4 20.77L13.22 20.76L13.03 20.73L12.85 20.68L12.68 20.61L12.52 20.52L12.36 20.4L12.22 20.27L12.16 20.2L12.1 20.13L12.05 20.05L12.01 19.98L11.96 19.9L11.93 19.82L11.89 19.73L11.87 19.65L11.84 19.56L11.83 19.47L11.81 19.39L11.81 19.3L11.8 19.2L11.8 16.42L11 16.49L10.23 16.58L9.51 16.71L8.82 16.88L8.18 17.09L7.57 17.33L7.01 17.6L6.48 17.91L5.99 18.26L5.55 18.64L5.14 19.05L4.77 19.51L4.43 19.99L4.29 20.23L4.21 20.35L4.11 20.47L4 20.57L3.88 20.65L3.75 20.72L3.62 20.78L3.48 20.82L3.33 20.84L3.19 20.84L3.04 20.83L2.9 20.79L2.75 20.74L2.62 20.68L2.53 20.62L2.45 20.56L2.38 20.5L2.31 20.43L2.25 20.36L2.2 20.28L2.15 20.19L2.11 20.11L2.07 20.02L2.04 19.92L2.02 19.83L2.01 19.73L2 19.63L2.04 17.99L2.19 16.46L2.46 15.05L2.85 13.75L3.35 12.58L3.97 11.53L4.7 10.6L5.55 9.8L6.51 9.12L7.59 8.56L8.77 8.13L10.07 7.83L11.48 7.65L11.8 7.63L11.8 4.8L11.91 4.56L12.02 4.35L12.14 4.16L12.25 3.98L12.37 3.82L12.48 3.68L12.61 3.56L12.73 3.46L12.85 3.38L12.98 3.31L13.11 3.27L13.24 3.24L13.37 3.23L13.46 3.23L13.55 3.24Z" id="b13RmHDQtl"></path> <path d="M13.55 3.24L13.64 3.25L13.73 3.27L13.81 3.29L13.9 3.32L13.98 3.35L14.06 3.39L14.14 3.43L14.22 3.48L14.29 3.53L14.36 3.59L14.43 3.64L22.23 10.85L22.36 10.99L22.48 11.15L22.57 11.31L22.64 11.48L22.69 11.66L22.72 11.85L22.73 12.04L22.71 12.22L22.67 12.41L22.61 12.59L22.53 12.76L22.42 12.93L22.29 13.09L22.23 13.15L14.43 20.36L14.28 20.48L14.12 20.58L13.95 20.66L13.77 20.72L13.58 20.76L13.4 20.77L13.22 20.76L13.03 20.73L12.85 20.68L12.68 20.61L12.52 20.52L12.36 20.4L12.22 20.27L12.16 20.2L12.1 20.13L12.05 20.05L12.01 19.98L11.96 19.9L11.93 19.82L11.89 19.73L11.87 19.65L11.84 19.56L11.83 19.47L11.81 19.39L11.81 19.3L11.8 19.2L11.8 16.42L11 16.49L10.23 16.58L9.51 16.71L8.82 16.88L8.18 17.09L7.57 17.33L7.01 17.6L6.48 17.91L5.99 18.26L5.55 18.64L5.14 19.05L4.77 19.51L4.43 19.99L4.29 20.23L4.21 20.35L4.11 20.47L4 20.57L3.88 20.65L3.75 20.72L3.62 20.78L3.48 20.82L3.33 20.84L3.19 20.84L3.04 20.83L2.9 20.79L2.75 20.74L2.62 20.68L2.53 20.62L2.45 20.56L2.38 20.5L2.31 20.43L2.25 20.36L2.2 20.28L2.15 20.19L2.11 20.11L2.07 20.02L2.04 19.92L2.02 19.83L2.01 19.73L2 19.63L2.04 17.99L2.19 16.46L2.46 15.05L2.85 13.75L3.35 12.58L3.97 11.53L4.7 10.6L5.55 9.8L6.51 9.12L7.59 8.56L8.77 8.13L10.07 7.83L11.48 7.65L11.8 7.63L11.8 4.8L11.91 4.56L12.02 4.35L12.14 4.16L12.25 3.98L12.37 3.82L12.48 3.68L12.61 3.56L12.73 3.46L12.85 3.38L12.98 3.31L13.11 3.27L13.24 3.24L13.37 3.23L13.46 3.23L13.55 3.24Z" id="b13RmHDQtl"></path>
</defs> </defs>
<use xlink:href="#b13RmHDQtl" opacity="1" fill="#fff" fill-opacity="1"></use> <use xlink:href="#b13RmHDQtl" opacity="1" fill="#fff" fill-opacity="1"></use>
</svg>`; </svg>`; */
bubbleContainer.append(fwd); bubbleContainer.append(goto);
bubble.dataset.savedFrom = message.savedFrom; bubble.dataset.savedFrom = message.savedFrom;
} }
if(!bubble.classList.contains('sticker')) { if(!bubble.classList.contains('sticker')) {
let nameDiv = document.createElement('div'); let nameDiv = document.createElement('div');
nameDiv.classList.add('name'); nameDiv.classList.add('name');
nameDiv.innerHTML = 'Forwarded from ' + title;
nameDiv.dataset.peerID = message.fwdFromID; nameDiv.dataset.peerID = message.fwdFromID;
//nameDiv.style.color = appPeersManager.getPeerColorByID(message.fromID, false);
if(this.peerID == this.myID) {
nameDiv.style.color = appPeersManager.getPeerColorByID(message.fwdFromID, false);
nameDiv.innerHTML = title;
} else {
nameDiv.innerHTML = 'Forwarded from ' + title;
}
bubbleContainer.append(nameDiv); bubbleContainer.append(nameDiv);
} }
} else { } else {
@ -2079,19 +2083,18 @@ export class AppImManager {
} }
} }
if(!our && this.peerID < 0 && (!appPeersManager.isChannel(this.peerID) || appPeersManager.isMegagroup(this.peerID))) { if((!our && this.peerID < 0 && (!appPeersManager.isChannel(this.peerID) || appPeersManager.isMegagroup(this.peerID)))
|| (this.peerID == this.myID && !message.reply_to_mid)) {
let avatarElem = new AvatarElement(); let avatarElem = new AvatarElement();
avatarElem.classList.add('user-avatar'); avatarElem.classList.add('user-avatar');
avatarElem.setAttribute('peer', '' + (message.fromID || 0));
if(!message.fromID && message.fwd_from && message.fwd_from.from_name) {
avatarElem.setAttribute('peer-title', message.fwd_from.from_name);
}
avatarElem.setAttribute('peer', '' + ((message.fwd_from ? message.fwdFromID : message.fromID) || 0));
/////////this.log('exec loadDialogPhoto', message); this.log('exec loadDialogPhoto', message);
/* if(message.fromID) { // if no - user hidden
appProfileManager.putPhoto(avatarDiv, message.fromID);
} else if(!title && message.fwd_from && message.fwd_from.from_name) {
title = message.fwd_from.from_name;
appProfileManager.putPhoto(avatarDiv, 0, false);
} */
bubbleContainer.append(avatarElem); bubbleContainer.append(avatarElem);
} }
@ -2099,7 +2102,7 @@ export class AppImManager {
bubble.classList.add('hide-name'); bubble.classList.add('hide-name');
} }
bubble.classList.add(our ? 'is-out' : 'is-in'); bubble.classList.add(our && (!message.fwd_from || this.peerID != this.myID) ? 'is-out' : 'is-in');
if(updatePosition) { if(updatePosition) {
this.bubbleGroups.addBubble(bubble, message, reverse); this.bubbleGroups.addBubble(bubble, message, reverse);
@ -2136,6 +2139,16 @@ export class AppImManager {
} }
} */ } */
let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0];
if(dialog && dialog.top_message) {
for(let mid of history) {
if(mid == dialog.top_message) {
this.scrolledAllDown = true;
break;
}
}
}
console.time('appImManager render history'); console.time('appImManager render history');
let firstLoad = !!this.setPeerPromise && false; let firstLoad = !!this.setPeerPromise && false;
@ -2199,7 +2212,7 @@ export class AppImManager {
let loadCount = realLoadCount; let loadCount = realLoadCount;
if(testScroll) { if(testScroll) {
loadCount = 1; //loadCount = 1;
if(Object.keys(this.bubbles).length > 0) if(Object.keys(this.bubbles).length > 0)
return {cached: false, promise: Promise.resolve(true)}; return {cached: false, promise: Promise.resolve(true)};
} }
@ -2217,11 +2230,6 @@ export class AppImManager {
} }
let result = appMessagesManager.getHistory(this.peerID, maxID, loadCount, backLimit); let result = appMessagesManager.getHistory(this.peerID, maxID, loadCount, backLimit);
/* if(!(result instanceof Promise)) {
let _result = result;
$rootScope.$broadcast('history_request'); // for ripple
result = new Promise((resolve, reject) => setTimeout(() => resolve(_result), 150));
} */
let promise: Promise<boolean>, cached: boolean; let promise: Promise<boolean>, cached: boolean;
if(result instanceof Promise) { if(result instanceof Promise) {

9
src/lib/appManagers/appMediaViewer.ts

@ -362,6 +362,7 @@ export class AppMediaViewer {
if(aspecter) { if(aspecter) {
this.setFullAspect(aspecter, containerRect, rect); this.setFullAspect(aspecter, containerRect, rect);
aspecter.classList.add('disable-hover');
} }
setTimeout(() => { setTimeout(() => {
@ -379,6 +380,8 @@ export class AppMediaViewer {
mover.classList.remove('active'); mover.classList.remove('active');
aspecter.style.cssText = ''; aspecter.style.cssText = '';
void mover.offsetLeft; // reflow void mover.offsetLeft; // reflow
aspecter.classList.remove('disable-hover');
} }
mover.classList.add('active'); mover.classList.add('active');
@ -727,7 +730,11 @@ export class AppMediaViewer {
} }
if(!video.parentElement) { if(!video.parentElement) {
aspecter.prepend(video); if(aspecter.classList.contains('media-viewer-aspecter')) {
aspecter.prepend(video);
} else {
mover.prepend(video);
}
} }
} }

58
src/lib/appManagers/appMessagesManager.ts

@ -1,4 +1,4 @@
import { $rootScope, copy, tsNow, safeReplaceObject, dT, _, listMergeSorted, deepEqual, langPack } from "../utils"; import { $rootScope, copy, tsNow, safeReplaceObject, dT, listMergeSorted, deepEqual, langPack } from "../utils";
import appMessagesIDsManager from "./appMessagesIDsManager"; import appMessagesIDsManager from "./appMessagesIDsManager";
import appChatsManager from "./appChatsManager"; import appChatsManager from "./appChatsManager";
import appUsersManager from "./appUsersManager"; import appUsersManager from "./appUsersManager";
@ -29,7 +29,10 @@ export type HistoryStorage = {
count: number | null, count: number | null,
history: number[], history: number[],
pending: number[], pending: number[],
readPromise?: any
readPromise?: any,
maxOutID?: number,
reply_markup?: any
}; };
export type HistoryResult = { export type HistoryResult = {
@ -189,6 +192,19 @@ export class AppMessagesManager {
} }
if(messages) { if(messages) {
/* let tempID = this.tempID;
for(let message of messages) {
if(message.id < tempID) {
tempID = message.id;
}
}
if(tempID != this.tempID) {
console.log('Set tempID to:', tempID);
this.tempID = tempID;
} */
this.saveMessages(messages); this.saveMessages(messages);
} }
@ -208,7 +224,7 @@ export class AppMessagesManager {
}).catch(resolve); }).catch(resolve);
}); });
setInterval(() => this.saveState(), 10000); //setInterval(() => this.saveState(), 10000);
} }
public saveState() { public saveState() {
@ -218,15 +234,30 @@ export class AppMessagesManager {
for(let folderID in this.dialogsStorage) { for(let folderID in this.dialogsStorage) {
for(let dialog of this.dialogsStorage[folderID]) { for(let dialog of this.dialogsStorage[folderID]) {
let message = this.getMessage(dialog.top_message); let historyStorage = this.historiesStorage[dialog.peerID];
if(message._ != 'messageEmpty' && message.id > 0) { let history = [].concat(historyStorage?.pending ?? [], historyStorage?.history ?? []);
messages.push(message);
dialog = copy(dialog);
let removeUnread = 0;
for(let mid of history) {
let message = this.getMessage(mid);
if(/* message._ != 'messageEmpty' && */message.id > 0) {
messages.push(message);
if(message.fromID != dialog.peerID) {
peers[message.fromID] = appPeersManager.getPeer(message.fromID);
}
dialog.top_message = message.mid;
if(message.fromID != dialog.peerID) { break;
peers[message.fromID] = appPeersManager.getPeer(message.fromID); } else if(message.pFlags && message.pFlags.unread) {
++removeUnread;
} }
} }
if(removeUnread && dialog.unread_count) dialog.unread_count -= removeUnread;
dialogs.push(dialog); dialogs.push(dialog);
peers[dialog.peerID] = appPeersManager.getPeer(dialog.peerID); peers[dialog.peerID] = appPeersManager.getPeer(dialog.peerID);
@ -2137,13 +2168,13 @@ export class AppMessagesManager {
else message.pFlags.unread = false; else message.pFlags.unread = false;
} }
if(this.historiesStorage[peerID] === undefined && !message.deleted) { if(this.historiesStorage[peerID] === undefined/* && !message.deleted */) { // warning
var historyStorage: any = {count: null, history: [], pending: []}; let historyStorage: HistoryStorage = {count: null, history: [], pending: []};
historyStorage[mid > 0 ? 'history' : 'pending'].push(mid) historyStorage[mid > 0 ? 'history' : 'pending'].push(mid);
if(mid < 0 && message.pFlags.unread) { if(mid < 0 && message.pFlags.unread) {
dialog.unread_count++; dialog.unread_count++;
} }
this.historiesStorage[peerID] = historyStorage this.historiesStorage[peerID] = historyStorage;
if(this.mergeReplyKeyboard(historyStorage, message)) { if(this.mergeReplyKeyboard(historyStorage, message)) {
$rootScope.$broadcast('history_reply_markup', {peerID: peerID}); $rootScope.$broadcast('history_reply_markup', {peerID: peerID});
} }
@ -2154,7 +2185,7 @@ export class AppMessagesManager {
} }
} }
public mergeReplyKeyboard(historyStorage: any, message: any) { public mergeReplyKeyboard(historyStorage: HistoryStorage, message: any) {
// console.log('merge', message.mid, message.reply_markup, historyStorage.reply_markup) // console.log('merge', message.mid, message.reply_markup, historyStorage.reply_markup)
if(!message.reply_markup && if(!message.reply_markup &&
!message.pFlags.out && !message.pFlags.out &&
@ -3204,6 +3235,7 @@ export class AppMessagesManager {
} }
case 'updateServiceNotification': { case 'updateServiceNotification': {
console.log('updateServiceNotification', update);
var fromID = 777000; var fromID = 777000;
var peerID = fromID; var peerID = fromID;
var messageID = this.tempID--; var messageID = this.tempID--;

41
src/lib/appManagers/appSidebarLeft.ts

@ -387,6 +387,9 @@ class AppEditProfileTab implements SliderTab {
private avatarElem = document.createElement('avatar-element'); private avatarElem = document.createElement('avatar-element');
private profileUrlContainer = this.container.querySelector('.profile-url-container') as HTMLDivElement;
private profileUrlAnchor = this.profileUrlContainer.lastElementChild as HTMLAnchorElement;
private originalValues = { private originalValues = {
firstName: '', firstName: '',
lastName: '', lastName: '',
@ -411,21 +414,27 @@ class AppEditProfileTab implements SliderTab {
this.lastNameInput.addEventListener('input', () => this.handleChange()); this.lastNameInput.addEventListener('input', () => this.handleChange());
this.bioInput.addEventListener('input', () => this.handleChange()); this.bioInput.addEventListener('input', () => this.handleChange());
this.userNameInput.addEventListener('input', () => { this.userNameInput.addEventListener('input', () => {
this.handleChange();
let value = this.userNameInput.value; let value = this.userNameInput.value;
console.log('userNameInput:', value); console.log('userNameInput:', value);
if(value == this.originalValues.userName) { if(value == this.originalValues.userName || !value.length) {
this.userNameInput.classList.remove('valid', 'error'); this.userNameInput.classList.remove('valid', 'error');
userNameLabel.innerText = 'Username (optional)'; userNameLabel.innerText = 'Username (optional)';
this.setProfileUrl();
this.handleChange();
return; return;
} else if(value.length < 5 || value.length > 32 || !/^[a-zA-Z0-9_]+$/.test(value)) { // does not check the last underscore } else if(!this.isUsernameValid(value)) { // does not check the last underscore
this.userNameInput.classList.add('error'); this.userNameInput.classList.add('error');
this.userNameInput.classList.remove('valid'); this.userNameInput.classList.remove('valid');
userNameLabel.innerText = 'Username is invalid'; userNameLabel.innerText = 'Username is invalid';
} else { } else {
this.userNameInput.classList.remove('error'); this.userNameInput.classList.remove('valid', 'error');
/* */ }
if(this.userNameInput.classList.contains('error')) {
this.setProfileUrl();
this.handleChange();
return;
} }
apiManager.invokeApi('account.checkUsername', { apiManager.invokeApi('account.checkUsername', {
@ -453,6 +462,9 @@ class AppEditProfileTab implements SliderTab {
break; break;
} }
} }
}).then(() => {
this.handleChange();
this.setProfileUrl();
}); });
}); });
@ -509,16 +521,33 @@ class AppEditProfileTab implements SliderTab {
} }
this.uploadAvatar = null; this.uploadAvatar = null;
this.setProfileUrl();
}
public isUsernameValid(username: string) {
return ((username.length >= 5 && username.length <= 32) || !username.length) && /^[a-zA-Z0-9_]*$/.test(username);
} }
private isChanged() { private isChanged() {
return !!this.uploadAvatar return !!this.uploadAvatar
|| this.firstNameInput.value != this.originalValues.firstName || this.firstNameInput.value != this.originalValues.firstName
|| this.lastNameInput.value != this.originalValues.lastName || this.lastNameInput.value != this.originalValues.lastName
|| this.userNameInput.value != this.originalValues.userName || (this.userNameInput.value != this.originalValues.userName && !this.userNameInput.classList.contains('error'))
|| this.bioInput.value != this.originalValues.bio; || this.bioInput.value != this.originalValues.bio;
} }
private setProfileUrl() {
if(this.userNameInput.classList.contains('error') || !this.userNameInput.value.length) {
this.profileUrlContainer.style.display = 'none';
} else {
this.profileUrlContainer.style.display = '';
let url = 'https://t.me/' + this.userNameInput.value;
this.profileUrlAnchor.innerText = url;
this.profileUrlAnchor.href = url;
}
}
private handleChange() { private handleChange() {
if(this.isChanged()) { if(this.isChanged()) {
this.nextBtn.classList.add('is-visible'); this.nextBtn.classList.add('is-visible');

130
src/lib/appManagers/appSidebarRight.ts

@ -29,8 +29,6 @@ let setText = (text: string, el: HTMLDivElement) => {
el.prepend(p); el.prepend(p);
el.style.display = ''; el.style.display = '';
//this.scroll.getScrollTopOffset();
}); });
}; };
@ -66,7 +64,8 @@ class AppSidebarRight {
private loadedAllMedia: {[type: string]: boolean} = {}; private loadedAllMedia: {[type: string]: boolean} = {};
public sharedMediaTypes = [ public sharedMediaTypes = [
'inputMessagesFilterContacts', 'members',
//'inputMessagesFilterContacts',
'inputMessagesFilterPhotoVideo', 'inputMessagesFilterPhotoVideo',
'inputMessagesFilterDocument', 'inputMessagesFilterDocument',
'inputMessagesFilterUrl', 'inputMessagesFilterUrl',
@ -75,7 +74,7 @@ class AppSidebarRight {
public sharedMediaType: AppSidebarRight['sharedMediaTypes'][number] = ''; public sharedMediaType: AppSidebarRight['sharedMediaTypes'][number] = '';
private sharedMediaSelected: HTMLDivElement = null; private sharedMediaSelected: HTMLDivElement = null;
private lazyLoadQueueSidebar = new LazyLoadQueue(5); private lazyLoadQueue = new LazyLoadQueue(5);
public historiesStorage: { public historiesStorage: {
[peerID: number]: { [peerID: number]: {
@ -114,15 +113,14 @@ class AppSidebarRight {
let container = this.profileContentEl.querySelector('.content-container .tabs-container') as HTMLDivElement; let container = this.profileContentEl.querySelector('.content-container .tabs-container') as HTMLDivElement;
this.profileTabs = this.profileContentEl.querySelector('.profile-tabs') as HTMLUListElement; this.profileTabs = this.profileContentEl.querySelector('.profile-tabs') as HTMLUListElement;
this.scroll = new Scrollable(this.profileContainer, 'y', 1200, 'SR', undefined, 400); this.scroll = new Scrollable(this.profileContainer, 'y', 'SR');
//this.scroll = new Scrollable(this.profileContentEl, 'y', 1200, 'SR', undefined, 400);
this.scroll.container.addEventListener('scroll', this.onSidebarScroll.bind(this));
this.scroll.onScrolledBottom = () => { this.scroll.onScrolledBottom = () => {
if(this.sharedMediaSelected && this.sharedMediaSelected.childElementCount/* && false */) { if(this.sharedMediaSelected && this.sharedMediaSelected.childElementCount/* && false */) {
this.log('onScrolledBottom will load media'); this.log('onScrolledBottom will load media');
this.loadSidebarMedia(true); this.loadSidebarMedia(true);
} }
}; };
this.scroll.attachSentinels(undefined, 400);
horizontalMenu(this.profileTabs, container, (id, tabContent) => { horizontalMenu(this.profileTabs, container, (id, tabContent) => {
if(this.prevTabID == id) return; if(this.prevTabID == id) return;
@ -130,24 +128,19 @@ class AppSidebarRight {
this.sharedMediaType = this.sharedMediaTypes[id]; this.sharedMediaType = this.sharedMediaTypes[id];
this.sharedMediaSelected = tabContent.firstElementChild as HTMLDivElement; this.sharedMediaSelected = tabContent.firstElementChild as HTMLDivElement;
if(this.profileTabs.offsetTop) { if(this.prevTabID != -1 && this.profileTabs.offsetTop) {
this.scroll.scrollTop -= this.profileTabs.offsetTop; this.scroll.scrollTop -= this.profileTabs.offsetTop;
} }
this.log('setVirtualContainer', id, this.sharedMediaSelected, this.sharedMediaSelected.childElementCount); /* this.log('setVirtualContainer', id, this.sharedMediaSelected, this.sharedMediaSelected.childElementCount);
this.scroll.setVirtualContainer(this.sharedMediaSelected); this.scroll.setVirtualContainer(this.sharedMediaSelected); */
if(this.prevTabID != -1 && !this.sharedMediaSelected.childElementCount) { // quick brown fix if(this.prevTabID != -1 && !this.sharedMediaSelected.childElementCount) { // quick brown fix
this.contentContainer.classList.remove('loaded'); //this.contentContainer.classList.remove('loaded');
this.loadSidebarMedia(true); this.loadSidebarMedia(true);
} }
this.prevTabID = id; this.prevTabID = id;
this.scroll.onScroll();
}, () => {
this.onSidebarScroll.bind(this);
this.scroll.onScroll();
}); });
let sidebarCloseBtn = this.sidebarEl.querySelector('.sidebar-close-button') as HTMLButtonElement; let sidebarCloseBtn = this.sidebarEl.querySelector('.sidebar-close-button') as HTMLButtonElement;
@ -183,9 +176,7 @@ class AppSidebarRight {
//let checked = this.profileElements.notificationsCheckbox.checked; //let checked = this.profileElements.notificationsCheckbox.checked;
appImManager.mutePeer(this.peerID); appImManager.mutePeer(this.peerID);
}); });
window.addEventListener('resize', this.onSidebarScroll.bind(this));
if(testScroll) { if(testScroll) {
let div = document.createElement('div'); let div = document.createElement('div');
for(let i = 0; i < 500; ++i) { for(let i = 0; i < 500; ++i) {
@ -209,24 +200,77 @@ class AppSidebarRight {
this.searchContainer.classList.add('active'); this.searchContainer.classList.add('active');
this.privateSearch.beginSearch(this.peerID); this.privateSearch.beginSearch(this.peerID);
} }
public onSidebarScroll() {
this.lazyLoadQueueSidebar.check();
}
public toggleSidebar(enable?: boolean) { public toggleSidebar(enable?: boolean) {
/////this.log('sidebarEl', this.sidebarEl, enable, isElementInViewport(this.sidebarEl)); /////this.log('sidebarEl', this.sidebarEl, enable, isElementInViewport(this.sidebarEl));
let active = this.sidebarEl.classList.contains('active');
let willChange: boolean;
if(enable !== undefined) { if(enable !== undefined) {
if(enable) { if(enable) {
setTimeout(() => this.lazyLoadQueueSidebar.check(), 200); if(!active) {
this.sidebarEl.classList.add('active'); willChange = true;
} else this.sidebarEl.classList.remove('active'); }
} else if(active) {
return; willChange = true;
}
} else {
willChange = true;
} }
this.sidebarEl.classList.toggle('active'); if(!willChange) return Promise.resolve();
let set = () => {
if(enable !== undefined) {
if(enable) this.sidebarEl.classList.add('active');
else this.sidebarEl.classList.remove('active');
} else {
this.sidebarEl.classList.toggle('active');
}
};
return new Promise((resolve, reject) => {
let hidden: {element: HTMLDivElement, height: number}[] = [];
let observer = new IntersectionObserver((entries) => {
for(let entry of entries) {
let bubble = entry.target as HTMLDivElement;
if(!entry.isIntersecting) {
hidden.push({element: bubble, height: bubble.scrollHeight});
}
}
for(let item of hidden) {
item.element.style.minHeight = item.height + 'px';
(item.element.firstElementChild as HTMLElement).style.display = 'none';
item.element.style.width = '1px';
}
//console.log('hidden', hidden);
observer.disconnect();
set();
setTimeout(() => {
for(let item of hidden) {
item.element.style.minHeight = '';
item.element.style.width = '';
(item.element.firstElementChild as HTMLElement).style.display = '';
}
resolve();
}, 200);
});
let length = Object.keys(appImManager.bubbles).length;
if(length) {
for(let i in appImManager.bubbles) {
observer.observe(appImManager.bubbles[i]);
}
} else {
set();
setTimeout(resolve, 200);
}
});
} }
public filterMessagesByType(ids: number[], type: string) { public filterMessagesByType(ids: number[], type: string) {
@ -396,7 +440,7 @@ class AppSidebarRight {
appPhotosManager.setAttachmentPreview(sizes[0].bytes, img, false, false); appPhotosManager.setAttachmentPreview(sizes[0].bytes, img, false, false);
} }
this.lazyLoadQueueSidebar.push({div, load}); this.lazyLoadQueue.push({div, load});
} }
this.lastSharedMediaDiv.append(div); this.lastSharedMediaDiv.append(div);
@ -451,10 +495,11 @@ class AppSidebarRight {
previewDiv.classList.remove('empty'); previewDiv.classList.remove('empty');
previewDiv.innerText = '';
renderImageFromUrl(previewDiv, webpage.photo.url); renderImageFromUrl(previewDiv, webpage.photo.url);
}); });
this.lazyLoadQueueSidebar.push({div: previewDiv, load}); this.lazyLoadQueue.push({div: previewDiv, load});
} }
let title = webpage.rTitle || ''; let title = webpage.rTitle || '';
@ -493,8 +538,8 @@ class AppSidebarRight {
} }
default: default:
//console.warn('death is my friend', message); console.warn('death is my friend', messages);
break; break;
} }
if(this.lastSharedMediaDiv.childElementCount && !this.scroll.contains(this.lastSharedMediaDiv)) { if(this.lastSharedMediaDiv.childElementCount && !this.scroll.contains(this.lastSharedMediaDiv)) {
@ -521,11 +566,9 @@ class AppSidebarRight {
let parent = sharedMediaDiv.parentElement; let parent = sharedMediaDiv.parentElement;
if(parent.lastElementChild.classList.contains('preloader')) { if(parent.lastElementChild.classList.contains('preloader')) {
parent.lastElementChild.remove(); parent.lastElementChild.remove();
this.contentContainer.classList.add('loaded'); //this.contentContainer.classList.add('loaded');
} }
} }
this.onSidebarScroll();
} }
public loadSidebarMedia(single = false) { public loadSidebarMedia(single = false) {
@ -626,11 +669,8 @@ class AppSidebarRight {
this.lastSharedMediaDiv.classList.add('media-row'); this.lastSharedMediaDiv.classList.add('media-row');
this.prevTabID = -1; this.prevTabID = -1;
this.scroll.setVirtualContainer(null);
this.mediaDivsByIDs = {}; this.mediaDivsByIDs = {};
this.lazyLoadQueue.clear();
this.lazyLoadQueueSidebar.clear();
this.sharedMediaTypes.forEach(type => { this.sharedMediaTypes.forEach(type => {
this.usedFromHistory[type] = 0; this.usedFromHistory[type] = 0;
@ -640,9 +680,9 @@ class AppSidebarRight {
} }
public cleanupHTML() { public cleanupHTML() {
this.contentContainer.classList.remove('loaded'); //this.contentContainer.classList.remove('loaded');
this.profileContentEl.parentElement.scrollTop = 0; //this.profileContentEl.parentElement.scrollTop = 0;
this.profileElements.bio.style.display = 'none'; this.profileElements.bio.style.display = 'none';
this.profileElements.phone.style.display = 'none'; this.profileElements.phone.style.display = 'none';
this.profileElements.username.style.display = 'none'; this.profileElements.username.style.display = 'none';
@ -710,7 +750,10 @@ class AppSidebarRight {
}); });
} }
let membersLi = this.profileTabs.firstElementChild.children[0] as HTMLLIElement;
if(peerID > 0) { if(peerID > 0) {
membersLi.style.display = 'none';
let user = appUsersManager.getUser(peerID); let user = appUsersManager.getUser(peerID);
if(user.phone && peerID != $rootScope.myID) { if(user.phone && peerID != $rootScope.myID) {
setText(user.rPhone, this.profileElements.phone); setText(user.rPhone, this.profileElements.phone);
@ -734,6 +777,7 @@ class AppSidebarRight {
} }
}); });
} else { } else {
membersLi.style.display = appPeersManager.isBroadcast(peerID) ? 'none' : '';
let chat = appPeersManager.getPeer(peerID); let chat = appPeersManager.getPeer(peerID);
appProfileManager.getChatFull(chat.id).then((chatFull: any) => { appProfileManager.getChatFull(chat.id).then((chatFull: any) => {

3
src/lib/appManagers/appStickersManager.ts

@ -118,7 +118,8 @@ class AppStickersManager {
public getAnimatedEmojiSticker(emoji: string) { public getAnimatedEmojiSticker(emoji: string) {
let stickerSet = this.stickerSets.emoji; let stickerSet = this.stickerSets.emoji;
return stickerSet.documents.find(doc => doc.attributes.find(attribute => attribute.alt == emoji)); emoji = emoji.replace(/\ufe0f/g, '');
return stickerSet.documents.find(doc => doc.stickerEmojiRaw == emoji);
} }
public async saveStickerSet(res: { public async saveStickerSet(res: {

42
src/lib/config.ts

File diff suppressed because one or more lines are too long

4
src/lib/lottie.ts

@ -1,5 +1,5 @@
// @ts-ignore // @ts-ignore
import LottiePlayer from "lottie-web/build/player/lottie_canvas.min.js"; //import LottiePlayer from "lottie-web/build/player/lottie_canvas.min.js";
//import LottiePlayer from "lottie-web/build/player/lottie_light.min.js"; import LottiePlayer from "lottie-web/build/player/lottie_light.min.js";
(window as any).lottie = LottiePlayer; (window as any).lottie = LottiePlayer;

15
src/lib/lottieLoader.ts

@ -1,4 +1,4 @@
import { isElementInViewport, isInDOM } from "./utils"; import { isInDOM } from "./utils";
import LottiePlayer, { AnimationConfigWithPath, AnimationConfigWithData, AnimationItem } from "lottie-web/build/player/lottie.d"; import LottiePlayer, { AnimationConfigWithPath, AnimationConfigWithData, AnimationItem } from "lottie-web/build/player/lottie.d";
class LottieLoader { class LottieLoader {
@ -59,7 +59,7 @@ class LottieLoader {
continue; continue;
} }
if(canvas) { /* if(canvas) {
let c = container.firstElementChild as HTMLCanvasElement; let c = container.firstElementChild as HTMLCanvasElement;
if(!c) { if(!c) {
console.warn('no canvas element for check!', container, animations[i]); console.warn('no canvas element for check!', container, animations[i]);
@ -70,11 +70,11 @@ class LottieLoader {
//console.log('lottie need resize'); //console.log('lottie need resize');
animation.resize(); animation.resize();
} }
} } */
if(!autoplay) continue; if(!autoplay) continue;
if(blurred || !isElementInViewport(container)) { /* if(blurred || !isElementInViewport(container)) {
if(!paused) { if(!paused) {
this.debug && console.log('pause animation', isElementInViewport(container), container); this.debug && console.log('pause animation', isElementInViewport(container), container);
animation.pause(); animation.pause();
@ -84,7 +84,7 @@ class LottieLoader {
this.debug && console.log('play animation', container); this.debug && console.log('play animation', container);
animation.play(); animation.play();
animations[i].paused = false; animations[i].paused = false;
} } */
} }
} }
} }
@ -92,7 +92,8 @@ class LottieLoader {
public async loadAnimation(params: /* any */AnimationConfigWithPath | AnimationConfigWithData, group = '') { public async loadAnimation(params: /* any */AnimationConfigWithPath | AnimationConfigWithData, group = '') {
//params.autoplay = false; //params.autoplay = false;
//if(group != 'auth') { //if(group != 'auth') {
params.renderer = 'canvas'; //params.renderer = 'canvas';
params.renderer = 'svg';
//} //}
params.rendererSettings = { params.rendererSettings = {
@ -131,7 +132,7 @@ class LottieLoader {
container: params.container as HTMLDivElement, container: params.container as HTMLDivElement,
paused: !params.autoplay, paused: !params.autoplay,
autoplay: params.autoplay, autoplay: params.autoplay,
canvas: params.renderer == 'canvas' canvas: false//params.renderer == 'canvas'
}); });
if(params.autoplay) { if(params.autoplay) {

9
src/lib/mtproto/apiFileManager.ts

@ -32,8 +32,6 @@ export class ApiFileManager {
} = {}; } = {};
public downloadActives: any = {}; public downloadActives: any = {};
public index = 0;
private log: ReturnType<typeof logger> = logger('AFM'); private log: ReturnType<typeof logger> = logger('AFM');
public downloadRequest(dcID: string | number, cb: () => Promise<unknown>, activeDelta?: number) { public downloadRequest(dcID: string | number, cb: () => Promise<unknown>, activeDelta?: number) {
@ -45,9 +43,8 @@ export class ApiFileManager {
var downloadPull = this.downloadPulls[dcID]; var downloadPull = this.downloadPulls[dcID];
let promise = new Promise((resolve, reject) => { let promise = new Promise((resolve, reject) => {
// WARNING deferred!
downloadPull.push({cb: cb, deferred: {resolve, reject}, activeDelta: activeDelta}); downloadPull.push({cb: cb, deferred: {resolve, reject}, activeDelta: activeDelta});
}).catch(() => {}); })/* .catch(() => {}) */;
setTimeout(() => { setTimeout(() => {
this.downloadCheck(dcID); this.downloadCheck(dcID);
@ -69,8 +66,6 @@ export class ApiFileManager {
this.downloadActives[dcID] += activeDelta; this.downloadActives[dcID] += activeDelta;
this.index++;
data.cb() data.cb()
.then((result: any) => { .then((result: any) => {
this.downloadActives[dcID] -= activeDelta; this.downloadActives[dcID] -= activeDelta;
@ -415,7 +410,7 @@ export class ApiFileManager {
}); });
}); });
}); });
}); }, errorHandler);
})(offset + limit >= size, offset, writeFileDeferred, writeFilePromise); })(offset + limit >= size, offset, writeFileDeferred, writeFilePromise);
writeFilePromise = writeFileDeferred; writeFilePromise = writeFileDeferred;

123
src/lib/richtextprocessor.js

File diff suppressed because one or more lines are too long

117
src/lib/utils.js

@ -441,42 +441,6 @@ export function findUpTag(el, tag) {
return null; return null;
} }
export function isElementInViewport(el) {
var rect = el.getBoundingClientRect(),
vWidth = window.innerWidth || document.documentElement.clientWidth,
vHeight = window.innerHeight || document.documentElement.clientHeight,
efp = function(x, y) { return document.elementFromPoint(x, y) };
// Return false if it's not in the viewport
if(rect.right < 0 || rect.bottom < 0
|| rect.left > vWidth || rect.top > vHeight
|| !rect.width || !rect.height) {
return false;
}
let elements = [
efp(rect.left + 1, rect.top + 1),
efp(rect.right - 1, rect.top + 1),
efp(rect.right - 1, rect.bottom - 1),
efp(rect.left + 1, rect.bottom - 1)
];
// Return true if any of its four corners are visible
return elements.find(e => el.contains(e) || el.parentElement == e) !== undefined;
}
export function isScrolledIntoView(el) {
var rect = el.getBoundingClientRect();
var elemTop = rect.top;
var elemBottom = rect.bottom;
// Only completely visible elements return true:
//var isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
// Partially visible elements return true:
var isVisible = elemTop < window.innerHeight && elemBottom >= 0;
return isVisible;
}
export function whichChild(elem/* : Node */) { export function whichChild(elem/* : Node */) {
let i = 0; let i = 0;
// @ts-ignore // @ts-ignore
@ -645,11 +609,11 @@ export function calcImageInBox (imageW, imageH, boxW, boxH, noZooom) {
* @param {String} input The emoji character. * @param {String} input The emoji character.
* @returns {String} The base 16 unicode code. * @returns {String} The base 16 unicode code.
*/ */
/* export function emojiUnicode (input) { export function emojiUnicode(input) {
let pairs = emojiUnicode.raw(input).split(' ').map(val => parseInt(val).toString(16)); let pairs = emojiUnicode.raw(input).split(' ').map(val => parseInt(val).toString(16)).filter(p => p != 'fe0f');
if(pairs[0].length == 2) pairs[0] = '00' + pairs[0]; if(pairs.length && pairs[0].length == 2) pairs[0] = '00' + pairs[0];
return pairs.join('-').toUpperCase(); return pairs.join('-');
} */ }
/** /**
* emojiunicode.raw * emojiunicode.raw
@ -660,7 +624,7 @@ export function calcImageInBox (imageW, imageH, boxW, boxH, noZooom) {
* @param {String} input The emoji character. * @param {String} input The emoji character.
* @returns {String} The unicode code points. * @returns {String} The unicode code points.
*/ */
/* emojiUnicode.raw = function (input) { emojiUnicode.raw = function(input) {
if(input.length === 1) { if(input.length === 1) {
return input.charCodeAt(0).toString(); return input.charCodeAt(0).toString();
} else if(input.length > 1) { } else if(input.length > 1) {
@ -685,74 +649,7 @@ export function calcImageInBox (imageW, imageH, boxW, boxH, noZooom) {
} }
return ''; return '';
}; */ };
// country code regex
const CC_REGEX = /^[a-z]{2}$/i;
// offset between uppercase ascii and regional indicator symbols
const OFFSET = 127397;
/**
* convert country code to corresponding emoji flag
* @param {string} cc - country code string
* @returns {string} country code emoji
*/
export function countryCodeEmoji(cc/* : string */) {
if(!CC_REGEX.test(cc)) {
const type = typeof cc;
throw new TypeError(
`cc argument must be an ISO 3166-1 alpha-2 string, but got '${
type === 'string' ? cc : type
}' instead.`,
);
}
const chars = [...cc.toUpperCase()].map(c => c.charCodeAt(0) + OFFSET);
//console.log(chars);
return String.fromCodePoint(...chars);
}
export function unifiedCountryCodeEmoji(cc/* : string */) {
if(!CC_REGEX.test(cc)) {
const type = typeof cc;
throw new TypeError(
`cc argument must be an ISO 3166-1 alpha-2 string, but got '${
type === 'string' ? cc : type
}' instead.`,
);
}
const chars = [...cc.toUpperCase()].map(c => c.charCodeAt(0) + OFFSET);
return chars.map(c => c.toString(16).toUpperCase()).join('-');
}
function versionCompare (ver1, ver2) {
if (typeof ver1 !== 'string') {
ver1 = ''
}
if (typeof ver2 !== 'string') {
ver2 = ''
}
ver1 = ver1.replace(/^\s+|\s+$/g, '').split('.')
ver2 = ver2.replace(/^\s+|\s+$/g, '').split('.')
var a = Math.max(ver1.length, ver2.length), i
for (i = 0; i < a; i++) {
if (ver1[i] == ver2[i]) {
continue
}
if (ver1[i] > ver2[i]) {
return 1
} else {
return -1
}
}
return 0
}
//var badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<\s]+/g, //var badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<\s]+/g,
var badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<]+/g, var badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<]+/g,

26
src/scss/partials/_chatBubble.scss

@ -106,7 +106,7 @@
} }
} }
&.forwarded { /* &.forwarded {
.forward { .forward {
opacity: 0; opacity: 0;
position: absolute; position: absolute;
@ -129,6 +129,24 @@
height: 20px; height: 20px;
} }
} }
} */
.goto-original {
opacity: 0;
position: absolute;
right: -46px;
bottom: 0;
width: 38px;
height: 38px;
font-size: 1.5rem;
align-items: center;
display: flex;
justify-content: center;
color: #fff;
border-radius: 50%;
background: rgba(0, 0, 0, 0.16);
cursor: pointer;
transition: .2s opacity;
} }
/* &.is-group-first { /* &.is-group-first {
@ -161,10 +179,8 @@
} }
} }
&:hover { .goto-original {
.forward { opacity: 1;
opacity: 1;
}
} }
.reply { .reply {

4
src/scss/partials/_rightSIdebar.scss

@ -75,12 +75,12 @@
position: relative; position: relative;
//height: 1%; // fix safari //height: 1%; // fix safari
&.loaded { /* &.loaded { // warning
.profile-tabs-content { .profile-tabs-content {
position: relative; position: relative;
min-height: auto; min-height: auto;
} }
} } */
} }
} }

8
src/scss/partials/_scrollable.scss

@ -50,6 +50,14 @@ div.scrollable::-webkit-scrollbar-thumb {
-ms-overflow-style: none; -ms-overflow-style: none;
} }
&-sentinel {
position: relative;
left: 0;
height: 1px;
background-color: transparent;
width: 1px;
}
/* &.scrollable-x ~ .scrollbar-thumb { /* &.scrollable-x ~ .scrollbar-thumb {
top: auto; top: auto;
right: auto; right: auto;

Loading…
Cancel
Save