Browse Source

send webpage & minor chat fixes

master
Eduard Kuzmenko 5 years ago
parent
commit
196bcf485c
  1. BIN
      .DS_Store
  2. 361
      src/components/emoticonsDropdown.ts
  3. 1
      src/components/misc.ts
  4. 531
      src/components/pageIm.ts
  5. 319
      src/lib/appManagers/appImManager.ts
  6. 6
      src/lib/appManagers/appMessagesManager.ts
  7. 9
      src/lib/appManagers/appSidebarLeft.ts
  8. 8
      src/scss/partials/_chat.scss

BIN
.DS_Store vendored

Binary file not shown.

361
src/components/emoticonsDropdown.ts

@ -0,0 +1,361 @@ @@ -0,0 +1,361 @@
import { AppImManager } from "../lib/appManagers/appImManager";
import { AppMessagesManager } from "../lib/appManagers/appMessagesManager";
import { LazyLoadQueue, horizontalMenu, MTDocument, wrapSticker } from "./misc";
import lottieLoader from "../lib/lottieLoader";
import Scrollable from "./scrollable";
import { findUpTag, whichChild } from "../lib/utils";
import { RichTextProcessor } from "../lib/richtextprocessor";
import appStickersManager, { MTStickerSet } from "../lib/appManagers/appStickersManager";
import apiManager from '../lib/mtproto/apiManager';
import CryptoWorker from '../lib/crypto/cryptoworker';
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
const initEmoticonsDropdown = (pageEl: HTMLDivElement,
appImManager: AppImManager, appMessagesManager: AppMessagesManager,
messageInput: HTMLDivElement, toggleEl: HTMLButtonElement, btnSend: HTMLButtonElement) => {
let dropdown = pageEl.querySelector('.emoji-dropdown') as HTMLDivElement;
dropdown.classList.add('active'); // need
let lazyLoadQueue = new LazyLoadQueue();
let container = pageEl.querySelector('.emoji-container .tabs-container') as HTMLDivElement;
let tabs = pageEl.querySelector('.emoji-dropdown .emoji-tabs') as HTMLUListElement;
horizontalMenu(tabs, container, (id) => {
lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP);
if(id == 1 && stickersInit) {
stickersInit();
}
}, () => {
lottieLoader.checkAnimations(false, EMOTICONSSTICKERGROUP);
lazyLoadQueue.check(); // for stickers
});
(tabs.children[0] as HTMLLIElement).click(); // set media
let emoticonsMenuOnClick = (menu: HTMLUListElement, heights: number[], scroll: Scrollable) => {
menu.addEventListener('click', function(e) {
let target = e.target as HTMLLIElement;
target = findUpTag(target, 'LI');
let index = whichChild(target);
let y = heights[index - 1/* 2 */] || 0; // 10 == padding .scrollable
console.log('emoticonsMenuOnClick', index, y, scroll.container.scrollHeight, scroll);
scroll.onAddedBottom = () => { // привет, костыль, давно не виделись!
scroll.container.scrollTop = y;
scroll.onAddedBottom = () => {};
};
scroll.container.scrollTop = y;
});
};
let emoticonsContentOnScroll = (menu: HTMLUListElement, heights: number[], prevCategoryIndex: number, scroll: HTMLDivElement) => {
let y = scroll.scrollTop;
//console.log(heights, y);
for(let i = 0; i < heights.length; ++i) {
let height = heights[i];
if(y < height) {
menu.children[prevCategoryIndex].classList.remove('active');
prevCategoryIndex = i/* + 1 */;
menu.children[prevCategoryIndex].classList.add('active');
break;
}
}
return prevCategoryIndex;
};
{
let categories = ["Smileys & Emotion", "Animals & Nature", "Food & Drink", "Travel & Places", "Activities", "Objects", "Symbols", "Flags", "Skin Tones"];
let divs: {
[category: string]: HTMLDivElement
} = {};
let keyCategory = Config.Emoji.keyCategory;
let sorted: {
[category: string]: any[]
} = {};
for(let unified in Config.Emoji.emoji) {
// @ts-ignore
let details = Config.Emoji.emoji[unified];
let category = details[keyCategory];
details.unified = unified;
if(!sorted[category]) sorted[category] = [];
sorted[category][details.sort_order] = details;
}
Object.keys(sorted).forEach(c => sorted[c].sort());
categories.pop();
delete sorted["Skin Tones"];
console.time('emojiParse');
for(let category in sorted) {
let div = document.createElement('div');
div.classList.add('emoji-category');
let emojis = sorted[category];
emojis.forEach(details => {
let emoji = details.unified;
//let emoji = (details.unified as string).split('-')
//.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
let spanEmoji = document.createElement('span');
let kek = RichTextProcessor.wrapRichText(emoji);
if(!kek.includes('emoji')) {
console.log(details, emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji));
return;
}
//console.log(kek);
spanEmoji.innerHTML = kek;
//spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
//spanEmoji.setAttribute('emoji', emoji);
div.appendChild(spanEmoji);
});
divs[category] = div;
}
console.timeEnd('emojiParse');
let heights: number[] = [0];
let contentEmojiDiv = document.getElementById('content-emoji') as HTMLDivElement;
categories.forEach(category => {
let div = divs[category];
if(!div) {
console.error('no div by category:', category);
}
contentEmojiDiv.append(div);
heights.push((heights[heights.length - 1] || 0) + div.scrollHeight);
//console.log(div, div.scrollHeight);
});
contentEmojiDiv.addEventListener('click', function(e) {
let target = e.target as any;
//if(target.tagName != 'SPAN') return;
if(target.tagName == 'SPAN' && !target.classList.contains('emoji')) {
target = target.firstElementChild;
} else if(target.tagName == 'DIV') return;
//console.log('contentEmoji div', target);
/* if(!target.classList.contains('emoji')) {
target = target.parentElement as HTMLSpanElement;
if(!target.classList.contains('emoji')) {
return;
}
} */
//messageInput.innerHTML += target.innerHTML;
messageInput.innerHTML += target.outerHTML;
btnSend.classList.add('tgico-send');
btnSend.classList.remove('tgico-microphone2');
});
let prevCategoryIndex = 1;
let menu = contentEmojiDiv.nextElementSibling as HTMLUListElement;
let emojiScroll = new Scrollable(contentEmojiDiv);
emojiScroll.container.addEventListener('scroll', (e) => {
prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, emojiScroll.container);
});
//emojiScroll.setVirtualContainer(emojiScroll.container);
emoticonsMenuOnClick(menu, heights, emojiScroll);
}
let stickersInit = () => {
let contentStickersDiv = document.getElementById('content-stickers') as HTMLDivElement;
//let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement;
let menuWrapper = contentStickersDiv.nextElementSibling as HTMLDivElement;
let menu = menuWrapper.firstElementChild as HTMLUListElement;
let menuScroll = new Scrollable(menuWrapper, true, false);
let stickersDiv = document.createElement('div');
stickersDiv.classList.add('stickers-categories');
contentStickersDiv.append(stickersDiv);
stickersDiv.addEventListener('mouseover', (e) => {
let target = e.target as HTMLElement;
if(target.tagName == 'CANVAS') { // turn on sticker
let animation = lottieLoader.getAnimation(target.parentElement, EMOTICONSSTICKERGROUP);
if(animation) {
// @ts-ignore
if(animation.currentFrame == animation.totalFrames - 1) {
animation.goToAndPlay(0, true);
} else {
animation.play();
}
}
}
});
stickersDiv.addEventListener('click', (e) => {
let target = e.target as HTMLDivElement;
target = findUpTag(target, 'DIV');
let fileID = target.getAttribute('file-id');
let document = appStickersManager.getSticker(fileID);
if(document) {
appMessagesManager.sendFile(appImManager.peerID, document, {isMedia: true});
appImManager.scroll.scrollTop = appImManager.scroll.scrollHeight;
dropdown.classList.remove('active');
toggleEl.classList.remove('active');
} else {
console.warn('got no sticker by id:', fileID);
}
});
let heights: number[] = [];
let categoryPush = (categoryDiv: HTMLDivElement, docs: MTDocument[], prepend?: boolean) => {
//if((docs.length % 5) != 0) categoryDiv.classList.add('not-full');
docs.forEach(doc => {
let div = document.createElement('div');
wrapSticker(doc, div, undefined, lazyLoadQueue, EMOTICONSSTICKERGROUP, true);
categoryDiv.append(div);
});
/* if(prepend) {
stickersDiv.prepend(categoryDiv);
} else {
stickersDiv.append(categoryDiv);
} */
setTimeout(() => lazyLoadQueue.check(), 0);
/* let scrollHeight = categoryDiv.scrollHeight;
let prevHeight = heights[heights.length - 1] || 0;
//console.log('scrollHeight', scrollHeight, categoryDiv, stickersDiv.childElementCount);
if(prepend && heights.length) {// all stickers loaded faster than recent
heights.forEach((h, i) => heights[i] += scrollHeight);
return heights.unshift(scrollHeight) - 1;
} */
heights.length = 0;
Array.from(stickersDiv.children).forEach((div, i) => {
heights[i] = (heights[i - 1] || 0) + div.scrollHeight;
});
//stickersScroll.onScroll();
//return heights.push(prevHeight + scrollHeight) - 1;
};
apiManager.invokeApi('messages.getRecentStickers', {flags: 0, hash: 0}).then((res) => {
let stickers: {
_: string,
hash: number,
packs: any[],
stickers: MTDocument[],
dates: number[]
} = res as any;
let categoryDiv = document.createElement('div');
categoryDiv.classList.add('sticker-category');
stickersDiv.prepend(categoryDiv);
categoryPush(categoryDiv, stickers.stickers, true);
});
apiManager.invokeApi('messages.getAllStickers', {hash: 0}).then((res) => {
let stickers: {
_: 'messages.allStickers',
hash: number,
sets: Array<MTStickerSet>
} = res as any;
stickers.sets/* .slice(0, 10) */.forEach(async(set) => {
let categoryDiv = document.createElement('div');
categoryDiv.classList.add('sticker-category');
let li = document.createElement('li');
li.classList.add('btn-icon');
menu.append(li);
stickersDiv.append(categoryDiv);
let stickerSet = await appStickersManager.getStickerSet(set);
if(stickerSet.set.thumb) {
let thumb = stickerSet.set.thumb;
appStickersManager.getStickerSetThumb(stickerSet.set).then(async(blob) => {
if(thumb.w == 1 && thumb.h == 1) {
const reader = new FileReader();
reader.addEventListener('loadend', async(e) => {
// @ts-ignore
const text = e.srcElement.result;
let json = await CryptoWorker.gzipUncompress<string>(text, true);
let animation = await lottieLoader.loadAnimation({
container: li,
loop: true,
autoplay: false,
animationData: JSON.parse(json)
}, EMOTICONSSTICKERGROUP);
});
reader.readAsArrayBuffer(blob);
} else {
let image = new Image();
image.src = URL.createObjectURL(blob);
li.append(image);
}
});
} else { // as thumb will be used first sticker
wrapSticker(stickerSet.documents[0], li as any, undefined, undefined, EMOTICONSSTICKERGROUP); // kostil
}
categoryPush(categoryDiv, stickerSet.documents);
});
});
let prevCategoryIndex = 0;
let stickersScroll = new Scrollable(contentStickersDiv);
stickersScroll.container.addEventListener('scroll', (e) => {
lazyLoadQueue.check();
lottieLoader.checkAnimations();
prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, stickersScroll.container);
});
stickersScroll.setVirtualContainer(stickersDiv);
emoticonsMenuOnClick(menu, heights, stickersScroll);
stickersInit = null;
};
return {dropdown, lazyLoadQueue};
};
export default initEmoticonsDropdown;

