fix mediaviewer
mediaviewer forward chat contextmenu with rights support manually attached stickers (not upload) right sidebar fix blink & preloader minor fixes Signed-off-by: morethanwords <thanwords24@gmail.com>
This commit is contained in:
parent
f903f8a1c7
commit
842f69216d
44
package-lock.json
generated
44
package-lock.json
generated
@ -4558,8 +4558,7 @@
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
@ -4577,13 +4576,11 @@
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@ -4596,18 +4593,15 @@
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
@ -4710,8 +4704,7 @@
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
@ -4721,7 +4714,6 @@
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
@ -4734,20 +4726,17 @@
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.9.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
@ -4764,7 +4753,6 @@
|
||||
"mkdirp": {
|
||||
"version": "0.5.3",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
@ -4820,8 +4808,7 @@
|
||||
},
|
||||
"npm-normalize-package-bin": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"npm-packlist": {
|
||||
"version": "1.4.8",
|
||||
@ -4846,8 +4833,7 @@
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
@ -4857,7 +4843,6 @@
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@ -4926,8 +4911,7 @@
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@ -4957,7 +4941,6 @@
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
@ -4975,7 +4958,6 @@
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
@ -5014,13 +4996,11 @@
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -17,7 +17,9 @@ export default class LazyLoadQueue {
|
||||
|
||||
private observer: IntersectionObserver;
|
||||
|
||||
constructor(private parallelLimit = 5) {
|
||||
constructor(private parallelLimit = 5, withObserver = true) {
|
||||
if(!withObserver) return;
|
||||
|
||||
this.observer = new IntersectionObserver(entries => {
|
||||
if(this.lockPromise) return;
|
||||
|
||||
@ -41,7 +43,10 @@ export default class LazyLoadQueue {
|
||||
this.tempID--;
|
||||
this.lazyLoadMedia.length = 0;
|
||||
this.loadingMedia = 0;
|
||||
this.observer.disconnect();
|
||||
|
||||
if(this.observer) {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public length() {
|
||||
@ -103,15 +108,26 @@ export default class LazyLoadQueue {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public push(el: LazyLoadElement) {
|
||||
this.lazyLoadMedia.push(el);
|
||||
|
||||
public addElement(el: LazyLoadElement) {
|
||||
if(el.wasSeen) {
|
||||
this.processQueue(el);
|
||||
} else {
|
||||
el.wasSeen = false;
|
||||
this.observer.observe(el.div);
|
||||
|
||||
if(this.observer) {
|
||||
this.observer.observe(el.div);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public push(el: LazyLoadElement) {
|
||||
this.lazyLoadMedia.push(el);
|
||||
this.addElement(el);
|
||||
}
|
||||
|
||||
public unshift(el: LazyLoadElement) {
|
||||
this.lazyLoadMedia.unshift(el);
|
||||
this.addElement(el);
|
||||
}
|
||||
}
|
||||
|
@ -341,6 +341,13 @@ export function formatPhoneNumber(str: string) {
|
||||
return {formatted: str, country};
|
||||
}
|
||||
|
||||
export function parseMenuButtonsTo(to: {[name: string]: HTMLButtonElement}, elements: HTMLCollection) {
|
||||
Array.from(elements).forEach(el => {
|
||||
let name = el.className.match(/ menu-(.+?) /)[1];
|
||||
to[name] = el as HTMLButtonElement;
|
||||
});
|
||||
}
|
||||
|
||||
let onMouseMove = (e: MouseEvent) => {
|
||||
let rect = openedMenu.getBoundingClientRect();
|
||||
let {clientX, clientY} = e;
|
||||
|
92
src/components/popup.ts
Normal file
92
src/components/popup.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import AvatarElement from "./avatar";
|
||||
import { ripple } from "./misc";
|
||||
|
||||
export class PopupElement {
|
||||
protected element = document.createElement('div');
|
||||
protected container = document.createElement('div');
|
||||
protected header = document.createElement('div');
|
||||
protected title = document.createElement('div');
|
||||
|
||||
constructor(className: string) {
|
||||
this.element.classList.add('popup');
|
||||
this.element.className = 'popup' + (className ? ' ' + className : '');
|
||||
this.container.classList.add('popup-container', 'z-depth-1');
|
||||
|
||||
this.header.classList.add('popup-header');
|
||||
this.title.classList.add('popup-title');
|
||||
|
||||
this.header.append(this.title);
|
||||
this.container.append(this.header);
|
||||
this.element.append(this.container);
|
||||
}
|
||||
|
||||
public show() {
|
||||
document.body.append(this.element);
|
||||
void this.element.offsetWidth; // reflow
|
||||
this.element.classList.add('active');
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.element.classList.remove('active');
|
||||
setTimeout(() => {
|
||||
this.element.remove();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
export type PopupPeerButton = {
|
||||
text: string,
|
||||
callback?: () => void,
|
||||
isDanger?: true,
|
||||
isCancel?: true
|
||||
};
|
||||
|
||||
export class PopupPeer extends PopupElement {
|
||||
constructor(private className: string, options: Partial<{
|
||||
peerID: number,
|
||||
title: string,
|
||||
description: string,
|
||||
buttons: Array<PopupPeerButton>
|
||||
}> = {}) {
|
||||
super('popup-peer' + (className ? ' ' + className : ''));
|
||||
|
||||
let avatarEl = new AvatarElement();
|
||||
avatarEl.setAttribute('dialog', '1');
|
||||
avatarEl.setAttribute('peer', '' + options.peerID);
|
||||
avatarEl.classList.add('peer-avatar');
|
||||
|
||||
this.title.innerText = options.title || '';
|
||||
this.header.prepend(avatarEl);
|
||||
|
||||
let p = document.createElement('p');
|
||||
p.classList.add('popup-description');
|
||||
p.innerHTML = options.description;
|
||||
|
||||
let buttonsDiv = document.createElement('div');
|
||||
buttonsDiv.classList.add('popup-buttons');
|
||||
|
||||
let buttons = options.buttons.map(b => {
|
||||
let button = document.createElement('button');
|
||||
ripple(button);
|
||||
button.className = 'btn' + (b.isDanger ? ' danger' : '');
|
||||
button.innerHTML = b.text;
|
||||
|
||||
if(b.callback) {
|
||||
button.addEventListener('click', () => {
|
||||
b.callback();
|
||||
this.destroy();
|
||||
});
|
||||
} else if(b.isCancel) {
|
||||
button.addEventListener('click', () => {
|
||||
this.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
return button;
|
||||
});
|
||||
|
||||
buttonsDiv.append(...buttons);
|
||||
|
||||
this.container.append(p, buttonsDiv);
|
||||
}
|
||||
}
|
@ -364,7 +364,10 @@ export default class Scrollable {
|
||||
public scrollIntoView(element: HTMLElement, smooth = true) {
|
||||
if(element.parentElement && !this.scrollLocked) {
|
||||
let isFirstUnread = element.classList.contains('is-first-unread');
|
||||
|
||||
let offsetTop = element.getBoundingClientRect().top - this.container.getBoundingClientRect().top;
|
||||
offsetTop = this.container.scrollTop + offsetTop;
|
||||
|
||||
if(!smooth && isFirstUnread) {
|
||||
this.scrollTo(offsetTop, false);
|
||||
return;
|
||||
@ -374,7 +377,7 @@ export default class Scrollable {
|
||||
let height = element.scrollHeight;
|
||||
|
||||
let d = (clientHeight - height) / 2;
|
||||
offsetTop = this.container.scrollTop + offsetTop - d;
|
||||
offsetTop -= d;
|
||||
|
||||
this.scrollTo(offsetTop, smooth);
|
||||
}
|
||||
|
@ -796,8 +796,6 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
|
||||
//console.timeEnd('decompress sticker' + doc.id);
|
||||
|
||||
console.log('sticker json:', json);
|
||||
|
||||
let animation = await LottieLoader.loadAnimation({
|
||||
container: div,
|
||||
loop: false,
|
||||
|
@ -176,8 +176,8 @@ export class ApiUpdatesManager {
|
||||
|
||||
public getDifference() {
|
||||
// console.trace(dT(), 'Get full diff')
|
||||
let updatesState = this.updatesState;
|
||||
if (!updatesState.syncLoading) {
|
||||
const updatesState = this.updatesState;
|
||||
if(!updatesState.syncLoading) {
|
||||
updatesState.syncLoading = true;
|
||||
updatesState.pendingSeqUpdates = {};
|
||||
updatesState.pendingPtsUpdates = [];
|
||||
@ -188,7 +188,7 @@ export class ApiUpdatesManager {
|
||||
updatesState.syncPending = false;
|
||||
}
|
||||
|
||||
apiManager.invokeApi('updates.getDifference', {
|
||||
return apiManager.invokeApi('updates.getDifference', {
|
||||
pts: updatesState.pts,
|
||||
date: updatesState.date,
|
||||
qts: -1
|
||||
@ -232,7 +232,7 @@ export class ApiUpdatesManager {
|
||||
});
|
||||
});
|
||||
|
||||
var nextState = differenceResult.intermediate_state || differenceResult.state;
|
||||
const nextState = differenceResult.intermediate_state || differenceResult.state;
|
||||
updatesState.seq = nextState.seq;
|
||||
updatesState.pts = nextState.pts;
|
||||
updatesState.date = nextState.date;
|
||||
@ -515,7 +515,6 @@ export class ApiUpdatesManager {
|
||||
});
|
||||
} else {
|
||||
Object.assign(this.updatesState, state);
|
||||
this.updatesState.syncLoading = false;
|
||||
this.getDifference();
|
||||
}
|
||||
}
|
||||
|
@ -128,12 +128,10 @@ export class AppChatsManager {
|
||||
return this.chats[id] || {id: id, deleted: true, access_hash: this.channelAccess[id]};
|
||||
}
|
||||
|
||||
public hasRights(id: number, action: 'send' | 'edit_title' | 'edit_photo' | 'invite') {
|
||||
if(!(id in this.chats)) {
|
||||
return false;
|
||||
}
|
||||
public hasRights(id: number, action: 'send' | 'edit_title' | 'edit_photo' | 'invite' | 'pin' | 'deleteRevoke') {
|
||||
const chat = this.getChat(id);
|
||||
if(!chat) return false;
|
||||
|
||||
let chat = this.getChat(id);
|
||||
if(chat._ == 'chatForbidden' ||
|
||||
chat._ == 'channelForbidden' ||
|
||||
chat.pFlags.kicked ||
|
||||
@ -145,24 +143,50 @@ export class AppChatsManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
let myFlags = (chat.admin_rights || chat.banned_rights || chat.default_banned_rights)?.pFlags ?? {};
|
||||
|
||||
switch(action) {
|
||||
// good
|
||||
case 'send': {
|
||||
if(chat._ == 'channel' &&
|
||||
!chat.pFlags.megagroup &&
|
||||
!chat.pFlags.editor) {
|
||||
!chat.pFlags.megagroup &&
|
||||
!myFlags.post_messages) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// good
|
||||
case 'deleteRevoke': {
|
||||
if(chat._ == 'channel') {
|
||||
return !!myFlags.delete_messages;
|
||||
} else if(!chat.pFlags.admin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// good
|
||||
case 'pin': {
|
||||
if(chat._ == 'channel') {
|
||||
return chat.admin_rights ? !!myFlags.pin_messages || !!myFlags.post_messages : !myFlags.pin_messages;
|
||||
} else {
|
||||
if(myFlags.pin_messages && !chat.pFlags.admin) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 'edit_title':
|
||||
case 'edit_photo':
|
||||
case 'invite': {
|
||||
if(chat._ == 'channel') {
|
||||
if(chat.pFlags.megagroup) {
|
||||
if(!chat.pFlags.editor &&
|
||||
!(action == 'invite' && chat.pFlags.democracy)) {
|
||||
if(!(action == 'invite' && chat.pFlags.democracy)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@ -174,6 +198,7 @@ export class AppChatsManager {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -306,7 +331,7 @@ export class AppChatsManager {
|
||||
let chat = this.getChat(id);
|
||||
let myID = appUsersManager.getSelf().id;
|
||||
if(this.isChannel(id)) {
|
||||
let isAdmin = chat.pFlags.creator || chat.pFlags.editor || chat.pFlags.moderator;
|
||||
let isAdmin = chat.pFlags.creator;
|
||||
participants.forEach((participant) => {
|
||||
participant.canLeave = myID == participant.user_id;
|
||||
participant.canKick = isAdmin && participant._ == 'channelParticipant';
|
||||
|
@ -4,12 +4,13 @@ import appPeersManager from './appPeersManager';
|
||||
import appMessagesManager, { AppMessagesManager, Dialog } from "./appMessagesManager";
|
||||
import appUsersManager, { User } from "./appUsersManager";
|
||||
import { RichTextProcessor } from "../richtextprocessor";
|
||||
import { ripple, putPreloader, positionMenu, openBtnMenu } from "../../components/misc";
|
||||
import { ripple, putPreloader, positionMenu, openBtnMenu, parseMenuButtonsTo } from "../../components/misc";
|
||||
//import Scrollable from "../../components/scrollable";
|
||||
import Scrollable from "../../components/scrollable_new";
|
||||
import { logger } from "../polyfill";
|
||||
import appChatsManager from "./appChatsManager";
|
||||
import AvatarElement from "../../components/avatar";
|
||||
import { PopupPeerButton, PopupPeer } from "../../components/popup";
|
||||
|
||||
type DialogDom = {
|
||||
avatarEl: AvatarElement,
|
||||
@ -25,95 +26,7 @@ type DialogDom = {
|
||||
|
||||
let testScroll = false;
|
||||
|
||||
class PopupElement {
|
||||
protected element = document.createElement('div');
|
||||
protected container = document.createElement('div');
|
||||
protected header = document.createElement('div');
|
||||
protected title = document.createElement('div');
|
||||
|
||||
constructor(className: string) {
|
||||
this.element.classList.add('popup');
|
||||
this.element.className = 'popup' + (className ? ' ' + className : '');
|
||||
this.container.classList.add('popup-container', 'z-depth-1');
|
||||
|
||||
this.header.classList.add('popup-header');
|
||||
this.title.classList.add('popup-title');
|
||||
|
||||
this.header.append(this.title);
|
||||
this.container.append(this.header);
|
||||
this.element.append(this.container);
|
||||
}
|
||||
|
||||
public show() {
|
||||
document.body.append(this.element);
|
||||
void this.element.offsetWidth; // reflow
|
||||
this.element.classList.add('active');
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.element.classList.remove('active');
|
||||
setTimeout(() => {
|
||||
this.element.remove();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
type PopupPeerButton = {
|
||||
text: string,
|
||||
callback?: () => void,
|
||||
isDanger?: true,
|
||||
isCancel?: true
|
||||
};
|
||||
|
||||
class PopupPeer extends PopupElement {
|
||||
constructor(private className: string, options: Partial<{
|
||||
peerID: number,
|
||||
title: string,
|
||||
description: string,
|
||||
buttons: Array<PopupPeerButton>
|
||||
}> = {}) {
|
||||
super('popup-peer' + (className ? ' ' + className : ''));
|
||||
|
||||
let avatarEl = new AvatarElement();
|
||||
avatarEl.setAttribute('dialog', '1');
|
||||
avatarEl.setAttribute('peer', '' + options.peerID);
|
||||
avatarEl.classList.add('peer-avatar');
|
||||
|
||||
this.title.innerText = options.title || '';
|
||||
this.header.prepend(avatarEl);
|
||||
|
||||
let p = document.createElement('p');
|
||||
p.classList.add('popup-description');
|
||||
p.innerHTML = options.description;
|
||||
|
||||
let buttonsDiv = document.createElement('div');
|
||||
buttonsDiv.classList.add('popup-buttons');
|
||||
|
||||
let buttons = options.buttons.map(b => {
|
||||
let button = document.createElement('button');
|
||||
ripple(button);
|
||||
button.className = 'btn' + (b.isDanger ? ' danger' : '');
|
||||
button.innerHTML = b.text;
|
||||
|
||||
if(b.callback) {
|
||||
button.addEventListener('click', () => {
|
||||
b.callback();
|
||||
this.destroy();
|
||||
});
|
||||
} else if(b.isCancel) {
|
||||
button.addEventListener('click', () => {
|
||||
this.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
return button;
|
||||
});
|
||||
|
||||
buttonsDiv.append(...buttons);
|
||||
|
||||
this.container.append(p, buttonsDiv);
|
||||
}
|
||||
}
|
||||
|
||||
class DialogsContextMenu {
|
||||
private element = document.getElementById('dialogs-contextmenu') as HTMLDivElement;
|
||||
@ -129,11 +42,7 @@ class DialogsContextMenu {
|
||||
private peerType: 'channel' | 'chat' | 'megagroup' | 'group' | 'saved';
|
||||
|
||||
constructor(private attachTo: HTMLElement[]) {
|
||||
(Array.from(this.element.querySelectorAll('.btn-menu-item')) as HTMLElement[]).forEach(el => {
|
||||
let name = el.className.match(/ menu-(.+?) /)[1];
|
||||
// @ts-ignore
|
||||
this.buttons[name] = el;
|
||||
});
|
||||
parseMenuButtonsTo(this.buttons, this.element.children);
|
||||
|
||||
const onContextMenu = (e: MouseEvent) => {
|
||||
let li: HTMLDivElement = null;
|
||||
@ -145,13 +54,9 @@ class DialogsContextMenu {
|
||||
if(!li) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
if(this.element.classList.contains('active')) {
|
||||
/* this.element.classList.remove('active');
|
||||
this.element.parentElement.classList.remove('menu-open'); */
|
||||
return false;
|
||||
}
|
||||
|
||||
e.cancelBubble = true;
|
||||
|
||||
this.selectedID = +li.getAttribute('data-peerID');
|
||||
|
@ -85,10 +85,6 @@ class AppDocsManager {
|
||||
if(/* apiDoc.thumbs && */apiDoc.mime_type == 'image/webp') {
|
||||
apiDoc.type = 'sticker';
|
||||
apiDoc.sticker = 1;
|
||||
} else if(apiDoc.mime_type == 'application/x-tgsticker') {
|
||||
apiDoc.type = 'sticker';
|
||||
apiDoc.animated = true;
|
||||
apiDoc.sticker = 2;
|
||||
}
|
||||
break;
|
||||
|
||||
@ -134,6 +130,12 @@ class AppDocsManager {
|
||||
if(!apiDoc.file_name) {
|
||||
apiDoc.file_name = '';
|
||||
}
|
||||
|
||||
if(apiDoc.mime_type == 'application/x-tgsticker' && apiDoc.file_name == "AnimatedSticker.tgs") {
|
||||
apiDoc.type = 'sticker';
|
||||
apiDoc.animated = true;
|
||||
apiDoc.sticker = 2;
|
||||
}
|
||||
|
||||
if(apiDoc._ == 'documentEmpty') {
|
||||
apiDoc.size = 0;
|
||||
|
@ -15,11 +15,10 @@ import lottieLoader from "../lottieLoader";
|
||||
import appMediaViewer from "./appMediaViewer";
|
||||
import appSidebarLeft from "./appSidebarLeft";
|
||||
import appChatsManager from "./appChatsManager";
|
||||
import appMessagesIDsManager from "./appMessagesIDsManager";
|
||||
import apiUpdatesManager from './apiUpdatesManager';
|
||||
import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, wrapAlbum, wrapPoll } from '../../components/wrappers';
|
||||
import ProgressivePreloader from '../../components/preloader';
|
||||
import { openBtnMenu, formatPhoneNumber, positionMenu, ripple } from '../../components/misc';
|
||||
import { openBtnMenu, formatPhoneNumber, positionMenu, ripple, parseMenuButtonsTo } from '../../components/misc';
|
||||
import { ChatInput } from '../../components/chatInput';
|
||||
//import Scrollable from '../../components/scrollable';
|
||||
import Scrollable from '../../components/scrollable_new';
|
||||
@ -31,6 +30,7 @@ import appStickersManager from './appStickersManager';
|
||||
import AvatarElement from '../../components/avatar';
|
||||
import appInlineBotsManager from './AppInlineBotsManager';
|
||||
import StickyIntersector from '../../components/stickyIntersector';
|
||||
import { PopupPeerButton, PopupPeer } from '../../components/popup';
|
||||
|
||||
console.log('appImManager included!');
|
||||
|
||||
@ -40,6 +40,172 @@ let testScroll = false;
|
||||
|
||||
const IGNOREACTIONS = ['messageActionChannelMigrateFrom'];
|
||||
|
||||
class ChatContextMenu {
|
||||
private element = document.getElementById('bubble-contextmenu') as HTMLDivElement;
|
||||
private buttons: {
|
||||
reply: HTMLButtonElement,
|
||||
edit: HTMLButtonElement,
|
||||
copy: HTMLButtonElement,
|
||||
pin: HTMLButtonElement,
|
||||
forward: HTMLButtonElement,
|
||||
delete: HTMLButtonElement
|
||||
} = {} as any;
|
||||
public msgID: number;
|
||||
|
||||
constructor(private attachTo: HTMLElement) {
|
||||
parseMenuButtonsTo(this.buttons, this.element.children);
|
||||
|
||||
attachTo.addEventListener('contextmenu', e => {
|
||||
let bubble: HTMLDivElement = null;
|
||||
|
||||
try {
|
||||
bubble = findUpClassName(e.target, 'bubble__container');
|
||||
} catch(e) {}
|
||||
|
||||
if(!bubble) return;
|
||||
|
||||
e.preventDefault();
|
||||
if(this.element.classList.contains('active')) {
|
||||
return false;
|
||||
}
|
||||
e.cancelBubble = true;
|
||||
|
||||
bubble = bubble.parentElement as HTMLDivElement; // bc container
|
||||
|
||||
let msgID = +bubble.dataset.mid;
|
||||
if(!msgID) return;
|
||||
|
||||
let peerID = $rootScope.selectedPeerID;
|
||||
this.msgID = msgID;
|
||||
|
||||
const message = appMessagesManager.getMessage(msgID);
|
||||
|
||||
this.buttons.copy.style.display = message.message ? '' : 'none';
|
||||
|
||||
if($rootScope.myID == peerID || (peerID < 0 && appChatsManager.hasRights(-peerID, 'pin'))) {
|
||||
this.buttons.pin.style.display = '';
|
||||
} else {
|
||||
this.buttons.pin.style.display = 'none';
|
||||
}
|
||||
|
||||
this.buttons.edit.style.display = appMessagesManager.canEditMessage(msgID) ? '' : 'none';
|
||||
|
||||
let side: 'left' | 'right' = bubble.classList.contains('is-in') ? 'left' : 'right';
|
||||
positionMenu(e, this.element, side);
|
||||
openBtnMenu(this.element);
|
||||
|
||||
/////this.log('contextmenu', e, bubble, msgID, side);
|
||||
});
|
||||
|
||||
this.buttons.copy.addEventListener('click', () => {
|
||||
let message = appMessagesManager.getMessage(this.msgID);
|
||||
|
||||
let str = message ? message.message : '';
|
||||
|
||||
var textArea = document.createElement("textarea");
|
||||
textArea.value = str;
|
||||
textArea.style.position = "fixed"; //avoid scrolling to bottom
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
} catch (err) {
|
||||
console.error('Oops, unable to copy', err);
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
});
|
||||
|
||||
this.buttons.delete.addEventListener('click', () => {
|
||||
let peerID = $rootScope.selectedPeerID;
|
||||
let firstName = appPeersManager.getPeerTitle(peerID, false, true);
|
||||
|
||||
let callback = (revoke: boolean) => {
|
||||
appMessagesManager.deleteMessages([this.msgID], revoke);
|
||||
};
|
||||
|
||||
let title: string, description: string, buttons: PopupPeerButton[];
|
||||
title = 'Delete Message?';
|
||||
description = `Are you sure you want to delete this message?`;
|
||||
|
||||
if(peerID == $rootScope.myID) {
|
||||
buttons = [{
|
||||
text: 'DELETE',
|
||||
isDanger: true,
|
||||
callback: () => callback(false)
|
||||
}];
|
||||
} else {
|
||||
buttons = [{
|
||||
text: 'DELETE JUST FOR ME',
|
||||
isDanger: true,
|
||||
callback: () => callback(false)
|
||||
}];
|
||||
|
||||
if(peerID > 0) {
|
||||
buttons.push({
|
||||
text: 'DELETE FOR ME AND ' + firstName,
|
||||
isDanger: true,
|
||||
callback: () => callback(true)
|
||||
});
|
||||
} else if(appChatsManager.hasRights(-peerID, 'deleteRevoke')) {
|
||||
buttons.push({
|
||||
text: 'DELETE FOR ALL',
|
||||
isDanger: true,
|
||||
callback: () => callback(true)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
buttons.push({
|
||||
text: 'CANCEL',
|
||||
isCancel: true
|
||||
});
|
||||
|
||||
let popup = new PopupPeer('popup-delete-chat', {
|
||||
peerID: peerID,
|
||||
title: title,
|
||||
description: description,
|
||||
buttons: buttons
|
||||
});
|
||||
|
||||
popup.show();
|
||||
});
|
||||
|
||||
this.buttons.reply.addEventListener('click', () => {
|
||||
const message = appMessagesManager.getMessage(this.msgID);
|
||||
const chatInputC = appImManager.chatInputC;
|
||||
chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message);
|
||||
chatInputC.replyToMsgID = this.msgID;
|
||||
chatInputC.editMsgID = 0;
|
||||
});
|
||||
|
||||
this.buttons.forward.addEventListener('click', () => {
|
||||
appForward.init([this.msgID]);
|
||||
});
|
||||
|
||||
this.buttons.edit.addEventListener('click', () => {
|
||||
const message = appMessagesManager.getMessage(this.msgID);
|
||||
const chatInputC = appImManager.chatInputC;
|
||||
chatInputC.setTopInfo('Editing', message.message, message.message, message);
|
||||
chatInputC.replyToMsgID = 0;
|
||||
chatInputC.editMsgID = this.msgID;
|
||||
});
|
||||
|
||||
this.buttons.pin.addEventListener('click', () => {
|
||||
apiManager.invokeApi('messages.updatePinnedMessage', {
|
||||
flags: 0,
|
||||
peer: appPeersManager.getInputPeerByID($rootScope.selectedPeerID),
|
||||
id: this.msgID
|
||||
}).then(updates => {
|
||||
/////this.log('pinned updates:', updates);
|
||||
apiUpdatesManager.processUpdateMessage(updates);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class AppImManager {
|
||||
public pageEl = document.getElementById('page-chats') as HTMLDivElement;
|
||||
public btnMute = this.pageEl.querySelector('.tool-mute') as HTMLButtonElement;
|
||||
@ -58,9 +224,8 @@ export class AppImManager {
|
||||
|
||||
public myID = 0;
|
||||
public peerID = 0;
|
||||
public muted = false;
|
||||
|
||||
public bubbles: {[mid: number]: HTMLDivElement} = {};
|
||||
|
||||
public bubbles: {[mid: string]: HTMLDivElement} = {};
|
||||
public dateMessages: {[timestamp: number]: {
|
||||
div: HTMLDivElement,
|
||||
firstTimestamp: number,
|
||||
@ -94,11 +259,8 @@ export class AppImManager {
|
||||
private scrolledAll: boolean;
|
||||
private scrolledAllDown: boolean;
|
||||
|
||||
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;
|
||||
|
||||
public contextMenu = new ChatContextMenu(this.bubblesContainer);
|
||||
|
||||
private popupDeleteMessage: {
|
||||
popupEl?: HTMLDivElement,
|
||||
deleteBothBtn?: HTMLButtonElement,
|
||||
@ -510,123 +672,14 @@ export class AppImManager {
|
||||
};
|
||||
|
||||
document.body.addEventListener('keydown', onKeyDown);
|
||||
|
||||
this.bubblesContainer.addEventListener('contextmenu', e => {
|
||||
let bubble: HTMLDivElement = null;
|
||||
|
||||
try {
|
||||
bubble = findUpClassName(e.target, 'bubble__container');
|
||||
} catch(e) {}
|
||||
|
||||
if(bubble) {
|
||||
bubble = bubble.parentElement as HTMLDivElement; // bc container
|
||||
|
||||
e.preventDefault();
|
||||
e.cancelBubble = true;
|
||||
|
||||
let msgID = 0;
|
||||
for(let id in this.bubbles) {
|
||||
if(this.bubbles[id] === bubble) {
|
||||
msgID = +id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!msgID) return;
|
||||
|
||||
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';
|
||||
|
||||
this.contextMenuMsgID = msgID;
|
||||
|
||||
let side = bubble.classList.contains('is-in') ? 'left' : 'right';
|
||||
|
||||
this.contextMenuEdit.style.display = side == 'right' ? '' : 'none';
|
||||
|
||||
positionMenu(e, this.contextMenu, side as any);
|
||||
openBtnMenu(this.contextMenu);
|
||||
|
||||
/////this.log('contextmenu', e, bubble, msgID, side);
|
||||
}
|
||||
});
|
||||
|
||||
this.contextMenu.querySelector('.menu-copy').addEventListener('click', () => {
|
||||
let message = appMessagesManager.getMessage(this.contextMenuMsgID);
|
||||
|
||||
let str = message ? message.message : '';
|
||||
|
||||
var textArea = document.createElement("textarea");
|
||||
textArea.value = str;
|
||||
textArea.style.position = "fixed"; //avoid scrolling to bottom
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
} catch (err) {
|
||||
console.error('Oops, unable to copy', err);
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
});
|
||||
|
||||
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';
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
this.popupDeleteMessage.popupEl.classList.add('active');
|
||||
});
|
||||
|
||||
this.contextMenu.querySelector('.menu-reply').addEventListener('click', () => {
|
||||
let message = appMessagesManager.getMessage(this.contextMenuMsgID);
|
||||
this.chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message);
|
||||
this.chatInputC.replyToMsgID = this.contextMenuMsgID;
|
||||
this.chatInputC.editMsgID = 0;
|
||||
});
|
||||
|
||||
this.contextMenu.querySelector('.menu-forward').addEventListener('click', () => {
|
||||
appForward.init([this.contextMenuMsgID]);
|
||||
});
|
||||
|
||||
this.contextMenuEdit.addEventListener('click', () => {
|
||||
let message = appMessagesManager.getMessage(this.contextMenuMsgID);
|
||||
this.chatInputC.setTopInfo('Editing', message.message, message.message, message);
|
||||
this.chatInputC.replyToMsgID = 0;
|
||||
this.chatInputC.editMsgID = this.contextMenuMsgID;
|
||||
});
|
||||
|
||||
this.contextMenuPin.addEventListener('click', () => {
|
||||
apiManager.invokeApi('messages.updatePinnedMessage', {
|
||||
flags: 0,
|
||||
peer: appPeersManager.getInputPeerByID(this.peerID),
|
||||
id: this.contextMenuMsgID
|
||||
}).then(updates => {
|
||||
/////this.log('pinned updates:', updates);
|
||||
apiUpdatesManager.processUpdateMessage(updates);
|
||||
});
|
||||
});
|
||||
|
||||
this.popupDeleteMessage.deleteBothBtn.addEventListener('click', () => {
|
||||
this.deleteMessages(true);
|
||||
appMessagesManager.deleteMessages([this.contextMenu.msgID], true);
|
||||
this.popupDeleteMessage.cancelBtn.click();
|
||||
});
|
||||
|
||||
this.popupDeleteMessage.deleteMeBtn.addEventListener('click', () => {
|
||||
this.deleteMessages(false);
|
||||
appMessagesManager.deleteMessages([this.contextMenu.msgID], false);
|
||||
this.popupDeleteMessage.cancelBtn.click();
|
||||
});
|
||||
|
||||
@ -690,7 +743,7 @@ export class AppImManager {
|
||||
} */
|
||||
|
||||
//appMessagesManager.readMessages(readed);
|
||||
/* false && */appMessagesManager.readHistory(this.peerID, max, length).catch((err: any) => {
|
||||
/* false && */ appMessagesManager.readHistory(this.peerID, max, length).catch((err: any) => {
|
||||
this.log.error('readHistory err:', err);
|
||||
appMessagesManager.readHistory(this.peerID, max, length);
|
||||
});
|
||||
@ -698,36 +751,6 @@ export class AppImManager {
|
||||
});
|
||||
}
|
||||
|
||||
public deleteMessages(revoke = false) {
|
||||
let flags = revoke ? 1 : 0;
|
||||
let ids = [this.contextMenuMsgID];
|
||||
|
||||
apiManager.invokeApi('messages.deleteMessages', {
|
||||
flags: flags,
|
||||
revoke: revoke,
|
||||
id: ids
|
||||
}).then((affectedMessages: any) => {
|
||||
/////this.log('deleted messages:', affectedMessages);
|
||||
|
||||
apiUpdatesManager.processUpdateMessage({
|
||||
_: 'updateShort',
|
||||
update: {
|
||||
_: 'updatePts',
|
||||
pts: affectedMessages.pts,
|
||||
pts_count: affectedMessages.pts_count
|
||||
}
|
||||
});
|
||||
|
||||
apiUpdatesManager.processUpdateMessage({
|
||||
_: 'updateShort',
|
||||
update: {
|
||||
_: 'updateDeleteMessages',
|
||||
messages: ids
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public updateStatus() {
|
||||
if(!this.myID) return Promise.resolve();
|
||||
|
||||
@ -897,8 +920,7 @@ export class AppImManager {
|
||||
////console.time('appImManager cleanup');
|
||||
this.scrolledAll = false;
|
||||
this.scrolledAllDown = false;
|
||||
this.muted = false;
|
||||
|
||||
|
||||
this.bubbles = {};
|
||||
this.dateMessages = {};
|
||||
this.bubbleGroups.cleanup();
|
||||
@ -951,15 +973,12 @@ export class AppImManager {
|
||||
const samePeer = this.peerID == peerID;
|
||||
|
||||
if(this.setPeerPromise && samePeer) return this.setPeerPromise;
|
||||
|
||||
/* if(lastMsgID) {
|
||||
appMessagesManager.readHistory(peerID, lastMsgID); // lol
|
||||
} */
|
||||
|
||||
const dialog = appMessagesManager.getDialogByPeerID(peerID)[0] || null;
|
||||
const topMessage = lastMsgID <= 0 ? lastMsgID : dialog?.top_message ?? 0;
|
||||
if(lastMsgID === undefined && dialog) {
|
||||
if(dialog.unread_count) {
|
||||
const isTarget = lastMsgID !== undefined;
|
||||
if(!isTarget && dialog) {
|
||||
if(dialog.unread_count && !samePeer) {
|
||||
lastMsgID = dialog.read_inbox_max_id;
|
||||
} else {
|
||||
lastMsgID = dialog.top_message;
|
||||
@ -971,7 +990,7 @@ export class AppImManager {
|
||||
if(dialog && lastMsgID == topMessage) {
|
||||
this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight);
|
||||
this.scroll.scrollTop = this.scroll.scrollHeight;
|
||||
} else {
|
||||
} else if(isTarget) {
|
||||
this.scrollable.scrollIntoView(this.bubbles[lastMsgID]);
|
||||
this.highlightBubble(this.bubbles[lastMsgID]);
|
||||
}
|
||||
@ -1053,11 +1072,11 @@ export class AppImManager {
|
||||
}
|
||||
|
||||
const fromUp = maxBubbleID > 0 && (maxBubbleID < lastMsgID || lastMsgID < 0);
|
||||
if(!fromUp && samePeer) {
|
||||
const forwardingUnread = dialog.read_inbox_max_id == lastMsgID;
|
||||
if(!fromUp && (samePeer || forwardingUnread)) {
|
||||
this.scrollable.scrollTop = this.scrollable.scrollHeight;
|
||||
}
|
||||
|
||||
const forwardingUnread = dialog.read_inbox_max_id == lastMsgID;
|
||||
const bubble = forwardingUnread ? (this.firstUnreadBubble || this.bubbles[lastMsgID]) : this.bubbles[lastMsgID];
|
||||
|
||||
this.scrollable.scrollIntoView(bubble, samePeer/* , fromUp */);
|
||||
@ -1075,6 +1094,12 @@ export class AppImManager {
|
||||
|
||||
this.log('scrolledAllDown:', this.scrolledAllDown);
|
||||
|
||||
if(!this.unreaded.length && dialog) { // lol
|
||||
appMessagesManager.readHistory(peerID, dialog.top_message);
|
||||
}
|
||||
|
||||
this.chatInner.classList.remove('disable-hover', 'is-scrolling'); // warning, performance!
|
||||
|
||||
//console.timeEnd('appImManager setPeer');
|
||||
|
||||
return true;
|
||||
@ -1126,6 +1151,8 @@ export class AppImManager {
|
||||
|
||||
this.pinnedMessageContainer.style.display = 'none';
|
||||
|
||||
this.btnMute.style.display = appPeersManager.isBroadcast(peerID) ? '' : 'none';
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
let title = '';
|
||||
if(this.peerID == this.myID) title = 'Saved Messages';
|
||||
@ -1642,6 +1669,8 @@ export class AppImManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isOut = our && (!message.fwd_from || this.peerID != this.myID);
|
||||
|
||||
// media
|
||||
if(messageMedia/* && messageMedia._ == 'messageMediaPhoto' */) {
|
||||
@ -1696,7 +1725,7 @@ export class AppImManager {
|
||||
boxWidth: 380,
|
||||
boxHeight: 380,
|
||||
withTail: doc.type != 'round',
|
||||
isOut: our,
|
||||
isOut: isOut,
|
||||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
middleware: null
|
||||
});
|
||||
@ -1744,7 +1773,7 @@ export class AppImManager {
|
||||
lazyLoadQueue: this.lazyLoadQueue
|
||||
});
|
||||
} else {
|
||||
wrapPhoto(photo.id, message, attachmentDiv, undefined, undefined, true, our, this.lazyLoadQueue, () => {
|
||||
wrapPhoto(photo.id, message, attachmentDiv, undefined, undefined, true, isOut, this.lazyLoadQueue, () => {
|
||||
return this.peerID == peerID;
|
||||
});
|
||||
}
|
||||
@ -1800,7 +1829,8 @@ export class AppImManager {
|
||||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
middleware: () => {
|
||||
return this.peerID == peerID;
|
||||
}
|
||||
},
|
||||
isOut
|
||||
});
|
||||
//}
|
||||
} else {
|
||||
@ -1907,7 +1937,7 @@ export class AppImManager {
|
||||
boxWidth: 380,
|
||||
boxHeight: 380,
|
||||
withTail: doc.type != 'round',
|
||||
isOut: our,
|
||||
isOut: isOut,
|
||||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
middleware: () => {
|
||||
return this.peerID == peerID;
|
||||
@ -2097,7 +2127,7 @@ export class AppImManager {
|
||||
bubble.classList.add('hide-name');
|
||||
}
|
||||
|
||||
bubble.classList.add(our && (!message.fwd_from || this.peerID != this.myID) ? 'is-out' : 'is-in');
|
||||
bubble.classList.add(isOut ? 'is-out' : 'is-in');
|
||||
if(updatePosition) {
|
||||
this.bubbleGroups.addBubble(bubble, message, reverse);
|
||||
|
||||
@ -2151,10 +2181,16 @@ export class AppImManager {
|
||||
|
||||
let realLength = this.scrollable.length;
|
||||
let previousScrollHeightMinusTop: number;
|
||||
if(realLength > 0 && reverse) {
|
||||
if(realLength > 0 && reverse) { // for safari need set when scrolling bottom too
|
||||
this.messagesQueueOnRender = () => {
|
||||
let scrollTop = this.scrollable.scrollTop;
|
||||
|
||||
previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop;
|
||||
/* if(reverse) {
|
||||
previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop;
|
||||
} else {
|
||||
previousScrollHeightMinusTop = scrollTop;
|
||||
} */
|
||||
|
||||
this.log('performHistoryResult: messagesQueueOnRender, scrollTop:', scrollTop, previousScrollHeightMinusTop);
|
||||
this.messagesQueueOnRender = undefined;
|
||||
@ -2168,8 +2204,9 @@ export class AppImManager {
|
||||
|
||||
(this.messagesQueuePromise || Promise.resolve()).then(() => {
|
||||
if(previousScrollHeightMinusTop !== undefined) {
|
||||
this.log('performHistoryResult: will set scrollTop', this.scrollable.scrollHeight, previousScrollHeightMinusTop);
|
||||
this.scrollable.scrollTop = this.scrollable.scrollHeight - previousScrollHeightMinusTop;
|
||||
const newScrollTop = reverse ? this.scrollable.scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
|
||||
this.log('performHistoryResult: will set scrollTop', this.scrollable.scrollHeight, newScrollTop, this.scrollable.container.clientHeight);
|
||||
this.scrollable.scrollTop = newScrollTop;
|
||||
}
|
||||
|
||||
resolve(true);
|
||||
@ -2186,7 +2223,7 @@ export class AppImManager {
|
||||
let peerID = this.peerID;
|
||||
|
||||
//console.time('appImManager call getHistory');
|
||||
let pageCount = this.bubblesContainer.clientHeight / 38/* * 1.25 */ | 0;
|
||||
let pageCount = appPhotosManager.windowH / 38/* * 1.25 */ | 0;
|
||||
//let loadCount = Object.keys(this.bubbles).length > 0 ? 50 : pageCount;
|
||||
let realLoadCount = Object.keys(this.bubbles).length > 0 ? Math.max(40, pageCount) : pageCount;//let realLoadCount = 50;
|
||||
let loadCount = realLoadCount;
|
||||
@ -2215,7 +2252,7 @@ export class AppImManager {
|
||||
if(result instanceof Promise) {
|
||||
cached = false;
|
||||
promise = result.then((result) => {
|
||||
this.log('getHistory result by maxID:', maxID, reverse, isBackLimit, result);
|
||||
this.log('getHistory not cached result by maxID:', maxID, reverse, isBackLimit, result, peerID);
|
||||
|
||||
//console.timeEnd('appImManager call getHistory');
|
||||
|
||||
@ -2234,8 +2271,8 @@ export class AppImManager {
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
this.log('getHistory result by maxID:', maxID, reverse, isBackLimit, result);
|
||||
cached = true;
|
||||
this.log('getHistory cached result by maxID:', maxID, reverse, isBackLimit, result, peerID);
|
||||
promise = this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID);
|
||||
//return (reverse ? this.getHistoryTopPromise = promise : this.getHistoryBottomPromise = promise);
|
||||
//return this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true);
|
||||
@ -2292,7 +2329,7 @@ export class AppImManager {
|
||||
if(!dialog?.unread_count) return;
|
||||
|
||||
let maxID = dialog.read_inbox_max_id;
|
||||
maxID = Object.keys(this.bubbles).map(i => +i).sort((a, b) => a - b).find(i => i > maxID);
|
||||
maxID = Object.keys(this.bubbles).filter(mid => !this.bubbles[mid].classList.contains('is-out')).map(i => +i).sort((a, b) => a - b).find(i => i > maxID);
|
||||
|
||||
if(maxID && this.bubbles[maxID]) {
|
||||
let bubble = this.bubbles[maxID];
|
||||
@ -2313,7 +2350,7 @@ export class AppImManager {
|
||||
for(let i in this.dateMessages) {
|
||||
let dateMessage = this.dateMessages[i];
|
||||
|
||||
if(dateMessage.container.childElementCount == 1) { // only date div
|
||||
if(dateMessage.container.childElementCount == 2) { // only date div + sentinel div
|
||||
dateMessage.container.remove();
|
||||
this.stickyIntersector.unobserve(dateMessage.container, dateMessage.div);
|
||||
delete this.dateMessages[i];
|
||||
@ -2324,19 +2361,11 @@ export class AppImManager {
|
||||
public setMutedState(muted = false) {
|
||||
appSidebarRight.profileElements.notificationsCheckbox.checked = !muted;
|
||||
appSidebarRight.profileElements.notificationsStatus.innerText = muted ? 'Disabled' : 'Enabled';
|
||||
|
||||
let peerID = this.peerID;
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
if(appPeersManager.isBroadcast(this.peerID)) { // not human
|
||||
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';
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import appDocsManager from "./appDocsManager";
|
||||
import VideoPlayer from "../mediaPlayer";
|
||||
import { renderImageFromUrl } from "../../components/misc";
|
||||
import AvatarElement from "../../components/avatar";
|
||||
import LazyLoadQueue from "../../components/lazyLoadQueue";
|
||||
import appForward from "../../components/appForward";
|
||||
|
||||
export class AppMediaViewer {
|
||||
private overlaysDiv = document.querySelector('.overlays') as HTMLDivElement;
|
||||
@ -59,10 +61,15 @@ export class AppMediaViewer {
|
||||
private needLoadMore = true;
|
||||
|
||||
private pageEl = document.getElementById('page-chats') as HTMLDivElement;
|
||||
|
||||
private setMoverPromise: Promise<void>;
|
||||
|
||||
private lazyLoadQueue: LazyLoadQueue;
|
||||
|
||||
constructor() {
|
||||
this.log = logger('AMV');
|
||||
this.preloader = new ProgressivePreloader();
|
||||
this.lazyLoadQueue = new LazyLoadQueue(5, false);
|
||||
|
||||
this.onKeyDownBinded = this.onKeyDown.bind(this);
|
||||
|
||||
@ -75,6 +82,7 @@ export class AppMediaViewer {
|
||||
|
||||
this.peerID = 0;
|
||||
this.currentMessageID = 0;
|
||||
this.lazyLoadQueue.clear();
|
||||
|
||||
this.setMoverToTarget(this.lastTarget, true);
|
||||
|
||||
@ -88,6 +96,8 @@ export class AppMediaViewer {
|
||||
});
|
||||
|
||||
this.buttons.prev.addEventListener('click', () => {
|
||||
if(this.setMoverPromise) return;
|
||||
|
||||
let target = this.prevTargets.pop();
|
||||
if(target) {
|
||||
this.nextTargets.unshift({element: this.lastTarget, mid: this.currentMessageID});
|
||||
@ -98,6 +108,8 @@ export class AppMediaViewer {
|
||||
});
|
||||
|
||||
this.buttons.next.addEventListener('click', () => {
|
||||
if(this.setMoverPromise) return;
|
||||
|
||||
let target = this.nextTargets.shift();
|
||||
if(target) {
|
||||
this.prevTargets.push({element: this.lastTarget, mid: this.currentMessageID});
|
||||
@ -124,6 +136,10 @@ export class AppMediaViewer {
|
||||
}
|
||||
});
|
||||
|
||||
this.buttons.forward.addEventListener('click', () => {
|
||||
appForward.init([this.currentMessageID]);
|
||||
});
|
||||
|
||||
this.onClickBinded = (e: MouseEvent) => {
|
||||
let target = e.target as HTMLElement;
|
||||
|
||||
@ -155,7 +171,7 @@ export class AppMediaViewer {
|
||||
}
|
||||
}
|
||||
|
||||
public setMoverToTarget(target: HTMLElement, closing = false, fromRight = 0) {
|
||||
public async setMoverToTarget(target: HTMLElement, closing = false, fromRight = 0) {
|
||||
let mover = this.content.mover;
|
||||
|
||||
if(!closing) {
|
||||
@ -222,7 +238,7 @@ export class AppMediaViewer {
|
||||
}
|
||||
} else {
|
||||
aspecter = document.createElement('div');
|
||||
aspecter.classList.add('media-viewer-aspecter');
|
||||
aspecter.classList.add('media-viewer-aspecter', 'disable-hover');
|
||||
mover.prepend(aspecter);
|
||||
}
|
||||
|
||||
@ -257,19 +273,25 @@ export class AppMediaViewer {
|
||||
let isOut = target.classList.contains('is-out');
|
||||
|
||||
if(!closing) {
|
||||
let img: HTMLImageElement;
|
||||
let video: HTMLVideoElement;
|
||||
let mediaElement: HTMLImageElement | HTMLVideoElement;
|
||||
let src: string;
|
||||
|
||||
if(target.tagName == 'DIV') { // means backgrounded with cover
|
||||
img = new Image();
|
||||
img.src = target.style.backgroundImage.slice(5, -2);
|
||||
if(target.tagName == 'DIV') { // useContainerAsTarget
|
||||
if(target.firstElementChild) {
|
||||
mediaElement = new Image();
|
||||
src = (target.firstElementChild as HTMLImageElement).src;
|
||||
mover.append(mediaElement);
|
||||
}
|
||||
/* mediaElement = new Image();
|
||||
src = target.style.backgroundImage.slice(5, -2); */
|
||||
|
||||
} else if(target instanceof HTMLImageElement) {
|
||||
img = new Image();
|
||||
img.src = target.src;
|
||||
mediaElement = new Image();
|
||||
src = target.src;
|
||||
} else if(target instanceof HTMLVideoElement) {
|
||||
video = document.createElement('video');
|
||||
let video = mediaElement = document.createElement('video');
|
||||
let source = document.createElement('source');
|
||||
source.src = target.querySelector('source')?.src;
|
||||
src = target.querySelector('source')?.src;
|
||||
video.append(source);
|
||||
} else if(target instanceof SVGSVGElement) {
|
||||
let clipID = target.dataset.clipID;
|
||||
@ -314,23 +336,42 @@ export class AppMediaViewer {
|
||||
path.setAttributeNS(null, 'd', d);
|
||||
}
|
||||
|
||||
let mediaEl = newSvg.lastElementChild;
|
||||
mediaEl.setAttributeNS(null, 'width', '' + containerRect.width);
|
||||
mediaEl.setAttributeNS(null, 'height', '' + containerRect.height);
|
||||
let foreignObject = newSvg.lastElementChild;
|
||||
foreignObject.setAttributeNS(null, 'width', '' + containerRect.width);
|
||||
foreignObject.setAttributeNS(null, 'height', '' + containerRect.height);
|
||||
|
||||
mover.prepend(newSvg);
|
||||
}
|
||||
|
||||
if(aspecter) {
|
||||
aspecter.style.borderRadius = borderRadius;
|
||||
aspecter.append(img || video);
|
||||
aspecter.append(mediaElement);
|
||||
}
|
||||
|
||||
mediaElement = mover.querySelector('video, img');
|
||||
if(mediaElement instanceof HTMLImageElement) {
|
||||
await new Promise((resolve, reject) => {
|
||||
mediaElement.addEventListener('load', resolve);
|
||||
|
||||
if(src) {
|
||||
mediaElement.src = src;
|
||||
}
|
||||
});
|
||||
} else if(mediaElement instanceof HTMLVideoElement && mediaElement.firstElementChild && (mediaElement.firstElementChild as HTMLSourceElement).src) {
|
||||
await new Promise((resolve, reject) => {
|
||||
mediaElement.addEventListener('loadeddata', resolve);
|
||||
|
||||
if(src) {
|
||||
(mediaElement.firstElementChild as HTMLSourceElement).src = src;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mover.style.display = '';
|
||||
|
||||
setTimeout(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
mover.classList.add(wasActive ? 'moving' : 'active');
|
||||
}, 0);
|
||||
});
|
||||
} else {
|
||||
if(target instanceof SVGSVGElement) {
|
||||
path = mover.querySelector('path');
|
||||
@ -340,6 +381,10 @@ export class AppMediaViewer {
|
||||
}
|
||||
}
|
||||
|
||||
if(target.classList.contains('media-viewer-media')) {
|
||||
mover.classList.add('hiding');
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.overlaysDiv.classList.remove('active');
|
||||
}, 0);
|
||||
@ -354,46 +399,48 @@ export class AppMediaViewer {
|
||||
|
||||
setTimeout(() => {
|
||||
mover.innerHTML = '';
|
||||
mover.classList.remove('moving', 'active');
|
||||
mover.classList.remove('moving', 'active', 'hiding');
|
||||
mover.style.display = 'none';
|
||||
}, delay);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return () => {
|
||||
mover.style.transform = `translate(${containerRect.left}px,${containerRect.top}px) scale(1,1)`;
|
||||
//await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
await new Promise((resolve) => window.requestAnimationFrame(resolve));
|
||||
|
||||
if(aspecter) {
|
||||
this.setFullAspect(aspecter, containerRect, rect);
|
||||
aspecter.classList.add('disable-hover');
|
||||
mover.style.transform = `translate(${containerRect.left}px,${containerRect.top}px) scale(1,1)`;
|
||||
|
||||
if(aspecter) {
|
||||
this.setFullAspect(aspecter, containerRect, rect);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
mover.style.borderRadius = '';
|
||||
|
||||
if(mover.firstElementChild) {
|
||||
(mover.firstElementChild as HTMLElement).style.borderRadius = '';
|
||||
}
|
||||
}, delay / 2);
|
||||
|
||||
mover.dataset.timeout = '' + setTimeout(() => {
|
||||
mover.classList.remove('moving');
|
||||
|
||||
if(aspecter) { // всё из-за видео, элементы управления скейлятся, так бы можно было этого не делать
|
||||
mover.classList.remove('active');
|
||||
//aspecter.style.cssText = '';
|
||||
void mover.offsetLeft; // reflow
|
||||
|
||||
aspecter.classList.remove('disable-hover');
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
mover.style.borderRadius = '';
|
||||
mover.classList.add('active');
|
||||
delete mover.dataset.timeout;
|
||||
}, delay);
|
||||
|
||||
if(mover.firstElementChild) {
|
||||
(mover.firstElementChild as HTMLElement).style.borderRadius = '';
|
||||
}
|
||||
}, delay / 2);
|
||||
|
||||
mover.dataset.timeout = '' + setTimeout(() => {
|
||||
mover.classList.remove('moving');
|
||||
|
||||
if(aspecter) { // всё из-за видео, элементы управления скейлятся, так бы можно было этого не делать
|
||||
mover.classList.remove('active');
|
||||
aspecter.style.cssText = '';
|
||||
void mover.offsetLeft; // reflow
|
||||
|
||||
aspecter.classList.remove('disable-hover');
|
||||
}
|
||||
|
||||
mover.classList.add('active');
|
||||
delete mover.dataset.timeout;
|
||||
}, delay);
|
||||
|
||||
if(path) {
|
||||
this.sizeTailPath(path, containerRect, scaleX, delay, true, isOut, borderRadius);
|
||||
}
|
||||
};
|
||||
if(path) {
|
||||
this.sizeTailPath(path, containerRect, scaleX, delay, true, isOut, borderRadius);
|
||||
}
|
||||
}
|
||||
|
||||
private setFullAspect(aspecter: HTMLDivElement, containerRect: DOMRect, rect: DOMRect) {
|
||||
@ -415,6 +462,8 @@ export class AppMediaViewer {
|
||||
height = width * proportion;
|
||||
}
|
||||
|
||||
//this.log('will set style aspecter:', `width: ${width}px; height: ${height}px; transform: scale(${containerRect.width / width}, ${containerRect.height / height});`);
|
||||
|
||||
aspecter.style.cssText = `width: ${width}px; height: ${height}px; transform: scale(${containerRect.width / width}, ${containerRect.height / height});`;
|
||||
}
|
||||
}
|
||||
@ -581,7 +630,8 @@ export class AppMediaViewer {
|
||||
|
||||
public openMedia(message: any, target?: HTMLElement, reverse = false, targetContainer?: HTMLElement,
|
||||
prevTargets: AppMediaViewer['prevTargets'] = [], nextTargets: AppMediaViewer['prevTargets'] = [], needLoadMore = true) {
|
||||
////////this.log('openMedia doc:', message, prevTarget, nextTarget);
|
||||
if(this.setMoverPromise) return this.setMoverPromise;
|
||||
this.log('openMedia doc:', message);
|
||||
const media = message.media.photo || message.media.document || message.media.webpage.document || message.media.webpage.photo;
|
||||
|
||||
const isVideo = media.mime_type == 'video/mp4';
|
||||
@ -652,7 +702,11 @@ export class AppMediaViewer {
|
||||
this.content.caption.innerHTML = '';
|
||||
}
|
||||
|
||||
let oldAvatar = this.author.avatarEl;
|
||||
// @ts-ignore
|
||||
this.author.avatarEl = this.author.avatarEl.cloneNode();
|
||||
this.author.avatarEl.setAttribute('peer', '' + message.fromID);
|
||||
oldAvatar.parentElement.replaceChild(this.author.avatarEl, oldAvatar);
|
||||
|
||||
// ok set
|
||||
|
||||
@ -675,18 +729,17 @@ export class AppMediaViewer {
|
||||
const size = appPhotosManager.setAttachmentSize(isVideo ? media : media.id, container, maxWidth, maxHeight);
|
||||
|
||||
// need after setAttachmentSize
|
||||
if(useContainerAsTarget) {
|
||||
/* if(useContainerAsTarget) {
|
||||
target = target.querySelector('img, video') || target;
|
||||
}
|
||||
} */
|
||||
|
||||
let setMoverPromise: Promise<void>;
|
||||
if(isVideo) {
|
||||
////////this.log('will wrap video', media, size);
|
||||
|
||||
let afterTimeout = this.setMoverToTarget(target, false, fromRight);
|
||||
setMoverPromise = this.setMoverToTarget(target, false, fromRight).then(() => {
|
||||
//return; // set and don't move
|
||||
//if(wasActive) return;
|
||||
setTimeout(() => {
|
||||
afterTimeout();
|
||||
//return;
|
||||
|
||||
let video = mover.querySelector('video') || document.createElement('video');
|
||||
@ -694,6 +747,7 @@ export class AppMediaViewer {
|
||||
|
||||
if(media.type == 'gif') {
|
||||
video.autoplay = true;
|
||||
video.loop = true;
|
||||
}
|
||||
|
||||
let createPlayer = () => {
|
||||
@ -704,88 +758,112 @@ export class AppMediaViewer {
|
||||
let player = new VideoPlayer(video, true);
|
||||
/* player.wrapper.parentElement.append(video);
|
||||
mover.append(player.wrapper); */
|
||||
} else {
|
||||
video.play();
|
||||
}
|
||||
};
|
||||
|
||||
if(!source || !source.src) {
|
||||
let promise = appDocsManager.downloadDoc(media);
|
||||
this.preloader.attach(mover, true, promise);
|
||||
let load = () => {
|
||||
let promise = appDocsManager.downloadDoc(media);
|
||||
this.preloader.attach(mover, true, promise);
|
||||
|
||||
promise.then(() => {
|
||||
if(this.currentMessageID != message.mid) {
|
||||
this.log.warn('media viewer changed video');
|
||||
return;
|
||||
}
|
||||
|
||||
promise.then(() => {
|
||||
if(this.currentMessageID != message.mid) {
|
||||
this.log.warn('media viewer changed video');
|
||||
return;
|
||||
}
|
||||
|
||||
let url = media.url;
|
||||
if(target instanceof SVGSVGElement) {
|
||||
this.updateMediaSource(mover, url, 'source');
|
||||
this.updateMediaSource(target, url, 'source');
|
||||
} else {
|
||||
let div = mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover;
|
||||
let image = div.firstElementChild as HTMLImageElement;
|
||||
if(image instanceof HTMLImageElement) {
|
||||
image.remove();
|
||||
let url = media.url;
|
||||
if(target instanceof SVGSVGElement) {
|
||||
this.updateMediaSource(mover, url, 'source');
|
||||
this.updateMediaSource(target, url, 'source');
|
||||
} else {
|
||||
let div = mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover;
|
||||
let image = div.firstElementChild as HTMLImageElement;
|
||||
if(image instanceof HTMLImageElement) {
|
||||
image.remove();
|
||||
}
|
||||
|
||||
renderImageFromUrl(source, url);
|
||||
source.type = media.mime_type;
|
||||
|
||||
if(!source.parentElement) {
|
||||
video.append(source);
|
||||
}
|
||||
|
||||
if(!video.parentElement) {
|
||||
div.prepend(video);
|
||||
}
|
||||
}
|
||||
|
||||
createPlayer();
|
||||
});
|
||||
|
||||
renderImageFromUrl(source, url);
|
||||
source.type = media.mime_type;
|
||||
return promise;
|
||||
};
|
||||
|
||||
if(!source.parentElement) {
|
||||
video.append(source);
|
||||
}
|
||||
|
||||
if(!video.parentElement) {
|
||||
div.prepend(video);
|
||||
}
|
||||
}
|
||||
|
||||
createPlayer();
|
||||
this.lazyLoadQueue.unshift({
|
||||
div: null,
|
||||
load,
|
||||
wasSeen: true
|
||||
});
|
||||
} else createPlayer();
|
||||
}, 0);
|
||||
});
|
||||
} else {
|
||||
let afterTimeout = this.setMoverToTarget(target, false, fromRight);
|
||||
setMoverPromise = this.setMoverToTarget(target, false, fromRight).then(() => {
|
||||
//return; // set and don't move
|
||||
//if(wasActive) return;
|
||||
setTimeout(() => {
|
||||
afterTimeout();
|
||||
//return;
|
||||
this.preloader.attach(mover);
|
||||
|
||||
let cancellablePromise = appPhotosManager.preloadPhoto(media.id, size);
|
||||
cancellablePromise.then(() => {
|
||||
if(this.currentMessageID != message.mid) {
|
||||
this.log.warn('media viewer changed photo');
|
||||
return;
|
||||
}
|
||||
|
||||
///////this.log('indochina', blob);
|
||||
|
||||
let url = media.url;
|
||||
if(target instanceof SVGSVGElement) {
|
||||
this.updateMediaSource(target, url, 'img');
|
||||
this.updateMediaSource(mover, url, 'img');
|
||||
} else {
|
||||
let div = mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover;
|
||||
let image = div.firstElementChild as HTMLImageElement;
|
||||
if(!image || image.tagName != 'IMG') {
|
||||
image = new Image();
|
||||
|
||||
let load = () => {
|
||||
let cancellablePromise = appPhotosManager.preloadPhoto(media.id, size);
|
||||
this.preloader.attach(mover, true, cancellablePromise);
|
||||
cancellablePromise.then(() => {
|
||||
if(this.currentMessageID != message.mid) {
|
||||
this.log.warn('media viewer changed photo');
|
||||
return;
|
||||
}
|
||||
|
||||
///////this.log('indochina', blob);
|
||||
|
||||
let url = media.url;
|
||||
if(target instanceof SVGSVGElement) {
|
||||
this.updateMediaSource(target, url, 'img');
|
||||
this.updateMediaSource(mover, url, 'img');
|
||||
} else {
|
||||
let div = mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover;
|
||||
let image = div.firstElementChild as HTMLImageElement;
|
||||
if(!image || image.tagName != 'IMG') {
|
||||
image = new Image();
|
||||
}
|
||||
|
||||
//this.log('will renderImageFromUrl:', image, div, target);
|
||||
|
||||
renderImageFromUrl(image, url).then(() => {
|
||||
div.append(image);
|
||||
});
|
||||
}
|
||||
|
||||
this.preloader.detach();
|
||||
}).catch(err => {
|
||||
this.log.error(err);
|
||||
});
|
||||
|
||||
//this.log('will renderImageFromUrl:', image, div, target);
|
||||
return cancellablePromise;
|
||||
};
|
||||
|
||||
renderImageFromUrl(image, url).then(() => {
|
||||
div.append(image);
|
||||
});
|
||||
}
|
||||
|
||||
this.preloader.detach();
|
||||
}).catch(err => {
|
||||
this.log.error(err);
|
||||
this.lazyLoadQueue.unshift({
|
||||
div: null,
|
||||
load,
|
||||
wasSeen: true
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
||||
return this.setMoverPromise = setMoverPromise.then(() => {
|
||||
this.setMoverPromise = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1844,42 +1844,21 @@ export class AppMessagesManager {
|
||||
case 'messageMediaDocument':
|
||||
let document = message.media.document;
|
||||
|
||||
let found = false;
|
||||
for(let attribute of document.attributes) {
|
||||
if(found) break;
|
||||
|
||||
switch(attribute._) {
|
||||
case 'documentAttributeSticker':
|
||||
messageText += RichTextProcessor.wrapRichText(attribute.alt) + '<i>Sticker</i>';
|
||||
found = true;
|
||||
break;
|
||||
case 'documentAttributeFilename':
|
||||
messageText += '<i>' + attribute.file_name + '</i>';
|
||||
found = true;
|
||||
break;
|
||||
/* default:
|
||||
console.warn('Got unknown document type!', message);
|
||||
break; */
|
||||
}
|
||||
}
|
||||
|
||||
if(document.type == 'video') {
|
||||
messageText = '<i>Video' + (message.message ? ', ' : '') + '</i>';
|
||||
found = true;
|
||||
} else if(document.type == 'voice') {
|
||||
messageText = '<i>Voice message</i>';
|
||||
found = true;
|
||||
} else if(document.type == 'gif') {
|
||||
messageText = '<i>GIF' + (message.message ? ', ' : '') + '</i>';
|
||||
found = true;
|
||||
} else if(document.type == 'round') {
|
||||
messageText = '<i>Video message' + (message.message ? ', ' : '') + '</i>';
|
||||
found = true;
|
||||
} else if(document.type == 'sticker') {
|
||||
messageText = (document.stickerEmoji || '') + '<i>Sticker</i>';
|
||||
} else {
|
||||
messageText = '<i>' + document.file_name + '</i>';
|
||||
}
|
||||
|
||||
if(found) {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
///////console.warn('Got unknown message.media type!', message);
|
||||
@ -2537,6 +2516,77 @@ export class AppMessagesManager {
|
||||
}
|
||||
}
|
||||
|
||||
public deleteMessages(messageIDs: number[], revoke: boolean) {
|
||||
const splitted = appMessagesIDsManager.splitMessageIDsByChannels(messageIDs);
|
||||
const promises: Promise<any>[] = [];
|
||||
for(const channelIDStr in splitted.msgIDs) {
|
||||
const channelID = +channelIDStr;
|
||||
let msgIDs = splitted.msgIDs[channelID];
|
||||
|
||||
let promise: Promise<any>;
|
||||
if(channelID > 0) {
|
||||
const channel = appChatsManager.getChat(channelID);
|
||||
if(!channel.pFlags.creator && !(channel.pFlags.editor && channel.pFlags.megagroup)) {
|
||||
const goodMsgIDs: number[] = [];
|
||||
if (channel.pFlags.editor || channel.pFlags.megagroup) {
|
||||
msgIDs.forEach((msgID, i) => {
|
||||
const message = this.getMessage(splitted.mids[channelID][i]);
|
||||
if(message.pFlags.out) {
|
||||
goodMsgIDs.push(msgID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(!goodMsgIDs.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
msgIDs = goodMsgIDs;
|
||||
}
|
||||
|
||||
promise = apiManager.invokeApi('channels.deleteMessages', {
|
||||
channel: appChatsManager.getChannelInput(channelID),
|
||||
id: msgIDs
|
||||
}).then((affectedMessages) => {
|
||||
apiUpdatesManager.processUpdateMessage({
|
||||
_: 'updateShort',
|
||||
update: {
|
||||
_: 'updateDeleteChannelMessages',
|
||||
channel_id: channelID,
|
||||
messages: msgIDs,
|
||||
pts: affectedMessages.pts,
|
||||
pts_count: affectedMessages.pts_count
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
let flags = 0;
|
||||
if(revoke) {
|
||||
flags |= 1;
|
||||
}
|
||||
|
||||
promise = apiManager.invokeApi('messages.deleteMessages', {
|
||||
flags: flags,
|
||||
id: msgIDs
|
||||
}).then((affectedMessages) => {
|
||||
apiUpdatesManager.processUpdateMessage({
|
||||
_: 'updateShort',
|
||||
update: {
|
||||
_: 'updateDeleteMessages',
|
||||
messages: msgIDs,
|
||||
pts: affectedMessages.pts,
|
||||
pts_count: affectedMessages.pts_count
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
public readHistory(peerID: number, maxID = 0, readLength = 0): Promise<boolean> {
|
||||
// console.trace('start read')
|
||||
const isChannel = appPeersManager.isChannel(peerID);
|
||||
@ -2590,7 +2640,7 @@ export class AppMessagesManager {
|
||||
index = historyStorage.history.indexOf(maxID);
|
||||
}
|
||||
|
||||
let readedLength = 0;
|
||||
let readedLength = 1;
|
||||
|
||||
if(historyStorage.history.length && maxID) {
|
||||
for(let i = index == -1 ? 0 : index, length = historyStorage.history.length; i < length; i++) {
|
||||
|
@ -5,7 +5,7 @@ import appImManager from "./appImManager";
|
||||
//import apiManager from '../mtproto/apiManager';
|
||||
import apiManager from '../mtproto/mtprotoworker';
|
||||
import AppSearch, { SearchGroup } from "../../components/appSearch";
|
||||
import { horizontalMenu, putPreloader } from "../../components/misc";
|
||||
import { horizontalMenu, putPreloader, parseMenuButtonsTo } from "../../components/misc";
|
||||
import appUsersManager from "./appUsersManager";
|
||||
import Scrollable from "../../components/scrollable_new";
|
||||
import appPhotosManager from "./appPhotosManager";
|
||||
@ -273,10 +273,14 @@ class AppContactsTab implements SliderTab {
|
||||
|
||||
public onCloseAfterTimeout() {
|
||||
this.list.innerHTML = '';
|
||||
this.input.value = '';
|
||||
}
|
||||
|
||||
public openContacts(query?: string) {
|
||||
appSidebarLeft.selectTab(SLIDERITEMSIDS.contacts);
|
||||
if(appSidebarLeft.historyTabIDs.indexOf(SLIDERITEMSIDS.contacts) === -1) {
|
||||
appSidebarLeft.selectTab(SLIDERITEMSIDS.contacts);
|
||||
}
|
||||
|
||||
if(this.promise) return this.promise;
|
||||
this.scrollable.onScrolledBottom = null;
|
||||
|
||||
@ -288,6 +292,9 @@ class AppContactsTab implements SliderTab {
|
||||
return;
|
||||
}
|
||||
|
||||
contacts = contacts.slice();
|
||||
contacts.findAndSplice(u => u == $rootScope.myID);
|
||||
|
||||
let sorted = contacts
|
||||
.map(userID => {
|
||||
let user = appUsersManager.getUser(userID);
|
||||
@ -340,11 +347,7 @@ class AppSettingsTab implements SliderTab {
|
||||
} = {} as any;
|
||||
|
||||
constructor() {
|
||||
(Array.from(this.container.querySelector('.profile-buttons').children) as HTMLButtonElement[]).forEach(el => {
|
||||
let name = el.className.match(/ menu-(.+?) /)[1];
|
||||
// @ts-ignore
|
||||
this.buttons[name] = el;
|
||||
});
|
||||
parseMenuButtonsTo(this.buttons, this.container.querySelector('.profile-buttons').children);
|
||||
|
||||
$rootScope.$on('user_auth', (e: CustomEvent) => {
|
||||
this.fillElements();
|
||||
@ -569,19 +572,22 @@ class AppSidebarLeft {
|
||||
private searchInput = document.getElementById('global-search') as HTMLInputElement;
|
||||
|
||||
private menuEl = this.toolsBtn.querySelector('.btn-menu');
|
||||
private newGroupBtn = this.menuEl.querySelector('.menu-new-group');
|
||||
private contactsBtn = this.menuEl.querySelector('.menu-contacts');
|
||||
private archivedBtn = this.menuEl.querySelector('.menu-archive');
|
||||
private savedBtn = this.menuEl.querySelector('.menu-saved');
|
||||
private settingsBtn = this.menuEl.querySelector('.menu-settings');
|
||||
public archivedCount = this.archivedBtn.querySelector('.archived-count') as HTMLSpanElement;
|
||||
private buttons: {
|
||||
newGroup: HTMLButtonElement,
|
||||
contacts: HTMLButtonElement,
|
||||
archived: HTMLButtonElement,
|
||||
saved: HTMLButtonElement,
|
||||
settings: HTMLButtonElement,
|
||||
help: HTMLButtonElement
|
||||
} = {} as any;
|
||||
public archivedCount: HTMLSpanElement;
|
||||
|
||||
private newBtnMenu = this.sidebarEl.querySelector('#new-menu');
|
||||
private newButtons = {
|
||||
channel: this.newBtnMenu.querySelector('.menu-channel'),
|
||||
group: this.newBtnMenu.querySelector('.menu-group'),
|
||||
privateChat: this.newBtnMenu.querySelector('.menu-private-chat'),
|
||||
};
|
||||
private newButtons: {
|
||||
channel: HTMLButtonElement,
|
||||
group: HTMLButtonElement,
|
||||
privateChat: HTMLButtonElement,
|
||||
} = {} as any;
|
||||
|
||||
public newChannelTab = new AppNewChannelTab();
|
||||
public addMembersTab = new AppAddMembersTab();
|
||||
@ -620,7 +626,12 @@ class AppSidebarLeft {
|
||||
this.searchGroups.people.container.append(peopleContainer);
|
||||
let peopleScrollable = new Scrollable(peopleContainer, 'x');
|
||||
|
||||
this.savedBtn.addEventListener('click', (e) => {
|
||||
parseMenuButtonsTo(this.buttons, this.menuEl.children);
|
||||
parseMenuButtonsTo(this.newButtons, this.newBtnMenu.firstElementChild.children);
|
||||
|
||||
this.archivedCount = this.buttons.archived.querySelector('.archived-count') as HTMLSpanElement;
|
||||
|
||||
this.buttons.saved.addEventListener('click', (e) => {
|
||||
///////this.log('savedbtn click');
|
||||
setTimeout(() => { // menu doesn't close if no timeout (lol)
|
||||
let dom = appDialogsManager.getDialogDom(appImManager.myID);
|
||||
@ -628,15 +639,15 @@ class AppSidebarLeft {
|
||||
}, 0);
|
||||
});
|
||||
|
||||
this.archivedBtn.addEventListener('click', (e) => {
|
||||
this.buttons.archived.addEventListener('click', (e) => {
|
||||
this.selectTab(SLIDERITEMSIDS.archived);
|
||||
});
|
||||
|
||||
this.contactsBtn.addEventListener('click', (e) => {
|
||||
this.buttons.contacts.addEventListener('click', (e) => {
|
||||
this.contactsTab.openContacts();
|
||||
});
|
||||
|
||||
this.settingsBtn.addEventListener('click', () => {
|
||||
this.buttons.settings.addEventListener('click', () => {
|
||||
this.settingsTab.fillElements();
|
||||
this.selectTab(SLIDERITEMSIDS.settings);
|
||||
});
|
||||
@ -676,7 +687,7 @@ class AppSidebarLeft {
|
||||
this.selectTab(SLIDERITEMSIDS.newChannel);
|
||||
});
|
||||
|
||||
[this.newButtons.group, this.newGroupBtn].forEach(btn => {
|
||||
[this.newButtons.group, this.buttons.newGroup].forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
this.addMembersTab.init(0, 'chat', false, (peerIDs) => {
|
||||
this.newGroupTab.init(peerIDs);
|
||||
|
@ -175,7 +175,7 @@ class AppSidebarRight {
|
||||
appImManager.mutePeer(this.peerID);
|
||||
});
|
||||
|
||||
if(testScroll) {
|
||||
if(testScroll && false) {
|
||||
let div = document.createElement('div');
|
||||
for(let i = 0; i < 500; ++i) {
|
||||
//div.insertAdjacentHTML('beforeend', `<div style="background-image: url(assets/img/camomile.jpg);"></div>`);
|
||||
@ -713,9 +713,8 @@ class AppSidebarRight {
|
||||
setText(appPeersManager.getPeerUsername(peerID), this.profileElements.username);
|
||||
}
|
||||
|
||||
let dialog: any = appMessagesManager.getDialogByPeerID(peerID);
|
||||
if(dialog.length) {
|
||||
dialog = dialog[0];
|
||||
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
|
||||
if(dialog) {
|
||||
let muted = false;
|
||||
if(dialog.notify_settings && dialog.notify_settings.mute_until) {
|
||||
muted = new Date(dialog.notify_settings.mute_until * 1000) > new Date();
|
||||
|
@ -68,7 +68,7 @@ class AppStickersManager {
|
||||
}
|
||||
|
||||
//if(!this.stickerSets['emoji']) {
|
||||
this.getStickerSet({id: 'emoji', access_hash: ''});
|
||||
this.getStickerSet({id: 'emoji', access_hash: ''}, {overwrite: true});
|
||||
//}
|
||||
});
|
||||
}
|
||||
@ -95,8 +95,10 @@ class AppStickersManager {
|
||||
public async getStickerSet(set: {
|
||||
id: string,
|
||||
access_hash: string
|
||||
}) {
|
||||
if(this.stickerSets[set.id]) return this.stickerSets[set.id];
|
||||
}, params: Partial<{
|
||||
overwrite: boolean
|
||||
}> = {}) {
|
||||
if(this.stickerSets[set.id] && !params.overwrite) return this.stickerSets[set.id];
|
||||
|
||||
let promise = apiManager.invokeApi('messages.getStickerSet', {
|
||||
stickerset: set.id == 'emoji' ? {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { isInDOM } from "./utils";
|
||||
//import { isInDOM } from "./utils";
|
||||
import LottiePlayer, { AnimationConfigWithPath, AnimationConfigWithData, AnimationItem } from "lottie-web/build/player/lottie.d";
|
||||
|
||||
let convert = (value: number) => {
|
||||
@ -85,7 +85,7 @@ class LottieLoader {
|
||||
for(let i = length - 1; i >= 0; --i) {
|
||||
let {animation, container, paused, autoplay, canvas} = animations[i];
|
||||
|
||||
if(destroy && !isInDOM(container)) {
|
||||
if(destroy && !container.parentElement/* !isInDOM(container) */) {
|
||||
this.debug && console.log('destroy animation');
|
||||
animation.destroy();
|
||||
animations.splice(i, 1);
|
||||
@ -140,7 +140,7 @@ class LottieLoader {
|
||||
k[2] = (foundReplacement[1] & 255) / 255;
|
||||
}
|
||||
|
||||
console.log('foundReplacement!', foundReplacement, color.toString(16), k);
|
||||
//console.log('foundReplacement!', foundReplacement, color.toString(16), k);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -206,7 +206,7 @@ let onFirstMount = () => {
|
||||
putPreloader(this);
|
||||
//this.innerHTML = 'PLEASE WAIT...';
|
||||
|
||||
return;
|
||||
//return;
|
||||
|
||||
let phone_number = telEl.value;
|
||||
apiManager.invokeApi('auth.sendCode', {
|
||||
|
@ -74,49 +74,39 @@ $move-duration: .35s;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.media-viewer-stub {
|
||||
flex: 1;
|
||||
}
|
||||
&-stub {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.media-viewer-container {
|
||||
align-self: center;
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
&-container {
|
||||
align-self: center;
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.media-viewer-media {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
visibility: hidden;
|
||||
}
|
||||
&-media {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
img, video {
|
||||
max-height: calc(100vh - 100px);
|
||||
max-width: calc(100vw - 16px);
|
||||
/* max-height: 720px;
|
||||
max-width: 1280px; */
|
||||
}
|
||||
&-caption {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
color: $color-gray;
|
||||
transition: $open-duration;
|
||||
max-width: 50vw;
|
||||
word-break: break-word;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.media-viewer-caption {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
color: $color-gray;
|
||||
transition: $open-duration;
|
||||
max-width: 50vw;
|
||||
word-break: break-word;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,6 +178,7 @@ $move-duration: .35s;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
object-fit: cover;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.active {
|
||||
@ -197,6 +188,13 @@ $move-duration: .35s;
|
||||
&.moving {
|
||||
transition: $move-duration transform ease;
|
||||
}
|
||||
|
||||
&.hiding {
|
||||
img, video {
|
||||
transition: $open-duration opacity;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// возможно тут это вообще не нужно
|
||||
@ -205,6 +203,7 @@ $move-duration: .35s;
|
||||
height: 100%;
|
||||
transform: scale(1);
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&-mover.active &-aspecter {
|
||||
|
@ -30,6 +30,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
#forward-container {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.sidebar-search {
|
||||
display: none;
|
||||
|
||||
@ -41,7 +45,7 @@
|
||||
|
||||
.profile {
|
||||
&-content {
|
||||
flex: 1 1 auto;
|
||||
/* flex: 1 1 auto; */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* height: 100%; */
|
||||
@ -54,7 +58,7 @@
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
flex: 0 0 auto;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 36px;
|
||||
@ -64,16 +68,18 @@
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
//overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
//height: 1%; // fix safari
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
min-height: calc(100vh - 100% - 60px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* &.loaded { // warning
|
||||
.profile-tabs-content {
|
||||
position: relative;
|
||||
min-height: auto;
|
||||
}
|
||||
} */
|
||||
&-container {
|
||||
> .scrollable {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,12 +165,13 @@
|
||||
position: -webkit-sticky !important;
|
||||
position: sticky !important;
|
||||
top: 0;
|
||||
z-index: 3;
|
||||
z-index: 2;
|
||||
background-color: #fff;
|
||||
|
||||
&-content {
|
||||
//min-height: 100%;
|
||||
min-height: calc(100% - 49px);
|
||||
flex: 1 1 auto;
|
||||
//position: absolute; // FIX THE SAFARI!
|
||||
//position: relative;
|
||||
/* width: 500%;
|
||||
|
@ -26,8 +26,8 @@ div.scrollable::-webkit-scrollbar-thumb {
|
||||
//position: relative;
|
||||
|
||||
//will-change: transform;
|
||||
transform: translateZ(0);
|
||||
-webkit-transform: translateZ(0);
|
||||
/* transform: translateZ(0);
|
||||
-webkit-transform: translateZ(0); */
|
||||
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
|
@ -18,6 +18,7 @@
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.125rem;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
&-description {
|
||||
|
@ -534,7 +534,7 @@ avatar-element {
|
||||
}
|
||||
|
||||
&-download {
|
||||
z-index: 2;
|
||||
z-index: 1;
|
||||
align-items: center;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
|
@ -1,7 +1,7 @@
|
||||
const path = require('path');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
let allowedIPs = ['195.66.140.39', '192.168.31.144', '127.0.0.1', '192.168.31.1', '192.168.31.192', '192.168.0.111'];
|
||||
let allowedIPs = ['195.66.140.39', '192.168.31.144', '127.0.0.1', '192.168.31.1', '192.168.31.192', '192.168.0.111', '192.168.0.105'];
|
||||
|
||||
const opts = {
|
||||
MTPROTO_WORKER: true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user