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. 50
      src/components/misc.ts
  4. 13
      src/components/popupAvatar.ts
  5. 26
      src/components/scrollable_new.ts
  6. 51
      src/lib/appManagers/appChatsManager.ts
  7. 423
      src/lib/appManagers/appDialogsManager.ts
  8. 179
      src/lib/appManagers/appImManager.ts
  9. 14
      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. 26
      src/lib/appManagers/appStickersManager.ts
  14. 12
      src/lib/appManagers/appUsersManager.ts
  15. 156
      src/lib/utils.js
  16. 8
      src/pages/pageSignUp.ts
  17. 30
      src/scss/partials/_chat.scss
  18. 58
      src/scss/partials/_chatBubble.scss
  19. 14
      src/scss/partials/_chatlist.scss
  20. 73
      src/scss/partials/_leftSidebar.scss
  21. 8
      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. 40
      src/scss/style.scss
  27. 1
      tsconfig.json

9
src/components/appSelectPeers.ts

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

62
src/components/avatar.ts

@ -0,0 +1,62 @@ @@ -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);

50
src/components/misc.ts

@ -329,33 +329,59 @@ let onMouseMove = (e: MouseEvent) => { @@ -329,33 +329,59 @@ let onMouseMove = (e: MouseEvent) => {
let diffY = clientY >= rect.bottom ? clientY - rect.bottom : rect.top - clientY;
if(diffX >= 100 || diffY >= 100) {
openedMenu.classList.remove('active');
openedMenu.parentElement.classList.remove('menu-open');
closeBtnMenu();
//openedMenu.parentElement.click();
}
//console.log('mousemove', diffX, diffY);
};
let openedMenu: HTMLDivElement = null;
export function openBtnMenu(menuElement: HTMLDivElement) {
let onClick = (e: MouseEvent) => {
e.preventDefault();
closeBtnMenu();
};
let closeBtnMenu = () => {
if(openedMenu) {
openedMenu.classList.remove('active');
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.classList.add('active');
openedMenu.parentElement.classList.add('menu-open');
window.addEventListener('click', () => {
if(openedMenu) {
openedMenu.parentElement.classList.remove('menu-open');
openedMenu.classList.remove('active');
openedMenu = null;
openedMenuOnClose = onClose;
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('click', onClick, {once: true});
window.addEventListener('contextmenu', onClick, {once: true});
}
window.removeEventListener('mousemove', onMouseMove);
}, {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');
window.addEventListener('mousemove', onMouseMove);
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 { @@ -58,12 +58,7 @@ export class PopupAvatar {
this.canvas.toBlob(blob => {
this.blob = blob; // save blob to send after reg
// 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.darkenCanvas();
this.resolve();
}, 'image/jpeg', 1);
});
@ -92,6 +87,12 @@ export class PopupAvatar { @@ -92,6 +87,12 @@ export class PopupAvatar {
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();

26
src/components/scrollable_new.ts

@ -68,7 +68,7 @@ export default class Scrollable { @@ -68,7 +68,7 @@ export default class Scrollable {
private lastBottomID = 0;
private lastScrollDirection = 0; // true = bottom
private scrollLocked = 0;
public scrollLocked = 0;
private setVisible(element: HTMLElement) {
if(this.visible.has(element)) return;
@ -367,8 +367,8 @@ export default class Scrollable { @@ -367,8 +367,8 @@ export default class Scrollable {
return !!element.parentElement;
}
public scrollIntoView(element: HTMLElement) {
if(element.parentElement) {
public scrollIntoView(element: HTMLElement, smooth = true, fromUp = false) {
if(element.parentElement && !this.scrollLocked) {
let scrollTop = this.scrollTop;
let offsetTop = element.offsetTop;
let clientHeight = this.container.clientHeight;
@ -383,12 +383,30 @@ export default class Scrollable { @@ -383,12 +383,30 @@ export default class Scrollable {
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);
this.scrollLocked = setTimeout(() => {
this.scrollLocked = 0;
this.onScroll();
}, 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'});
}
}

51
src/lib/appManagers/appChatsManager.ts

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

423
src/lib/appManagers/appDialogsManager.ts

@ -1,18 +1,18 @@ @@ -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 appPeersManager from './appPeersManager';
import appMessagesManager, { AppMessagesManager, Dialog } from "./appMessagesManager";
import appUsersManager, { User } from "./appUsersManager";
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_new";
import appProfileManager from "./appProfileManager";
import { logger } from "../polyfill";
import appChatsManager from "./appChatsManager";
import AvatarElement from "../../components/avatar";
type DialogDom = {
avatarDiv: HTMLDivElement,
avatarEl: AvatarElement,
captionDiv: HTMLDivElement,
titleSpan: HTMLSpanElement,
statusSpan: HTMLSpanElement,
@ -25,6 +25,346 @@ type DialogDom = { @@ -25,6 +25,346 @@ type DialogDom = {
let testScroll = false;
class PopupElement {
protected element = document.createElement('div');
protected container = document.createElement('div');
protected header = document.createElement('div');
protected title = document.createElement('div');
constructor(className: string) {
this.element.classList.add('popup');
this.element.className = 'popup' + (className ? ' ' + className : '');
this.container.classList.add('popup-container', 'z-depth-1');
this.header.classList.add('popup-header');
this.title.classList.add('popup-title');
this.header.append(this.title);
this.container.append(this.header);
this.element.append(this.container);
}
public show() {
document.body.append(this.element);
void this.element.offsetWidth; // reflow
this.element.classList.add('active');
}
public destroy() {
this.element.classList.remove('active');
setTimeout(() => {
this.element.remove();
}, 1000);
}
}
type PopupPeerButton = {
text: string,
callback?: () => void,
isDanger?: true,
isCancel?: true
};
class PopupPeer extends PopupElement {
constructor(private className: string, options: Partial<{
peerID: number,
title: string,
description: string,
buttons: Array<PopupPeerButton>
}> = {}) {
super('popup-peer' + (className ? ' ' + className : ''));
let avatarEl = new AvatarElement();
avatarEl.setAttribute('dialog', '1');
avatarEl.setAttribute('peer', '' + options.peerID);
avatarEl.classList.add('peer-avatar');
this.title.innerText = options.title || '';
this.header.prepend(avatarEl);
let p = document.createElement('p');
p.classList.add('popup-description');
p.innerHTML = options.description;
let buttonsDiv = document.createElement('div');
buttonsDiv.classList.add('popup-buttons');
let buttons = options.buttons.map(b => {
let button = document.createElement('button');
ripple(button);
button.className = 'btn' + (b.isDanger ? ' danger' : '');
button.innerHTML = b.text;
if(b.callback) {
button.addEventListener('click', () => {
b.callback();
this.destroy();
});
} else if(b.isCancel) {
button.addEventListener('click', () => {
this.destroy();
});
}
return button;
});
buttonsDiv.append(...buttons);
this.container.append(p, buttonsDiv);
}
}
class DialogsContextMenu {
private element = document.getElementById('dialogs-contextmenu') as HTMLDivElement;
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 {
public chatList = document.getElementById('dialogs') as HTMLUListElement;
public chatListArchived = document.getElementById('dialogs-archived') as HTMLUListElement;
@ -59,6 +399,8 @@ export class AppDialogsManager { @@ -59,6 +399,8 @@ export class AppDialogsManager {
private log = logger('DIALOGS');
private contextMenu = new DialogsContextMenu([this.chatList, this.chatListArchived]);
constructor() {
this.chatsPreloader = putPreloader(null, true);
//this.chatsContainer.append(this.chatsPreloader);
@ -127,9 +469,9 @@ export class AppDialogsManager { @@ -127,9 +469,9 @@ export class AppDialogsManager {
if(dom) {
if(online) {
dom.avatarDiv.classList.add('is-online');
dom.avatarEl.classList.add('is-online');
} else {
dom.avatarDiv.classList.remove('is-online');
dom.avatarEl.classList.remove('is-online');
}
}
}
@ -143,12 +485,19 @@ export class AppDialogsManager { @@ -143,12 +485,19 @@ export class AppDialogsManager {
let dialog: any = e.detail;
this.setLastMessage(dialog);
this.setUnreadMessages(dialog);
this.setDialogPosition(dialog);
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) => {
let dialogs = e.detail;
@ -162,7 +511,6 @@ export class AppDialogsManager { @@ -162,7 +511,6 @@ export class AppDialogsManager {
}
this.setLastMessage(dialog);
this.setUnreadMessages(dialog);
this.setDialogPosition(dialog);
}
@ -309,7 +657,7 @@ export class AppDialogsManager { @@ -309,7 +657,7 @@ export class AppDialogsManager {
this.lastActiveListElement = elem;
}
result = appImManager.setPeer(peerID, lastMsgID, false, true);
result = appImManager.setPeer(peerID, lastMsgID, true);
if(result instanceof Promise) {
this.lastGoodClickID = this.lastClickID;
@ -350,6 +698,9 @@ export class AppDialogsManager { @@ -350,6 +698,9 @@ export class AppDialogsManager {
let dom = this.getDialogDom(dialog.peerID);
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) {
return;
} else if(prevPos < pos) { // was higher
@ -408,13 +759,17 @@ export class AppDialogsManager { @@ -408,13 +759,17 @@ export class AppDialogsManager {
}
///////console.log('setlastMessage:', lastMessage);
if(lastMessage._ == 'messageEmpty') return;
if(!dom) {
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 peerID = dialog.peerID;
//let peerID = appMessagesManager.getMessagePeer(lastMessage);
@ -422,11 +777,12 @@ export class AppDialogsManager { @@ -422,11 +777,12 @@ export class AppDialogsManager {
//console.log('setting last message:', lastMessage);
/* if(!dom.lastMessageSpan.classList.contains('user-typing')) */ {
/* let messageText = lastMessage.message;
let messageWrapped = '';
if(messageText) {
if(highlightWord && lastMessage.message) {
let lastMessageText = appMessagesManager.getRichReplyText(lastMessage, '');
let messageText = lastMessage.message;
let entities = RichTextProcessor.parseEntities(messageText.replace(/\n/g, ' '), {noLinebreakers: true});
if(highlightWord) {
let regExp = new RegExp(escapeRegExp(highlightWord), 'gi');
let match: any;
@ -440,17 +796,19 @@ export class AppDialogsManager { @@ -440,17 +796,19 @@ export class AppDialogsManager {
if(found) {
entities.sort((a: any, b: any) => a.offset - b.offset);
}
}
messageWrapped = RichTextProcessor.wrapRichText(messageText, {
let messageWrapped = RichTextProcessor.wrapRichText(messageText, {
noLinebreakers: true,
entities: entities,
noTextFormat: true
});
} */
//dom.lastMessageSpan.innerHTML = lastMessageText + messageWrapped;
dom.lastMessageSpan.innerHTML = lastMessageText + messageWrapped;
} else if(!lastMessage.deleted) {
dom.lastMessageSpan.innerHTML = lastMessage.rReply;
} else {
dom.lastMessageSpan.innerHTML = '';
}
/* if(lastMessage.from_id == auth.id) { // You: */
if(peer._ != 'peerUser' && peerID != -lastMessage.from_id) {
@ -473,6 +831,7 @@ export class AppDialogsManager { @@ -473,6 +831,7 @@ export class AppDialogsManager {
}
}
if(!lastMessage.deleted) {
let timeStr = '';
let timestamp = lastMessage.date;
let now = Date.now() / 1000;
@ -492,6 +851,7 @@ export class AppDialogsManager { @@ -492,6 +851,7 @@ export class AppDialogsManager {
}
dom.lastTimeSpan.innerHTML = timeStr;
} else dom.lastTimeSpan.innerHTML = '';
dom.listEl.setAttribute('data-mid', lastMessage.mid);
@ -504,7 +864,7 @@ export class AppDialogsManager { @@ -504,7 +864,7 @@ export class AppDialogsManager {
let dom = this.getDialogDom(dialog.peerID);
let lastMessage = appMessagesManager.getMessage(dialog.top_message);
if(lastMessage._ != 'messageEmpty' &&
if(lastMessage._ != 'messageEmpty' && !lastMessage.deleted &&
lastMessage.from_id == $rootScope.myID && lastMessage.peerID != $rootScope.myID &&
dialog.read_outbox_max_id) { // maybe comment, 06.20.2020
let outgoing = (lastMessage.pFlags && lastMessage.pFlags.unread)
@ -523,12 +883,12 @@ export class AppDialogsManager { @@ -523,12 +883,12 @@ export class AppDialogsManager {
dom.unreadMessagesSpan.innerText = '';
dom.unreadMessagesSpan.classList.remove('tgico-pinnedchat');
if(dialog.unread_count) {
dom.unreadMessagesSpan.innerText = '' + dialog.unread_count;
if(dialog.unread_count || dialog.pFlags.unread_mark) {
dom.unreadMessagesSpan.innerText = '' + (dialog.unread_count || ' ');
//dom.unreadMessagesSpan.classList.remove('tgico-pinnedchat');
dom.unreadMessagesSpan.classList.add(new Date(dialog.notify_settings.mute_until * 1000) > new Date() ?
'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.add('tgico-pinnedchat');
}
@ -575,8 +935,10 @@ export class AppDialogsManager { @@ -575,8 +935,10 @@ export class AppDialogsManager {
let title = appPeersManager.getPeerTitle(peerID, false, onlyFirstName);
let avatarDiv = document.createElement('div');
avatarDiv.classList.add('user-avatar');
let avatarEl = new AvatarElement();
avatarEl.setAttribute('dialog', '1');
avatarEl.setAttribute('peer', '' + peerID);
avatarEl.classList.add('dialog-avatar');
if(drawStatus && peerID != $rootScope.myID && dialog.peer) {
let peer = dialog.peer;
@ -587,7 +949,7 @@ export class AppDialogsManager { @@ -587,7 +949,7 @@ export class AppDialogsManager {
//console.log('found user', user);
if(user.status && user.status._ == 'userStatusOnline') {
avatarDiv.classList.add('is-online');
avatarEl.classList.add('is-online');
}
break;
@ -618,9 +980,6 @@ export class AppDialogsManager { @@ -618,9 +980,6 @@ export class AppDialogsManager {
title = onlyFirstName ? 'Saved' : 'Saved Messages';
}
//console.log('trying to load photo for:', title);
appProfileManager.putPhoto(avatarDiv, dialog.peerID, true);
titleSpan.innerHTML = title;
//p.classList.add('')
@ -632,7 +991,7 @@ export class AppDialogsManager { @@ -632,7 +991,7 @@ export class AppDialogsManager {
let paddingDiv = document.createElement('div');
paddingDiv.classList.add('rp');
paddingDiv.append(avatarDiv, captionDiv);
paddingDiv.append(avatarEl, captionDiv);
if(rippleEnabled) {
ripple(paddingDiv, (id) => {
@ -677,7 +1036,7 @@ export class AppDialogsManager { @@ -677,7 +1036,7 @@ export class AppDialogsManager {
captionDiv.append(titleP, messageP);
let dom: DialogDom = {
avatarDiv,
avatarEl,
captionDiv,
titleSpan,
statusSpan,

179
src/lib/appManagers/appImManager.ts

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

14
src/lib/appManagers/appMediaViewer.ts

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

250
src/lib/appManagers/appMessagesManager.ts

@ -56,7 +56,8 @@ export type Dialog = { @@ -56,7 +56,8 @@ export type Dialog = {
peerID: number,
pinnedIndex: number,
pFlags: Partial<{
pinned: boolean
pinned: true,
unread_mark: true
}>,
pts: number
}
@ -1253,8 +1254,8 @@ export class AppMessagesManager { @@ -1253,8 +1254,8 @@ export class AppMessagesManager {
this.allDialogsLoaded[folderID] = true;
}
if(hasPrepend && !this.newDialogsHandlePromise) {
this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0);
if(hasPrepend) {
this.scheduleHandleNewDialogs();
} else {
$rootScope.$broadcast('dialogs_multiupdate', {});
}
@ -1344,7 +1345,7 @@ export class AppMessagesManager { @@ -1344,7 +1345,7 @@ export class AppMessagesManager {
topDate = savedDraft.date;
}
if(dialog.pFlags.pinned) {
if(dialog.pFlags.pinned && dialog.folder_id == 0) {
topDate = this.generateDialogPinnedDate(dialog);
//console.log('topDate', peerID, topDate);
}
@ -1354,15 +1355,15 @@ export class AppMessagesManager { @@ -1354,15 +1355,15 @@ export class AppMessagesManager {
public pushDialogToStorage(dialog: Dialog, offsetDate?: number) {
let dialogs = this.dialogsStorage.dialogs[dialog.folder_id] ?? (this.dialogsStorage.dialogs[dialog.folder_id] = []);
let pos = this.getDialogByPeerID(dialog.peerID)[1];
if(pos !== undefined) {
let pos = dialogs.findIndex(d => d.peerID == dialog.peerID);
if(pos !== -1) {
dialogs.splice(pos, 1);
}
if(offsetDate &&
!dialog.pFlags.pinned &&
(!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
return false;
}
@ -1426,6 +1427,81 @@ export class AppMessagesManager { @@ -1426,6 +1427,81 @@ export class AppMessagesManager {
}).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: {
isNew?: boolean,
isEdited?: boolean
@ -1651,7 +1727,7 @@ export class AppMessagesManager { @@ -1651,7 +1727,7 @@ export class AppMessagesManager {
});
}
public getRichReplyText(message: any) {
public getRichReplyText(message: any, text: string = message.message) {
let messageText = '';
if(message.media) {
@ -1739,7 +1815,6 @@ export class AppMessagesManager { @@ -1739,7 +1815,6 @@ export class AppMessagesManager {
messageText = '<i>' + langPack[_] + suffix + '</i>';
}
let text = message.message;
let messageWrapped = '';
if(text) {
let entities = RichTextProcessor.parseEntities(text.replace(/\n/g, ' '), {noLinebreakers: true});
@ -1754,6 +1829,69 @@ export class AppMessagesManager { @@ -1754,6 +1829,69 @@ export class AppMessagesManager {
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) {
if(!this.migratedFromTo[migrateFrom] &&
!this.migratedToFrom[migrateTo] &&
@ -1931,10 +2069,10 @@ export class AppMessagesManager { @@ -1931,10 +2069,10 @@ export class AppMessagesManager {
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);
this.generateIndexForDialog(dialog);
if(!dialog.hasOwnProperty('folder_id')) dialog.folder_id = 0;
dialog.peerID = peerID;
if(!dialog.folder_id) dialog.folder_id = 0;
this.generateIndexForDialog(dialog);
this.pushDialogToStorage(dialog, offsetDate);
// Because we saved message without dialog present
@ -2283,7 +2421,7 @@ export class AppMessagesManager { @@ -2283,7 +2421,7 @@ export class AppMessagesManager {
this.newDialogsHandlePromise = 0;
let newMaxSeenID = 0;
Object.keys(this.newDialogsToHandle).forEach((peerID) => {
for(let peerID in this.newDialogsToHandle) {
let dialog = this.newDialogsToHandle[peerID];
if('reload' in dialog) {
this.reloadConversation(+peerID);
@ -2294,7 +2432,7 @@ export class AppMessagesManager { @@ -2294,7 +2432,7 @@ export class AppMessagesManager {
newMaxSeenID = Math.max(newMaxSeenID, dialog.top_message || 0);
}
}
});
}
console.log('after order:', this.dialogsStorage.dialogs[0].map(d => d.peerID));
@ -2306,6 +2444,12 @@ export class AppMessagesManager { @@ -2306,6 +2444,12 @@ export class AppMessagesManager {
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> {
// console.trace('start read')
var isChannel = AppPeersManager.isChannel(peerID);
@ -2486,9 +2630,7 @@ export class AppMessagesManager { @@ -2486,9 +2630,7 @@ export class AppMessagesManager {
if(!foundDialog.length) {
this.newDialogsToHandle[peerID] = {reload: true}
if(!this.newDialogsHandlePromise) {
this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0);
}
this.scheduleHandleNewDialogs();
if(this.newUpdatesAfterReloadToHandle[peerID] === undefined) {
this.newUpdatesAfterReloadToHandle[peerID] = [];
}
@ -2572,10 +2714,57 @@ export class AppMessagesManager { @@ -2572,10 +2714,57 @@ export class AppMessagesManager {
}
this.newDialogsToHandle[peerID] = dialog;
if(!this.newDialogsHandlePromise) {
this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0);
this.scheduleHandleNewDialogs();
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;
}
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;
}
@ -2584,10 +2773,7 @@ export class AppMessagesManager { @@ -2584,10 +2773,7 @@ export class AppMessagesManager {
let peerID = AppPeersManager.getPeerID(update.peer.peer);
let foundDialog = this.getDialogByPeerID(peerID);
if(!this.newDialogsHandlePromise) {
this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0);
}
this.scheduleHandleNewDialogs();
if(!foundDialog.length) {
this.newDialogsToHandle[peerID] = {reload: true};
break;
@ -2597,6 +2783,7 @@ export class AppMessagesManager { @@ -2597,6 +2783,7 @@ export class AppMessagesManager {
if(!update.pFlags.pinned) {
delete dialog.pFlags.pinned;
delete dialog.pinnedIndex;
} else { // means set
dialog.pFlags.pinned = true;
}
@ -2623,9 +2810,7 @@ export class AppMessagesManager { @@ -2623,9 +2810,7 @@ export class AppMessagesManager {
let peerID = dialog.peerID;
if(dialog.pFlags.pinned && !newPinned[peerID]) {
this.newDialogsToHandle[peerID] = {reload: true};
if(!this.newDialogsHandlePromise) {
this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0);
}
this.scheduleHandleNewDialogs();
}
});
});
@ -2665,8 +2850,8 @@ export class AppMessagesManager { @@ -2665,8 +2850,8 @@ export class AppMessagesManager {
}
});
if(willHandle && !this.newDialogsHandlePromise) {
this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0);
if(willHandle) {
this.scheduleHandleNewDialogs();
}
break;
@ -3119,7 +3304,7 @@ export class AppMessagesManager { @@ -3119,7 +3304,7 @@ export class AppMessagesManager {
if(!offsetNotFound && (
historyStorage.count !== null && historyStorage.history.length == historyStorage.count ||
historyStorage.history.length >= offset + (limit || 1)
historyStorage.history.length >= offset + limit
)) {
if(backLimit) {
backLimit = Math.min(offset, backLimit);
@ -3134,7 +3319,7 @@ export class AppMessagesManager { @@ -3134,7 +3319,7 @@ export class AppMessagesManager {
history = historyStorage.pending.slice().concat(history);
}
return this.wrapHistoryResult(peerID, {
return this.wrapHistoryResult({
count: historyStorage.count,
history: history,
unreadOffset: unreadOffset,
@ -3166,7 +3351,7 @@ export class AppMessagesManager { @@ -3166,7 +3351,7 @@ export class AppMessagesManager {
history = historyStorage.pending.slice().concat(history);
}
return this.wrapHistoryResult(peerID, {
return this.wrapHistoryResult({
count: historyStorage.count,
history: history,
unreadOffset: unreadOffset,
@ -3190,7 +3375,7 @@ export class AppMessagesManager { @@ -3190,7 +3375,7 @@ export class AppMessagesManager {
history = historyStorage.pending.slice().concat(history);
}
return this.wrapHistoryResult(peerID, {
return this.wrapHistoryResult({
count: historyStorage.count,
history: history,
unreadOffset: unreadOffset,
@ -3262,7 +3447,7 @@ export class AppMessagesManager { @@ -3262,7 +3447,7 @@ export class AppMessagesManager {
});
}
public wrapHistoryResult(peerID: number, result: HistoryResult) {
public wrapHistoryResult(result: HistoryResult) {
var unreadOffset = result.unreadOffset;
if(unreadOffset) {
var i;
@ -3276,7 +3461,6 @@ export class AppMessagesManager { @@ -3276,7 +3461,6 @@ export class AppMessagesManager {
}
}
return result;
//return Promise.resolve(result);
}
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"; @@ -13,6 +13,10 @@ import { appPeersManager } from "../services";
import popupAvatar from "../../components/popupAvatar";
import appChatsManager from "./appChatsManager";
import { AppSelectPeers } from "../../components/appSelectPeers";
import AvatarElement from "../../components/avatar";
import appProfileManager from "./appProfileManager";
AvatarElement;
const SLIDERITEMSIDS = {
archived: 1,
@ -20,6 +24,8 @@ const SLIDERITEMSIDS = { @@ -20,6 +24,8 @@ const SLIDERITEMSIDS = {
newChannel: 3,
addMembers: 4,
newGroup: 5,
settings: 6,
editProfile: 7,
};
interface SliderTab {
@ -317,6 +323,215 @@ class AppContactsTab implements 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 {
private sidebarEl = document.getElementById('column-left') as HTMLDivElement;
private toolsBtn = this.sidebarEl.querySelector('.sidebar-tools-button') as HTMLButtonElement;
@ -329,7 +544,7 @@ class AppSidebarLeft { @@ -329,7 +544,7 @@ class AppSidebarLeft {
private contactsBtn = this.menuEl.querySelector('.menu-contacts');
private archivedBtn = this.menuEl.querySelector('.menu-archive');
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;
private newBtnMenu = this.sidebarEl.querySelector('#new-menu');
@ -343,12 +558,16 @@ class AppSidebarLeft { @@ -343,12 +558,16 @@ class AppSidebarLeft {
public addMembersTab = new AppAddMembersTab();
public contactsTab = new AppContactsTab();
public newGroupTab = new AppNewGroupTab();
public settingsTab = new AppSettingsTab();
public editProfileTab = new AppEditProfileTab();
private tabs: {[id: number]: SliderTab} = {
[SLIDERITEMSIDS.newChannel]: this.newChannelTab,
[SLIDERITEMSIDS.contacts]: this.contactsTab,
[SLIDERITEMSIDS.addMembers]: this.addMembersTab,
[SLIDERITEMSIDS.newGroup]: this.newGroupTab,
[SLIDERITEMSIDS.settings]: this.settingsTab,
[SLIDERITEMSIDS.editProfile]: this.editProfileTab,
};
//private log = logger('SL');
@ -388,8 +607,9 @@ class AppSidebarLeft { @@ -388,8 +607,9 @@ class AppSidebarLeft {
this.contactsTab.openContacts();
});
this.logOutBtn.addEventListener('click', (e) => {
apiManager.logOut();
this.settingsBtn.addEventListener('click', () => {
this.settingsTab.fillElements();
this.selectTab(SLIDERITEMSIDS.settings);
});
this.searchInput.addEventListener('focus', (e) => {
@ -399,13 +619,13 @@ class AppSidebarLeft { @@ -399,13 +619,13 @@ class AppSidebarLeft {
void this.searchContainer.offsetWidth; // reflow
this.searchContainer.classList.add('active');
false && this.searchInput.addEventListener('blur', (e) => {
/* this.searchInput.addEventListener('blur', (e) => {
if(!this.searchInput.value) {
this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active');
this.backBtn.click();
}
}, {once: true});
}, {once: true}); */
});
this.backBtn.addEventListener('click', (e) => {
@ -446,7 +666,7 @@ class AppSidebarLeft { @@ -446,7 +666,7 @@ class AppSidebarLeft {
console.log('sidebar-close-button click:', this.historyTabIDs);
let closingID = this.historyTabIDs.pop(); // pop current
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 => {
el.addEventListener('click', onCloseBtnClick);

9
src/lib/appManagers/appSidebarRight.ts

@ -14,6 +14,7 @@ import appMediaViewer from "./appMediaViewer"; @@ -14,6 +14,7 @@ import appMediaViewer from "./appMediaViewer";
import LazyLoadQueue from "../../components/lazyLoadQueue";
import { wrapDocument, wrapAudio } from "../../components/wrappers";
import AppSearch, { SearchGroup } from "../../components/appSearch";
import AvatarElement from "../../components/avatar";
const testScroll = false;
@ -23,7 +24,7 @@ class AppSidebarRight { @@ -23,7 +24,7 @@ class AppSidebarRight {
public profileContentEl = this.sidebarEl.querySelector('.profile-content') as HTMLDivElement;
public contentContainer = this.sidebarEl.querySelector('.content-container') as HTMLDivElement;
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,
subtitle: this.profileContentEl.querySelector('.profile-subtitle') as HTMLDivElement,
bio: this.profileContentEl.querySelector('.profile-row-bio') as HTMLDivElement,
@ -162,7 +163,7 @@ class AppSidebarRight { @@ -162,7 +163,7 @@ class AppSidebarRight {
this.profileElements.notificationsCheckbox.addEventListener('change', () => {
//let checked = this.profileElements.notificationsCheckbox.checked;
appImManager.mutePeer();
appImManager.mutePeer(this.peerID);
});
window.addEventListener('resize', this.onSidebarScroll.bind(this));
@ -577,6 +578,8 @@ class AppSidebarRight { @@ -577,6 +578,8 @@ class AppSidebarRight {
this.contentContainer.classList.remove('loaded');
this.profileElements.avatar.setAttribute('peer', '' + peerID);
window.requestAnimationFrame(() => {
this.profileContentEl.parentElement.scrollTop = 0;
this.profileElements.bio.style.display = 'none';
@ -660,7 +663,7 @@ class AppSidebarRight { @@ -660,7 +663,7 @@ class AppSidebarRight {
if(peerID > 0) {
let user = appUsersManager.getUser(peerID);
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 => {

26
src/lib/appManagers/appStickersManager.ts

@ -43,7 +43,7 @@ export type MTStickerSetFull = { @@ -43,7 +43,7 @@ export type MTStickerSetFull = {
documents: MTDocument[]
};
class appStickersManager {
class AppStickersManager {
private documents: {
[fileID: string]: MTDocument
} = {};
@ -69,6 +69,10 @@ class appStickersManager { @@ -69,6 +69,10 @@ class appStickersManager {
this.stickerSets = sets;
}
if(!this.stickerSets['emoji']) {
this.getStickerSet({id: 'emoji', access_hash: ''});
}
});
}
@ -99,7 +103,9 @@ class appStickersManager { @@ -99,7 +103,9 @@ class appStickersManager {
if(this.stickerSets[set.id]) return this.stickerSets[set.id];
let promise = apiManager.invokeApi('messages.getStickerSet', {
stickerset: {
stickerset: set.id == 'emoji' ? {
_: 'inputStickerSetAnimatedEmoji'
} : {
_: 'inputStickerSetID',
id: set.id,
access_hash: set.access_hash
@ -114,19 +120,23 @@ class appStickersManager { @@ -114,19 +120,23 @@ class appStickersManager {
documents: MTDocument[]
} = res as any;
this.saveStickerSet(stickerSet);
this.saveStickerSet(stickerSet, set.id);
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: {
_: "messages.stickerSet",
set: MTStickerSet,
packs: any[],
documents: MTDocument[]
}) {
let id = res.set.id;
}, id: string) {
//console.log('stickers save set', res);
this.stickerSets[id] = {
@ -171,4 +181,6 @@ class appStickersManager { @@ -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"; @@ -4,6 +4,7 @@ import appChatsManager from "./appChatsManager";
//import apiManager from '../mtproto/apiManager';
import apiManager from '../mtproto/mtprotoworker';
import serverTimeManager from "../mtproto/serverTimeManager";
import { formatPhoneNumber } from "../../components/misc";
export type User = {
_: 'user',
@ -28,6 +29,7 @@ export type User = { @@ -28,6 +29,7 @@ export type User = {
pFlags: Partial<{verified: boolean, support: boolean, self: boolean, bot: boolean, min: number, deleted: boolean}>,
rFirstName?: string,
rFullName?: string,
rPhone?: string,
sortName?: string,
sortStatus?: number,
};
@ -97,6 +99,7 @@ export class AppUsersManager { @@ -97,6 +99,7 @@ export class AppUsersManager {
}
$rootScope.$broadcast('user_update', userID);
$rootScope.$broadcast('avatar_update', userID);
} else console.warn('No user by id:', userID);
break
@ -204,11 +207,9 @@ export class AppUsersManager { @@ -204,11 +207,9 @@ export class AppUsersManager {
}
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) {
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;
@ -222,8 +223,7 @@ export class AppUsersManager { @@ -222,8 +223,7 @@ export class AppUsersManager {
this.usernames[searchUsername] = userID;
}
//apiUser.sortName = apiUser.pFlags.deleted ? '' : SearchIndexManager.cleanSearchText(apiUser.first_name + ' ' + (apiUser.last_name || ''));
apiUser.sortName = apiUser.pFlags.deleted ? '' : apiUser.first_name + ' ' + (apiUser.last_name || '');
apiUser.sortName = apiUser.pFlags.deleted ? '' : SearchIndexManager.cleanSearchText(apiUser.first_name + ' ' + (apiUser.last_name || ''), false);
var nameWords = apiUser.sortName.split(' ');
var firstWord = nameWords.shift();
@ -290,7 +290,7 @@ export class AppUsersManager { @@ -290,7 +290,7 @@ export class AppUsersManager {
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() {

156
src/lib/utils.js

@ -6,30 +6,31 @@ @@ -6,30 +6,31 @@
*/
var _logTimer = Date.now();
export function dT () {
return '[' + ((Date.now() - _logTimer) / 1000).toFixed(3) + ']'
return '[' + ((Date.now() - _logTimer) / 1000).toFixed(3) + ']';
}
export function checkClick(e, noprevent) {
if(e.which == 1 && (e.ctrlKey || e.metaKey) || e.which == 2) {
return true
return true;
}
if(!noprevent) {
e.preventDefault()
e.preventDefault();
}
return false
return false;
}
export function isInDOM(element, parentNode) {
if(!element) {
return false
return false;
}
parentNode = parentNode || document.body
parentNode = parentNode || document.body;
if(element == parentNode) {
return true
return true;
}
return isInDOM(element.parentNode, parentNode)
return isInDOM(element.parentNode, parentNode);
}
export function checkDragEvent(e) {
@ -48,17 +49,17 @@ export function checkDragEvent(e) { @@ -48,17 +49,17 @@ export function checkDragEvent(e) {
}
export function cancelEvent (event) {
event = event || window.event
event = event || window.event;
if(event) {
event = event.originalEvent || event
event = event.originalEvent || event;
if (event.stopPropagation) event.stopPropagation()
if (event.preventDefault) event.preventDefault()
event.returnValue = false
event.cancelBubble = true
if (event.stopPropagation) event.stopPropagation();
if (event.preventDefault) event.preventDefault();
event.returnValue = false;
event.cancelBubble = true;
}
return false
return false;
}
export function setFieldSelection (field, from, to) {
@ -371,6 +372,7 @@ export const langPack = { @@ -371,6 +372,7 @@ export const langPack = {
"messageActionChatDeleteUser": "removed user",
"messageActionChatJoinedByLink": "joined the group",
"messageActionPinMessage": "pinned message",
"messageActionContactSignUp": "joined Telegram",
"messageActionChannelCreate": "Channel created",
"messageActionChannelEditTitle": "Channel renamed",
"messageActionChannelEditPhoto": "Channel photo updated",
@ -475,23 +477,6 @@ export function isScrolledIntoView(el) { @@ -475,23 +477,6 @@ export function isScrolledIntoView(el) {
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 */) {
let i = 0;
// @ts-ignore
@ -529,30 +514,6 @@ export function copy(obj) { @@ -529,30 +514,6 @@ export function copy(obj) {
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) {
if (bytes === 0) return '0 Bytes';
@ -803,94 +764,97 @@ function versionCompare (ver1, ver2) { @@ -803,94 +764,97 @@ function versionCompare (ver1, ver2) {
}
}
function cleanSearchText (text) {
var hasTag = text.charAt(0) == '%'
text = text.replace(badCharsRe, ' ').replace(trimRe, '')
text = text.replace(/[^A-Za-z0-9]/g, function (ch) {
var latinizeCh = Config.LatinizeMap[ch]
return latinizeCh !== undefined ? latinizeCh : ch
})
text = text.toLowerCase()
function cleanSearchText(text, latinize = true) {
var hasTag = text.charAt(0) == '%';
text = text.replace(badCharsRe, ' ').replace(trimRe, '');
if(latinize) {
text = text.replace(/[^A-Za-z0-9]/g, (ch) => {
var latinizeCh = Config.LatinizeMap[ch];
return latinizeCh !== undefined ? latinizeCh : ch;
});
}
text = text.toLowerCase();
if(hasTag) {
text = '%' + text
text = '%' + text;
}
return text
return text;
}
function cleanUsername(username) {
return username && username.toLowerCase() || ''
return username && username.toLowerCase() || '';
}
function indexObject(id, searchText, searchIndex) {
if(searchIndex.fullTexts[id] !== undefined) {
return false
return false;
}
searchText = cleanSearchText(searchText)
searchText = cleanSearchText(searchText);
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),
wordPart, i
wordPart, i;
for(i = 1; i <= len; i++) {
wordPart = searchWord.substr(0, i)
wordPart = searchWord.substr(0, i);
if(shortIndexes[wordPart] === undefined) {
shortIndexes[wordPart] = [id]
shortIndexes[wordPart] = [id];
} else {
shortIndexes[wordPart].push(id)
shortIndexes[wordPart].push(id);
}
}
})
});
}
function search(query, searchIndex) {
var shortIndexes = searchIndex.shortIndexes
var fullTexts = searchIndex.fullTexts
var shortIndexes = searchIndex.shortIndexes;
var fullTexts = searchIndex.fullTexts;
query = cleanSearchText(query)
query = cleanSearchText(query);
var queryWords = query.split(' ')
var queryWords = query.split(' ');
var foundObjs = false,
newFoundObjs, i
var j, searchText
var found
newFoundObjs, i;
var j, searchText;
var found;
for(i = 0; i < queryWords.length; i++) {
newFoundObjs = shortIndexes[queryWords[i].substr(0, 3)]
newFoundObjs = shortIndexes[queryWords[i].substr(0, 3)];
if(!newFoundObjs) {
foundObjs = []
break
foundObjs = [];
break;
}
if(foundObjs === false || foundObjs.length > newFoundObjs.length) {
foundObjs = newFoundObjs
foundObjs = newFoundObjs;
}
}
newFoundObjs = {}
newFoundObjs = {};
for(j = 0; j < foundObjs.length; j++) {
found = true
searchText = fullTexts[foundObjs[j]]
found = true;
searchText = fullTexts[foundObjs[j]];
for(i = 0; i < queryWords.length; i++) {
if(searchText.indexOf(queryWords[i]) == -1) {
found = false
break
found = false;
break;
}
}
if(found) {
newFoundObjs[foundObjs[j]] = true
newFoundObjs[foundObjs[j]] = true;
}
}
return newFoundObjs
return newFoundObjs;
}
let SearchIndexManager = {

8
src/pages/pageSignUp.ts

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

30
src/scss/partials/_chat.scss

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

58
src/scss/partials/_chatBubble.scss

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

14
src/scss/partials/_chatlist.scss

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

73
src/scss/partials/_leftSidebar.scss

@ -101,7 +101,7 @@ @@ -101,7 +101,7 @@
margin: 0;
}
.user-avatar {
.dialog-avatar {
width: 54px;
height: 54px;
}
@ -155,7 +155,7 @@ @@ -155,7 +155,7 @@
}
}
.new-channel-container, .new-group-container {
.new-channel-container, .new-group-container, .edit-profile-container {
.sidebar-content {
flex-direction: column;
}
@ -178,9 +178,76 @@ @@ -178,9 +178,76 @@
}
.caption {
font-size: 14px;
font-size: 0.875rem;
margin-top: 14px;
margin-left: 23px;
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%;
}
}

8
src/scss/partials/_mediaViewer.scss

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

2
src/scss/partials/_rightSIdebar.scss

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

17
src/scss/partials/_selector.scss

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

2
src/scss/partials/_sidebar.scss

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

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

@ -0,0 +1,48 @@ @@ -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;
}
}

40
src/scss/style.scss

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

1
tsconfig.json

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

Loading…
Cancel
Save