From 13c257ddbd794084ba00489c5ae34999e099a37d Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Thu, 20 Feb 2020 15:13:15 +0700 Subject: [PATCH] :( --- src/components/chatInput.ts | 446 ++++++++++++++++++++++++ src/components/pageIm.ts | 13 +- src/components/scrollable.ts | 20 ++ src/lib/appManagers/appImManager.ts | 513 ++-------------------------- src/scss/partials/_chat.scss | 3 +- 5 files changed, 492 insertions(+), 503 deletions(-) create mode 100644 src/components/chatInput.ts diff --git a/src/components/chatInput.ts b/src/components/chatInput.ts new file mode 100644 index 00000000..4a695ff4 --- /dev/null +++ b/src/components/chatInput.ts @@ -0,0 +1,446 @@ +import Scrollable from "./scrollable"; +import LazyLoadQueue from "./lazyLoadQueue"; +import { RichTextProcessor } from "../lib/richtextprocessor"; +import apiManager from "../lib/mtproto/apiManager"; +import appWebPagesManager from "../lib/appManagers/appWebPagesManager"; +import appImManager from "../lib/appManagers/appImManager"; +import { calcImageInBox, getRichValue } from "../lib/utils"; +import { wrapDocument, wrapReply } from "./wrappers"; +import appMessagesManager from "../lib/appManagers/appMessagesManager"; +import initEmoticonsDropdown, { EMOTICONSSTICKERGROUP } from "./emoticonsDropdown"; +import lottieLoader from "../lib/lottieLoader"; + +export class ChatInput { + public pageEl = document.querySelector('.page-chats') as HTMLDivElement; + public messageInput = document.getElementById('input-message') as HTMLDivElement/* HTMLInputElement */; + public fileInput = document.getElementById('input-file') as HTMLInputElement; + public inputMessageContainer = document.getElementsByClassName('input-message-container')[0] as HTMLDivElement; + public inputScroll = new Scrollable(this.inputMessageContainer); + public btnSend = document.getElementById('btn-send') as HTMLButtonElement; + public emoticonsDropdown: HTMLDivElement = null; + public emoticonsTimeout: number = 0; + public toggleEmoticons: HTMLButtonElement; + public emoticonsLazyLoadQueue: LazyLoadQueue = null; + public lastUrl = ''; + public lastTimeType = 0; + + public attachMenu: { + container?: HTMLButtonElement, + media?: HTMLDivElement, + document?: HTMLDivElement, + poll?: HTMLDivElement + } = {}; + + public attachMediaPopUp: { + container?: HTMLDivElement, + titleEl?: HTMLDivElement, + sendBtn?: HTMLButtonElement, + mediaContainer?: HTMLDivElement, + captionInput?: HTMLInputElement + } = {}; + + public replyElements: { + container?: HTMLDivElement, + cancelBtn?: HTMLButtonElement, + titleEl?: HTMLDivElement, + subtitleEl?: HTMLDivElement + } = {}; + + public willSendWebPage: any = null; + public replyToMsgID = 0; + public editMsgID = 0; + public noWebPage = false; + + constructor() { + this.toggleEmoticons = this.pageEl.querySelector('.toggle-emoticons') as HTMLButtonElement; + + this.attachMenu.container = document.getElementById('attach-file') as HTMLButtonElement; + this.attachMenu.media = this.attachMenu.container.querySelector('.menu-media') as HTMLDivElement; + this.attachMenu.document = this.attachMenu.container.querySelector('.menu-document') as HTMLDivElement; + this.attachMenu.poll = this.attachMenu.container.querySelector('.menu-poll') as HTMLDivElement; + + this.attachMediaPopUp.container = this.pageEl.querySelector('.popup-send-photo') as HTMLDivElement; + this.attachMediaPopUp.titleEl = this.attachMediaPopUp.container.querySelector('.popup-title') as HTMLDivElement; + this.attachMediaPopUp.sendBtn = this.attachMediaPopUp.container.querySelector('.btn-primary') as HTMLButtonElement; + this.attachMediaPopUp.mediaContainer = this.attachMediaPopUp.container.querySelector('.popup-photo') as HTMLDivElement; + this.attachMediaPopUp.captionInput = this.attachMediaPopUp.container.querySelector('input') as HTMLInputElement; + + this.replyElements.container = this.pageEl.querySelector('.reply-wrapper') as HTMLDivElement; + this.replyElements.cancelBtn = this.replyElements.container.querySelector('.reply-cancel') as HTMLButtonElement; + this.replyElements.titleEl = this.replyElements.container.querySelector('.reply-title') as HTMLDivElement; + this.replyElements.subtitleEl = this.replyElements.container.querySelector('.reply-subtitle') as HTMLDivElement; + + this.messageInput.addEventListener('keydown', (e: KeyboardEvent) => { + if(e.key == 'Enter') { + /* if(e.ctrlKey || e.metaKey) { + this.messageInput.innerHTML += '
'; + placeCaretAtEnd(this.message) + return; + } */ + + if(e.shiftKey || e.ctrlKey || e.metaKey) { + return; + } + + this.sendMessage(); + } + }); + + this.messageInput.addEventListener('input', (e) => { + //console.log('messageInput input', this.messageInput.innerText, this.serializeNodes(Array.from(this.messageInput.childNodes))); + + let value = this.messageInput.innerText; + + let entities = RichTextProcessor.parseEntities(value); + //console.log('messageInput entities', entities); + + let entityUrl = entities.find(e => e._ == 'messageEntityUrl'); + if(entityUrl) { // need to get webpage + let url = value.slice(entityUrl.offset, entityUrl.offset + entityUrl.length); + + //console.log('messageInput url:', url); + + if(this.lastUrl != url) { + this.lastUrl = url; + this.willSendWebPage = null; + apiManager.invokeApi('messages.getWebPage', { + url: url, + hash: 0 + }).then((webpage: any) => { + appWebPagesManager.saveWebPage(webpage); + if(this.lastUrl != url) return; + console.log('got webpage: ', webpage); + + this.setTopInfo(webpage.site_name || webpage.title, webpage.description || webpage.url); + + this.replyToMsgID = 0; + this.noWebPage = false; + this.willSendWebPage = webpage; + }); + } + } + + if(!value.trim() && !this.serializeNodes(Array.from(this.messageInput.childNodes)).trim()) { + this.messageInput.innerHTML = ''; + this.btnSend.classList.remove('tgico-send'); + this.btnSend.classList.add('tgico-microphone2'); + + appImManager.setTyping('sendMessageCancelAction'); + } else if(!this.btnSend.classList.contains('tgico-send')) { + this.btnSend.classList.add('tgico-send'); + this.btnSend.classList.remove('tgico-microphone2'); + + let time = Date.now(); + if(time - this.lastTimeType >= 6000) { + this.lastTimeType = time; + appImManager.setTyping('sendMessageTypingAction'); + } + } + }); + + if(!RichTextProcessor.emojiSupported) { + this.messageInput.addEventListener('copy', (e) => { + const selection = document.getSelection(); + + let range = selection.getRangeAt(0); + let ancestorContainer = range.commonAncestorContainer; + + let str = ''; + + let selectedNodes = Array.from(ancestorContainer.childNodes).slice(range.startOffset, range.endOffset); + if(selectedNodes.length) { + str = this.serializeNodes(selectedNodes); + } else { + str = selection.toString(); + } + + //console.log('messageInput copy', str, ancestorContainer.childNodes, range); + + //let str = getRichValueWithCaret(this.messageInput); + //console.log('messageInput childNode copy:', str); + + // @ts-ignore + event.clipboardData.setData('text/plain', str); + event.preventDefault(); + }); + } + + this.messageInput.addEventListener('paste', (e) => { + //console.log('messageInput paste'); + + e.preventDefault(); + // @ts-ignore + let text = (e.originalEvent || e).clipboardData.getData('text/plain'); + + // console.log('messageInput paste', text); + text = RichTextProcessor.wrapEmojiText(text); + + // console.log('messageInput paste after', text); + + // @ts-ignore + //let html = (e.originalEvent || e).clipboardData.getData('text/html'); + + // @ts-ignore + //console.log('paste text', text, ); + window.document.execCommand('insertHTML', false, text); + }); + + let attachFile = (file: File) => { + console.log('selected file:', file, typeof(file)); + + willAttachFile = file; + + this.fileInput.value = ''; + + this.attachMediaPopUp.captionInput.value = ''; + this.attachMediaPopUp.mediaContainer.innerHTML = ''; + this.attachMediaPopUp.mediaContainer.style.width = ''; + this.attachMediaPopUp.mediaContainer.style.height = ''; + this.attachMediaPopUp.mediaContainer.classList.remove('is-document'); + + if(file.type.indexOf('video/') === 0) { + willAttach = 'document'; + } else if(file.type.indexOf('image/') === -1 && willAttach == 'media') { + willAttach = 'document'; + } + + switch(willAttach) { + case 'media': { + let img = new Image(); + img.src = URL.createObjectURL(file); + img.onload = () => { + willAttachWidth = img.naturalWidth; + willAttachHeight = img.naturalHeight; + + let {w, h} = calcImageInBox(willAttachWidth, willAttachHeight, 378, 256); + this.attachMediaPopUp.mediaContainer.style.width = w + 'px'; + this.attachMediaPopUp.mediaContainer.style.height = h + 'px'; + this.attachMediaPopUp.mediaContainer.append(img); + }; + + this.attachMediaPopUp.titleEl.innerText = 'Send Photo'; + this.attachMediaPopUp.container.classList.add('active'); + + break; + } + + case 'document': { + let docDiv = wrapDocument({ + file: file, + file_name: file.name || '', + size: file.size, + type: ['image/jpeg', + 'image/png', + 'image/gif', + 'image/webp', + 'image/bmp'].indexOf(file.type) !== -1 ? 'photo' : 'doc' + } as any, false, true); + + this.attachMediaPopUp.titleEl.innerText = 'Send File'; + + this.attachMediaPopUp.mediaContainer.append(docDiv); + this.attachMediaPopUp.mediaContainer.classList.add('is-document'); + this.attachMediaPopUp.container.classList.add('active'); + break; + } + } + }; + + let willAttach = ''; + let willAttachFile: File = null; + let willAttachWidth = 0, willAttachHeight = 0; + this.fileInput.addEventListener('change', (e) => { + var file = (e.target as HTMLInputElement & EventTarget).files[0]; + if(!file) { + return; + } + + attachFile(file); + }, false); + + this.attachMenu.media.addEventListener('click', () => { + willAttach = 'media'; + this.fileInput.click(); + }); + + this.attachMenu.document.addEventListener('click', () => { + willAttach = 'document'; + this.fileInput.click(); + }); + + document.addEventListener('paste', (event) => { + if(!appImManager.peerID || this.attachMediaPopUp.container.classList.contains('active')) { + return; + } + + //console.log('document paste'); + + // @ts-ignore + var items = (event.clipboardData || event.originalEvent.clipboardData).items; + //console.log('item', event.clipboardData.getData()); + for(let i = 0; i < items.length; ++i) { + if(items[i].kind == 'file') { + event.preventDefault() + event.cancelBubble = true; + event.stopPropagation(); + + let file = items[i].getAsFile(); + //console.log(items[i], file); + if(!file) continue; + + willAttach = file.type.indexOf('image/') === 0 ? 'media' : "document"; + attachFile(file); + } + } + }, true); + + this.attachMediaPopUp.sendBtn.addEventListener('click', () => { + this.attachMediaPopUp.container.classList.remove('active'); + let caption = this.attachMediaPopUp.captionInput.value; + + appMessagesManager.sendFile(appImManager.peerID, willAttachFile, { + isMedia: true, + caption, + width: willAttachWidth, + height: willAttachHeight + }); + appImManager.scroll.scrollTop = appImManager.scroll.scrollHeight; + + let dialog = appMessagesManager.getDialogByPeerID(appImManager.peerID)[0]; + if(dialog && dialog.top_message) { + appMessagesManager.readHistory(appImManager.peerID, dialog.top_message); // lol + } + }); + + this.btnSend.addEventListener('click', () => { + if(this.btnSend.classList.contains('tgico-send')) { + this.sendMessage(); + } + }); + + this.toggleEmoticons.onmouseover = (e) => { + clearTimeout(this.emoticonsTimeout); + this.emoticonsTimeout = setTimeout(() => { + if(!this.emoticonsDropdown) { + let res = initEmoticonsDropdown(this.pageEl, appImManager, + appMessagesManager, this.messageInput, this.toggleEmoticons, this.btnSend); + + this.emoticonsDropdown = res.dropdown; + this.emoticonsLazyLoadQueue = res.lazyLoadQueue; + + this.toggleEmoticons.onmouseout = this.emoticonsDropdown.onmouseout = (e) => { + clearTimeout(this.emoticonsTimeout); + this.emoticonsTimeout = setTimeout(() => { + this.emoticonsDropdown.classList.remove('active'); + this.toggleEmoticons.classList.remove('active'); + lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP); + }, 200); + }; + + this.emoticonsDropdown.onmouseover = (e) => { + clearTimeout(this.emoticonsTimeout); + }; + } else { + this.emoticonsDropdown.classList.add('active'); + this.emoticonsLazyLoadQueue.check(); + } + + this.toggleEmoticons.classList.add('active'); + + lottieLoader.checkAnimations(false, EMOTICONSSTICKERGROUP); + }, 0/* 200 */); + }; + + this.replyElements.cancelBtn.addEventListener('click', () => { + this.replyElements.container.classList.remove('active'); + this.replyToMsgID = 0; + + if(this.editMsgID) { + if(this.willSendWebPage) { + let message = appMessagesManager.getMessage(this.editMsgID); + this.setTopInfo('Editing', message.message); + } else { + this.editMsgID = 0; + this.messageInput.innerHTML = ''; + this.btnSend.classList.remove('tgico-send'); + this.btnSend.classList.add('tgico-microphone2'); + } + } + + this.noWebPage = true; + this.willSendWebPage = null; + }); + } + + public serializeNodes(nodes: Node[]): string { + return nodes.reduce((str, child: any) => { + //console.log('childNode', str, child, typeof(child), typeof(child) === 'string', child.innerText); + + if(typeof(child) === 'object' && child.textContent) return str += child.textContent; + if(child.innerText) return str += child.innerText; + if(child.tagName == 'IMG' && child.classList && child.classList.contains('emoji')) return str += child.getAttribute('alt'); + + return str; + }, ''); + }; + + public sendMessage() { + //let str = this.serializeNodes(Array.from(this.messageInput.childNodes)); + let str = getRichValue(this.messageInput); + + //console.log('childnode str after:', str/* , getRichValue(this.messageInput) */); + + //return; + this.lastUrl = ''; + + if(this.editMsgID) { + appMessagesManager.editMessage(this.editMsgID, str, { + noWebPage: this.noWebPage + }); + } else { + appMessagesManager.sendText(appImManager.peerID, str, { + replyToMsgID: this.replyToMsgID == 0 ? undefined : this.replyToMsgID, + noWebPage: this.noWebPage, + webPage: this.willSendWebPage + }); + + appImManager.scroll.scrollTop = appImManager.scroll.scrollHeight; + } + + let dialog = appMessagesManager.getDialogByPeerID(appImManager.peerID)[0]; + if(dialog && dialog.top_message) { + appMessagesManager.readHistory(appImManager.peerID, dialog.top_message); // lol + } + + this.editMsgID = 0; + this.replyToMsgID = 0; + this.noWebPage = false; + this.replyElements.container.classList.remove('active'); + this.willSendWebPage = null; + this.messageInput.innerText = ''; + + this.btnSend.classList.remove('tgico-send'); + this.btnSend.classList.add('tgico-microphone2'); + }; + + public setTopInfo(title: string, subtitle: string, input?: string, media?: any) { + //appImManager.scrollPosition.prepareFor('down'); + + if(this.replyElements.container.lastElementChild.tagName == 'DIV') { + this.replyElements.container.lastElementChild.remove(); + this.replyElements.container.append(wrapReply(title, subtitle, media)); + } + //this.replyElements.titleEl.innerHTML = title ? RichTextProcessor.wrapEmojiText(title) : ''; + //this.replyElements.subtitleEl.innerHTML = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : ''; + this.replyElements.container.classList.add('active'); + + if(input !== undefined) { + this.messageInput.innerHTML = input ? RichTextProcessor.wrapRichText(input) : ''; + + this.btnSend.classList.remove('tgico-microphone2'); + this.btnSend.classList.add('tgico-send'); + } + + //appImManager.scrollPosition.restore(); + } +} diff --git a/src/components/pageIm.ts b/src/components/pageIm.ts index 0d02f02e..c33d5a26 100644 --- a/src/components/pageIm.ts +++ b/src/components/pageIm.ts @@ -1,17 +1,10 @@ //import { appImManager, appMessagesManager, appDialogsManager, apiUpdatesManager, appUsersManager } from "../lib/services"; import { openBtnMenu } from "./misc"; -import Scrollable from './scrollable'; import {stackBlurImage} from '../lib/StackBlur'; -import lottieLoader from "../lib/lottieLoader"; import appSidebarLeft from "../lib/appManagers/appSidebarLeft"; -/* (window as any).libraryLoaded = function(lol: any) { - // @ts-ignore - console.log('libraryLoaded', lol, this, window.webpMachine); -} */ - export default () => import('../lib/services').then(services => { //console.log('included services', services); @@ -21,8 +14,6 @@ export default () => import('../lib/services').then(services => { let pageEl = document.body.getElementsByClassName('page-chats')[0] as HTMLDivElement; pageEl.style.display = ''; - let chatScroll = new Scrollable(document.getElementById('bubbles') as HTMLDivElement).container; - apiUpdatesManager.attach(); // @ts-ignore @@ -179,7 +170,7 @@ export default () => import('../lib/services').then(services => { Array.from(document.getElementsByClassName('btn-menu-toggle')).forEach((el) => { el.addEventListener('click', (e) => { - console.log('click pageIm'); + //console.log('click pageIm'); if(!el.classList.contains('btn-menu-toggle')) return false; //window.removeEventListener('mousemove', onMouseMove); @@ -197,8 +188,6 @@ export default () => import('../lib/services').then(services => { appSidebarLeft.loadDialogs().then(result => { appSidebarLeft.onChatsScroll(); - appImManager.setScroll(chatScroll); - appSidebarLeft.loadDialogs(true); }); }); diff --git a/src/components/scrollable.ts b/src/components/scrollable.ts index 3bba42c0..41971610 100644 --- a/src/components/scrollable.ts +++ b/src/components/scrollable.ts @@ -292,4 +292,24 @@ export default class Scrollable { //lastScrollPos = st; } + + 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; + } } diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 859cdcf8..e8f03ce5 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -1,15 +1,13 @@ import apiManager from '../mtproto/apiManager'; -import { $rootScope, isElementInViewport, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, calcImageInBox, findUpTag, getRichValue, getRichValueWithCaret, getSelectedText, langPack } from "../utils"; +import { $rootScope, isElementInViewport, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, calcImageInBox, findUpTag, langPack } from "../utils"; import appUsersManager from "./appUsersManager"; import appMessagesManager from "./appMessagesManager"; import appPeersManager from "./appPeersManager"; import appProfileManager from "./appProfileManager"; -//import { ProgressivePreloader, wrapDocument, wrapSticker, wrapVideo, wrapPhoto, openBtnMenu, LazyLoadQueue } from "../../components/misc"; import appDialogsManager from "./appDialogsManager"; import { RichTextProcessor } from "../richtextprocessor"; import appPhotosManager from "./appPhotosManager"; import appSidebarRight from './appSidebarRight'; -import Scrollable from '../../components/scrollable'; import { logger } from "../polyfill"; import lottieLoader from "../lottieLoader"; @@ -18,12 +16,11 @@ import appSidebarLeft from "./appSidebarLeft"; import appChatsManager from "./appChatsManager"; import appMessagesIDsManager from "./appMessagesIDsManager"; import apiUpdatesManager from './apiUpdatesManager'; -import initEmoticonsDropdown, { EMOTICONSSTICKERGROUP } from '../../components/emoticonsDropdown'; -import LazyLoadQueue from '../../components/lazyLoadQueue'; import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply } from '../../components/wrappers'; import ProgressivePreloader from '../../components/preloader'; import { openBtnMenu } from '../../components/misc'; -import appWebPagesManager from './appWebPagesManager'; +import { ChatInput } from '../../components/chatInput'; +import Scrollable from '../../components/scrollable'; console.log('appImManager included!'); @@ -38,37 +35,6 @@ class ScrollPosition { this.container = node.parentElement; } - /* public restore() { - if(!this.scroll) return; - //console.log('restore', this.readyFor, this.previousScrollHeightMinusTop); - - //if(this.readyFor === 'up') { - this.scroll.update(true); - - //console.log('restore 2', this.node.scrollHeight, (this.node.scrollHeight - //- this.previousScrollHeightMinusTop) + 'px') - - this.scroll.scroll({y: (this.node.scrollHeight - - this.previousScrollHeightMinusTop) + 'px'}); - //} - - // 'down' doesn't need to be special cased unless the - // content was flowing upwards, which would only happen - // if the container is position: absolute, bottom: 0 for - // a Facebook messages effect - } - - public prepareFor(direction: string) { - if(!this.scroll) return; - - this.readyFor = direction || 'up'; - - this.scroll.update(true); - let pos = this.scroll.scroll(); - this.previousScrollHeightMinusTop = this.node.scrollHeight - - pos.position.y; - } */ - public restore() { //console.log('scrollPosition restore 2', this.node.scrollHeight, (this.node.scrollHeight //- this.previousScrollHeightMinusTop) + 'px', this.container); @@ -93,441 +59,6 @@ class ScrollPosition { } } -class ChatInput { - public pageEl = document.querySelector('.page-chats') as HTMLDivElement; - public messageInput = document.getElementById('input-message') as HTMLDivElement/* HTMLInputElement */; - public fileInput = document.getElementById('input-file') as HTMLInputElement; - public inputMessageContainer = document.getElementsByClassName('input-message-container')[0] as HTMLDivElement; - public inputScroll = new Scrollable(this.inputMessageContainer); - public btnSend = document.getElementById('btn-send') as HTMLButtonElement; - public emoticonsDropdown: HTMLDivElement = null; - public emoticonsTimeout: number = 0; - public toggleEmoticons: HTMLButtonElement; - public emoticonsLazyLoadQueue: LazyLoadQueue = null; - public lastUrl = ''; - public lastTimeType = 0; - - public attachMenu: { - container?: HTMLButtonElement, - media?: HTMLDivElement, - document?: HTMLDivElement, - poll?: HTMLDivElement - } = {}; - - public attachMediaPopUp: { - container?: HTMLDivElement, - titleEl?: HTMLDivElement, - sendBtn?: HTMLButtonElement, - mediaContainer?: HTMLDivElement, - captionInput?: HTMLInputElement - } = {}; - - public replyElements: { - container?: HTMLDivElement, - cancelBtn?: HTMLButtonElement, - titleEl?: HTMLDivElement, - subtitleEl?: HTMLDivElement - } = {}; - - public willSendWebPage: any = null; - public replyToMsgID = 0; - public editMsgID = 0; - public noWebPage = false; - - constructor() { - this.toggleEmoticons = this.pageEl.querySelector('.toggle-emoticons') as HTMLButtonElement; - - this.attachMenu.container = document.getElementById('attach-file') as HTMLButtonElement; - this.attachMenu.media = this.attachMenu.container.querySelector('.menu-media') as HTMLDivElement; - this.attachMenu.document = this.attachMenu.container.querySelector('.menu-document') as HTMLDivElement; - this.attachMenu.poll = this.attachMenu.container.querySelector('.menu-poll') as HTMLDivElement; - - this.attachMediaPopUp.container = this.pageEl.querySelector('.popup-send-photo') as HTMLDivElement; - this.attachMediaPopUp.titleEl = this.attachMediaPopUp.container.querySelector('.popup-title') as HTMLDivElement; - this.attachMediaPopUp.sendBtn = this.attachMediaPopUp.container.querySelector('.btn-primary') as HTMLButtonElement; - this.attachMediaPopUp.mediaContainer = this.attachMediaPopUp.container.querySelector('.popup-photo') as HTMLDivElement; - this.attachMediaPopUp.captionInput = this.attachMediaPopUp.container.querySelector('input') as HTMLInputElement; - - this.replyElements.container = this.pageEl.querySelector('.reply-wrapper') as HTMLDivElement; - this.replyElements.cancelBtn = this.replyElements.container.querySelector('.reply-cancel') as HTMLButtonElement; - this.replyElements.titleEl = this.replyElements.container.querySelector('.reply-title') as HTMLDivElement; - this.replyElements.subtitleEl = this.replyElements.container.querySelector('.reply-subtitle') as HTMLDivElement; - - this.messageInput.addEventListener('keydown', (e: KeyboardEvent) => { - if(e.key == 'Enter') { - /* if(e.ctrlKey || e.metaKey) { - this.messageInput.innerHTML += '
'; - placeCaretAtEnd(this.message) - return; - } */ - - if(e.shiftKey || e.ctrlKey || e.metaKey) { - return; - } - - this.sendMessage(); - } - }); - - this.messageInput.addEventListener('input', (e) => { - //console.log('messageInput input', this.messageInput.innerText, this.serializeNodes(Array.from(this.messageInput.childNodes))); - - let value = this.messageInput.innerText; - - let entities = RichTextProcessor.parseEntities(value); - //console.log('messageInput entities', entities); - - let entityUrl = entities.find(e => e._ == 'messageEntityUrl'); - if(entityUrl) { // need to get webpage - let url = value.slice(entityUrl.offset, entityUrl.offset + entityUrl.length); - - //console.log('messageInput url:', url); - - if(this.lastUrl != url) { - this.lastUrl = url; - this.willSendWebPage = null; - apiManager.invokeApi('messages.getWebPage', { - url: url, - hash: 0 - }).then((webpage: any) => { - appWebPagesManager.saveWebPage(webpage); - if(this.lastUrl != url) return; - console.log('got webpage: ', webpage); - - this.setTopInfo(webpage.site_name || webpage.title, webpage.description || webpage.url); - - this.replyToMsgID = 0; - this.noWebPage = false; - this.willSendWebPage = webpage; - }); - } - } - - if(!value.trim() && !this.serializeNodes(Array.from(this.messageInput.childNodes)).trim()) { - this.messageInput.innerHTML = ''; - this.btnSend.classList.remove('tgico-send'); - this.btnSend.classList.add('tgico-microphone2'); - - appImManager.setTyping('sendMessageCancelAction'); - } else if(!this.btnSend.classList.contains('tgico-send')) { - this.btnSend.classList.add('tgico-send'); - this.btnSend.classList.remove('tgico-microphone2'); - - let time = Date.now(); - if(time - this.lastTimeType >= 6000) { - this.lastTimeType = time; - appImManager.setTyping('sendMessageTypingAction'); - } - } - }); - - if(!RichTextProcessor.emojiSupported) { - this.messageInput.addEventListener('copy', (e) => { - const selection = document.getSelection(); - - let range = selection.getRangeAt(0); - let ancestorContainer = range.commonAncestorContainer; - - let str = ''; - - let selectedNodes = Array.from(ancestorContainer.childNodes).slice(range.startOffset, range.endOffset); - if(selectedNodes.length) { - str = this.serializeNodes(selectedNodes); - } else { - str = selection.toString(); - } - - //console.log('messageInput copy', str, ancestorContainer.childNodes, range); - - //let str = getRichValueWithCaret(this.messageInput); - //console.log('messageInput childNode copy:', str); - - // @ts-ignore - event.clipboardData.setData('text/plain', str); - event.preventDefault(); - }); - } - - this.messageInput.addEventListener('paste', (e) => { - //console.log('messageInput paste'); - - e.preventDefault(); - // @ts-ignore - let text = (e.originalEvent || e).clipboardData.getData('text/plain'); - - // console.log('messageInput paste', text); - text = RichTextProcessor.wrapEmojiText(text); - - // console.log('messageInput paste after', text); - - // @ts-ignore - //let html = (e.originalEvent || e).clipboardData.getData('text/html'); - - // @ts-ignore - //console.log('paste text', text, ); - window.document.execCommand('insertHTML', false, text); - }); - - let attachFile = (file: File) => { - console.log('selected file:', file, typeof(file)); - - willAttachFile = file; - - this.fileInput.value = ''; - - this.attachMediaPopUp.captionInput.value = ''; - this.attachMediaPopUp.mediaContainer.innerHTML = ''; - this.attachMediaPopUp.mediaContainer.style.width = ''; - this.attachMediaPopUp.mediaContainer.style.height = ''; - this.attachMediaPopUp.mediaContainer.classList.remove('is-document'); - - if(file.type.indexOf('video/') === 0) { - willAttach = 'document'; - } else if(file.type.indexOf('image/') === -1 && willAttach == 'media') { - willAttach = 'document'; - } - - switch(willAttach) { - case 'media': { - let img = new Image(); - img.src = URL.createObjectURL(file); - img.onload = () => { - willAttachWidth = img.naturalWidth; - willAttachHeight = img.naturalHeight; - - let {w, h} = calcImageInBox(willAttachWidth, willAttachHeight, 378, 256); - this.attachMediaPopUp.mediaContainer.style.width = w + 'px'; - this.attachMediaPopUp.mediaContainer.style.height = h + 'px'; - this.attachMediaPopUp.mediaContainer.append(img); - }; - - this.attachMediaPopUp.titleEl.innerText = 'Send Photo'; - this.attachMediaPopUp.container.classList.add('active'); - - break; - } - - case 'document': { - let docDiv = wrapDocument({ - file: file, - file_name: file.name || '', - size: file.size, - type: ['image/jpeg', - 'image/png', - 'image/gif', - 'image/webp', - 'image/bmp'].indexOf(file.type) !== -1 ? 'photo' : 'doc' - } as any, false, true); - - this.attachMediaPopUp.titleEl.innerText = 'Send File'; - - this.attachMediaPopUp.mediaContainer.append(docDiv); - this.attachMediaPopUp.mediaContainer.classList.add('is-document'); - this.attachMediaPopUp.container.classList.add('active'); - break; - } - } - }; - - let willAttach = ''; - let willAttachFile: File = null; - let willAttachWidth = 0, willAttachHeight = 0; - this.fileInput.addEventListener('change', (e) => { - var file = (e.target as HTMLInputElement & EventTarget).files[0]; - if(!file) { - return; - } - - attachFile(file); - }, false); - - this.attachMenu.media.addEventListener('click', () => { - willAttach = 'media'; - this.fileInput.click(); - }); - - this.attachMenu.document.addEventListener('click', () => { - willAttach = 'document'; - this.fileInput.click(); - }); - - document.addEventListener('paste', (event) => { - if(!appImManager.peerID || this.attachMediaPopUp.container.classList.contains('active')) { - return; - } - - //console.log('document paste'); - - // @ts-ignore - var items = (event.clipboardData || event.originalEvent.clipboardData).items; - //console.log('item', event.clipboardData.getData()); - for(let i = 0; i < items.length; ++i) { - if(items[i].kind == 'file') { - event.preventDefault() - event.cancelBubble = true; - event.stopPropagation(); - - let file = items[i].getAsFile(); - //console.log(items[i], file); - if(!file) continue; - - willAttach = file.type.indexOf('image/') === 0 ? 'media' : "document"; - attachFile(file); - } - } - }, true); - - this.attachMediaPopUp.sendBtn.addEventListener('click', () => { - this.attachMediaPopUp.container.classList.remove('active'); - let caption = this.attachMediaPopUp.captionInput.value; - - appMessagesManager.sendFile(appImManager.peerID, willAttachFile, { - isMedia: true, - caption, - width: willAttachWidth, - height: willAttachHeight - }); - appImManager.scroll.scrollTop = appImManager.scroll.scrollHeight; - - let dialog = appMessagesManager.getDialogByPeerID(appImManager.peerID)[0]; - if(dialog && dialog.top_message) { - appMessagesManager.readHistory(appImManager.peerID, dialog.top_message); // lol - } - }); - - this.btnSend.addEventListener('click', () => { - if(this.btnSend.classList.contains('tgico-send')) { - this.sendMessage(); - } - }); - - this.toggleEmoticons.onmouseover = (e) => { - clearTimeout(this.emoticonsTimeout); - this.emoticonsTimeout = setTimeout(() => { - if(!this.emoticonsDropdown) { - let res = initEmoticonsDropdown(this.pageEl, appImManager, - appMessagesManager, this.messageInput, this.toggleEmoticons, this.btnSend); - - this.emoticonsDropdown = res.dropdown; - this.emoticonsLazyLoadQueue = res.lazyLoadQueue; - - this.toggleEmoticons.onmouseout = this.emoticonsDropdown.onmouseout = (e) => { - clearTimeout(this.emoticonsTimeout); - this.emoticonsTimeout = setTimeout(() => { - this.emoticonsDropdown.classList.remove('active'); - this.toggleEmoticons.classList.remove('active'); - lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP); - }, 200); - }; - - this.emoticonsDropdown.onmouseover = (e) => { - clearTimeout(this.emoticonsTimeout); - }; - } else { - this.emoticonsDropdown.classList.add('active'); - this.emoticonsLazyLoadQueue.check(); - } - - this.toggleEmoticons.classList.add('active'); - - lottieLoader.checkAnimations(false, EMOTICONSSTICKERGROUP); - }, 0/* 200 */); - }; - - this.replyElements.cancelBtn.addEventListener('click', () => { - this.replyElements.container.classList.remove('active'); - this.replyToMsgID = 0; - - if(this.editMsgID) { - if(this.willSendWebPage) { - let message = appMessagesManager.getMessage(this.editMsgID); - this.setTopInfo('Editing', message.message); - } else { - this.editMsgID = 0; - this.messageInput.innerHTML = ''; - this.btnSend.classList.remove('tgico-send'); - this.btnSend.classList.add('tgico-microphone2'); - } - } - - this.noWebPage = true; - this.willSendWebPage = null; - }); - } - - public serializeNodes(nodes: Node[]): string { - return nodes.reduce((str, child: any) => { - //console.log('childNode', str, child, typeof(child), typeof(child) === 'string', child.innerText); - - if(typeof(child) === 'object' && child.textContent) return str += child.textContent; - if(child.innerText) return str += child.innerText; - if(child.tagName == 'IMG' && child.classList && child.classList.contains('emoji')) return str += child.getAttribute('alt'); - - return str; - }, ''); - }; - - public sendMessage() { - //let str = this.serializeNodes(Array.from(this.messageInput.childNodes)); - let str = getRichValue(this.messageInput); - - //console.log('childnode str after:', str/* , getRichValue(this.messageInput) */); - - //return; - this.lastUrl = ''; - - if(this.editMsgID) { - appMessagesManager.editMessage(this.editMsgID, str, { - noWebPage: this.noWebPage - }); - } else { - appMessagesManager.sendText(appImManager.peerID, str, { - replyToMsgID: this.replyToMsgID == 0 ? undefined : this.replyToMsgID, - noWebPage: this.noWebPage, - webPage: this.willSendWebPage - }); - - appImManager.scroll.scrollTop = appImManager.scroll.scrollHeight; - } - - let dialog = appMessagesManager.getDialogByPeerID(appImManager.peerID)[0]; - if(dialog && dialog.top_message) { - appMessagesManager.readHistory(appImManager.peerID, dialog.top_message); // lol - } - - this.editMsgID = 0; - this.replyToMsgID = 0; - this.noWebPage = false; - this.replyElements.container.classList.remove('active'); - this.willSendWebPage = null; - this.messageInput.innerText = ''; - - this.btnSend.classList.remove('tgico-send'); - this.btnSend.classList.add('tgico-microphone2'); - }; - - public setTopInfo(title: string, subtitle: string, input?: string, media?: any) { - //appImManager.scrollPosition.prepareFor('down'); - - if(this.replyElements.container.lastElementChild.tagName == 'DIV') { - this.replyElements.container.lastElementChild.remove(); - this.replyElements.container.append(wrapReply(title, subtitle, media)); - } - //this.replyElements.titleEl.innerHTML = title ? RichTextProcessor.wrapEmojiText(title) : ''; - //this.replyElements.subtitleEl.innerHTML = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : ''; - this.replyElements.container.classList.add('active'); - - if(input !== undefined) { - this.messageInput.innerHTML = input ? RichTextProcessor.wrapRichText(input) : ''; - - this.btnSend.classList.remove('tgico-microphone2'); - this.btnSend.classList.add('tgico-send'); - } - - //appImManager.scrollPosition.restore(); - } -} - export class AppImManager { public pageEl = document.querySelector('.page-chats') as HTMLDivElement; public btnMute = this.pageEl.querySelector('.tool-mute') as HTMLButtonElement; @@ -535,6 +66,7 @@ export class AppImManager { public avatarEl = document.getElementById('im-avatar') as HTMLDivElement; public titleEl = document.getElementById('im-title') as HTMLDivElement; public subtitleEl = document.getElementById('im-subtitle') as HTMLDivElement; + public bubblesContainer = document.getElementById('bubbles') as HTMLDivElement; public chatInner = document.getElementById('bubbles-inner') as HTMLDivElement; public searchBtn = this.pageEl.querySelector('.chat-search-button') as HTMLButtonElement; public goDownBtn = this.pageEl.querySelector('#bubbles-go-down') as HTMLButtonElement; @@ -569,6 +101,7 @@ export class AppImManager { private loadingMedia = 0; public scroll: HTMLDivElement = null; + public scrollable: Scrollable = null; public scrollPosition: ScrollPosition = null; public log: ReturnType; @@ -920,16 +453,6 @@ export class AppImManager { document.body.addEventListener('keydown', onKeyDown); - /* this.chatInner.addEventListener('mouseover', () => { - document.body.addEventListener('keydown', onKeyDown); - - this.log('mouseover'); - - this.chatInner.addEventListener('mouseout', () => { - document.body.removeEventListener('keydown', onKeyDown); - }, {once: true}); - }); */ - this.chatInner.addEventListener('contextmenu', e => { let bubble: HTMLDivElement = null; @@ -1069,8 +592,8 @@ export class AppImManager { this.updateStatusInterval = window.setInterval(() => this.updateStatus(), 50e3); this.updateStatus(); setInterval(() => this.setPeerStatus(), 60e3); - - this.loadMediaQueueProcess(); + + this.setScroll(); } public deleteMessages(revoke = false) { @@ -1320,8 +843,12 @@ export class AppImManager { } } - public setScroll(scroll: HTMLDivElement) { - this.scroll = scroll; + public setScroll() { + this.scrollable = new Scrollable(this.bubblesContainer); + this.scroll = this.scrollable.container; + + //this.scrollable.setVirtualContainer(this.chatInner); + this.scrollPosition = new ScrollPosition(this.chatInner); this.scroll.addEventListener('scroll', this.onScroll.bind(this)); this.scroll.parentElement.classList.add('scrolled-down'); @@ -1522,8 +1049,14 @@ export class AppImManager { this.setPeerStatus(); + let title = ''; + if(this.peerID == this.myID) { + title = 'Saved Messages'; + } else { + title = appPeersManager.getPeerTitle(this.peerID); + } //this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = dom.titleSpan.innerHTML; - this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = appPeersManager.getPeerTitle(this.peerID); + this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = title; this.topbar.style.display = this.goDownBtn.style.display = ''; appSidebarRight.toggleSidebar(true); @@ -2201,11 +1734,11 @@ export class AppImManager { 20 : (this.chatInner.parentElement.parentElement.scrollHeight) / 30 * 1.25 | 0; - if(testScroll) { + /* if(testScroll) { loadCount = 1; - //if(Object.keys(this.bubbles).length > 0) + if(Object.keys(this.bubbles).length > 0) return Promise.resolve(true); - } + } */ //console.time('render getHistory'); //console.time('render history total'); diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 3a177b89..3cdde24a 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -1144,7 +1144,8 @@ &-title, &-subtitle { font-size: 14px; //line-height: 18px; - line-height: 1; + //line-height: 1; + line-height: 15px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;