Browse Source

Settings tab & edit profile tab & animated emojis & update photos & dialogs contextmenu & smooth forward scroll & fix private messages & fix search forward result to center

master
morethanwords 5 years ago
parent
commit
448a781f8e
  1. 9
      src/components/appSelectPeers.ts
  2. 62
      src/components/avatar.ts
  3. 52
      src/components/misc.ts
  4. 13
      src/components/popupAvatar.ts
  5. 26
      src/components/scrollable_new.ts
  6. 55
      src/lib/appManagers/appChatsManager.ts
  7. 467
      src/lib/appManagers/appDialogsManager.ts
  8. 211
      src/lib/appManagers/appImManager.ts
  9. 16
      src/lib/appManagers/appMediaViewer.ts
  10. 250
      src/lib/appManagers/appMessagesManager.ts
  11. 232
      src/lib/appManagers/appSidebarLeft.ts
  12. 9
      src/lib/appManagers/appSidebarRight.ts
  13. 28
      src/lib/appManagers/appStickersManager.ts
  14. 12
      src/lib/appManagers/appUsersManager.ts
  15. 204
      src/lib/utils.js
  16. 8
      src/pages/pageSignUp.ts
  17. 32
      src/scss/partials/_chat.scss
  18. 60
      src/scss/partials/_chatBubble.scss
  19. 16
      src/scss/partials/_chatlist.scss
  20. 73
      src/scss/partials/_leftSidebar.scss
  21. 28
      src/scss/partials/_mediaViewer.scss
  22. 2
      src/scss/partials/_rightSIdebar.scss
  23. 17
      src/scss/partials/_selector.scss
  24. 2
      src/scss/partials/_sidebar.scss
  25. 48
      src/scss/partials/popups/_peer.scss
  26. 42
      src/scss/style.scss
  27. 1
      tsconfig.json

9
src/components/appSelectPeers.ts

@ -184,12 +184,13 @@ export class AppSelectPeers {
let title = appPeersManager.getPeerTitle(peerID, false, true); let title = appPeersManager.getPeerTitle(peerID, false, true);
let avatarDiv = document.createElement('div'); let avatarEl = document.createElement('avatar-element');
avatarDiv.classList.add('user-avatar', 'tgico'); avatarEl.classList.add('selector-user-avatar', 'tgico');
appProfileManager.putPhoto(avatarDiv, peerID); avatarEl.setAttribute('dialog', '1');
avatarEl.setAttribute('peer', '' + peerID);
div.innerHTML = title; div.innerHTML = title;
div.insertAdjacentElement('afterbegin', avatarDiv); div.insertAdjacentElement('afterbegin', avatarEl);
this.selectedContainer.insertBefore(div, this.input); this.selectedContainer.insertBefore(div, this.input);
this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight; this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight;

62
src/components/avatar.ts

@ -0,0 +1,62 @@
import appProfileManager from "../lib/appManagers/appProfileManager";
import { $rootScope } from "../lib/utils";
$rootScope.$on('avatar_update', (e: CustomEvent) => {
let peerID = e.detail;
appProfileManager.removeFromAvatarsCache(peerID);
(Array.from(document.querySelectorAll('avatar-element[peer="' + peerID + '"]')) as AvatarElement[]).forEach(elem => {
console.log('updating avatar:', elem);
elem.update();
});
});
export default class AvatarElement extends HTMLElement {
private peerID: number;
private isDialog = false;
constructor() {
super();
// элемент создан
}
connectedCallback() {
// браузер вызывает этот метод при добавлении элемента в документ
// (может вызываться много раз, если элемент многократно добавляется/удаляется)
this.isDialog = !!this.getAttribute('dialog');
}
disconnectedCallback() {
// браузер вызывает этот метод при удалении элемента из документа
// (может вызываться много раз, если элемент многократно добавляется/удаляется)
}
static get observedAttributes(): string[] {
return ['peer', 'dialog'/* массив имён атрибутов для отслеживания их изменений */];
}
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
// вызывается при изменении одного из перечисленных выше атрибутов
if(name == 'peer') {
this.peerID = +newValue;
} else if(name == 'dialog') {
this.isDialog = !!newValue;
}
this.update();
}
public update() {
appProfileManager.putPhoto(this, this.peerID, this.isDialog);
}
adoptedCallback() {
// вызывается, когда элемент перемещается в новый документ
// (происходит в document.adoptNode, используется очень редко)
}
// у элемента могут быть ещё другие методы и свойства
}
customElements.define("avatar-element", AvatarElement);

52
src/components/misc.ts

@ -329,33 +329,59 @@ let onMouseMove = (e: MouseEvent) => {
let diffY = clientY >= rect.bottom ? clientY - rect.bottom : rect.top - clientY; let diffY = clientY >= rect.bottom ? clientY - rect.bottom : rect.top - clientY;
if(diffX >= 100 || diffY >= 100) { if(diffX >= 100 || diffY >= 100) {
openedMenu.classList.remove('active'); closeBtnMenu();
openedMenu.parentElement.classList.remove('menu-open');
//openedMenu.parentElement.click(); //openedMenu.parentElement.click();
} }
//console.log('mousemove', diffX, diffY); //console.log('mousemove', diffX, diffY);
}; };
let openedMenu: HTMLDivElement = null; let onClick = (e: MouseEvent) => {
export function openBtnMenu(menuElement: HTMLDivElement) { e.preventDefault();
closeBtnMenu();
};
let closeBtnMenu = () => {
if(openedMenu) { if(openedMenu) {
openedMenu.classList.remove('active'); openedMenu.classList.remove('active');
openedMenu.parentElement.classList.remove('menu-open'); openedMenu.parentElement.classList.remove('menu-open');
openedMenu = null;
} }
if(openedMenuOnClose) {
openedMenuOnClose();
openedMenuOnClose = null;
}
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('click', onClick);
window.removeEventListener('contextmenu', onClick);
};
let openedMenu: HTMLDivElement = null, openedMenuOnClose: () => void = null;
export function openBtnMenu(menuElement: HTMLDivElement, onClose?: () => void) {
closeBtnMenu();
openedMenu = menuElement; openedMenu = menuElement;
openedMenu.classList.add('active'); openedMenu.classList.add('active');
openedMenu.parentElement.classList.add('menu-open'); openedMenu.parentElement.classList.add('menu-open');
window.addEventListener('click', () => { openedMenuOnClose = onClose;
if(openedMenu) {
openedMenu.parentElement.classList.remove('menu-open');
openedMenu.classList.remove('active');
openedMenu = null;
}
window.removeEventListener('mousemove', onMouseMove);
}, {once: true});
window.addEventListener('mousemove', onMouseMove); window.addEventListener('mousemove', onMouseMove);
window.addEventListener('click', onClick, {once: true});
window.addEventListener('contextmenu', onClick, {once: true});
}
export function positionMenu(e: MouseEvent, elem: HTMLElement, side: 'left' | 'right' = 'left') {
elem.classList.remove('bottom-left', 'bottom-right');
elem.classList.add(side == 'left' ? 'bottom-right' : 'bottom-left');
let {clientX, clientY} = e;
elem.style.left = (side == 'right' ? clientX - elem.scrollWidth : clientX) + 'px';
if((clientY + elem.scrollHeight) > window.innerHeight) {
elem.style.top = (window.innerHeight - elem.scrollHeight) + 'px';
} else {
elem.style.top = clientY + 'px';
}
} }

13
src/components/popupAvatar.ts

@ -58,12 +58,7 @@ export class PopupAvatar {
this.canvas.toBlob(blob => { this.canvas.toBlob(blob => {
this.blob = blob; // save blob to send after reg this.blob = blob; // save blob to send after reg
this.darkenCanvas();
// darken
let ctx = this.canvas.getContext('2d');
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.resolve(); this.resolve();
}, 'image/jpeg', 1); }, 'image/jpeg', 1);
}); });
@ -92,6 +87,12 @@ export class PopupAvatar {
this.input.click(); this.input.click();
} }
public darkenCanvas() {
let ctx = this.canvas.getContext('2d');
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
} }
export default new PopupAvatar(); export default new PopupAvatar();

26
src/components/scrollable_new.ts

