Telegram Web K with changes to work inside I2P https://web.telegram.i2p/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2023 lines
69 KiB

import apiManager from '../mtproto/apiManager';
4 years ago
import { $rootScope, isElementInViewport, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, calcImageInBox, findUpTag, langPack } from "../utils";
4 years ago
import appUsersManager from "./appUsersManager";
import appMessagesManager from "./appMessagesManager";
import appPeersManager from "./appPeersManager";
import appProfileManager from "./appProfileManager";
import appDialogsManager from "./appDialogsManager";
import { RichTextProcessor } from "../richtextprocessor";
import appPhotosManager from "./appPhotosManager";
import appSidebarRight from './appSidebarRight';
import { logger } from "../polyfill";
import lottieLoader from "../lottieLoader";
import appMediaViewer from "./appMediaViewer";
import appSidebarLeft from "./appSidebarLeft";
import appChatsManager from "./appChatsManager";
import appMessagesIDsManager from "./appMessagesIDsManager";
import apiUpdatesManager from './apiUpdatesManager';
4 years ago
import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply } from '../../components/wrappers';
import ProgressivePreloader from '../../components/preloader';
import { openBtnMenu } from '../../components/misc';
4 years ago
import { ChatInput } from '../../components/chatInput';
import Scrollable from '../../components/scrollable';
4 years ago
console.log('appImManager included!');
let testScroll = false;
class ScrollPosition {
4 years ago
previousScrollHeightMinusTop = 0;
readyFor = 'up';
container: HTMLElement;
rAF: number;
debug = true;
constructor(node: HTMLElement) {
4 years ago
this.container = node.parentElement;
}
4 years ago
restore() {
let setScrollTop = this.container.scrollHeight - this.previousScrollHeightMinusTop;
if(this.debug) appImManager.log('scrollPosition restore', this.readyFor, this.container.scrollHeight,
setScrollTop, this.container, this.container.parentElement.classList.contains('scrolled-down'));
if(this.readyFor === 'up'/* || this.container.parentElement.classList.contains('scrolled-down') */) {
if(this.debug) appImManager.log('scrollPosition restore 2', this.readyFor, this.container.scrollHeight,
setScrollTop, this.container);
if(this.rAF) window.cancelAnimationFrame(this.rAF);
this.rAF = window.requestAnimationFrame(() => {
this.container.scrollTop = this.container.scrollHeight - this.previousScrollHeightMinusTop;
this.rAF = 0;
});
} else if(this.container.parentElement.classList.contains('scrolled-down')) {
if(this.debug) appImManager.log('scrollPosition restore 2', this.readyFor, this.container.scrollHeight,
setScrollTop, this.container);
4 years ago
4 years ago
this.container.scrollTop = setScrollTop;
}
4 years ago
4 years ago
// '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
}
4 years ago
prepareFor(direction = 'up') {
if(this.rAF) {
window.cancelAnimationFrame(this.rAF);
this.rAF = 0;
}
4 years ago
4 years ago
this.readyFor = direction;
4 years ago
4 years ago
if(direction == 'down') {
let scrollTop = this.container.scrollTop;
this.previousScrollHeightMinusTop = scrollTop > 0 ? this.container.scrollHeight - scrollTop : 0;
} else {
this.previousScrollHeightMinusTop = this.container.scrollHeight - this.container.scrollTop;
}
//let scrollTop = this.container.scrollTop;
//this.previousScrollHeightMinusTop = scrollTop > 0 || this.readyFor == 'up' ? this.container.scrollHeight - this.container.scrollTop : 0;
if(this.debug) appImManager.log.trace('scrollPosition prepareFor', direction, this.container.scrollHeight,
this.container.scrollTop, this.previousScrollHeightMinusTop);
}
}
class BubbleGroups {
bubblesByGroups: Array<{timestamp: number, fromID: number, mid: number, group: HTMLDivElement[]}> = []; // map to group
groups: Array<HTMLDivElement[]> = [];
updateRAFs: Map<HTMLDivElement[], number> = new Map();
newGroupDiff = 120;
removeBubble(bubble: HTMLDivElement, mid: number) {
let details = this.bubblesByGroups.findAndSplice(g => g.mid == mid);
if(details && details.group.length) {
details.group.findAndSplice(d => d == bubble);
if(!details.group.length) {
this.groups.findAndSplice(g => g == details.group);
}
}
}
addBubble(bubble: HTMLDivElement, message: any, reverse: boolean) {
let timestamp = message.date;
let fromID = message.fromID;
let group: HTMLDivElement[];
// try to find added
//this.removeBubble(message.mid);
if(this.bubblesByGroups.length) {
if(reverse) {
let g = this.bubblesByGroups[0];
if(g.fromID == fromID && (g.timestamp - timestamp) < this.newGroupDiff) {
group = g.group;
group.unshift(bubble);
} else {
this.groups.unshift(group = [bubble]);
}
} else {
let g = this.bubblesByGroups[this.bubblesByGroups.length - 1];
if(g.fromID == fromID && (timestamp - g.timestamp) < this.newGroupDiff) {
group = g.group;
group.push(bubble);
} else {
this.groups.push(group = [bubble]);
}
}
} else {
this.groups.push(group = [bubble]);
}
//console.log('addBubble', bubble, message.mid, fromID, reverse, group);
4 years ago
this.bubblesByGroups[reverse ? 'unshift' : 'push']({timestamp, fromID, mid: message.mid, group});
this.updateGroup(group);
}
updateGroup(group: HTMLDivElement[]) {
if(this.updateRAFs.has(group)) {
window.cancelAnimationFrame(this.updateRAFs.get(group));
this.updateRAFs.delete(group);
}
this.updateRAFs.set(group, window.requestAnimationFrame(() => {
this.updateRAFs.delete(group);
if(!group.length) {
return;
}
let first = group[0];
//console.log('updateGroup', group, first);
4 years ago
if(group.length == 1) {
first.classList.add('is-group-first', 'is-group-last');
return;
} else {
first.classList.remove('is-group-last');
first.classList.add('is-group-first');
}
let length = group.length - 1;
for(let i = 1; i < length; ++i) {
let bubble = group[i];
bubble.classList.remove('is-group-last', 'is-group-first');
}
let last = group[group.length - 1];
last.classList.remove('is-group-first');
last.classList.add('is-group-last');
}));
}
updateGroupByMessageID(mid: number) {
let details = this.bubblesByGroups.find(g => g.mid == mid);
if(details) {
this.updateGroup(details.group);
}
}
cleanup() {
this.bubblesByGroups = [];
for(let value of this.updateRAFs.values()) {
window.cancelAnimationFrame(value);
}
this.updateRAFs.clear();
4 years ago
}
}
export class AppImManager {
public pageEl = document.querySelector('.page-chats') as HTMLDivElement;
public btnMute = this.pageEl.querySelector('.tool-mute') as HTMLButtonElement;
public btnMenuMute = this.pageEl.querySelector('.menu-mute') as HTMLButtonElement;
4 years ago
public avatarEl = document.getElementById('im-avatar') as HTMLDivElement;
public titleEl = document.getElementById('im-title') as HTMLDivElement;
public subtitleEl = document.getElementById('im-subtitle') as HTMLDivElement;
4 years ago
public bubblesContainer = document.getElementById('bubbles') as HTMLDivElement;
4 years ago
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;
4 years ago
private getHistoryPromise: Promise<boolean>;
private getHistoryTimeout = 0;
4 years ago
private chatInputC: ChatInput = null;
4 years ago
4 years ago
public myID = 0;
public peerID = 0;
public muted = false;
4 years ago
4 years ago
public bubbles: {[mid: number]: HTMLDivElement} = {};
public dateMessages: {[timestamp: number]: { div: HTMLDivElement, firstTimestamp: number }} = {};
public unreaded: number[] = [];
public unreadOut: number[] = [];
public needUpdate: {replyMid: number, mid: number}[] = []; // if need wrapSingleMessage
4 years ago
public offline = false;
public updateStatusInterval = 0;
4 years ago
4 years ago
public pinnedMsgID = 0;
private pinnedMessageContainer = this.pageEl.querySelector('.pinned-message') as HTMLDivElement;
private pinnedMessageContent = this.pinnedMessageContainer.querySelector('.pinned-message-subtitle') as HTMLDivElement;
private firstTopMsgID = 0;
4 years ago
4 years ago
public loadMediaQueue: Array<() => Promise<void>> = [];
private loadMediaQueuePromise: Promise<void[]> = null;
private loadingMedia = 0;
4 years ago
public scroll: HTMLDivElement = null;
4 years ago
public scrollable: Scrollable = null;
4 years ago
public scrollPosition: ScrollPosition = null;
4 years ago
public log: ReturnType<typeof logger>;
4 years ago
4 years ago
private preloader: ProgressivePreloader = null;
4 years ago
4 years ago
private typingTimeouts: {[peerID: number]: number} = {};
private typingUsers: {[userID: number]: number} = {} // to peerID
4 years ago
private topbar: HTMLDivElement = null;
private chatInput: HTMLDivElement = null;
private scrolledAll: boolean;
private scrolledAllDown: boolean;
4 years ago
public contextMenu = document.getElementById('bubble-contextmenu') as HTMLDivElement;
private contextMenuPin = this.contextMenu.querySelector('.menu-pin') as HTMLDivElement;
private contextMenuEdit = this.contextMenu.querySelector('.menu-edit') as HTMLDivElement;
private contextMenuMsgID: number;
4 years ago
private popupDeleteMessage: {
popupEl?: HTMLDivElement,
deleteBothBtn?: HTMLButtonElement,
deleteMeBtn?: HTMLButtonElement,
cancelBtn?: HTMLButtonElement
} = {};
4 years ago
private setPeerPromise: Promise<boolean> = null;
4 years ago
public bubbleGroups = new BubbleGroups();
4 years ago
constructor() {
this.log = logger('IM');
4 years ago
this.chatInputC = new ChatInput();
4 years ago
4 years ago
this.preloader = new ProgressivePreloader(null, false);
4 years ago
this.popupDeleteMessage.popupEl = this.pageEl.querySelector('.popup-delete-message') as HTMLDivElement;
this.popupDeleteMessage.deleteBothBtn = this.popupDeleteMessage.popupEl.querySelector('.popup-delete-both') as HTMLButtonElement;
this.popupDeleteMessage.deleteMeBtn = this.popupDeleteMessage.popupEl.querySelector('.popup-delete-me') as HTMLButtonElement;
this.popupDeleteMessage.cancelBtn = this.popupDeleteMessage.popupEl.querySelector('.popup-close') as HTMLButtonElement;
4 years ago
apiManager.getUserID().then((id) => {
4 years ago
this.myID = id;
});
4 years ago
this.topbar = document.getElementById('topbar') as HTMLDivElement;
this.chatInput = document.getElementById('chat-input') as HTMLDivElement;
4 years ago
4 years ago
$rootScope.$on('user_auth', (e: CustomEvent) => {
let userAuth = e.detail;
this.myID = userAuth ? userAuth.id : 0;
});
4 years ago
// will call when message is sent (only 1)
4 years ago
$rootScope.$on('history_append', (e: CustomEvent) => {
let details = e.detail;
4 years ago
4 years ago
this.renderMessagesByIDs([details.messageID]);
});
4 years ago
// will call when sent for update pos
$rootScope.$on('history_update', (e: CustomEvent) => {
let details = e.detail;
4 years ago
if(details.mid && details.peerID == this.peerID) {
let mid = details.mid;
4 years ago
let bubble = this.bubbles[mid];
if(!bubble) return;
4 years ago
let message = appMessagesManager.getMessage(mid);
//this.log('history_update', this.bubbles[mid], mid, message);
4 years ago
this.renderMessage(message, false, false, bubble);
this.deleteEmptySideDivs();
}
});
4 years ago
4 years ago
$rootScope.$on('history_multiappend', (e: CustomEvent) => {
let msgIDsByPeer = e.detail;
if(!(this.peerID in msgIDsByPeer)) return;
4 years ago
4 years ago
let msgIDs = msgIDsByPeer[this.peerID];
4 years ago
4 years ago
this.renderMessagesByIDs(msgIDs);
4 years ago
//appDialogsManager.sortDom();
});
4 years ago
$rootScope.$on('history_delete', (e: CustomEvent) => {
let detail: {
peerID: string,
msgs: {[x: number]: boolean}
} = e.detail;
4 years ago
this.deleteMessagesByIDs(Object.keys(detail.msgs).map(s => +s));
4 years ago
4 years ago
setTimeout(() => {
this.deleteEmptySideDivs();
}, 0);
4 years ago
});
4 years ago
// Calls when message successfully sent and we have an ID
4 years ago
$rootScope.$on('message_sent', (e: CustomEvent) => {
let {tempID, mid} = e.detail;
4 years ago
4 years ago
////this.log('message_sent', e.detail);
4 years ago
4 years ago
let bubble = this.bubbles[tempID];
if(bubble) {
this.bubbles[mid] = bubble;
4 years ago
4 years ago
/////this.log('message_sent', bubble);
4 years ago
let media = bubble.querySelector('img, video');
if(media) {
media.setAttribute('message-id', mid);
}
bubble.classList.remove('is-sending');
bubble.classList.add('is-sent');
4 years ago
this.bubbleGroups.removeBubble(bubble, tempID);
4 years ago
delete this.bubbles[tempID];
} else {
this.log.warn('message_sent there is no bubble', e.detail);
4 years ago
}
4 years ago
4 years ago
let length = this.unreadOut.length;
for(let i = 0; i < length; i++) {
if(this.unreadOut[i] == tempID) {
this.unreadOut[i] = mid;
}
}
});
4 years ago
$rootScope.$on('message_edit', (e: CustomEvent) => {
let {peerID, mid, id, justMedia} = e.detail;
4 years ago
if(peerID != this.peerID) return;
4 years ago
let bubble = this.bubbles[mid];
if(!bubble) return;
4 years ago
let message = appMessagesManager.getMessage(mid);
this.renderMessage(message, false, false, bubble, false);
});
4 years ago
4 years ago
$rootScope.$on('messages_downloaded', (e: CustomEvent) => {
let mids: number[] = e.detail;
4 years ago
mids.forEach(mid => {
if(this.pinnedMsgID == mid) {
let message = appMessagesManager.getMessage(mid);
4 years ago
/////this.log('setting pinned message', message);
this.pinnedMessageContainer.dataset.mid = '' + mid;
this.pinnedMessageContainer.style.display = '';
this.pinnedMessageContent.innerHTML = RichTextProcessor.wrapEmojiText(message.message);
}
4 years ago
4 years ago
this.needUpdate.forEachReverse((obj, idx) => {
if(obj.replyMid == mid) {
let {mid, replyMid} = this.needUpdate.splice(idx, 1)[0];
4 years ago
//this.log('messages_downloaded', mid, replyMid, i, this.needUpdate, this.needUpdate.length, mids, this.bubbles[mid]);
let bubble = this.bubbles[mid];
if(!bubble) return;
4 years ago
let message = appMessagesManager.getMessage(mid);
4 years ago
let repliedMessage = appMessagesManager.getMessage(replyMid);
if(repliedMessage.deleted) { // чтобы не пыталось бесконечно загрузить удалённое сообщение
delete message.reply_to_mid; // WARNING!
}
4 years ago
this.renderMessage(message, false, false, bubble, false);
}
4 years ago
});
});
4 years ago
});
4 years ago
4 years ago
$rootScope.$on('apiUpdate', (e: CustomEvent) => {
let update = e.detail;
4 years ago
this.handleUpdate(update);
});
4 years ago
4 years ago
window.addEventListener('blur', () => {
lottieLoader.checkAnimations(true);
4 years ago
4 years ago
this.offline = true;
this.updateStatus();
clearInterval(this.updateStatusInterval);
window.addEventListener('focus', () => {
lottieLoader.checkAnimations(false);
4 years ago
4 years ago
this.offline = false;
this.updateStatus();
this.updateStatusInterval = window.setInterval(() => this.updateStatus(), 50e3);
}, {once: true});
});
4 years ago
4 years ago
(this.pageEl.querySelector('.person') as HTMLDivElement).addEventListener('click', (e) => {
appSidebarRight.toggleSidebar(true);
});
4 years ago
4 years ago
this.chatInner.addEventListener('click', (e) => {
let target = e.target as HTMLElement;
let bubble: HTMLDivElement = null;
try {
bubble = findUpClassName(e.target, 'bubble');
} catch(err) {}
4 years ago
if(!bubble) return;
4 years ago
if(['IMG', 'VIDEO', 'SVG', 'DIV'].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV');
4 years ago
if(target.tagName == 'DIV') {
if(target.classList.contains('forward')) {
let savedFrom = bubble.dataset.savedFrom;
let splitted = savedFrom.split('_');
let peerID = +splitted[0];
let msgID = +splitted[1];
4 years ago
////this.log('savedFrom', peerID, msgID);
this.setPeer(peerID, msgID, true);
return;
} else if(target.classList.contains('user-avatar') || target.classList.contains('name')) {
let peerID = +target.dataset.peerID;
if(!isNaN(peerID)) {
this.setPeer(peerID);
}
4 years ago
return;
}
4 years ago
let isReplyClick = false;
4 years ago
try {
4 years ago
isReplyClick = !!findUpClassName(e.target, 'reply');
} catch(err) {}
4 years ago
if(isReplyClick && bubble.classList.contains('is-reply')/* || bubble.classList.contains('forwarded') */) {
let originalMessageID = +bubble.getAttribute('data-original-mid');
this.setPeer(this.peerID, originalMessageID);
}
} else if(bubble.classList.contains('round')) {
} else if(target.tagName == 'IMG' && target.parentElement.classList.contains('user-avatar')) {
let peerID = +target.parentElement.dataset.peerID;
4 years ago
if(!isNaN(peerID)) {
this.setPeer(peerID);
}
} else if((target.tagName == 'IMG' && !target.classList.contains('emoji')) || target.tagName == 'VIDEO') {
let messageID = +target.getAttribute('message-id');
let message = appMessagesManager.getMessage(messageID);
4 years ago
if(!message) {
this.log.warn('no message by messageID:', messageID);
return;
}
4 years ago
let ids = Object.keys(this.bubbles).map(k => +k).filter(id => {
let message = appMessagesManager.getMessage(id);
4 years ago
return message.media && (message.media.photo || (message.media.document && (message.media.document.type == 'video' || message.media.document.type == 'gif')) || (message.media.webpage && (message.media.webpage.document || message.media.webpage.photo)));
}).sort();
let idx = ids.findIndex(i => i == messageID);
4 years ago
let prev = ids[idx + 1] || null;
let next = ids[idx - 1] || null;
4 years ago
let prevTarget = this.bubbles[prev] ? this.bubbles[prev].querySelector('img, video') as HTMLElement : null;
let nextTarget = this.bubbles[next] ? this.bubbles[next].querySelector('img, video') as HTMLElement : null;
4 years ago
4 years ago
/////this.log('ids', ids, idx, this.bubbles[prev], this.bubbles[next]);
4 years ago
appMediaViewer.openMedia(message, target, nextTarget, prevTarget);
4 years ago
//appMediaViewer.openMedia(message, target as HTMLImageElement);
4 years ago
}
4 years ago
4 years ago
//console.log('chatInner click', e);
});
4 years ago
4 years ago
this.searchBtn.addEventListener('click', (e) => {
if(this.peerID) {
appSidebarLeft.beginSearch(this.peerID);
}
});
4 years ago
4 years ago
this.pinnedMessageContainer.addEventListener('click', (e) => {
e.preventDefault();
e.cancelBubble = true;
4 years ago
4 years ago
let mid = +this.pinnedMessageContainer.getAttribute('data-mid');
this.setPeer(this.peerID, mid);
});
4 years ago
this.btnMenuMute.addEventListener('click', () => this.mutePeer());
this.btnMute.addEventListener('click', () => this.mutePeer());
4 years ago
let onKeyDown = (e: KeyboardEvent) => {
let target = e.target as HTMLElement;
4 years ago
//if(target.tagName == 'INPUT') return;
4 years ago
//this.log('onkeydown', e);
4 years ago
if(this.chatInputC.attachMediaPopUp.container.classList.contains('active')) {
if(target.tagName != 'INPUT') {
this.chatInputC.attachMediaPopUp.captionInput.focus();
}
4 years ago
if(e.key == 'Enter') {
this.chatInputC.attachMediaPopUp.sendBtn.click();
} else if(e.key == 'Escape') {
this.chatInputC.attachMediaPopUp.container.classList.remove('active');
}
4 years ago
return;
}
4 years ago
4 years ago
if(e.key == 'Meta' || e.key == 'Control') {
return;
} else if(e.key == 'c' && (e.ctrlKey || e.metaKey) && target.tagName != 'INPUT') {
return;
}
4 years ago
if(e.target != this.chatInputC.messageInput && target.tagName != 'INPUT') {
this.chatInputC.messageInput.focus();
placeCaretAtEnd(this.chatInputC.messageInput);
}
};
4 years ago
document.body.addEventListener('keydown', onKeyDown);
4 years ago
this.chatInner.addEventListener('contextmenu', e => {
let bubble: HTMLDivElement = null;
4 years ago
try {
bubble = findUpClassName(e.target, 'bubble');
} catch(e) {}
4 years ago
if(bubble) {
e.preventDefault();
e.cancelBubble = true;
let msgID = 0;
for(let id in this.bubbles) {
if(this.bubbles[id] === bubble) {
msgID = +id;
break;
}
}
4 years ago
if(!msgID) return;
4 years ago
if(this.myID == this.peerID || (this.peerID < 0 && !appPeersManager.isChannel(this.peerID) && !appPeersManager.isMegagroup(this.peerID))) {
this.contextMenuPin.style.display = '';
} else this.contextMenuPin.style.display = 'none';
4 years ago
this.contextMenuMsgID = msgID;
4 years ago
let side = bubble.parentElement.classList.contains('in') ? 'left' : 'right';
4 years ago
this.contextMenuEdit.style.display = side == 'right' ? '' : 'none';
4 years ago
this.contextMenu.classList.remove('bottom-left', 'bottom-right');
this.contextMenu.classList.add(side == 'left' ? 'bottom-right' : 'bottom-left');
4 years ago
let {clientX, clientY} = e;
4 years ago
this.contextMenu.style.left = (side == 'right' ? clientX - this.contextMenu.scrollWidth : clientX) + 'px';
if((clientY + this.contextMenu.scrollHeight) > window.innerHeight) {
this.contextMenu.style.top = (window.innerHeight - this.contextMenu.scrollHeight) + 'px';
} else {
this.contextMenu.style.top = clientY + 'px';
}
4 years ago
//this.contextMenu.classList.add('active');
openBtnMenu(this.contextMenu);
4 years ago
4 years ago
/////this.log('contextmenu', e, bubble, msgID, side);
}
});
4 years ago
this.contextMenu.querySelector('.menu-copy').addEventListener('click', () => {
let message = appMessagesManager.getMessage(this.contextMenuMsgID);
4 years ago
let str = message ? message.message : '';
4 years ago
var textArea = document.createElement("textarea");
textArea.value = str;
textArea.style.position = "fixed"; //avoid scrolling to bottom
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
4 years ago
try {
document.execCommand('copy');
} catch (err) {
console.error('Oops, unable to copy', err);
}
4 years ago
document.body.removeChild(textArea);
});
4 years ago
this.contextMenu.querySelector('.menu-delete').addEventListener('click', () => {
if(this.peerID == this.myID) {
this.popupDeleteMessage.deleteBothBtn.style.display = 'none';
this.popupDeleteMessage.deleteMeBtn.innerText = 'DELETE';
} else {
this.popupDeleteMessage.deleteBothBtn.style.display = '';
this.popupDeleteMessage.deleteMeBtn.innerText = 'DELETE JUST FOR ME';
4 years ago
if(this.peerID > 0) {
let title = appPeersManager.getPeerTitle(this.peerID);
this.popupDeleteMessage.deleteBothBtn.innerHTML = 'DELETE FOR ME AND ' + title;
} else {
this.popupDeleteMessage.deleteBothBtn.innerText = 'DELETE FOR ALL';
}
}
4 years ago
this.popupDeleteMessage.popupEl.classList.add('active');
});
this.contextMenu.querySelector('.menu-reply').addEventListener('click', () => {
let message = appMessagesManager.getMessage(this.contextMenuMsgID);
4 years ago
this.chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message.media);
this.chatInputC.replyToMsgID = this.contextMenuMsgID;
this.chatInputC.editMsgID = 0;
});
4 years ago
this.contextMenuEdit.addEventListener('click', () => {
let message = appMessagesManager.getMessage(this.contextMenuMsgID);
4 years ago
this.chatInputC.setTopInfo('Editing', message.message, message.message, message.media);
this.chatInputC.replyToMsgID = 0;
this.chatInputC.editMsgID = this.contextMenuMsgID;
});
4 years ago
this.contextMenuPin.addEventListener('click', () => {
apiManager.invokeApi('messages.updatePinnedMessage', {
flags: 0,
peer: appPeersManager.getInputPeerByID(this.peerID),
id: this.contextMenuMsgID
}).then(updates => {
4 years ago
/////this.log('pinned updates:', updates);
apiUpdatesManager.processUpdateMessage(updates);
});
});
4 years ago
this.popupDeleteMessage.deleteBothBtn.addEventListener('click', () => {
this.deleteMessages(true);
this.popupDeleteMessage.cancelBtn.click();
});
4 years ago
this.popupDeleteMessage.deleteMeBtn.addEventListener('click', () => {
this.deleteMessages(false);
this.popupDeleteMessage.cancelBtn.click();
});
this.goDownBtn.addEventListener('click', () => {
let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0];
4 years ago
if(dialog) {
this.setPeer(this.peerID, dialog.top_message);
} else {
this.log('will scroll down 3');
this.scroll.scrollTop = this.scroll.scrollHeight;
}
});
4 years ago
4 years ago
this.updateStatusInterval = window.setInterval(() => this.updateStatus(), 50e3);
this.updateStatus();
setInterval(() => this.setPeerStatus(), 60e3);
4 years ago
4 years ago
this.setScroll();
4 years ago
}
4 years ago
public deleteMessages(revoke = false) {
let flags = revoke ? 1 : 0;
let ids = [this.contextMenuMsgID];
4 years ago
apiManager.invokeApi('messages.deleteMessages', {
flags: flags,
revoke: revoke,
id: ids
}).then((affectedMessages: any) => {
4 years ago
/////this.log('deleted messages:', affectedMessages);
4 years ago
apiUpdatesManager.processUpdateMessage({
_: 'updateShort',
update: {
_: 'updatePts',
pts: affectedMessages.pts,
pts_count: affectedMessages.pts_count
}
});
4 years ago
apiUpdatesManager.processUpdateMessage({
_: 'updateShort',
update: {
_: 'updateDeleteMessages',
messages: ids
}
});
});
}
4 years ago
public deleteEmptySideDivs() {
return;
4 years ago
let nodes = Array.from(this.chatInner.childNodes) as HTMLDivElement[];
nodes.filter((node) => {
let childElementCount = node.childElementCount;
4 years ago
if(!childElementCount) {
node.remove();
return false;
} else if(childElementCount == 1) {
let child = node.firstElementChild;
if(child.classList.contains('service')) {
node.remove();
return false;
}
}
4 years ago
return true;
}).forEach(node => {
let nextNode = node.nextElementSibling;
if(nextNode && node.className == nextNode.className) {
(Array.from(node.childNodes) as HTMLDivElement[]).reverse().forEach(div => {
nextNode.prepend(div);
});
4 years ago
node.remove();
}
});
}
4 years ago
4 years ago
public loadMediaQueuePush(cb: () => Promise<void>) {
this.loadMediaQueue.push(cb);
this.loadMediaQueueProcess();
}
4 years ago
public async loadMediaQueueProcess(): Promise<void[]> {
4 years ago
if(this.loadingMedia >= 5/* || 1 == 1 */) return;
let item = this.loadMediaQueue.pop();
if(item) {
this.loadingMedia++;
4 years ago
let peerID = this.peerID;
4 years ago
let promise = item();
try {
await promise;
} catch(err) {
this.log.error('loadMediaQueue error:', err);
}
4 years ago
if(peerID == this.peerID) {
this.loadingMedia--;
}
}
if(this.loadMediaQueue.length) return this.loadMediaQueueProcess();
}
4 years ago
4 years ago
public updateStatus() {
if(!this.myID) return Promise.resolve();
4 years ago
4 years ago
appUsersManager.setUserStatus(this.myID, this.offline);
4 years ago
return apiManager.invokeApi('account.updateStatus', {offline: this.offline});
4 years ago
}
4 years ago
public loadMoreHistory(top: boolean) {
// load more history
// возможно нужно добавить разные таймауты для верха и низа
if(!this.getHistoryPromise && !this.getHistoryTimeout && this.peerID && !testScroll) {
this.getHistoryTimeout = setTimeout(() => { // must be
let history = Object.keys(this.bubbles).map(id => +id).sort();
/* let history = appMessagesManager.historiesStorage[this.peerID].history;
let length = history.length; */
// filter negative ids
let lastBadIndex = -1;
for(let i = 0; i < history.length; ++i) {
if(history[i] <= 0) lastBadIndex = i;
else break;
}
if(lastBadIndex != -1) {
history = history.slice(lastBadIndex + 1);
}
this.getHistoryTimeout = 0;
if(!this.scrolledAll && top) {
this.log('Will load more (up) history by id:', history[0], 'maxID:', history[history.length - 1], history);
/* false && */!testScroll && this.getHistory(history[0], true).then(() => { // uncomment
this.onScroll();
}).catch(err => {
this.log.warn('Could not load more history, err:', err);
});
}
if(this.scrolledAllDown) return;
let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0];
/* if(!dialog) {
this.log.warn('no dialog for load history');
return;
} */
// if scroll down after search
if(!top && (!dialog || history.indexOf(dialog.top_message) === -1)) {
this.log('Will load more (down) history by maxID:', history[history.length - 1], history);
/* false && */!testScroll && this.getHistory(history[history.length - 1], false, true).then(() => { // uncomment
this.onScroll();
}).catch(err => {
this.log.warn('Could not load more history, err:', err);
});
}
}, 0);
}
}
4 years ago
public onScroll() {
let readed: number[] = [];
4 years ago
4 years ago
this.unreaded.forEachReverse((msgID, idx) => {
4 years ago
let bubble = this.bubbles[msgID];
4 years ago
4 years ago
if(isElementInViewport(bubble)) {
readed.push(msgID);
4 years ago
this.unreaded.splice(idx, 1);
4 years ago
}
4 years ago
});
4 years ago
lottieLoader.checkAnimations(false, 'chat');
4 years ago
4 years ago
if(readed.length) {
let max = Math.max(...readed);
4 years ago
let min = Math.min(...readed);
4 years ago
if(this.peerID < 0) {
max = appMessagesIDsManager.getMessageIDInfo(max)[0];
min = appMessagesIDsManager.getMessageIDInfo(min)[0];
}
4 years ago
4 years ago
//appMessagesManager.readMessages(readed);
appMessagesManager.readHistory(this.peerID, max, min).catch((err: any) => {
this.log.error('readHistory err:', err);
appMessagesManager.readHistory(this.peerID, max, min);
});
4 years ago
}
4 years ago
4 years ago
if(this.scroll.scrollHeight - (this.scroll.scrollTop + this.scroll.offsetHeight) == 0/* <= 5 */) {
this.scroll.parentElement.classList.add('scrolled-down');
} else if(this.scroll.parentElement.classList.contains('scrolled-down')) {
this.scroll.parentElement.classList.remove('scrolled-down');
}
}
4 years ago
4 years ago
public setScroll() {
this.scrollable = new Scrollable(this.bubblesContainer, false, true, 750, 'IM', this.chatInner/* 1500 */, 450);
4 years ago
this.scroll = this.scrollable.container;
this.bubblesContainer.append(this.goDownBtn);
4 years ago
this.scrollable.setVirtualContainer(this.chatInner);
4 years ago
this.scrollable.onScrolledTop = () => this.loadMoreHistory(true);
this.scrollable.onScrolledBottom = () => this.loadMoreHistory(false);
4 years ago
this.scrollPosition = new ScrollPosition(this.chatInner);
this.scroll.addEventListener('scroll', this.onScroll.bind(this));
this.scroll.parentElement.classList.add('scrolled-down');
4 years ago
}
4 years ago
4 years ago
public setPeerStatus() {
if(!this.myID) return;
4 years ago
4 years ago
// set subtitle
this.subtitleEl.innerText = appSidebarRight.profileElements.subtitle.innerText = '';
this.subtitleEl.classList.remove('online');
appSidebarRight.profileElements.subtitle.classList.remove('online');
4 years ago
4 years ago
if(this.peerID < 0) { // not human
let chat = appPeersManager.getPeer(this.peerID);
let isChannel = appPeersManager.isChannel(this.peerID) && !appPeersManager.isMegagroup(this.peerID);
4 years ago
4 years ago
///////this.log('setPeerStatus', chat);
4 years ago
Promise.all([
appPeersManager.isMegagroup(this.peerID) ? apiManager.invokeApi('messages.getOnlines', {
peer: appPeersManager.getInputPeerByID(this.peerID)
}) as Promise<any> : Promise.resolve(),
// will redirect if wrong
appProfileManager.getChatFull(chat.id)
]).then(results => {
let [chatOnlines, chatInfo] = results;
4 years ago
let onlines = chatOnlines ? chatOnlines.onlines : 1;
4 years ago
4 years ago
///////////this.log('chatInfo res:', chatInfo);
4 years ago
if(chatInfo.pinned_msg_id) { // request pinned message
this.pinnedMsgID = chatInfo.pinned_msg_id;
appMessagesManager.wrapSingleMessage(chatInfo.pinned_msg_id);
4 years ago
}
4 years ago
let participants_count = chatInfo.participants_count || chatInfo.participants.participants.length;
4 years ago
let subtitle = numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members');
4 years ago
if(onlines > 1) {
subtitle += ', ' + numberWithCommas(onlines) + ' online';
}
4 years ago
4 years ago
this.subtitleEl.innerText = appSidebarRight.profileElements.subtitle.innerText = subtitle;
});
} else if(!appUsersManager.isBot(this.peerID)) { // user
let user = appUsersManager.getUser(this.peerID);
4 years ago
4 years ago
//this.subtitleEl.classList.remove('online');
4 years ago
4 years ago
if(user && user.status && this.myID != this.peerID) {
let subtitle = '';
switch(user.status._) {
case 'userStatusRecently':
4 years ago
subtitle += 'last seen recently';
break;
4 years ago
case 'userStatusOffline':
4 years ago
subtitle = 'last seen ';
let date = user.status.was_online;
let now = Date.now() / 1000;
if((now - date) < 60) {
subtitle += ' just now';
} else if((now - date) < 3600) {
subtitle += ((now - date) / 60 | 0) + ' minutes ago';
} else if(now - date < 86400) {
subtitle += ((now - date) / 3600 | 0) + ' hours ago';
} else {
let d = new Date(date * 1000);
subtitle += ('0' + d.getDate()).slice(-2) + '.' + ('0' + (d.getMonth() + 1)).slice(-2) + ' at ' +
('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2);
}
break;
4 years ago
case 'userStatusOnline':
4 years ago
this.subtitleEl.classList.add('online');
appSidebarRight.profileElements.subtitle.classList.add('online');
subtitle = 'online';
break;
4 years ago
}
4 years ago
4 years ago
appSidebarRight.profileElements.subtitle.innerText = subtitle;
4 years ago
4 years ago
if(this.typingUsers[this.peerID] == this.peerID) {
this.subtitleEl.innerText = 'typing...';
this.subtitleEl.classList.add('online');
} else this.subtitleEl.innerText = subtitle;
}
}
}
4 years ago
4 years ago
public cleanup() {
this.peerID = $rootScope.selectedPeerID = 0;
this.scrolledAll = false;
this.scrolledAllDown = false;
this.muted = false;
4 years ago
4 years ago
for(let i in this.bubbles) {
let bubble = this.bubbles[i];
bubble.remove();
}
this.bubbles = {};
this.dateMessages = {};
4 years ago
this.bubbleGroups.cleanup();
4 years ago
this.unreaded = [];
this.unreadOut = [];
this.loadMediaQueue = [];
this.loadingMedia = 0;
this.needUpdate.length = 0;
4 years ago
lottieLoader.checkAnimations(false, 'chat', true);
4 years ago
// clear input
this.chatInputC.messageInput.innerHTML = '';
this.chatInputC.replyElements.cancelBtn.click();
4 years ago
// clear messages
4 years ago
this.chatInner.innerHTML = '';
4 years ago
this.scrollable.setVirtualContainer(this.chatInner);
4 years ago
4 years ago
//appSidebarRight.minMediaID = {};
}
4 years ago
public setPeer(peerID: number, lastMsgID = 0, forwarding = false) {
if(peerID == 0) {
appSidebarRight.toggleSidebar(false);
this.topbar.style.display = this.chatInput.style.display = this.goDownBtn.style.display = 'none';
this.cleanup();
return Promise.resolve(false);
}
4 years ago
4 years ago
let samePeer = this.peerID == peerID;
4 years ago
if(this.setPeerPromise && samePeer) return this.setPeerPromise;
4 years ago
if(lastMsgID) {
appMessagesManager.readHistory(peerID, lastMsgID); // lol
}
4 years ago
if(samePeer) {
if(!testScroll && !lastMsgID) {
return Promise.resolve(true);
}
4 years ago
4 years ago
if(this.bubbles[lastMsgID]) {
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
4 years ago
if(dialog && lastMsgID == dialog.top_message) {
this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight);
this.scroll.scrollTop = this.scroll.scrollHeight;
} else {
4 years ago
//this.bubbles[lastMsgID].scrollIntoView();
this.scrollable.scrollIntoView(this.bubbles[lastMsgID]);
}
4 years ago
4 years ago
return Promise.resolve(true);
}
}
4 years ago
4 years ago
// clear
this.cleanup();
4 years ago
4 years ago
// set new
this.peerID = $rootScope.selectedPeerID = peerID;
4 years ago
4 years ago
// no dialog
/* if(!appMessagesManager.getDialogByPeerID(this.peerID).length) {
4 years ago
this.log.error('No dialog by peerID:', this.peerID);
return Promise.reject();
} */
4 years ago
4 years ago
this.pinnedMessageContainer.style.display = 'none';
4 years ago
this.preloader.attach(this.bubblesContainer);
4 years ago
let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0] || null;
4 years ago
//////this.log('setPeer peerID:', this.peerID, dialog, lastMsgID);
appDialogsManager.loadDialogPhoto(this.avatarEl, this.peerID);
appDialogsManager.loadDialogPhoto(appSidebarRight.profileElements.avatar, this.peerID);
if(!samePeer && appDialogsManager.lastActiveListElement) {
appDialogsManager.lastActiveListElement.classList.remove('active');
}
4 years ago
this.firstTopMsgID = dialog ? dialog.top_message : 0;
4 years ago
/* let dom = appDialogsManager.getDialogDom(this.peerID);
4 years ago
if(!dom) {
this.log.warn('No rendered dialog by peerID:', this.peerID);
appDialogsManager.addDialog(dialog);
dom = appDialogsManager.getDialogDom(this.peerID);
}
// warning need check
dom.listEl.classList.add('active'); */
4 years ago
4 years ago
this.setPeerStatus();
4 years ago
4 years ago
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;
4 years ago
this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = title;
4 years ago
this.topbar.style.display = this.goDownBtn.style.display = '';
//appSidebarRight.toggleSidebar(true);
4 years ago
this.chatInput.style.display = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID) ? 'none' : '';
4 years ago
if(appPeersManager.isAnyGroup(peerID)) {
this.chatInner.classList.add('is-chat');
} else {
this.chatInner.classList.remove('is-chat');
}
4 years ago
//this.scroll.scrollTop = this.scroll.scrollHeight;
return this.setPeerPromise = Promise.all([
this.getHistory(forwarding ? lastMsgID + 1 : lastMsgID).then(() => {
4 years ago
////this.log('setPeer removing preloader');
4 years ago
if(lastMsgID) {
if(!forwarding) {
let message = appMessagesManager.getMessage(lastMsgID);
4 years ago
//////this.log('setPeer render last message:', message, lastMsgID);
this.renderMessage(message);
}
4 years ago
if(!dialog || lastMsgID != dialog.top_message) {
let bubble = this.bubbles[lastMsgID];
if(bubble) this.bubbles[lastMsgID].scrollIntoView();
else this.log.warn('no bubble by lastMsgID:', lastMsgID);
} else {
this.log('will scroll down 2');
this.scroll.scrollTop = this.scroll.scrollHeight;
4 years ago
}
} else if(dialog && dialog.top_message) { // add last message, bc in getHistory will load < max_id
4 years ago
this.renderMessage(appMessagesManager.getMessage(dialog.top_message), false, true);
//this.scroll.scrollTop = this.scroll.scrollHeight;
4 years ago
}
4 years ago
this.onScroll();
this.scrollable.onScroll();
4 years ago
this.preloader.detach();
4 years ago
//setTimeout(() => {
4 years ago
//appSidebarRight.fillProfileElements();
appSidebarRight.loadSidebarMedia(true);
//}, 500);
4 years ago
return true;
})/* .catch(err => {
this.log.error(err);
}) */,
4 years ago
4 years ago
appSidebarRight.fillProfileElements()
]).then(() => {
if(this.peerID == peerID) {
this.setPeerPromise = null;
}
4 years ago
return true;
}).catch(err => {
if(this.peerID == peerID) {
this.setPeerPromise = null;
}
4 years ago
this.log.error('setPeer promises error:', err);
this.preloader.detach();
return false;
4 years ago
});
}
4 years ago
public setTyping(action: any): Promise<boolean> {
if(!this.peerID) return Promise.resolve(false);
4 years ago
if(typeof(action) == 'string') {
action = {_: action};
}
4 years ago
let input = appPeersManager.getInputPeerByID(this.peerID);
return apiManager.invokeApi('messages.setTyping', {
peer: input,
action: action
}) as Promise<boolean>;
}
4 years ago
4 years ago
public updateUnreadByDialog(dialog: any) {
let maxID = this.peerID == this.myID ? dialog.read_inbox_max_id : dialog.read_outbox_max_id;
4 years ago
4 years ago
///////this.log('updateUnreadByDialog', maxID, dialog, this.unreadOut);
4 years ago
4 years ago
this.unreadOut.forEachReverse((msgID, idx) => {
if(msgID > 0 && msgID <= maxID) {
4 years ago
let bubble = this.bubbles[msgID];
bubble.classList.remove('is-sent');
bubble.classList.add('is-read');
4 years ago
this.unreadOut.splice(idx, 1);
4 years ago
}
4 years ago
});
4 years ago
}
4 years ago
4 years ago
public deleteMessagesByIDs(msgIDs: number[]) {
msgIDs.forEach(id => {
if(this.firstTopMsgID == id) {
let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0];
4 years ago
if(dialog) {
4 years ago
///////this.log('setting firstTopMsgID after delete:', id, dialog.top_message, dialog);
this.firstTopMsgID = dialog.top_message;
}
}
4 years ago
4 years ago
if(!(id in this.bubbles)) return;
let bubble = this.bubbles[id];
delete this.bubbles[id];
4 years ago
this.scrollable.removeElement(bubble);
//bubble.remove();
4 years ago
});
4 years ago
4 years ago
lottieLoader.checkAnimations();
}
4 years ago
4 years ago
public renderMessagesByIDs(msgIDs: number[]) {
if(!this.bubbles[this.firstTopMsgID] && Object.keys(this.bubbles).length) { // seems search active
4 years ago
//////this.log('seems search is active, skipping render:', msgIDs);
4 years ago
return;
}
4 years ago
4 years ago
msgIDs.forEach((msgID: number) => {
let message = appMessagesManager.getMessage(msgID);
4 years ago
4 years ago
/////////this.log('got new message to append:', message);
4 years ago
4 years ago
//this.unreaded.push(msgID);
this.renderMessage(message);
});
}
4 years ago
public renderMessage(message: any, reverse = false, multipleRender?: boolean, bubble: HTMLDivElement = null, updatePosition = true) {
4 years ago
/////this.log('message to render:', message);
if(message.deleted) return;
4 years ago
4 years ago
let peerID = this.peerID;
let our = message.fromID == this.myID;
4 years ago
4 years ago
let messageDiv = document.createElement('div');
messageDiv.classList.add('message');
4 years ago
4 years ago
//messageDiv.innerText = message.message;
4 years ago
if(!multipleRender) {
this.scrollPosition.prepareFor(reverse ? 'up' : 'down'); // лагает из-за этого
}
4 years ago
4 years ago
let bubbleContainer: HTMLDivElement;
4 years ago
// bubble
if(!bubble) {
4 years ago
bubbleContainer = document.createElement('div');
bubbleContainer.classList.add('bubble__container');
4 years ago
bubble = document.createElement('div');
bubble.classList.add('bubble');
4 years ago
bubble.appendChild(bubbleContainer);
this.bubbles[+message.mid] = bubble;
} else {
bubble.className = 'bubble';
4 years ago
bubbleContainer = bubble.firstElementChild as HTMLDivElement;
bubbleContainer.innerHTML = '';
//bubble.innerHTML = '';
}
4 years ago
if(message._ == 'messageService') {
bubble.className = 'bubble service';
let action = message.action;
let title = appPeersManager.getPeerTitle(message.fromID);
let name = document.createElement('div');
name.classList.add('name');
name.dataset.peerID = message.fromID;
name.innerHTML = title;
let _ = action._;
if(_ == "messageActionPhoneCall") {
_ += '.' + action.type;
}
// @ts-ignore
let str = (name.innerText ? name.outerHTML + ' ' : '') + langPack[_];
bubbleContainer.innerHTML = `<div class="service-msg">${str}</div>`;
if(!multipleRender) {
this.scrollPosition.restore(); // лагает из-за этого
}
return;
}
4 years ago
// time section
4 years ago
4 years ago
let date = new Date(message.date * 1000);
4 years ago
let time = ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2);
if(message.views) {
bubble.classList.add('channel-post');
time = formatNumber(message.views, 1) + ' <i class="tgico-channelviews"></i> ' + time;
}
4 years ago
if(message.edit_date) {
bubble.classList.add('is-edited');
time = '<i class="edited">edited</i> ' + time;
}
4 years ago
4 years ago
let timeSpan = document.createElement('span');
timeSpan.classList.add('time');
4 years ago
4 years ago
let timeInner = document.createElement('div');
timeInner.classList.add('inner', 'tgico');
timeInner.innerHTML = time;
4 years ago
4 years ago
let richText = RichTextProcessor.wrapRichText(message.message, {
entities: message.totalEntities
});
4 years ago
4 years ago
if(message.totalEntities) {
let emojiEntities = message.totalEntities.filter((e: any) => e._ == 'messageEntityEmoji');
let strLength = message.message.length;
let emojiStrLength = emojiEntities.reduce((acc: number, curr: any) => acc + curr.length, 0);
4 years ago
4 years ago
if(emojiStrLength == strLength && emojiEntities.length <= 3) {
let attachmentDiv = document.createElement('div');
attachmentDiv.classList.add('attachment');
4 years ago
4 years ago
attachmentDiv.innerHTML = richText;
4 years ago
4 years ago
messageDiv.classList.add('message-empty');
bubble.classList.add('emoji-' + emojiEntities.length + 'x', 'emoji-big');
4 years ago
4 years ago
bubbleContainer.append(attachmentDiv);
4 years ago
} else {
messageDiv.innerHTML = richText;
}
4 years ago
4 years ago
/* if(strLength == emojiStrLength) {
messageDiv.classList.add('emoji-only');
messageDiv.classList.add('message-empty');
} */
} else {
messageDiv.innerHTML = richText;
}
4 years ago
4 years ago
//messageDiv.innerHTML = 'samsung samsung samsung';
4 years ago
4 years ago
timeSpan.appendChild(timeInner);
messageDiv.append(timeSpan);
4 years ago
bubbleContainer.prepend(messageDiv);
//bubble.prepend(timeSpan, messageDiv); // that's bad
4 years ago
4 years ago
if(our) {
if(message.pFlags.unread || message.mid < 0) this.unreadOut.push(message.mid); // message.mid < 0 added 11.02.2020
let status = '';
if(message.mid < 0) status = 'is-sending';
else status = message.pFlags.unread ? 'is-sent' : 'is-read';
4 years ago
bubble.classList.add(status);
} else {
//this.log('not our message', message, message.pFlags.unread);
4 years ago
if(message.pFlags.unread) this.unreaded.push(message.mid);
}
4 years ago
4 years ago
// media
if(message.media) {
let attachmentDiv = document.createElement('div');
attachmentDiv.classList.add('attachment');
4 years ago
4 years ago
if(!message.message) {
messageDiv.classList.add('message-empty');
}
4 years ago
4 years ago
let processingWebPage = false;
switch(message.media._) {
case 'messageMediaPending': {
let pending = message.media;
let preloader = pending.preloader as ProgressivePreloader;
4 years ago
switch(pending.type) {
case 'photo': {
if(pending.size < 5e6) {
let img = new Image();
img.src = URL.createObjectURL(pending.file);
4 years ago
let {w, h} = calcImageInBox(pending.w, pending.h, 380, 380);
4 years ago
attachmentDiv.style.width = w + 'px';
attachmentDiv.style.height = h + 'px';
4 years ago
attachmentDiv.append(img);
preloader.attach(attachmentDiv, false);
bubble.classList.add('hide-name', 'photo');
4 years ago
break;
}
}
4 years ago
case 'audio':
case 'document': {
4 years ago
let docDiv = wrapDocument(pending, false, true);
4 years ago
let icoDiv = docDiv.querySelector('.document-ico');
preloader.attach(icoDiv, false);
4 years ago
messageDiv.classList.remove('message-empty');
messageDiv.append(docDiv);
processingWebPage = true;
break;
}
4 years ago
}
4 years ago
break;
}
4 years ago
4 years ago
case 'messageMediaPhoto': {
let photo = message.media.photo;
4 years ago
////////this.log('messageMediaPhoto', photo);
4 years ago
bubble.classList.add('hide-name', 'photo');
4 years ago
wrapPhoto.call(this, photo, message, attachmentDiv);
4 years ago
break;
}
4 years ago
4 years ago
case 'messageMediaWebPage': {
processingWebPage = true;
4 years ago
4 years ago
let webpage = message.media.webpage;
4 years ago
////////this.log('messageMediaWebPage', webpage);
4 years ago
if(webpage._ == 'webPageEmpty') {
break;
}
4 years ago
4 years ago
bubble.classList.add('webpage');
4 years ago
4 years ago
let box = document.createElement('div');
box.classList.add('box', 'web');
4 years ago
4 years ago
let quote = document.createElement('div');
quote.classList.add('quote');
4 years ago
4 years ago
let nameEl = document.createElement('a');
nameEl.classList.add('name');
4 years ago
4 years ago
let titleDiv = document.createElement('div');
titleDiv.classList.add('title');
4 years ago
4 years ago
let textDiv = document.createElement('div');
textDiv.classList.add('text');
4 years ago
4 years ago
let preview: HTMLDivElement = null;
if(webpage.photo || webpage.document) {
preview = document.createElement('div');
preview.classList.add('preview');
}
4 years ago
4 years ago
let doc: any = null;
if(webpage.document) {
doc = webpage.document;
4 years ago
4 years ago
if(doc.type == 'gif' || doc.type == 'video') {
//if(doc.size <= 20e6) {
4 years ago
bubble.classList.add('video');
wrapVideo.call(this, doc, preview, message);
//}
4 years ago
} else {
doc = null;
}
}
4 years ago
4 years ago
if(webpage.photo && !doc) {
bubble.classList.add('photo');
//appPhotosManager.savePhoto(webpage.photo); // hot-fix because no webpage manager
4 years ago
wrapPhoto.call(this, webpage.photo, message, preview, 380, 300);
4 years ago
}
4 years ago
4 years ago
if(preview) {
quote.append(preview);
}
4 years ago
4 years ago
nameEl.setAttribute('target', '_blank');
nameEl.href = webpage.url || '#';
nameEl.innerHTML = webpage.site_name ? RichTextProcessor.wrapEmojiText(webpage.site_name) : '';
4 years ago
4 years ago
if(webpage.description) {
textDiv.innerHTML = RichTextProcessor.wrapRichText(webpage.description);
}
4 years ago
if(webpage.title) {
titleDiv.innerHTML = RichTextProcessor.wrapRichText(webpage.title);
}
4 years ago
4 years ago
quote.append(nameEl, titleDiv, textDiv);
box.append(quote);
4 years ago
//bubble.prepend(box);
4 years ago
bubbleContainer.prepend(timeSpan, box);
4 years ago
4 years ago
//this.log('night running', bubble.scrollHeight);
4 years ago
4 years ago
break;
}
4 years ago
4 years ago
case 'messageMediaDocument': {
let doc = message.media.document;
/* if(document.size > 1e6) { // 1mb
break;
} */
4 years ago
4 years ago
////////this.log('messageMediaDocument', doc);
4 years ago
4 years ago
if(doc.sticker && doc.size <= 1e6) {
bubble.classList.add('sticker');
4 years ago
4 years ago
if(doc.animated) {
bubble.classList.add('sticker-animated');
}
4 years ago
appPhotosManager.setAttachmentSize(doc, attachmentDiv, undefined, undefined, true);
let preloader = new ProgressivePreloader(attachmentDiv, false);
4 years ago
bubbleContainer.style.height = attachmentDiv.style.height;
bubbleContainer.style.width = attachmentDiv.style.width;
4 years ago
//appPhotosManager.setAttachmentSize(doc, bubble);
let load = () => wrapSticker(doc, attachmentDiv, () => {
if(this.peerID != peerID) {
this.log.warn('peer changed, canceling sticker attach');
return false;
}
4 years ago
4 years ago
return true;
}, null, 'chat', false, !!message.pending || !multipleRender).then(() => {
preloader.detach();
/* attachmentDiv.style.width = '';
attachmentDiv.style.height = ''; */
});
4 years ago
4 years ago
this.loadMediaQueuePush(load);
4 years ago
4 years ago
break;
} else if(doc.mime_type == 'video/mp4' && doc.size <= 20e6) {
4 years ago
this.log('never get free 2', doc);
if(doc.type == 'round') {
bubble.classList.add('round');
}
4 years ago
4 years ago
bubble.classList.add('video');
wrapVideo.call(this, doc, attachmentDiv, message, true, null, false, doc.type == 'round');
4 years ago
break;
} else if(doc.mime_type == 'audio/ogg') {
let docDiv = wrapDocument(doc);
messageDiv.classList.remove('message-empty');
bubble.classList.add('bubble-audio');
messageDiv.append(docDiv);
processingWebPage = true;
4 years ago
break;
} else {
let docDiv = wrapDocument(doc);
4 years ago
4 years ago
messageDiv.classList.remove('message-empty');
messageDiv.append(docDiv);
processingWebPage = true;
4 years ago
4 years ago
break;
}
}
4 years ago
4 years ago
default:
4 years ago
messageDiv.classList.remove('message-empty');
messageDiv.innerHTML = 'unrecognized media type: ' + message.media._;
messageDiv.append(timeSpan);
this.log.warn('unrecognized media type:', message.media._, message);
break;
4 years ago
}
4 years ago
4 years ago
if(!processingWebPage) {
4 years ago
bubbleContainer.append(attachmentDiv);
4 years ago
}
}
4 years ago
if((this.peerID < 0 && !our) || message.fwd_from || message.reply_to_mid) { // chat
4 years ago
let title = appPeersManager.getPeerTitle(message.fwdFromID || message.fromID);
4 years ago
let isHidden = message.fwd_from && !message.fwd_from.from_id && !message.fwd_from.channel_id;
if(isHidden) {
4 years ago
///////this.log('message to render hidden', message);
title = message.fwd_from.from_name;
bubble.classList.add('hidden-profile');
}
4 years ago
//this.log(title);
4 years ago
if(message.fwdFromID || message.fwd_from) {
4 years ago
bubble.classList.add('forwarded');
4 years ago
if(message.savedFrom) {
let fwd = document.createElement('div');
fwd.classList.add('forward'/* , 'tgico-forward' */);
4 years ago
fwd.innerHTML = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24">
<defs><path d="M13.55 3.24L13.64 3.25L13.73 3.27L13.81 3.29L13.9 3.32L13.98 3.35L14.06 3.39L14.14 3.43L14.22 3.48L14.29 3.53L14.36 3.59L14.43 3.64L22.23 10.85L22.36 10.99L22.48 11.15L22.57 11.31L22.64 11.48L22.69 11.66L22.72 11.85L22.73 12.04L22.71 12.22L22.67 12.41L22.61 12.59L22.53 12.76L22.42 12.93L22.29 13.09L22.23 13.15L14.43 20.36L14.28 20.48L14.12 20.58L13.95 20.66L13.77 20.72L13.58 20.76L13.4 20.77L13.22 20.76L13.03 20.73L12.85 20.68L12.68 20.61L12.52 20.52L12.36 20.4L12.22 20.27L12.16 20.2L12.1 20.13L12.05 20.05L12.01 19.98L11.96 19.9L11.93 19.82L11.89 19.73L11.87 19.65L11.84 19.56L11.83 19.47L11.81 19.39L11.81 19.3L11.8 19.2L11.8 16.42L11 16.49L10.23 16.58L9.51 16.71L8.82 16.88L8.18 17.09L7.57 17.33L7.01 17.6L6.48 17.91L5.99 18.26L5.55 18.64L5.14 19.05L4.77 19.51L4.43 19.99L4.29 20.23L4.21 20.35L4.11 20.47L4 20.57L3.88 20.65L3.75 20.72L3.62 20.78L3.48 20.82L3.33 20.84L3.19 20.84L3.04 20.83L2.9 20.79L2.75 20.74L2.62 20.68L2.53 20.62L2.45 20.56L2.38 20.5L2.31 20.43L2.25 20.36L2.2 20.28L2.15 20.19L2.11 20.11L2.07 20.02L2.04 19.92L2.02 19.83L2.01 19.73L2 19.63L2.04 17.99L2.19 16.46L2.46 15.05L2.85 13.75L3.35 12.58L3.97 11.53L4.7 10.6L5.55 9.8L6.51 9.12L7.59 8.56L8.77 8.13L10.07 7.83L11.48 7.65L11.8 7.63L11.8 4.8L11.91 4.56L12.02 4.35L12.14 4.16L12.25 3.98L12.37 3.82L12.48 3.68L12.61 3.56L12.73 3.46L12.85 3.38L12.98 3.31L13.11 3.27L13.24 3.24L13.37 3.23L13.46 3.23L13.55 3.24Z" id="b13RmHDQtl"></path></defs><use xlink:href="#b13RmHDQtl" opacity="1" fill="#fff" fill-opacity="1"></use></svg>`;
4 years ago
bubbleContainer.append(fwd);
bubble.dataset.savedFrom = message.savedFrom;
}
4 years ago
4 years ago
if(!bubble.classList.contains('sticker')) {
let nameDiv = document.createElement('div');
nameDiv.classList.add('name');
nameDiv.innerHTML = 'Forwarded from ' + title;
nameDiv.dataset.peerID = message.fwdFromID;
//nameDiv.style.color = appPeersManager.getPeerColorByID(message.fromID, false);
4 years ago
bubbleContainer.append(nameDiv);
4 years ago
}
} else {
if(message.reply_to_mid) {
let originalMessage = appMessagesManager.getMessage(message.reply_to_mid);
4 years ago
let originalPeerTitle = appPeersManager.getPeerTitle(originalMessage.fromID, true) || '';
4 years ago
4 years ago
/////////this.log('message to render reply', originalMessage, originalPeerTitle, bubble, message);
4 years ago
// need to download separately
if(originalMessage._ == 'messageEmpty') {
4 years ago
//////////this.log('message to render reply empty, need download', message, message.reply_to_mid);
appMessagesManager.wrapSingleMessage(message.reply_to_mid);
this.needUpdate.push({replyMid: message.reply_to_mid, mid: message.mid});
4 years ago
originalPeerTitle = 'Loading...';
}
4 years ago
if(originalMessage.mid) {
bubble.setAttribute('data-original-mid', originalMessage.mid);
} else {
bubble.setAttribute('data-original-mid', message.reply_to_mid);
}
4 years ago
4 years ago
bubbleContainer.append(wrapReply(originalPeerTitle, originalMessage.message || '', originalMessage.media));
bubble.classList.add('is-reply');
4 years ago
}
4 years ago
if(!bubble.classList.contains('sticker') && (peerID < 0 && peerID != message.fromID)) {
4 years ago
let nameDiv = document.createElement('div');
nameDiv.classList.add('name');
nameDiv.innerHTML = title;
nameDiv.style.color = appPeersManager.getPeerColorByID(message.fromID, false);
nameDiv.dataset.peerID = message.fromID;
4 years ago
bubbleContainer.append(nameDiv);
} else /* if(!message.reply_to_mid) */ {
bubble.classList.add('hide-name');
4 years ago
}
}
4 years ago
if(!our && this.peerID < 0 && (!appPeersManager.isChannel(this.peerID) || appPeersManager.isMegagroup(this.peerID))) {
let avatarDiv = document.createElement('div');
avatarDiv.classList.add('user-avatar');
4 years ago
4 years ago
/////////this.log('exec loadDialogPhoto', message);
if(message.fromID) { // if no - user hidden
appDialogsManager.loadDialogPhoto(avatarDiv, message.fromID);
} else if(!title && message.fwd_from && message.fwd_from.from_name) {
title = message.fwd_from.from_name;
4 years ago
appDialogsManager.loadDialogPhoto(avatarDiv, 0, false, title);
}
4 years ago
avatarDiv.dataset.peerID = message.fromID;
4 years ago
4 years ago
bubbleContainer.append(avatarDiv);
4 years ago
}
} else {
bubble.classList.add('hide-name');
4 years ago
}
4 years ago
4 years ago
bubble.classList.add(our ? 'is-out' : 'is-in');
if(updatePosition) {
if(reverse) {
this.scrollable.prepend(bubble);
} else {
this.scrollable.append(bubble);
4 years ago
}
4 years ago
this.bubbleGroups.addBubble(bubble, message, reverse);
let justDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
let dateTimestamp = justDate.getTime();
if(!(dateTimestamp in this.dateMessages)) {
let str = '';
4 years ago
let today = new Date();
today.setHours(0);
today.setMinutes(0);
today.setSeconds(0);
4 years ago
if(today < date) {
str = 'Today';
} else {
4 years ago
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
str = justDate.getFullYear() == new Date().getFullYear() ?
4 years ago
months[justDate.getMonth()] + ' ' + justDate.getDate() :
justDate.toISOString().split('T')[0].split('-').reverse().join('.');
}
4 years ago
let div = document.createElement('div');
4 years ago
div.className = 'bubble service';
div.innerHTML = `<div class="service-msg">${str}</div>`;
4 years ago
////////this.log('need to render date message', dateTimestamp, str);
this.dateMessages[dateTimestamp] = {
div,
firstTimestamp: date.getTime()
};
4 years ago
4 years ago
this.scrollable.insertBefore(div, bubble);
} else {
let dateMessage = this.dateMessages[dateTimestamp];
if(dateMessage.firstTimestamp > date.getTime()) {
4 years ago
this.scrollable.insertBefore(dateMessage.div, bubble);
}
}
4 years ago
} else {
this.bubbleGroups.updateGroupByMessageID(message.mid);
}
4 years ago
/* if(bubble.classList.contains('webpage')) {
this.log('night running', bubble, bubble.scrollHeight);
} */
4 years ago
//return //this.scrollPosition.restore();
4 years ago
4 years ago
if(!multipleRender) {
this.scrollPosition.restore(); // лагает из-за этого
}
}
4 years ago
4 years ago
// reverse means scroll up
public getHistory(maxID = 0, reverse = false, isBackLimit = false) {
let peerID = this.peerID;
4 years ago
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
if(!maxID && dialog && dialog.top_message) {
maxID = dialog.top_message/* + 1 */;
4 years ago
}
4 years ago
let loadCount = Object.keys(this.bubbles).length > 0 ? 20 : this.scrollable.container.parentElement.scrollHeight / 30 * 1.25 | 0;
4 years ago
/* if(testScroll) {
loadCount = 1;
4 years ago
if(Object.keys(this.bubbles).length > 0)
return Promise.resolve(true);
4 years ago
} */
4 years ago
4 years ago
//console.time('render getHistory');
//console.time('render history total');
4 years ago
4 years ago
let backLimit = 0;
if(isBackLimit) {
backLimit = loadCount;
loadCount = 0;
maxID += 1;
}
4 years ago
4 years ago
return this.getHistoryPromise = appMessagesManager.getHistory(this.peerID, maxID, loadCount, backLimit)
.then((result: any) => {
4 years ago
this.log('getHistory result by maxID:', maxID, reverse, isBackLimit, result);
4 years ago
4 years ago
//console.timeEnd('render getHistory');
4 years ago
4 years ago
if(this.peerID != peerID) {
this.log.warn('peer changed');
4 years ago
//console.timeEnd('render history total');
4 years ago
return Promise.reject();
}
4 years ago
4 years ago
if(!result || !result.history) {
4 years ago
//console.timeEnd('render history total');
4 years ago
return true;
}
4 years ago
// commented bot getProfile in getHistory!
if(!result.history/* .filter((id: number) => id > 0) */.length) {
if(!isBackLimit) {
this.scrolledAll = true;
} else {
this.scrolledAllDown = true;
}
}
4 years ago
4 years ago
//this.chatInner.innerHTML = '';
4 years ago
4 years ago
let history = result.history.slice();
if(reverse) history.reverse();
4 years ago
4 years ago
//console.time('render history');
4 years ago
4 years ago
if(!isBackLimit) {
this.scrollPosition.prepareFor(reverse ? 'up' : 'down');
}
if(testScroll) {
for(let i = 0; i < 25; ++i) history.forEachReverse((msgID: number) => {
let message = appMessagesManager.getMessage(msgID);
this.renderMessage(message, reverse, true);
});
} else {
history.forEachReverse((msgID: number) => {
let message = appMessagesManager.getMessage(msgID);
this.renderMessage(message, reverse, true);
});
}
4 years ago
4 years ago
if(!isBackLimit) {
this.scrollPosition.restore();
}
4 years ago
4 years ago
//console.timeEnd('render history');
4 years ago
4 years ago
this.getHistoryPromise = undefined;
4 years ago
4 years ago
//console.timeEnd('render history total');
4 years ago
4 years ago
return true;
});
}
4 years ago
public setMutedState(muted = false) {
appSidebarRight.profileElements.notificationsCheckbox.checked = !muted;
appSidebarRight.profileElements.notificationsStatus.innerText = muted ? 'Disabled' : 'Enabled';
4 years ago
let peerID = this.peerID;
4 years ago
this.muted = muted;
if(peerID < 0) { // not human
let isChannel = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID);
if(isChannel) {
this.btnMute.classList.remove('tgico-mute', 'tgico-unmute');
this.btnMute.classList.add(muted ? 'tgico-unmute' : 'tgico-mute');
this.btnMute.style.display = '';
} else {
this.btnMute.style.display = 'none';
}
} else {
this.btnMute.style.display = 'none';
}
4 years ago
this.btnMenuMute.classList.remove('tgico-mute', 'tgico-unmute');
this.btnMenuMute.classList.add(muted ? 'tgico-unmute' : 'tgico-mute');
let rp = this.btnMenuMute.firstElementChild;
this.btnMenuMute.innerText = muted ? 'Unmute' : 'Mute';
this.btnMenuMute.appendChild(rp);
}
4 years ago
public mutePeer() {
let inputPeer = appPeersManager.getInputPeerByID(this.peerID);
let inputNotifyPeer = {
_: 'inputNotifyPeer',
peer: inputPeer
};
4 years ago
let settings = {
_: 'inputPeerNotifySettings',
flags: 0,
mute_until: 0
};
4 years ago
if(!this.muted) {
settings.flags |= 1 << 2;
settings.mute_until = 2147483646;
} else {
settings.flags |= 2;
}
4 years ago
apiManager.invokeApi('account.updateNotifySettings', {
peer: inputNotifyPeer,
settings: settings
}).then(res => {
this.handleUpdate({_: 'updateNotifySettings', peer: inputNotifyPeer, notify_settings: settings});
});
4 years ago
/* return apiManager.invokeApi('account.getNotifySettings', {
peer: inputNotifyPeer
}).then((settings: any) => {
settings.flags |= 2 << 1;
settings.mute_until = 2000000000; // 2147483646
4 years ago
return apiManager.invokeApi('account.updateNotifySettings', {
peer: inputNotifyPeer,
settings: Object.assign(settings, {
_: 'inputPeerNotifySettings'
})
}).then(res => {
this.log('mute result:', res);
});
}); */
}
4 years ago
public handleUpdate(update: any) {
switch(update._) {
case 'updateUserTyping':
case 'updateChatUserTyping':
4 years ago
if(this.myID == update.user_id) {
return;
}
var peerID = update._ == 'updateUserTyping' ? update.user_id : -update.chat_id;
this.typingUsers[update.user_id] = peerID;
if(!appUsersManager.hasUser(update.user_id)) {
if(update.chat_id && appChatsManager.hasChat(update.chat_id) && !appChatsManager.isChannel(update.chat_id)) {
appProfileManager.getChatFull(update.chat_id);
}
4 years ago
//return;
}
appUsersManager.forceUserOnline(update.user_id);
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
let currentPeer = this.peerID == peerID;
if(this.typingTimeouts[peerID]) clearTimeout(this.typingTimeouts[peerID]);
else if(dialog) {
appDialogsManager.setTyping(dialog, appUsersManager.getUser(update.user_id));
if(currentPeer) { // user
this.setPeerStatus();
}
4 years ago
}
this.typingTimeouts[peerID] = setTimeout(() => {
this.typingTimeouts[peerID] = 0;
delete this.typingUsers[update.user_id];
if(dialog) {
appDialogsManager.unsetTyping(dialog);
}
4 years ago
// лень просчитывать случаи
this.setPeerStatus();
}, 6000);
break;
case 'updateNotifySettings': {
let {peer, notify_settings} = update;
4 years ago
// peer was NotifyPeer
peer = peer.peer;
4 years ago
let peerID = appPeersManager.getPeerID(peer);
4 years ago
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
if(dialog) {
dialog.notify_settings = notify_settings;
}
4 years ago
if(peerID == this.peerID) {
let muted = notify_settings.mute_until ? new Date(notify_settings.mute_until * 1000) > new Date() : false;
this.setMutedState(muted);
}
4 years ago
4 years ago
/////this.log('updateNotifySettings', peerID, notify_settings);
break;
}
4 years ago
case 'updateChatPinnedMessage':
case 'updateUserPinnedMessage': {
let {id} = update;
4 years ago
4 years ago
/////this.log('updateUserPinnedMessage', update);
4 years ago
this.pinnedMsgID = id;
// hz nado li tut appMessagesIDsManager.getFullMessageID(update.max_id, channelID);
let peerID = update.user_id || -update.chat_id || -update.channel_id;
if(peerID == this.peerID) {
appMessagesManager.wrapSingleMessage(id);
}
4 years ago
break;
}
}
}
4 years ago
}
const appImManager = new AppImManager();
export default appImManager;