1
src/components/misc.ts

@ -807,6 +807,7 @@ export function openBtnMenu(menuElement: HTMLDivElement) { @@ -807,6 +807,7 @@ export function openBtnMenu(menuElement: HTMLDivElement) {
openedMenu = menuElement;
openedMenu.classList.add('active');
openedMenu.parentElement.classList.add('menu-open');
window.addEventListener('click', () => {
if(openedMenu) {

531
src/components/pageIm.ts

@ -1,368 +1,13 @@ @@ -1,368 +1,13 @@
//import { appImManager, appMessagesManager, appDialogsManager, apiUpdatesManager, appUsersManager } from "../lib/services";
import { horizontalMenu, wrapSticker, MTDocument, LazyLoadQueue, openBtnMenu } from "./misc";
import { LazyLoadQueue, openBtnMenu } from "./misc";
import Scrollable from './scrollable';
import { whichChild, findUpTag } from "../lib/utils";
import {stackBlurImage} from '../lib/StackBlur';
import * as Config from '../lib/config';
import { RichTextProcessor } from "../lib/richtextprocessor";
import { MTProto } from "../lib/mtproto/mtproto";
import lottieLoader from "../lib/lottieLoader";
import CryptoWorker from '../lib/crypto/cryptoworker';
import appStickersManager, { MTStickerSet } from "../lib/appManagers/appStickersManager";
import { AppImManager } from "../lib/appManagers/appImManager";
import { AppMessagesManager } from "../lib/appManagers/appMessagesManager";
import appSidebarLeft from "../lib/appManagers/appSidebarLeft";
const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
let initEmoticonsDropdown = (pageEl: HTMLDivElement,
appImManager: AppImManager, appMessagesManager: AppMessagesManager,
messageInput: HTMLDivElement, toggleEl: HTMLButtonElement, btnSend: HTMLButtonElement) => {
let dropdown = pageEl.querySelector('.emoji-dropdown') as HTMLDivElement;
dropdown.classList.add('active'); // need
let lazyLoadQueue = new LazyLoadQueue();
let container = pageEl.querySelector('.emoji-container .tabs-container') as HTMLDivElement;
let tabs = pageEl.querySelector('.emoji-dropdown .emoji-tabs') as HTMLUListElement;
horizontalMenu(tabs, container, (id) => {
lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP);
if(id == 1 && stickersInit) {
stickersInit();
}
}, () => {
lottieLoader.checkAnimations(false, EMOTICONSSTICKERGROUP);
lazyLoadQueue.check(); // for stickers
});
(tabs.children[0] as HTMLLIElement).click(); // set media
let emoticonsMenuOnClick = (menu: HTMLUListElement, heights: number[], scroll: Scrollable) => {
menu.addEventListener('click', function(e) {
let target = e.target as HTMLLIElement;
target = findUpTag(target, 'LI');
let index = whichChild(target);
let y = heights[index - 1/* 2 */] || 0; // 10 == padding .scrollable
console.log('emoticonsMenuOnClick', index, y, scroll.container.scrollHeight, scroll);
scroll.onAddedBottom = () => { // привет, костыль, давно не виделись!
scroll.container.scrollTop = y;
scroll.onAddedBottom = () => {};
};
scroll.container.scrollTop = y;
});
};
let emoticonsContentOnScroll = (menu: HTMLUListElement, heights: number[], prevCategoryIndex: number, scroll: HTMLDivElement) => {
let y = scroll.scrollTop;
//console.log(heights, y);
for(let i = 0; i < heights.length; ++i) {
let height = heights[i];
if(y < height) {
menu.children[prevCategoryIndex].classList.remove('active');
prevCategoryIndex = i/* + 1 */;
menu.children[prevCategoryIndex].classList.add('active');
break;
}
}
return prevCategoryIndex;
};
{
let categories = ["Smileys & Emotion", "Animals & Nature", "Food & Drink", "Travel & Places", "Activities", "Objects", "Symbols", "Flags", "Skin Tones"];
let divs: {
[category: string]: HTMLDivElement
} = {};
let keyCategory = Config.Emoji.keyCategory;
let sorted: {
[category: string]: any[]
} = {};
for(let unified in Config.Emoji.emoji) {
// @ts-ignore
let details = Config.Emoji.emoji[unified];
let category = details[keyCategory];
details.unified = unified;
if(!sorted[category]) sorted[category] = [];
sorted[category][details.sort_order] = details;
}
Object.keys(sorted).forEach(c => sorted[c].sort());
categories.pop();
delete sorted["Skin Tones"];
console.time('emojiParse');
for(let category in sorted) {
let div = document.createElement('div');
div.classList.add('emoji-category');
let emojis = sorted[category];
emojis.forEach(details => {
let emoji = details.unified;
//let emoji = (details.unified as string).split('-')
//.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
let spanEmoji = document.createElement('span');
let kek = RichTextProcessor.wrapRichText(emoji);
if(!kek.includes('emoji')) {
console.log(details, emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji));
return;
}
//console.log(kek);
spanEmoji.innerHTML = kek;
//spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
//spanEmoji.setAttribute('emoji', emoji);
div.appendChild(spanEmoji);
});
divs[category] = div;
}
console.timeEnd('emojiParse');
let heights: number[] = [0];
let contentEmojiDiv = document.getElementById('content-emoji') as HTMLDivElement;
categories.forEach(category => {
let div = divs[category];
if(!div) {
console.error('no div by category:', category);
}
contentEmojiDiv.append(div);
heights.push((heights[heights.length - 1] || 0) + div.scrollHeight);
//console.log(div, div.scrollHeight);
});
contentEmojiDiv.addEventListener('click', function(e) {
let target = e.target as any;
//if(target.tagName != 'SPAN') return;
if(target.tagName == 'SPAN' && !target.classList.contains('emoji')) {
target = target.firstElementChild;
} else if(target.tagName == 'DIV') return;
//console.log('contentEmoji div', target);
/* if(!target.classList.contains('emoji')) {
target = target.parentElement as HTMLSpanElement;
if(!target.classList.contains('emoji')) {
return;
}
} */
//messageInput.innerHTML += target.innerHTML;
messageInput.innerHTML += target.outerHTML;
btnSend.classList.add('tgico-send');
btnSend.classList.remove('tgico-microphone2');
});
let prevCategoryIndex = 1;
let menu = contentEmojiDiv.nextElementSibling as HTMLUListElement;
let emojiScroll = new Scrollable(contentEmojiDiv);
emojiScroll.container.addEventListener('scroll', (e) => {
prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, emojiScroll.container);
});
//emojiScroll.setVirtualContainer(emojiScroll.container);
emoticonsMenuOnClick(menu, heights, emojiScroll);
}
let stickersInit = () => {
let contentStickersDiv = document.getElementById('content-stickers') as HTMLDivElement;
//let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement;
let menuWrapper = contentStickersDiv.nextElementSibling as HTMLDivElement;
let menu = menuWrapper.firstElementChild as HTMLUListElement;
let menuScroll = new Scrollable(menuWrapper, true, false);
let stickersDiv = document.createElement('div');
stickersDiv.classList.add('stickers-categories');
contentStickersDiv.append(stickersDiv);
stickersDiv.addEventListener('mouseover', (e) => {
let target = e.target as HTMLElement;
if(target.tagName == 'CANVAS') { // turn on sticker
let animation = lottieLoader.getAnimation(target.parentElement, EMOTICONSSTICKERGROUP);
if(animation) {
// @ts-ignore
if(animation.currentFrame == animation.totalFrames - 1) {
animation.goToAndPlay(0, true);
} else {
animation.play();
}
}
}
});
stickersDiv.addEventListener('click', (e) => {
let target = e.target as HTMLDivElement;
target = findUpTag(target, 'DIV');
let fileID = target.getAttribute('file-id');
let document = appStickersManager.getSticker(fileID);
if(document) {
appMessagesManager.sendFile(appImManager.peerID, document, {isMedia: true});
appImManager.scroll.scrollTop = appImManager.scroll.scrollHeight;
dropdown.classList.remove('active');
toggleEl.classList.remove('active');
} else {
console.warn('got no sticker by id:', fileID);
}
});
let heights: number[] = [];
let categoryPush = (categoryDiv: HTMLDivElement, docs: MTDocument[], prepend?: boolean) => {
//if((docs.length % 5) != 0) categoryDiv.classList.add('not-full');
docs.forEach(doc => {
let div = document.createElement('div');
wrapSticker(doc, div, undefined, lazyLoadQueue, EMOTICONSSTICKERGROUP, true);
categoryDiv.append(div);
});
/* if(prepend) {
stickersDiv.prepend(categoryDiv);
} else {
stickersDiv.append(categoryDiv);
} */
setTimeout(() => lazyLoadQueue.check(), 0);
/* let scrollHeight = categoryDiv.scrollHeight;
let prevHeight = heights[heights.length - 1] || 0;
//console.log('scrollHeight', scrollHeight, categoryDiv, stickersDiv.childElementCount);
if(prepend && heights.length) {// all stickers loaded faster than recent
heights.forEach((h, i) => heights[i] += scrollHeight);
return heights.unshift(scrollHeight) - 1;
} */
heights.length = 0;
Array.from(stickersDiv.children).forEach((div, i) => {
heights[i] = (heights[i - 1] || 0) + div.scrollHeight;
});
//stickersScroll.onScroll();
//return heights.push(prevHeight + scrollHeight) - 1;
};
MTProto.apiManager.invokeApi('messages.getRecentStickers', {flags: 0, hash: 0}).then((res) => {
let stickers: {
_: string,
hash: number,
packs: any[],
stickers: MTDocument[],
dates: number[]
} = res as any;
let categoryDiv = document.createElement('div');
categoryDiv.classList.add('sticker-category');
stickersDiv.prepend(categoryDiv);
categoryPush(categoryDiv, stickers.stickers, true);
});
MTProto.apiManager.invokeApi('messages.getAllStickers', {hash: 0}).then((res) => {
let stickers: {
_: 'messages.allStickers',
hash: number,
sets: Array<MTStickerSet>
} = res as any;
stickers.sets/* .slice(0, 10) */.forEach(async(set) => {
let categoryDiv = document.createElement('div');
categoryDiv.classList.add('sticker-category');
let li = document.createElement('li');
li.classList.add('btn-icon');
menu.append(li);
stickersDiv.append(categoryDiv);
let stickerSet = await appStickersManager.getStickerSet(set);
if(stickerSet.set.thumb) {
let thumb = stickerSet.set.thumb;
appStickersManager.getStickerSetThumb(stickerSet.set).then(async(blob) => {
if(thumb.w == 1 && thumb.h == 1) {
const reader = new FileReader();
reader.addEventListener('loadend', async(e) => {
// @ts-ignore
const text = e.srcElement.result;
let json = await CryptoWorker.gzipUncompress<string>(text, true);
let animation = await lottieLoader.loadAnimation({
container: li,
loop: true,
autoplay: false,
animationData: JSON.parse(json)
}, EMOTICONSSTICKERGROUP);
});
reader.readAsArrayBuffer(blob);
} else {
let image = new Image();
image.src = URL.createObjectURL(blob);
li.append(image);
}
});
} else { // as thumb will be used first sticker
wrapSticker(stickerSet.documents[0], li as any, undefined, undefined, EMOTICONSSTICKERGROUP); // kostil
}
categoryPush(categoryDiv, stickerSet.documents);
});
});
let prevCategoryIndex = 0;
let stickersScroll = new Scrollable(contentStickersDiv);
stickersScroll.container.addEventListener('scroll', (e) => {
lazyLoadQueue.check();
lottieLoader.checkAnimations();
prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, stickersScroll.container);
});
stickersScroll.setVirtualContainer(stickersDiv);
emoticonsMenuOnClick(menu, heights, stickersScroll);
stickersInit = null;
};
return {dropdown, lazyLoadQueue};
};
export default () => import('../lib/services').then(services => {
console.log('included services', services);
@ -476,142 +121,7 @@ export default () => import('../lib/services').then(services => { @@ -476,142 +121,7 @@ export default () => import('../lib/services').then(services => {
});
return;
*/
let messageInput = document.getElementById('input-message') as HTMLDivElement/* HTMLInputElement */;
messageInput.addEventListener('keydown', function(this: typeof messageInput, e: KeyboardEvent) {
if(e.key == 'Enter') {
if(e.shiftKey) {
return;
}
sendMessage();
}
});
let lastTimeType = 0;
messageInput.addEventListener('input', function(this: typeof messageInput, e) {
//console.log('messageInput input', this.innerText, serializeNodes(Array.from(messageInput.childNodes)));
if(!this.innerText.trim() && !serializeNodes(Array.from(messageInput.childNodes)).trim()) {
this.innerHTML = '';
btnSend.classList.remove('tgico-send');
btnSend.classList.add('tgico-microphone2');
appImManager.setTyping('sendMessageCancelAction');
} else if(!btnSend.classList.contains('tgico-send')) {
btnSend.classList.add('tgico-send');
btnSend.classList.remove('tgico-microphone2');
let time = Date.now();
if(time - lastTimeType >= 6000) {
lastTimeType = time;
appImManager.setTyping('sendMessageTypingAction');
}
}
});
let 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('emoji');
return str;
}, '');
};
messageInput.addEventListener('copy', function(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 = serializeNodes(selectedNodes);
} else {
str = selection.toString();
}
console.log('messageInput copy', str, ancestorContainer.childNodes, range);
// @ts-ignore
event.clipboardData.setData('text/plain', str);
event.preventDefault();
});
messageInput.addEventListener('paste', function(this: typeof messageInput, e) {
e.preventDefault();
// @ts-ignore
let text = (e.originalEvent || e).clipboardData.getData('text/plain');
// console.log('messageInput paste', text);
text = RichTextProcessor.wrapRichText(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 fileInput = document.getElementById('input-file') as HTMLInputElement;
fileInput.addEventListener('change', (e) => {
var file = (e.target as HTMLInputElement & EventTarget).files[0];
if(!file) {
return;
}
console.log('selected file:', file, typeof(file));
fileInput.value = '';
appMessagesManager.sendFile(appImManager.peerID, file, {isMedia: true});
appImManager.scroll.scrollTop = appImManager.scroll.scrollHeight;
/* MTProto.apiFileManager.uploadFile(file).then((inputFile) => {
console.log('uploaded smthn', inputFile);
}); */
}, false);
pageEl.querySelector('#attach-file').addEventListener('click', () => {
fileInput.click();
});
let inputMessageContainer = document.getElementsByClassName('input-message-container')[0] as HTMLDivElement;
let inputScroll = new Scrollable(inputMessageContainer);
let sendMessage = () => {
let str = serializeNodes(Array.from(messageInput.childNodes));
//console.log('childnode str after:', str);
appMessagesManager.sendText(appImManager.peerID, str, {
replyToMsgID: appImManager.replyToMsgID == 0 ? undefined : appImManager.replyToMsgID
});
appImManager.replyToMsgID = 0;
appImManager.replyElements.container.classList.remove('active');
appImManager.scroll.scrollTop = appImManager.scroll.scrollHeight;
messageInput.innerText = '';
btnSend.classList.remove('tgico-send');
btnSend.classList.add('tgico-microphone2');
};
let btnSend = document.getElementById('btn-send') as HTMLButtonElement;
btnSend.addEventListener('click', () => {
if(btnSend.classList.contains('tgico-send')) {
sendMessage();
}
});
/* function placeCaretAfterNode(node: HTMLElement) {
if (typeof window.getSelection != "undefined") {
@ -668,43 +178,6 @@ export default () => import('../lib/services').then(services => { @@ -668,43 +178,6 @@ export default () => import('../lib/services').then(services => {
};
});
let emoticonsDropdown: HTMLDivElement = null;
let emoticonsTimeout: number = 0;
let toggleEmoticons = pageEl.querySelector('.toggle-emoticons') as HTMLButtonElement;
let emoticonsLazyLoadQueue: LazyLoadQueue = null;
toggleEmoticons.onmouseover = (e) => {
clearTimeout(emoticonsTimeout);
emoticonsTimeout = setTimeout(() => {
if(!emoticonsDropdown) {
let res = initEmoticonsDropdown(pageEl, appImManager,
appMessagesManager, messageInput, toggleEmoticons, btnSend);
emoticonsDropdown = res.dropdown;
emoticonsLazyLoadQueue = res.lazyLoadQueue;
toggleEmoticons.onmouseout = emoticonsDropdown.onmouseout = (e) => {
clearTimeout(emoticonsTimeout);
emoticonsTimeout = setTimeout(() => {
emoticonsDropdown.classList.remove('active');
toggleEmoticons.classList.remove('active');
lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP);
}, 200);
};
emoticonsDropdown.onmouseover = (e) => {
clearTimeout(emoticonsTimeout);
};
} else {
emoticonsDropdown.classList.add('active');
emoticonsLazyLoadQueue.check();
}
toggleEmoticons.classList.add('active');
lottieLoader.checkAnimations(false, EMOTICONSSTICKERGROUP);
}, 0/* 200 */);
};
/* toggleEmoticons.onclick = (e) => {
if(!emoticonsDropdown) {
emoticonsDropdown = initEmoticonsDropdown(pageEl, appImManager,
@ -729,8 +202,6 @@ export default () => import('../lib/services').then(services => { @@ -729,8 +202,6 @@ export default () => import('../lib/services').then(services => {
el.classList.remove('menu-open');
openedMenu.classList.remove('active');
} else {
el.classList.add('menu-open');
openBtnMenu(openedMenu);
}
});

319
src/lib/appManagers/appImManager.ts

@ -4,11 +4,12 @@ import appUsersManager from "./appUsersManager"; @@ -4,11 +4,12 @@ import appUsersManager from "./appUsersManager";
import appMessagesManager from "./appMessagesManager";
import appPeersManager from "./appPeersManager";
import appProfileManager from "./appProfileManager";
import { ProgressivePreloader, wrapDocument, wrapSticker, wrapVideo, wrapPhoto, openBtnMenu } from "../../components/misc";
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";
@ -17,6 +18,7 @@ import appSidebarLeft from "./appSidebarLeft"; @@ -17,6 +18,7 @@ import appSidebarLeft from "./appSidebarLeft";
import appChatsManager from "./appChatsManager";
import appMessagesIDsManager from "./appMessagesIDsManager";
import apiUpdatesManager from './apiUpdatesManager';
import initEmoticonsDropdown, { EMOTICONSSTICKERGROUP } from '../../components/emoticonsDropdown';
console.log('appImManager included!');
@ -86,6 +88,221 @@ class ScrollPosition { @@ -86,6 +88,221 @@ 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;
constructor() {
this.toggleEmoticons = this.pageEl.querySelector('.toggle-emoticons') as HTMLButtonElement;
this.messageInput.addEventListener('keydown', (e: KeyboardEvent) => {
if(e.key == 'Enter') {
if(e.shiftKey) {
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;
apiManager.invokeApi('messages.getWebPage', {
url: url,
hash: 0
}).then((webpage: any) => {
if(this.lastUrl != url) return;
console.log(webpage);
appImManager.replyElements.titleEl.innerText = webpage.site_name || webpage.title || '';
appImManager.replyElements.subtitleEl.innerText = webpage.description || webpage.url || '';
appImManager.replyElements.container.classList.add('active');
appImManager.replyToMsgID = 0;
appImManager.noWebPage = false;
});
}
}
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');
}
}
});
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);
// @ts-ignore
event.clipboardData.setData('text/plain', str);
event.preventDefault();
});
this.messageInput.addEventListener('paste', (e) => {
e.preventDefault();
// @ts-ignore
let text = (e.originalEvent || e).clipboardData.getData('text/plain');
// console.log('messageInput paste', text);
let entities = RichTextProcessor.parseEntities(text);
text = RichTextProcessor.wrapRichText(text, {
entities: entities.filter(e => e._ == 'messageEntityEmoji')
});
// 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);
});
this.fileInput.addEventListener('change', (e) => {
var file = (e.target as HTMLInputElement & EventTarget).files[0];
if(!file) {
return;
}
console.log('selected file:', file, typeof(file));
this.fileInput.value = '';
appMessagesManager.sendFile(appImManager.peerID, file, {isMedia: true});
appImManager.scroll.scrollTop = appImManager.scroll.scrollHeight;
/* MTProto.apiFileManager.uploadFile(file).then((inputFile) => {
console.log('uploaded smthn', inputFile);
}); */
}, false);
this.pageEl.querySelector('#attach-file').addEventListener('click', () => {
this.fileInput.click();
});
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 */);
};
}
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('emoji');
return str;
}, '');
};
public sendMessage() {
let str = this.serializeNodes(Array.from(this.messageInput.childNodes));
//console.log('childnode str after:', str);
this.lastUrl = '';
appMessagesManager.sendText(appImManager.peerID, str, {
replyToMsgID: appImManager.replyToMsgID == 0 ? undefined : appImManager.replyToMsgID,
noWebPage: appImManager.noWebPage
});
appImManager.replyToMsgID = 0;
appImManager.noWebPage = false;
appImManager.replyElements.container.classList.remove('active');
appImManager.scroll.scrollTop = appImManager.scroll.scrollHeight;
this.messageInput.innerText = '';
this.btnSend.classList.remove('tgico-send');
this.btnSend.classList.add('tgico-microphone2');
};
}
export class AppImManager {
public pageEl = document.querySelector('.page-chats') as HTMLDivElement;
public btnMute = this.pageEl.querySelector('.tool-mute') as HTMLButtonElement;
@ -100,6 +317,8 @@ export class AppImManager { @@ -100,6 +317,8 @@ export class AppImManager {
private getHistoryPromise: Promise<boolean>;
private getHistoryTimeout = 0;
private chatInputC: ChatInput = null;
public myID = 0;
public peerID = 0;
public muted = false;
@ -155,10 +374,13 @@ export class AppImManager { @@ -155,10 +374,13 @@ export class AppImManager {
} = {};
public replyToMsgID = 0;
public noWebPage = false;
constructor() {
this.log = logger('IM');
this.chatInputC = new ChatInput();
this.preloader = new ProgressivePreloader(null, false);
this.popupDeleteMessage.popupEl = this.pageEl.querySelector('.popup-delete-message') as HTMLDivElement;
@ -265,6 +487,15 @@ export class AppImManager { @@ -265,6 +487,15 @@ export class AppImManager {
}
appMediaViewer.openMedia(message, true);
} else if(target.tagName == 'DIV') {
let bubble = findUpClassName(e.target, 'bubble');
if(bubble) {
if(bubble.classList.contains('is-reply')/* || bubble.classList.contains('forwarded') */) {
let originalMessageID = +bubble.getAttribute('data-original-mid');
this.setPeer(this.peerID, originalMessageID);
}
}
}
//console.log('chatInner click', e);
@ -288,7 +519,12 @@ export class AppImManager { @@ -288,7 +519,12 @@ export class AppImManager {
this.btnMute.addEventListener('click', () => this.mutePeer());
this.chatInner.addEventListener('contextmenu', e => {
let bubble = findUpClassName(e.target, 'bubble');
let bubble: HTMLDivElement = null;
try {
bubble = findUpClassName(e.target, 'bubble');
} catch(e) {}
if(bubble) {
e.preventDefault();
e.cancelBubble = true;
@ -394,6 +630,7 @@ export class AppImManager { @@ -394,6 +630,7 @@ export class AppImManager {
this.replyElements.cancelBtn.addEventListener('click', () => {
this.replyElements.container.classList.remove('active');
this.replyToMsgID = 0;
this.noWebPage = true;
});
this.popupDeleteMessage.deleteBothBtn.addEventListener('click', () => {
@ -684,14 +921,12 @@ export class AppImManager { @@ -684,14 +921,12 @@ export class AppImManager {
lottieLoader.checkAnimations(false, 'chat', true);
console.time('chatInner clear');
// clear input
this.chatInputC.messageInput.innerHTML = '';
this.replyElements.cancelBtn.click();
// clear messages
this.chatInner.innerHTML = '';
/* Array.from(this.chatInner.children).forEach(c => {
this.chatInner.removeChild(c);
}); */
console.timeEnd('chatInner clear');
//appSidebarRight.minMediaID = {};
}
@ -706,13 +941,18 @@ export class AppImManager { @@ -706,13 +941,18 @@ export class AppImManager {
let samePeer = this.peerID == peerID;
if(samePeer && !testScroll && !lastMsgID) {
return Promise.resolve(true); // uncomment
}
if(samePeer) {
if(!testScroll && !lastMsgID) {
return Promise.resolve(true);
}
if(samePeer && lastMsgID == this.lastDialog.top_message) {
if(this.bubbles[lastMsgID]) {
this.scroll.scrollTop = this.scroll.scrollHeight;
if(lastMsgID == this.lastDialog.top_message) {
this.scroll.scrollTop = this.scroll.scrollHeight;
} else {
this.bubbles[lastMsgID].scrollIntoView();
}
return Promise.resolve(true);
}
}
@ -1135,17 +1375,19 @@ export class AppImManager { @@ -1135,17 +1375,19 @@ export class AppImManager {
}
}
if(message.fwd_from) {
let fwd = message.fwd_from;
//let peerFrom = appPeersManager.getPeerTitle()
/* let fromTitle = */appPeersManager.getPeerTitle(fwd.from_id);
}
if((this.peerID < 0 && !our) || message.fwd_from || message.reply_to_mid) { // chat
let title = appPeersManager.getPeerTitle(message.fwdFromID || message.fromID);
let isHidden = message.fwd_from && !message.fwd_from.from_id;
if(isHidden) {
this.log('message render hidden', message);
title = message.fwd_from.from_name;
bubble.classList.add('hidden-profile');
}
//this.log(title);
if(message.fwdFromID) {
if(message.fwdFromID || message.fwd_from) {
bubble.classList.add('forwarded');
if(!bubble.classList.contains('sticker')) {
@ -1171,16 +1413,39 @@ export class AppImManager { @@ -1171,16 +1413,39 @@ export class AppImManager {
let originalMessage = appMessagesManager.getMessage(message.reply_to_mid);
let originalPeerTitle = appPeersManager.getPeerTitle(originalMessage.fromID) || '';
this.log('message to render one more time punks not dead', originalMessage, originalPeerTitle, bubble);
let originalText = '';
if(originalMessage.message) {
originalText = RichTextProcessor.wrapRichText(originalMessage.message, {
entities: originalMessage.totalEntities
});
}
if(originalMessage.media) {
switch(originalMessage.media._) {
case 'messageMediaPhoto':
if(!originalText) originalText = 'Photo';
break;
default:
if(!originalText) originalText = originalMessage.media._;
break;
}
}
nameEl.innerText = originalPeerTitle;
textDiv.innerHTML = RichTextProcessor.wrapRichText(originalMessage.message, {
entities: originalMessage.totalEntities
});
textDiv.innerHTML = originalText;
quote.append(nameEl, textDiv);
box.append(quote);
if(originalMessage.mid) {
bubble.setAttribute('data-original-mid', originalMessage.mid);
}
bubble.append(box);
//bubble.classList.add('reply');
bubble.classList.add('is-reply');
}
/* if(message.media) {
@ -1212,7 +1477,8 @@ export class AppImManager { @@ -1212,7 +1477,8 @@ export class AppImManager {
//}
}
if(!our && this.peerID < 0) {
if(!our && this.peerID < 0 &&
(!appPeersManager.isChannel(this.peerID) || appPeersManager.isMegagroup(this.peerID))) {
let avatarDiv = document.createElement('div');
avatarDiv.classList.add('user-avatar');
@ -1556,4 +1822,5 @@ export class AppImManager { @@ -1556,4 +1822,5 @@ export class AppImManager {
}
}
export default new AppImManager();
const appImManager = new AppImManager();
export default appImManager;

6
src/lib/appManagers/appMessagesManager.ts

@ -186,6 +186,7 @@ export class AppMessagesManager { @@ -186,6 +186,7 @@ export class AppMessagesManager {
var isMegagroup = isChannel && AppPeersManager.isMegagroup(peerID);
var asChannel = isChannel && !isMegagroup ? true : false;
var message: any;
let noWebPage = options.noWebPage || false;
if(historyStorage === undefined) {
historyStorage = this.historiesStorage[peerID] = {count: null, history: [], pending: []};
@ -267,6 +268,10 @@ export class AppMessagesManager { @@ -267,6 +268,10 @@ export class AppMessagesManager {
flags |= 128;
}
if(noWebPage) {
flags |= 2;
}
var apiPromise: any;
if(options.viaBotID) {
apiPromise = MTProto.apiManager.invokeApi('messages.sendInlineBotResult', {
@ -284,6 +289,7 @@ export class AppMessagesManager { @@ -284,6 +289,7 @@ export class AppMessagesManager {
apiPromise = MTProto.apiManager.invokeApi('messages.sendMessage', {
flags: flags,
no_webpage: noWebPage,
peer: AppPeersManager.getInputPeerByID(peerID),
message: text,
random_id: randomID,

9
src/lib/appManagers/appSidebarLeft.ts

@ -57,8 +57,11 @@ class AppSidebarLeft { @@ -57,8 +57,11 @@ class AppSidebarLeft {
this.listsContainer = new Scrollable(this.searchContainer).container;
this.searchMessagesList = document.createElement('ul');
this.savedBtn.addEventListener('click', () => {
appImManager.setPeer(appImManager.myID);
this.savedBtn.addEventListener('click', (e) => {
this.log('savedbtn click');
setTimeout(() => { // menu doesn't close if no timeout (lol)
appImManager.setPeer(appImManager.myID);
}, 0);
});
/* this.listsContainer.insertBefore(this.searchMessagesList, this.listsContainer.lastElementChild);
@ -141,6 +144,8 @@ class AppSidebarLeft { @@ -141,6 +144,8 @@ class AppSidebarLeft {
e.preventDefault();
return false;
}
return true;
}, true);
window.addEventListener('resize', () => {

8
src/scss/partials/_chat.scss

@ -257,7 +257,8 @@ @@ -257,7 +257,8 @@
}
}
.message:not(.message-empty) + .attachment {
.message:not(.message-empty) + .attachment,
&.is-reply .attachment {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
@ -682,6 +683,7 @@ @@ -682,6 +683,7 @@
box-shadow: 0 1px 2px 0 rgba(16, 35, 47, 0.07);
margin-right: .5rem;
padding: 4.5px .5rem;
/* padding: 3px .5rem 6px .5rem; */
min-height: 3.25rem;
max-height: 30rem;
caret-color: $button-primary-background;
@ -716,7 +718,7 @@ @@ -716,7 +718,7 @@
height: 0px;
&.active {
height: 35px;
height: 39px;
}
.reply {
@ -732,7 +734,7 @@ @@ -732,7 +734,7 @@
align-items: flex-end;
.btn-icon:before {
vertical-align: middle;
vertical-align: bottom;
}
}

Loading…
Cancel
Save