Browse Source

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>
master
morethanwords 5 years ago
parent
commit
842f69216d
  1. 44
      package-lock.json
  2. 28
      src/components/lazyLoadQueue.ts
  3. 7
      src/components/misc.ts
  4. 92
      src/components/popup.ts
  5. 5
      src/components/scrollable_new.ts
  6. 2
      src/components/wrappers.ts
  7. 9
      src/lib/appManagers/apiUpdatesManager.ts
  8. 45
      src/lib/appManagers/appChatsManager.ts
  9. 101
      src/lib/appManagers/appDialogsManager.ts
  10. 10
      src/lib/appManagers/appDocsManager.ts
  11. 399
      src/lib/appManagers/appImManager.ts
  12. 296
      src/lib/appManagers/appMediaViewer.ts
  13. 104
      src/lib/appManagers/appMessagesManager.ts
  14. 57
      src/lib/appManagers/appSidebarLeft.ts
  15. 7
      src/lib/appManagers/appSidebarRight.ts
  16. 8
      src/lib/appManagers/appStickersManager.ts
  17. 6
      src/lib/lottieLoader.ts
  18. 2
      src/pages/pageSignIn.ts
  19. 75
      src/scss/partials/_mediaViewer.scss
  20. 31
      src/scss/partials/_rightSIdebar.scss
  21. 4
      src/scss/partials/_scrollable.scss
  22. 1
      src/scss/partials/popups/_peer.scss
  23. 2
      src/scss/style.scss
  24. 2
      webpack.common.js

44
package-lock.json generated

@ -4558,8 +4558,7 @@
}, },
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@ -4577,13 +4576,11 @@
}, },
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -4596,18 +4593,15 @@
}, },
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -4710,8 +4704,7 @@
}, },
"inherits": { "inherits": {
"version": "2.0.4", "version": "2.0.4",
"bundled": true, "bundled": true
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -4721,7 +4714,6 @@
"is-fullwidth-code-point": { "is-fullwidth-code-point": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -4734,20 +4726,17 @@
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
}, },
"minimist": { "minimist": {
"version": "1.2.5", "version": "1.2.5",
"bundled": true, "bundled": true
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.9.0", "version": "2.9.0",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -4764,7 +4753,6 @@
"mkdirp": { "mkdirp": {
"version": "0.5.3", "version": "0.5.3",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"minimist": "^1.2.5" "minimist": "^1.2.5"
} }
@ -4820,8 +4808,7 @@
}, },
"npm-normalize-package-bin": { "npm-normalize-package-bin": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true
"optional": true
}, },
"npm-packlist": { "npm-packlist": {
"version": "1.4.8", "version": "1.4.8",
@ -4846,8 +4833,7 @@
}, },
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -4857,7 +4843,6 @@
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -4926,8 +4911,7 @@
}, },
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"bundled": true, "bundled": true
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -4957,7 +4941,6 @@
"string-width": { "string-width": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@ -4975,7 +4958,6 @@
"strip-ansi": { "strip-ansi": {
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@ -5014,13 +4996,11 @@
}, },
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.1.1", "version": "3.1.1",
"bundled": true, "bundled": true
"optional": true
} }
} }
}, },

28
src/components/lazyLoadQueue.ts

