From a6d9d69909f69783aa173149754cce53e4b381fe Mon Sep 17 00:00:00 2001 From: morethanwords Date: Sun, 10 May 2020 04:23:21 +0300 Subject: [PATCH] Poll vote & update & scroll chat fix & QR page & chat list updates --- src/components/poll.ts | 299 +++++++++++++------ src/components/scrollable_new.ts | 106 +++---- src/components/wrappers.ts | 22 +- src/lib/appManagers/appDialogsManager.ts | 249 ++++------------ src/lib/appManagers/appImManager.ts | 66 +++-- src/lib/appManagers/appMessagesManager.ts | 346 ++++++++++++++-------- src/lib/appManagers/appPollsManager.ts | 68 ++++- src/lib/smoothscroll.js | 2 + src/lib/utils.js | 2 +- src/pages/pageSignIn.ts | 7 +- src/pages/pageSignQR.ts | 5 + src/scss/partials/_chat.scss | 5 + src/scss/partials/_chatBubble.scss | 106 +++++-- src/scss/partials/_chatlist.scss | 4 - src/scss/style.scss | 14 + 15 files changed, 765 insertions(+), 536 deletions(-) create mode 100644 src/lib/smoothscroll.js diff --git a/src/components/poll.ts b/src/components/poll.ts index 0b83ed65..f69923d9 100644 --- a/src/components/poll.ts +++ b/src/components/poll.ts @@ -1,5 +1,6 @@ -import appPollsManager, { PollResults } from "../lib/appManagers/appPollsManager"; +import appPollsManager, { PollResults, Poll } from "../lib/appManagers/appPollsManager"; import { RichTextProcessor } from "../lib/richtextprocessor"; +import { findUpClassName, $rootScope } from "../lib/utils"; let lineTotalLength = 0; const tailLength = 9; @@ -7,13 +8,81 @@ const times = 10; const fullTime = 340; const oneTime = fullTime / times; +let roundPercents = (percents: number[]) => { + //console.log('roundPercents before percents:', percents); + + let sum = percents.reduce((acc, p) => acc + Math.round(p), 0); + if(sum > 100) { + let diff = sum - 100; + let length = percents.length; + for(let i = 0; i < diff; ++i) { + let minIndex = -1, minRemainder = 1; + for(let k = 0; k < length; ++k) { + let remainder = percents[k] % 1; + if(remainder >= 0.5 && remainder < minRemainder) { + minRemainder = remainder; + minIndex = k; + } + } + + if(minIndex == -1) { + throw new Error('lol chto'); + } + + percents[minIndex] -= minRemainder; + } + } else if(sum < 100) { + let diff = 100 - sum; + let length = percents.length; + for(let i = 0; i < diff; ++i) { + let minIndex = -1, maxRemainder = 0; + for(let k = 0; k < length; ++k) { + let remainder = percents[k] % 1; + if(remainder < 0.5 && remainder > maxRemainder) { + maxRemainder = remainder; + minIndex = k; + } + } + + if(minIndex == -1) { + throw new Error('lol chto'); + } + + percents[minIndex] += 1 - maxRemainder; + } + } + + //console.log('roundPercents after percents:', percents); +}; + +const connectedPolls: {id: string, element: PollElement}[] = []; +$rootScope.$on('poll_update', (e: CustomEvent) => { + let {poll, results} = e.detail as {poll: Poll, results: PollResults}; + + for(let connected of connectedPolls) { + if(connected.id == poll.id) { + let pollElement = connected.element; + pollElement.performResults(results, poll.chosenIndex); + } + } +}); + export default class PollElement extends HTMLElement { private svgLines: SVGSVGElement[]; private numberDivs: HTMLDivElement[]; - private maxOffset = -44.8; + private selectedSpan: HTMLSpanElement; + private answerDivs: HTMLDivElement[]; + private votersCountDiv: HTMLDivElement; + + private maxOffset = -46.5; private maxLength: number; private maxLengths: number[]; + private isQuiz = false; + private isRetracted = false; + private chosenIndex = -1; + private percents: number[]; + constructor() { super(); // элемент создан @@ -31,6 +100,8 @@ export default class PollElement extends HTMLElement { let pollID = this.getAttribute('poll-id'); let {poll, results} = appPollsManager.getPoll(pollID); + connectedPolls.push({id: pollID, element: this}); + console.log('pollElement poll:', poll, results); let desc = ''; @@ -38,13 +109,18 @@ export default class PollElement extends HTMLElement { if(poll.pFlags.closed) { desc = 'Final results'; } else { - desc = poll.pFlags.public_voters ? 'Public Poll' : 'Anonymous Poll'; + if(poll.pFlags.quiz) { + this.isQuiz = true; + } + + let type = this.isQuiz ? 'Quiz' : 'Poll'; + desc = (poll.pFlags.public_voters ? 'Public' : 'Anonymous') + ' ' + type; } } - let votes = poll.answers.map(answer => { + let votes = poll.answers.map((answer, idx) => { return ` -
+
@@ -52,7 +128,7 @@ export default class PollElement extends HTMLElement {
- +
${RichTextProcessor.wrapEmojiText(answer.text)}
@@ -64,17 +140,30 @@ export default class PollElement extends HTMLElement {
${poll.rQuestion}
${desc}
${votes} -
${results.total_voters ? results.total_voters + ' voters' : 'No votes'}
+
`; + this.answerDivs = Array.from(this.querySelectorAll('.poll-answer')) as HTMLDivElement[]; + this.votersCountDiv = this.querySelector('.poll-votes-count') as HTMLDivElement; + this.svgLines = Array.from(this.querySelectorAll('.poll-line')) as SVGSVGElement[]; + this.numberDivs = Array.from(this.querySelectorAll('.poll-answer-percents')) as HTMLDivElement[]; + let width = this.getBoundingClientRect().width; - this.maxLength = width + tailLength + this.maxOffset + -9; // 13 - position left - this.performResults(results); + this.maxLength = width + tailLength + this.maxOffset + -13.7; // 13 - position left + + if(poll.chosenIndex !== -1) { + this.performResults(results, poll.chosenIndex); + } else { + this.setVotersCount(results); + this.addEventListener('click', this.clickHandler); + } } disconnectedCallback() { // браузер вызывает этот метод при удалении элемента из документа // (может вызываться много раз, если элемент многократно добавляется/удаляется) + + connectedPolls.findAndSplice(c => c.element == this); } static get observedAttributes(): string[] { @@ -90,108 +179,136 @@ export default class PollElement extends HTMLElement { // (происходит в document.adoptNode, используется очень редко) } - performResults(results: PollResults) { - const percents = results.results.map(v => v.voters / results.total_voters * 100); - this.setResults(percents); + clickHandler(e: MouseEvent) { + let target = findUpClassName(e.target, 'poll-answer') as HTMLElement; + if(!target) { + return; + } + + let answerIndex = +target.dataset.index; + this.sendVote(answerIndex); + + /* target.classList.add('is-voting'); + setTimeout(() => { // simulate + this.setResults([100, 0], answerIndex); + target.classList.remove('is-voting'); + }, 1000); */ } - setResults(percents: number[]) { - if(!this.svgLines) { - this.svgLines = Array.from(this.querySelectorAll('.poll-line')) as SVGSVGElement[]; - this.numberDivs = Array.from(this.querySelectorAll('.poll-answer-percents')) as HTMLDivElement[]; + sendVote(index: number) { + let target = this.answerDivs[index]; + target.classList.add('is-voting'); + let mid = +this.getAttribute('message-id'); + + this.classList.add('disable-hover'); + appPollsManager.sendVote(mid, [index]).then(() => { + target.classList.remove('is-voting'); + this.classList.remove('disable-hover'); + }); + } + + performResults(results: PollResults, chosenIndex: number) { + if(this.chosenIndex != chosenIndex) { // if we voted + this.isRetracted = this.chosenIndex != -1 && chosenIndex == -1; + this.chosenIndex = chosenIndex; + + if(this.isRetracted) { + this.addEventListener('click', this.clickHandler); + } else { + this.removeEventListener('click', this.clickHandler); + } + } + + // is need update + if(this.chosenIndex != -1 || this.isRetracted) { + const percents = results.results.map(v => v.voters / results.total_voters * 100); + this.setResults(this.isRetracted ? this.percents : percents, chosenIndex); + this.percents = percents; + this.isRetracted = false; + } + + this.setVotersCount(results); + } + + setResults(percents: number[], chosenIndex: number) { + this.svgLines.forEach(svg => svg.style.display = ''); + + if(chosenIndex !== -1) { + let answerDiv = this.answerDivs[chosenIndex]; + if(!this.selectedSpan) { + this.selectedSpan = document.createElement('span'); + this.selectedSpan.classList.add('poll-answer-selected', 'tgico-check'); + } + answerDiv.append(this.selectedSpan); } let maxValue = Math.max(...percents); - this.maxLengths = percents.map(p => p / maxValue * this.maxLength); - /* this.svgLines.forEach((svg, idx) => { - this.setLineProgress(idx, 1); - }); */ + // line + if(this.isRetracted) { + this.svgLines.forEach((svg, idx) => { + this.setLineProgress(idx, -1); + }); + } else { + this.svgLines.forEach((svg, idx) => { + void svg.getBoundingClientRect(); // reflow + this.setLineProgress(idx, 1); + }); + } - /* percents = percents.map(p => { - return Math.round(p); - }); */ - - console.log('setResults before percents:', percents); - - let sum = percents.reduce((acc, p) => acc + Math.round(p), 0); - if(sum > 100) { - let diff = sum - 100; - let length = percents.length; - for(let i = 0; i < diff; ++i) { - let minIndex = -1, minRemainder = 1; - for(let k = 0; k < length; ++k) { - let remainder = percents[k] % 1; - if(remainder >= 0.5 && remainder < minRemainder) { - minRemainder = remainder; - minIndex = k; - } - } - - if(minIndex == -1) { - throw new Error('lol chto'); - } - - percents[minIndex] -= minRemainder; + percents = percents.slice(); + roundPercents(percents); + // numbers + if(this.isRetracted) { + for(let i = (times - 1), k = 0; i >= 0; --i, ++k) { + setTimeout(() => { + percents.forEach((percents, idx) => { + let value = Math.round(percents / times * i); + this.numberDivs[idx].innerText = value + '%'; + }); + }, oneTime * k); } } else { - let diff = 100 - sum; - let length = percents.length; - for(let i = 0; i < diff; ++i) { - let minIndex = -1, maxRemainder = 0; - for(let k = 0; k < length; ++k) { - let remainder = percents[k] % 1; - if(remainder < 0.5 && remainder > maxRemainder) { - maxRemainder = remainder; - minIndex = k; - } - } - - if(minIndex == -1) { - throw new Error('lol chto'); - } - - percents[minIndex] += 1 - maxRemainder; + for(let i = 0; i < times; ++i) { + setTimeout(() => { + percents.forEach((percents, idx) => { + let value = Math.round(percents / times * (i + 1)); + this.numberDivs[idx].innerText = value + '%'; + }); + }, oneTime * i); } } - console.log('setResults after percents:', percents, sum); - - let start = Date.now(); - let r = () => { - let diff = Date.now() - start; - let progress = diff / fullTime; - if(progress > 1) progress = 1; - - this.svgLines.forEach((svg, idx) => { - this.setLineProgress(idx, progress); - }); - - if(progress < 1) { - window.requestAnimationFrame(r); - } - }; - window.requestAnimationFrame(r); - - for(let i = 0; i < times; ++i) { + if(this.isRetracted) { + this.classList.add('is-retracting'); + this.classList.remove('is-voted'); setTimeout(() => { - percents.forEach((percents, idx) => { - let value = Math.round(percents / times * (i + 1)); - let div = this.numberDivs[idx]; - //div.style.opacity = ((i + 1) * 0.10).toFixed(1); // опасити в 10 шагов от 0.1 до 1 - div.innerText = value + '%'; - }); - }, oneTime * i); + this.classList.remove('is-retracting'); + this.svgLines.forEach(svg => svg.style.display = 'none'); + }, fullTime); + } else { + this.classList.add('is-voted'); } + } - this.classList.add('is-voted'); + setVotersCount(results: PollResults) { + let votersCount = results.total_voters || 0; + let votersOrAnswers = this.isQuiz ? (votersCount > 1 || !votersCount ? 'answers' : 'answer') : (votersCount > 1 || !votersCount ? 'votes' : 'vote'); + + this.votersCountDiv.innerText = `${results.total_voters ? results.total_voters + ' ' + votersOrAnswers : 'No ' + votersOrAnswers}`; } setLineProgress(index: number, percents: number) { let svg = this.svgLines[index]; - svg.style.strokeDasharray = (percents * this.maxLengths[index]) + ', 485.9'; - svg.style.strokeDashoffset = '' + percents * this.maxOffset; + + if(percents == -1) { + svg.style.strokeDasharray = ''; + svg.style.strokeDashoffset = ''; + } else { + svg.style.strokeDasharray = (percents * this.maxLengths[index]) + ', 485.9'; + svg.style.strokeDashoffset = '' + percents * this.maxOffset; + } } // у элемента могут быть ещё другие методы и свойства diff --git a/src/components/scrollable_new.ts b/src/components/scrollable_new.ts index 66b42ba6..3ea5f75f 100644 --- a/src/components/scrollable_new.ts +++ b/src/components/scrollable_new.ts @@ -1,5 +1,7 @@ import { logger, deferredPromise, CancellablePromise } from "../lib/polyfill"; - +import smoothscroll from '../lib/smoothscroll'; +(window as any).__forceSmoothScrollPolyfill__ = true; +smoothscroll.polyfill(); /* var el = $0; var height = 0; @@ -66,6 +68,8 @@ export default class Scrollable { private lastBottomID = 0; private lastScrollDirection = 0; // true = bottom + private scrollLocked = 0; + private setVisible(element: HTMLElement) { if(this.visible.has(element)) return; @@ -152,61 +156,6 @@ export default class Scrollable { } }); - // внизу - самый производительный вариант - if(false) this.observer = new IntersectionObserver(entries => { - entries/* .filter(entry => entry.isIntersecting) */.forEach((entry, idx, arr) => { - let target = entry.target as HTMLElement; - - if(entry.isIntersecting) { - let isTop = entry.boundingClientRect.top <= 0; - let isBottom = entry.rootBounds.height <= (entry.boundingClientRect.top + entry.boundingClientRect.height); - - /* let id = +target.dataset.virtual; - let isOutOfRange = id < (this.lastTopID - 15) || id > (this.lastBottomID + 15); - if(isOutOfRange) { - this.debug && this.log('out of range, scroll jumped!'); - if(idx == 0) this.lastTopID = id; - else if(idx == (arr.length - 1)) this.lastBottomID = id; - } */ - - this.setVisible(target); - if(isTop) { - /* this.lastTopID = id; - this.debug && this.log('set lastTopID to:', this.lastTopID); */ - - for(let i = 0; i < 15; ++i) { - target = target.previousElementSibling as HTMLElement; - if(!target) break; - this.setVisible(target); - } - } else if(isBottom) { - /* this.lastBottomID = id; - this.debug && this.log('set lastBottomID to:', this.lastBottomID); */ - - for(let i = 0; i < 15; ++i) { - target = target.nextElementSibling as HTMLElement; - if(!target) break; - this.setVisible(target); - } - } - } else { - this.setHidden(target); - } - - - //this.debug && this.log('intersection entry:', entry, isTop, isBottom, this.lastTopID, this.lastBottomID); - }); - - /* let minVisibleID = this.lastTopID - 15; - let maxVisibleID = this.lastBottomID + 15; - for(let target of this.visible) { - let id = +target.dataset.virtual; - if(id < minVisibleID || id > maxVisibleID) { - this.setHidden(target); - } - } */ - }); - if(!appendTo) { this.appendTo = this.container; } @@ -326,7 +275,7 @@ export default class Scrollable { let scrollTop = scrollPos - this.scrollTopOffset; let maxScrollTop = this.scrollSize - this.scrollTopOffset - this.size; - if(this.onScrolledBottom) { + if(this.onScrolledBottom && !this.scrollLocked) { if((maxScrollTop - scrollTop) <= this.onScrollOffset) { //if(!this.onScrolledBottomFired) { this.onScrolledBottomFired = true; @@ -337,7 +286,7 @@ export default class Scrollable { } } - if(this.onScrolledTop) { + if(this.onScrolledTop && !this.scrollLocked) { //this.log('onScrolledTop:', scrollTop, this.onScrollOffset); if(scrollTop <= this.onScrollOffset) { this.onScrolledTopFired = true; @@ -357,6 +306,23 @@ export default class Scrollable { }); } + public reorder() { + (Array.from(this.splitUp.children) as HTMLElement[]).forEach((el, idx) => { + el.dataset.virtual = '' + idx; + }); + } + + public updateElement(element: HTMLElement) { + element.style.minHeight = ''; + window.requestAnimationFrame(() => { + let height = element.scrollHeight; + + window.requestAnimationFrame(() => { + element.style.minHeight = height + 'px'; + }); + }); + } + public prepareElement(element: HTMLElement, append = true) { element.dataset.virtual = '' + (append ? this.virtualTempIDBottom++ : this.virtualTempIDTop--); @@ -401,9 +367,29 @@ export default class Scrollable { return !!element.parentElement; } - public scrollIntoView(element: Element) { + public scrollIntoView(element: HTMLElement) { if(element.parentElement) { - element.scrollIntoView(); + let scrollTop = this.scrollTop; + let offsetTop = element.offsetTop; + let clientHeight = this.container.clientHeight; + + let height = element.scrollHeight; + + let diff = (clientHeight - height) / 2; + + /* if(scrollTop < offsetTop) { + offsetTop += diff; + } else { */ + offsetTop -= diff; + //} + + if(this.scrollLocked) clearTimeout(this.scrollLocked); + this.scrollLocked = setTimeout(() => { + this.scrollLocked = 0; + this.onScroll(); + }, 468); + this.container.scrollTo({behavior: 'smooth', top: offsetTop}); + //element.scrollIntoView({behavior: 'smooth', block: 'center'}); } } diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 5e0d0a47..c8c527c7 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -875,22 +875,7 @@ export function wrapReply(title: string, subtitle: string, message?: any) { let media = message && message.media; if(media) { - if(message.grouped_id) { - replySubtitle.innerHTML = 'Album'; - } else if(media._ == 'messageMediaContact') { - replySubtitle.innerHTML = 'Contact'; - } else if(media._ == 'messageMediaPoll') { - replySubtitle.innerHTML = media.poll.rReply; - } else if(media.photo) { - replySubtitle.innerHTML = 'Photo'; - } else if(media.document && media.document.type) { - let type = media.document.type as string; - replySubtitle.innerHTML = type.charAt(0).toUpperCase() + type.slice(1); // capitalizeFirstLetter - } else if(media.webpage) { - replySubtitle.innerHTML = RichTextProcessor.wrapPlainText(media.webpage.url); - } else { - replySubtitle.innerHTML = media._; - } + replySubtitle.innerHTML = message.rReply; if(media.photo || (media.document && ['video'].indexOf(media.document.type) !== -1)) { let replyMedia = document.createElement('div'); @@ -1054,8 +1039,9 @@ export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLo } } -export function wrapPoll(poll: Poll, results: PollResults) { +export function wrapPoll(pollID: string, mid: number) { let elem = new PollElement(); - elem.setAttribute('poll-id', poll.id); + elem.setAttribute('poll-id', pollID); + elem.setAttribute('message-id', '' + mid); return elem; } diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index 9aa02951..5758745e 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -143,28 +143,41 @@ export class AppDialogsManager { let dialog: any = e.detail; this.setLastMessage(dialog); + this.setUnreadMessages(dialog); this.setDialogPosition(dialog); + + this.setPinnedDelimiter(); }); $rootScope.$on('dialogs_multiupdate', (e: CustomEvent) => { let dialogs = e.detail; - let performed = 0; for(let id in dialogs) { let dialog = dialogs[id]; /////console.log('updating dialog:', dialog); - ++performed; - if(!(dialog.peerID in this.doms)) { this.addDialog(dialog); - continue; } this.setLastMessage(dialog); + this.setUnreadMessages(dialog); this.setDialogPosition(dialog); } + + this.setPinnedDelimiter(); + }); + + $rootScope.$on('dialog_drop', (e: CustomEvent) => { + let {peerID, dialog} = e.detail; + + let dom = this.getDialogDom(peerID); + if(dom) { + dom.listEl.remove(); + delete this.doms[peerID]; + (dialog.folder_id == 1 ? this.scrollArchived : this.scroll).reorder(); + } }); $rootScope.$on('dialog_unread', (e: CustomEvent) => { @@ -184,6 +197,7 @@ export class AppDialogsManager { }); this.loadDialogs().then(result => { + this.setPinnedDelimiter(); //appSidebarLeft.onChatsScroll(); this.loadDialogs(true); }); @@ -331,10 +345,11 @@ export class AppDialogsManager { }); } - public setDialogPosition(dialog: any) { + public setDialogPosition(dialog: Dialog) { let pos = appMessagesManager.getDialogByPeerID(dialog.peerID)[1]; let dom = this.getDialogDom(dialog.peerID); let prevPos = whichChild(dom.listEl); + if(prevPos == pos) { return; } else if(prevPos < pos) { // was higher @@ -348,102 +363,44 @@ export class AppDialogsManager { chatList.append(dom.listEl); } - // fix order - (Array.from(chatList.children) as HTMLElement[]).forEach((el, idx) => { - el.dataset.virtual = '' + idx; - }); + (dialog.folder_id == 1 ? this.scrollArchived : this.scroll).reorder(); this.log('setDialogPosition:', dialog, dom, pos); } - /* public sortDom(archived = false) { - //if(archived) return; - - let dialogs = appMessagesManager.dialogsStorage.dialogs.slice(); - - let inUpper: Scrollable['hiddenElements']['up'] = []; - let inBottom: Scrollable['hiddenElements']['down'] = []; - let inVisible: Scrollable['visibleElements'] = []; - let pinnedDialogs = []; - - let sorted = dialogs; - - if(!archived) { - for(let i = 0; i < dialogs.length; ++i) { - let dialog = dialogs[i]; - if(!dialog.pFlags.pinned) break; - pinnedDialogs.push(dialog); + public setPinnedDelimiter() { + let index = -1; + let dialogs = appMessagesManager.dialogsStorage.dialogs[0]; + for(let dialog of dialogs) { + if(dialog.pFlags?.pinned) { + index++; } + } - if(pinnedDialogs.length) { - let dom = this.getDialogDom(pinnedDialogs[pinnedDialogs.length - 1].peerID); - if(dom) { - dom.listEl.append(this.pinnedDelimiter); - } - } else { - if(this.pinnedDelimiter.parentElement) { - this.pinnedDelimiter.parentElement.removeChild(this.pinnedDelimiter); - } - } + let currentIndex = (this.pinnedDelimiter.parentElement && whichChild(this.pinnedDelimiter.parentElement)) ?? -1; - sorted = sorted.filter((d: any) => !d.pFlags.pinned && d.folder_id != 1); + if(index == currentIndex) return; + + let children = this.chatList.children; + + let modifying: HTMLElement[] = []; + if(currentIndex != -1 && children.length > currentIndex) { + let li = children[currentIndex] as HTMLElement; + modifying.push(li); + } + + if(index != -1 && children.length > index) { + let li = children[index] as HTMLElement; + modifying.push(li); + li.append(this.pinnedDelimiter); } else { - sorted = sorted.filter((d: any) => d.folder_id == 1); + this.pinnedDelimiter.remove(); } - - sorted = sorted.sort((a: any, b: any) => { - let timeA = appMessagesManager.getMessage(a.top_message).date; - let timeB = appMessagesManager.getMessage(b.top_message).date; - - return timeB - timeA; + + modifying.forEach(elem => { + this.scroll.updateElement(elem); }); - - if(!archived) { - sorted = pinnedDialogs.concat(sorted); - } - - //console.log('sortDom', sorted, this.chatsHidden, this.chatsHidden.up, this.chatsHidden.down); - - let chatList = archived ? this.chatListArchived : this.chatList; - let chatsHidden = archived ? this.chatsArchivedHidden : this.chatsHidden; - let chatsVisible = archived ? this.chatsArchivedVisible : this.chatsVisible; - - let hiddenLength: number = chatsHidden.up.length; - let inViewportLength = chatList.childElementCount; - let concated = chatsHidden.up.concat(chatsVisible, chatsHidden.down); - - //console.log('sortDom clearing innerHTML', archived, hiddenLength, inViewportLength); - - chatList.innerHTML = ''; - - let inViewportIndex = 0; - sorted.forEach((d: any, idx) => { - let dom = this.getDialogDom(d.peerID); - if(!dom) return; - - let child = concated.find((obj: any) => obj.element == dom.listEl); - if(!child) { - return this.log.error('no child by listEl:', dom.listEl, archived, concated); - } - - if(inUpper.length < hiddenLength) { - inUpper.push(child); - } else if(inViewportIndex <= inViewportLength - 1) { - chatList.append(dom.listEl); - inVisible.push(child); - ++inViewportIndex; - } else { - inBottom.push(child); - } - }); - - //console.log('sortDom', sorted.length, inUpper.length, chatList.childElementCount, inBottom.length); - - chatsHidden.up = inUpper; - chatsVisible.length = 0; - chatsVisible.push(...inVisible); - chatsHidden.down = inBottom; - } */ + } public setLastMessage(dialog: any, lastMessage?: any, dom?: DialogDom, highlightWord?: string) { if(!lastMessage) { @@ -465,94 +422,7 @@ export class AppDialogsManager { //console.log('setting last message:', lastMessage); /* if(!dom.lastMessageSpan.classList.contains('user-typing')) */ { - let lastMessageText = ''; - - if(lastMessage.media) { - switch(lastMessage.media._) { - case 'messageMediaPhoto': - lastMessageText += '' + (lastMessage.grouped_id ? 'Album' : 'Photo') + (lastMessage.message ? ', ' : '') + ''; - break; - case 'messageMediaGeo': - lastMessageText += 'Geolocation'; - break; - case 'messageMediaPoll': - lastMessageText += '' + lastMessage.media.poll.rReply + ''; - break; - case 'messageMediaContact': - lastMessageText += 'Contact'; - break; - case 'messageMediaDocument': - let document = lastMessage.media.document; - - let found = false; - for(let attribute of document.attributes) { - if(found) break; - - switch(attribute._) { - case 'documentAttributeSticker': - lastMessageText += RichTextProcessor.wrapRichText(attribute.alt) + 'Sticker'; - found = true; - break; - case 'documentAttributeFilename': - lastMessageText += '' + attribute.file_name + ''; - found = true; - break; - /* default: - console.warn('Got unknown document type!', lastMessage); - break; */ - } - } - - if(document.type == 'video') { - lastMessageText = 'Video' + (lastMessage.message ? ', ' : '') + ''; - found = true; - } else if(document.type == 'voice') { - lastMessageText = 'Voice message'; - found = true; - } else if(document.type == 'gif') { - lastMessageText = 'GIF' + (lastMessage.message ? ', ' : '') + ''; - found = true; - } else if(document.type == 'round') { - lastMessageText = 'Video message' + (lastMessage.message ? ', ' : '') + ''; - found = true; - } - - if(found) { - break; - } - - default: - ///////console.warn('Got unknown lastMessage.media type!', lastMessage); - break; - } - } - - if(lastMessage.action) { - let action = lastMessage.action; - - console.log('lastMessage action:', action); - - let suffix = ''; - let _ = action._; - if(_ == "messageActionPhoneCall") { - _ += '.' + action.type; - - let duration = action.duration; - if(duration) { - let d = []; - - d.push(duration % 60 + ' s'); - if(duration >= 60) d.push((duration / 60 | 0) + ' min'); - //if(duration >= 3600) d.push((duration / 3600 | 0) + ' h'); - suffix = ' (' + d.reverse().join(' ') + ')'; - } - } - - // @ts-ignore - lastMessageText = '' + langPack[_] + suffix + ''; - } - - let messageText = lastMessage.message; + /* let messageText = lastMessage.message; let messageWrapped = ''; if(messageText) { let entities = RichTextProcessor.parseEntities(messageText.replace(/\n/g, ' '), {noLinebreakers: true}); @@ -577,9 +447,10 @@ export class AppDialogsManager { entities: entities, noTextFormat: true }); - } + } */ - dom.lastMessageSpan.innerHTML = lastMessageText + messageWrapped; + //dom.lastMessageSpan.innerHTML = lastMessageText + messageWrapped; + dom.lastMessageSpan.innerHTML = lastMessage.rReply; /* if(lastMessage.from_id == auth.id) { // You: */ if(peer._ != 'peerUser' && peerID != -lastMessage.from_id) { @@ -629,10 +500,9 @@ export class AppDialogsManager { } } - public setUnreadMessages(dialog: any) { + public setUnreadMessages(dialog: Dialog) { let dom = this.getDialogDom(dialog.peerID); - dom.statusSpan.innerHTML = ''; let lastMessage = appMessagesManager.getMessage(dialog.top_message); if(lastMessage._ != 'messageEmpty' && lastMessage.from_id == $rootScope.myID && lastMessage.peerID != $rootScope.myID && @@ -651,10 +521,11 @@ export class AppDialogsManager { } } else dom.statusSpan.classList.remove('tgico-check', 'tgico-checks'); - dom.unreadMessagesSpan.innerHTML = ''; + dom.unreadMessagesSpan.innerText = ''; + dom.unreadMessagesSpan.classList.remove('tgico-pinnedchat'); if(dialog.unread_count) { - dom.unreadMessagesSpan.innerHTML = dialog.unread_count; - dom.unreadMessagesSpan.classList.remove('tgico-pinnedchat'); + dom.unreadMessagesSpan.innerText = '' + dialog.unread_count; + //dom.unreadMessagesSpan.classList.remove('tgico-pinnedchat'); dom.unreadMessagesSpan.classList.add(new Date(dialog.notify_settings.mute_until * 1000) > new Date() ? 'unread-muted' : 'unread'); } else if(dialog.pFlags.pinned) { @@ -826,12 +697,6 @@ export class AppDialogsManager { this.doms[dialog.peerID] = dom; } - if(dialog.pFlags.pinned) { - li.classList.add('dialog-pinned'); - //this.chatList.insertBefore(this.pinnedDelimiter, li.nextSibling); - dom.listEl.append(this.pinnedDelimiter); - } - this.setLastMessage(dialog); } else { container.append(li); diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index d12baf0a..7108a9b2 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -274,7 +274,7 @@ export class AppImManager { /////this.log('setting pinned message', message); this.pinnedMessageContainer.dataset.mid = '' + mid; this.pinnedMessageContainer.style.display = ''; - this.pinnedMessageContent.innerHTML = RichTextProcessor.wrapEmojiText(message.message); + this.pinnedMessageContent.innerHTML = message.rReply; } this.needUpdate.forEachReverse((obj, idx) => { @@ -329,11 +329,17 @@ export class AppImManager { let target = e.target as HTMLElement; let bubble: HTMLDivElement = null; try { - bubble = findUpClassName(e.target, 'bubble'); + bubble = findUpClassName(target, 'bubble'); } catch(err) {} if(!bubble) return; + let contactDiv = findUpClassName(target, 'contact'); + if(contactDiv) { + this.setPeer(+contactDiv.dataset.peerID); + return; + } + //this.log('chatInner click:', target); if(target.tagName == 'SPAN') { (target.parentElement.querySelector('video') as HTMLElement).click(); // hot-fix for time and play button @@ -859,7 +865,7 @@ export class AppImManager { appMessagesManager.wrapSingleMessage(chatInfo.pinned_msg_id); } - let participants_count = chatInfo.participants_count || chatInfo.participants.participants.length; + let participants_count = chatInfo.participants_count || (chatInfo.participants && chatInfo.participants.participants.length); let subtitle = numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members'); if(onlines > 1) { @@ -1091,9 +1097,9 @@ export class AppImManager { return true; })/* .catch(err => { this.log.error(err); - }) *//* , + }) */, - appSidebarRight.fillProfileElements() *//* , + appSidebarRight.fillProfileElements()/* , appSidebarRight.loadSidebarMedia(true) */ ]).catch(err => { this.log.error('setPeer promises error:', err); @@ -1716,6 +1722,8 @@ export class AppImManager { let contactDiv = document.createElement('div'); contactDiv.classList.add('contact'); + contactDiv.dataset.peerID = '' + message.media.user_id; + messageDiv.classList.add('contact-message'); processingWebPage = true; @@ -1724,12 +1732,16 @@ export class AppImManager { if(message.media.last_name) texts.push(RichTextProcessor.wrapEmojiText(message.media.last_name)); contactDiv.innerHTML = ` -
${texts.join(' ')}
${message.media.phone_number ? '+' + formatPhoneNumber(message.media.phone_number).formatted : 'Unknown phone number'}
`; + let avatarDiv = document.createElement('div'); + avatarDiv.classList.add('contact-avatar', 'user-avatar'); + contactDiv.prepend(avatarDiv); + appProfileManager.putPhoto(avatarDiv, message.media.user_id); + bubble.classList.remove('is-message-empty'); messageDiv.append(contactDiv); @@ -1739,7 +1751,7 @@ export class AppImManager { case 'messageMediaPoll': { bubble.classList.remove('is-message-empty'); - let pollElement = wrapPoll(message.media.poll, message.media.results); + let pollElement = wrapPoll(message.media.poll.id, message.mid); messageDiv.prepend(pollElement); break; @@ -1927,11 +1939,14 @@ export class AppImManager { bubbles.push(bubble); }); */ - //let leftHeightToScroll = this.scrollable.innerHeight; + let scrollTop = this.scrollable.scrollTop; + let leftHeightToScroll = scrollTop > 0 ? 0 : (Object.keys(this.bubbles).length > 0 ? 1 : this.scrollable.container.clientHeight); + let setScrollRAF = 0; + let previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop; //console.timeEnd('appImManager: pre render start'); - //this.log('start performHistoryResult, scrollTop:', this.scrollable.scrollTop, this.scrollable.scrollHeight, this.scrollable.innerHeight); + this.log('start performHistoryResult, scrollTop:', scrollTop, leftHeightToScroll); let method = (reverse ? history.shift : history.pop).bind(history); @@ -1981,21 +1996,27 @@ export class AppImManager { } */ if(!renderedFirstScreen) { - if(!this.scrollable.scrollTop) { + /* if(!this.scrollable.scrollTop) { let height = bubble.scrollHeight; //let height = Math.ceil(bubble.getBoundingClientRect().height); this.scrollable.scrollTop += height; //innerHeight -= height; - } - /* if(leftHeightToScroll >= 0) { + } */ + /* if(leftHeightToScroll > 0) { let height = bubble.scrollHeight; leftHeightToScroll -= height; - this.scrollable.scrollTop += height; - } */ else { + + if(setScrollRAF) window.cancelAnimationFrame(setScrollRAF); + setScrollRAF = window.requestAnimationFrame(() => { + //this.scrollable.scrollTop += height; + this.scrollable.scrollTop = this.scrollable.scrollHeight - previousScrollHeightMinusTop; + }); + //this.scrollable.scrollTop += height; + } else { */ renderedFirstScreen = true; resolve(); resolved = true; - } + //} } } else { ////////this.scrollable.append(bubble); @@ -2026,6 +2047,14 @@ export class AppImManager { }; firstLoad ? window.requestAnimationFrame(r) : r(); + + if(reverse) { + //if(setScrollRAF) window.cancelAnimationFrame(setScrollRAF); + //setScrollRAF = window.requestAnimationFrame(() => { + //this.scrollable.scrollTop += height; + this.scrollable.scrollTop = this.scrollable.scrollHeight - previousScrollHeightMinusTop; + //}); + } //r(); /* method((msgID) => { let message = appMessagesManager.getMessage(msgID); @@ -2054,7 +2083,7 @@ export class AppImManager { let pageCount = this.bubblesContainer.clientHeight / 38/* * 1.25 */ | 0; //let loadCount = Object.keys(this.bubbles).length > 0 ? 50 : pageCount; - let realLoadCount = Object.keys(this.bubbles).length > 0 ? 40 : pageCount;//let realLoadCount = 50; + let realLoadCount = Object.keys(this.bubbles).length > 0 ? Math.max(40, pageCount) : pageCount;//let realLoadCount = 50; let loadCount = realLoadCount; if(testScroll) { @@ -2122,10 +2151,9 @@ export class AppImManager { ids = Object.keys(this.bubbles).map(i => +i).sort((a, b) => a - b); } - this.log('getHistory: slice loadedTimes:', reverse, pageCount, this.loadedTopTimes, this.loadedBottomTimes, ids && ids.length); - //let removeCount = loadCount / 2; - let safeCount = Math.min(realLoadCount * 2, 35); // cause i've been runningrunningrunning all day + let safeCount = realLoadCount * 2; // cause i've been runningrunningrunning all day + this.log('getHistory: slice loadedTimes:', reverse, pageCount, this.loadedTopTimes, this.loadedBottomTimes, ids && ids.length, safeCount); if(ids && ids.length > safeCount) { if(reverse) { //ids = ids.slice(-removeCount); diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 0abfd945..b5932851 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -1,4 +1,4 @@ -import { SearchIndexManager, $rootScope, copy, tsNow, safeReplaceObject, dT, _, listMergeSorted, deepEqual } from "../utils"; +import { SearchIndexManager, $rootScope, copy, tsNow, safeReplaceObject, dT, _, listMergeSorted, deepEqual, langPack } from "../utils"; import appMessagesIDsManager from "./appMessagesIDsManager"; import appChatsManager from "./appChatsManager"; import appUsersManager from "./appUsersManager"; @@ -106,15 +106,13 @@ export class AppMessagesManager { public pinnedIndex = 0; public dialogsNum = 0; - public migratedFromTo: any = {}; - public migratedToFrom: any = {}; + public migratedFromTo: {[peerID: number]: number} = {}; + public migratedToFrom: {[peerID: number]: number} = {}; public newMessagesHandlePromise = 0; public newMessagesToHandle: any = {}; public newDialogsHandlePromise = 0; - public newDialogsToHandle: any = {}; - //public notificationsHandlePromise = 0; - //public notificationsToHandle: any = {}; + public newDialogsToHandle: {[peerID: string]: {reload: true} | Dialog} = {}; public newUpdatesAfterReloadToHandle: any = {}; public fwdMessagesPluralize = _('conversation_forwarded_X_messages'); @@ -125,7 +123,7 @@ export class AppMessagesManager { if(maxID && !appMessagesIDsManager.getMessageIDInfo(maxID)[1]) { this.maxSeenID = maxID; } - }) + }); $rootScope.$on('apiUpdate', (e: CustomEvent) => { let update: any = e.detail; @@ -183,7 +181,7 @@ export class AppMessagesManager { index: dialog.index }); } - }) + }); } public getInputEntities(entities: any) { @@ -248,7 +246,7 @@ export class AppMessagesManager { entities: this.getInputEntities(entities), no_webpage: noWebPage, }).then((updates) => { - apiUpdatesManager.processUpdateMessage(updates) + apiUpdatesManager.processUpdateMessage(updates); }, (error) => { if(error && error.type == 'MESSAGE_NOT_MODIFIED') { error.handled = true; @@ -1129,20 +1127,6 @@ export class AppMessagesManager { return false; } - public async getConversation(peerID: number) { - var foundDialog = this.getDialogByPeerID(peerID); - if(foundDialog.length) { - return foundDialog[0]; - } - - return { - peerID: peerID, - top_message: 0, - index: this.generateDialogIndex(this.generateDialogPinnedDate()), - pFlags: {} - }; - } - public getConversations(offsetIndex?: number, limit = 20, folderID = 0) { let curDialogStorage = this.dialogsStorage.dialogs[folderID] ?? (this.dialogsStorage.dialogs[folderID] = []); @@ -1327,13 +1311,47 @@ export class AppMessagesManager { return Promise.all(promises); } + /* + var date = Date.now() / 1000 | 0; + var m = date * 0x10000; + + var k = (date + 1) * 0x10000; + k - m; + 65536 + */ public generateDialogIndex(date?: any) { if(date === undefined) { date = tsNow(true) + serverTimeManager.serverTimeOffset; } + return (date * 0x10000) + ((++this.dialogsNum) & 0xFFFF); } + public generateIndexForDialog(dialog: Dialog) { + let channelID = AppPeersManager.isChannel(dialog.peerID) ? -dialog.peerID : 0; + let mid = appMessagesIDsManager.getFullMessageID(dialog.top_message, channelID); + let message = this.getMessage(mid); + + let topDate = message.date; + if(channelID) { + let channel = appChatsManager.getChat(channelID); + if(!topDate || channel.date && channel.date > topDate) { + topDate = channel.date; + } + } + let savedDraft: any = {};// DraftsManager.saveDraft(peerID, dialog.draft); // warning + if(savedDraft && savedDraft.date > topDate) { + topDate = savedDraft.date; + } + + if(dialog.pFlags.pinned) { + topDate = this.generateDialogPinnedDate(dialog); + //console.log('topDate', peerID, topDate); + } + + dialog.index = this.generateDialogIndex(topDate); + } + public pushDialogToStorage(dialog: Dialog, offsetDate?: number) { let dialogs = this.dialogsStorage.dialogs[dialog.folder_id] ?? (this.dialogsStorage.dialogs[dialog.folder_id] = []); let pos = this.getDialogByPeerID(dialog.peerID)[1]; @@ -1504,7 +1522,7 @@ export class AppMessagesManager { } break; case 'messageMediaPoll': - appPollsManager.savePoll(apiMessage.media.poll, apiMessage.media.results); + apiMessage.media.poll = appPollsManager.savePoll(apiMessage.media.poll, apiMessage.media.results); break; case 'messageMediaDocument': if(apiMessage.media.ttl_seconds) { @@ -1512,6 +1530,8 @@ export class AppMessagesManager { } else { apiMessage.media.document = appDocsManager.saveDoc(apiMessage.media.document, mediaContext); // 11.04.2020 warning } + + break; case 'messageMediaWebPage': /* if(apiMessage.media.webpage.document) { @@ -1615,6 +1635,8 @@ export class AppMessagesManager { } } + apiMessage.rReply = this.getRichReplyText(apiMessage); + if(apiMessage.message && apiMessage.message.length) { var myEntities = RichTextProcessor.parseEntities(apiMessage.message); var apiEntities = apiMessage.entities || []; @@ -1626,7 +1648,110 @@ export class AppMessagesManager { if(!options.isEdited) { this.messagesStorage[mid] = apiMessage; } - }) + }); + } + + public getRichReplyText(message: any) { + let messageText = ''; + + if(message.media) { + switch(message.media._) { + case 'messageMediaPhoto': + messageText += '' + (message.grouped_id ? 'Album' : 'Photo') + (message.message ? ', ' : '') + ''; + break; + case 'messageMediaGeo': + messageText += 'Geolocation'; + break; + case 'messageMediaPoll': + messageText += '' + message.media.poll.rReply + ''; + break; + case 'messageMediaContact': + messageText += 'Contact'; + break; + case 'messageMediaDocument': + let document = message.media.document; + + let found = false; + for(let attribute of document.attributes) { + if(found) break; + + switch(attribute._) { + case 'documentAttributeSticker': + messageText += RichTextProcessor.wrapRichText(attribute.alt) + 'Sticker'; + found = true; + break; + case 'documentAttributeFilename': + messageText += '' + attribute.file_name + ''; + found = true; + break; + /* default: + console.warn('Got unknown document type!', message); + break; */ + } + } + + if(document.type == 'video') { + messageText = 'Video' + (message.message ? ', ' : '') + ''; + found = true; + } else if(document.type == 'voice') { + messageText = 'Voice message'; + found = true; + } else if(document.type == 'gif') { + messageText = 'GIF' + (message.message ? ', ' : '') + ''; + found = true; + } else if(document.type == 'round') { + messageText = 'Video message' + (message.message ? ', ' : '') + ''; + found = true; + } + + if(found) { + break; + } + + default: + ///////console.warn('Got unknown message.media type!', message); + break; + } + } + + if(message.action) { + let action = message.action; + + console.log('message action:', action); + + let suffix = ''; + let _ = action._; + if(_ == "messageActionPhoneCall") { + _ += '.' + action.type; + + let duration = action.duration; + if(duration) { + let d = []; + + d.push(duration % 60 + ' s'); + if(duration >= 60) d.push((duration / 60 | 0) + ' min'); + //if(duration >= 3600) d.push((duration / 3600 | 0) + ' h'); + suffix = ' (' + d.reverse().join(' ') + ')'; + } + } + + // @ts-ignore + messageText = '' + langPack[_] + suffix + ''; + } + + let text = message.message; + let messageWrapped = ''; + if(text) { + let entities = RichTextProcessor.parseEntities(text.replace(/\n/g, ' '), {noLinebreakers: true}); + + messageWrapped = RichTextProcessor.wrapRichText(text, { + noLinebreakers: true, + entities: entities, + noTextFormat: true + }); + } + + return messageText + messageWrapped; } public migrateChecks(migrateFrom: number, migrateTo: number) { @@ -1644,7 +1769,7 @@ export class AppMessagesManager { var foundDialog = this.getDialogByPeerID(migrateFrom); if(foundDialog.length) { this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1); - $rootScope.$broadcast('dialog_drop', {peerID: migrateFrom}); + $rootScope.$broadcast('dialog_drop', {peerID: migrateFrom, dialog: foundDialog[0]}); } $rootScope.$broadcast('dialog_migrate', {migrateFrom: migrateFrom, migrateTo: migrateTo}); @@ -1703,7 +1828,7 @@ export class AppMessagesManager { //console.log('applyConversation', dialogsResult); - var updatedDialogs: any = {}; + var updatedDialogs: {[peerID: number]: Dialog} = {}; var hasUpdated = false; dialogsResult.dialogs.forEach((dialog: any) => { var peerID = AppPeersManager.getPeerID(dialog.peer); @@ -1738,7 +1863,7 @@ export class AppMessagesManager { var foundDialog = this.getDialogByPeerID(peerID); if(foundDialog.length) { this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1); - $rootScope.$broadcast('dialog_drop', {peerID: peerID}); + $rootScope.$broadcast('dialog_drop', {peerID: peerID, dialog: foundDialog[0]}); } } @@ -1806,24 +1931,7 @@ export class AppMessagesManager { dialog.read_inbox_max_id = appMessagesIDsManager.getFullMessageID(dialog.read_inbox_max_id, channelID); dialog.read_outbox_max_id = appMessagesIDsManager.getFullMessageID(dialog.read_outbox_max_id, channelID); - var topDate = message.date; - if(channelID) { - var channel = appChatsManager.getChat(channelID); - if(!topDate || channel.date && channel.date > topDate) { - topDate = channel.date; - } - } - var savedDraft: any = {};// DraftsManager.saveDraft(peerID, dialog.draft); // warning - if(savedDraft && savedDraft.date > topDate) { - topDate = savedDraft.date; - } - - if(dialog.pFlags.pinned) { - topDate = this.generateDialogPinnedDate(dialog); - //console.log('topDate', peerID, topDate); - } - - dialog.index = this.generateDialogIndex(topDate); + this.generateIndexForDialog(dialog); dialog.peerID = peerID; if(!dialog.folder_id) dialog.folder_id = 0; @@ -2146,7 +2254,7 @@ export class AppMessagesManager { }); } - public generateDialogPinnedDate(dialog?: any) { + public generateDialogPinnedDate(dialog?: Dialog) { let pinnedIndex: number; if(dialog) { @@ -2174,10 +2282,10 @@ export class AppMessagesManager { clearTimeout(this.newDialogsHandlePromise); this.newDialogsHandlePromise = 0; - var newMaxSeenID = 0 + let newMaxSeenID = 0; Object.keys(this.newDialogsToHandle).forEach((peerID) => { let dialog = this.newDialogsToHandle[peerID]; - if(dialog.reload) { + if('reload' in dialog) { this.reloadConversation(+peerID); delete this.newDialogsToHandle[peerID]; } else { @@ -2186,7 +2294,9 @@ export class AppMessagesManager { newMaxSeenID = Math.max(newMaxSeenID, dialog.top_message || 0); } } - }) + }); + + console.log('after order:', this.dialogsStorage.dialogs[0].map(d => d.peerID)); if(newMaxSeenID != 0) { this.incrementMaxSeenID(newMaxSeenID); @@ -2339,7 +2449,7 @@ export class AppMessagesManager { } public handleUpdate(update: any) { - //console.log('AMM: handleUpdate:', update._); + console.log('AMM: handleUpdate:', update._); switch(update._) { case 'updateMessageID': { var randomID = update.random_id; @@ -2469,26 +2579,37 @@ export class AppMessagesManager { break; } - /* case 'updateDialogPinned': { - var peerID = AppPeersManager.getPeerID(update.peer); - var foundDialog = this.getDialogByPeerID(peerID); + case 'updateDialogPinned': { + console.log('updateDialogPinned', update); + let peerID = AppPeersManager.getPeerID(update.peer.peer); + let foundDialog = this.getDialogByPeerID(peerID); - if(!foundDialog.length || !update.pFlags.pinned) { - this.newDialogsToHandle[peerID] = {reload: true}; - if(!this.newDialogsHandlePromise) { - this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0); - } - break; + if(!this.newDialogsHandlePromise) { + this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0); } - var dialog = foundDialog[0]; - dialog.index = this.generateDialogIndex(this.generateDialogPinnedDate(dialog)); - dialog.pFlags.pinned = true; + if(!foundDialog.length) { + this.newDialogsToHandle[peerID] = {reload: true}; + break; + } else { + let dialog = foundDialog[0]; + this.newDialogsToHandle[peerID] = dialog; + + if(!update.pFlags.pinned) { + delete dialog.pFlags.pinned; + } else { // means set + dialog.pFlags.pinned = true; + } + + this.generateIndexForDialog(dialog); + } + break; } case 'updatePinnedDialogs': { - var newPinned: any = {}; + console.log('updatePinnedDialogs', update); + let newPinned: {[peerID: number]: true} = {}; if(!update.order) { apiManager.invokeApi('messages.getPinnedDialogs', {}).then((dialogsResult: any) => { dialogsResult.dialogs.reverse(); @@ -2498,8 +2619,8 @@ export class AppMessagesManager { newPinned[dialog.peerID] = true; }); - this.dialogsStorage.dialogs.forEach((dialog: any) => { - var peerID = dialog.peerID; + this.dialogsStorage.dialogs[0].forEach((dialog: any) => { + let peerID = dialog.peerID; if(dialog.pFlags.pinned && !newPinned[peerID]) { this.newDialogsToHandle[peerID] = {reload: true}; if(!this.newDialogsHandlePromise) { @@ -2511,42 +2632,45 @@ export class AppMessagesManager { break; } - update.order.reverse(); + console.log('before order:', this.dialogsStorage.dialogs[0].map(d => d.peerID)); + + this.pinnedIndex = 0; + let willHandle = false; + update.order.reverse(); // index must be higher update.order.forEach((peer: any) => { - var peerID = AppPeersManager.getPeerID(peer); + let peerID = AppPeersManager.getPeerID(peer.peer); newPinned[peerID] = true; - var foundDialog = this.getDialogByPeerID(peerID); - + let foundDialog = this.getDialogByPeerID(peerID); if(!foundDialog.length) { - this.newDialogsToHandle[peerID] = {reload: true} - if(!this.newDialogsHandlePromise) { - this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0); - } + this.newDialogsToHandle[peerID] = {reload: true}; + willHandle = true; return; } - var dialog = foundDialog[0] - dialog.index = this.generateDialogIndex(this.generateDialogPinnedDate(dialog)); + let dialog = foundDialog[0]; + delete dialog.pinnedIndex; dialog.pFlags.pinned = true; + this.generateIndexForDialog(dialog); - this.newDialogsToHandle[peerID] = dialog - if(!this.newDialogsHandlePromise) { - this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0); - } - }) + this.newDialogsToHandle[peerID] = dialog; + willHandle = true; + }); - this.dialogsStorage.dialogs.forEach((dialog: any) => { - var peerID = dialog.peerID; + this.dialogsStorage.dialogs[0].forEach(dialog => { + let peerID = dialog.peerID; if(dialog.pFlags.pinned && !newPinned[peerID]) { - this.newDialogsToHandle[peerID] = {reload: true} - if(!this.newDialogsHandlePromise) { - this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0); - } + this.newDialogsToHandle[peerID] = {reload: true}; + willHandle = true; } - }) + }); + + if(willHandle && !this.newDialogsHandlePromise) { + this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0); + } + break; - } */ + } case 'updateEditMessage': case 'updateEditChannelMessage': { @@ -2819,7 +2943,7 @@ export class AppMessagesManager { } else { if(foundDialog[0]) { this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1); - $rootScope.$broadcast('dialog_drop', {peerID: peerID}); + $rootScope.$broadcast('dialog_drop', {peerID: peerID, dialog: foundDialog[0]}); } } } @@ -2964,25 +3088,16 @@ export class AppMessagesManager { }); } - public getHistory(peerID: number, maxID = 0, limit = 0, backLimit?: number, prerendered?: number) { + public getHistory(peerID: number, maxID = 0, limit: number, backLimit?: number) { if(this.migratedFromTo[peerID]) { peerID = this.migratedFromTo[peerID]; } - var historyStorage = this.historiesStorage[peerID]; + var historyStorage = this.historiesStorage[peerID] ?? (this.historiesStorage[peerID] = {count: null, history: [], pending: []}); var offset = 0; var offsetNotFound = false; var unreadOffset = 0; var unreadSkip = false; - prerendered = prerendered ? Math.min(50, prerendered) : 0; - - if(historyStorage === undefined) { - historyStorage = this.historiesStorage[peerID] = {count: null, history: [], pending: []}; - } - - if(maxID < 0) { - maxID = 0; - } var isMigrated = false; var reqPeerID = peerID; if(this.migratedToFrom[peerID]) { @@ -2992,30 +3107,6 @@ export class AppMessagesManager { } } - if(!limit && !maxID) { - var foundDialog = this.getDialogByPeerID(peerID)[0]; - if(foundDialog && foundDialog.unread_count > 1) { - var unreadCount = foundDialog.unread_count; - if(unreadSkip = (unreadCount > 50)) { - if(foundDialog.read_inbox_max_id) { - maxID = foundDialog.read_inbox_max_id; - backLimit = 16; - unreadOffset = 16; - limit = 4; - } else { - limit = 20; - unreadOffset = 16; - offset = unreadCount - unreadOffset; - } - } else { - limit = Math.max(10, prerendered, unreadCount + 2); - unreadOffset = unreadCount; - } - }/* else if('Mobile' in Config) { - limit = 20; - } */ - } - if(maxID > 0) { offsetNotFound = true; for(offset = 0; offset < historyStorage.history.length; offset++) { @@ -3035,7 +3126,7 @@ export class AppMessagesManager { offset = Math.max(0, offset - backLimit); limit += backLimit; } else { - limit = limit || (offset ? 20 : (prerendered || 5)); + limit = limit; } var history = historyStorage.history.slice(offset, offset + limit); @@ -3051,9 +3142,6 @@ export class AppMessagesManager { }); } - if(!backLimit && !limit) { - limit = prerendered || 20; - } if(offsetNotFound) { offset = 0; } diff --git a/src/lib/appManagers/appPollsManager.ts b/src/lib/appManagers/appPollsManager.ts index b0eaa4e4..01da304d 100644 --- a/src/lib/appManagers/appPollsManager.ts +++ b/src/lib/appManagers/appPollsManager.ts @@ -1,4 +1,9 @@ import { RichTextProcessor } from "../richtextprocessor"; +import appMessagesManager from './appMessagesManager'; +import appPeersManager from './appPeersManager'; +import apiManager from "../mtproto/mtprotoworker"; +import apiUpdatesManager from "./apiUpdatesManager"; +import { $rootScope } from "../utils"; export type PollAnswer = { _: 'pollAnswer', @@ -58,24 +63,60 @@ export type Poll = { }>, rQuestion?: string, rReply?: string, + chosenIndex?: number }; class AppPollsManager { private polls: {[id: string]: Poll} = {}; private results: {[id: string]: PollResults} = {}; + constructor() { + $rootScope.$on('apiUpdate', (e: CustomEvent) => { + let update = e.detail; + + this.handleUpdate(update); + }); + } + + public handleUpdate(update: any) { + switch(update._) { + case 'updateMessagePoll': { // when someone voted, we too + console.log('updateMessagePoll:', update); + + let poll: Poll = this.polls[update.poll_id] || update.poll; + if(!poll) { + break; + } + + poll = this.savePoll(poll, update.results); + $rootScope.$broadcast('poll_update', {poll, results: update.results}); + break; + } + + default: + break; + } + } + public savePoll(poll: Poll, results: PollResults) { let id = poll.id; if(this.polls[id]) { - this.results[id] = results; - return; + poll = this.polls[id]; + this.saveResults(poll, results); + return poll; } this.polls[id] = poll; - this.results[id] = results; poll.rQuestion = RichTextProcessor.wrapEmojiText(poll.question); poll.rReply = RichTextProcessor.wrapEmojiText('📊') + ' ' + (poll.rQuestion || 'poll'); + this.saveResults(poll, results); + return poll; + } + + public saveResults(poll: Poll, results: PollResults) { + this.results[poll.id] = results; + poll.chosenIndex = (results && results.results && results.results.findIndex(answer => answer.pFlags?.chosen)) ?? -1; } public getPoll(pollID: string): {poll: Poll, results: PollResults} { @@ -84,6 +125,27 @@ class AppPollsManager { results: this.results[pollID] }; } + + public sendVote(mid: number, optionIDs: number[]) { + let message = appMessagesManager.getMessage(mid); + let poll: Poll = message.media.poll; + + let options: Uint8Array[] = optionIDs.map(index => { + return poll.answers[index].option; + }); + + let inputPeer = appPeersManager.getInputPeerByID(message.peerID); + let messageID = message.id; + + return apiManager.invokeApi('messages.sendVote', { + peer: inputPeer, + msg_id: messageID, + options + }).then(updates => { + console.log('appPollsManager sendVote updates:', updates); + apiUpdatesManager.processUpdateMessage(updates); + }); + } } export default new AppPollsManager(); \ No newline at end of file diff --git a/src/lib/smoothscroll.js b/src/lib/smoothscroll.js new file mode 100644 index 00000000..ef5dbebb --- /dev/null +++ b/src/lib/smoothscroll.js @@ -0,0 +1,2 @@ +// credits to https://github.com/iamdustan/smoothscroll +!function(){"use strict";function o(){var o=window,t=document;if(!("scrollBehavior"in t.documentElement.style&&!0!==o.__forceSmoothScrollPolyfill__)){var l,e=o.HTMLElement||o.Element,r=468,i={scroll:o.scroll||o.scrollTo,scrollBy:o.scrollBy,elementScroll:e.prototype.scroll||n,scrollIntoView:e.prototype.scrollIntoView},s=o.performance&&o.performance.now?o.performance.now.bind(o.performance):Date.now,c=(l=o.navigator.userAgent,new RegExp(["MSIE ","Trident/","Edge/"].join("|")).test(l)?1:0);o.scroll=o.scrollTo=function(){void 0!==arguments[0]&&(!0!==f(arguments[0])?h.call(o,t.body,void 0!==arguments[0].left?~~arguments[0].left:o.scrollX||o.pageXOffset,void 0!==arguments[0].top?~~arguments[0].top:o.scrollY||o.pageYOffset):i.scroll.call(o,void 0!==arguments[0].left?arguments[0].left:"object"!=typeof arguments[0]?arguments[0]:o.scrollX||o.pageXOffset,void 0!==arguments[0].top?arguments[0].top:void 0!==arguments[1]?arguments[1]:o.scrollY||o.pageYOffset))},o.scrollBy=function(){void 0!==arguments[0]&&(f(arguments[0])?i.scrollBy.call(o,void 0!==arguments[0].left?arguments[0].left:"object"!=typeof arguments[0]?arguments[0]:0,void 0!==arguments[0].top?arguments[0].top:void 0!==arguments[1]?arguments[1]:0):h.call(o,t.body,~~arguments[0].left+(o.scrollX||o.pageXOffset),~~arguments[0].top+(o.scrollY||o.pageYOffset)))},e.prototype.scroll=e.prototype.scrollTo=function(){if(void 0!==arguments[0])if(!0!==f(arguments[0])){var o=arguments[0].left,t=arguments[0].top;h.call(this,this,void 0===o?this.scrollLeft:~~o,void 0===t?this.scrollTop:~~t)}else{if("number"==typeof arguments[0]&&void 0===arguments[1])throw new SyntaxError("Value could not be converted");i.elementScroll.call(this,void 0!==arguments[0].left?~~arguments[0].left:"object"!=typeof arguments[0]?~~arguments[0]:this.scrollLeft,void 0!==arguments[0].top?~~arguments[0].top:void 0!==arguments[1]?~~arguments[1]:this.scrollTop)}},e.prototype.scrollBy=function(){void 0!==arguments[0]&&(!0!==f(arguments[0])?this.scroll({left:~~arguments[0].left+this.scrollLeft,top:~~arguments[0].top+this.scrollTop,behavior:arguments[0].behavior}):i.elementScroll.call(this,void 0!==arguments[0].left?~~arguments[0].left+this.scrollLeft:~~arguments[0]+this.scrollLeft,void 0!==arguments[0].top?~~arguments[0].top+this.scrollTop:~~arguments[1]+this.scrollTop))},e.prototype.scrollIntoView=function(){if(!0!==f(arguments[0])){var l=function(o){for(;o!==t.body&&!1===(e=p(l=o,"Y")&&a(l,"Y"),r=p(l,"X")&&a(l,"X"),e||r);)o=o.parentNode||o.host;var l,e,r;return o}(this),e=l.getBoundingClientRect(),r=this.getBoundingClientRect();l!==t.body?(h.call(this,l,l.scrollLeft+r.left-e.left,l.scrollTop+r.top-e.top),"fixed"!==o.getComputedStyle(l).position&&o.scrollBy({left:e.left,top:e.top,behavior:"smooth"})):o.scrollBy({left:r.left,top:r.top,behavior:"smooth"})}else i.scrollIntoView.call(this,void 0===arguments[0]||arguments[0])}}function n(o,t){this.scrollLeft=o,this.scrollTop=t}function f(o){if(null===o||"object"!=typeof o||void 0===o.behavior||"auto"===o.behavior||"instant"===o.behavior)return!0;if("object"==typeof o&&"smooth"===o.behavior)return!1;throw new TypeError("behavior member of ScrollOptions "+o.behavior+" is not a valid value for enumeration ScrollBehavior.")}function p(o,t){return"Y"===t?o.clientHeight+c1?1:n,l=.5*(1-Math.cos(Math.PI*c)),e=t.startX+(t.x-t.startX)*l,i=t.startY+(t.y-t.startY)*l,t.method.call(t.scrollable,e,i),e===t.x&&i===t.y||o.requestAnimationFrame(d.bind(o,t))}function h(l,e,r){var c,f,p,a,h=s();l===t.body?(c=o,f=o.scrollX||o.pageXOffset,p=o.scrollY||o.pageYOffset,a=i.scroll):(c=l,f=l.scrollLeft,p=l.scrollTop,a=n),d({scrollable:c,method:a,startTime:h,startX:f,startY:p,x:e,y:r})}}"object"==typeof exports&&"undefined"!=typeof module?module.exports={polyfill:o}:o()}(); \ No newline at end of file diff --git a/src/lib/utils.js b/src/lib/utils.js index 302ce2b8..37d6025b 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -288,7 +288,7 @@ export function getSelectedText() { export const $rootScope = { $broadcast: (name/* : string */, detail/*? : any */) => { - //console.log(dT(), 'Broadcasting ' + name + ' event, with args:', detail); + console.log(dT(), 'Broadcasting ' + name + ' event, with args:', detail); //console.trace(); let myCustomEvent = new CustomEvent(name, {detail}); document.dispatchEvent(myCustomEvent); diff --git a/src/pages/pageSignIn.ts b/src/pages/pageSignIn.ts index 70776a3e..2156f42c 100644 --- a/src/pages/pageSignIn.ts +++ b/src/pages/pageSignIn.ts @@ -5,6 +5,7 @@ import Config from '../lib/config'; import { findUpTag } from "../lib/utils"; import pageAuthCode from "./pageAuthCode"; +import pageSignQR from './pageSignQR'; //import apiManager from "../lib/mtproto/apiManager"; import apiManager from "../lib/mtproto/mtprotoworker"; import Page from "./page"; @@ -54,6 +55,10 @@ let onFirstMount = () => { let initedSelect = false; + page.pageEl.querySelector('.a-qr').addEventListener('click', () => { + pageSignQR.mount(); + }); + selectCountryCode.addEventListener('focus', function(this: typeof selectCountryCode, e) { /* this.removeAttribute('readonly'); */ if(!initedSelect) { @@ -114,7 +119,7 @@ let onFirstMount = () => { parent.appendChild(wrapper); }/* , {once: true} */); selectCountryCode.addEventListener('blur', function(this: typeof selectCountryCode, e) { - //parent.removeChild(wrapper); + parent.removeChild(wrapper); e.cancelBubble = true; }, {capture: true}); diff --git a/src/pages/pageSignQR.ts b/src/pages/pageSignQR.ts index da828a64..2ba6a2d3 100644 --- a/src/pages/pageSignQR.ts +++ b/src/pages/pageSignQR.ts @@ -3,6 +3,7 @@ import apiManager from '../lib/mtproto/mtprotoworker'; import Page from './page'; import pageIm from './pageIm'; import pagePassword from './pagePassword'; +import pageSignIn from './pageSignIn'; import { App } from '../lib/mtproto/mtproto_config'; import { bytesToBase64, bytesCmp } from '../lib/bin_utils'; import serverTimeManager from '../lib/mtproto/serverTimeManager'; @@ -52,6 +53,10 @@ interface LoginTokenSuccess { let onFirstMount = async() => { const pageElement = page.pageEl; const imageDiv = pageElement.querySelector('.auth-image') as HTMLDivElement; + + page.pageEl.querySelector('.a-qr').addEventListener('click', () => { + pageSignIn.mount(); + }); const results = await Promise.all([ import('qr-code-styling' as any) diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 04921504..4fbe79db 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -362,6 +362,11 @@ $time-background: rgba(0, 0, 0, 0.35); background-position: center center; } + i { + font-style: normal; + color: $color-blue; + } + img.emoji { height: 16px; width: 16px; diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index 136f317b..43f7e299 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -6,6 +6,18 @@ max-width: $chat-max-width; margin: 0 auto; + &.is-selected { + &:before { + position: absolute; + width: 100%; + height: 100%; + background-color: #7ca09f; + content: " "; + display: block; + left: 0; + } + } + &.is-date { position: -webkit-sticky; position: sticky; @@ -106,7 +118,7 @@ } } - &:not(.is-group-last) .user-avatar { + &:not(.is-group-last) .bubble__container > .user-avatar { display: none; } @@ -188,7 +200,7 @@ } } - &.emoji-1x { + &.emoji-1x .attachment { font-size: 96px; img.emoji { @@ -199,7 +211,7 @@ } } - &.emoji-2x { + &.emoji-2x .attachment { font-size: 64px; img.emoji { @@ -210,7 +222,7 @@ } } - &.emoji-3x { + &.emoji-3x .attachment { font-size: 52px; img.emoji { @@ -590,6 +602,10 @@ content: "\e929"; margin-right: -2px; } + + .time { + width: unset; + } } .message.contact-message { @@ -599,6 +615,7 @@ .contact { display: flex; padding: 2px 0; + cursor: pointer; &-avatar { color: #fff; @@ -639,6 +656,8 @@ &-name { line-height: 1.4; margin-top: 1px; + overflow: hidden; + text-overflow: ellipsis; } } } @@ -844,15 +863,15 @@ cursor: pointer; margin-right: 5px; } + + img.emoji { + margin-bottom: 3px; + } } } -.bubble-audio.is-in .time { - width: inherit; -} - -.bubble-audio.is-out .time { - width: inherit; +.bubble-audio .time { + width: unset !important; } .bubble.is-in { @@ -959,6 +978,14 @@ } } } + + /* .poll { + &-answer-selected { + &:before { + margin-left: -1px; + } + } + } */ } .bubble.is-out { @@ -1122,6 +1149,10 @@ stroke: #4fae4e; } + &-answer-selected { + background-color: #4fae4e; + } + &-answer:hover { .animation-ring { background-color: rgba(79, 174, 78, 0.08); @@ -1163,7 +1194,7 @@ poll-element { &-text { margin-top: 7px; - margin-left: 7px; + margin-left: 14px; } &-percents { @@ -1174,7 +1205,26 @@ poll-element { font-weight: 500; margin-top: 7px; transition: .34s opacity; - margin-left: -1px; + margin-left: -3px; + text-align: right; + width: 40px; + } + + &-selected { + position: absolute; + top: 33px; + left: 26px; + color: #fff; + background: #50a2e9; + border-radius: 50%; + height: 12px; + width: 12px; + font-size: 11px; + line-height: 15px; + opacity: 0; + animation: fadeIn .1s ease forwards; + animation-direction: reverse; + animation-delay: .24s; } &:hover { @@ -1182,7 +1232,9 @@ poll-element { visibility: visible; transform: scale(1); } - + } + + &.is-voting { .progress-ring__circle { stroke-dashoffset: -19.792; animation: pollAnswerRotate 0.65s linear infinite; @@ -1197,13 +1249,16 @@ poll-element { } &-line { - height: 28px; + height: 35px; position: absolute; - left: 11.5px; - top: 14px; + left: 17.5px; + top: 11px; + transition: stroke-dashoffset .34s linear, stroke-dasharray .34s linear; + stroke-dashoffset: 0; + stroke-dasharray: 0, 485.9; use { - stroke-width: 3.5px; + stroke-width: 4px; stroke-linecap: round; stroke: #50a2e9; fill: none; @@ -1223,7 +1278,7 @@ poll-element { align-items: center; width: 34px; height: 34px; - margin-left: -1px; + margin-left: 5px; position: absolute; left: 0; top: 0; @@ -1256,6 +1311,7 @@ poll-element { stroke-dashoffset: 0; stroke-opacity: 1; stroke-width: 2; + stroke: #8d969c; fill: transparent; } } @@ -1268,6 +1324,20 @@ poll-element { .poll-answer-percents { opacity: 1; } + + .poll-answer-selected { + animation-direction: normal; + } + } + + &.is-retracting { + .circle-hover { + transition-delay: .24s; + } + + .animation-ring { + transition-delay: .22s; + } } } diff --git a/src/scss/partials/_chatlist.scss b/src/scss/partials/_chatlist.scss index 8d21ed7c..a8448777 100644 --- a/src/scss/partials/_chatlist.scss +++ b/src/scss/partials/_chatlist.scss @@ -102,10 +102,6 @@ } } - /* li.dialog-pinned + .pinned-delimiter { - display: flex; - } */ - p { margin: 0; display: flex; diff --git a/src/scss/style.scss b/src/scss/style.scss index 4c03b680..229425d2 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -634,6 +634,20 @@ input { } } +.page-sign, .page-signQR { + .qr { + margin-top: 1.5rem; + } + + p.qr-description { + color: #707579; + line-height: 1.85; + text-align: left; + margin-left: auto; + margin-right: auto; + } +} + /* .page-signQR { .auth-image { position: relative;