@ -68,7 +68,7 @@ export default class Scrollable {
private lastBottomID = 0; private lastBottomID = 0;
private lastScrollDirection = 0; // true = bottom private lastScrollDirection = 0; // true = bottom
private scrollLocked = 0; public scrollLocked = 0;
private setVisible(element: HTMLElement) { private setVisible(element: HTMLElement) {
if(this.visible.has(element)) return; if(this.visible.has(element)) return;
@ -367,8 +367,8 @@ export default class Scrollable {
return !!element.parentElement; return !!element.parentElement;
} }
public scrollIntoView(element: HTMLElement) { public scrollIntoView(element: HTMLElement, smooth = true, fromUp = false) {
if(element.parentElement) { if(element.parentElement && !this.scrollLocked) {
let scrollTop = this.scrollTop; let scrollTop = this.scrollTop;
let offsetTop = element.offsetTop; let offsetTop = element.offsetTop;
let clientHeight = this.container.clientHeight; let clientHeight = this.container.clientHeight;
@ -383,12 +383,30 @@ export default class Scrollable {
offsetTop -= diff; offsetTop -= diff;
//} //}
if(element.dataset.timeout) {
clearTimeout(+element.dataset.timeout);
element.classList.remove('is-selected');
void element.offsetWidth; // reflow
}
element.classList.add('is-selected');
element.dataset.timeout = '' + setTimeout(() => {
element.classList.remove('is-selected');
delete element.dataset.timeout;
}, 2000);
if(scrollTop == Math.floor(offsetTop)) {
return;
}
if(this.scrollLocked) clearTimeout(this.scrollLocked); if(this.scrollLocked) clearTimeout(this.scrollLocked);
this.scrollLocked = setTimeout(() => { this.scrollLocked = setTimeout(() => {
this.scrollLocked = 0; this.scrollLocked = 0;
this.onScroll(); this.onScroll();
}, 468); }, 468);
this.container.scrollTo({behavior: 'smooth', top: offsetTop}); if(fromUp) {
this.container.scrollTo({behavior: 'auto', top: 0});
}
this.container.scrollTo({behavior: smooth ? 'smooth' : 'auto', top: offsetTop});
//element.scrollIntoView({behavior: 'smooth', block: 'center'}); //element.scrollIntoView({behavior: 'smooth', block: 'center'});
} }
} }

55
src/lib/appManagers/appChatsManager.ts

@ -1,7 +1,6 @@
import { $rootScope, isObject, SearchIndexManager, safeReplaceObject, copy, numberWithCommas } from "../utils"; import { $rootScope, isObject, SearchIndexManager, safeReplaceObject, copy, numberWithCommas } from "../utils";
import { RichTextProcessor } from "../richtextprocessor"; import { RichTextProcessor } from "../richtextprocessor";
import appUsersManager from "./appUsersManager"; import appUsersManager from "./appUsersManager";
import appPeersManager from "./appPeersManager";
import apiManager from '../mtproto/mtprotoworker'; import apiManager from '../mtproto/mtprotoworker';
import apiUpdatesManager from "./apiUpdatesManager"; import apiUpdatesManager from "./apiUpdatesManager";
@ -68,40 +67,46 @@ export class AppChatsManager {
apiChat.rTitle = apiChat.title || 'chat_title_deleted'; apiChat.rTitle = apiChat.title || 'chat_title_deleted';
apiChat.rTitle = RichTextProcessor.wrapRichText(apiChat.title, {noLinks: true, noLinebreaks: true}) || 'chat_title_deleted'; apiChat.rTitle = RichTextProcessor.wrapRichText(apiChat.title, {noLinks: true, noLinebreaks: true}) || 'chat_title_deleted';
var result = this.chats[apiChat.id]; let oldChat = this.chats[apiChat.id];
var titleWords = SearchIndexManager.cleanSearchText(apiChat.title || '').split(' ');
var firstWord = titleWords.shift();
var lastWord = titleWords.pop();
apiChat.initials = firstWord.charAt(0) + (lastWord ? lastWord.charAt(0) : firstWord.charAt(1));
apiChat.num = (Math.abs(apiChat.id >> 1) % 8) + 1; let titleWords = SearchIndexManager.cleanSearchText(apiChat.title || '', false).split(' ');
let firstWord = titleWords.shift();
let lastWord = titleWords.pop();
apiChat.initials = firstWord.charAt(0) + (lastWord ? lastWord.charAt(0) : firstWord.charAt(1));
if(apiChat.pFlags === undefined) { if(apiChat.pFlags === undefined) {
apiChat.pFlags = {}; apiChat.pFlags = {};
} }
if(apiChat.pFlags.min) { if(apiChat.pFlags.min) {
if(result !== undefined) { if(oldChat !== undefined) {
return; return;
} }
} }
if(apiChat._ == 'channel' && if(apiChat._ == 'channel' &&
apiChat.participants_count === undefined && apiChat.participants_count === undefined &&
result !== undefined && oldChat !== undefined &&
result.participants_count) { oldChat.participants_count) {
apiChat.participants_count = result.participants_count; apiChat.participants_count = oldChat.participants_count;
} }
if(apiChat.username) { if(apiChat.username) {
var searchUsername = SearchIndexManager.cleanUsername(apiChat.username); let searchUsername = SearchIndexManager.cleanUsername(apiChat.username);
this.usernames[searchUsername] = apiChat.id; this.usernames[searchUsername] = apiChat.id;
} }
if(result === undefined) { let changedPhoto = false;
result = this.chats[apiChat.id] = apiChat; if(oldChat === undefined) {
oldChat = this.chats[apiChat.id] = apiChat;
} else { } else {
safeReplaceObject(result, apiChat); let oldPhoto = oldChat.photo && oldChat.photo.photo_small;
let newPhoto = apiChat.photo && apiChat.photo.photo_small;
if(JSON.stringify(oldPhoto) !== JSON.stringify(newPhoto)) {
changedPhoto = true;
}
safeReplaceObject(oldChat, apiChat);
$rootScope.$broadcast('chat_update', apiChat.id); $rootScope.$broadcast('chat_update', apiChat.id);
} }
@ -109,6 +114,10 @@ export class AppChatsManager {
safeReplaceObject(this.cachedPhotoLocations[apiChat.id], apiChat && safeReplaceObject(this.cachedPhotoLocations[apiChat.id], apiChat &&
apiChat.photo ? apiChat.photo : {empty: true}); apiChat.photo ? apiChat.photo : {empty: true});
} }
if(changedPhoto) {
$rootScope.$broadcast('avatar_update', -apiChat.id);
}
} }
public getChat(id: number) { public getChat(id: number) {
@ -116,10 +125,11 @@ 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: any) { public hasRights(id: number, action: 'send' | 'edit_title' | 'edit_photo' | 'invite') {
if(!(id in this.chats)) { if(!(id in this.chats)) {
return false; return false;
} }
var chat = this.getChat(id); var chat = this.getChat(id);
if(chat._ == 'chatForbidden' || if(chat._ == 'chatForbidden' ||
chat._ == 'channelForbidden' || chat._ == 'channelForbidden' ||
@ -127,22 +137,25 @@ export class AppChatsManager {
chat.pFlags.left) { chat.pFlags.left) {
return false; return false;
} }
if(chat.pFlags.creator) { if(chat.pFlags.creator) {
return true; return true;
} }
switch(action) { switch(action) {
case 'send': case 'send': {
if(chat._ == 'channel' && if(chat._ == 'channel' &&
!chat.pFlags.megagroup && !chat.pFlags.megagroup &&
!chat.pFlags.editor) { !chat.pFlags.editor) {
return false; return false;
} }
break; 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(!chat.pFlags.editor &&
@ -159,7 +172,9 @@ export class AppChatsManager {
} }
} }
break; break;
}
} }
return true; return true;
} }

467
src/lib/appManagers/appDialogsManager.ts

@ -1,18 +1,18 @@
import { langPack, findUpClassName, $rootScope, escapeRegExp, whichChild } from "../utils"; import { findUpClassName, $rootScope, escapeRegExp, whichChild, findUpTag } from "../utils";
import appImManager, { AppImManager } from "./appImManager"; import appImManager, { AppImManager } from "./appImManager";
import appPeersManager from './appPeersManager'; 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 } from "../../components/misc"; import { ripple, putPreloader, positionMenu, openBtnMenu } 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 appProfileManager from "./appProfileManager";
import { logger } from "../polyfill"; import { logger } from "../polyfill";
import appChatsManager from "./appChatsManager"; import appChatsManager from "./appChatsManager";
import AvatarElement from "../../components/avatar";
type DialogDom = { type DialogDom = {
avatarDiv: HTMLDivElement, avatarEl: AvatarElement,
captionDiv: HTMLDivElement, captionDiv: HTMLDivElement,
titleSpan: HTMLSpanElement, titleSpan: HTMLSpanElement,
statusSpan: HTMLSpanElement, statusSpan: HTMLSpanElement,
@ -25,6 +25,346 @@ 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 {
private element = document.getElementById('dialogs-contextmenu') as HTMLDivElement;
private buttons: {
archive: HTMLButtonElement,
pin: HTMLButtonElement,
mute: HTMLButtonElement,
unread: HTMLButtonElement,
delete: HTMLButtonElement,
//clear: HTMLButtonElement,
} = {} as any;
private selectedID: number;
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;
});
const onContextMenu = (e: MouseEvent) => {
let li: HTMLDivElement = null;
try {
li = findUpTag(e.target, 'LI');
} catch(e) {}
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');
const dialog = appMessagesManager.getDialogByPeerID(this.selectedID)[0];
const notOurDialog = dialog.peerID != $rootScope.myID;
// archive button
if(notOurDialog) {
const button = this.buttons.archive;
let text = '';
if(dialog.folder_id == 1) {
text = 'Unarchive chat';
button.classList.remove('tgico-archive');
} else {
text = 'Archive chat';
button.classList.add('tgico-archive');
}
button.innerText = text;
this.buttons.archive.style.display = '';
} else {
this.buttons.archive.style.display = 'none';
}
// pin button
{
const button = this.buttons.pin;
let text = '';
if(dialog.pFlags?.pinned) {
text = 'Unpin from top';
button.classList.remove('tgico-pin');
} else {
text = 'Pin to top';
button.classList.add('tgico-pin');
}
button.innerText = text;
}
// mute button
if(notOurDialog) {
const button = this.buttons.mute;
let text = '';
if(dialog.notify_settings && dialog.notify_settings.mute_until > (Date.now() / 1000 | 0)) {
text = 'Enable notifications';
button.classList.remove('tgico-mute');
} else {
text = 'Disable notifications';
button.classList.add('tgico-mute');
}
button.innerText = text;
this.buttons.mute.style.display = '';
} else {
this.buttons.mute.style.display = 'none';
}
// unread button
{
const button = this.buttons.unread;
let text = '';
if(dialog.pFlags?.unread_mark) {
text = 'Mark as read';
button.classList.add('tgico-message');
} else {
text = 'Mark as unread';
button.classList.remove('tgico-message');
}
button.innerText = text;
}
/* // clear history button
if(appPeersManager.isChannel(this.selectedID)) {
this.buttons.clear.style.display = 'none';
} else {
this.buttons.clear.style.display = '';
} */
// delete button
let deleteButtonText = '';
if(appPeersManager.isMegagroup(this.selectedID)) {
deleteButtonText = 'Leave group';
this.peerType = 'megagroup';
} else if(appPeersManager.isChannel(this.selectedID)) {
deleteButtonText = 'Leave channel';
this.peerType = 'channel';
} else if(this.selectedID < 0) {
deleteButtonText = 'Delete and leave';
this.peerType = 'group';
} else {
deleteButtonText = 'Delete chat';
this.peerType = this.selectedID == $rootScope.myID ? 'saved' : 'chat';
}
this.buttons.delete.innerText = deleteButtonText;
li.classList.add('menu-open');
positionMenu(e, this.element);
openBtnMenu(this.element, () => {
li.classList.remove('menu-open');
});
};
this.attachTo.forEach(el => {
el.addEventListener('contextmenu', onContextMenu);
});
this.buttons.archive.addEventListener('click', () => {
let dialog = appMessagesManager.getDialogByPeerID(this.selectedID)[0];
if(dialog) {
appMessagesManager.editPeerFolders([dialog.peerID], +!dialog.folder_id);
}
});
this.buttons.pin.addEventListener('click', () => {
appMessagesManager.toggleDialogPin(this.selectedID);
});
this.buttons.mute.addEventListener('click', () => {
appImManager.mutePeer(this.selectedID);
});
this.buttons.unread.addEventListener('click', () => {
appMessagesManager.markDialogUnread(this.selectedID);
});
this.buttons.delete.addEventListener('click', () => {
let firstName = appPeersManager.getPeerTitle(this.selectedID, false, true);
let callback = (justClear: boolean) => {
appMessagesManager.flushHistory(this.selectedID, justClear);
};
let title: string, description: string, buttons: PopupPeerButton[];
switch(this.peerType) {
case 'channel': {
title = 'Leave Channel?';
description = `Are you sure you want to leave this channel?`;
buttons = [{
text: 'LEAVE ' + firstName,
isDanger: true,
callback: () => callback(true)
}];
break;
}
case 'megagroup': {
title = 'Leave Group?';
description = `Are you sure you want to leave this group?`;
buttons = [{
text: 'LEAVE ' + firstName,
isDanger: true,
callback: () => callback(true)
}];
break;
}
case 'chat': {
title = 'Delete Chat?';
description = `Are you sure you want to delete chat with <b>${firstName}</b>?`;
buttons = [{
text: 'DELETE FOR ME AND ' + firstName,
isDanger: true,
callback: () => callback(false)
}, {
text: 'DELETE JUST FOR ME',
isDanger: true,
callback: () => callback(true)
}];
break;
}
case 'saved': {
title = 'Delete Saved Messages?';
description = `Are you sure you want to delete all your saved messages?`;
buttons = [{
text: 'DELETE SAVED MESSAGES',
isDanger: true,
callback: () => callback(false)
}];
break;
}
case 'group': {
title = 'Delete and leave Group?';
description = `Are you sure you want to delete all message history and leave <b>${firstName}</b>?`;
buttons = [{
text: 'DELETE AND LEAVE ' + firstName,
isDanger: true,
callback: () => callback(true)
}];
break;
}
}
buttons.push({
text: 'CANCEL',
isCancel: true
});
let popup = new PopupPeer('popup-delete-chat', {
peerID: this.selectedID,
title: title,
description: description,
buttons: buttons
});
popup.show();
});
}
}
export class AppDialogsManager { export class AppDialogsManager {
public chatList = document.getElementById('dialogs') as HTMLUListElement; public chatList = document.getElementById('dialogs') as HTMLUListElement;
public chatListArchived = document.getElementById('dialogs-archived') as HTMLUListElement; public chatListArchived = document.getElementById('dialogs-archived') as HTMLUListElement;
@ -59,6 +399,8 @@ export class AppDialogsManager {
private log = logger('DIALOGS'); private log = logger('DIALOGS');
private contextMenu = new DialogsContextMenu([this.chatList, this.chatListArchived]);
constructor() { constructor() {
this.chatsPreloader = putPreloader(null, true); this.chatsPreloader = putPreloader(null, true);
//this.chatsContainer.append(this.chatsPreloader); //this.chatsContainer.append(this.chatsPreloader);
@ -127,9 +469,9 @@ export class AppDialogsManager {
if(dom) { if(dom) {
if(online) { if(online) {
dom.avatarDiv.classList.add('is-online'); dom.avatarEl.classList.add('is-online');
} else { } else {
dom.avatarDiv.classList.remove('is-online'); dom.avatarEl.classList.remove('is-online');
} }
} }
} }
@ -143,12 +485,19 @@ export class AppDialogsManager {
let dialog: any = e.detail; let dialog: any = e.detail;
this.setLastMessage(dialog); this.setLastMessage(dialog);
this.setUnreadMessages(dialog);
this.setDialogPosition(dialog); this.setDialogPosition(dialog);
this.setPinnedDelimiter(); this.setPinnedDelimiter();
}); });
$rootScope.$on('dialog_flush', (e: CustomEvent) => {
let peerID: number = e.detail.peerID;
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
if(dialog) {
this.setLastMessage(dialog);
}
});
$rootScope.$on('dialogs_multiupdate', (e: CustomEvent) => { $rootScope.$on('dialogs_multiupdate', (e: CustomEvent) => {
let dialogs = e.detail; let dialogs = e.detail;
@ -162,7 +511,6 @@ export class AppDialogsManager {
} }
this.setLastMessage(dialog); this.setLastMessage(dialog);
this.setUnreadMessages(dialog);
this.setDialogPosition(dialog); this.setDialogPosition(dialog);
} }
@ -309,7 +657,7 @@ export class AppDialogsManager {
this.lastActiveListElement = elem; this.lastActiveListElement = elem;
} }
result = appImManager.setPeer(peerID, lastMsgID, false, true); result = appImManager.setPeer(peerID, lastMsgID, true);
if(result instanceof Promise) { if(result instanceof Promise) {
this.lastGoodClickID = this.lastClickID; this.lastGoodClickID = this.lastClickID;
@ -350,6 +698,9 @@ export class AppDialogsManager {
let dom = this.getDialogDom(dialog.peerID); let dom = this.getDialogDom(dialog.peerID);
let prevPos = whichChild(dom.listEl); let prevPos = whichChild(dom.listEl);
let wrongFolder = (dialog.folder_id == 1 && this.chatList == dom.listEl.parentElement) || (dialog.folder_id == 0 && this.chatListArchived == dom.listEl.parentElement);
if(wrongFolder) prevPos = 0xFFFF;
if(prevPos == pos) { if(prevPos == pos) {
return; return;
} else if(prevPos < pos) { // was higher } else if(prevPos < pos) { // was higher
@ -408,13 +759,17 @@ export class AppDialogsManager {
} }
///////console.log('setlastMessage:', lastMessage); ///////console.log('setlastMessage:', lastMessage);
if(lastMessage._ == 'messageEmpty') return;
if(!dom) { if(!dom) {
dom = this.getDialogDom(dialog.peerID); dom = this.getDialogDom(dialog.peerID);
} }
if(lastMessage._ == 'messageEmpty') {
dom.lastMessageSpan.innerHTML = '';
dom.lastTimeSpan.innerHTML = '';
dom.listEl.removeAttribute('data-mid');
return;
}
let peer = dialog.peer; let peer = dialog.peer;
let peerID = dialog.peerID; let peerID = dialog.peerID;
//let peerID = appMessagesManager.getMessagePeer(lastMessage); //let peerID = appMessagesManager.getMessagePeer(lastMessage);
@ -422,11 +777,12 @@ export class AppDialogsManager {
//console.log('setting last message:', lastMessage); //console.log('setting last message:', lastMessage);
/* if(!dom.lastMessageSpan.classList.contains('user-typing')) */ { /* if(!dom.lastMessageSpan.classList.contains('user-typing')) */ {
/* let messageText = lastMessage.message;
let messageWrapped = ''; if(highlightWord && lastMessage.message) {
if(messageText) { let lastMessageText = appMessagesManager.getRichReplyText(lastMessage, '');
let messageText = lastMessage.message;
let entities = RichTextProcessor.parseEntities(messageText.replace(/\n/g, ' '), {noLinebreakers: true}); let entities = RichTextProcessor.parseEntities(messageText.replace(/\n/g, ' '), {noLinebreakers: true});
if(highlightWord) {
let regExp = new RegExp(escapeRegExp(highlightWord), 'gi'); let regExp = new RegExp(escapeRegExp(highlightWord), 'gi');
let match: any; let match: any;
@ -436,21 +792,23 @@ export class AppDialogsManager {
entities.push({_: 'messageEntityHighlight', length: highlightWord.length, offset: match.index}); entities.push({_: 'messageEntityHighlight', length: highlightWord.length, offset: match.index});
found = true; found = true;
} }
if(found) { if(found) {
entities.sort((a: any, b: any) => a.offset - b.offset); entities.sort((a: any, b: any) => a.offset - b.offset);
} }
}
let messageWrapped = RichTextProcessor.wrapRichText(messageText, {
messageWrapped = RichTextProcessor.wrapRichText(messageText, {
noLinebreakers: true, noLinebreakers: true,
entities: entities, entities: entities,
noTextFormat: true noTextFormat: true
}); });
} */
//dom.lastMessageSpan.innerHTML = lastMessageText + messageWrapped; dom.lastMessageSpan.innerHTML = lastMessageText + messageWrapped;
dom.lastMessageSpan.innerHTML = lastMessage.rReply; } else if(!lastMessage.deleted) {
dom.lastMessageSpan.innerHTML = lastMessage.rReply;
} else {
dom.lastMessageSpan.innerHTML = '';
}
/* if(lastMessage.from_id == auth.id) { // You: */ /* if(lastMessage.from_id == auth.id) { // You: */
if(peer._ != 'peerUser' && peerID != -lastMessage.from_id) { if(peer._ != 'peerUser' && peerID != -lastMessage.from_id) {
@ -473,25 +831,27 @@ export class AppDialogsManager {
} }
} }
let timeStr = ''; if(!lastMessage.deleted) {
let timestamp = lastMessage.date; let timeStr = '';
let now = Date.now() / 1000; let timestamp = lastMessage.date;
let time = new Date(lastMessage.date * 1000); let now = Date.now() / 1000;
let time = new Date(lastMessage.date * 1000);
if((now - timestamp) < 86400) { // if < 1 day
timeStr = ('0' + time.getHours()).slice(-2) + if((now - timestamp) < 86400) { // if < 1 day
':' + ('0' + time.getMinutes()).slice(-2); timeStr = ('0' + time.getHours()).slice(-2) +
} else if((now - timestamp) < (86400 * 7)) { // week ':' + ('0' + time.getMinutes()).slice(-2);
let date = new Date(timestamp * 1000); } else if((now - timestamp) < (86400 * 7)) { // week
timeStr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][date.getDay()]; let date = new Date(timestamp * 1000);
} else { timeStr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][date.getDay()];
let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; } else {
let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
timeStr = months[time.getMonth()] +
' ' + ('0' + time.getDate()).slice(-2); timeStr = months[time.getMonth()] +
} ' ' + ('0' + time.getDate()).slice(-2);
}
dom.lastTimeSpan.innerHTML = timeStr;
dom.lastTimeSpan.innerHTML = timeStr;
} else dom.lastTimeSpan.innerHTML = '';
dom.listEl.setAttribute('data-mid', lastMessage.mid); dom.listEl.setAttribute('data-mid', lastMessage.mid);
@ -504,7 +864,7 @@ export class AppDialogsManager {
let dom = this.getDialogDom(dialog.peerID); let dom = this.getDialogDom(dialog.peerID);
let lastMessage = appMessagesManager.getMessage(dialog.top_message); let lastMessage = appMessagesManager.getMessage(dialog.top_message);
if(lastMessage._ != 'messageEmpty' && if(lastMessage._ != 'messageEmpty' && !lastMessage.deleted &&
lastMessage.from_id == $rootScope.myID && lastMessage.peerID != $rootScope.myID && lastMessage.from_id == $rootScope.myID && lastMessage.peerID != $rootScope.myID &&
dialog.read_outbox_max_id) { // maybe comment, 06.20.2020 dialog.read_outbox_max_id) { // maybe comment, 06.20.2020
let outgoing = (lastMessage.pFlags && lastMessage.pFlags.unread) let outgoing = (lastMessage.pFlags && lastMessage.pFlags.unread)
@ -523,12 +883,12 @@ export class AppDialogsManager {
dom.unreadMessagesSpan.innerText = ''; dom.unreadMessagesSpan.innerText = '';
dom.unreadMessagesSpan.classList.remove('tgico-pinnedchat'); dom.unreadMessagesSpan.classList.remove('tgico-pinnedchat');
if(dialog.unread_count) { if(dialog.unread_count || dialog.pFlags.unread_mark) {
dom.unreadMessagesSpan.innerText = '' + dialog.unread_count; dom.unreadMessagesSpan.innerText = '' + (dialog.unread_count || ' ');
//dom.unreadMessagesSpan.classList.remove('tgico-pinnedchat'); //dom.unreadMessagesSpan.classList.remove('tgico-pinnedchat');
dom.unreadMessagesSpan.classList.add(new Date(dialog.notify_settings.mute_until * 1000) > new Date() ? dom.unreadMessagesSpan.classList.add(new Date(dialog.notify_settings.mute_until * 1000) > new Date() ?
'unread-muted' : 'unread'); 'unread-muted' : 'unread');
} else if(dialog.pFlags.pinned) { } else if(dialog.pFlags.pinned && dialog.folder_id == 0) {
dom.unreadMessagesSpan.classList.remove('unread', 'unread-muted'); dom.unreadMessagesSpan.classList.remove('unread', 'unread-muted');
dom.unreadMessagesSpan.classList.add('tgico-pinnedchat'); dom.unreadMessagesSpan.classList.add('tgico-pinnedchat');
} }
@ -575,8 +935,10 @@ export class AppDialogsManager {
let title = appPeersManager.getPeerTitle(peerID, false, onlyFirstName); let title = appPeersManager.getPeerTitle(peerID, false, onlyFirstName);
let avatarDiv = document.createElement('div'); let avatarEl = new AvatarElement();
avatarDiv.classList.add('user-avatar'); avatarEl.setAttribute('dialog', '1');
avatarEl.setAttribute('peer', '' + peerID);
avatarEl.classList.add('dialog-avatar');
if(drawStatus && peerID != $rootScope.myID && dialog.peer) { if(drawStatus && peerID != $rootScope.myID && dialog.peer) {
let peer = dialog.peer; let peer = dialog.peer;
@ -587,7 +949,7 @@ export class AppDialogsManager {
//console.log('found user', user); //console.log('found user', user);
if(user.status && user.status._ == 'userStatusOnline') { if(user.status && user.status._ == 'userStatusOnline') {
avatarDiv.classList.add('is-online'); avatarEl.classList.add('is-online');
} }
break; break;
@ -618,9 +980,6 @@ export class AppDialogsManager {
title = onlyFirstName ? 'Saved' : 'Saved Messages'; title = onlyFirstName ? 'Saved' : 'Saved Messages';
} }
//console.log('trying to load photo for:', title);
appProfileManager.putPhoto(avatarDiv, dialog.peerID, true);
titleSpan.innerHTML = title; titleSpan.innerHTML = title;
//p.classList.add('') //p.classList.add('')
@ -632,7 +991,7 @@ export class AppDialogsManager {
let paddingDiv = document.createElement('div'); let paddingDiv = document.createElement('div');
paddingDiv.classList.add('rp'); paddingDiv.classList.add('rp');
paddingDiv.append(avatarDiv, captionDiv); paddingDiv.append(avatarEl, captionDiv);
if(rippleEnabled) { if(rippleEnabled) {
ripple(paddingDiv, (id) => { ripple(paddingDiv, (id) => {
@ -677,7 +1036,7 @@ export class AppDialogsManager {
captionDiv.append(titleP, messageP); captionDiv.append(titleP, messageP);
let dom: DialogDom = { let dom: DialogDom = {
avatarDiv, avatarEl,
captionDiv, captionDiv,
titleSpan, titleSpan,
statusSpan, statusSpan,

211
src/lib/appManagers/appImManager.ts

@ -19,7 +19,7 @@ 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 } from '../../components/misc'; import { openBtnMenu, formatPhoneNumber, positionMenu } 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';
@ -27,6 +27,8 @@ import BubbleGroups from '../../components/bubbleGroups';
import LazyLoadQueue from '../../components/lazyLoadQueue'; import LazyLoadQueue from '../../components/lazyLoadQueue';
import appDocsManager from './appDocsManager'; import appDocsManager from './appDocsManager';
import appForward from '../../components/appForward'; import appForward from '../../components/appForward';
import appStickersManager from './appStickersManager';
import AvatarElement from '../../components/avatar';
console.log('appImManager included!'); console.log('appImManager included!');
@ -38,7 +40,7 @@ export class AppImManager {
public pageEl = document.querySelector('.page-chats') as HTMLDivElement; public pageEl = document.querySelector('.page-chats') as HTMLDivElement;
public btnMute = this.pageEl.querySelector('.tool-mute') as HTMLButtonElement; public btnMute = this.pageEl.querySelector('.tool-mute') as HTMLButtonElement;
public btnMenuMute = this.pageEl.querySelector('.menu-mute') as HTMLButtonElement; public btnMenuMute = this.pageEl.querySelector('.menu-mute') as HTMLButtonElement;
public avatarEl = document.getElementById('im-avatar') as HTMLDivElement; public avatarEl = document.getElementById('im-avatar') as AvatarElement;
public titleEl = document.getElementById('im-title') as HTMLDivElement; public titleEl = document.getElementById('im-title') as HTMLDivElement;
public subtitleEl = document.getElementById('im-subtitle') as HTMLDivElement; public subtitleEl = document.getElementById('im-subtitle') as HTMLDivElement;
public bubblesContainer = document.getElementById('bubbles') as HTMLDivElement; public bubblesContainer = document.getElementById('bubbles') as HTMLDivElement;
@ -71,8 +73,6 @@ export class AppImManager {
private pinnedMessageContainer = this.pageEl.querySelector('.pinned-message') as HTMLDivElement; private pinnedMessageContainer = this.pageEl.querySelector('.pinned-message') as HTMLDivElement;
private pinnedMessageContent = this.pinnedMessageContainer.querySelector('.pinned-message-subtitle') as HTMLDivElement; private pinnedMessageContent = this.pinnedMessageContainer.querySelector('.pinned-message-subtitle') as HTMLDivElement;
private firstTopMsgID = 0;
public lazyLoadQueue = new LazyLoadQueue(); public lazyLoadQueue = new LazyLoadQueue();
public scroll: HTMLDivElement = null; public scroll: HTMLDivElement = null;
@ -189,6 +189,13 @@ export class AppImManager {
this.deleteMessagesByIDs(Object.keys(detail.msgs).map(s => +s)); this.deleteMessagesByIDs(Object.keys(detail.msgs).map(s => +s));
}); });
$rootScope.$on('dialog_flush', (e: CustomEvent) => {
let peerID: number = e.detail.peerID;
if(this.peerID == peerID) {
this.deleteMessagesByIDs(Object.keys(this.bubbles).map(m => +m));
}
});
// Calls when message successfully sent and we have an ID // Calls when message successfully sent and we have an ID
$rootScope.$on('message_sent', (e: CustomEvent) => { $rootScope.$on('message_sent', (e: CustomEvent) => {
@ -346,7 +353,7 @@ export class AppImManager {
return; return;
} }
if((target.tagName == 'IMG' && !target.classList.contains('emoji') && !target.parentElement.classList.contains('user-avatar')) if((target.tagName == 'IMG' && !target.classList.contains('emoji') && target.parentElement.tagName != "AVATAR-ELEMENT")
|| target.tagName == 'image' || target.tagName == 'image'
|| target.classList.contains('album-item') || target.classList.contains('album-item')
|| (target.tagName == 'VIDEO' && !bubble.classList.contains('round'))) { || (target.tagName == 'VIDEO' && !bubble.classList.contains('round'))) {
@ -391,16 +398,16 @@ export class AppImManager {
if(['IMG', 'DIV'].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV'); if(['IMG', 'DIV'].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV');
if(target.tagName == 'DIV') { if(target.tagName == 'DIV' || target.tagName == "AVATAR-ELEMENT") {
if(target.classList.contains('forward')) { if(target.classList.contains('forward')) {
let savedFrom = bubble.dataset.savedFrom; let savedFrom = bubble.dataset.savedFrom;
let splitted = savedFrom.split('_'); let splitted = savedFrom.split('_');
let peerID = +splitted[0]; let peerID = +splitted[0];
let msgID = +splitted[1]; let msgID = +splitted[1];
////this.log('savedFrom', peerID, msgID); ////this.log('savedFrom', peerID, msgID);
this.setPeer(peerID, msgID, true); this.setPeer(peerID, msgID/* , true */);
return; return;
} else if(target.classList.contains('user-avatar') || target.classList.contains('name')) { } else if(target.tagName == "AVATAR-ELEMENT" || target.classList.contains('name')) {
let peerID = +target.dataset.peerID; let peerID = +target.dataset.peerID;
if(!isNaN(peerID)) { if(!isNaN(peerID)) {
@ -420,7 +427,7 @@ export class AppImManager {
let originalMessageID = +bubble.getAttribute('data-original-mid'); let originalMessageID = +bubble.getAttribute('data-original-mid');
this.setPeer(this.peerID, originalMessageID); this.setPeer(this.peerID, originalMessageID);
} }
} else if(target.tagName == 'IMG' && target.parentElement.classList.contains('user-avatar')) { } else if(target.tagName == 'IMG' && target.parentElement.tagName == "AVATAR-ELEMENT") {
let peerID = +target.parentElement.dataset.peerID; let peerID = +target.parentElement.dataset.peerID;
if(!isNaN(peerID)) { if(!isNaN(peerID)) {
@ -447,8 +454,8 @@ export class AppImManager {
this.setPeer(this.peerID, mid); this.setPeer(this.peerID, mid);
}); });
this.btnMenuMute.addEventListener('click', () => this.mutePeer()); this.btnMenuMute.addEventListener('click', () => this.mutePeer(this.peerID));
this.btnMute.addEventListener('click', () => this.mutePeer()); this.btnMute.addEventListener('click', () => this.mutePeer(this.peerID));
let onKeyDown = (e: KeyboardEvent) => { let onKeyDown = (e: KeyboardEvent) => {
let target = e.target as HTMLElement; let target = e.target as HTMLElement;
@ -520,19 +527,7 @@ export class AppImManager {
this.contextMenuEdit.style.display = side == 'right' ? '' : 'none'; this.contextMenuEdit.style.display = side == 'right' ? '' : 'none';
this.contextMenu.classList.remove('bottom-left', 'bottom-right'); positionMenu(e, this.contextMenu, side as any);
this.contextMenu.classList.add(side == 'left' ? 'bottom-right' : 'bottom-left');
let {clientX, clientY} = e;
this.contextMenu.style.left = (side == 'right' ? clientX - this.contextMenu.scrollWidth : clientX) + 'px';
if((clientY + this.contextMenu.scrollHeight) > window.innerHeight) {
this.contextMenu.style.top = (window.innerHeight - this.contextMenu.scrollHeight) + 'px';
} else {
this.contextMenu.style.top = clientY + 'px';
}
//this.contextMenu.classList.add('active');
openBtnMenu(this.contextMenu); openBtnMenu(this.contextMenu);
/////this.log('contextmenu', e, bubble, msgID, side); /////this.log('contextmenu', e, bubble, msgID, side);
@ -793,7 +788,7 @@ export class AppImManager {
this.isScrollingTimeout = 0; this.isScrollingTimeout = 0;
}, 300); }, 300);
if(this.scroll.scrollHeight - (this.scroll.scrollTop + this.scroll.offsetHeight) == 0/* <= 5 */) { if(this.scroll.scrollHeight - Math.round(this.scroll.scrollTop + this.scroll.offsetHeight) <= 1/* <= 5 */) {
this.scroll.parentElement.classList.add('scrolled-down'); this.scroll.parentElement.classList.add('scrolled-down');
this.scrolledDown = true; this.scrolledDown = true;
} else if(this.scroll.parentElement.classList.contains('scrolled-down')) { } else if(this.scroll.parentElement.classList.contains('scrolled-down')) {
@ -865,14 +860,16 @@ export class AppImManager {
appMessagesManager.wrapSingleMessage(chatInfo.pinned_msg_id); appMessagesManager.wrapSingleMessage(chatInfo.pinned_msg_id);
} }
let participants_count = chatInfo.participants_count || (chatInfo.participants && chatInfo.participants.participants.length); let participants_count = chatInfo.participants_count || (chatInfo.participants && chatInfo.participants.participants && chatInfo.participants.participants.length);
let subtitle = numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members'); if(participants_count) {
let subtitle = numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members');
if(onlines > 1) { if(onlines > 1) {
subtitle += ', ' + numberWithCommas(onlines) + ' online'; subtitle += ', ' + numberWithCommas(onlines) + ' online';
}
this.subtitleEl.innerText = appSidebarRight.profileElements.subtitle.innerText = subtitle;
} }
this.subtitleEl.innerText = appSidebarRight.profileElements.subtitle.innerText = subtitle;
}); });
} else if(!appUsersManager.isBot(this.peerID)) { // user } else if(!appUsersManager.isBot(this.peerID)) { // user
let user = appUsersManager.getUser(this.peerID); let user = appUsersManager.getUser(this.peerID);
@ -914,10 +911,6 @@ export class AppImManager {
this.scrolledAllDown = false; this.scrolledAllDown = false;
this.muted = false; this.muted = false;
/* for(let i in this.bubbles) {
let bubble = this.bubbles[i];
bubble.remove();
} */
this.bubbles = {}; this.bubbles = {};
this.dateMessages = {}; this.dateMessages = {};
this.bubbleGroups.cleanup(); this.bubbleGroups.cleanup();
@ -949,7 +942,7 @@ export class AppImManager {
////console.timeEnd('appImManager cleanup'); ////console.timeEnd('appImManager cleanup');
} }
public setPeer(peerID: number, lastMsgID = 0, forwarding = false, fromClick = false) { public setPeer(peerID: number, lastMsgID = 0, fromClick = false) {
console.time('appImManager setPeer'); console.time('appImManager setPeer');
console.time('appImManager setPeer pre promise'); console.time('appImManager setPeer pre promise');
////console.time('appImManager: pre render start'); ////console.time('appImManager: pre render start');
@ -994,6 +987,8 @@ export class AppImManager {
appSidebarRight.searchCloseBtn.click(); appSidebarRight.searchCloseBtn.click();
} }
const maxBubbleID = Math.max(...Object.keys(this.bubbles).map(mid => +mid));
// clear // clear
this.cleanup(); this.cleanup();
@ -1008,17 +1003,20 @@ export class AppImManager {
let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0] || null; let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0] || null;
//////this.log('setPeer peerID:', this.peerID, dialog, lastMsgID); //////this.log('setPeer peerID:', this.peerID, dialog, lastMsgID);
appProfileManager.putPhoto(this.avatarEl, this.peerID);
appProfileManager.putPhoto(appSidebarRight.profileElements.avatar, this.peerID);
this.firstTopMsgID = dialog ? dialog.top_message : 0; this.avatarEl.setAttribute('peer', '' + this.peerID);
const isChannel = appPeersManager.isChannel(peerID);
const hasRights = isChannel && appChatsManager.hasRights(-peerID, 'send');
if(hasRights) this.chatInner.classList.add('has-rights');
else this.chatInner.classList.remove('has-rights');
this.chatInput.style.display = !isChannel || hasRights ? '' : 'none';
this.chatInner.style.visibility = 'hidden'; this.chatInner.style.visibility = 'hidden';
this.chatInput.style.display = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID) ? 'none' : '';
this.topbar.style.display = ''; this.topbar.style.display = '';
if(appPeersManager.isAnyGroup(peerID)) this.chatInner.classList.add('is-chat'); if(appPeersManager.isAnyGroup(peerID)) this.chatInner.classList.add('is-chat');
else this.chatInner.classList.remove('is-chat'); else this.chatInner.classList.remove('is-chat');
if(appPeersManager.isChannel(peerID)) this.chatInner.classList.add('is-channel'); if(isChannel) this.chatInner.classList.add('is-channel');
else this.chatInner.classList.remove('is-channel'); else this.chatInner.classList.remove('is-channel');
this.pinnedMessageContainer.style.display = 'none'; this.pinnedMessageContainer.style.display = 'none';
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
@ -1052,10 +1050,10 @@ export class AppImManager {
this.setPeerStatus(true); this.setPeerStatus(true);
}); });
const isJump = lastMsgID != dialog?.top_message;
// add last message, bc in getHistory will load < max_id // add last message, bc in getHistory will load < max_id
let additionMsgID = 0; const additionMsgID = isJump ? 0 : dialog.top_message;
if(lastMsgID && !forwarding) additionMsgID = lastMsgID;
else if(dialog && dialog.top_message) additionMsgID = dialog.top_message;
/* this.setPeerPromise = null; /* this.setPeerPromise = null;
this.preloader.detach(); this.preloader.detach();
@ -1066,21 +1064,32 @@ export class AppImManager {
console.timeEnd('appImManager setPeer pre promise'); console.timeEnd('appImManager setPeer pre promise');
this.preloader.attach(this.bubblesContainer); this.preloader.attach(this.bubblesContainer);
return this.setPeerPromise = Promise.all([ return this.setPeerPromise = Promise.all([
this.getHistory(forwarding ? lastMsgID + 1 : lastMsgID, true, false, additionMsgID).then(() => { this.getHistory(lastMsgID, true, isJump, additionMsgID).then(() => {
////this.log('setPeer removing preloader'); ////this.log('setPeer removing preloader');
if(lastMsgID) { if(lastMsgID) {
if(!dialog || lastMsgID != dialog.top_message) { if(!dialog || lastMsgID != dialog.top_message) {
let bubble = this.bubbles[lastMsgID]; let bubble = this.bubbles[lastMsgID];
if(bubble) this.bubbles[lastMsgID].scrollIntoView(); let fromUp = maxBubbleID && maxBubbleID < lastMsgID;
else this.log.warn('no bubble by lastMsgID:', lastMsgID); if(bubble) {
if(this.scrollable.scrollLocked) {
clearTimeout(this.scrollable.scrollLocked);
this.scrollable.scrollLocked = 0;
}
this.scrollable.scrollIntoView(bubble, samePeer, fromUp);
} else this.log.warn('no bubble by lastMsgID:', lastMsgID);
} else { } else {
this.log('will scroll down 2'); this.log('will scroll down 2');
this.scroll.scrollTop = this.scroll.scrollHeight; this.scroll.scrollTop = this.scroll.scrollHeight;
} }
} }
if(!lastMsgID || (dialog && dialog.top_message == lastMsgID)) {
this.scrolledAllDown = true;
}
/* this.onScroll(); /* this.onScroll();
this.scrollable.onScroll();*/ this.scrollable.onScroll();*/
@ -1146,17 +1155,8 @@ export class AppImManager {
} }
} }
public deleteMessagesByIDs(msgIDs: number[], forever = true) { public deleteMessagesByIDs(msgIDs: number[]) {
msgIDs.forEach(id => { msgIDs.forEach(id => {
if(this.firstTopMsgID == id && forever) {
let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0];
if(dialog) {
///////this.log('setting firstTopMsgID after delete:', id, dialog.top_message, dialog);
this.firstTopMsgID = dialog.top_message;
}
}
if(!(id in this.bubbles)) return; if(!(id in this.bubbles)) return;
let bubble = this.bubbles[id]; let bubble = this.bubbles[id];
@ -1173,7 +1173,7 @@ export class AppImManager {
} }
public renderNewMessagesByIDs(msgIDs: number[]) { public renderNewMessagesByIDs(msgIDs: number[]) {
if(!this.bubbles[this.firstTopMsgID] && Object.keys(this.bubbles).length) { // seems search active if(!this.scrolledAllDown) { // seems search active or sliced
//////this.log('seems search is active, skipping render:', msgIDs); //////this.log('seems search is active, skipping render:', msgIDs);
return; return;
} }
@ -1385,20 +1385,32 @@ export class AppImManager {
entities: totalEntities entities: totalEntities
}); });
let messageMedia = message.media;
if(totalEntities) { if(totalEntities) {
let emojiEntities = totalEntities.filter((e: any) => e._ == 'messageEntityEmoji'); let emojiEntities = totalEntities.filter((e: any) => e._ == 'messageEntityEmoji');
let strLength = messageMessage.length; let strLength = messageMessage.length;
let emojiStrLength = emojiEntities.reduce((acc: number, curr: any) => acc + curr.length, 0); let emojiStrLength = emojiEntities.reduce((acc: number, curr: any) => acc + curr.length, 0);
if(emojiStrLength == strLength && emojiEntities.length <= 3) { if(emojiStrLength == strLength && emojiEntities.length <= 3) {
let attachmentDiv = document.createElement('div'); let sticker = appStickersManager.getAnimatedEmojiSticker(messageMessage);
attachmentDiv.classList.add('attachment'); if(emojiEntities.length == 1 && !messageMedia && sticker) {
messageMedia = {
attachmentDiv.innerHTML = richText; _: 'messageMediaDocument',
document: sticker
bubble.classList.add('is-message-empty', 'emoji-' + emojiEntities.length + 'x', 'emoji-big'); };
} else {
bubbleContainer.append(attachmentDiv); let attachmentDiv = document.createElement('div');
attachmentDiv.classList.add('attachment');
attachmentDiv.innerHTML = richText;
bubble.classList.add('emoji-' + emojiEntities.length + 'x');
bubbleContainer.append(attachmentDiv);
}
bubble.classList.add('is-message-empty', 'emoji-big');
} else { } else {
messageDiv.innerHTML = richText; messageDiv.innerHTML = richText;
} }
@ -1430,7 +1442,7 @@ export class AppImManager {
} }
// media // media
if(message.media/* && message.media._ == 'messageMediaPhoto' */) { if(messageMedia/* && messageMedia._ == 'messageMediaPhoto' */) {
let attachmentDiv = document.createElement('div'); let attachmentDiv = document.createElement('div');
attachmentDiv.classList.add('attachment'); attachmentDiv.classList.add('attachment');
@ -1440,9 +1452,9 @@ export class AppImManager {
let processingWebPage = false; let processingWebPage = false;
switch(message.media._) { switch(messageMedia._) {
case 'messageMediaPending': { case 'messageMediaPending': {
let pending = message.media; let pending = messageMedia;
let preloader = pending.preloader as ProgressivePreloader; let preloader = pending.preloader as ProgressivePreloader;
switch(pending.type) { switch(pending.type) {
@ -1513,7 +1525,7 @@ export class AppImManager {
} }
case 'messageMediaPhoto': { case 'messageMediaPhoto': {
let photo = message.media.photo; let photo = messageMedia.photo;
////////this.log('messageMediaPhoto', photo); ////////this.log('messageMediaPhoto', photo);
bubble.classList.add('hide-name', 'photo'); bubble.classList.add('hide-name', 'photo');
@ -1541,7 +1553,7 @@ export class AppImManager {
case 'messageMediaWebPage': { case 'messageMediaWebPage': {
processingWebPage = true; processingWebPage = true;
let webpage = message.media.webpage; let webpage = messageMedia.webpage;
////////this.log('messageMediaWebPage', webpage); ////////this.log('messageMediaWebPage', webpage);
if(webpage._ == 'webPageEmpty') { if(webpage._ == 'webPageEmpty') {
break; break;
@ -1630,7 +1642,7 @@ export class AppImManager {
} }
case 'messageMediaDocument': { case 'messageMediaDocument': {
let doc = message.media.document; let doc = messageMedia.document;
//this.log('messageMediaDocument', doc, bubble); //this.log('messageMediaDocument', doc, bubble);
@ -1722,7 +1734,7 @@ export class AppImManager {
let contactDiv = document.createElement('div'); let contactDiv = document.createElement('div');
contactDiv.classList.add('contact'); contactDiv.classList.add('contact');
contactDiv.dataset.peerID = '' + message.media.user_id; contactDiv.dataset.peerID = '' + messageMedia.user_id;
messageDiv.classList.add('contact-message'); messageDiv.classList.add('contact-message');
processingWebPage = true; processingWebPage = true;
@ -1737,10 +1749,11 @@ export class AppImManager {
<div class="contact-number">${message.media.phone_number ? '+' + formatPhoneNumber(message.media.phone_number).formatted : 'Unknown phone number'}</div> <div class="contact-number">${message.media.phone_number ? '+' + formatPhoneNumber(message.media.phone_number).formatted : 'Unknown phone number'}</div>
</div>`; </div>`;
let avatarDiv = document.createElement('div'); let avatarElem = new AvatarElement();
avatarDiv.classList.add('contact-avatar', 'user-avatar'); avatarElem.setAttribute('peer', '' + message.media.user_id);
contactDiv.prepend(avatarDiv); avatarElem.classList.add('contact-avatar');
appProfileManager.putPhoto(avatarDiv, message.media.user_id);
contactDiv.prepend(avatarElem);
bubble.classList.remove('is-message-empty'); bubble.classList.remove('is-message-empty');
messageDiv.append(contactDiv); messageDiv.append(contactDiv);
@ -1841,21 +1854,20 @@ export class AppImManager {
} }
if(!our && this.peerID < 0 && (!appPeersManager.isChannel(this.peerID) || appPeersManager.isMegagroup(this.peerID))) { if(!our && this.peerID < 0 && (!appPeersManager.isChannel(this.peerID) || appPeersManager.isMegagroup(this.peerID))) {
let avatarDiv = document.createElement('div'); let avatarElem = new AvatarElement();
avatarDiv.classList.add('user-avatar'); avatarElem.classList.add('user-avatar');
avatarElem.setAttribute('peer', '' + (message.fromID || 0));
/////////this.log('exec loadDialogPhoto', message); /////////this.log('exec loadDialogPhoto', message);
if(message.fromID) { // if no - user hidden /* if(message.fromID) { // if no - user hidden
appProfileManager.putPhoto(avatarDiv, message.fromID); appProfileManager.putPhoto(avatarDiv, message.fromID);
} else if(!title && message.fwd_from && message.fwd_from.from_name) { } else if(!title && message.fwd_from && message.fwd_from.from_name) {
title = message.fwd_from.from_name; title = message.fwd_from.from_name;
appProfileManager.putPhoto(avatarDiv, 0, false, title); appProfileManager.putPhoto(avatarDiv, 0, false);
} } */
avatarDiv.dataset.peerID = message.fromID; bubbleContainer.append(avatarElem);
bubbleContainer.append(avatarDiv);
} }
} else { } else {
bubble.classList.add('hide-name'); bubble.classList.add('hide-name');
@ -2097,8 +2109,11 @@ export class AppImManager {
let backLimit = 0; let backLimit = 0;
if(isBackLimit) { if(isBackLimit) {
backLimit = loadCount; backLimit = loadCount;
loadCount = 0;
maxID += 1; if(!reverse) { // if not jump
loadCount = 0;
maxID += 1;
}
} }
let result = appMessagesManager.getHistory(this.peerID, maxID, loadCount, backLimit); let result = appMessagesManager.getHistory(this.peerID, maxID, loadCount, backLimit);
@ -2170,7 +2185,7 @@ export class AppImManager {
this.log('getHistory: will slice ids:', ids, reverse); this.log('getHistory: will slice ids:', ids, reverse);
this.deleteMessagesByIDs(ids, false); this.deleteMessagesByIDs(ids);
/* ids.forEach(id => { /* ids.forEach(id => {
this.bubbles[id].remove(); this.bubbles[id].remove();
delete this.bubbles[id]; delete this.bubbles[id];
@ -2222,8 +2237,8 @@ export class AppImManager {
this.btnMenuMute.appendChild(rp); this.btnMenuMute.appendChild(rp);
} }
public mutePeer() { public mutePeer(peerID: number) {
let inputPeer = appPeersManager.getInputPeerByID(this.peerID); let inputPeer = appPeersManager.getInputPeerByID(peerID);
let inputNotifyPeer = { let inputNotifyPeer = {
_: 'inputNotifyPeer', _: 'inputNotifyPeer',
peer: inputPeer peer: inputPeer
@ -2234,10 +2249,16 @@ export class AppImManager {
flags: 0, flags: 0,
mute_until: 0 mute_until: 0
}; };
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
let muted = true;
if(dialog && dialog.notify_settings) {
muted = dialog.notify_settings.mute_until > (Date.now() / 1000 | 0);
}
if(!this.muted) { if(!muted) {
settings.flags |= 1 << 2; settings.flags |= 1 << 2;
settings.mute_until = 2147483646; settings.mute_until = 2147483647;
} else { } else {
settings.flags |= 2; settings.flags |= 2;
} }
@ -2245,7 +2266,7 @@ export class AppImManager {
apiManager.invokeApi('account.updateNotifySettings', { apiManager.invokeApi('account.updateNotifySettings', {
peer: inputNotifyPeer, peer: inputNotifyPeer,
settings: settings settings: settings
}).then(res => { }).then(bool => {
this.handleUpdate({_: 'updateNotifySettings', peer: inputNotifyPeer, notify_settings: settings}); this.handleUpdate({_: 'updateNotifySettings', peer: inputNotifyPeer, notify_settings: settings});
}); });

16
src/lib/appManagers/appMediaViewer.ts

@ -8,13 +8,13 @@ import { findUpClassName, $rootScope, generatePathData, fillPropertyValue } from
import appDocsManager from "./appDocsManager"; import appDocsManager from "./appDocsManager";
import VideoPlayer from "../mediaPlayer"; import VideoPlayer from "../mediaPlayer";
import { renderImageFromUrl } from "../../components/misc"; import { renderImageFromUrl } from "../../components/misc";
import appProfileManager from "./appProfileManager"; import AvatarElement from "../../components/avatar";
export class AppMediaViewer { export class AppMediaViewer {
private overlaysDiv = document.querySelector('.overlays') as HTMLDivElement; private overlaysDiv = document.querySelector('.overlays') as HTMLDivElement;
private mediaViewerDiv = this.overlaysDiv.firstElementChild as HTMLDivElement; private mediaViewerDiv = this.overlaysDiv.firstElementChild as HTMLDivElement;
private author = { private author = {
avatarEl: this.overlaysDiv.querySelector('.user-avatar') as HTMLDivElement, avatarEl: this.overlaysDiv.querySelector('.media-viewer-userpic') as AvatarElement,
nameEl: this.overlaysDiv.querySelector('.media-viewer-name') as HTMLDivElement, nameEl: this.overlaysDiv.querySelector('.media-viewer-name') as HTMLDivElement,
date: this.overlaysDiv.querySelector('.media-viewer-date') as HTMLDivElement date: this.overlaysDiv.querySelector('.media-viewer-date') as HTMLDivElement
}; };
@ -590,7 +590,7 @@ export class AppMediaViewer {
this.content.caption.innerHTML = ''; this.content.caption.innerHTML = '';
} }
appProfileManager.putPhoto(this.author.avatarEl, message.fromID); this.author.avatarEl.setAttribute('peer', '' + message.fromID);
// ok set // ok set
@ -629,6 +629,10 @@ export class AppMediaViewer {
source = video.firstElementChild as HTMLSourceElement; source = video.firstElementChild as HTMLSourceElement;
} }
if(media.type == 'gif') {
video.autoplay = true;
}
video.dataset.ckin = 'default'; video.dataset.ckin = 'default';
video.dataset.overlay = '1'; video.dataset.overlay = '1';
@ -661,9 +665,11 @@ export class AppMediaViewer {
video.append(source); video.append(source);
} }
let player = new VideoPlayer(video, true); if(media.type != 'gif') {
let player = new VideoPlayer(video, true);
}
}); });
} else { } else if(media.type != 'gif') {
let player = new VideoPlayer(video, true); let player = new VideoPlayer(video, true);
} }

250
src/lib/appManagers/appMessagesManager.ts

@ -56,7 +56,8 @@ export type Dialog = {
peerID: number, peerID: number,
pinnedIndex: number, pinnedIndex: number,
pFlags: Partial<{ pFlags: Partial<{
pinned: boolean pinned: true,
unread_mark: true
}>, }>,
pts: number pts: number
} }
@ -1253,8 +1254,8 @@ export class AppMessagesManager {
this.allDialogsLoaded[folderID] = true; this.allDialogsLoaded[folderID] = true;
} }
if(hasPrepend && !this.newDialogsHandlePromise) { if(hasPrepend) {
this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0); this.scheduleHandleNewDialogs();
} else { } else {
$rootScope.$broadcast('dialogs_multiupdate', {}); $rootScope.$broadcast('dialogs_multiupdate', {});
} }
@ -1344,7 +1345,7 @@ export class AppMessagesManager {
topDate = savedDraft.date; topDate = savedDraft.date;
} }
if(dialog.pFlags.pinned) { if(dialog.pFlags.pinned && dialog.folder_id == 0) {
topDate = this.generateDialogPinnedDate(dialog); topDate = this.generateDialogPinnedDate(dialog);
//console.log('topDate', peerID, topDate); //console.log('topDate', peerID, topDate);
} }
@ -1354,15 +1355,15 @@ export class AppMessagesManager {
public pushDialogToStorage(dialog: Dialog, offsetDate?: number) { public pushDialogToStorage(dialog: Dialog, offsetDate?: number) {
let dialogs = this.dialogsStorage.dialogs[dialog.folder_id] ?? (this.dialogsStorage.dialogs[dialog.folder_id] = []); let dialogs = this.dialogsStorage.dialogs[dialog.folder_id] ?? (this.dialogsStorage.dialogs[dialog.folder_id] = []);
let pos = this.getDialogByPeerID(dialog.peerID)[1]; let pos = dialogs.findIndex(d => d.peerID == dialog.peerID);
if(pos !== undefined) { if(pos !== -1) {
dialogs.splice(pos, 1); dialogs.splice(pos, 1);
} }
if(offsetDate && if(offsetDate &&
!dialog.pFlags.pinned && !dialog.pFlags.pinned &&
(!this.dialogsOffsetDate[dialog.folder_id] || offsetDate < this.dialogsOffsetDate[dialog.folder_id])) { (!this.dialogsOffsetDate[dialog.folder_id] || offsetDate < this.dialogsOffsetDate[dialog.folder_id])) {
if(pos !== undefined) { if(pos !== -1) {
// So the dialog jumped to the last position // So the dialog jumped to the last position
return false; return false;
} }
@ -1426,6 +1427,81 @@ export class AppMessagesManager {
}).then(this.applyConversations.bind(this)); }).then(this.applyConversations.bind(this));
} }
private doFlushHistory(inputPeer: any, justClear: boolean): Promise<true> {
let flags = 0;
if(justClear) {
flags |= 1;
}
return apiManager.invokeApi('messages.deleteHistory', {
flags: flags,
peer: inputPeer,
max_id: 0
}).then((affectedHistory) => {
apiUpdatesManager.processUpdateMessage({
_: 'updateShort',
update: {
_: 'updatePts',
pts: affectedHistory.pts,
pts_count: affectedHistory.pts_count
}
});
if(!affectedHistory.offset) {
return true;
}
return this.doFlushHistory(inputPeer, justClear);
})
}
public async flushHistory(peerID: number, justClear: boolean) {
if(AppPeersManager.isChannel(peerID)) {
let promise = this.getHistory(peerID, 0, 1);
let historyResult = promise instanceof Promise ? await promise : promise;
let channelID = -peerID;
let maxID = appMessagesIDsManager.getMessageLocalID(historyResult.history[0] || 0);
return apiManager.invokeApi('channels.deleteHistory', {
channel: appChatsManager.getChannelInput(channelID),
max_id: maxID
}).then(() => {
apiUpdatesManager.processUpdateMessage({
_: 'updateShort',
update: {
_: 'updateChannelAvailableMessages',
channel_id: channelID,
available_min_id: maxID
}
});
return true;
});
}
return this.doFlushHistory(AppPeersManager.getInputPeerByID(peerID), justClear).then(() => {
delete this.historiesStorage[peerID];
for(let mid in this.messagesStorage) {
let message = this.messagesStorage[mid];
if(message.peerID == peerID) {
delete this.messagesStorage[mid];
}
}
if(justClear) {
$rootScope.$broadcast('dialog_flush', {peerID: peerID});
} else {
let foundDialog = this.getDialogByPeerID(peerID);
if(foundDialog[0]) {
this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1);
}
$rootScope.$broadcast('dialog_drop', {peerID: peerID});
}
});
}
public saveMessages(apiMessages: any[], options: { public saveMessages(apiMessages: any[], options: {
isNew?: boolean, isNew?: boolean,
isEdited?: boolean isEdited?: boolean
@ -1651,7 +1727,7 @@ export class AppMessagesManager {
}); });
} }
public getRichReplyText(message: any) { public getRichReplyText(message: any, text: string = message.message) {
let messageText = ''; let messageText = '';
if(message.media) { if(message.media) {
@ -1739,7 +1815,6 @@ export class AppMessagesManager {
messageText = '<i>' + langPack[_] + suffix + '</i>'; messageText = '<i>' + langPack[_] + suffix + '</i>';
} }
let text = message.message;
let messageWrapped = ''; let messageWrapped = '';
if(text) { if(text) {
let entities = RichTextProcessor.parseEntities(text.replace(/\n/g, ' '), {noLinebreakers: true}); let entities = RichTextProcessor.parseEntities(text.replace(/\n/g, ' '), {noLinebreakers: true});
@ -1754,6 +1829,69 @@ export class AppMessagesManager {
return messageText + messageWrapped; return messageText + messageWrapped;
} }
public editPeerFolders(peerIDs: number[], folderID: number) {
apiManager.invokeApi('folders.editPeerFolders', {
folder_peers: peerIDs.map(peerID => {
return {
_: 'inputFolderPeer',
peer: AppPeersManager.getInputPeerByID(peerID),
folder_id: folderID
};
})
}).then(updates => {
console.log('editPeerFolders updates:', updates);
apiUpdatesManager.processUpdateMessage(updates); // WARNING! возможно тут нужно добавлять channelID, и вызывать апдейт для каждого канала отдельно
});
}
public toggleDialogPin(peerID: number) {
let dialog = this.getDialogByPeerID(peerID)[0];
if(!dialog) return Promise.reject();
let peer = {
_: 'inputDialogPeer',
peer: AppPeersManager.getInputPeerByID(peerID)
};
let flags = dialog.pFlags?.pinned ? 0 : 1;
return apiManager.invokeApi('messages.toggleDialogPin', {
flags,
peer
}).then(bool => {
this.handleUpdate({
_: 'updateDialogPinned',
peer: peer,
pFlags: {
pinned: flags
}
});
});
}
public markDialogUnread(peerID: number) {
let dialog = this.getDialogByPeerID(peerID)[0];
if(!dialog) return Promise.reject();
let peer = {
_: 'inputDialogPeer',
peer: AppPeersManager.getInputPeerByID(peerID)
};
let flags = dialog.pFlags?.unread_mark ? 0 : 1;
return apiManager.invokeApi('messages.markDialogUnread', {
flags,
peer
}).then(bool => {
this.handleUpdate({
_: 'updateDialogUnreadMark',
peer: peer,
pFlags: {
unread: flags
}
});
});
}
public migrateChecks(migrateFrom: number, migrateTo: number) { public migrateChecks(migrateFrom: number, migrateTo: number) {
if(!this.migratedFromTo[migrateFrom] && if(!this.migratedFromTo[migrateFrom] &&
!this.migratedToFrom[migrateTo] && !this.migratedToFrom[migrateTo] &&
@ -1931,10 +2069,10 @@ export class AppMessagesManager {
dialog.read_inbox_max_id = appMessagesIDsManager.getFullMessageID(dialog.read_inbox_max_id, channelID); dialog.read_inbox_max_id = appMessagesIDsManager.getFullMessageID(dialog.read_inbox_max_id, channelID);
dialog.read_outbox_max_id = appMessagesIDsManager.getFullMessageID(dialog.read_outbox_max_id, channelID); dialog.read_outbox_max_id = appMessagesIDsManager.getFullMessageID(dialog.read_outbox_max_id, channelID);
this.generateIndexForDialog(dialog); if(!dialog.hasOwnProperty('folder_id')) dialog.folder_id = 0;
dialog.peerID = peerID; dialog.peerID = peerID;
if(!dialog.folder_id) dialog.folder_id = 0;
this.generateIndexForDialog(dialog);
this.pushDialogToStorage(dialog, offsetDate); this.pushDialogToStorage(dialog, offsetDate);
// Because we saved message without dialog present // Because we saved message without dialog present
@ -2283,7 +2421,7 @@ export class AppMessagesManager {
this.newDialogsHandlePromise = 0; this.newDialogsHandlePromise = 0;
let newMaxSeenID = 0; let newMaxSeenID = 0;
Object.keys(this.newDialogsToHandle).forEach((peerID) => { for(let peerID in this.newDialogsToHandle) {
let dialog = this.newDialogsToHandle[peerID]; let dialog = this.newDialogsToHandle[peerID];
if('reload' in dialog) { if('reload' in dialog) {
this.reloadConversation(+peerID); this.reloadConversation(+peerID);
@ -2294,7 +2432,7 @@ export class AppMessagesManager {
newMaxSeenID = Math.max(newMaxSeenID, dialog.top_message || 0); newMaxSeenID = Math.max(newMaxSeenID, dialog.top_message || 0);
} }
} }
}); }
console.log('after order:', this.dialogsStorage.dialogs[0].map(d => d.peerID)); console.log('after order:', this.dialogsStorage.dialogs[0].map(d => d.peerID));
@ -2306,6 +2444,12 @@ export class AppMessagesManager {
this.newDialogsToHandle = {}; this.newDialogsToHandle = {};
} }
public scheduleHandleNewDialogs() {
if(!this.newDialogsHandlePromise) {
this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0);
}
}
public readHistory(peerID: number, maxID = 0, minID = 0): Promise<boolean> { public readHistory(peerID: number, maxID = 0, minID = 0): Promise<boolean> {
// console.trace('start read') // console.trace('start read')
var isChannel = AppPeersManager.isChannel(peerID); var isChannel = AppPeersManager.isChannel(peerID);
@ -2486,9 +2630,7 @@ export class AppMessagesManager {
if(!foundDialog.length) { if(!foundDialog.length) {
this.newDialogsToHandle[peerID] = {reload: true} this.newDialogsToHandle[peerID] = {reload: true}
if(!this.newDialogsHandlePromise) { this.scheduleHandleNewDialogs();
this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0);
}
if(this.newUpdatesAfterReloadToHandle[peerID] === undefined) { if(this.newUpdatesAfterReloadToHandle[peerID] === undefined) {
this.newUpdatesAfterReloadToHandle[peerID] = []; this.newUpdatesAfterReloadToHandle[peerID] = [];
} }
@ -2572,22 +2714,66 @@ export class AppMessagesManager {
} }
this.newDialogsToHandle[peerID] = dialog; this.newDialogsToHandle[peerID] = dialog;
if(!this.newDialogsHandlePromise) { this.scheduleHandleNewDialogs();
this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0);
break;
}
case 'updateDialogUnreadMark': {
console.log('updateDialogUnreadMark', update);
let peerID = AppPeersManager.getPeerID(update.peer.peer);
let foundDialog = this.getDialogByPeerID(peerID);
if(!foundDialog.length) {
this.newDialogsToHandle[peerID] = {reload: true};
this.scheduleHandleNewDialogs();
} else {
let dialog = foundDialog[0];
if(!update.pFlags.unread) {
delete dialog.pFlags.unread_mark;
} else {
dialog.pFlags.unread_mark = true;
}
$rootScope.$broadcast('dialogs_multiupdate', {peerID: dialog});
} }
break; break;
} }
case 'updateFolderPeers': {
console.log('updateFolderPeers', update);
let peers = update.folder_peers;
this.scheduleHandleNewDialogs();
peers.forEach((folderPeer: any) => {
let {folder_id, peer} = folderPeer;
let peerID = AppPeersManager.getPeerID(peer);
let foundDialog = this.getDialogByPeerID(peerID);
if(!foundDialog.length) {
this.newDialogsToHandle[peerID] = {reload: true};
} else {
let dialog = foundDialog[0];
this.newDialogsToHandle[peerID] = dialog;
this.dialogsStorage.dialogs[dialog.folder_id].splice(foundDialog[1], 1);
dialog.folder_id = folder_id;
this.generateIndexForDialog(dialog);
this.pushDialogToStorage(dialog); // need for simultaneously updatePinnedDialogs
}
});
break;
}
case 'updateDialogPinned': { case 'updateDialogPinned': {
console.log('updateDialogPinned', update); console.log('updateDialogPinned', update);
let peerID = AppPeersManager.getPeerID(update.peer.peer); let peerID = AppPeersManager.getPeerID(update.peer.peer);
let foundDialog = this.getDialogByPeerID(peerID); let foundDialog = this.getDialogByPeerID(peerID);
if(!this.newDialogsHandlePromise) { this.scheduleHandleNewDialogs();
this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0);
}
if(!foundDialog.length) { if(!foundDialog.length) {
this.newDialogsToHandle[peerID] = {reload: true}; this.newDialogsToHandle[peerID] = {reload: true};
break; break;
@ -2597,6 +2783,7 @@ export class AppMessagesManager {
if(!update.pFlags.pinned) { if(!update.pFlags.pinned) {
delete dialog.pFlags.pinned; delete dialog.pFlags.pinned;
delete dialog.pinnedIndex;
} else { // means set } else { // means set
dialog.pFlags.pinned = true; dialog.pFlags.pinned = true;
} }
@ -2623,9 +2810,7 @@ export class AppMessagesManager {
let peerID = dialog.peerID; let peerID = dialog.peerID;
if(dialog.pFlags.pinned && !newPinned[peerID]) { if(dialog.pFlags.pinned && !newPinned[peerID]) {
this.newDialogsToHandle[peerID] = {reload: true}; this.newDialogsToHandle[peerID] = {reload: true};
if(!this.newDialogsHandlePromise) { this.scheduleHandleNewDialogs();
this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0);
}
} }
}); });
}); });
@ -2665,8 +2850,8 @@ export class AppMessagesManager {
} }
}); });
if(willHandle && !this.newDialogsHandlePromise) { if(willHandle) {
this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0); this.scheduleHandleNewDialogs();
} }
break; break;
@ -3119,7 +3304,7 @@ export class AppMessagesManager {
if(!offsetNotFound && ( if(!offsetNotFound && (
historyStorage.count !== null && historyStorage.history.length == historyStorage.count || historyStorage.count !== null && historyStorage.history.length == historyStorage.count ||
historyStorage.history.length >= offset + (limit || 1) historyStorage.history.length >= offset + limit
)) { )) {
if(backLimit) { if(backLimit) {
backLimit = Math.min(offset, backLimit); backLimit = Math.min(offset, backLimit);
@ -3134,7 +3319,7 @@ export class AppMessagesManager {
history = historyStorage.pending.slice().concat(history); history = historyStorage.pending.slice().concat(history);
} }
return this.wrapHistoryResult(peerID, { return this.wrapHistoryResult({
count: historyStorage.count, count: historyStorage.count,
history: history, history: history,
unreadOffset: unreadOffset, unreadOffset: unreadOffset,
@ -3166,7 +3351,7 @@ export class AppMessagesManager {
history = historyStorage.pending.slice().concat(history); history = historyStorage.pending.slice().concat(history);
} }
return this.wrapHistoryResult(peerID, { return this.wrapHistoryResult({
count: historyStorage.count, count: historyStorage.count,
history: history, history: history,
unreadOffset: unreadOffset, unreadOffset: unreadOffset,
@ -3190,7 +3375,7 @@ export class AppMessagesManager {
history = historyStorage.pending.slice().concat(history); history = historyStorage.pending.slice().concat(history);
} }
return this.wrapHistoryResult(peerID, { return this.wrapHistoryResult({
count: historyStorage.count, count: historyStorage.count,
history: history, history: history,
unreadOffset: unreadOffset, unreadOffset: unreadOffset,
@ -3262,7 +3447,7 @@ export class AppMessagesManager {
}); });
} }
public wrapHistoryResult(peerID: number, result: HistoryResult) { public wrapHistoryResult(result: HistoryResult) {
var unreadOffset = result.unreadOffset; var unreadOffset = result.unreadOffset;
if(unreadOffset) { if(unreadOffset) {
var i; var i;
@ -3276,7 +3461,6 @@ export class AppMessagesManager {
} }
} }
return result; return result;
//return Promise.resolve(result);
} }
public requestHistory(peerID: number, maxID: number, limit: number, offset = 0): Promise<any> { public requestHistory(peerID: number, maxID: number, limit: number, offset = 0): Promise<any> {

232
src/lib/appManagers/appSidebarLeft.ts

@ -13,6 +13,10 @@ import { appPeersManager } from "../services";
import popupAvatar from "../../components/popupAvatar"; import popupAvatar from "../../components/popupAvatar";
import appChatsManager from "./appChatsManager"; import appChatsManager from "./appChatsManager";
import { AppSelectPeers } from "../../components/appSelectPeers"; import { AppSelectPeers } from "../../components/appSelectPeers";
import AvatarElement from "../../components/avatar";
import appProfileManager from "./appProfileManager";
AvatarElement;
const SLIDERITEMSIDS = { const SLIDERITEMSIDS = {
archived: 1, archived: 1,
@ -20,6 +24,8 @@ const SLIDERITEMSIDS = {
newChannel: 3, newChannel: 3,
addMembers: 4, addMembers: 4,
newGroup: 5, newGroup: 5,
settings: 6,
editProfile: 7,
}; };
interface SliderTab { interface SliderTab {
@ -317,6 +323,215 @@ class AppContactsTab implements SliderTab {
} }
} }
class AppSettingsTab implements SliderTab {
private container = document.querySelector('.settings-container') as HTMLDivElement;
private avatarElem = this.container.querySelector('.profile-avatar') as AvatarElement;
private nameDiv = this.container.querySelector('.profile-name') as HTMLDivElement;
private phoneDiv = this.container.querySelector('.profile-subtitle') as HTMLDivElement;
private logOutBtn = this.container.querySelector('.menu-logout') as HTMLButtonElement;
private buttons: {
edit: HTMLButtonElement,
general: HTMLButtonElement,
notifications: HTMLButtonElement,
privacy: HTMLButtonElement,
language: HTMLButtonElement
} = {} 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;
});
$rootScope.$on('user_auth', (e: CustomEvent) => {
this.fillElements();
});
this.logOutBtn.addEventListener('click', (e) => {
apiManager.logOut();
});
this.buttons.edit.addEventListener('click', () => {
appSidebarLeft.selectTab(SLIDERITEMSIDS.editProfile);
appSidebarLeft.editProfileTab.fillElements();
});
}
public fillElements() {
let user = appUsersManager.getSelf();
this.avatarElem.setAttribute('peer', '' + user.id);
this.nameDiv.innerHTML = user.rFullName || '';
this.phoneDiv.innerHTML = user.rPhone || '';
}
onClose() {
}
}
class AppEditProfileTab implements SliderTab {
private container = document.querySelector('.edit-profile-container') as HTMLDivElement;
private scrollWrapper = this.container.querySelector('.scroll-wrapper') as HTMLDivElement;
private nextBtn = this.container.querySelector('.btn-corner') as HTMLButtonElement;
private canvas = this.container.querySelector('.avatar-edit-canvas') as HTMLCanvasElement;
private uploadAvatar: () => Promise<any> = null;
private firstNameInput = this.container.querySelector('.firstname') as HTMLInputElement;
private lastNameInput = this.container.querySelector('.lastname') as HTMLInputElement;
private bioInput = this.container.querySelector('.bio') as HTMLInputElement;
private userNameInput = this.container.querySelector('.username') as HTMLInputElement;
private avatarElem = document.createElement('avatar-element');
private originalValues = {
firstName: '',
lastName: '',
userName: '',
bio: ''
};
constructor() {
this.container.querySelector('.avatar-edit').addEventListener('click', () => {
popupAvatar.open(this.canvas, (_upload) => {
this.uploadAvatar = _upload;
this.handleChange();
this.avatarElem.remove();
});
});
this.avatarElem.classList.add('avatar-placeholder');
let userNameLabel = this.userNameInput.nextElementSibling as HTMLLabelElement;
this.firstNameInput.addEventListener('input', () => this.handleChange());
this.lastNameInput.addEventListener('input', () => this.handleChange());
this.bioInput.addEventListener('input', () => this.handleChange());
this.userNameInput.addEventListener('input', () => {
this.handleChange();
let value = this.userNameInput.value;
console.log('userNameInput:', value);
if(value == this.originalValues.userName) {
this.userNameInput.classList.remove('valid', 'error');
userNameLabel.innerText = 'Username (optional)';
return;
} else if(value.length < 5 || value.length > 32 || !/^[a-zA-Z0-9_]+$/.test(value)) { // does not check the last underscore
this.userNameInput.classList.add('error');
this.userNameInput.classList.remove('valid');
userNameLabel.innerText = 'Username is invalid';
} else {
this.userNameInput.classList.remove('error');
/* */
}
apiManager.invokeApi('account.checkUsername', {
username: value
}).then(available => {
if(this.userNameInput.value != value) return;
if(available) {
this.userNameInput.classList.add('valid');
this.userNameInput.classList.remove('error');
userNameLabel.innerText = 'Username is available';
} else {
this.userNameInput.classList.add('error');
this.userNameInput.classList.remove('valid');
userNameLabel.innerText = 'Username is already taken';
}
}, (err) => {
if(this.userNameInput.value != value) return;
switch(err.type) {
case 'USERNAME_INVALID': {
this.userNameInput.classList.add('error');
this.userNameInput.classList.remove('valid');
userNameLabel.innerText = 'Username is invalid';
break;
}
}
});
});
this.nextBtn.addEventListener('click', () => {
this.nextBtn.disabled = true;
let promises: Promise<any>[] = [];
promises.push(appProfileManager.updateProfile(this.firstNameInput.value, this.lastNameInput.value, this.bioInput.value).then(() => {
appSidebarLeft.selectTab(0);
}, (err) => {
console.error('updateProfile error:', err);
}));
if(this.uploadAvatar) {
promises.push(this.uploadAvatar().then(inputFile => {
appProfileManager.uploadProfilePhoto(inputFile);
}));
}
if(this.userNameInput.value != this.originalValues.userName && this.userNameInput.classList.contains('valid')) {
promises.push(appProfileManager.updateUsername(this.userNameInput.value));
}
Promise.race(promises).then(() => {
this.nextBtn.disabled = false;
}, () => {
this.nextBtn.disabled = false;
});
});
let scrollable = new Scrollable(this.scrollWrapper as HTMLElement, 'y');
}
public fillElements() {
let user = appUsersManager.getSelf();
this.firstNameInput.value = this.originalValues.firstName = user.first_name ?? '';
this.lastNameInput.value = this.originalValues.lastName = user.last_name ?? '';
this.userNameInput.value = this.originalValues.userName = user.username ?? '';
this.userNameInput.classList.remove('valid', 'error');
this.userNameInput.nextElementSibling.innerHTML = 'Username (optional)';
appProfileManager.getProfile(user.id).then(userFull => {
if(userFull.rAbout) {
this.bioInput.value = this.originalValues.bio = userFull.rAbout;
}
});
this.avatarElem.setAttribute('peer', '' + $rootScope.myID);
if(!this.avatarElem.parentElement) {
this.canvas.parentElement.append(this.avatarElem);
}
this.uploadAvatar = null;
}
private isChanged() {
return !!this.uploadAvatar
|| this.firstNameInput.value != this.originalValues.firstName
|| this.lastNameInput.value != this.originalValues.lastName
|| this.userNameInput.value != this.originalValues.userName
|| this.bioInput.value != this.originalValues.bio;
}
private handleChange() {
if(this.isChanged()) {
this.nextBtn.classList.add('is-visible');
} else {
this.nextBtn.classList.remove('is-visible');
}
}
onCloseAfterTimeout() {
this.nextBtn.classList.remove('is-visible');
}
}
class AppSidebarLeft { class AppSidebarLeft {
private sidebarEl = document.getElementById('column-left') as HTMLDivElement; private sidebarEl = document.getElementById('column-left') as HTMLDivElement;
private toolsBtn = this.sidebarEl.querySelector('.sidebar-tools-button') as HTMLButtonElement; private toolsBtn = this.sidebarEl.querySelector('.sidebar-tools-button') as HTMLButtonElement;
@ -329,7 +544,7 @@ class AppSidebarLeft {
private contactsBtn = this.menuEl.querySelector('.menu-contacts'); private contactsBtn = this.menuEl.querySelector('.menu-contacts');
private archivedBtn = this.menuEl.querySelector('.menu-archive'); private archivedBtn = this.menuEl.querySelector('.menu-archive');
private savedBtn = this.menuEl.querySelector('.menu-saved'); private savedBtn = this.menuEl.querySelector('.menu-saved');
private logOutBtn = this.menuEl.querySelector('.menu-logout'); private settingsBtn = this.menuEl.querySelector('.menu-settings');
public archivedCount = this.archivedBtn.querySelector('.archived-count') as HTMLSpanElement; public archivedCount = this.archivedBtn.querySelector('.archived-count') as HTMLSpanElement;
private newBtnMenu = this.sidebarEl.querySelector('#new-menu'); private newBtnMenu = this.sidebarEl.querySelector('#new-menu');
@ -343,12 +558,16 @@ class AppSidebarLeft {
public addMembersTab = new AppAddMembersTab(); public addMembersTab = new AppAddMembersTab();
public contactsTab = new AppContactsTab(); public contactsTab = new AppContactsTab();
public newGroupTab = new AppNewGroupTab(); public newGroupTab = new AppNewGroupTab();
public settingsTab = new AppSettingsTab();
public editProfileTab = new AppEditProfileTab();
private tabs: {[id: number]: SliderTab} = { private tabs: {[id: number]: SliderTab} = {
[SLIDERITEMSIDS.newChannel]: this.newChannelTab, [SLIDERITEMSIDS.newChannel]: this.newChannelTab,
[SLIDERITEMSIDS.contacts]: this.contactsTab, [SLIDERITEMSIDS.contacts]: this.contactsTab,
[SLIDERITEMSIDS.addMembers]: this.addMembersTab, [SLIDERITEMSIDS.addMembers]: this.addMembersTab,
[SLIDERITEMSIDS.newGroup]: this.newGroupTab, [SLIDERITEMSIDS.newGroup]: this.newGroupTab,
[SLIDERITEMSIDS.settings]: this.settingsTab,
[SLIDERITEMSIDS.editProfile]: this.editProfileTab,
}; };
//private log = logger('SL'); //private log = logger('SL');
@ -388,8 +607,9 @@ class AppSidebarLeft {
this.contactsTab.openContacts(); this.contactsTab.openContacts();
}); });
this.logOutBtn.addEventListener('click', (e) => { this.settingsBtn.addEventListener('click', () => {
apiManager.logOut(); this.settingsTab.fillElements();
this.selectTab(SLIDERITEMSIDS.settings);
}); });
this.searchInput.addEventListener('focus', (e) => { this.searchInput.addEventListener('focus', (e) => {
@ -399,13 +619,13 @@ class AppSidebarLeft {
void this.searchContainer.offsetWidth; // reflow void this.searchContainer.offsetWidth; // reflow
this.searchContainer.classList.add('active'); this.searchContainer.classList.add('active');
false && this.searchInput.addEventListener('blur', (e) => { /* this.searchInput.addEventListener('blur', (e) => {
if(!this.searchInput.value) { if(!this.searchInput.value) {
this.toolsBtn.classList.add('active'); this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active'); this.backBtn.classList.remove('active');
this.backBtn.click(); this.backBtn.click();
} }
}, {once: true}); }, {once: true}); */
}); });
this.backBtn.addEventListener('click', (e) => { this.backBtn.addEventListener('click', (e) => {
@ -446,7 +666,7 @@ class AppSidebarLeft {
console.log('sidebar-close-button click:', this.historyTabIDs); console.log('sidebar-close-button click:', this.historyTabIDs);
let closingID = this.historyTabIDs.pop(); // pop current let closingID = this.historyTabIDs.pop(); // pop current
this.onCloseTab(closingID); this.onCloseTab(closingID);
this._selectTab(this.historyTabIDs.pop() || 0); this._selectTab(this.historyTabIDs[this.historyTabIDs.length - 1] || 0);
}; };
Array.from(this.sidebarEl.querySelectorAll('.sidebar-close-button') as any as HTMLElement[]).forEach(el => { Array.from(this.sidebarEl.querySelectorAll('.sidebar-close-button') as any as HTMLElement[]).forEach(el => {
el.addEventListener('click', onCloseBtnClick); el.addEventListener('click', onCloseBtnClick);

9
src/lib/appManagers/appSidebarRight.ts

@ -14,6 +14,7 @@ import appMediaViewer from "./appMediaViewer";
import LazyLoadQueue from "../../components/lazyLoadQueue"; import LazyLoadQueue from "../../components/lazyLoadQueue";
import { wrapDocument, wrapAudio } from "../../components/wrappers"; import { wrapDocument, wrapAudio } from "../../components/wrappers";
import AppSearch, { SearchGroup } from "../../components/appSearch"; import AppSearch, { SearchGroup } from "../../components/appSearch";
import AvatarElement from "../../components/avatar";
const testScroll = false; const testScroll = false;
@ -23,7 +24,7 @@ class AppSidebarRight {
public profileContentEl = this.sidebarEl.querySelector('.profile-content') as HTMLDivElement; public profileContentEl = this.sidebarEl.querySelector('.profile-content') as HTMLDivElement;
public contentContainer = this.sidebarEl.querySelector('.content-container') as HTMLDivElement; public contentContainer = this.sidebarEl.querySelector('.content-container') as HTMLDivElement;
public profileElements = { public profileElements = {
avatar: this.profileContentEl.querySelector('.profile-avatar') as HTMLDivElement, avatar: this.profileContentEl.querySelector('.profile-avatar') as AvatarElement,
name: this.profileContentEl.querySelector('.profile-name') as HTMLDivElement, name: this.profileContentEl.querySelector('.profile-name') as HTMLDivElement,
subtitle: this.profileContentEl.querySelector('.profile-subtitle') as HTMLDivElement, subtitle: this.profileContentEl.querySelector('.profile-subtitle') as HTMLDivElement,
bio: this.profileContentEl.querySelector('.profile-row-bio') as HTMLDivElement, bio: this.profileContentEl.querySelector('.profile-row-bio') as HTMLDivElement,
@ -162,7 +163,7 @@ class AppSidebarRight {
this.profileElements.notificationsCheckbox.addEventListener('change', () => { this.profileElements.notificationsCheckbox.addEventListener('change', () => {
//let checked = this.profileElements.notificationsCheckbox.checked; //let checked = this.profileElements.notificationsCheckbox.checked;
appImManager.mutePeer(); appImManager.mutePeer(this.peerID);
}); });
window.addEventListener('resize', this.onSidebarScroll.bind(this)); window.addEventListener('resize', this.onSidebarScroll.bind(this));
@ -576,6 +577,8 @@ class AppSidebarRight {
//this.log('fillProfileElements'); //this.log('fillProfileElements');
this.contentContainer.classList.remove('loaded'); this.contentContainer.classList.remove('loaded');
this.profileElements.avatar.setAttribute('peer', '' + peerID);
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
this.profileContentEl.parentElement.scrollTop = 0; this.profileContentEl.parentElement.scrollTop = 0;
@ -660,7 +663,7 @@ class AppSidebarRight {
if(peerID > 0) { if(peerID > 0) {
let user = appUsersManager.getUser(peerID); let user = appUsersManager.getUser(peerID);
if(user.phone && peerID != appImManager.myID) { if(user.phone && peerID != appImManager.myID) {
setText('+' + formatPhoneNumber(user.phone).formatted, this.profileElements.phone); setText(user.rPhone, this.profileElements.phone);
} }
appProfileManager.getProfile(peerID, true).then(userFull => { appProfileManager.getProfile(peerID, true).then(userFull => {

28
src/lib/appManagers/appStickersManager.ts

@ -43,7 +43,7 @@ export type MTStickerSetFull = {
documents: MTDocument[] documents: MTDocument[]
}; };
class appStickersManager { class AppStickersManager {
private documents: { private documents: {
[fileID: string]: MTDocument [fileID: string]: MTDocument
} = {}; } = {};
@ -69,8 +69,12 @@ class appStickersManager {
this.stickerSets = sets; this.stickerSets = sets;
} }
if(!this.stickerSets['emoji']) {
this.getStickerSet({id: 'emoji', access_hash: ''});
}
}); });
} }
public saveSticker(doc: MTDocument) { public saveSticker(doc: MTDocument) {
if(this.documents[doc.id]) return this.documents[doc.id]; if(this.documents[doc.id]) return this.documents[doc.id];
@ -99,7 +103,9 @@ class appStickersManager {
if(this.stickerSets[set.id]) return this.stickerSets[set.id]; if(this.stickerSets[set.id]) return this.stickerSets[set.id];
let promise = apiManager.invokeApi('messages.getStickerSet', { let promise = apiManager.invokeApi('messages.getStickerSet', {
stickerset: { stickerset: set.id == 'emoji' ? {
_: 'inputStickerSetAnimatedEmoji'
} : {
_: 'inputStickerSetID', _: 'inputStickerSetID',
id: set.id, id: set.id,
access_hash: set.access_hash access_hash: set.access_hash
@ -114,19 +120,23 @@ class appStickersManager {
documents: MTDocument[] documents: MTDocument[]
} = res as any; } = res as any;
this.saveStickerSet(stickerSet); this.saveStickerSet(stickerSet, set.id);
return stickerSet; return stickerSet;
} }
public getAnimatedEmojiSticker(emoji: string) {
let stickerSet = this.stickerSets.emoji;
return stickerSet.documents.find(doc => doc.attributes.find(attribute => attribute.alt == emoji));
}
public async saveStickerSet(res: { public async saveStickerSet(res: {
_: "messages.stickerSet", _: "messages.stickerSet",
set: MTStickerSet, set: MTStickerSet,
packs: any[], packs: any[],
documents: MTDocument[] documents: MTDocument[]
}) { }, id: string) {
let id = res.set.id;
//console.log('stickers save set', res); //console.log('stickers save set', res);
this.stickerSets[id] = { this.stickerSets[id] = {
@ -171,4 +181,6 @@ class appStickersManager {
} }
} }
export default new appStickersManager(); const appStickersManager = new AppStickersManager();
(window as any).appStickersManager = appStickersManager;
export default appStickersManager;

12
src/lib/appManagers/appUsersManager.ts

@ -4,6 +4,7 @@ import appChatsManager from "./appChatsManager";
//import apiManager from '../mtproto/apiManager'; //import apiManager from '../mtproto/apiManager';
import apiManager from '../mtproto/mtprotoworker'; import apiManager from '../mtproto/mtprotoworker';
import serverTimeManager from "../mtproto/serverTimeManager"; import serverTimeManager from "../mtproto/serverTimeManager";
import { formatPhoneNumber } from "../../components/misc";
export type User = { export type User = {
_: 'user', _: 'user',
@ -28,6 +29,7 @@ export type User = {
pFlags: Partial<{verified: boolean, support: boolean, self: boolean, bot: boolean, min: number, deleted: boolean}>, pFlags: Partial<{verified: boolean, support: boolean, self: boolean, bot: boolean, min: number, deleted: boolean}>,
rFirstName?: string, rFirstName?: string,
rFullName?: string, rFullName?: string,
rPhone?: string,
sortName?: string, sortName?: string,
sortStatus?: number, sortStatus?: number,
}; };
@ -97,6 +99,7 @@ export class AppUsersManager {
} }
$rootScope.$broadcast('user_update', userID); $rootScope.$broadcast('user_update', userID);
$rootScope.$broadcast('avatar_update', userID);
} else console.warn('No user by id:', userID); } else console.warn('No user by id:', userID);
break break
@ -204,11 +207,9 @@ export class AppUsersManager {
} }
if(apiUser.phone) { if(apiUser.phone) {
//apiUser.rPhone = $filter('phoneNumber')(apiUser.phone); // warning apiUser.rPhone = '+' + formatPhoneNumber(apiUser.phone).formatted;
} }
apiUser.num = (Math.abs(userID) % 8) + 1;
if(apiUser.first_name) { if(apiUser.first_name) {
apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.first_name, {noLinks: true, noLinebreaks: true}) apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.first_name, {noLinks: true, noLinebreaks: true})
apiUser.rFullName = apiUser.last_name ? RichTextProcessor.wrapRichText(apiUser.first_name + ' ' + (apiUser.last_name || ''), {noLinks: true, noLinebreaks: true}) : apiUser.rFirstName; apiUser.rFullName = apiUser.last_name ? RichTextProcessor.wrapRichText(apiUser.first_name + ' ' + (apiUser.last_name || ''), {noLinks: true, noLinebreaks: true}) : apiUser.rFirstName;
@ -222,8 +223,7 @@ export class AppUsersManager {
this.usernames[searchUsername] = userID; this.usernames[searchUsername] = userID;
} }
//apiUser.sortName = apiUser.pFlags.deleted ? '' : SearchIndexManager.cleanSearchText(apiUser.first_name + ' ' + (apiUser.last_name || '')); apiUser.sortName = apiUser.pFlags.deleted ? '' : SearchIndexManager.cleanSearchText(apiUser.first_name + ' ' + (apiUser.last_name || ''), false);
apiUser.sortName = apiUser.pFlags.deleted ? '' : apiUser.first_name + ' ' + (apiUser.last_name || '');
var nameWords = apiUser.sortName.split(' '); var nameWords = apiUser.sortName.split(' ');
var firstWord = nameWords.shift(); var firstWord = nameWords.shift();
@ -290,7 +290,7 @@ export class AppUsersManager {
return id; return id;
} }
return this.users[id] || {id: id, pFlags: {deleted: true}, num: 1, access_hash: this.userAccess[id]} as User; return this.users[id] || {id: id, pFlags: {deleted: true}, access_hash: this.userAccess[id]} as User;
} }
public getSelf() { public getSelf() {

204
src/lib/utils.js

@ -6,30 +6,31 @@
*/ */
var _logTimer = Date.now(); var _logTimer = Date.now();
export function dT () { export function dT () {
return '[' + ((Date.now() - _logTimer) / 1000).toFixed(3) + ']' return '[' + ((Date.now() - _logTimer) / 1000).toFixed(3) + ']';
} }
export function checkClick (e, noprevent) { export function checkClick(e, noprevent) {
if (e.which == 1 && (e.ctrlKey || e.metaKey) || e.which == 2) { if(e.which == 1 && (e.ctrlKey || e.metaKey) || e.which == 2) {
return true return true;
} }
if (!noprevent) { if(!noprevent) {
e.preventDefault() e.preventDefault();
} }
return false return false;
} }
export function isInDOM (element, parentNode) { export function isInDOM(element, parentNode) {
if (!element) { if(!element) {
return false return false;
} }
parentNode = parentNode || document.body
if (element == parentNode) { parentNode = parentNode || document.body;
return true if(element == parentNode) {
return true;
} }
return isInDOM(element.parentNode, parentNode) return isInDOM(element.parentNode, parentNode);
} }
export function checkDragEvent(e) { export function checkDragEvent(e) {
@ -48,17 +49,17 @@ export function checkDragEvent(e) {
} }
export function cancelEvent (event) { export function cancelEvent (event) {
event = event || window.event event = event || window.event;
if (event) { if(event) {
event = event.originalEvent || event event = event.originalEvent || event;
if (event.stopPropagation) event.stopPropagation() if (event.stopPropagation) event.stopPropagation();
if (event.preventDefault) event.preventDefault() if (event.preventDefault) event.preventDefault();
event.returnValue = false event.returnValue = false;
event.cancelBubble = true event.cancelBubble = true;
} }
return false return false;
} }
export function setFieldSelection (field, from, to) { export function setFieldSelection (field, from, to) {
@ -370,7 +371,8 @@ export const langPack = {
"messageActionChatLeave": "left the group", "messageActionChatLeave": "left the group",
"messageActionChatDeleteUser": "removed user", "messageActionChatDeleteUser": "removed user",
"messageActionChatJoinedByLink": "joined the group", "messageActionChatJoinedByLink": "joined the group",
"messageActionPinMessage": "pinned message", "messageActionPinMessage": "pinned message",
"messageActionContactSignUp": "joined Telegram",
"messageActionChannelCreate": "Channel created", "messageActionChannelCreate": "Channel created",
"messageActionChannelEditTitle": "Channel renamed", "messageActionChannelEditTitle": "Channel renamed",
"messageActionChannelEditPhoto": "Channel photo updated", "messageActionChannelEditPhoto": "Channel photo updated",
@ -475,23 +477,6 @@ export function isScrolledIntoView(el) {
return isVisible; return isVisible;
} }
/* export function isScrolledIntoView(el) {
var rect = el.getBoundingClientRect(), top = rect.top, height = rect.height,
el = el.parentNode
// Check if bottom of the element is off the page
if (rect.bottom < 0) return false
// Check its within the document viewport
if (top > document.documentElement.clientHeight) return false
do {
rect = el.getBoundingClientRect()
if (top <= rect.bottom === false) return false
// Check if the element is out of view due to a container scrolling
if ((top + height) <= rect.top) return false
el = el.parentNode
} while (el != document.body)
return true
}; */
export function whichChild(elem/* : Node */) { export function whichChild(elem/* : Node */) {
let i = 0; let i = 0;
// @ts-ignore // @ts-ignore
@ -529,30 +514,6 @@ export function copy(obj) {
return clonedObj; return clonedObj;
} }
/* export function ripple(elem) {
elem.addEventListener('mousedown', function(e) {
let rect = this.getBoundingClientRect();
const startTime = Date.now();
const animationTime = 350;
let X = e.clientX - rect.left;
let Y = e.clientY - rect.top;
let rippleDiv = document.createElement("div");
rippleDiv.classList.add("ripple");
rippleDiv.setAttribute("style", "top:" + Y + "px; left:" + X + "px;");
this.appendChild(rippleDiv);
elem.addEventListener('mouseup', () => {
let elapsed = Date.now() - startTime;
setTimeout(() => {
rippleDiv.parentElement.removeChild(rippleDiv);
}, elapsed < animationTime ? animationTime - elapsed : 0);
}, {once: true});
});
}; */
export function formatBytes(bytes, decimals = 2) { export function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes'; if (bytes === 0) return '0 Bytes';
@ -803,94 +764,97 @@ function versionCompare (ver1, ver2) {
} }
} }
function cleanSearchText (text) { function cleanSearchText(text, latinize = true) {
var hasTag = text.charAt(0) == '%' var hasTag = text.charAt(0) == '%';
text = text.replace(badCharsRe, ' ').replace(trimRe, '') text = text.replace(badCharsRe, ' ').replace(trimRe, '');
text = text.replace(/[^A-Za-z0-9]/g, function (ch) { if(latinize) {
var latinizeCh = Config.LatinizeMap[ch] text = text.replace(/[^A-Za-z0-9]/g, (ch) => {
return latinizeCh !== undefined ? latinizeCh : ch var latinizeCh = Config.LatinizeMap[ch];
}) return latinizeCh !== undefined ? latinizeCh : ch;
text = text.toLowerCase() });
if (hasTag) { }
text = '%' + text
text = text.toLowerCase();
if(hasTag) {
text = '%' + text;
} }
return text return text;
} }
function cleanUsername (username) { function cleanUsername(username) {
return username && username.toLowerCase() || '' return username && username.toLowerCase() || '';
} }
function indexObject (id, searchText, searchIndex) { function indexObject(id, searchText, searchIndex) {
if (searchIndex.fullTexts[id] !== undefined) { if(searchIndex.fullTexts[id] !== undefined) {
return false return false;
} }
searchText = cleanSearchText(searchText) searchText = cleanSearchText(searchText);
if (!searchText.length) { if(!searchText.length) {
return false return false;
} }
var shortIndexes = searchIndex.shortIndexes var shortIndexes = searchIndex.shortIndexes;
searchIndex.fullTexts[id] = searchText searchIndex.fullTexts[id] = searchText;
searchText.split(' ').forEach(function(searchWord) { searchText.split(' ').forEach((searchWord) => {
var len = Math.min(searchWord.length, 3), var len = Math.min(searchWord.length, 3),
wordPart, i wordPart, i;
for (i = 1; i <= len; i++) { for(i = 1; i <= len; i++) {
wordPart = searchWord.substr(0, i) wordPart = searchWord.substr(0, i);
if (shortIndexes[wordPart] === undefined) { if(shortIndexes[wordPart] === undefined) {
shortIndexes[wordPart] = [id] shortIndexes[wordPart] = [id];
} else { } else {
shortIndexes[wordPart].push(id) shortIndexes[wordPart].push(id);
} }
} }
}) });
} }
function search (query, searchIndex) { function search(query, searchIndex) {
var shortIndexes = searchIndex.shortIndexes var shortIndexes = searchIndex.shortIndexes;
var fullTexts = searchIndex.fullTexts var fullTexts = searchIndex.fullTexts;
query = cleanSearchText(query) query = cleanSearchText(query);
var queryWords = query.split(' ') var queryWords = query.split(' ');
var foundObjs = false, var foundObjs = false,
newFoundObjs, i newFoundObjs, i;
var j, searchText var j, searchText;
var found var found;
for (i = 0; i < queryWords.length; i++) { for(i = 0; i < queryWords.length; i++) {
newFoundObjs = shortIndexes[queryWords[i].substr(0, 3)] newFoundObjs = shortIndexes[queryWords[i].substr(0, 3)];
if (!newFoundObjs) { if(!newFoundObjs) {
foundObjs = [] foundObjs = [];
break break;
} }
if (foundObjs === false || foundObjs.length > newFoundObjs.length) { if(foundObjs === false || foundObjs.length > newFoundObjs.length) {
foundObjs = newFoundObjs foundObjs = newFoundObjs;
} }
} }
newFoundObjs = {} newFoundObjs = {};
for (j = 0; j < foundObjs.length; j++) { for(j = 0; j < foundObjs.length; j++) {
found = true found = true;
searchText = fullTexts[foundObjs[j]] searchText = fullTexts[foundObjs[j]];
for (i = 0; i < queryWords.length; i++) { for(i = 0; i < queryWords.length; i++) {
if (searchText.indexOf(queryWords[i]) == -1) { if(searchText.indexOf(queryWords[i]) == -1) {
found = false found = false;
break break;
} }
} }
if (found) { if(found) {
newFoundObjs[foundObjs[j]] = true newFoundObjs[foundObjs[j]] = true;
} }
} }
return newFoundObjs return newFoundObjs;
} }
let SearchIndexManager = { let SearchIndexManager = {

8
src/pages/pageSignUp.ts

@ -4,6 +4,7 @@ import pageIm from './pageIm';
import apiManager from '../lib/mtproto/mtprotoworker'; import apiManager from '../lib/mtproto/mtprotoworker';
import Page from './page'; import Page from './page';
import popupAvatar from '../components/popupAvatar'; import popupAvatar from '../components/popupAvatar';
import appProfileManager from '../lib/appManagers/appProfileManager';
let authCode: { let authCode: {
'phone_number': string, 'phone_number': string,
@ -45,12 +46,7 @@ let onFirstMount = () => {
uploadAvatar().then((inputFile: any) => { uploadAvatar().then((inputFile: any) => {
console.log('uploaded smthn', inputFile); console.log('uploaded smthn', inputFile);
apiManager.invokeApi('photos.uploadProfilePhoto', { appProfileManager.uploadProfilePhoto(inputFile).then(resolve, reject);
file: inputFile
}).then((updateResult) => {
console.log('updated photo!');
resolve();
}, reject);
}, reject); }, reject);
}); });

32
src/scss/partials/_chat.scss

@ -1,5 +1,5 @@
$chat-max-width: 696px; $chat-max-width: 696px;
$time-background: rgba(0, 0, 0, 0.35); $time-background: rgba(0, 0, 0, .35);
#bubble-contextmenu > div { #bubble-contextmenu > div {
padding: 0 84px 0 16px; padding: 0 84px 0 16px;
@ -13,7 +13,7 @@ $time-background: rgba(0, 0, 0, 0.35);
-webkit-user-select: none; -webkit-user-select: none;
display: flex; display: flex;
align-items: center; align-items: center;
box-shadow: 0 1px 2px 0 rgba(16, 35, 47, 0.07); box-shadow: 0 1px 2px 0 rgba(16, 35, 47, .07);
padding: .5rem 15px; padding: .5rem 15px;
flex: 0 0 auto; /* Forces side columns to stay same width */ flex: 0 0 auto; /* Forces side columns to stay same width */
min-height: 61px; min-height: 61px;
@ -51,13 +51,7 @@ $time-background: rgba(0, 0, 0, 0.35);
&:hover { &:hover {
background-color: transparent; background-color: transparent;
} }
.user-avatar {
width: 44px;
height: 44px;
line-height: 44px;
}
.bottom { .bottom {
font-size: 14px; font-size: 14px;
line-height: 18px; line-height: 18px;
@ -68,6 +62,12 @@ $time-background: rgba(0, 0, 0, 0.35);
} }
} }
} }
#im-avatar {
width: 44px;
height: 44px;
line-height: 44px;
}
} }
#chat-input { #chat-input {
@ -522,11 +522,13 @@ $time-background: rgba(0, 0, 0, 0.35);
} }
&.is-channel:not(.is-chat) { &.is-channel:not(.is-chat) {
padding-bottom: 55px;
.bubble__container { .bubble__container {
max-width: 100%; max-width: 100%;
} }
&:not(.has-rights) {
padding-bottom: 55px;
}
} }
&:not(.is-channel), &.is-chat { &:not(.is-channel), &.is-chat {
@ -565,12 +567,6 @@ $time-background: rgba(0, 0, 0, 0.35);
} }
} }
#bubble-contextmenu {
position: fixed;
right: auto;
bottom: auto;
}
.popup { .popup {
&.popup-delete-message { &.popup-delete-message {
.popup-header { .popup-header {
@ -588,7 +584,7 @@ $time-background: rgba(0, 0, 0, 0.35);
background: none; background: none;
outline: none; outline: none;
border: none; border: none;
padding: .5rem .5rem; padding: .5rem;
text-transform: uppercase; text-transform: uppercase;
transition: .2s; transition: .2s;
border-radius: $border-radius; border-radius: $border-radius;

60
src/scss/partials/_chatBubble.scss

@ -1,3 +1,17 @@
@keyframes bubbleSelected {
0% {
opacity: 0;
}
25% {
opacity: 1;
}
to {
opacity: 0;
}
}
.bubble { .bubble {
padding-top: 5px; padding-top: 5px;
/* display: grid; /* display: grid;
@ -5,16 +19,23 @@
grid-row-gap: 0px; */ grid-row-gap: 0px; */
max-width: $chat-max-width; max-width: $chat-max-width;
margin: 0 auto; margin: 0 auto;
position: relative;
&.is-selected { &.is-selected {
&:before { &:before {
position: absolute; position: absolute;
width: 100%; width: 200%;
height: 100%; height: 100%;
background-color: #7ca09f; background-color: rgba(0, 132, 255, .3);
content: " "; content: " ";
display: block; display: block;
left: 0; left: -50%;
top: 0;
animation: bubbleSelected 2s linear;
}
&:not(.is-group-last):before {
height: calc(100% + 5px);
} }
} }
@ -187,7 +208,7 @@
-webkit-user-select: none; -webkit-user-select: none;
} }
.attachment { &:not(.sticker) .attachment {
padding-top: .5rem; padding-top: .5rem;
padding-bottom: 1.5rem; padding-bottom: 1.5rem;
max-width: fit-content!important; max-width: fit-content!important;
@ -198,6 +219,11 @@
width: auto; width: auto;
} }
} }
&.sticker .bubble__container {
max-width: 140px;
max-height: 140px;
}
} }
&.emoji-1x .attachment { &.emoji-1x .attachment {
@ -233,7 +259,7 @@
} }
} }
&.sticker, &.round { &.sticker, &.round, &.emoji-big {
.bubble__container { .bubble__container {
cursor: pointer; cursor: pointer;
background: none!important; background: none!important;
@ -245,14 +271,18 @@
img { img {
object-fit: contain; object-fit: contain;
} }
&.is-message-empty .message { &.is-message-empty .message {
background-color: rgba(0, 0, 0, .23);
}
/* &.is-message-empty .message {
display: none; display: none;
} }
&.is-message-empty:hover .message { &.is-message-empty:hover .message {
display: block; display: block;
} } */
} }
&.sticker { &.sticker {
@ -756,8 +786,16 @@
width: 5rem; width: 5rem;
} }
&.is-edited .time { &.is-edited {
width: 90px; .time {
width: 78px !important;
}
&.emoji-big, &.sticker {
.time {
width: 81px !important;
}
}
} }
&:not(.forwarded).hide-name, &.emoji-big { &:not(.forwarded).hide-name, &.emoji-big {
@ -821,7 +859,7 @@
bottom: 0; bottom: 0;
width: 11px; width: 11px;
height: 20px; height: 20px;
background-repeat: no-repeat repeat; background-repeat: no-repeat no-repeat;
content: ''; content: '';
background-size: 11px 20px; background-size: 11px 20px;
background-position-y: 1px; background-position-y: 1px;
@ -1171,7 +1209,7 @@
poll-element { poll-element {
margin-top: -1px; margin-top: -1px;
display: block; display: block;
min-width: 240px; min-width: 280px;
.poll { .poll {
&-title { &-title {

16
src/scss/partials/_chatlist.scss

@ -5,6 +5,7 @@
position: relative; position: relative;
width: 100%; width: 100%;
margin-left: 22px; margin-left: 22px;
margin-right: 4px;
input { input {
background-color: rgba(112, 117, 121, .08); background-color: rgba(112, 117, 121, .08);
@ -86,8 +87,10 @@
} }
} }
li.active > .rp { li.active, li.menu-open {
background: rgba(112, 117, 121, 0.08); > .rp {
background: rgba(112, 117, 121, 0.08);
}
} }
.pinned-delimiter { .pinned-delimiter {
@ -139,7 +142,7 @@
user-select: none; user-select: none;
} }
.user-avatar { .dialog-avatar {
flex: 0 0 auto; flex: 0 0 auto;
} }
@ -157,6 +160,8 @@
} }
.user-title { .user-title {
max-width: 82%;
img.emoji { img.emoji {
vertical-align: top; vertical-align: top;
margin-top: 4px; margin-top: 4px;
@ -185,6 +190,7 @@
} }
.user-last-message { .user-last-message {
max-width: 86%;
img.emoji { img.emoji {
width: 20px; width: 20px;
height: 20px; height: 20px;
@ -200,8 +206,6 @@
} }
.user-title, .user-last-message { .user-title, .user-last-message {
max-width: 86%;
i { i {
font-style: normal; font-style: normal;
color: $color-blue; color: $color-blue;
@ -285,7 +289,7 @@
// use together like class="chats-container contacts-container" // use together like class="chats-container contacts-container"
.contacts-container, .search-group-contacts { .contacts-container, .search-group-contacts {
.user-avatar { .dialog-avatar {
width: 48px; width: 48px;
height: 48px; height: 48px;
} }

73
src/scss/partials/_leftSidebar.scss

@ -101,7 +101,7 @@
margin: 0; margin: 0;
} }
.user-avatar { .dialog-avatar {
width: 54px; width: 54px;
height: 54px; height: 54px;
} }
@ -155,7 +155,7 @@
} }
} }
.new-channel-container, .new-group-container { .new-channel-container, .new-group-container, .edit-profile-container {
.sidebar-content { .sidebar-content {
flex-direction: column; flex-direction: column;
} }
@ -178,9 +178,76 @@
} }
.caption { .caption {
font-size: 14px; font-size: 0.875rem;
margin-top: 14px; margin-top: 14px;
margin-left: 23px; margin-left: 23px;
color: #707579; color: #707579;
} }
} }
.new-group-members {
padding: 1.5rem 0 0.4375rem;
.search-group__name {
text-transform: capitalize;
}
}
.settings-container {
.profile {
&-button {
display: flex;
padding: 1.125rem 0.625rem;
height: 3.5rem;
line-height: 1.4;
border-radius: 0.625rem;
margin: 0px 0.5rem 0px 0.4375rem;
&:hover {
background: rgba(112, 117, 121, 0.08);
cursor: pointer;
}
&:before {
font-size: 24px;
color: #707579;
margin-left: 0.375rem;
margin-top: -0.0625rem;
}
p {
padding-left: 2rem;
user-select: none;
}
}
&-buttons {
margin-top: .9375rem;
width: 100%;
}
}
}
.edit-profile-container {
.caption {
margin-top: 1.063rem;
margin-left: 1.438rem;
line-height: 1.2;
padding-bottom: 1.438rem;
}
.sidebar-left-h2 {
color: #707579;
padding: 0 1.438rem;
padding-bottom: 1.5rem;
font-weight: 500;
}
hr {
margin-bottom: 1.5rem;
}
.scroll-wrapper {
width: 100%;
}
}

28
src/scss/partials/_mediaViewer.scss

@ -25,22 +25,22 @@
&:hover { &:hover {
color: #fff; color: #fff;
} }
}
.user-avatar { &-userpic {
width: 44px; width: 44px;
height: 44px; height: 44px;
position: absolute; position: absolute;
top: 8px; top: 8px;
left: 20px; left: 20px;
} }
.media-viewer-name {
font-weight: 500;
}
.media-viewer-date { &-name {
font-size: 15px; font-weight: 500;
} }
&-date {
font-size: 15px;
} }
&-buttons { &-buttons {

2
src/scss/partials/_rightSIdebar.scss

@ -150,7 +150,7 @@
} }
} }
&-avatar.user-avatar { &-avatar {
width: 120px; width: 120px;
height: 120px; height: 120px;
margin: 1px auto 21px; margin: 1px auto 21px;

17
src/scss/partials/_selector.scss

@ -55,8 +55,9 @@
background-color: #fae2e3; background-color: #fae2e3;
cursor: pointer; cursor: pointer;
.user-avatar:after { .selector-user-avatar:after {
opacity: 1; opacity: 1;
transform: scaleX(-1) rotate(-90deg);
} }
} }
@ -69,7 +70,7 @@
animation-direction: reverse; animation-direction: reverse;
} }
.user-avatar { &-avatar {
height: 32px !important; height: 32px !important;
width: 32px !important; width: 32px !important;
float: left; float: left;
@ -87,10 +88,10 @@
width: 100%; width: 100%;
z-index: 2; z-index: 2;
font-size: 23px; font-size: 23px;
line-height: 32px; line-height: 32px !important;
opacity: 0; opacity: 0;
transition: .2s opacity; transition: .2s opacity, .2s transform;
transform: scaleX(-1); transform: scaleX(-1) rotate(0deg);
} }
} }
} }
@ -101,7 +102,7 @@
} }
ul { ul {
.user-avatar { .dialog-avatar {
height: 48px; height: 48px;
width: 48px; width: 48px;
} }
@ -133,10 +134,6 @@
} }
hr { hr {
width: 100%;
height: 1px;
border: none;
background-color: #DADCE0;
margin: 0 0 8px; margin: 0 0 8px;
} }

2
src/scss/partials/_sidebar.scss

@ -14,7 +14,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 7.5px 20px 7.5px 15px; padding: 7.5px 16px;
min-height: 60px; min-height: 60px;
&__title { &__title {

48
src/scss/partials/popups/_peer.scss

@ -0,0 +1,48 @@
.popup-peer {
$parent: ".popup";
#{$parent} {
&-header {
display: flex;
margin-bottom: 0.4375rem;
align-items: center;
padding: 0.125rem 0.25rem;
}
&-container {
padding: 1rem 1.5rem 0.75rem 1rem;
}
&-title {
padding-left: 0.75rem;
font-size: 1.25rem;
font-weight: 500;
margin-bottom: 0.125rem;
}
&-description {
padding: 0 0.25rem;
margin-top: 0;
margin-bottom: 1.625rem;
min-width: 15rem;
max-width: fit-content;
}
&-buttons {
margin-right: -0.75rem;
.btn {
font-weight: 500;
& + .btn {
margin-top: 0.625rem;
}
}
}
}
.peer-avatar {
height: 2rem;
width: 2rem;
}
}

42
src/scss/style.scss

@ -38,6 +38,7 @@ $large-screen: 1680px;
@import "partials/popups/popup"; @import "partials/popups/popup";
@import "partials/popups/editAvatar"; @import "partials/popups/editAvatar";
@import "partials/popups/mediaAttacher"; @import "partials/popups/mediaAttacher";
@import "partials/popups/peer";
html, body { html, body {
height: 100%; height: 100%;
@ -259,6 +260,12 @@ input {
} }
} }
#bubble-contextmenu, #dialogs-contextmenu {
position: fixed;
right: auto;
bottom: auto;
}
@keyframes fadeIn { @keyframes fadeIn {
0% { 0% {
opacity: 0; opacity: 0;
@ -269,7 +276,14 @@ input {
} }
} }
.user-avatar { hr {
width: 100%;
border: none;
border-bottom: 1px solid #DADCE0;
margin: 0 0 8px;
}
avatar-element {
color: #fff; color: #fff;
width: 54px; width: 54px;
height: 54px; height: 54px;
@ -689,6 +703,15 @@ input {
z-index: 2; z-index: 2;
color: #fff; color: #fff;
} }
.avatar-placeholder {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
filter: brightness(0.7);
}
} }
.page-signUp { .page-signUp {
@ -775,12 +798,23 @@ input {
&.error { &.error {
border-color: $color-error; border-color: $color-error;
transition: .2s border-width;
& + label { & + label {
color: $color-error!important; color: $color-error!important;
} }
} }
&.valid {
border-color: #26962F;
& + label {
color: #26962F !important;
}
}
/* &.error, &.valid {
transition: .2s border-width;
} */
&:focus ~ .arrow-down { &:focus ~ .arrow-down {
margin-top: -4px; margin-top: -4px;
@ -894,7 +928,7 @@ input {
} }
} }
.input-wrapper > * ~ * { .input-wrapper > * + * {
margin-top: 1.5rem; margin-top: 1.5rem;
} }

1
tsconfig.json

@ -66,6 +66,7 @@
"public", "public",
"coverage", "coverage",
"./src/lib/ckin.js", "./src/lib/ckin.js",
"./src/lib/crypto/crypto.worker.js",
"src/lib/config.ts", "src/lib/config.ts",
"./src/lib/StackBlur.js", "./src/lib/StackBlur.js",
"./src/lib/*.js", "./src/lib/*.js",

Loading…
Cancel
Save