@ -17,7 +17,9 @@ export default class LazyLoadQueue {
private observer: IntersectionObserver; private observer: IntersectionObserver;
constructor(private parallelLimit = 5) { constructor(private parallelLimit = 5, withObserver = true) {
if(!withObserver) return;
this.observer = new IntersectionObserver(entries => { this.observer = new IntersectionObserver(entries => {
if(this.lockPromise) return; if(this.lockPromise) return;
@ -41,7 +43,10 @@ export default class LazyLoadQueue {
this.tempID--; this.tempID--;
this.lazyLoadMedia.length = 0; this.lazyLoadMedia.length = 0;
this.loadingMedia = 0; this.loadingMedia = 0;
this.observer.disconnect();
if(this.observer) {
this.observer.disconnect();
}
} }
public length() { public length() {
@ -104,14 +109,25 @@ export default class LazyLoadQueue {
} }
} }
public push(el: LazyLoadElement) { public addElement(el: LazyLoadElement) {
this.lazyLoadMedia.push(el);
if(el.wasSeen) { if(el.wasSeen) {
this.processQueue(el); this.processQueue(el);
} else { } else {
el.wasSeen = false; 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);
}
} }

7
src/components/misc.ts

@ -341,6 +341,13 @@ export function formatPhoneNumber(str: string) {
return {formatted: str, country}; 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 onMouseMove = (e: MouseEvent) => {
let rect = openedMenu.getBoundingClientRect(); let rect = openedMenu.getBoundingClientRect();
let {clientX, clientY} = e; let {clientX, clientY} = e;

92
src/components/popup.ts

@ -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);
}
}

5
src/components/scrollable_new.ts

@ -364,7 +364,10 @@ export default class Scrollable {
public scrollIntoView(element: HTMLElement, smooth = true) { public scrollIntoView(element: HTMLElement, smooth = true) {
if(element.parentElement && !this.scrollLocked) { if(element.parentElement && !this.scrollLocked) {
let isFirstUnread = element.classList.contains('is-first-unread'); let isFirstUnread = element.classList.contains('is-first-unread');
let offsetTop = element.getBoundingClientRect().top - this.container.getBoundingClientRect().top; let offsetTop = element.getBoundingClientRect().top - this.container.getBoundingClientRect().top;
offsetTop = this.container.scrollTop + offsetTop;
if(!smooth && isFirstUnread) { if(!smooth && isFirstUnread) {
this.scrollTo(offsetTop, false); this.scrollTo(offsetTop, false);
return; return;
@ -374,7 +377,7 @@ export default class Scrollable {
let height = element.scrollHeight; let height = element.scrollHeight;
let d = (clientHeight - height) / 2; let d = (clientHeight - height) / 2;
offsetTop = this.container.scrollTop + offsetTop - d; offsetTop -= d;
this.scrollTo(offsetTop, smooth); this.scrollTo(offsetTop, smooth);
} }

2
src/components/wrappers.ts

@ -796,8 +796,6 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
//console.timeEnd('decompress sticker' + doc.id); //console.timeEnd('decompress sticker' + doc.id);
console.log('sticker json:', json);
let animation = await LottieLoader.loadAnimation({ let animation = await LottieLoader.loadAnimation({
container: div, container: div,
loop: false, loop: false,

9
src/lib/appManagers/apiUpdatesManager.ts

@ -176,8 +176,8 @@ export class ApiUpdatesManager {
public getDifference() { public getDifference() {
// console.trace(dT(), 'Get full diff') // console.trace(dT(), 'Get full diff')
let updatesState = this.updatesState; const updatesState = this.updatesState;
if (!updatesState.syncLoading) { if(!updatesState.syncLoading) {
updatesState.syncLoading = true; updatesState.syncLoading = true;
updatesState.pendingSeqUpdates = {}; updatesState.pendingSeqUpdates = {};
updatesState.pendingPtsUpdates = []; updatesState.pendingPtsUpdates = [];
@ -188,7 +188,7 @@ export class ApiUpdatesManager {
updatesState.syncPending = false; updatesState.syncPending = false;
} }
apiManager.invokeApi('updates.getDifference', { return apiManager.invokeApi('updates.getDifference', {
pts: updatesState.pts, pts: updatesState.pts,
date: updatesState.date, date: updatesState.date,
qts: -1 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.seq = nextState.seq;
updatesState.pts = nextState.pts; updatesState.pts = nextState.pts;
updatesState.date = nextState.date; updatesState.date = nextState.date;
@ -515,7 +515,6 @@ export class ApiUpdatesManager {
}); });
} else { } else {
Object.assign(this.updatesState, state); Object.assign(this.updatesState, state);
this.updatesState.syncLoading = false;
this.getDifference(); this.getDifference();
} }
} }

45
src/lib/appManagers/appChatsManager.ts

@ -128,12 +128,10 @@ export class AppChatsManager {
return this.chats[id] || {id: id, deleted: true, access_hash: this.channelAccess[id]}; return this.chats[id] || {id: id, deleted: true, access_hash: this.channelAccess[id]};
} }
public hasRights(id: number, action: 'send' | 'edit_title' | 'edit_photo' | 'invite') { public hasRights(id: number, action: 'send' | 'edit_title' | 'edit_photo' | 'invite' | 'pin' | 'deleteRevoke') {
if(!(id in this.chats)) { const chat = this.getChat(id);
return false; if(!chat) return false;
}
let chat = this.getChat(id);
if(chat._ == 'chatForbidden' || if(chat._ == 'chatForbidden' ||
chat._ == 'channelForbidden' || chat._ == 'channelForbidden' ||
chat.pFlags.kicked || chat.pFlags.kicked ||
@ -145,24 +143,50 @@ export class AppChatsManager {
return true; return true;
} }
let myFlags = (chat.admin_rights || chat.banned_rights || chat.default_banned_rights)?.pFlags ?? {};
switch(action) { switch(action) {
// good
case 'send': { case 'send': {
if(chat._ == 'channel' && if(chat._ == 'channel' &&
!chat.pFlags.megagroup && !chat.pFlags.megagroup &&
!chat.pFlags.editor) { !myFlags.post_messages) {
return false; return false;
} }
break; 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_title':
case 'edit_photo': case 'edit_photo':
case 'invite': { case 'invite': {
if(chat._ == 'channel') { if(chat._ == 'channel') {
if(chat.pFlags.megagroup) { if(chat.pFlags.megagroup) {
if(!chat.pFlags.editor && if(!(action == 'invite' && chat.pFlags.democracy)) {
!(action == 'invite' && chat.pFlags.democracy)) {
return false; return false;
} }
} else { } else {
@ -174,6 +198,7 @@ export class AppChatsManager {
return false; return false;
} }
} }
break; break;
} }
} }
@ -306,7 +331,7 @@ export class AppChatsManager {
let chat = this.getChat(id); let chat = this.getChat(id);
let myID = appUsersManager.getSelf().id; let myID = appUsersManager.getSelf().id;
if(this.isChannel(id)) { if(this.isChannel(id)) {
let isAdmin = chat.pFlags.creator || chat.pFlags.editor || chat.pFlags.moderator; let isAdmin = chat.pFlags.creator;
participants.forEach((participant) => { participants.forEach((participant) => {
participant.canLeave = myID == participant.user_id; participant.canLeave = myID == participant.user_id;
participant.canKick = isAdmin && participant._ == 'channelParticipant'; participant.canKick = isAdmin && participant._ == 'channelParticipant';

101
src/lib/appManagers/appDialogsManager.ts

@ -4,12 +4,13 @@ import appPeersManager from './appPeersManager';
import appMessagesManager, { AppMessagesManager, Dialog } from "./appMessagesManager"; import appMessagesManager, { AppMessagesManager, Dialog } from "./appMessagesManager";
import appUsersManager, { User } from "./appUsersManager"; import appUsersManager, { User } from "./appUsersManager";
import { RichTextProcessor } from "../richtextprocessor"; 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";
import Scrollable from "../../components/scrollable_new"; import Scrollable from "../../components/scrollable_new";
import { logger } from "../polyfill"; import { logger } from "../polyfill";
import appChatsManager from "./appChatsManager"; import appChatsManager from "./appChatsManager";
import AvatarElement from "../../components/avatar"; import AvatarElement from "../../components/avatar";
import { PopupPeerButton, PopupPeer } from "../../components/popup";
type DialogDom = { type DialogDom = {
avatarEl: AvatarElement, avatarEl: AvatarElement,
@ -25,95 +26,7 @@ type DialogDom = {
let testScroll = false; 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 { class DialogsContextMenu {
private element = document.getElementById('dialogs-contextmenu') as HTMLDivElement; private element = document.getElementById('dialogs-contextmenu') as HTMLDivElement;
@ -129,11 +42,7 @@ class DialogsContextMenu {
private peerType: 'channel' | 'chat' | 'megagroup' | 'group' | 'saved'; private peerType: 'channel' | 'chat' | 'megagroup' | 'group' | 'saved';
constructor(private attachTo: HTMLElement[]) { constructor(private attachTo: HTMLElement[]) {
(Array.from(this.element.querySelectorAll('.btn-menu-item')) as HTMLElement[]).forEach(el => { parseMenuButtonsTo(this.buttons, this.element.children);
let name = el.className.match(/ menu-(.+?) /)[1];
// @ts-ignore
this.buttons[name] = el;
});
const onContextMenu = (e: MouseEvent) => { const onContextMenu = (e: MouseEvent) => {
let li: HTMLDivElement = null; let li: HTMLDivElement = null;
@ -145,13 +54,9 @@ class DialogsContextMenu {
if(!li) return; if(!li) return;
e.preventDefault(); e.preventDefault();
if(this.element.classList.contains('active')) { if(this.element.classList.contains('active')) {
/* this.element.classList.remove('active');
this.element.parentElement.classList.remove('menu-open'); */
return false; return false;
} }
e.cancelBubble = true; e.cancelBubble = true;
this.selectedID = +li.getAttribute('data-peerID'); this.selectedID = +li.getAttribute('data-peerID');

10
src/lib/appManagers/appDocsManager.ts

@ -85,10 +85,6 @@ class AppDocsManager {
if(/* apiDoc.thumbs && */apiDoc.mime_type == 'image/webp') { if(/* apiDoc.thumbs && */apiDoc.mime_type == 'image/webp') {
apiDoc.type = 'sticker'; apiDoc.type = 'sticker';
apiDoc.sticker = 1; apiDoc.sticker = 1;
} else if(apiDoc.mime_type == 'application/x-tgsticker') {
apiDoc.type = 'sticker';
apiDoc.animated = true;
apiDoc.sticker = 2;
} }
break; break;
@ -135,6 +131,12 @@ class AppDocsManager {
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') { if(apiDoc._ == 'documentEmpty') {
apiDoc.size = 0; apiDoc.size = 0;
} }

399
src/lib/appManagers/appImManager.ts

@ -15,11 +15,10 @@ import lottieLoader from "../lottieLoader";
import appMediaViewer from "./appMediaViewer"; import appMediaViewer from "./appMediaViewer";
import appSidebarLeft from "./appSidebarLeft"; import appSidebarLeft from "./appSidebarLeft";
import appChatsManager from "./appChatsManager"; import appChatsManager from "./appChatsManager";
import appMessagesIDsManager from "./appMessagesIDsManager";
import apiUpdatesManager from './apiUpdatesManager'; import apiUpdatesManager from './apiUpdatesManager';
import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, wrapAlbum, wrapPoll } from '../../components/wrappers'; import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, wrapAlbum, wrapPoll } from '../../components/wrappers';
import ProgressivePreloader from '../../components/preloader'; 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 { ChatInput } from '../../components/chatInput';
//import Scrollable from '../../components/scrollable'; //import Scrollable from '../../components/scrollable';
import Scrollable from '../../components/scrollable_new'; import Scrollable from '../../components/scrollable_new';
@ -31,6 +30,7 @@ import appStickersManager from './appStickersManager';
import AvatarElement from '../../components/avatar'; import AvatarElement from '../../components/avatar';
import appInlineBotsManager from './AppInlineBotsManager'; import appInlineBotsManager from './AppInlineBotsManager';
import StickyIntersector from '../../components/stickyIntersector'; import StickyIntersector from '../../components/stickyIntersector';
import { PopupPeerButton, PopupPeer } from '../../components/popup';
console.log('appImManager included!'); console.log('appImManager included!');
@ -40,6 +40,172 @@ let testScroll = false;
const IGNOREACTIONS = ['messageActionChannelMigrateFrom']; 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 { export class AppImManager {
public pageEl = document.getElementById('page-chats') as HTMLDivElement; public pageEl = document.getElementById('page-chats') as HTMLDivElement;
public btnMute = this.pageEl.querySelector('.tool-mute') as HTMLButtonElement; public btnMute = this.pageEl.querySelector('.tool-mute') as HTMLButtonElement;
@ -58,9 +224,8 @@ export class AppImManager {
public myID = 0; public myID = 0;
public peerID = 0; public peerID = 0;
public muted = false;
public bubbles: {[mid: number]: HTMLDivElement} = {}; public bubbles: {[mid: string]: HTMLDivElement} = {};
public dateMessages: {[timestamp: number]: { public dateMessages: {[timestamp: number]: {
div: HTMLDivElement, div: HTMLDivElement,
firstTimestamp: number, firstTimestamp: number,
@ -94,10 +259,7 @@ export class AppImManager {
private scrolledAll: boolean; private scrolledAll: boolean;
private scrolledAllDown: boolean; private scrolledAllDown: boolean;
public contextMenu = document.getElementById('bubble-contextmenu') as HTMLDivElement; public contextMenu = new ChatContextMenu(this.bubblesContainer);
private contextMenuPin = this.contextMenu.querySelector('.menu-pin') as HTMLDivElement;
private contextMenuEdit = this.contextMenu.querySelector('.menu-edit') as HTMLDivElement;
private contextMenuMsgID: number;
private popupDeleteMessage: { private popupDeleteMessage: {
popupEl?: HTMLDivElement, popupEl?: HTMLDivElement,
@ -511,122 +673,13 @@ export class AppImManager {
document.body.addEventListener('keydown', onKeyDown); 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.popupDeleteMessage.deleteBothBtn.addEventListener('click', () => {
this.deleteMessages(true); appMessagesManager.deleteMessages([this.contextMenu.msgID], true);
this.popupDeleteMessage.cancelBtn.click(); this.popupDeleteMessage.cancelBtn.click();
}); });
this.popupDeleteMessage.deleteMeBtn.addEventListener('click', () => { this.popupDeleteMessage.deleteMeBtn.addEventListener('click', () => {
this.deleteMessages(false); appMessagesManager.deleteMessages([this.contextMenu.msgID], false);
this.popupDeleteMessage.cancelBtn.click(); this.popupDeleteMessage.cancelBtn.click();
}); });
@ -690,7 +743,7 @@ export class AppImManager {
} */ } */
//appMessagesManager.readMessages(readed); //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); this.log.error('readHistory err:', err);
appMessagesManager.readHistory(this.peerID, max, length); 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() { public updateStatus() {
if(!this.myID) return Promise.resolve(); if(!this.myID) return Promise.resolve();
@ -897,7 +920,6 @@ export class AppImManager {
////console.time('appImManager cleanup'); ////console.time('appImManager cleanup');
this.scrolledAll = false; this.scrolledAll = false;
this.scrolledAllDown = false; this.scrolledAllDown = false;
this.muted = false;
this.bubbles = {}; this.bubbles = {};
this.dateMessages = {}; this.dateMessages = {};
@ -952,14 +974,11 @@ export class AppImManager {
if(this.setPeerPromise && samePeer) return this.setPeerPromise; if(this.setPeerPromise && samePeer) return this.setPeerPromise;
/* if(lastMsgID) {
appMessagesManager.readHistory(peerID, lastMsgID); // lol
} */
const dialog = appMessagesManager.getDialogByPeerID(peerID)[0] || null; const dialog = appMessagesManager.getDialogByPeerID(peerID)[0] || null;
const topMessage = lastMsgID <= 0 ? lastMsgID : dialog?.top_message ?? 0; const topMessage = lastMsgID <= 0 ? lastMsgID : dialog?.top_message ?? 0;
if(lastMsgID === undefined && dialog) { const isTarget = lastMsgID !== undefined;
if(dialog.unread_count) { if(!isTarget && dialog) {
if(dialog.unread_count && !samePeer) {
lastMsgID = dialog.read_inbox_max_id; lastMsgID = dialog.read_inbox_max_id;
} else { } else {
lastMsgID = dialog.top_message; lastMsgID = dialog.top_message;
@ -971,7 +990,7 @@ export class AppImManager {
if(dialog && lastMsgID == topMessage) { if(dialog && lastMsgID == topMessage) {
this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight); this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight);
this.scroll.scrollTop = this.scroll.scrollHeight; this.scroll.scrollTop = this.scroll.scrollHeight;
} else { } else if(isTarget) {
this.scrollable.scrollIntoView(this.bubbles[lastMsgID]); this.scrollable.scrollIntoView(this.bubbles[lastMsgID]);
this.highlightBubble(this.bubbles[lastMsgID]); this.highlightBubble(this.bubbles[lastMsgID]);
} }
@ -1053,11 +1072,11 @@ export class AppImManager {
} }
const fromUp = maxBubbleID > 0 && (maxBubbleID < lastMsgID || lastMsgID < 0); 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; 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]; const bubble = forwardingUnread ? (this.firstUnreadBubble || this.bubbles[lastMsgID]) : this.bubbles[lastMsgID];
this.scrollable.scrollIntoView(bubble, samePeer/* , fromUp */); this.scrollable.scrollIntoView(bubble, samePeer/* , fromUp */);
@ -1075,6 +1094,12 @@ export class AppImManager {
this.log('scrolledAllDown:', this.scrolledAllDown); 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'); //console.timeEnd('appImManager setPeer');
return true; return true;
@ -1126,6 +1151,8 @@ export class AppImManager {
this.pinnedMessageContainer.style.display = 'none'; this.pinnedMessageContainer.style.display = 'none';
this.btnMute.style.display = appPeersManager.isBroadcast(peerID) ? '' : 'none';
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
let title = ''; let title = '';
if(this.peerID == this.myID) title = 'Saved Messages'; if(this.peerID == this.myID) title = 'Saved Messages';
@ -1643,6 +1670,8 @@ export class AppImManager {
} }
} }
const isOut = our && (!message.fwd_from || this.peerID != this.myID);
// media // media
if(messageMedia/* && messageMedia._ == 'messageMediaPhoto' */) { if(messageMedia/* && messageMedia._ == 'messageMediaPhoto' */) {
let attachmentDiv = document.createElement('div'); let attachmentDiv = document.createElement('div');
@ -1696,7 +1725,7 @@ export class AppImManager {
boxWidth: 380, boxWidth: 380,
boxHeight: 380, boxHeight: 380,
withTail: doc.type != 'round', withTail: doc.type != 'round',
isOut: our, isOut: isOut,
lazyLoadQueue: this.lazyLoadQueue, lazyLoadQueue: this.lazyLoadQueue,
middleware: null middleware: null
}); });
@ -1744,7 +1773,7 @@ export class AppImManager {
lazyLoadQueue: this.lazyLoadQueue lazyLoadQueue: this.lazyLoadQueue
}); });
} else { } 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; return this.peerID == peerID;
}); });
} }
@ -1800,7 +1829,8 @@ export class AppImManager {
lazyLoadQueue: this.lazyLoadQueue, lazyLoadQueue: this.lazyLoadQueue,
middleware: () => { middleware: () => {
return this.peerID == peerID; return this.peerID == peerID;
} },
isOut
}); });
//} //}
} else { } else {
@ -1907,7 +1937,7 @@ export class AppImManager {
boxWidth: 380, boxWidth: 380,
boxHeight: 380, boxHeight: 380,
withTail: doc.type != 'round', withTail: doc.type != 'round',
isOut: our, isOut: isOut,
lazyLoadQueue: this.lazyLoadQueue, lazyLoadQueue: this.lazyLoadQueue,
middleware: () => { middleware: () => {
return this.peerID == peerID; return this.peerID == peerID;
@ -2097,7 +2127,7 @@ export class AppImManager {
bubble.classList.add('hide-name'); 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) { if(updatePosition) {
this.bubbleGroups.addBubble(bubble, message, reverse); this.bubbleGroups.addBubble(bubble, message, reverse);
@ -2151,10 +2181,16 @@ export class AppImManager {
let realLength = this.scrollable.length; let realLength = this.scrollable.length;
let previousScrollHeightMinusTop: number; let previousScrollHeightMinusTop: number;
if(realLength > 0 && reverse) { if(realLength > 0 && reverse) { // for safari need set when scrolling bottom too
this.messagesQueueOnRender = () => { this.messagesQueueOnRender = () => {
let scrollTop = this.scrollable.scrollTop; let scrollTop = this.scrollable.scrollTop;
previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop; previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop;
/* if(reverse) {
previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop;
} else {
previousScrollHeightMinusTop = scrollTop;
} */
this.log('performHistoryResult: messagesQueueOnRender, scrollTop:', scrollTop, previousScrollHeightMinusTop); this.log('performHistoryResult: messagesQueueOnRender, scrollTop:', scrollTop, previousScrollHeightMinusTop);
this.messagesQueueOnRender = undefined; this.messagesQueueOnRender = undefined;
@ -2168,8 +2204,9 @@ export class AppImManager {
(this.messagesQueuePromise || Promise.resolve()).then(() => { (this.messagesQueuePromise || Promise.resolve()).then(() => {
if(previousScrollHeightMinusTop !== undefined) { if(previousScrollHeightMinusTop !== undefined) {
this.log('performHistoryResult: will set scrollTop', this.scrollable.scrollHeight, previousScrollHeightMinusTop); const newScrollTop = reverse ? this.scrollable.scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
this.scrollable.scrollTop = this.scrollable.scrollHeight - previousScrollHeightMinusTop; this.log('performHistoryResult: will set scrollTop', this.scrollable.scrollHeight, newScrollTop, this.scrollable.container.clientHeight);
this.scrollable.scrollTop = newScrollTop;
} }
resolve(true); resolve(true);
@ -2186,7 +2223,7 @@ export class AppImManager {
let peerID = this.peerID; let peerID = this.peerID;
//console.time('appImManager call getHistory'); //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 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 realLoadCount = Object.keys(this.bubbles).length > 0 ? Math.max(40, pageCount) : pageCount;//let realLoadCount = 50;
let loadCount = realLoadCount; let loadCount = realLoadCount;
@ -2215,7 +2252,7 @@ export class AppImManager {
if(result instanceof Promise) { if(result instanceof Promise) {
cached = false; cached = false;
promise = result.then((result) => { 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'); //console.timeEnd('appImManager call getHistory');
@ -2234,8 +2271,8 @@ export class AppImManager {
return false; return false;
}); });
} else { } else {
this.log('getHistory result by maxID:', maxID, reverse, isBackLimit, result);
cached = true; cached = true;
this.log('getHistory cached result by maxID:', maxID, reverse, isBackLimit, result, peerID);
promise = this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID); promise = this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID);
//return (reverse ? this.getHistoryTopPromise = promise : this.getHistoryBottomPromise = promise); //return (reverse ? this.getHistoryTopPromise = promise : this.getHistoryBottomPromise = promise);
//return this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true); //return this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true);
@ -2292,7 +2329,7 @@ export class AppImManager {
if(!dialog?.unread_count) return; if(!dialog?.unread_count) return;
let maxID = dialog.read_inbox_max_id; 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]) { if(maxID && this.bubbles[maxID]) {
let bubble = this.bubbles[maxID]; let bubble = this.bubbles[maxID];
@ -2313,7 +2350,7 @@ export class AppImManager {
for(let i in this.dateMessages) { for(let i in this.dateMessages) {
let dateMessage = this.dateMessages[i]; 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(); dateMessage.container.remove();
this.stickyIntersector.unobserve(dateMessage.container, dateMessage.div); this.stickyIntersector.unobserve(dateMessage.container, dateMessage.div);
delete this.dateMessages[i]; delete this.dateMessages[i];
@ -2325,18 +2362,10 @@ export class AppImManager {
appSidebarRight.profileElements.notificationsCheckbox.checked = !muted; appSidebarRight.profileElements.notificationsCheckbox.checked = !muted;
appSidebarRight.profileElements.notificationsStatus.innerText = muted ? 'Disabled' : 'Enabled'; appSidebarRight.profileElements.notificationsStatus.innerText = muted ? 'Disabled' : 'Enabled';
let peerID = this.peerID; if(appPeersManager.isBroadcast(this.peerID)) { // not human
this.btnMute.classList.remove('tgico-mute', 'tgico-unmute');
this.muted = muted; this.btnMute.classList.add(muted ? 'tgico-unmute' : 'tgico-mute');
if(peerID < 0) { // not human this.btnMute.style.display = '';
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 { } else {
this.btnMute.style.display = 'none'; this.btnMute.style.display = 'none';
} }

296
src/lib/appManagers/appMediaViewer.ts

@ -9,6 +9,8 @@ import appDocsManager from "./appDocsManager";
import VideoPlayer from "../mediaPlayer"; import VideoPlayer from "../mediaPlayer";
import { renderImageFromUrl } from "../../components/misc"; import { renderImageFromUrl } from "../../components/misc";
import AvatarElement from "../../components/avatar"; import AvatarElement from "../../components/avatar";
import LazyLoadQueue from "../../components/lazyLoadQueue";
import appForward from "../../components/appForward";
export class AppMediaViewer { export class AppMediaViewer {
private overlaysDiv = document.querySelector('.overlays') as HTMLDivElement; private overlaysDiv = document.querySelector('.overlays') as HTMLDivElement;
@ -60,9 +62,14 @@ export class AppMediaViewer {
private pageEl = document.getElementById('page-chats') as HTMLDivElement; private pageEl = document.getElementById('page-chats') as HTMLDivElement;
private setMoverPromise: Promise<void>;
private lazyLoadQueue: LazyLoadQueue;
constructor() { constructor() {
this.log = logger('AMV'); this.log = logger('AMV');
this.preloader = new ProgressivePreloader(); this.preloader = new ProgressivePreloader();
this.lazyLoadQueue = new LazyLoadQueue(5, false);
this.onKeyDownBinded = this.onKeyDown.bind(this); this.onKeyDownBinded = this.onKeyDown.bind(this);
@ -75,6 +82,7 @@ export class AppMediaViewer {
this.peerID = 0; this.peerID = 0;
this.currentMessageID = 0; this.currentMessageID = 0;
this.lazyLoadQueue.clear();
this.setMoverToTarget(this.lastTarget, true); this.setMoverToTarget(this.lastTarget, true);
@ -88,6 +96,8 @@ export class AppMediaViewer {
}); });
this.buttons.prev.addEventListener('click', () => { this.buttons.prev.addEventListener('click', () => {
if(this.setMoverPromise) return;
let target = this.prevTargets.pop(); let target = this.prevTargets.pop();
if(target) { if(target) {
this.nextTargets.unshift({element: this.lastTarget, mid: this.currentMessageID}); this.nextTargets.unshift({element: this.lastTarget, mid: this.currentMessageID});
@ -98,6 +108,8 @@ export class AppMediaViewer {
}); });
this.buttons.next.addEventListener('click', () => { this.buttons.next.addEventListener('click', () => {
if(this.setMoverPromise) return;
let target = this.nextTargets.shift(); let target = this.nextTargets.shift();
if(target) { if(target) {
this.prevTargets.push({element: this.lastTarget, mid: this.currentMessageID}); 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) => { this.onClickBinded = (e: MouseEvent) => {
let target = e.target as HTMLElement; 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; let mover = this.content.mover;
if(!closing) { if(!closing) {
@ -222,7 +238,7 @@ export class AppMediaViewer {
} }
} else { } else {
aspecter = document.createElement('div'); aspecter = document.createElement('div');
aspecter.classList.add('media-viewer-aspecter'); aspecter.classList.add('media-viewer-aspecter', 'disable-hover');
mover.prepend(aspecter); mover.prepend(aspecter);
} }
@ -257,19 +273,25 @@ export class AppMediaViewer {
let isOut = target.classList.contains('is-out'); let isOut = target.classList.contains('is-out');
if(!closing) { if(!closing) {
let img: HTMLImageElement; let mediaElement: HTMLImageElement | HTMLVideoElement;
let video: HTMLVideoElement; let src: string;
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); */
if(target.tagName == 'DIV') { // means backgrounded with cover
img = new Image();
img.src = target.style.backgroundImage.slice(5, -2);
} else if(target instanceof HTMLImageElement) { } else if(target instanceof HTMLImageElement) {
img = new Image(); mediaElement = new Image();
img.src = target.src; src = target.src;
} else if(target instanceof HTMLVideoElement) { } else if(target instanceof HTMLVideoElement) {
video = document.createElement('video'); let video = mediaElement = document.createElement('video');
let source = document.createElement('source'); let source = document.createElement('source');
source.src = target.querySelector('source')?.src; src = target.querySelector('source')?.src;
video.append(source); video.append(source);
} else if(target instanceof SVGSVGElement) { } else if(target instanceof SVGSVGElement) {
let clipID = target.dataset.clipID; let clipID = target.dataset.clipID;
@ -314,23 +336,42 @@ export class AppMediaViewer {
path.setAttributeNS(null, 'd', d); path.setAttributeNS(null, 'd', d);
} }
let mediaEl = newSvg.lastElementChild; let foreignObject = newSvg.lastElementChild;
mediaEl.setAttributeNS(null, 'width', '' + containerRect.width); foreignObject.setAttributeNS(null, 'width', '' + containerRect.width);
mediaEl.setAttributeNS(null, 'height', '' + containerRect.height); foreignObject.setAttributeNS(null, 'height', '' + containerRect.height);
mover.prepend(newSvg); mover.prepend(newSvg);
} }
if(aspecter) { if(aspecter) {
aspecter.style.borderRadius = borderRadius; 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 = ''; mover.style.display = '';
setTimeout(() => { window.requestAnimationFrame(() => {
mover.classList.add(wasActive ? 'moving' : 'active'); mover.classList.add(wasActive ? 'moving' : 'active');
}, 0); });
} else { } else {
if(target instanceof SVGSVGElement) { if(target instanceof SVGSVGElement) {
path = mover.querySelector('path'); path = mover.querySelector('path');
@ -340,6 +381,10 @@ export class AppMediaViewer {
} }
} }
if(target.classList.contains('media-viewer-media')) {
mover.classList.add('hiding');
}
setTimeout(() => { setTimeout(() => {
this.overlaysDiv.classList.remove('active'); this.overlaysDiv.classList.remove('active');
}, 0); }, 0);
@ -354,46 +399,48 @@ export class AppMediaViewer {
setTimeout(() => { setTimeout(() => {
mover.innerHTML = ''; mover.innerHTML = '';
mover.classList.remove('moving', 'active'); mover.classList.remove('moving', 'active', 'hiding');
mover.style.display = 'none'; mover.style.display = 'none';
}, delay); }, delay);
}
return () => { return;
mover.style.transform = `translate(${containerRect.left}px,${containerRect.top}px) scale(1,1)`; }
if(aspecter) { //await new Promise((resolve) => setTimeout(resolve, 0));
this.setFullAspect(aspecter, containerRect, rect); await new Promise((resolve) => window.requestAnimationFrame(resolve));
aspecter.classList.add('disable-hover');
}
setTimeout(() => { mover.style.transform = `translate(${containerRect.left}px,${containerRect.top}px) scale(1,1)`;
mover.style.borderRadius = '';
if(mover.firstElementChild) { if(aspecter) {
(mover.firstElementChild as HTMLElement).style.borderRadius = ''; this.setFullAspect(aspecter, containerRect, rect);
} }
}, delay / 2);
mover.dataset.timeout = '' + setTimeout(() => { setTimeout(() => {
mover.classList.remove('moving'); mover.style.borderRadius = '';
if(aspecter) { // всё из-за видео, элементы управления скейлятся, так бы можно было этого не делать if(mover.firstElementChild) {
mover.classList.remove('active'); (mover.firstElementChild as HTMLElement).style.borderRadius = '';
aspecter.style.cssText = ''; }
void mover.offsetLeft; // reflow }, delay / 2);
aspecter.classList.remove('disable-hover'); mover.dataset.timeout = '' + setTimeout(() => {
} mover.classList.remove('moving');
mover.classList.add('active'); if(aspecter) { // всё из-за видео, элементы управления скейлятся, так бы можно было этого не делать
delete mover.dataset.timeout; mover.classList.remove('active');
}, delay); //aspecter.style.cssText = '';
void mover.offsetLeft; // reflow
if(path) { aspecter.classList.remove('disable-hover');
this.sizeTailPath(path, containerRect, scaleX, delay, true, isOut, borderRadius);
} }
};
mover.classList.add('active');
delete mover.dataset.timeout;
}, delay);
if(path) {
this.sizeTailPath(path, containerRect, scaleX, delay, true, isOut, borderRadius);
}
} }
private setFullAspect(aspecter: HTMLDivElement, containerRect: DOMRect, rect: DOMRect) { private setFullAspect(aspecter: HTMLDivElement, containerRect: DOMRect, rect: DOMRect) {
@ -415,6 +462,8 @@ export class AppMediaViewer {
height = width * proportion; 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});`; 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, public openMedia(message: any, target?: HTMLElement, reverse = false, targetContainer?: HTMLElement,
prevTargets: AppMediaViewer['prevTargets'] = [], nextTargets: AppMediaViewer['prevTargets'] = [], needLoadMore = true) { 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 media = message.media.photo || message.media.document || message.media.webpage.document || message.media.webpage.photo;
const isVideo = media.mime_type == 'video/mp4'; const isVideo = media.mime_type == 'video/mp4';
@ -652,7 +702,11 @@ export class AppMediaViewer {
this.content.caption.innerHTML = ''; 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); this.author.avatarEl.setAttribute('peer', '' + message.fromID);
oldAvatar.parentElement.replaceChild(this.author.avatarEl, oldAvatar);
// ok set // ok set
@ -675,18 +729,17 @@ export class AppMediaViewer {
const size = appPhotosManager.setAttachmentSize(isVideo ? media : media.id, container, maxWidth, maxHeight); const size = appPhotosManager.setAttachmentSize(isVideo ? media : media.id, container, maxWidth, maxHeight);
// need after setAttachmentSize // need after setAttachmentSize
if(useContainerAsTarget) { /* if(useContainerAsTarget) {
target = target.querySelector('img, video') || target; target = target.querySelector('img, video') || target;
} } */
let setMoverPromise: Promise<void>;
if(isVideo) { if(isVideo) {
////////this.log('will wrap video', media, size); ////////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 //return; // set and don't move
//if(wasActive) return; //if(wasActive) return;
setTimeout(() => {
afterTimeout();
//return; //return;
let video = mover.querySelector('video') || document.createElement('video'); let video = mover.querySelector('video') || document.createElement('video');
@ -694,6 +747,7 @@ export class AppMediaViewer {
if(media.type == 'gif') { if(media.type == 'gif') {
video.autoplay = true; video.autoplay = true;
video.loop = true;
} }
let createPlayer = () => { let createPlayer = () => {
@ -704,88 +758,112 @@ export class AppMediaViewer {
let player = new VideoPlayer(video, true); let player = new VideoPlayer(video, true);
/* player.wrapper.parentElement.append(video); /* player.wrapper.parentElement.append(video);
mover.append(player.wrapper); */ mover.append(player.wrapper); */
} else {
video.play();
} }
}; };
if(!source || !source.src) { if(!source || !source.src) {
let promise = appDocsManager.downloadDoc(media); let load = () => {
this.preloader.attach(mover, true, promise); let promise = appDocsManager.downloadDoc(media);
this.preloader.attach(mover, true, promise);
promise.then(() => {
if(this.currentMessageID != message.mid) { promise.then(() => {
this.log.warn('media viewer changed video'); if(this.currentMessageID != message.mid) {
return; 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();
} }
renderImageFromUrl(source, url); let url = media.url;
source.type = media.mime_type; if(target instanceof SVGSVGElement) {
this.updateMediaSource(mover, url, 'source');
if(!source.parentElement) { this.updateMediaSource(target, url, 'source');
video.append(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);
}
} }
if(!video.parentElement) { createPlayer();
div.prepend(video); });
}
} return promise;
};
createPlayer(); this.lazyLoadQueue.unshift({
div: null,
load,
wasSeen: true
}); });
} else createPlayer(); } else createPlayer();
}, 0); });
} else { } else {
let afterTimeout = this.setMoverToTarget(target, false, fromRight); setMoverPromise = this.setMoverToTarget(target, false, fromRight).then(() => {
//return; // set and don't move //return; // set and don't move
//if(wasActive) return; //if(wasActive) return;
setTimeout(() => {
afterTimeout();
//return; //return;
this.preloader.attach(mover);
let cancellablePromise = appPhotosManager.preloadPhoto(media.id, size); let load = () => {
cancellablePromise.then(() => { let cancellablePromise = appPhotosManager.preloadPhoto(media.id, size);
if(this.currentMessageID != message.mid) { this.preloader.attach(mover, true, cancellablePromise);
this.log.warn('media viewer changed photo'); cancellablePromise.then(() => {
return; if(this.currentMessageID != message.mid) {
} this.log.warn('media viewer changed photo');
return;
}
///////this.log('indochina', blob); ///////this.log('indochina', blob);
let url = media.url; let url = media.url;
if(target instanceof SVGSVGElement) { if(target instanceof SVGSVGElement) {
this.updateMediaSource(target, url, 'img'); this.updateMediaSource(target, url, 'img');
this.updateMediaSource(mover, url, 'img'); this.updateMediaSource(mover, url, 'img');
} else { } else {
let div = mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover; let div = mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover;
let image = div.firstElementChild as HTMLImageElement; let image = div.firstElementChild as HTMLImageElement;
if(!image || image.tagName != 'IMG') { if(!image || image.tagName != 'IMG') {
image = new Image(); image = new Image();
}
//this.log('will renderImageFromUrl:', image, div, target);
renderImageFromUrl(image, url).then(() => {
div.append(image);
});
} }
//this.log('will renderImageFromUrl:', image, div, target); this.preloader.detach();
}).catch(err => {
this.log.error(err);
});
renderImageFromUrl(image, url).then(() => { return cancellablePromise;
div.append(image); };
});
}
this.preloader.detach(); this.lazyLoadQueue.unshift({
}).catch(err => { div: null,
this.log.error(err); load,
wasSeen: true
}); });
}, 0); });
} }
return this.setMoverPromise = setMoverPromise.then(() => {
this.setMoverPromise = null;
});
} }
} }

104
src/lib/appManagers/appMessagesManager.ts

@ -1844,42 +1844,21 @@ export class AppMessagesManager {
case 'messageMediaDocument': case 'messageMediaDocument':
let document = message.media.document; 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') { if(document.type == 'video') {
messageText = '<i>Video' + (message.message ? ', ' : '') + '</i>'; messageText = '<i>Video' + (message.message ? ', ' : '') + '</i>';
found = true;
} else if(document.type == 'voice') { } else if(document.type == 'voice') {
messageText = '<i>Voice message</i>'; messageText = '<i>Voice message</i>';
found = true;
} else if(document.type == 'gif') { } else if(document.type == 'gif') {
messageText = '<i>GIF' + (message.message ? ', ' : '') + '</i>'; messageText = '<i>GIF' + (message.message ? ', ' : '') + '</i>';
found = true;
} else if(document.type == 'round') { } else if(document.type == 'round') {
messageText = '<i>Video message' + (message.message ? ', ' : '') + '</i>'; 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: default:
///////console.warn('Got unknown message.media type!', message); ///////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> { public readHistory(peerID: number, maxID = 0, readLength = 0): Promise<boolean> {
// console.trace('start read') // console.trace('start read')
const isChannel = appPeersManager.isChannel(peerID); const isChannel = appPeersManager.isChannel(peerID);
@ -2590,7 +2640,7 @@ export class AppMessagesManager {
index = historyStorage.history.indexOf(maxID); index = historyStorage.history.indexOf(maxID);
} }
let readedLength = 0; let readedLength = 1;
if(historyStorage.history.length && maxID) { if(historyStorage.history.length && maxID) {
for(let i = index == -1 ? 0 : index, length = historyStorage.history.length; i < length; i++) { for(let i = index == -1 ? 0 : index, length = historyStorage.history.length; i < length; i++) {

57
src/lib/appManagers/appSidebarLeft.ts

@ -5,7 +5,7 @@ import appImManager from "./appImManager";
//import apiManager from '../mtproto/apiManager'; //import apiManager from '../mtproto/apiManager';
import apiManager from '../mtproto/mtprotoworker'; import apiManager from '../mtproto/mtprotoworker';
import AppSearch, { SearchGroup } from "../../components/appSearch"; import AppSearch, { SearchGroup } from "../../components/appSearch";
import { horizontalMenu, putPreloader } from "../../components/misc"; import { horizontalMenu, putPreloader, parseMenuButtonsTo } from "../../components/misc";
import appUsersManager from "./appUsersManager"; import appUsersManager from "./appUsersManager";
import Scrollable from "../../components/scrollable_new"; import Scrollable from "../../components/scrollable_new";
import appPhotosManager from "./appPhotosManager"; import appPhotosManager from "./appPhotosManager";
@ -273,10 +273,14 @@ class AppContactsTab implements SliderTab {
public onCloseAfterTimeout() { public onCloseAfterTimeout() {
this.list.innerHTML = ''; this.list.innerHTML = '';
this.input.value = '';
} }
public openContacts(query?: string) { 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; if(this.promise) return this.promise;
this.scrollable.onScrolledBottom = null; this.scrollable.onScrolledBottom = null;
@ -288,6 +292,9 @@ class AppContactsTab implements SliderTab {
return; return;
} }
contacts = contacts.slice();
contacts.findAndSplice(u => u == $rootScope.myID);
let sorted = contacts let sorted = contacts
.map(userID => { .map(userID => {
let user = appUsersManager.getUser(userID); let user = appUsersManager.getUser(userID);
@ -340,11 +347,7 @@ class AppSettingsTab implements SliderTab {
} = {} as any; } = {} as any;
constructor() { constructor() {
(Array.from(this.container.querySelector('.profile-buttons').children) as HTMLButtonElement[]).forEach(el => { parseMenuButtonsTo(this.buttons, this.container.querySelector('.profile-buttons').children);
let name = el.className.match(/ menu-(.+?) /)[1];
// @ts-ignore
this.buttons[name] = el;
});
$rootScope.$on('user_auth', (e: CustomEvent) => { $rootScope.$on('user_auth', (e: CustomEvent) => {
this.fillElements(); this.fillElements();
@ -569,19 +572,22 @@ class AppSidebarLeft {
private searchInput = document.getElementById('global-search') as HTMLInputElement; private searchInput = document.getElementById('global-search') as HTMLInputElement;
private menuEl = this.toolsBtn.querySelector('.btn-menu'); private menuEl = this.toolsBtn.querySelector('.btn-menu');
private newGroupBtn = this.menuEl.querySelector('.menu-new-group'); private buttons: {
private contactsBtn = this.menuEl.querySelector('.menu-contacts'); newGroup: HTMLButtonElement,
private archivedBtn = this.menuEl.querySelector('.menu-archive'); contacts: HTMLButtonElement,
private savedBtn = this.menuEl.querySelector('.menu-saved'); archived: HTMLButtonElement,
private settingsBtn = this.menuEl.querySelector('.menu-settings'); saved: HTMLButtonElement,
public archivedCount = this.archivedBtn.querySelector('.archived-count') as HTMLSpanElement; settings: HTMLButtonElement,
help: HTMLButtonElement
} = {} as any;
public archivedCount: HTMLSpanElement;
private newBtnMenu = this.sidebarEl.querySelector('#new-menu'); private newBtnMenu = this.sidebarEl.querySelector('#new-menu');
private newButtons = { private newButtons: {
channel: this.newBtnMenu.querySelector('.menu-channel'), channel: HTMLButtonElement,
group: this.newBtnMenu.querySelector('.menu-group'), group: HTMLButtonElement,
privateChat: this.newBtnMenu.querySelector('.menu-private-chat'), privateChat: HTMLButtonElement,
}; } = {} as any;
public newChannelTab = new AppNewChannelTab(); public newChannelTab = new AppNewChannelTab();
public addMembersTab = new AppAddMembersTab(); public addMembersTab = new AppAddMembersTab();
@ -620,7 +626,12 @@ class AppSidebarLeft {
this.searchGroups.people.container.append(peopleContainer); this.searchGroups.people.container.append(peopleContainer);
let peopleScrollable = new Scrollable(peopleContainer, 'x'); 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'); ///////this.log('savedbtn click');
setTimeout(() => { // menu doesn't close if no timeout (lol) setTimeout(() => { // menu doesn't close if no timeout (lol)
let dom = appDialogsManager.getDialogDom(appImManager.myID); let dom = appDialogsManager.getDialogDom(appImManager.myID);
@ -628,15 +639,15 @@ class AppSidebarLeft {
}, 0); }, 0);
}); });
this.archivedBtn.addEventListener('click', (e) => { this.buttons.archived.addEventListener('click', (e) => {
this.selectTab(SLIDERITEMSIDS.archived); this.selectTab(SLIDERITEMSIDS.archived);
}); });
this.contactsBtn.addEventListener('click', (e) => { this.buttons.contacts.addEventListener('click', (e) => {
this.contactsTab.openContacts(); this.contactsTab.openContacts();
}); });
this.settingsBtn.addEventListener('click', () => { this.buttons.settings.addEventListener('click', () => {
this.settingsTab.fillElements(); this.settingsTab.fillElements();
this.selectTab(SLIDERITEMSIDS.settings); this.selectTab(SLIDERITEMSIDS.settings);
}); });
@ -676,7 +687,7 @@ class AppSidebarLeft {
this.selectTab(SLIDERITEMSIDS.newChannel); this.selectTab(SLIDERITEMSIDS.newChannel);
}); });
[this.newButtons.group, this.newGroupBtn].forEach(btn => { [this.newButtons.group, this.buttons.newGroup].forEach(btn => {
btn.addEventListener('click', (e) => { btn.addEventListener('click', (e) => {
this.addMembersTab.init(0, 'chat', false, (peerIDs) => { this.addMembersTab.init(0, 'chat', false, (peerIDs) => {
this.newGroupTab.init(peerIDs); this.newGroupTab.init(peerIDs);

7
src/lib/appManagers/appSidebarRight.ts

@ -175,7 +175,7 @@ class AppSidebarRight {
appImManager.mutePeer(this.peerID); appImManager.mutePeer(this.peerID);
}); });
if(testScroll) { if(testScroll && false) {
let div = document.createElement('div'); let div = document.createElement('div');
for(let i = 0; i < 500; ++i) { for(let i = 0; i < 500; ++i) {
//div.insertAdjacentHTML('beforeend', `<div style="background-image: url(assets/img/camomile.jpg);"></div>`); //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); setText(appPeersManager.getPeerUsername(peerID), this.profileElements.username);
} }
let dialog: any = appMessagesManager.getDialogByPeerID(peerID); let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
if(dialog.length) { if(dialog) {
dialog = dialog[0];
let muted = false; let muted = false;
if(dialog.notify_settings && dialog.notify_settings.mute_until) { if(dialog.notify_settings && dialog.notify_settings.mute_until) {
muted = new Date(dialog.notify_settings.mute_until * 1000) > new Date(); muted = new Date(dialog.notify_settings.mute_until * 1000) > new Date();

8
src/lib/appManagers/appStickersManager.ts

@ -68,7 +68,7 @@ class AppStickersManager {
} }
//if(!this.stickerSets['emoji']) { //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: { public async getStickerSet(set: {
id: string, id: string,
access_hash: string access_hash: string
}) { }, params: Partial<{
if(this.stickerSets[set.id]) return this.stickerSets[set.id]; overwrite: boolean
}> = {}) {
if(this.stickerSets[set.id] && !params.overwrite) return this.stickerSets[set.id];
let promise = apiManager.invokeApi('messages.getStickerSet', { let promise = apiManager.invokeApi('messages.getStickerSet', {
stickerset: set.id == 'emoji' ? { stickerset: set.id == 'emoji' ? {

6
src/lib/lottieLoader.ts

@ -1,4 +1,4 @@
import { isInDOM } from "./utils"; //import { isInDOM } from "./utils";
import LottiePlayer, { AnimationConfigWithPath, AnimationConfigWithData, AnimationItem } from "lottie-web/build/player/lottie.d"; import LottiePlayer, { AnimationConfigWithPath, AnimationConfigWithData, AnimationItem } from "lottie-web/build/player/lottie.d";
let convert = (value: number) => { let convert = (value: number) => {
@ -85,7 +85,7 @@ class LottieLoader {
for(let i = length - 1; i >= 0; --i) { for(let i = length - 1; i >= 0; --i) {
let {animation, container, paused, autoplay, canvas} = animations[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'); this.debug && console.log('destroy animation');
animation.destroy(); animation.destroy();
animations.splice(i, 1); animations.splice(i, 1);
@ -140,7 +140,7 @@ class LottieLoader {
k[2] = (foundReplacement[1] & 255) / 255; k[2] = (foundReplacement[1] & 255) / 255;
} }
console.log('foundReplacement!', foundReplacement, color.toString(16), k); //console.log('foundReplacement!', foundReplacement, color.toString(16), k);
break; break;
} }

2
src/pages/pageSignIn.ts

@ -206,7 +206,7 @@ let onFirstMount = () => {
putPreloader(this); putPreloader(this);
//this.innerHTML = 'PLEASE WAIT...'; //this.innerHTML = 'PLEASE WAIT...';
return; //return;
let phone_number = telEl.value; let phone_number = telEl.value;
apiManager.invokeApi('auth.sendCode', { apiManager.invokeApi('auth.sendCode', {

75
src/scss/partials/_mediaViewer.scss

@ -74,49 +74,39 @@ $move-duration: .35s;
max-height: 100%; max-height: 100%;
max-width: 100%; max-width: 100%;
overflow: hidden; overflow: hidden;
}
.media-viewer-stub { &-stub {
flex: 1; 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;
}
.media-viewer-media { &-container {
display: flex; align-self: center;
align-items: center; position: relative;
justify-content: center; max-width: 100%;
visibility: hidden; max-height: 100%;
} overflow: hidden;
flex: 1 1 auto;
display: flex;
align-items: center;
}
img, video { &-media {
max-height: calc(100vh - 100px); visibility: hidden;
max-width: calc(100vw - 16px); }
/* max-height: 720px;
max-width: 1280px; */
}
.media-viewer-caption { &-caption {
flex: 1; flex: 1;
text-align: center; text-align: center;
color: $color-gray; color: $color-gray;
transition: $open-duration; transition: $open-duration;
max-width: 50vw; max-width: 50vw;
word-break: break-word; word-break: break-word;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
&:hover { &:hover {
color: #fff; color: #fff;
}
} }
} }
@ -188,6 +178,7 @@ $move-duration: .35s;
height: 100%; height: 100%;
user-select: none; user-select: none;
object-fit: cover; object-fit: cover;
opacity: 1;
} }
&.active { &.active {
@ -197,6 +188,13 @@ $move-duration: .35s;
&.moving { &.moving {
transition: $move-duration transform ease; transition: $move-duration transform ease;
} }
&.hiding {
img, video {
transition: $open-duration opacity;
opacity: 0;
}
}
} }
// возможно тут это вообще не нужно // возможно тут это вообще не нужно
@ -205,6 +203,7 @@ $move-duration: .35s;
height: 100%; height: 100%;
transform: scale(1); transform: scale(1);
overflow: hidden; overflow: hidden;
position: absolute;
} }
&-mover.active &-aspecter { &-mover.active &-aspecter {

31
src/scss/partials/_rightSIdebar.scss

@ -30,6 +30,10 @@
} }
} }
#forward-container {
z-index: 5;
}
.sidebar-search { .sidebar-search {
display: none; display: none;
@ -41,7 +45,7 @@
.profile { .profile {
&-content { &-content {
flex: 1 1 auto; /* flex: 1 1 auto; */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
/* height: 100%; */ /* height: 100%; */
@ -54,7 +58,7 @@
} }
&-wrapper { &-wrapper {
flex: 0 0 auto; flex: 1 1 auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-bottom: 36px; margin-bottom: 36px;
@ -64,16 +68,18 @@
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
//overflow: hidden; //overflow: hidden;
flex: 1 1 auto; position: absolute;
position: relative; top: 100%;
//height: 1%; // fix safari min-height: calc(100vh - 100% - 60px);
display: flex;
flex-direction: column;
}
}
/* &.loaded { // warning &-container {
.profile-tabs-content { > .scrollable {
position: relative; display: flex;
min-height: auto; flex-direction: column;
}
} */
} }
} }
@ -159,12 +165,13 @@
position: -webkit-sticky !important; position: -webkit-sticky !important;
position: sticky !important; position: sticky !important;
top: 0; top: 0;
z-index: 3; z-index: 2;
background-color: #fff; background-color: #fff;
&-content { &-content {
//min-height: 100%; //min-height: 100%;
min-height: calc(100% - 49px); min-height: calc(100% - 49px);
flex: 1 1 auto;
//position: absolute; // FIX THE SAFARI! //position: absolute; // FIX THE SAFARI!
//position: relative; //position: relative;
/* width: 500%; /* width: 500%;

4
src/scss/partials/_scrollable.scss

@ -26,8 +26,8 @@ div.scrollable::-webkit-scrollbar-thumb {
//position: relative; //position: relative;
//will-change: transform; //will-change: transform;
transform: translateZ(0); /* transform: translateZ(0);
-webkit-transform: translateZ(0); -webkit-transform: translateZ(0); */
position: absolute; position: absolute;
top: 0px; top: 0px;

1
src/scss/partials/popups/_peer.scss

@ -18,6 +18,7 @@
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 500; font-weight: 500;
margin-bottom: 0.125rem; margin-bottom: 0.125rem;
text-transform: capitalize;
} }
&-description { &-description {

2
src/scss/style.scss

@ -534,7 +534,7 @@ avatar-element {
} }
&-download { &-download {
z-index: 2; z-index: 1;
align-items: center; align-items: center;
font-size: 24px; font-size: 24px;
cursor: pointer; cursor: pointer;

2
webpack.common.js

@ -1,7 +1,7 @@
const path = require('path'); const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); 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 = { const opts = {
MTPROTO_WORKER: true, MTPROTO_WORKER: true,

Loading…
Cancel
Save